├── .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 | --------------------------------------------------------------------------------