Giter Site home page Giter Site logo

bem-sdk-archive / bem-walk Goto Github PK

View Code? Open in Web Editor NEW
16.0 10.0 7.0 183 KB

:footprints: Walk easily thru BEM file structure. DEPRECATED →

Home Page: https://github.com/bem/bem-sdk/tree/master/packages/walk

License: Other

JavaScript 100.00%
bem file-scanner nested-sheme flat-sheme bem-entity bem-cell introspection

bem-walk's Introduction

bem-walk

Tool for traversing a BEM project's file system.

NPM Status Travis Status Windows Status Coverage Status Dependency Status

It returns the following information about found files:

Quick start

Note To use bem-walk, you must install Node.js 4.0+.

1. Install bem-walk

$ npm install --save @bem/walk

2. Enable bem-walk

Create a JavaScript file with any name and add the following string:

const walk = require('@bem/walk');

Note You will use this JavaScript file for all the following steps.

3. Define file system levels

Define the project's file system levels in the config object.

const config = {
    // project levels
    levels: {
        'lib/bem-core/common.blocks': {
            // `naming` — file naming scheme
            naming: 'two-dashes'
        },
        'common.blocks': {
            // `scheme` — file system scheme
            scheme: 'nested'
        }
    }
};

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 or using the names of files and directories.

The table shows the possible values that can be set for each of the schemes.

Key Scheme Default value Possible values
naming File naming. origin origin, two-dashes
scheme File system. nested nested, flat

More information:

Note Instead of defining the project's levels manually, use the bem-config tool.

const config = require('bem-config')();
const levelMap = config.levelMapSync();
const stream = walk(levels, levelMap);

4. Define the paths to traverse

Specify the paths to walk in the levels object.

Path options:

  • Relative to the root directory.

    const levels = [
        'libs/bem-core/common.blocks',
        'common.blocks'
    ];
  • Absolute.

    const levels = [
        '/path/to/project/lib/bem-core/common.blocks',
        '/path/to/project/common.blocks'
    ];

5. Get information about found files

Pass the walk() method the levels and config objects.

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

const files = [];

const stream = walk(levels, config);
// adds information about a found file to the end of the "files" array
stream.on('data', file => files.push(file));

stream.on('error', console.error);

stream.on('end', () => console.log(files));

Complete code sample

When all these steps have been completed, the full code of the JavaScript file should look like this:

const walk = require('@bem/walk');
const config = require('bem-config')();
const levels = [
    'libs/bem-components/common.blocks',
    'common.blocks'
];
const files = [];

const stream = walk(levels, {
        levels: config.levelMapSync()
    })
    .on('data', file => files.push(file))
    .on('error', console.error)
    .on('end', () => console.log(files));

Note This sample uses the bem-config package.

API

walk method

walk(levels, config);

Description

Traverses the directories described in the levels parameter and returns stream.Readable.

Input parameters

Requirements for the search are defined in the levels and config parameters.

Parameter Type Description
levels string[] Paths to traverse
config object Project levels

Output data

Readable stream (stream.Readable) that has the following events:

Event Description
'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.
'error' Generated if an error occurred while traversing the levels. Returns an object with the error description.
'end' Generated when bem-walk finishes traversing the levels defined in the levels object.

Usage examples

Typical tasks that use the resulting JavaScript objects:

Grouping

Grouping found files by block name.

const walk = require('@bem/walk');
const config = require('bem-config')();
const util = require('util');
const levels = [
    'libs/bem-components/common.blocks',
    'common.blocks'
];
const groups = {};

const stream = walk(levels, {
        levels: config.levelMapSync()
    })
    .on('data', file => {
        // Getting the block name for a found file.
        const block = file.entity.block;

        // Adding information about the found file.
        (groups[block] = []).push(file);
    })
    .on('error', console.error)
    .on('end', () => console.log(util.inspect(groups, {
        depth: null
    })));

/*
{ button:
   [ BemFile {
      cell: BemCell {
         entity: BemEntityName { block: 'button', mod: { name: 'togglable', val: 'radio' } },
         tech: 'spec.js',
         layer: 'libs/bem-components/common.blocks'
      },
      path: 'libs/bem-components/common.blocks/button/_togglable/
        button_togglable_radio.spec.js' } ] },
 ...
}
*/

Filtering

Finding files for the popup block.

const walk = require('@bem/walk');
const config = require('bem-config')();
const levels = [
    'libs/bem-components/common.blocks',
    'common.blocks'
];
const files = [];

const stream = walk(levels, {
        levels: config.levelMapSync()
    })
    .on('data', file => {
        // Getting the block name for a found file.
        const block = file.entity.block;

        // Adding information about the found file.
        if (block == 'popup') {
            files.push(file);
        }
    })
    .on('error', console.error)
    .on('end', () => console.log(files));

/*
[BemFile {
   cell: BemCell {
     entity: BemEntityName { block: 'popup', mod: { name: 'target', val: true } },
     tech: 'js',
     layer: 'libs/bem-components/common.blocks'
   },
   path: 'libs/bem-components/common.blocks/popup/_target/popup_target.js'
},
...
]
*/

Transformation

Finding BEM files, reading the contents, and creating the new source property.

const fs = require('fs');
const walk = require('@bem/walk');
const config = require('bem-config')();
const stringify = require('JSONStream').stringify;
const through2 = require('through2');
const levels = [
    'libs/bem-components/common.blocks',
    'common.blocks'
];

const stream = walk(levels, {
        levels: config.levelMapSync()
    })
    .pipe(through2.obj(function(file, enc, callback) {
        try {
            // Certain technologies (for example, `i18n`) might be directories instead of files.
            if (fs.statSync(file.path).isFile()) {
                // Writing the file content to the `source` field.
                file.source = fs.readFileSync(file.path, 'utf-8');
                this.push(file);
            }

        } catch (err) {
            callback(err);
        }
    }))
    .pipe(stringify())
    .pipe(process.stdout);

/*
[{"cell":{
    "entity":{"block":"search","elem":"header"},
    "tech":"css",
    "layer":"common.blocks"
  },
  "path":"common.blocks/search/__header/search__header.css",
  "source":".search__header {\n\tdisplay: block;\n\tfont-size: 20px;\n\tcolor:
  rgba(0,0,0,0.84);\n\tmargin: 0;\n\tpadding: 0 0 16px;\n\n}\n\n"},
...
]
*/

bem-walk's People

Contributors

blond avatar egavr avatar floatdrop avatar godfreyd avatar greenkeeper[bot] avatar greenkeeperio-bot avatar qfox avatar yeti-or avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bem-walk's Issues

Bug in walk

Пытаюсь обойти блоки волкером и получаю

/Users/gavryushin/job/bemhint/node_modules/bem-walk/lib/schemes/nested.js:53
pth === 3 && dirType === 'blockMod' && dirNotation.block === notation.block &&
                                                                     ^
TypeError: Cannot read property 'block' of undefined
    at /Users/gavryushin/job/bemhint/node_modules/bem-walk/lib/schemes/nested.js:53:93
    at /Users/gavryushin/job/bemhint/node_modules/bem-walk/lib/index.js:49:21
    at Object.oncomplete (fs.js:107:15)

Incompatibility with mock-fs?

Looks like it does not support empty directories mocked with mock-fs:
npm i bem-walk mock-fs stream-to-array

const walk = require('bem-walk');
const toArray = require('stream-to-array');
const mockfs = require('mock-fs');

mockfs({l1: {}, l2: {}});
toArray(walk(['l1', 'l2'])).then(console.log).catch(console.error); // will never be resolved

Support short-naming

Now:

block-super-long/block-super-long.css
block-super-long/block-super-long.deps.js
block-super-long/block-super-long.bemtree.js
block-super-long/block-super-long.bemhtml.js
block-super-long/__elem/block-super-long__elem.css
block-super-long/__elem/_modifier/block-super-long__elem_modifier_value.css

Short:

block-super-long/style.css
block-super-long/deps.js
block-super-long/bemtree.js
block-super-long/bemhtml.js
block-super-long/__elem/style.css
block-super-long/__elem/_modifier_value/style.css

2016-08-15 23-42-02
2016-08-15 23-42-55

Probably better to change event names

data event usually used for data chunk. But here you pushing not chunks but ready to use entities. Would be better to rename this to something like entity.

Ability to read bemObjects by levels, not randomly.

Currently bemObjects are pushed from different levels.
what about adding some API like this:

walk.sorted([
    'libs/bem-core/common.blocks',
    'libs/bem-core/desktop.blocks',
    'common.blocks',
    'desktop.blocks'
], config)
    .pipe(stringify())
    .pipe(process.stdout);

so walk will flush objects randomly from levels,
walk.sorted will flush it grouping by levels, in order of levels in array.
@blond what do u think?

Flex scheme

flex scheme should support all states between flat and nested.

# original `flat`
blocks/
├── block.tech
├── block_mod.tech
├── block__elem.tech
└── block__elem_mod.tech

# blocks are divided into dirs
blocks/
└── block/
    ├── block.tech
    ├── block_mod.tech
    ├── block__elem.tech
    └── block__elem_mod.tech

# less strict `nested`
# blocks, elems and mods are divided into dirs, but not necessarily
blocks/
└── block/
    ├── _mod/
        └── block_mod.tech
    ├── block__elem.tech
    ├── block__elem_mod.tech
    └── block.tech

# original `nested`
blocks/
└── block/
    ├── __elem/
        ├── _mod/
            └── block__elem_mod.tech
        └── block__elem.tech
    ├── _mod/
        └── block_mod.tech
    └── block.tech

Memory Tests

Need to find a way to check that bem-walk does not contain memory leaks.

This will be important if we release caching mech #44 #50.

Don't parse files of elem and mods

Example:

button/
  __text/
    button__text.css
    button__text.js

We can only test that stem (basename without extension) of the button__text.css file matches on button dirname.

const stem = getStem('button__text.css');

if (stem === 'button__text') {
    // add button__text
}

Now we still parse stem:

For modifiers you cannot use only stem because of key-value modifiers:

button/
  _size/
    button__size_m.css
    button__size_l.css

Что-то знать про НЕ БЭМ сущности

Пожалуй стоит давать какую-то инфу про файл, который не принадледжит никакой БЭМ сущности, а не просто пропускать его; например, имени и полного пути было бы достаточно, ну и какое-то отдельное поле , которое бы говорило, что это не БЭМ сущность.

В общем считаю, что это должен быть обычный вывод, не ошибка, не варнинг и т д.

Support wildcards

Need use glob.

Now we need to specify all levels:

var walk = require('bem-walk');

walk([
    'libs/bem-core/common.blocks',
    'libs/bem-core/desktop.blocks',
    'libs/bem-components/common.blocks',
    'libs/bem-components/desktop.blocks',
    'libs/bem-components/design/common.blocks',
    'libs/bem-components/design/desktop.blocks',
    'common.blocks', 
    'desktop.blocks'
]);

If use wildcards:

var walk = require('bem-walk');

walk([
    'libs/**/+(common|desktop).blocks'
    '*.blocks'
]);

Not valid object

bemObject
Object
bem: "popup2-directions"
block: "popup2-directions"
copy: undefined
id: "popup2-directions"
level: "common.blocks"
path: "common.blocks/popup2/popup2-directions.png"
tech: "png"
__proto__: Object

Or is it okay?

Tests are falling on node 0.12

213:bem-walk yeti-or$ mocha test/*/.test.js
/Users/yeti-or/Projects/BEM/bem-walk/node_modules/mock-fs/lib/index.js:21
throw new Error('Unsupported Node version: ' + process.versions.node);

Falling tests on throwing error

Turned off two falling tests. Because tests stops after first assertion. I think that's not very user-friendly.

naming
    ✓ must support original naming 
    ✓ must support Convention by Harry Roberts 
    ✓ must support custom naming 
    ✓ must support several naming 

  flat scheme
    ✓ must end if levels is empty 
    - must throw error if levels is not found
    ignore
      ✓ must ignore entity dir 
      ✓ must ignore entity without ext 
      ✓ must support invalid BEM-notation 
    detect
      ✓ must detect block 
      ✓ must support complex tech 
      ✓ must detect bool mod 
      ✓ must detect mod 
      ✓ must detect elem 
      ✓ must detect bool mod of elem 
      ✓ must detect elem mod 
      ✓ must support few levels 

  multi scheme
    ✓ must support several schemes 

  nested scheme
    ✓ must end if levels is empty 
    - must throw error if levels is not found
    ignore
      ✓ must ignore entity dir 
      ✓ must ignore entity without ext 
      ✓ must support invalid BEM-notation 
      ✓ must ignore file in root of level 
      ✓ must not detect block if filename not match with dirname 
      ✓ must not detect elem if filename not match with dirname 
      ✓ must not detect mod if filename not match with dirname 
      ✓ must not detect elem mod if filename not match with dirname 
    detect
      ✓ must detect block 
      ✓ must support complex tech 
      ✓ must detect bool mod 
      ✓ must detect mod 
      ✓ must detect elem 
      ✓ must detect bool mod of elem 
      ✓ must detect elem mod 
      ✓ must detect complex entities 
      ✓ must support few levels 


  35 passing (97ms)
  2 pending

Move schemes to separate packages

I think we should leave only flex scheme (#18) in this package.

  • bem-walk-nested-scheme
  • bem-walk-flat-scheme
  • bem-walk-flex-scheme

Scheme package should provide walk and path method.

const nestedScheme = require('bem-nested-scheme');

const blockPath = nestedScheme.path({ block: 'button', elem: 'text' }); // button/__elem/

const info = {
    path: 'blocks/button/',
    depth: 1,
    naming: 'origin',
    level: 'blocks/'
};

const add = (file) => console.log(file);

nestedScheme.walk(info, add, err => console.log(err));

Event plugins

We can think of a lot of different events: entity, block, elem, mod, blockMod, elemMod, tech, techs, etc.

Obviously, if all events will be at the core of this can significantly slow down its work. We need to try to provide the API, which will extend the standard events.

Remove Russian docs

  • CHANGELOG.ru.md
  • README.ru.md

Documentation for multiple languages is more difficult to maintain.

The main part of the documentation is API so it is not difficult to understand.

Files Mode

Now we can get info about files that relate to entity parts. But we don't know anything about other files.

Example:

blocks/
  button/
    button.css # file relate to button with css tech
    icon.png   # unknown file

We could emit about icon.png file with

{
    entity: undefined,
    tech: undefined,
    level: 'blocks',
    path: 'blocks/button/icon.png'
}

For example such information may be useful for bemhint-fs-naming plugin to warn about such files.

API

We can split walk method to walk.files() and walk.bemFiles().

const walk = require('bem-walk');
const stringify = require('JSONStream').stringify;

walk.files(['path/to/level'], config)
    .pipe(stringify())
    .pipe(process.stdout);

// [{
//     entity: { block: 'button' },
//     tech: 'css',
//     level: 'blocks',
//     path: 'blocks/button/button.css'
// },
// {
//     entity: undefined,
//     tech: undefined,
//     level: 'blocks',
//     path: 'blocks/button/icon.png'
// }]

walk.bemFiles(['path/to/level'], config);

// [{
//     entity: { block: 'button' },
//     tech: 'css',
//     level: 'blocks',
//     path: 'blocks/button/button.css'
// }]

Implementation

I think no need add this logic to schemes.

If release #37 we can run scheme on each file:

const nested = require('...');

const files = getAllFilesList();

files.forEach(file => {
    let isAdded = false;

    nested(file, () =>  isAdded = true, (err, res) => {
        if (isAdded) {
            /* provide res */ 
        } else {
            /* provide object with only `path` and `level` fields */ 
        }
    });
});

`data` object: wrap bem entity fields into `entity`.

Actual data object:

{
     block: 'block',
     elem: 'elem',
     modName: 'mod',
     modVal: true,
     tech: 'tech',
     level: 'level',
     path: 'level/block__elem_mod'
}

Expected data object:

{
     entity: { block: 'block', elem: 'elem', modName: 'mod', modVal: true },
     tech: 'tech',
     level: 'level',
     path: 'level/block__elem_mod'
}

Often need to handle BEM entity. Now this knowledge is spread out in four fields, three of which are optional. How to detect type of BEM entity now:

var entity = { block: data.block },
    type;

data.elem && (entity.elem = data.elem);
data.modName && (entity.modName = data.modName);
data.modVal && (entity.modVal = data.modVal);

type = bemNaming.typeOf(entity);

How to make it easier:

var type = bemNaming.typeOf(data.entity);

Move level info to config

Now we can specify the object with path field and some optional fields: naming, scheme.

var walk = require('bem-walk'),
    opts = { scheme: 'nested' };

walk([{ path: 'path/to/level', scheme: 'flat' }], opts)

This API may be inconvenient for some cases. It is better to describe information on all possible levels, and then use only paths.

var walk = require('bem-walk'),
    config = {
        defaults: { scheme: 'nested' },
        levels: {
            'common.blocks': { scheme: 'flat' },
            'desktop.blocks': { scheme: 'flat' }
        }
    };

walk(['common.blocks', 'desktop.blocks'], config);

Layer should not contain absolute path

Atm BemCell.layer contains path to blocks directory. But layer is an Id of layer.

E.g. for classic scheme (${level}/${layer}.blocks/${entity.id}.${tech}) we have paths:

/levels/bem-core/common.blocks/page/page.css
/levels/bem-core/desktop.blocks/page/page.css

For modern scheme (${level}/${entity.id}@${layer}.${tech}) we have paths:

/levels/bem-core/blocks/page/page.css
/levels/bem-core/blocks/page/[email protected]

It means we should not include any absolute paths into layer and should provide level as a property of BemFile while BemCell should contains pure layer id.

Entities Mode

The event will emit when will provide information on an entity with each file in each level.

Example of file system:

common.blocks/
└── button/
    ├── button.css   # (1)
    └── button.js    # (2)

desktop.blocks/
└── button/
    └── button.css   # (3) (4)

Run the following code:

var walk = require('bem-walk'),
    walker = walk(['common.blocks', 'desktop.blocks']);

walker.on('data', function (data) {
    console.log(data);
});

walker.on('entity', function (data) {
    console.log(data);
});

The result will be 4 events:

// event (1) on `data`
{
     entity: { block: 'button' },
     tech: 'css',
     level: 'common.blocks',
     path: 'common.blocks/button/button.css'
}
// event (2) on `data`
{
     entity: { block: 'button' },
     tech: 'js',
     level: 'common.blocks',
     path: 'common.blocks/button/button.js'
}
// event (3) on `data`
{
     entity: { block: 'button' },
     tech: 'css',
     level: 'desktop.blocks',
     path: 'desktop.blocks/button/button.css'
}
// event (4) on `entity`
{
    entity: { block: 'button' },
    levels: ['common.blocks', 'desktop.blocks'],
    techs: ['css']
    files: [
        {
            path: 'common.blocks/button/button.css',
            level: 'common.blocks',
            tech: 'css'
        },
        {
            path: 'desktop.blocks/button/button.css',
            level: 'desktop.blocks',
            tech: 'css'
        }
    ]
}

Files method

This package should provide namespace instead of function.

Now:

const walk = require('bem-walk');

walk('path/to/level', opts);

Need:

const walk = require('bem-walk');

walk.files('path/to/level', opts);

It's necessary for #52 #20

Пустые блоки

Если на уровне есть пустые блоки, то не получаю никаких результатов, например:

blocks: {
    block1: {
        'block1.css': 'css content goes here'
    },
    block: {}
}

Без пустого блока block2 все работает.

Could not run benchmarks

bem-walk yeti-or$ npm run bench

> [email protected] bench /Users/yeti-or/Projects/BEM/bem-walk
> npm run bench-deps && matcha benchmark/*.js


> [email protected] bench-deps /Users/yeti-or/Projects/BEM/bem-walk
> cd benchmark && npm i && cd fixtures && bower i

child_process: customFds option is deprecated, use stdio instead.
module.js:338
    throw err;
          ^
Error: Cannot find module './fixtures'
    at Function.Module._resolveFilename (module.js:336:15)
    at Function.Module._load (module.js:278:25)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at Object.<anonymous> (/Users/yeti-or/Projects/BEM/bem-walk/benchmark/bem-walk.js:2:16)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)

Cache parsing of file stem for the same entity part

I think we can slightly speed up scanning if will cache parsing of file stem (basename without extension) for the same entity part.

Example:

button/
  __text/
    button__text.examples/
    button__text.tmpl-specs/
    button__text.gemini-bundles/
    button__text.css
    button__text.js
    button__text.deps.js
    button__text.bemhtml.js
    button__text.bh.js
    button__text.spec.js
    button__text.ru.md
    button__text.en.md

The button__text entity part has 11 techs. Each tech can be written in several levels.

This means that we will be scanning 11 (or more) files with button__text stem.

Now for each file will be called the bemNaming.parse() method.

Spaces in entity name

This one /Button Action/Button Action.svg doesn't work :(
But this /ButtonAction/ButtonAction.svg is ok.

Bug or feature?

Make logging more human friendly

now

require('@bem/walk')(['blocks']).on('data', console.log);

results in

BemFile {
  cell:
   BemCell {
     _entity: BemEntityName { block: 'b1' },
     _layer: 'blocks',
     _tech: 'css' },
  path: 'blocks/b1/b1.css' }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.