Skip to content

Commit 0eeb02d

Browse files
committed
Merge pull request #120 from bem/dev
Dev
2 parents 07e78af + dce7aa4 commit 0eeb02d

28 files changed

Lines changed: 249 additions & 170 deletions

.jscs.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ module.exports = {
1515
disallowSpacesInNamedFunctionExpression: {
1616
beforeOpeningRoundBrace: true
1717
},
18-
requireMultipleVarDecl: true,
1918
requireBlocksOnNewline: 1,
2019
disallowPaddingNewlinesInBlocks: true,
2120
disallowSpacesInsideArrayBrackets: 'nested',

.jshintrc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"bitwise": true,
3-
"curly": true,
43
"eqeqeq": true,
54
"es3": true,
65
"forin": true,

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,33 @@ Study the following `config.json` file:
369369
"ignoreDuplicateAttributes": false
370370
}
371371
```
372+
373+
## Masks
374+
375+
**html-differ** supports handling of _masks_ in HTML.
376+
377+
For example, the following two code samples will be considered to be equivalent:
378+
379+
```html
380+
<div id="{{[a-z]*\s\d+}}">
381+
```
382+
383+
```html
384+
<div id="text 12345">
385+
```
386+
387+
### Syntax
388+
389+
_Masks_ in **html-differ** have the following syntax:
390+
391+
```js
392+
{{RegExp}}
393+
```
394+
395+
where:
396+
397+
* `{{` – opening identifier of the _mask_.
398+
399+
* `RegExp` – regular expression for matching with the corresponding value in another HTML. The syntax is similar to regular expressions in JavaScript written in a literal notation.
400+
401+
* `}}` – closing identifier of the _mask_.

README.ru.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ var HtmlDiffer = require('html-differ').HtmlDiffer,
7171
htmlDiffer = new HtmlDiffer(options);
7272
```
7373

74-
Где `options` – это объект:
74+
где `options` – это объект:
7575

7676
* **ignoreAttributes: [ Array ]**
7777

@@ -366,3 +366,33 @@ $ html-differ --bem путь/к/html1 путь/к/html2
366366
"ignoreDuplicateAttributes": false
367367
}
368368
```
369+
370+
## Маски
371+
372+
**html-differ** поддерживает использование _масок_ в HTML.
373+
374+
Например, следующие два HTML будут считаться эквивалентными:
375+
376+
```html
377+
<div id="{{[a-z]*\s\d+}}">
378+
```
379+
380+
```html
381+
<div id="text 12345">
382+
```
383+
384+
### Синтаксис
385+
386+
Для записи _масок_ в **html-differ** используется следующий синтаксис:
387+
388+
```js
389+
{{RegExp}}
390+
```
391+
392+
где:
393+
394+
* `{{` – открывающий идентификатор _маски_.
395+
396+
* `RegExp` – регулярное выражение для сопоставления с соответствующим значением в сравниваемом HTML. Имеет такой же синтаксис как и регулярные выражения в JavaScript, записанные в _literal notation_.
397+
398+
* `}}` – закрывающий идентификатор _маски_.

lib/HtmlDiff.js

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
var diff = require('diff'),
2-
defaults = require('./utils/defaults'),
2+
defaults = require('./utils/defaults');
33

4-
/**
5-
* @class HtmlDiff
6-
* @constructor
7-
* @augments Diff
8-
* @param {Object} [options]
9-
* @param {String[]} [options.ignoreAttributes]
10-
* @param {String[]} [options.compareAttributesAsJSON]
11-
* @param {Boolean} [options.ignoreWhitespaces=true]
12-
* @param {Boolean} [options.ignoreComments]
13-
* @param {Boolean} [options.ignoreEndTags=false]
14-
* @param {Boolean} [options.ignoreDuplicateAttributes=false]
15-
*/
16-
HtmlDiff = function (options) {
17-
this.options = defaults(options);
18-
},
19-
/**
20-
* @class Diff
21-
* @constructor
22-
*/
23-
Diff = diff.Diff;
4+
/**
5+
* @class HtmlDiff
6+
* @constructor
7+
* @augments Diff
8+
* @param {Object} [options]
9+
* @param {String[]} [options.ignoreAttributes]
10+
* @param {String[]} [options.compareAttributesAsJSON]
11+
* @param {Boolean} [options.ignoreWhitespaces=true]
12+
* @param {Boolean} [options.ignoreComments]
13+
* @param {Boolean} [options.ignoreEndTags=false]
14+
* @param {Boolean} [options.ignoreDuplicateAttributes=false]
15+
*/
16+
var HtmlDiff = function (options) {
17+
this.options = defaults(options);
18+
};
19+
20+
/**
21+
* @class Diff
22+
* @constructor
23+
*/
24+
var Diff = diff.Diff;
2425

2526
HtmlDiff.prototype = Diff.prototype;
2627

lib/index.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
var _ = require('lodash'),
22
HtmlDiff = require('./HtmlDiff'),
33
modifyHtmlAccordingToOptions = require('./utils/modify'),
4-
defaults = require('./utils/defaults');
4+
defaults = require('./utils/defaults'),
5+
handleMasks = require('./utils/mask');
56

67
/**
78
* Tokenizes the given HTML
@@ -11,19 +12,19 @@ var _ = require('lodash'),
1112
HtmlDiff.prototype.tokenize = function (html) {
1213
html = modifyHtmlAccordingToOptions(html, this.options);
1314

14-
return _.filter(html.split(/([{}:;,<>"'\[\]\/]|\s+)/));
15+
return _.filter(html.split(/({{.+?[^\\]}}(?!})|[{}=:;,<>"'\[\]\/]|\s+)/));
1516
};
1617

1718
/**
1819
* @class HtmlDiffer
1920
* @constructor
20-
* @param {Object} [options]
21+
* @param {Object} [options]
2122
* @param {String[]} [options.ignoreAttributes]
2223
* @param {String[]} [options.compareAttributesAsJSON]
23-
* @param {Boolean} [options.ignoreWhitespaces=true]
24-
* @param {Boolean} [options.ignoreComments=true]
25-
* @param {Boolean} [options.ignoreEndTags=false]
26-
* @param {Boolean} [options.ignoreDuplicateAttributes=false]
24+
* @param {Boolean} [options.ignoreWhitespaces=true]
25+
* @param {Boolean} [options.ignoreComments=true]
26+
* @param {Boolean} [options.ignoreEndTags=false]
27+
* @param {Boolean} [options.ignoreDuplicateAttributes=false]
2728
*/
2829
var HtmlDiffer = function (options) {
2930
options = defaults(options);
@@ -40,9 +41,10 @@ var HtmlDiffer = function (options) {
4041
* @returns {Diff}
4142
*/
4243
HtmlDiffer.prototype.diffHtml = function (html1, html2) {
43-
var htmlDiffer = new HtmlDiff(this.options);
44+
var htmlDiffer = new HtmlDiff(this.options),
45+
diff = htmlDiffer.diff(html1, html2);
4446

45-
return htmlDiffer.diff(html1, html2);
47+
return handleMasks(diff);
4648
};
4749

4850
/**
@@ -54,8 +56,7 @@ HtmlDiffer.prototype.diffHtml = function (html1, html2) {
5456
* @returns {Boolean}
5557
*/
5658
HtmlDiffer.prototype.isEqual = function (html1, html2) {
57-
var htmlDiffer = new HtmlDiff(this.options),
58-
diff = htmlDiffer.diff(html1, html2);
59+
var diff = this.diffHtml(html1, html2);
5960

6061
return (diff.length === 1 && !diff[0].added && !diff[0].removed);
6162
};

lib/logger.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
require('colors');
22

3+
/**
4+
* @typedef {Object} Diff
5+
* @property {String} value
6+
* @property {String|undefined} added
7+
* @property {String|undefined} removed
8+
*/
9+
310
/**
411
* Returns readable diff text
5-
* @param {Object[]} diff
12+
* @param {Diff[]} diff
613
* @param {Object} [options]
714
* @param {Number} [options.charsAroundDiff=40]
815
* @returns {String}
@@ -19,15 +26,15 @@ function getDiffText(diff, options) {
1926
charsAroundDiff = 40;
2027
}
2128

22-
if (diff.length === 1 && !diff[0].added && !diff[0].removed) { return output; }
29+
if (diff.length === 1 && !diff[0].added && !diff[0].removed) return output;
2330

2431
diff.forEach(function (part) {
2532
var index = diff.indexOf(part),
2633
partValue = part.value,
2734
color = 'grey';
2835

29-
if (part.added) { color = 'green'; }
30-
if (part.removed) { color = 'red'; }
36+
if (part.added) color = 'green';
37+
if (part.removed) color = 'red';
3138

3239
if (color !== 'grey') {
3340
output += (!index ? '\n' : '') + partValue.inverse[color];

lib/utils/defaults.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ var _ = require('lodash');
22

33
/**
44
* Sets options
5-
* @param {Object} [options]
5+
* @param {Object} [options]
66
* @param {String[]} [options.ignoreAttributes]
77
* @param {String[]} [options.compareAttributesAsJSON]
8-
* @param {Boolean} [options.ignoreWhitespaces=true]
9-
* @param {Boolean} [options.ignoreComments=true]
10-
* @param {Boolean} [options.ignoreClosingTags=false]
11-
* @param {Boolean} [options.ignoreDuplicateAttributes=false]
8+
* @param {Boolean} [options.ignoreWhitespaces=true]
9+
* @param {Boolean} [options.ignoreComments=true]
10+
* @param {Boolean} [options.ignoreClosingTags=false]
11+
* @param {Boolean} [options.ignoreDuplicateAttributes=false]
1212
* returns {Object}
1313
*/
1414
module.exports = function (options) {

lib/utils/mask.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Mask
3+
* ====
4+
*
5+
* {{RegExp}}
6+
*/
7+
var MASK_REGEXP = /(.*){{(.+[^\\])}}(?!})(.*)/;
8+
9+
/**
10+
* Checks whether the given part of the diff should be added or removed
11+
* @param {Diff} part
12+
* @returns {Boolean}
13+
*/
14+
function _isDiffPart(part) {
15+
return part.added || part.removed;
16+
}
17+
18+
/**
19+
* Removes diff parts which are equal by mask
20+
* @param {Diff[]} diff
21+
* @returns {Diff[]}
22+
*/
23+
function _revealMasks(diff) {
24+
for (var i = 0; i < diff.length; i++) {
25+
var currPart = diff[i];
26+
27+
if (!_isDiffPart(currPart)) continue;
28+
29+
var prevPart = diff[i - 1],
30+
nextPart = diff[i + 1],
31+
matchedMask = currPart.value.match(MASK_REGEXP);
32+
33+
if (!matchedMask) continue;
34+
35+
var regExp = new RegExp('^' + matchedMask[1] + matchedMask[2] + matchedMask[3] + '$');
36+
if (currPart.added && nextPart && nextPart.removed) {
37+
if (nextPart.value.match(regExp)) {
38+
nextPart.removed = undefined;
39+
diff.splice(i--, 1);
40+
}
41+
} else if (currPart.removed && prevPart && prevPart.added) {
42+
if (prevPart.value.match(regExp)) {
43+
prevPart.added = undefined;
44+
diff.splice(i--, 1);
45+
}
46+
}
47+
}
48+
49+
return diff;
50+
}
51+
52+
/**
53+
* Concats not diff parts which go one by one
54+
* @param {Diff[]} diff
55+
* @returns {Diff[]}
56+
*/
57+
function _concatNotDiffParts(diff) {
58+
for (var i = 1; i < diff.length; i++) {
59+
var currPart = diff[i],
60+
prevPart = diff[i - 1];
61+
62+
if (!_isDiffPart(currPart) && !_isDiffPart(prevPart)) {
63+
prevPart.value += currPart.value;
64+
65+
diff.splice(i--, 1);
66+
}
67+
}
68+
69+
return diff;
70+
}
71+
72+
/**
73+
* @typedef {Object} Diff
74+
* @property {String} value
75+
* @property {String|undefined} added
76+
* @property {String|undefined} removed
77+
*/
78+
79+
/**
80+
* Handles masks in the given diff
81+
* @param {Diff[]} diff
82+
* @returns {Diff[]}
83+
*/
84+
module.exports = function (diff) {
85+
diff = _revealMasks(diff);
86+
87+
return _concatNotDiffParts(diff);
88+
};

lib/utils/modify.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ var SimpleApiParser = require('parse5').SimpleApiParser,
55
/**
66
* Parses the given HTML and modifies it according to the given options
77
* @param {String} value
8-
* @param {Object} [options]
8+
* @param {Object} [options]
99
* @param {String[]} [options.ignoreAttributes]
1010
* @param {String[]} [options.compareAttributesAsJSON]
11-
* @param {Boolean} [options.ignoreWhitespaces=true]
12-
* @param {Boolean} [options.ignoreComments=true]
13-
* @param {Boolean} [options.ignoreEndTags=false]
14-
* @param {Boolean} [options.ignoreDuplicateAttributes=false]
11+
* @param {Boolean} [options.ignoreWhitespaces=true]
12+
* @param {Boolean} [options.ignoreComments=true]
13+
* @param {Boolean} [options.ignoreEndTags=false]
14+
* @param {Boolean} [options.ignoreDuplicateAttributes=false]
1515
* returns {String}
1616
*/
1717
function modify(value, options) {

0 commit comments

Comments
 (0)