├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .jscs.js
├── .travis.yml
├── CONTRIBUTORS.md
├── LICENSE.txt
├── README.md
├── appveyor.yml
├── bench
├── README.md
├── enb.js
├── fixtures
│ ├── .bowerrc
│ ├── bower.json
│ ├── index.js
│ └── levels
│ │ ├── flat.level
│ │ ├── block.txt
│ │ ├── block__elem.txt
│ │ ├── block__elem_bool-mod.txt
│ │ └── block_bool-mod.txt
│ │ └── nested.level
│ │ └── block
│ │ ├── __elem
│ │ ├── _bool-mod
│ │ │ └── block__elem_bool-mod.txt
│ │ └── block__elem.txt
│ │ ├── _bool-mod
│ │ └── block_bool-mod.txt
│ │ └── block.txt
├── package.json
├── run.js
└── scan-level.js
├── lib
├── bem-file.js
├── index.js
└── walkers
│ ├── flat.js
│ ├── index.js
│ └── nested.js
├── package.json
└── test
├── core
├── bem-file.test.js
├── defaults.test.js
└── walkers.test.js
├── naming
└── naming.test.js
└── schemes
├── flat
├── detect.test.js
├── error.test.js
├── ignore.test.js
├── levels.test.js
└── techs.test.js
├── multi.test.js
└── nested
├── detect.test.js
├── error.test.js
├── ignore.test.js
├── levels.test.js
└── techs.test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | trim_trailing_whitespace = true
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 |
10 | [*.{json,*rc,yml}]
11 | indent_size = 2
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .nyc_output
2 | node_modules
3 | coverage
4 | bench/fixtures/libs/**
5 | bench/node_modules/**
6 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | env:
2 | es6: true
3 | node: true
4 | mocha: true
5 |
6 | extends: pedant
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .nyc_output
2 | node_modules
3 | coverage
4 | bench/fixtures/libs
5 |
--------------------------------------------------------------------------------
/.jscs.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | excludeFiles: [
3 | 'node_modules/**',
4 | 'coverage/**',
5 | 'bench/fixtures/libs/**',
6 | 'bench/node_modules/**'
7 | ],
8 | requireSpaceAfterKeywords: ['if', 'else', 'for', 'while', 'do', 'switch', 'return', 'try', 'catch'],
9 | requireSpaceBeforeBlockStatements: true,
10 | requireSpacesInConditionalExpression: true,
11 | requireSpacesInFunction: {
12 | beforeOpeningCurlyBrace: true
13 | },
14 | requireSpacesInAnonymousFunctionExpression: {
15 | beforeOpeningRoundBrace: true
16 | },
17 | disallowSpacesInNamedFunctionExpression: {
18 | beforeOpeningRoundBrace: true
19 | },
20 | requireBlocksOnNewline: 1,
21 | disallowPaddingNewlinesInBlocks: true,
22 | disallowSpacesInsideArrayBrackets: 'nested',
23 | disallowSpacesInsideParentheses: true,
24 | requireSpacesInsideObjectBrackets: 'all',
25 | disallowSpaceAfterObjectKeys: true,
26 | requireCommaBeforeLineBreak: true,
27 | disallowSpaceAfterPrefixUnaryOperators: true,
28 | disallowSpaceBeforePostfixUnaryOperators: true,
29 | requireSpaceBeforeBinaryOperators: true,
30 | requireSpaceAfterBinaryOperators: true,
31 | requireCamelCaseOrUpperCaseIdentifiers: true,
32 | disallowKeywords: ['with'],
33 | disallowMultipleLineStrings: true,
34 | disallowMultipleLineBreaks: true,
35 | validateLineBreaks: 'LF',
36 | validateQuoteMarks: {
37 | mark: '\'',
38 | escape: true
39 | },
40 | disallowMixedSpacesAndTabs: true,
41 | disallowTrailingWhitespace: true,
42 | disallowKeywordsOnNewLine: ['else', 'catch'],
43 | requireLineFeedAtFileEnd: true,
44 | maximumLineLength: 120,
45 | requireCapitalizedConstructors: true,
46 | safeContextKeyword: ['_this'],
47 | disallowYodaConditions: true,
48 | requireSpaceAfterLineComment: true,
49 | disallowNewlineBeforeBlockStatements: true
50 | };
51 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 |
5 | matrix:
6 | include:
7 | - node_js: "4"
8 | env: COVERALLS=1
9 | - node_js: "6"
10 |
11 | after_success:
12 | - if [ "x$COVERALLS" = "x1" ]; then npm run coveralls; fi
13 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | Contributors
2 | ============
3 |
4 | The list of contributors is available at https://github.com/bem/bem-walk/graphs/contributors.
5 |
6 | You may also get it with `git log --pretty=format:"%an <%ae>" | sort -u`.
7 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | © YANDEX LLC, 2014
2 |
3 | The Source Code called `bem-walk` available at https://github.com/bem-sdk/bem-walk is subject to the terms of the Mozilla Public License, v. 2.0 (hereinafter - MPL). The text of MPL is the following:
4 |
5 | Mozilla Public License, version 2.0
6 |
7 | 1. Definitions
8 |
9 | 1.1. "Contributor"
10 |
11 | means each individual or legal entity that creates, contributes to the
12 | creation of, or owns Covered Software.
13 |
14 | 1.2. "Contributor Version"
15 |
16 | means the combination of the Contributions of others (if any) used by a
17 | Contributor and that particular Contributor's Contribution.
18 |
19 | 1.3. "Contribution"
20 |
21 | means Covered Software of a particular Contributor.
22 |
23 | 1.4. "Covered Software"
24 |
25 | means Source Code Form to which the initial Contributor has attached the
26 | notice in Exhibit A, the Executable Form of such Source Code Form, and
27 | Modifications of such Source Code Form, in each case including portions
28 | thereof.
29 |
30 | 1.5. "Incompatible With Secondary Licenses"
31 | means
32 |
33 | a. that the initial Contributor has attached the notice described in
34 | Exhibit B to the Covered Software; or
35 |
36 | b. that the Covered Software was made available under the terms of
37 | version 1.1 or earlier of the License, but not also under the terms of
38 | a Secondary License.
39 |
40 | 1.6. "Executable Form"
41 |
42 | means any form of the work other than Source Code Form.
43 |
44 | 1.7. "Larger Work"
45 |
46 | means a work that combines Covered Software with other material, in a
47 | separate file or files, that is not Covered Software.
48 |
49 | 1.8. "License"
50 |
51 | means this document.
52 |
53 | 1.9. "Licensable"
54 |
55 | means having the right to grant, to the maximum extent possible, whether
56 | at the time of the initial grant or subsequently, any and all of the
57 | rights conveyed by this License.
58 |
59 | 1.10. "Modifications"
60 |
61 | means any of the following:
62 |
63 | a. any file in Source Code Form that results from an addition to,
64 | deletion from, or modification of the contents of Covered Software; or
65 |
66 | b. any new file in Source Code Form that contains any Covered Software.
67 |
68 | 1.11. "Patent Claims" of a Contributor
69 |
70 | means any patent claim(s), including without limitation, method,
71 | process, and apparatus claims, in any patent Licensable by such
72 | Contributor that would be infringed, but for the grant of the License,
73 | by the making, using, selling, offering for sale, having made, import,
74 | or transfer of either its Contributions or its Contributor Version.
75 |
76 | 1.12. "Secondary License"
77 |
78 | means either the GNU General Public License, Version 2.0, the GNU Lesser
79 | General Public License, Version 2.1, the GNU Affero General Public
80 | License, Version 3.0, or any later versions of those licenses.
81 |
82 | 1.13. "Source Code Form"
83 |
84 | means the form of the work preferred for making modifications.
85 |
86 | 1.14. "You" (or "Your")
87 |
88 | means an individual or a legal entity exercising rights under this
89 | License. For legal entities, "You" includes any entity that controls, is
90 | controlled by, or is under common control with You. For purposes of this
91 | definition, "control" means (a) the power, direct or indirect, to cause
92 | the direction or management of such entity, whether by contract or
93 | otherwise, or (b) ownership of more than fifty percent (50%) of the
94 | outstanding shares or beneficial ownership of such entity.
95 |
96 |
97 | 2. License Grants and Conditions
98 |
99 | 2.1. Grants
100 |
101 | Each Contributor hereby grants You a world-wide, royalty-free,
102 | non-exclusive license:
103 |
104 | a. under intellectual property rights (other than patent or trademark)
105 | Licensable by such Contributor to use, reproduce, make available,
106 | modify, display, perform, distribute, and otherwise exploit its
107 | Contributions, either on an unmodified basis, with Modifications, or
108 | as part of a Larger Work; and
109 |
110 | b. under Patent Claims of such Contributor to make, use, sell, offer for
111 | sale, have made, import, and otherwise transfer either its
112 | Contributions or its Contributor Version.
113 |
114 | 2.2. Effective Date
115 |
116 | The licenses granted in Section 2.1 with respect to any Contribution
117 | become effective for each Contribution on the date the Contributor first
118 | distributes such Contribution.
119 |
120 | 2.3. Limitations on Grant Scope
121 |
122 | The licenses granted in this Section 2 are the only rights granted under
123 | this License. No additional rights or licenses will be implied from the
124 | distribution or licensing of Covered Software under this License.
125 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
126 | Contributor:
127 |
128 | a. for any code that a Contributor has removed from Covered Software; or
129 |
130 | b. for infringements caused by: (i) Your and any other third party's
131 | modifications of Covered Software, or (ii) the combination of its
132 | Contributions with other software (except as part of its Contributor
133 | Version); or
134 |
135 | c. under Patent Claims infringed by Covered Software in the absence of
136 | its Contributions.
137 |
138 | This License does not grant any rights in the trademarks, service marks,
139 | or logos of any Contributor (except as may be necessary to comply with
140 | the notice requirements in Section 3.4).
141 |
142 | 2.4. Subsequent Licenses
143 |
144 | No Contributor makes additional grants as a result of Your choice to
145 | distribute the Covered Software under a subsequent version of this
146 | License (see Section 10.2) or under the terms of a Secondary License (if
147 | permitted under the terms of Section 3.3).
148 |
149 | 2.5. Representation
150 |
151 | Each Contributor represents that the Contributor believes its
152 | Contributions are its original creation(s) or it has sufficient rights to
153 | grant the rights to its Contributions conveyed by this License.
154 |
155 | 2.6. Fair Use
156 |
157 | This License is not intended to limit any rights You have under
158 | applicable copyright doctrines of fair use, fair dealing, or other
159 | equivalents.
160 |
161 | 2.7. Conditions
162 |
163 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
164 | Section 2.1.
165 |
166 |
167 | 3. Responsibilities
168 |
169 | 3.1. Distribution of Source Form
170 |
171 | All distribution of Covered Software in Source Code Form, including any
172 | Modifications that You create or to which You contribute, must be under
173 | the terms of this License. You must inform recipients that the Source
174 | Code Form of the Covered Software is governed by the terms of this
175 | License, and how they can obtain a copy of this License. You may not
176 | attempt to alter or restrict the recipients' rights in the Source Code
177 | Form.
178 |
179 | 3.2. Distribution of Executable Form
180 |
181 | If You distribute Covered Software in Executable Form then:
182 |
183 | a. such Covered Software must also be made available in Source Code Form,
184 | as described in Section 3.1, and You must inform recipients of the
185 | Executable Form how they can obtain a copy of such Source Code Form by
186 | reasonable means in a timely manner, at a charge no more than the cost
187 | of distribution to the recipient; and
188 |
189 | b. You may distribute such Executable Form under the terms of this
190 | License, or sublicense it under different terms, provided that the
191 | license for the Executable Form does not attempt to limit or alter the
192 | recipients' rights in the Source Code Form under this License.
193 |
194 | 3.3. Distribution of a Larger Work
195 |
196 | You may create and distribute a Larger Work under terms of Your choice,
197 | provided that You also comply with the requirements of this License for
198 | the Covered Software. If the Larger Work is a combination of Covered
199 | Software with a work governed by one or more Secondary Licenses, and the
200 | Covered Software is not Incompatible With Secondary Licenses, this
201 | License permits You to additionally distribute such Covered Software
202 | under the terms of such Secondary License(s), so that the recipient of
203 | the Larger Work may, at their option, further distribute the Covered
204 | Software under the terms of either this License or such Secondary
205 | License(s).
206 |
207 | 3.4. Notices
208 |
209 | You may not remove or alter the substance of any license notices
210 | (including copyright notices, patent notices, disclaimers of warranty, or
211 | limitations of liability) contained within the Source Code Form of the
212 | Covered Software, except that You may alter any license notices to the
213 | extent required to remedy known factual inaccuracies.
214 |
215 | 3.5. Application of Additional Terms
216 |
217 | You may choose to offer, and to charge a fee for, warranty, support,
218 | indemnity or liability obligations to one or more recipients of Covered
219 | Software. However, You may do so only on Your own behalf, and not on
220 | behalf of any Contributor. You must make it absolutely clear that any
221 | such warranty, support, indemnity, or liability obligation is offered by
222 | You alone, and You hereby agree to indemnify every Contributor for any
223 | liability incurred by such Contributor as a result of warranty, support,
224 | indemnity or liability terms You offer. You may include additional
225 | disclaimers of warranty and limitations of liability specific to any
226 | jurisdiction.
227 |
228 | 4. Inability to Comply Due to Statute or Regulation
229 |
230 | If it is impossible for You to comply with any of the terms of this License
231 | with respect to some or all of the Covered Software due to statute,
232 | judicial order, or regulation then You must: (a) comply with the terms of
233 | this License to the maximum extent possible; and (b) describe the
234 | limitations and the code they affect. Such description must be placed in a
235 | text file included with all distributions of the Covered Software under
236 | this License. Except to the extent prohibited by statute or regulation,
237 | such description must be sufficiently detailed for a recipient of ordinary
238 | skill to be able to understand it.
239 |
240 | 5. Termination
241 |
242 | 5.1. The rights granted under this License will terminate automatically if You
243 | fail to comply with any of its terms. However, if You become compliant,
244 | then the rights granted under this License from a particular Contributor
245 | are reinstated (a) provisionally, unless and until such Contributor
246 | explicitly and finally terminates Your grants, and (b) on an ongoing
247 | basis, if such Contributor fails to notify You of the non-compliance by
248 | some reasonable means prior to 60 days after You have come back into
249 | compliance. Moreover, Your grants from a particular Contributor are
250 | reinstated on an ongoing basis if such Contributor notifies You of the
251 | non-compliance by some reasonable means, this is the first time You have
252 | received notice of non-compliance with this License from such
253 | Contributor, and You become compliant prior to 30 days after Your receipt
254 | of the notice.
255 |
256 | 5.2. If You initiate litigation against any entity by asserting a patent
257 | infringement claim (excluding declaratory judgment actions,
258 | counter-claims, and cross-claims) alleging that a Contributor Version
259 | directly or indirectly infringes any patent, then the rights granted to
260 | You by any and all Contributors for the Covered Software under Section
261 | 2.1 of this License shall terminate.
262 |
263 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
264 | license agreements (excluding distributors and resellers) which have been
265 | validly granted by You or Your distributors under this License prior to
266 | termination shall survive termination.
267 |
268 | 6. Disclaimer of Warranty
269 |
270 | Covered Software is provided under this License on an "as is" basis,
271 | without warranty of any kind, either expressed, implied, or statutory,
272 | including, without limitation, warranties that the Covered Software is free
273 | of defects, merchantable, fit for a particular purpose or non-infringing.
274 | The entire risk as to the quality and performance of the Covered Software
275 | is with You. Should any Covered Software prove defective in any respect,
276 | You (not any Contributor) assume the cost of any necessary servicing,
277 | repair, or correction. This disclaimer of warranty constitutes an essential
278 | part of this License. No use of any Covered Software is authorized under
279 | this License except under this disclaimer.
280 |
281 | 7. Limitation of Liability
282 |
283 | Under no circumstances and under no legal theory, whether tort (including
284 | negligence), contract, or otherwise, shall any Contributor, or anyone who
285 | distributes Covered Software as permitted above, be liable to You for any
286 | direct, indirect, special, incidental, or consequential damages of any
287 | character including, without limitation, damages for lost profits, loss of
288 | goodwill, work stoppage, computer failure or malfunction, or any and all
289 | other commercial damages or losses, even if such party shall have been
290 | informed of the possibility of such damages. This limitation of liability
291 | shall not apply to liability for death or personal injury resulting from
292 | such party's negligence to the extent applicable law prohibits such
293 | limitation. Some jurisdictions do not allow the exclusion or limitation of
294 | incidental or consequential damages, so this exclusion and limitation may
295 | not apply to You.
296 |
297 | 8. Litigation
298 |
299 | Any litigation relating to this License may be brought only in the courts
300 | of a jurisdiction where the defendant maintains its principal place of
301 | business and such litigation shall be governed by laws of that
302 | jurisdiction, without reference to its conflict-of-law provisions. Nothing
303 | in this Section shall prevent a party's ability to bring cross-claims or
304 | counter-claims.
305 |
306 | 9. Miscellaneous
307 |
308 | This License represents the complete agreement concerning the subject
309 | matter hereof. If any provision of this License is held to be
310 | unenforceable, such provision shall be reformed only to the extent
311 | necessary to make it enforceable. Any law or regulation which provides that
312 | the language of a contract shall be construed against the drafter shall not
313 | be used to construe this License against a Contributor.
314 |
315 |
316 | 10. Versions of the License
317 |
318 | 10.1. New Versions
319 |
320 | Mozilla Foundation is the license steward. Except as provided in Section
321 | 10.3, no one other than the license steward has the right to modify or
322 | publish new versions of this License. Each version will be given a
323 | distinguishing version number.
324 |
325 | 10.2. Effect of New Versions
326 |
327 | You may distribute the Covered Software under the terms of the version
328 | of the License under which You originally received the Covered Software,
329 | or under the terms of any subsequent version published by the license
330 | steward.
331 |
332 | 10.3. Modified Versions
333 |
334 | If you create software not governed by this License, and you want to
335 | create a new license for such software, you may create and use a
336 | modified version of this License if you rename the license and remove
337 | any references to the name of the license steward (except to note that
338 | such modified license differs from this License).
339 |
340 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
341 | Licenses If You choose to distribute Source Code Form that is
342 | Incompatible With Secondary Licenses under the terms of this version of
343 | the License, the notice described in Exhibit B of this License must be
344 | attached.
345 |
346 | Exhibit A - Source Code Form License Notice
347 |
348 | This Source Code Form is subject to the
349 | terms of the Mozilla Public License, v.
350 | 2.0. If a copy of the MPL was not
351 | distributed with this file, You can
352 | obtain one at
353 | http://mozilla.org/MPL/2.0/.
354 |
355 | If it is not possible or desirable to put the notice in a particular file,
356 | then You may include the notice in a location (such as a LICENSE file in a
357 | relevant directory) where a recipient would be likely to look for such a
358 | notice.
359 |
360 | You may add additional accurate notices of copyright ownership.
361 |
362 | Exhibit B - "Incompatible With Secondary Licenses" Notice
363 |
364 | This Source Code Form is "Incompatible
365 | With Secondary Licenses", as defined by
366 | the Mozilla Public License, v. 2.0.
367 |
368 |
369 | A copy of the MPL is also available at http://mozilla.org/MPL/2.0/.
370 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bem-walk
2 |
3 | Tool for traversing a BEM project's file system.
4 |
5 | [![NPM Status][npm-img]][npm]
6 | [![Travis Status][test-img]][travis]
7 | [![Windows Status][appveyor-img]][appveyor]
8 | [![Coverage Status][coverage-img]][coveralls]
9 | [![Dependency Status][david-img]][david]
10 |
11 | [npm]: https://www.npmjs.org/package/@bem/walk
12 | [npm-img]: https://img.shields.io/npm/v/@bem/walk.svg
13 |
14 | [travis]: https://travis-ci.org/bem-sdk/bem-walk
15 | [test-img]: https://img.shields.io/travis/bem-sdk/bem-walk.svg?label=tests
16 |
17 | [appveyor]: https://ci.appveyor.com/project/blond/bem-walk
18 | [appveyor-img]: http://img.shields.io/appveyor/ci/blond/bem-walk.svg?style=flat&label=windows
19 |
20 | [coveralls]: https://coveralls.io/r/bem-sdk/bem-walk
21 | [coverage-img]: https://img.shields.io/coveralls/bem-sdk/bem-walk.svg
22 |
23 | [david]: https://david-dm.org/bem-sdk/bem-walk
24 | [david-img]: http://img.shields.io/david/bem-sdk/bem-walk.svg?style=flat
25 |
26 | It returns the following information about found files:
27 |
28 | * The type of BEM entity ([block](https://en.bem.info/methodology/key-concepts/#block), [element](https://en.bem.info/methodology/key-concepts/#element) or [modifier]( https://en.bem.info/methodology/key-concepts/#modifier)).
29 | * The [implementation technology]( https://en.bem.info/methodology/key-concepts/#implementation-technology).
30 | * The location in the file system.
31 |
32 | ## Quick start
33 |
34 | **Note** To use `bem-walk`, you must install Node.js 4.0+.
35 |
36 | ### 1. Install bem-walk
37 |
38 | ```
39 | $ npm install --save @bem/walk
40 | ```
41 |
42 | ### 2. Enable bem-walk
43 |
44 | Create a JavaScript file with any name and add the following string:
45 |
46 | ```js
47 | const walk = require('@bem/walk');
48 | ```
49 |
50 | **Note** You will use this JavaScript file for all the following steps.
51 |
52 | ### 3. Define file system levels
53 |
54 | Define the project's file system levels in the `config` object.
55 |
56 | ```js
57 | const config = {
58 | // project levels
59 | levels: {
60 | 'lib/bem-core/common.blocks': {
61 | // `naming` — file naming scheme
62 | naming: 'two-dashes'
63 | },
64 | 'common.blocks': {
65 | // `scheme` — file system scheme
66 | scheme: 'nested'
67 | }
68 | }
69 | };
70 | ```
71 |
72 | Specify either the file naming scheme or the file system type for each level. This lets you get information about BEM entities [using their names]( https://en.bem.info/toolbox/sdk/bem-naming/#string-representation) or using the names of files and directories.
73 |
74 | The table shows the possible values that can be set for each of the schemes.
75 |
76 | | Key | Scheme | Default value | Possible values |
77 | |----|------|-----|----------|
78 | | `naming` | File naming.|`origin`| `origin`, `two-dashes`|
79 | | `scheme` | File system.|`nested`|`nested`, `flat`|
80 |
81 | More information:
82 | * [ @bem/naming]( https://en.bem.info/toolbox/sdk/bem-naming/)
83 | * [ bem-fs-scheme]( https://en.bem.info/toolbox/sdk/bem-fs-scheme/)
84 |
85 | **Note** Instead of defining the project's levels manually, use the [`bem-config`]( https://en.bem.info/toolbox/sdk/bem-config/) tool.
86 |
87 | ```js
88 | const config = require('bem-config')();
89 | const levelMap = config.levelMapSync();
90 | const stream = walk(levels, levelMap);
91 | ```
92 |
93 | ### 4. Define the paths to traverse
94 |
95 | Specify the paths to walk in the `levels` object.
96 |
97 | Path options:
98 |
99 | * Relative to the root directory.
100 |
101 | ```js
102 | const levels = [
103 | 'libs/bem-core/common.blocks',
104 | 'common.blocks'
105 | ];
106 | ```
107 |
108 | * Absolute.
109 |
110 | ```js
111 | const levels = [
112 | '/path/to/project/lib/bem-core/common.blocks',
113 | '/path/to/project/common.blocks'
114 | ];
115 | ```
116 |
117 | ### 5. Get information about found files
118 |
119 | Pass the walk() method the `levels` and `config` objects.
120 |
121 | Streaming is used for getting data about found files. When a portion of data is received, the `data` event is generated and [information about the found file](#output-data) is added to the `files` array. If an error occurs, `bem-walk` stops processing the request and returns a response containing the error ID and description. The `end` event occurs when all the data has been received from the stream.
122 |
123 | ```js
124 | const files = [];
125 |
126 | const stream = walk(levels, config);
127 | // adds information about a found file to the end of the "files" array
128 | stream.on('data', file => files.push(file));
129 |
130 | stream.on('error', console.error);
131 |
132 | stream.on('end', () => console.log(files));
133 | ```
134 | ### Complete code sample
135 |
136 | When all these steps have been completed, the full code of the JavaScript file should look like this:
137 |
138 | ```js
139 | const walk = require('@bem/walk');
140 | const config = require('bem-config')();
141 | const levels = [
142 | 'libs/bem-components/common.blocks',
143 | 'common.blocks'
144 | ];
145 | const files = [];
146 |
147 | const stream = walk(levels, {
148 | levels: config.levelMapSync()
149 | })
150 | .on('data', file => files.push(file))
151 | .on('error', console.error)
152 | .on('end', () => console.log(files));
153 | ```
154 |
155 | **Note** This sample uses the `bem-config` package.
156 |
157 | ## API
158 |
159 | ### walk method
160 |
161 | ` walk(levels, config);`
162 |
163 | #### Description
164 |
165 | Traverses the directories described in the `levels` parameter and returns `stream.Readable`.
166 |
167 | #### Input parameters
168 |
169 | Requirements for the search are defined in the `levels` and `config` parameters.
170 |
171 | | Parameter | Type | Description |
172 | |----------|-----|----------|
173 | |**levels**|`string[]`|Paths to traverse|
174 | |**config**|`object`|Project levels|
175 |
176 | #### Output data
177 |
178 | Readable stream (`stream.Readable`) that has the following events:
179 |
180 | | Event | Description |
181 | |----------|-----|
182 | |'data'|Returns a JavaScript object with information about a found file.
The example below shows a JSON interface with elements that are in the response for the `walk` method. Objects and keys have sample values.
**Example**
{
"cell": {
"entity": { "block": "page" },
"layer": "libs/bem-core/desktop.blocks",
"tech": "bemhtml"
},
"path": "libs/bem-core/desktop.blocks/page/page.bemhtml.js"
}
`cell` — BEM cell instance.
`entity` — BEM entity name instance.
`layer` — Directory path.
`tech` — Implementation technology.
`path` — Relative path to the file.|
183 | | 'error' | Generated if an error occurred while traversing the levels. Returns an object with the error description.|
184 | | 'end' | Generated when `bem-walk` finishes traversing the levels defined in the `levels` object. |
185 |
186 | ## Usage examples
187 |
188 | Typical tasks that use the resulting JavaScript objects:
189 | * [Grouping](#grouping)
190 | * [Filtering]( #filtering)
191 | * [Data transformation](#transformation)
192 |
193 | ### Grouping
194 |
195 | > Grouping found files by block name.
196 |
197 | ```js
198 | const walk = require('@bem/walk');
199 | const config = require('bem-config')();
200 | const util = require('util');
201 | const levels = [
202 | 'libs/bem-components/common.blocks',
203 | 'common.blocks'
204 | ];
205 | const groups = {};
206 |
207 | const stream = walk(levels, {
208 | levels: config.levelMapSync()
209 | })
210 | .on('data', file => {
211 | // Getting the block name for a found file.
212 | const block = file.entity.block;
213 |
214 | // Adding information about the found file.
215 | (groups[block] = []).push(file);
216 | })
217 | .on('error', console.error)
218 | .on('end', () => console.log(util.inspect(groups, {
219 | depth: null
220 | })));
221 |
222 | /*
223 | { button:
224 | [ BemFile {
225 | cell: BemCell {
226 | entity: BemEntityName { block: 'button', mod: { name: 'togglable', val: 'radio' } },
227 | tech: 'spec.js',
228 | layer: 'libs/bem-components/common.blocks'
229 | },
230 | path: 'libs/bem-components/common.blocks/button/_togglable/
231 | button_togglable_radio.spec.js' } ] },
232 | ...
233 | }
234 | */
235 | ```
236 |
237 | ### Filtering
238 |
239 | > Finding files for the `popup` block.
240 |
241 | ```js
242 | const walk = require('@bem/walk');
243 | const config = require('bem-config')();
244 | const levels = [
245 | 'libs/bem-components/common.blocks',
246 | 'common.blocks'
247 | ];
248 | const files = [];
249 |
250 | const stream = walk(levels, {
251 | levels: config.levelMapSync()
252 | })
253 | .on('data', file => {
254 | // Getting the block name for a found file.
255 | const block = file.entity.block;
256 |
257 | // Adding information about the found file.
258 | if (block == 'popup') {
259 | files.push(file);
260 | }
261 | })
262 | .on('error', console.error)
263 | .on('end', () => console.log(files));
264 |
265 | /*
266 | [BemFile {
267 | cell: BemCell {
268 | entity: BemEntityName { block: 'popup', mod: { name: 'target', val: true } },
269 | tech: 'js',
270 | layer: 'libs/bem-components/common.blocks'
271 | },
272 | path: 'libs/bem-components/common.blocks/popup/_target/popup_target.js'
273 | },
274 | ...
275 | ]
276 | */
277 | ```
278 |
279 | ### Transformation
280 |
281 | > Finding BEM files, reading the contents, and creating the new `source` property.
282 |
283 | ```js
284 | const fs = require('fs');
285 | const walk = require('@bem/walk');
286 | const config = require('bem-config')();
287 | const stringify = require('JSONStream').stringify;
288 | const through2 = require('through2');
289 | const levels = [
290 | 'libs/bem-components/common.blocks',
291 | 'common.blocks'
292 | ];
293 |
294 | const stream = walk(levels, {
295 | levels: config.levelMapSync()
296 | })
297 | .pipe(through2.obj(function(file, enc, callback) {
298 | try {
299 | // Certain technologies (for example, `i18n`) might be directories instead of files.
300 | if (fs.statSync(file.path).isFile()) {
301 | // Writing the file content to the `source` field.
302 | file.source = fs.readFileSync(file.path, 'utf-8');
303 | this.push(file);
304 | }
305 |
306 | } catch (err) {
307 | callback(err);
308 | }
309 | }))
310 | .pipe(stringify())
311 | .pipe(process.stdout);
312 |
313 | /*
314 | [{"cell":{
315 | "entity":{"block":"search","elem":"header"},
316 | "tech":"css",
317 | "layer":"common.blocks"
318 | },
319 | "path":"common.blocks/search/__header/search__header.css",
320 | "source":".search__header {\n\tdisplay: block;\n\tfont-size: 20px;\n\tcolor:
321 | rgba(0,0,0,0.84);\n\tmargin: 0;\n\tpadding: 0 0 16px;\n\n}\n\n"},
322 | ...
323 | ]
324 | */
325 | ```
326 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: "{build}"
2 |
3 | environment:
4 | nodejs_version: "4"
5 |
6 | install:
7 | - ps: Install-Product node $env:nodejs_version
8 | - node --version
9 | - npm --version
10 | - npm install
11 |
12 | test_script:
13 | - npm run unit-test
14 |
15 | build: off
16 |
--------------------------------------------------------------------------------
/bench/README.md:
--------------------------------------------------------------------------------
1 | benchmarks
2 | ==========
3 |
4 | **2016-05-07** Latest results, using `Node.js@4` and latest versions of modules:
5 |
6 | * `enb@0.15.0`
7 | * `scan-level@0.0.4`
8 |
9 | ```
10 | flat level
11 | bem-walk x 13,091 ops/sec ±7.22% (70 runs sampled) mean 0.08 ms
12 | bem-walk + fs.stat() x 5,169 ops/sec ±3.20% (69 runs sampled) mean 0.19 ms
13 | bem-walk + fs.statSync() x 9,522 ops/sec ±3.43% (78 runs sampled) mean 0.11 ms
14 | enb x 10,469 ops/sec ±1.89% (79 runs sampled) mean 0.10 ms
15 | scanl x 10,646 ops/sec ±6.66% (68 runs sampled) mean 0.09 ms
16 |
17 | nested level
18 | bem-walk x 3,693 ops/sec ±2.78% (71 runs sampled) mean 0.27 ms
19 | bem-walk + fs.stat() x 2,511 ops/sec ±3.92% (68 runs sampled) mean 0.40 ms
20 | bem-walk + fs.statSync() x 3,392 ops/sec ±3.21% (74 runs sampled) mean 0.29 ms
21 | enb x 6,762 ops/sec ±1.50% (82 runs sampled) mean 0.15 ms
22 | scanl x 4,355 ops/sec ±4.11% (73 runs sampled) mean 0.23 ms
23 |
24 | bem-bl
25 | bem-walk x 195 ops/sec ±4.01% (67 runs sampled) mean 5.12 ms
26 | bem-walk + fs.stat() x 106 ops/sec ±3.09% (69 runs sampled) mean 9.42 ms
27 | bem-walk + fs.statSync() x 145 ops/sec ±2.30% (75 runs sampled) mean 6.89 ms
28 | enb x 164 ops/sec ±2.74% (82 runs sampled) mean 6.12 ms
29 | scanl x 139 ops/sec ±3.18% (78 runs sampled) mean 7.22 ms
30 |
31 | bem-components
32 | bem-walk x 139 ops/sec ±3.29% (72 runs sampled) mean 7.20 ms
33 | bem-walk + fs.stat() x 60.70 ops/sec ±2.77% (69 runs sampled) mean 16.48 ms
34 | bem-walk + fs.statSync() x 85.82 ops/sec ±2.44% (77 runs sampled) mean 11.65 ms
35 | enb x 93.10 ops/sec ±1.67% (71 runs sampled) mean 10.74 ms
36 | scanl x 82.47 ops/sec ±2.79% (74 runs sampled) mean 12.13 ms
37 | ```
38 |
--------------------------------------------------------------------------------
/bench/enb.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const vow = require('vow');
4 | const Level = require('enb/lib/levels/level');
5 | const LevelPlain = require('enb/lib/levels/level-plain');
6 |
7 | module.exports = function run(levels, scheme, done) {
8 | var plain = scheme === 'flat' ? LevelPlain : null;
9 |
10 | vow.all(levels.map(function (level) {
11 | return (new Level(level, plain)).load();
12 | })).then(done, done);
13 | }
14 |
--------------------------------------------------------------------------------
/bench/fixtures/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "libs"
3 | }
4 |
--------------------------------------------------------------------------------
/bench/fixtures/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bem-walk-bench-fixtures",
3 | "private": true,
4 | "dependencies": {
5 | "bem-bl": "2.11.0",
6 | "bem-core": "2.9.0",
7 | "bem-components": "2.5.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/bench/fixtures/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const levels = path.resolve(__dirname, 'levels');
5 | const libs = path.resolve(__dirname, 'libs');
6 | const fixtures = {
7 | levels: {
8 | flat: [path.resolve(levels, 'flat.level')],
9 | nested: [path.resolve(levels, 'nested.level')]
10 | },
11 | libs: {
12 | 'bem-bl': [
13 | 'blocks-common',
14 | 'blocks-desktop',
15 | 'blocks-test',
16 | 'blocks-touch'
17 | ].map(function (level) {
18 | return path.resolve(libs, 'bem-bl', level);
19 | }),
20 | 'bem-core': [
21 | 'common.blocks',
22 | 'desktop.blocks',
23 | 'touch.blocks'
24 | ].map(function (level) {
25 | return path.resolve(libs, 'bem-core', level);
26 | }),
27 | 'bem-components': [
28 | 'common.blocks',
29 | 'desktop.blocks',
30 | 'touch.blocks',
31 | 'touch-pad.blocks',
32 | 'touch-phone.blocks'
33 | ].map(function (level) {
34 | return path.resolve(libs, 'bem-components', level);
35 | })
36 | }
37 | };
38 |
39 | fixtures.libs.o2 = [].concat(fixtures.libs['bem-core'], fixtures.libs['bem-components']);
40 |
41 | module.exports = fixtures;
42 |
--------------------------------------------------------------------------------
/bench/fixtures/levels/flat.level/block.txt:
--------------------------------------------------------------------------------
1 | block
2 |
--------------------------------------------------------------------------------
/bench/fixtures/levels/flat.level/block__elem.txt:
--------------------------------------------------------------------------------
1 | block__elem
2 |
--------------------------------------------------------------------------------
/bench/fixtures/levels/flat.level/block__elem_bool-mod.txt:
--------------------------------------------------------------------------------
1 | block__elem_bool-mod
2 |
--------------------------------------------------------------------------------
/bench/fixtures/levels/flat.level/block_bool-mod.txt:
--------------------------------------------------------------------------------
1 | block_bool-mod
2 |
--------------------------------------------------------------------------------
/bench/fixtures/levels/nested.level/block/__elem/_bool-mod/block__elem_bool-mod.txt:
--------------------------------------------------------------------------------
1 | block__elem_bool-mod
2 |
--------------------------------------------------------------------------------
/bench/fixtures/levels/nested.level/block/__elem/block__elem.txt:
--------------------------------------------------------------------------------
1 | block__elem
2 |
--------------------------------------------------------------------------------
/bench/fixtures/levels/nested.level/block/_bool-mod/block_bool-mod.txt:
--------------------------------------------------------------------------------
1 | block_bool-mod
2 |
--------------------------------------------------------------------------------
/bench/fixtures/levels/nested.level/block/block.txt:
--------------------------------------------------------------------------------
1 | block
2 |
--------------------------------------------------------------------------------
/bench/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": "git://github.com/bem/bem-walk",
3 | "description": "BEM levels introspection performance",
4 | "dependencies": {
5 | "enb": "0.15.0",
6 | "scan-level": "0.0.4",
7 | "vow": "0.4.12"
8 | },
9 | "devDependencies": {
10 | "bower": "1.7.9"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/bench/run.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const stream = require('stream');
5 |
6 | const Benchmark = require('Benchmark');
7 | const series = require('promise-map-series');
8 | const stringifyEntity = require('@bem/naming').stringify;
9 |
10 | const fixtures = require('./fixtures');
11 |
12 | const walk = require('../lib');
13 | const enb = require('./enb');
14 | const scanl = require('./scan-level');
15 |
16 | const cases = [
17 | { name: 'flat level', levels: fixtures.levels.flat, scheme: 'flat' },
18 | { name: 'nested level', levels: fixtures.levels.nested, scheme: 'nested' },
19 | { name: 'bem-bl', levels: fixtures.libs['bem-bl'], scheme: 'nested' },
20 | { name: 'bem-components', levels: fixtures.libs.o2, scheme: 'nested' }
21 | ];
22 |
23 | series(cases, item => {
24 | return new Promise(resolve => {
25 | const suite = new Benchmark.Suite(item.name);
26 |
27 | suite
28 | .add(' bem-walk', deferred => {
29 | walk(item.levels, { defaults: { scheme: item.scheme } })
30 | .resume().on('end', () => deferred.resolve());
31 | }, { defer: true })
32 | .add(' bem-walk + fs.stat()', deferred => {
33 | const data = {};
34 |
35 | walk(item.levels, { defaults: { scheme: item.scheme } })
36 | .pipe(new stream.Writable({
37 | objectMode: true,
38 | write: function (file, encoding, callback) {
39 | fs.stat(file.path, (err, stats) => {
40 | if (err) {
41 | return callback(err);
42 | }
43 |
44 | const id = stringifyEntity(file.entity);
45 |
46 | file.stat = stats;
47 | data[id] = file;
48 |
49 | callback();
50 | });
51 | }
52 | }))
53 | .on('error', err => deferred.reject(err))
54 | .on('finish', () => deferred.resolve());
55 | }, { defer: true })
56 | .add('bem-walk + fs.statSync()', deferred => {
57 | const data = {};
58 |
59 | walk(item.levels, { defaults: { scheme: item.scheme } })
60 | .on('data', file => {
61 | const id = stringifyEntity(file.entity);
62 | const stats = fs.statSync(file.path);
63 |
64 | file.stat = stats;
65 | data[id] = file;
66 | })
67 | .resume()
68 | .on('error', err => deferred.reject(err))
69 | .on('end', () => deferred.resolve());
70 | }, { defer: true })
71 | .add(' enb', deferred => {
72 | enb(item.levels, item.scheme, () => deferred.resolve());
73 | }, { defer: true })
74 | .add(' scanl', deferred => {
75 | scanl(item.levels, item.scheme, () => deferred.resolve());
76 | }, { defer: true })
77 | .on('start', () => {
78 | console.log(' ' + item.name)
79 | })
80 | .on('cycle', event => {
81 | const target = event.target;
82 | const mean = target.stats.mean * 1000;
83 |
84 | console.log(`${target} mean ${mean.toFixed(2)} ms`);
85 | })
86 | .on('complete', () => {
87 | console.log('');
88 | resolve();
89 | })
90 | .run();
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/bench/scan-level.js:
--------------------------------------------------------------------------------
1 | var scan = require('scan-level');
2 |
3 | module.exports = function run(levels, scheme, done) {
4 | var opts = {};
5 |
6 | if (scheme === 'flat') {
7 | opts.scanner = scanSimple;
8 | }
9 |
10 | var n = 0,
11 | l = levels.length;
12 |
13 | if (l === 0) {
14 | return done();
15 | } else {
16 | for (var i = 0; i < l; ++i) {
17 | scan(levels[i], opts, scancb);
18 | }
19 | }
20 |
21 | function scancb(err) {
22 | if (err) {
23 | done(err);
24 | n = l + 1;
25 | }
26 |
27 | ++n === l && done();
28 | }
29 | }
30 |
31 | function scanSimple(block, elem, items, next) {
32 | var file = block.file,
33 | underscore = file.indexOf('_'),
34 | dot = file.indexOf('.'),
35 | bk = file.substr(0, underscore !== -1 ? underscore : dot),
36 | el;
37 |
38 | if (!bk || dot < underscore) { return next(); }
39 |
40 | var suffix = file.substr(dot),
41 | item = {
42 | block: bk,
43 | suffix: suffix
44 | };
45 |
46 | file = file.substring(0, dot);
47 |
48 | if (underscore === -1) {
49 | items.push(block, item);
50 | next();
51 | return;
52 | }
53 |
54 | file = file.substr(underscore + 1);
55 | underscore = file.indexOf('_', 1);
56 |
57 | if (file[0] === '_') {
58 | el = file.substring(1, underscore !== -1 ? underscore : file.length);
59 | item.elem = el;
60 |
61 | // block__elem
62 | if (underscore === -1) {
63 | items.push(block, item);
64 | next();
65 | return;
66 | }
67 |
68 | file = file.substr(underscore + 1);
69 | }
70 |
71 | underscore = file.indexOf('_');
72 | item.mod = file.substring(0, underscore !== -1 ? underscore : file.length);
73 |
74 | // block_mod or block__elem_mod
75 | if (underscore === -1) {
76 | items.push(block, item);
77 | next();
78 | return;
79 | }
80 |
81 | // block_mod_val or block__elem_mod_val
82 | item.val = file.substr(underscore + 1);
83 |
84 | items.push(block, item);
85 | next();
86 | }
87 |
--------------------------------------------------------------------------------
/lib/bem-file.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = class BemFile {
4 | constructor(cell, path) {
5 | this.cell = cell;
6 | this.path = path;
7 | }
8 |
9 | get entity() {
10 | return this.cell.entity;
11 | }
12 |
13 | get tech() {
14 | return this.cell.tech;
15 | }
16 |
17 | get layer() {
18 | return this.cell.layer;
19 | }
20 |
21 | get level() {
22 | return this.cell.layer;
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Readable = require('stream').Readable;
4 | const each = require('async-each');
5 |
6 | const walkers = require('./walkers');
7 |
8 | /**
9 | * Scans levels in file system.
10 | *
11 | * If file or directory is valid BEM entity then `add` will be called with info about this file.
12 | *
13 | * @param {string[]} levels The paths to levels.
14 | * @param {object} options The options.
15 | * @param {object} options.levels The level map. A key is path to a level,
16 | * a value is an options object for this level.
17 | * @param {object} options.defaults The options for levels by default.
18 | * @param {object} options.defaults.naming Any options for `@bem/naming`.
19 | * @param {string} options.defaults.scheme The name of level scheme. Available values: `flat` or `nested`.
20 | *
21 | * @returns {module:stream.Readable} stream with info about found files and directories.
22 | */
23 | module.exports = (levels, options) => {
24 | options || (options = {});
25 |
26 | const defaults = options.defaults || {};
27 | const levelConfigs = options.levels || {};
28 | const defaultNaming = defaults.naming;
29 | const defaultWalker = (typeof defaults.scheme === 'string' ? walkers[defaults.scheme] : defaults.scheme)
30 | || walkers.nested;
31 |
32 | const output = new Readable({ objectMode: true, read: () => {} });
33 | const add = (obj) => output.push(obj);
34 |
35 | const scan = (level, callback) => {
36 | const config = levelConfigs[level];
37 | const scheme = config && config.scheme;
38 | const naming = config && config.naming || defaultNaming;
39 | const walk = typeof scheme === 'string' ? walkers[scheme] : (scheme || defaultWalker);
40 |
41 | walk({ path: level, naming: naming }, add, callback);
42 | };
43 |
44 | each(levels, scan, err => {
45 | err
46 | ? output.emit('error', err)
47 | : output.push(null);
48 | });
49 |
50 | return output;
51 | };
52 |
--------------------------------------------------------------------------------
/lib/walkers/flat.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | const bemNaming = require('@bem/naming');
7 | const BemCell = require('@bem/cell');
8 |
9 | const BemFile = require('../bem-file');
10 |
11 | /**
12 | * Plugin to scan flat levels.
13 | *
14 | * @param {object} info The info about scaned level.
15 | * @param {string} info.path The level path to scan.
16 | * @param {object|string} info.naming The naming options.
17 | * @param {function} add The function to provide info about found files.
18 | * @param {function} callback The callback function.
19 | */
20 | module.exports = (info, add, callback) => {
21 | const levelpath = info.path;
22 | // Create `bem-naming` instance for specified options.
23 | const parseEntityName = bemNaming(info.naming).parse;
24 |
25 | fs.readdir(levelpath, (err, files) => {
26 | if (err) {
27 | return callback(err);
28 | }
29 |
30 | files.forEach(basename => {
31 | const dotIndex = basename.indexOf('.');
32 |
33 | // has tech
34 | if (dotIndex > 0) {
35 | const entity = parseEntityName(basename.substring(0, dotIndex));
36 |
37 | if (entity) {
38 | const cell = new BemCell({
39 | entity: entity,
40 | tech: basename.substring(dotIndex + 1),
41 | layer: levelpath
42 | });
43 |
44 | add(new BemFile(cell, path.join(levelpath, basename)));
45 | }
46 | }
47 | });
48 |
49 | callback();
50 | });
51 | };
52 |
--------------------------------------------------------------------------------
/lib/walkers/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | nested: require('./nested'),
5 | flat: require('./flat')
6 | };
7 |
--------------------------------------------------------------------------------
/lib/walkers/nested.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | const each = require('async-each');
7 | const bemNaming = require('@bem/naming');
8 | const BemEntityName = require('@bem/entity-name');
9 | const BemCell = require('@bem/cell');
10 |
11 | const BemFile = require('../bem-file');
12 |
13 | /**
14 | * Calls specified callback for each file or directory in specified directory.
15 | *
16 | * Each item is object with the following fields:
17 | * * {string} path — the absolute path to file or directory.
18 | * * {string} basename — the name of file or directory (the last portion of a path).
19 | * * {string} stem - the name without tech name (complex extention).
20 | * * {string} tech - the complex extention for the file or directory path.
21 | *
22 | * @param {string} dirname — the path to directory.
23 | * @param {function} fn — the function that is called on each file or directory.
24 | * @param {function} callback — the callback function.
25 | */
26 | const eachDirItem = (dirname, fn, callback) => {
27 | fs.readdir(dirname, (err, filenames) => {
28 | if (err) {
29 | return callback(err);
30 | }
31 |
32 | const files = filenames.map(basename => {
33 | const dotIndex = basename.indexOf('.');
34 |
35 | // has tech
36 | if (dotIndex > 0) {
37 | return {
38 | path: path.join(dirname, basename),
39 | basename: basename,
40 | stem: basename.substring(0, dotIndex),
41 | tech: basename.substring(dotIndex + 1)
42 | };
43 | }
44 |
45 | return {
46 | path: path.join(dirname, basename),
47 | basename: basename,
48 | stem: basename
49 | };
50 | });
51 |
52 | each(files, fn, callback);
53 | });
54 | };
55 |
56 | /**
57 | * Helper to scan one level.
58 | */
59 | class LevelWalker {
60 | /**
61 | * @param {object} info The info about scaned level.
62 | * @param {string} info.path The level path to scan.
63 | * @param {object|string} info.naming The naming options.
64 | * @param {function} add The function to provide info about found files.
65 | */
66 | constructor (info, add) {
67 | this.levelpath = info.path;
68 | // Create `bem-naming` instance for specified options.
69 | this.naming = bemNaming(info.naming);
70 |
71 | this.add = add;
72 | }
73 | /**
74 | * Scans the level fully.
75 | *
76 | * @param {function} callback — the callback function.
77 | */
78 | scanLevel (callback) {
79 | eachDirItem(this.levelpath, (item, cb) => {
80 | const entity = this.naming.parse(item.stem);
81 | const type = entity && entity.type;
82 |
83 | if (!item.tech && type === 'block') {
84 | return this.scanBlockDir(item.path, item.basename, cb);
85 | }
86 |
87 | cb();
88 | }, callback);
89 | }
90 | /**
91 | * Scans the block directory.
92 | *
93 | * @param {string} dirname - the path to directory of block.
94 | * @param {string} blockname - the name of block.
95 | * @param {function} callback — the callback function.
96 | */
97 | scanBlockDir (dirname, blockname, callback) {
98 | eachDirItem(dirname, (item, cb) => {
99 | const filename = item.path;
100 | const stem = item.stem;
101 | const tech = item.tech;
102 |
103 | if (tech) {
104 | if (blockname === stem) {
105 | this.add(new BemFile(
106 | new BemCell({
107 | entity: new BemEntityName({ block: blockname }),
108 | tech: tech,
109 | layer: this.levelpath
110 | }),
111 | filename
112 | ));
113 | }
114 |
115 | return cb();
116 | }
117 |
118 | const entity = this.naming.parse(blockname + stem);
119 | const type = entity && entity.type;
120 |
121 | if (type === 'blockMod') {
122 | return this.scanBlockModDir(filename, entity, cb);
123 | }
124 |
125 | if (type === 'elem') {
126 | return this.scanElemDir(filename, entity, cb);
127 | }
128 |
129 | cb();
130 | }, callback);
131 | }
132 | /**
133 | * Scans the modifier of block directory.
134 | *
135 | * @param {string} dirname - the path to directory of modifier.
136 | * @param {object} scope - the entity object for modifier.
137 | * @param {function} callback — the callback function.
138 | */
139 | scanBlockModDir (dirname, scope, callback) {
140 | eachDirItem(dirname, (item, cb) => {
141 | const entity = this.naming.parse(item.stem);
142 | const tech = item.tech;
143 |
144 | // Find file with same modifier name.
145 | if (tech && entity && scope.block === entity.block
146 | && scope.mod.name === (entity.mod && entity.mod.name)) {
147 | this.add(new BemFile(
148 | new BemCell({
149 | entity: entity,
150 | tech: tech,
151 | layer: this.levelpath
152 | }),
153 | item.path
154 | ));
155 | }
156 |
157 | cb();
158 | }, callback);
159 | }
160 | /**
161 | * Scans the element directory.
162 | *
163 | * @param {string} dirname - the path to directory of element.
164 | * @param {object} scope - the entity object for element.
165 | * @param {function} callback — the callback function.
166 | */
167 | scanElemDir (dirname, scope, callback) {
168 | eachDirItem(dirname, (item, cb) => {
169 | const filename = item.path;
170 | const stem = item.stem;
171 | const tech = item.tech;
172 |
173 | if (tech) {
174 | // Find file with same element name.
175 | if (this.naming.stringify(scope) === stem) {
176 | const entity = this.naming.parse(stem);
177 |
178 | this.add(new BemFile(
179 | new BemCell({
180 | entity: entity,
181 | tech: tech,
182 | layer: this.levelpath
183 | }),
184 | item.path
185 | ));
186 | }
187 |
188 | return cb();
189 | }
190 |
191 | const entity = this.naming.parse(scope.block + path.basename(dirname) + stem);
192 | const type = entity && entity.type;
193 |
194 | if (type === 'elemMod') {
195 | return this.scanElemModDir(filename, entity, cb);
196 | }
197 |
198 | cb();
199 | }, callback);
200 | }
201 | /**
202 | * Scans the modifier of element directory.
203 | *
204 | * @param {string} dirname - the path to directory of modifier.
205 | * @param {object} scope - the entity object for modifier.
206 | * @param {function} callback — the callback function.
207 | */
208 | scanElemModDir (dirname, scope, callback) {
209 | eachDirItem(dirname, (item, cb) => {
210 | const entity = this.naming.parse(item.stem);
211 | const tech = item.tech;
212 |
213 | // Find file with same modifier name.
214 | if (tech && entity
215 | && scope.block === entity.block
216 | && scope.elem === entity.elem
217 | && scope.mod.name === (entity.mod && entity.mod.name)
218 | ) {
219 | this.add(new BemFile(
220 | new BemCell({
221 | entity: entity,
222 | tech: tech,
223 | layer: this.levelpath
224 | }),
225 | item.path
226 | ));
227 | }
228 |
229 | cb();
230 | }, callback);
231 | }
232 | }
233 |
234 | /**
235 | * Plugin to scan nested levels.
236 | *
237 | * @param {object} info The info about scaned level.
238 | * @param {string} info.path The level path to scan.
239 | * @param {function} add The function to provide info about found files.
240 | * @param {function} callback The callback function.
241 | */
242 | module.exports = (info, add, callback) => {
243 | const walker = new LevelWalker(info, add);
244 |
245 | walker.scanLevel(callback);
246 | };
247 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@bem/walk",
3 | "version": "1.0.0-2",
4 | "description": "Walk easily thru BEM file structure",
5 | "keywords": [
6 | "bem",
7 | "walk",
8 | "level",
9 | "scheme",
10 | "nested",
11 | "flat"
12 | ],
13 | "author": "Andrew Abramov (github.com/blond)",
14 | "license": "MPL-2.0",
15 | "repository": "bem-sdk/bem-walk",
16 | "engines": {
17 | "node": ">= 4.0"
18 | },
19 | "main": "lib/index.js",
20 | "files": [
21 | "lib/**"
22 | ],
23 | "dependencies": {
24 | "@bem/cell": "0.2.5",
25 | "@bem/entity-name": "1.5.0",
26 | "@bem/naming": "2.0.0-6",
27 | "async-each": "1.0.1"
28 | },
29 | "devDependencies": {
30 | "ava": "^0.19.0",
31 | "benchmark": "^2.1.0",
32 | "coveralls": "^2.11.9",
33 | "eslint": "^3.0.0",
34 | "eslint-config-pedant": "^0.9.0",
35 | "jscs": "^3.0.3",
36 | "mock-fs": "^4.0.0",
37 | "nyc": "^10.0.0",
38 | "promise-map-series": "^0.2.2",
39 | "proxyquire": "^1.7.10",
40 | "sinon": "^2.0.0",
41 | "stream-to-array": "^2.3.0"
42 | },
43 | "scripts": {
44 | "pretest": "eslint . && jscs . -c .jscs.js",
45 | "test": "nyc ava",
46 | "unit-test": "ava",
47 | "bench": "npm run bench-deps && node ./bench/run.js",
48 | "bench-deps": "cd bench && npm i && cd fixtures && bower i",
49 | "coveralls": "nyc report --reporter=text-lcov | coveralls"
50 | },
51 | "ava": {
52 | "serial": true,
53 | "verbose": true,
54 | "files": [
55 | "test/**/*.test.js"
56 | ]
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/test/core/bem-file.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 |
5 | const BemEntityName = require('@bem/entity-name');
6 | const BemCell = require('@bem/cell');
7 |
8 | const BemFile = require('../../lib/bem-file');
9 |
10 | test('should provide classic bem-file fields', t => {
11 | const cell = new BemCell({
12 | entity: new BemEntityName({ block: 'b', elem: 'e' }),
13 | tech: 'css',
14 | layer: 'bem-components/desktop'
15 | });
16 | const path = 'bem-components/desktop.blocks/b/__e/b__e.css';
17 | const file = new BemFile(cell, path);
18 |
19 | t.is(file.cell, cell);
20 | t.is(file.entity, cell.entity);
21 | t.is(file.tech, cell.tech);
22 | t.is(file.level, cell.layer);
23 | t.is(file.layer, cell.layer);
24 | t.is(file.path, path);
25 | });
26 |
--------------------------------------------------------------------------------
/test/core/defaults.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const proxyquire = require('proxyquire');
5 | const sinon = require('sinon');
6 | const mockFs = require('mock-fs');
7 |
8 | const walkers = require('../../lib/walkers');
9 |
10 | test.beforeEach(t => {
11 | const flatStub = sinon.stub(walkers, 'flat').callsArg(2);
12 | const nestedStub = sinon.stub(walkers, 'nested').callsArg(2);
13 |
14 | const walk = proxyquire('../../lib/index', {
15 | './walkers': {
16 | 'flat': flatStub,
17 | 'nested': nestedStub
18 | }
19 | });
20 |
21 | t.context.walk = walk;
22 | t.context.flatStub = flatStub;
23 | t.context.nestedStub = nestedStub;
24 | });
25 |
26 | test.afterEach(t => {
27 | mockFs.restore();
28 |
29 | t.context.flatStub.restore();
30 | t.context.nestedStub.restore();
31 | });
32 |
33 | test.cb('should run nested walker by default', t => {
34 | mockFs({
35 | blocks: {}
36 | });
37 |
38 | t.context.walk(['blocks'])
39 | .resume()
40 | .on('end', () => {
41 | t.true(t.context.nestedStub.calledOnce);
42 | t.end();
43 | })
44 | .on('error', err => t.end(err));
45 | });
46 |
47 | test.cb('should run walker for default scheme', t => {
48 | mockFs({
49 | blocks: {}
50 | });
51 |
52 | t.context.walk(['blocks'], { defaults: { scheme: 'flat' } })
53 | .resume()
54 | .on('end', () => {
55 | t.true(t.context.flatStub.calledOnce);
56 | t.end();
57 | })
58 | .on('error', err => t.end(err));
59 | });
60 |
61 | test.cb('should run walker with default naming', t => {
62 | mockFs({
63 | blocks: {}
64 | });
65 |
66 | t.context.walk(['blocks'], { defaults: { naming: 'two-dashes' } })
67 | .resume()
68 | .on('end', () => {
69 | t.true(t.context.nestedStub.calledWith(sinon.match({ naming: 'two-dashes' })));
70 | t.end();
71 | })
72 | .on('error', err => t.end(err));
73 | });
74 |
--------------------------------------------------------------------------------
/test/core/walkers.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const proxyquire = require('proxyquire');
5 | const sinon = require('sinon');
6 | const mockFs = require('mock-fs');
7 |
8 | const walkers = require('../../lib/walkers');
9 |
10 | test.beforeEach(t => {
11 | const flatStub = sinon.stub(walkers, 'flat').callsArg(2);
12 | const nestedStub = sinon.stub(walkers, 'nested').callsArg(2);
13 |
14 | const walk = proxyquire('../../lib/index', {
15 | './walkers': {
16 | 'flat': flatStub,
17 | 'nested': nestedStub
18 | }
19 | });
20 |
21 | t.context.walk = walk;
22 | t.context.flatStub = flatStub;
23 | t.context.nestedStub = nestedStub;
24 | });
25 |
26 | test.afterEach(t => {
27 | mockFs.restore();
28 |
29 | t.context.flatStub.restore();
30 | t.context.nestedStub.restore();
31 | });
32 |
33 | test.cb('should run walker for level', t => {
34 | mockFs({
35 | blocks: {}
36 | });
37 |
38 | const options = {
39 | levels: {
40 | blocks: { scheme: 'flat' }
41 | }
42 | };
43 |
44 | t.context.walk(['blocks'], options)
45 | .resume()
46 | .on('end', () => {
47 | t.true(t.context.flatStub.calledOnce);
48 | t.end();
49 | });
50 | });
51 |
52 | test.cb('should run walker with naming for level', t => {
53 | mockFs({
54 | blocks: {}
55 | });
56 |
57 | const options = {
58 | levels: {
59 | blocks: { naming: 'two-dashes' }
60 | }
61 | };
62 |
63 | t.context.walk(['blocks'], options)
64 | .resume()
65 | .on('end', () => {
66 | t.true(t.context.nestedStub.calledWith(sinon.match({ naming: 'two-dashes' })));
67 | t.end();
68 | });
69 | });
70 |
71 | test.cb('should run different walkers for different levels', t => {
72 | mockFs({
73 | 'flat.blocks': {},
74 | 'nested.blocks': {}
75 | });
76 |
77 | const options = {
78 | levels: {
79 | 'flat.blocks': { scheme: 'flat' },
80 | 'nested.blocks': { scheme: 'nested' }
81 | }
82 | };
83 |
84 | t.context.walk(['flat.blocks', 'nested.blocks'], options)
85 | .resume()
86 | .on('end', () => {
87 | t.true(t.context.flatStub.calledWith(sinon.match({ path: 'flat.blocks' })));
88 | t.true(t.context.nestedStub.calledWith(sinon.match({ path: 'nested.blocks' })));
89 | t.end();
90 | });
91 | });
92 |
93 | test.cb('should run walkers with different namings for different levels', t => {
94 | mockFs({
95 | 'origin.blocks': {},
96 | 'two-dashes.blocks': {}
97 | });
98 |
99 | const options = {
100 | levels: {
101 | 'origin.blocks': { naming: 'origin' },
102 | 'two-dashes.blocks': { naming: 'two-dashes' }
103 | }
104 | };
105 |
106 | t.context.walk(['origin.blocks', 'two-dashes.blocks'], options)
107 | .resume()
108 | .on('end', () => {
109 | t.true(t.context.nestedStub.calledWith({ path: 'origin.blocks', naming: 'origin' }));
110 | t.true(t.context.nestedStub.calledWith({ path: 'two-dashes.blocks', naming: 'two-dashes' }));
111 | t.end();
112 | });
113 | });
114 |
--------------------------------------------------------------------------------
/test/naming/naming.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const mockFs = require('mock-fs');
5 | const toArray = require('stream-to-array');
6 |
7 | const walk = require('../../lib/index');
8 |
9 | test.afterEach('restore fs', () => {
10 | mockFs.restore();
11 | });
12 |
13 | test('should support original naming', t => {
14 | mockFs({
15 | blocks: {
16 | 'block__elem_mod_val.tech': ''
17 | }
18 | });
19 |
20 | const options = {
21 | levels: {
22 | blocks: {
23 | naming: 'origin',
24 | scheme: 'flat'
25 | }
26 | }
27 | };
28 |
29 | return toArray(walk(['blocks'], options))
30 | .then(files => {
31 | const entities = files.map(file => file.cell.entity.valueOf());
32 |
33 | t.deepEqual(entities, [{
34 | block: 'block',
35 | elem: 'elem',
36 | mod: { name: 'mod', val: 'val' }
37 | }]);
38 | });
39 | });
40 |
41 | test('should support two-dashes naming', t => {
42 | mockFs({
43 | blocks: {
44 | 'block__elem--mod_val.tech': ''
45 | }
46 | });
47 |
48 | const options = {
49 | levels: {
50 | blocks: {
51 | naming: 'two-dashes',
52 | scheme: 'flat'
53 | }
54 | }
55 | };
56 |
57 | return toArray(walk(['blocks'], options))
58 | .then(files => {
59 | const entities = files.map(file => file.cell.entity.valueOf());
60 |
61 | t.deepEqual(entities, [{
62 | block: 'block',
63 | elem: 'elem',
64 | mod: { name: 'mod', val: 'val' }
65 | }]);
66 | });
67 | });
68 |
69 | test('should support custom naming', t => {
70 | mockFs({
71 | blocks: {
72 | 'block-elem--boolMod.tech': ''
73 | }
74 | });
75 |
76 | const options = {
77 | levels: {
78 | blocks: {
79 | naming: {
80 | delims: {
81 | elem: '-',
82 | mod: '--'
83 | },
84 | wordPattern: '[a-zA-Z0-9]+'
85 | },
86 | scheme: 'flat'
87 | }
88 | }
89 | };
90 |
91 | return toArray(walk(['blocks'], options))
92 | .then(files => {
93 | const entities = files.map(file => file.cell.entity.valueOf());
94 |
95 | t.deepEqual(entities, [{
96 | block: 'block',
97 | elem: 'elem',
98 | mod: { name: 'boolMod', val: true }
99 | }]);
100 | });
101 | });
102 |
103 | test('should support several naming', t => {
104 | mockFs({
105 | 'origin.blocks': {
106 | 'block_mod.tech': ''
107 | },
108 | 'two-dashes.blocks': {
109 | 'block--mod_val.tech': ''
110 | }
111 | });
112 |
113 | const options = {
114 | levels: {
115 | 'origin.blocks': {
116 | naming: 'origin',
117 | scheme: 'flat'
118 | },
119 | 'two-dashes.blocks': {
120 | naming: 'two-dashes',
121 | scheme: 'flat'
122 | }
123 | }
124 | };
125 |
126 | return toArray(walk(['origin.blocks', 'two-dashes.blocks'], options))
127 | .then(files => {
128 | const entities = files.map(file => file.cell.entity.valueOf());
129 |
130 | t.deepEqual(entities, [
131 | {
132 | block: 'block',
133 | mod: { name: 'mod', val: true }
134 | },
135 | {
136 | block: 'block',
137 | mod: { name: 'mod', val: 'val' }
138 | }
139 | ]);
140 | });
141 | });
142 |
--------------------------------------------------------------------------------
/test/schemes/flat/detect.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const mockFs = require('mock-fs');
5 | const toArray = require('stream-to-array');
6 |
7 | const walk = require('../../../lib/index');
8 |
9 | const options = {
10 | levels: {
11 | blocks: { scheme: 'flat' }
12 | }
13 | };
14 |
15 | test.afterEach('restore fs', () => {
16 | mockFs.restore();
17 | });
18 |
19 | test('should detect block', t => {
20 | mockFs({
21 | blocks: {
22 | 'block.tech': ''
23 | }
24 | });
25 |
26 | return toArray(walk(['blocks'], options))
27 | .then(files => {
28 | const entities = files.map(file => file.cell.entity.valueOf());
29 |
30 | t.deepEqual(entities, [{ block: 'block' }]);
31 | });
32 | });
33 |
34 | test('should detect bool mod of block', t => {
35 | mockFs({
36 | blocks: {
37 | 'block_mod.tech': ''
38 | }
39 | });
40 |
41 | return toArray(walk(['blocks'], options))
42 | .then(files => {
43 | const entities = files.map(file => file.cell.entity.valueOf());
44 |
45 | t.deepEqual(entities, [{
46 | block: 'block',
47 | mod: { name: 'mod', val: true }
48 | }]);
49 | });
50 | });
51 |
52 | test('should detect key-val mod of block', t => {
53 | mockFs({
54 | blocks: {
55 | 'block_mod_val.tech': ''
56 | }
57 | });
58 |
59 | return toArray(walk(['blocks'], options))
60 | .then(files => {
61 | const entities = files.map(file => file.cell.entity.valueOf());
62 |
63 | t.deepEqual(entities, [{
64 | block: 'block',
65 | mod: { name: 'mod', val: 'val' }
66 | }]);
67 | });
68 | });
69 |
70 | test('should detect elem', t => {
71 | mockFs({
72 | blocks: {
73 | 'block__elem.tech': ''
74 | }
75 | });
76 |
77 | return toArray(walk(['blocks'], options))
78 | .then(files => {
79 | const entities = files.map(file => file.cell.entity.valueOf());
80 |
81 | t.deepEqual(entities, [{ block: 'block', elem: 'elem' }]);
82 | });
83 | });
84 |
85 | test('should detect bool mod of elem', t => {
86 | mockFs({
87 | blocks: {
88 | 'block__elem_mod.tech': ''
89 | }
90 | });
91 |
92 | return toArray(walk(['blocks'], options))
93 | .then(files => {
94 | const entities = files.map(file => file.cell.entity.valueOf());
95 |
96 | t.deepEqual(entities, [{
97 | block: 'block',
98 | elem: 'elem',
99 | mod: { name: 'mod', val: true }
100 | }]);
101 | });
102 | });
103 |
104 | test('should detect key-val mod of elem', t => {
105 | mockFs({
106 | blocks: {
107 | 'block__elem_mod_val.tech': ''
108 | }
109 | });
110 |
111 | return toArray(walk(['blocks'], options))
112 | .then(files => {
113 | const entities = files.map(file => file.cell.entity.valueOf());
114 |
115 | t.deepEqual(entities, [{
116 | block: 'block',
117 | elem: 'elem',
118 | mod: { name: 'mod', val: 'val' }
119 | }]);
120 | });
121 | });
122 |
--------------------------------------------------------------------------------
/test/schemes/flat/error.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const test = require('ava');
5 |
6 | const walk = require('../../../lib/index');
7 |
8 | test.cb('should throw error if level is not found', t => {
9 | t.plan(2);
10 |
11 | const levelpath = path.resolve('./not-existing-level');
12 | const options = {
13 | defaults: { scheme: 'flat' }
14 | };
15 |
16 | walk([levelpath], options)
17 | .resume()
18 | .on('error', err => {
19 | t.is(err.code, 'ENOENT');
20 | t.is(err.path, levelpath);
21 | t.end();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/schemes/flat/ignore.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const mockFs = require('mock-fs');
5 | const toArray = require('stream-to-array');
6 |
7 | const walk = require('../../../lib/index');
8 |
9 | const options = {
10 | levels: {
11 | blocks: { scheme: 'flat' }
12 | }
13 | };
14 |
15 | test.afterEach('restore fs', () => {
16 | mockFs.restore();
17 | });
18 |
19 | test('should end if levels are not specified', t => {
20 | mockFs({});
21 |
22 | return toArray(walk([], options))
23 | .then(files => {
24 | t.deepEqual(files, []);
25 | });
26 | });
27 |
28 | test('should ignore empty level', t => {
29 | mockFs({
30 | blocks: {}
31 | });
32 |
33 | return toArray(walk(['blocks'], options))
34 | .then(files => {
35 | t.deepEqual(files, []);
36 | });
37 | });
38 |
39 | test('should ignore files without extension', t => {
40 | mockFs({
41 | blocks: {
42 | block: ''
43 | }
44 | });
45 |
46 | return toArray(walk(['blocks'], options))
47 | .then(files => {
48 | t.deepEqual(files, []);
49 | });
50 | });
51 |
52 | test('should ignore files with no BEM basename', t => {
53 | mockFs({
54 | blocks: {
55 | '^_^.ext': ''
56 | }
57 | });
58 |
59 | return toArray(walk(['blocks'], options))
60 | .then(files => {
61 | t.deepEqual(files, []);
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/test/schemes/flat/levels.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | const test = require('ava');
6 | const mockFs = require('mock-fs');
7 | const toArray = require('stream-to-array');
8 |
9 | const walk = require('../../../lib/index');
10 |
11 | test.afterEach('restore fs', () => {
12 | mockFs.restore();
13 | });
14 |
15 | test('should support level name with extension', t => {
16 | mockFs({
17 | 'name.blocks': {
18 | 'block.tech': ''
19 | }
20 | });
21 |
22 | const options = {
23 | levels: {
24 | 'name.blocks': { scheme: 'flat' }
25 | }
26 | };
27 |
28 | return toArray(walk(['name.blocks'], options))
29 | .then(files => {
30 | const file = files[0];
31 |
32 | t.deepEqual(file.cell.entity.valueOf(), { block: 'block' });
33 | t.is(file.cell.layer, 'name.blocks');
34 | t.is(file.path, path.join('name.blocks', 'block.tech'));
35 | t.is(file.cell.tech, 'tech');
36 | });
37 | });
38 |
39 | test('should support few levels', t => {
40 | mockFs({
41 | 'level-1': {
42 | 'block-1.tech': ''
43 | },
44 | 'level-2': {
45 | 'block-2.tech': ''
46 | }
47 | });
48 |
49 | const options = {
50 | levels: {
51 | 'level-1': { scheme: 'flat' },
52 | 'level-2': { scheme: 'flat' }
53 | }
54 | };
55 |
56 | return toArray(walk(['level-1', 'level-2'], options))
57 | .then(files => {
58 | const file1 = files[0];
59 | const file2 = files[1];
60 |
61 | t.deepEqual(file1.cell.entity.valueOf(), { block: 'block-1' });
62 | t.is(file1.cell.layer, 'level-1');
63 | t.is(file1.cell.tech, 'tech');
64 | t.is(file1.path, path.join('level-1', 'block-1.tech'));
65 |
66 | t.deepEqual(file2.cell.entity.valueOf(), { block: 'block-2' });
67 | t.is(file2.cell.layer, 'level-2');
68 | t.is(file2.cell.tech, 'tech');
69 | t.is(file2.path, path.join('level-2', 'block-2.tech'));
70 | });
71 | });
72 |
73 | test('should detect entity with the same name on every level', t => {
74 | mockFs({
75 | 'level-1': {
76 | 'block.tech': ''
77 | },
78 | 'level-2': {
79 | 'block.tech': ''
80 | }
81 | });
82 |
83 | const options = {
84 | levels: {
85 | 'level-1': { scheme: 'flat' },
86 | 'level-2': { scheme: 'flat' }
87 | }
88 | };
89 |
90 | return toArray(walk(['level-1', 'level-2'], options))
91 | .then(files => {
92 | const file1 = files[0];
93 | const file2 = files[1];
94 |
95 | t.deepEqual(file1.cell.entity.valueOf(), { block: 'block' });
96 | t.is(file1.cell.layer, 'level-1');
97 | t.is(file1.cell.tech, 'tech');
98 | t.is(file1.path, path.join('level-1', 'block.tech'));
99 |
100 | t.deepEqual(file2.cell.entity.valueOf(), { block: 'block' });
101 | t.is(file2.cell.layer, 'level-2');
102 | t.is(file2.cell.tech, 'tech');
103 | t.is(file2.path, path.join('level-2', 'block.tech'));
104 | });
105 | });
106 |
--------------------------------------------------------------------------------
/test/schemes/flat/techs.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const mockFs = require('mock-fs');
5 | const toArray = require('stream-to-array');
6 |
7 | const walk = require('../../../lib/index');
8 |
9 | const options = {
10 | levels: {
11 | blocks: { scheme: 'flat' }
12 | }
13 | };
14 |
15 | test.afterEach('restore fs', () => {
16 | mockFs.restore();
17 | });
18 |
19 | test('should detect each techs of the same entity', t => {
20 | mockFs({
21 | blocks: {
22 | 'block.tech-1': '',
23 | 'block.tech-2': ''
24 | }
25 | });
26 |
27 | return toArray(walk(['blocks'], options))
28 | .then(files => {
29 | const techs = files.map(file => file.cell.tech);
30 |
31 | t.deepEqual(techs, ['tech-1', 'tech-2']);
32 | });
33 | });
34 |
35 | test('should support complex tech', t => {
36 | mockFs({
37 | blocks: {
38 | 'block.tech-1.tech-2': ''
39 | }
40 | });
41 |
42 | return toArray(walk(['blocks'], options))
43 | .then(files => {
44 | const techs = files.map(file => file.cell.tech);
45 |
46 | t.deepEqual(techs, ['tech-1.tech-2']);
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/test/schemes/multi.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | const test = require('ava');
6 | const mockFs = require('mock-fs');
7 | const toArray = require('stream-to-array');
8 |
9 | const walk = require('../../lib/index');
10 |
11 | test.afterEach('restore fs', () => {
12 | mockFs.restore();
13 | });
14 |
15 | test('should support several schemes', t => {
16 | mockFs({
17 | 'flat.blocks': {
18 | 'block.tech': ''
19 | },
20 | 'nested.blocks': {
21 | 'block': {
22 | 'block.tech': ''
23 | }
24 | }
25 | });
26 |
27 | const options = {
28 | levels: {
29 | 'flat.blocks': { scheme: 'flat' },
30 | 'nested.blocks': { scheme: 'nested' }
31 | }
32 | };
33 |
34 | return toArray(walk(['flat.blocks', 'nested.blocks'], options))
35 | .then(files => {
36 | const file1 = files[0];
37 | const file2 = files[1];
38 |
39 | t.deepEqual(file1.cell.entity.valueOf(), { block: 'block' });
40 | t.is(file1.cell.layer, 'flat.blocks');
41 | t.is(file1.cell.tech, 'tech');
42 | t.is(file1.path, path.join('flat.blocks', 'block.tech'));
43 |
44 | t.deepEqual(file2.cell.entity.valueOf(), { block: 'block' });
45 | t.is(file2.cell.layer, 'nested.blocks');
46 | t.is(file2.cell.tech, 'tech');
47 | t.is(file2.path, path.join('nested.blocks', 'block', 'block.tech'));
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/test/schemes/nested/detect.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const mockFs = require('mock-fs');
5 | const toArray = require('stream-to-array');
6 |
7 | const walk = require('../../../lib/index');
8 |
9 | const options = {
10 | levels: {
11 | blocks: { scheme: 'nested' }
12 | }
13 | };
14 |
15 | test.afterEach('restore fs', () => {
16 | mockFs.restore();
17 | });
18 |
19 | test('should detect block', t => {
20 | mockFs({
21 | blocks: {
22 | block: {
23 | 'block.tech': ''
24 | }
25 | }
26 | });
27 |
28 | return toArray(walk(['blocks'], options))
29 | .then(files => {
30 | const entities = files.map(file => file.cell.entity.valueOf());
31 |
32 | t.deepEqual(entities, [{ block: 'block' }]);
33 | });
34 | });
35 |
36 | test('should detect bool mod of block', t => {
37 | mockFs({
38 | blocks: {
39 | block: {
40 | _mod: {
41 | 'block_mod.tech': ''
42 | }
43 | }
44 | }
45 | });
46 |
47 | return toArray(walk(['blocks'], options))
48 | .then(files => {
49 | const entities = files.map(file => file.cell.entity.valueOf());
50 |
51 | t.deepEqual(entities, [{
52 | block: 'block',
53 | mod: { name: 'mod', val: true }
54 | }]);
55 | });
56 | });
57 |
58 | test('should detect key-val mod of block', t => {
59 | mockFs({
60 | blocks: {
61 | block: {
62 | _mod: {
63 | 'block_mod_val.tech': ''
64 | }
65 | }
66 | }
67 | });
68 |
69 | return toArray(walk(['blocks'], options))
70 | .then(files => {
71 | const entities = files.map(file => file.cell.entity.valueOf());
72 |
73 | t.deepEqual(entities, [{
74 | block: 'block',
75 | mod: { name: 'mod', val: 'val' }
76 | }]);
77 | });
78 | });
79 |
80 | test('should detect elem', t => {
81 | mockFs({
82 | blocks: {
83 | block: {
84 | __elem: {
85 | 'block__elem.tech': ''
86 | }
87 | }
88 | }
89 | });
90 |
91 | return toArray(walk(['blocks'], options))
92 | .then(files => {
93 | const entities = files.map(file => file.cell.entity.valueOf());
94 |
95 | t.deepEqual(entities, [{ block: 'block', elem: 'elem' }]);
96 | });
97 | });
98 |
99 | test('should detect bool mod of elem', t => {
100 | mockFs({
101 | blocks: {
102 | block: {
103 | __elem: {
104 | '_mod': {
105 | 'block__elem_mod.tech': ''
106 | }
107 | }
108 | }
109 | }
110 | });
111 |
112 | return toArray(walk(['blocks'], options))
113 | .then(files => {
114 | const entities = files.map(file => file.cell.entity.valueOf());
115 |
116 | t.deepEqual(entities, [{
117 | block: 'block',
118 | elem: 'elem',
119 | mod: { name: 'mod', val: true }
120 | }]);
121 | });
122 | });
123 |
124 | test('should detect key-val mod of elem', t => {
125 | mockFs({
126 | blocks: {
127 | block: {
128 | __elem: {
129 | _mod: {
130 | 'block__elem_mod_val.tech': ''
131 | }
132 | }
133 | }
134 | }
135 | });
136 |
137 | return toArray(walk(['blocks'], options))
138 | .then(files => {
139 | const entities = files.map(file => file.cell.entity.valueOf());
140 |
141 | t.deepEqual(entities, [{
142 | block: 'block',
143 | elem: 'elem',
144 | mod: { name: 'mod', val: 'val' }
145 | }]);
146 | });
147 | });
148 |
149 | test('should detect complex entities', t => {
150 | mockFs({
151 | blocks: {
152 | block: {
153 | 'block.tech': '',
154 | '_bool-mod': {
155 | 'block_bool-mod.tech': ''
156 | },
157 | _mod: {
158 | 'block_mod_val.tech': ''
159 | },
160 | __elem: {
161 | 'block__elem.tech': '',
162 | '_bool-mod': {
163 | 'block__elem_bool-mod.tech': ''
164 | },
165 | _mod: {
166 | 'block__elem_mod_val.tech': ''
167 | }
168 | }
169 | }
170 | }
171 | });
172 |
173 | return toArray(walk(['blocks'], options))
174 | .then(files => {
175 | const entities = files.map(file => file.cell.entity.valueOf());
176 |
177 | t.deepEqual(entities, [
178 | { block: 'block' },
179 | { block: 'block', elem: 'elem' },
180 | { block: 'block', mod: { name: 'bool-mod', val: true } },
181 | { block: 'block', mod: { name: 'mod', val: 'val' } },
182 | { block: 'block', elem: 'elem', mod: { name: 'bool-mod', val: true } },
183 | { block: 'block', elem: 'elem', mod: { name: 'mod', val: 'val' } }
184 | ]);
185 | });
186 | });
187 |
--------------------------------------------------------------------------------
/test/schemes/nested/error.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const test = require('ava');
5 |
6 | const walk = require('../../../lib/index');
7 |
8 | test.cb('should throw error if level is not found', t => {
9 | t.plan(2);
10 |
11 | const levelpath = path.resolve('./not-existing-level');
12 | const options = {
13 | defaults: { scheme: 'nested' }
14 | };
15 |
16 | walk([levelpath], options)
17 | .resume()
18 | .on('error', err => {
19 | t.is(err.code, 'ENOENT');
20 | t.is(err.path, levelpath);
21 | t.end();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/schemes/nested/ignore.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const mockFs = require('mock-fs');
5 | const toArray = require('stream-to-array');
6 |
7 | const walk = require('../../../lib/index');
8 |
9 | const options = {
10 | levels: {
11 | blocks: { scheme: 'nested' }
12 | }
13 | };
14 |
15 | test.afterEach('restore fs', () => {
16 | mockFs.restore();
17 | });
18 |
19 | test('should end if levels are not specified', t => {
20 | mockFs({});
21 |
22 | return toArray(walk([], options))
23 | .then(files => t.deepEqual(files, []));
24 | });
25 |
26 | test('should ignore empty level', t => {
27 | mockFs({
28 | blocks: {}
29 | });
30 |
31 | return toArray(walk(['blocks'], options))
32 | .then(files => t.deepEqual(files, []));
33 | });
34 |
35 | test('should ignore files without extension', t => {
36 | mockFs({
37 | blocks: {
38 | block: {
39 | block: ''
40 | }
41 | }
42 | });
43 |
44 | return toArray(walk(['blocks'], options))
45 | .then(files => t.deepEqual(files, []));
46 | });
47 |
48 | test('should ignore files with no BEM basename in block dir', t => {
49 | mockFs({
50 | blocks: {
51 | block: {
52 | '^_^.tech': ''
53 | }
54 | }
55 | });
56 |
57 | return toArray(walk(['blocks'], options))
58 | .then(files => t.deepEqual(files, []));
59 | });
60 |
61 | test('should ignore files with no BEM basename in mod dir', t => {
62 | mockFs({
63 | blocks: {
64 | block: {
65 | _mod: {
66 | '^_^.tech': ''
67 | }
68 | }
69 | }
70 | });
71 |
72 | return toArray(walk(['blocks'], options))
73 | .then(files => t.deepEqual(files, []));
74 | });
75 |
76 | test('should ignore files with no BEM basename in elem dir', t => {
77 | mockFs({
78 | blocks: {
79 | block: {
80 | __elem: {
81 | '^_^.tech': ''
82 | }
83 | }
84 | }
85 | });
86 |
87 | return toArray(walk(['blocks'], options))
88 | .then(files => t.deepEqual(files, []));
89 | });
90 |
91 | test('should ignore files with no BEM basename in elem mod dir', t => {
92 | mockFs({
93 | blocks: {
94 | block: {
95 | __elem: {
96 | _mod: {
97 | '^_^.tech': ''
98 | }
99 | }
100 | }
101 | }
102 | });
103 |
104 | return toArray(walk(['blocks'], options))
105 | .then(files => t.deepEqual(files, []));
106 | });
107 |
108 | test('should ignore dirs with no BEM basename in block dir', t => {
109 | mockFs({
110 | blocks: {
111 | block: {
112 | '^_^': {}
113 | }
114 | }
115 | });
116 |
117 | return toArray(walk(['blocks'], options))
118 | .then(files => t.deepEqual(files, []));
119 | });
120 |
121 | test('should ignore dirs with no BEM basename in mod dir', t => {
122 | mockFs({
123 | blocks: {
124 | block: {
125 | _mod: {
126 | '^_^': {}
127 | }
128 | }
129 | }
130 | });
131 |
132 | return toArray(walk(['blocks'], options))
133 | .then(files => t.deepEqual(files, []));
134 | });
135 |
136 | test('should ignore dirs with no BEM basename in elem dir', t => {
137 | mockFs({
138 | blocks: {
139 | block: {
140 | __elem: {
141 | '^_^': {}
142 | }
143 | }
144 | }
145 | });
146 |
147 | return toArray(walk(['blocks'], options))
148 | .then(files => t.deepEqual(files, []));
149 | });
150 |
151 | test('should ignore dirs with no BEM basename in elem mod dir', t => {
152 | mockFs({
153 | blocks: {
154 | block: {
155 | __elem: {
156 | _mod: {
157 | '^_^': {}
158 | }
159 | }
160 | }
161 | }
162 | });
163 |
164 | return toArray(walk(['blocks'], options))
165 | .then(files => t.deepEqual(files, []));
166 | });
167 |
168 | test('should ignore file in root of level', t => {
169 | mockFs({
170 | blocks: {
171 | 'block.tech': ''
172 | }
173 | });
174 |
175 | return toArray(walk(['blocks'], options))
176 | .then(files => t.deepEqual(files, []));
177 | });
178 |
179 | test('should ignore block if filename not match with dirname', t => {
180 | mockFs({
181 | blocks: {
182 | block: {
183 | 'other-block.tech': ''
184 | }
185 | }
186 | });
187 |
188 | return toArray(walk(['blocks'], options))
189 | .then(files => t.deepEqual(files, []));
190 | });
191 |
192 | test('should ignore block mod if filename not match with dirname', t => {
193 | mockFs({
194 | blocks: {
195 | block: {
196 | _mod: {
197 | 'block_other-mod.tech': ''
198 | }
199 | }
200 | }
201 | });
202 |
203 | return toArray(walk(['blocks'], options))
204 | .then(files => t.deepEqual(files, []));
205 | });
206 |
207 | test('should ignore elem if filename not match with dirname', t => {
208 | mockFs({
209 | blocks: {
210 | block: {
211 | __elem: {
212 | 'block__other-elem.tech': ''
213 | }
214 | }
215 | }
216 | });
217 |
218 | return toArray(walk(['blocks'], options))
219 | .then(files => t.deepEqual(files, []));
220 | });
221 |
222 | test('should ignore elem mod if filename not match with dirname', t => {
223 | mockFs({
224 | blocks: {
225 | block: {
226 | __elem: {
227 | _mod: {
228 | 'block__elem_other-mod.tech': ''
229 | }
230 | }
231 | }
232 | }
233 | });
234 |
235 | return toArray(walk(['blocks'], options))
236 | .then(files => t.deepEqual(files, []));
237 | });
238 |
--------------------------------------------------------------------------------
/test/schemes/nested/levels.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | const test = require('ava');
6 | const mockFs = require('mock-fs');
7 | const toArray = require('stream-to-array');
8 |
9 | const walk = require('../../../lib/index');
10 |
11 | test.afterEach('restore fs', () => {
12 | mockFs.restore();
13 | });
14 |
15 | test('should support level name with extension', t => {
16 | mockFs({
17 | 'name.blocks': {
18 | block: {
19 | 'block.tech': ''
20 | }
21 | }
22 | });
23 |
24 | const options = {
25 | levels: {
26 | 'name.blocks': { scheme: 'nested' }
27 | }
28 | };
29 |
30 | return toArray(walk(['name.blocks'], options))
31 | .then(files => {
32 | const file = files[0];
33 |
34 | t.deepEqual(file.cell.entity.valueOf(), { block: 'block' });
35 | t.is(file.cell.layer, 'name.blocks');
36 | t.is(file.cell.tech, 'tech');
37 | t.is(file.path, path.join('name.blocks', 'block', 'block.tech'));
38 | });
39 | });
40 |
41 | test('should support few levels', t => {
42 | mockFs({
43 | 'level-1': {
44 | 'block-1': {
45 | 'block-1.tech': ''
46 | }
47 | },
48 | 'level-2': {
49 | 'block-2': {
50 | 'block-2.tech': ''
51 | }
52 | }
53 | });
54 |
55 | const options = {
56 | levels: {
57 | 'level-1': { scheme: 'nested' },
58 | 'level-2': { scheme: 'nested' }
59 | }
60 | };
61 |
62 | return toArray(walk(['level-1', 'level-2'], options))
63 | .then(files => {
64 | const file1 = files[0];
65 | const file2 = files[1];
66 |
67 | t.deepEqual(file1.cell.entity.valueOf(), { block: 'block-1' });
68 | t.is(file1.cell.layer, 'level-1');
69 | t.is(file1.cell.tech, 'tech');
70 | t.is(file1.path, path.join('level-1', 'block-1', 'block-1.tech'));
71 |
72 | t.deepEqual(file2.cell.entity.valueOf(), { block: 'block-2' });
73 | t.is(file2.cell.layer, 'level-2');
74 | t.is(file2.cell.tech, 'tech');
75 | t.is(file2.path, path.join('level-2', 'block-2', 'block-2.tech'));
76 | });
77 | });
78 |
79 | test('should detect entity with the same name on every level', t => {
80 | mockFs({
81 | 'level-1': {
82 | block: {
83 | 'block.tech': ''
84 | }
85 | },
86 | 'level-2': {
87 | block: {
88 | 'block.tech': ''
89 | }
90 | }
91 | });
92 |
93 | const options = {
94 | levels: {
95 | 'level-1': { scheme: 'nested' },
96 | 'level-2': { scheme: 'nested' }
97 | }
98 | };
99 |
100 | return toArray(walk(['level-1', 'level-2'], options))
101 | .then(files => {
102 | const file1 = files[0];
103 | const file2 = files[1];
104 |
105 | t.deepEqual(file1.cell.entity.valueOf(), { block: 'block' });
106 | t.is(file1.cell.layer, 'level-1');
107 | t.is(file1.cell.tech, 'tech');
108 | t.is(file1.path, path.join('level-1', 'block', 'block.tech'));
109 |
110 | t.deepEqual(file2.cell.entity.valueOf(), { block: 'block' });
111 | t.is(file2.cell.layer, 'level-2');
112 | t.is(file2.cell.tech, 'tech');
113 | t.is(file2.path, path.join('level-2', 'block', 'block.tech'));
114 | });
115 | });
116 |
--------------------------------------------------------------------------------
/test/schemes/nested/techs.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const test = require('ava');
4 | const mockFs = require('mock-fs');
5 | const toArray = require('stream-to-array');
6 |
7 | const walk = require('../../../lib/index');
8 |
9 | const options = {
10 | levels: {
11 | blocks: { scheme: 'nested' }
12 | }
13 | };
14 |
15 | test.afterEach('restore fs', () => {
16 | mockFs.restore();
17 | });
18 |
19 | test('should detect each techs of the same entity', t => {
20 | mockFs({
21 | blocks: {
22 | block: {
23 | 'block.tech-1': '',
24 | 'block.tech-2': ''
25 | }
26 | }
27 | });
28 |
29 | return toArray(walk(['blocks'], options))
30 | .then(files => {
31 | const techs = files.map(file => file.cell.tech);
32 |
33 | t.deepEqual(techs, ['tech-1', 'tech-2']);
34 | });
35 | });
36 |
37 | test('should support complex tech', t => {
38 | mockFs({
39 | blocks: {
40 | block: {
41 | 'block.tech-1.tech-2': ''
42 | }
43 | }
44 | });
45 |
46 | return toArray(walk(['blocks'], options))
47 | .then(files => {
48 | const techs = files.map(file => file.cell.tech);
49 |
50 | t.deepEqual(techs, ['tech-1.tech-2']);
51 | });
52 | });
53 |
--------------------------------------------------------------------------------