Skip to content

Commit

Permalink
feat(variables): allow regex in "name" field
Browse files Browse the repository at this point in the history
  • Loading branch information
eliranmal committed Sep 25, 2016
1 parent 3d836be commit 12acab7
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 29 deletions.
18 changes: 15 additions & 3 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,23 @@ module.exports = function (grunt) {
name: 'myNumDefaultVar',
from: '10px',
to: '20em'
},
}
]
}
},
'variables_regex-name': {
files: {
'tmp/variables/regex-name': 'test/fixtures/variables'
},
options: {
variables: [
{
name: /my[-_]?[Vv]ar/,
//from: '10px',
to: 'watwatwat'
to: 1000000000000
},
{
name: /my[-_]?[Nn]um[-_]?[Vv]ar/,
to: -1
}
]
}
Expand Down
73 changes: 47 additions & 26 deletions tasks/lib/sass-replace.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,62 +16,71 @@ exports.init = function (grunt) {

replacements = [].concat(variableReplacements, importReplacements);

grunt.verbose.writeln('effective string-replace replacements:');
grunt.verbose.writeln(stringify(replacements));
bla('effective string-replace replacements:');
bla(stringify(replacements));

return replacements;
};


// todo - add option to preserve !default in variables
// todo - (which means the default behavior will be to overwrite everything after the ':')

// todo - add option to pass regex as filter in the "name" field, e.g. name: /my[-_][vV]ar/

function variableReplacementBuilder(v) {
var pattern,
name = v.name,
from = v.from,
to = v.to;

if (isUndefined(name) && isUndefined(from)) {
grunt.log.errorlns('one of "name" or "from" must be defined in a variable replacement');
err('one of "name" or "from" must be defined in a variable replacement');
return false;
}

if (!isUndefined(name) && !isString(name)) {
grunt.log.errorlns('"name" must be a string in a variable replacement');
if (!isUndefined(name) && !isString(name) && !isRegex(name)) {
err('"name" must be a string or a regex in a variable replacement');
return false;
}

if (isUndefined(to)) {
grunt.log.errorlns('"to" must be defined in a variable replacement');
err('"to" must be defined in a variable replacement');
return false;
}

pattern = buildVariableReplacementPattern(from, name);
bla('> building variable replacement', 'name: ' + name, 'from: ' + from, 'to: ' + to);

pattern = buildVariableReplacementPattern(name, from);

return {
pattern: new RegExp(pattern, 'gm'),
replacement: '$1' + to + '$2'
replacement: function (match, p1, p2, p3) {
bla('> replacement callback', 'match: ' + match, 'p1: ' + p1, 'p2: ' + p2, 'p3: ' + p3);
return p1 + to + p3;
}
};
}

function buildVariableReplacementPattern(from, name) {
name = name ? asRegexString(name) : '\\S+'; // match at least one non-whitespace character
from = from ? asRegexString(from) : '.*';
function buildVariableReplacementPattern(name, from) {
if (isUndefined(name)) {
name = '\\S+'; // match at least one non-whitespace character
} else if (isString(name)) {
name = regexEscape(name);
} else if (isRegex(name)) {
name = name.source;
}
from = from ? regexEscape(from) : '[^\\s"\';!]*';
bla('effective name: ' + name, 'effective from: ' + from);
return [
/**/ '^', // start line
/**/ '(', // start capture first group
/**/ '(', // start capture group $1
/**/ '\\$', // name must start with $
/**/ name, //
/**/ '(' + name + ')', // capture group $2 (for debugging)
/**/ ':\\s*["\']?', // the value's optional open quote
/**/ ')', // end capture first group
/**/ ')', // end capture group $1
/**/ from, //
/**/ '(', // start capture second group
/**/ '(', // start capture group $3
/**/ '["\']?\\s*', // the value's optional close quote
/**/ '(?:(?:!default\\s*;)|;)', // allow for !default after value
/**/ ')', // end capture second group
/**/ ')', // end capture group $3
/**/ '$' // end line
].join('');
}
Expand All @@ -81,8 +90,8 @@ exports.init = function (grunt) {
from = i.from,
to = i.to;

if (!isString(from) || !isString(to)) {
grunt.log.errorlns('both "from" and "to" must be defined and of type string in an import replacement');
if ((isUndefined(from) || !isString(from)) || (isUndefined(to) || !isString(to))) {
err('both "from" and "to" must be defined and of type string in an import replacement');
return false;
}

Expand All @@ -95,14 +104,14 @@ exports.init = function (grunt) {
}

function buildImportReplacementPattern(from) {
from = asRegexString(from);
from = regexEscape(from);
return [
/**/ '^', // start line
/**/ '(', // start group $1
/**/ '\\s*', // allow for indentation (e.g. for nested imports)
/**/ '\\s*', // allow indentation (e.g. for nested imports)
///**/ '@import\\s+', //
/**/ '(?:', //
/**/ '(?:@import\\s+)|(?:@import.*,\\s*)', // allow for multiple values, e.g. @import "wat", "wow"; todo - can i do this with \2 capturing the whole second group + from variable ?
/**/ '(?:@import\\s+)|(?:@import.*,\\s*)', // allow multiple values, e.g. @import "wat", "wow"; todo - can i do this with \2 capturing the whole second group + from variable ?
///**/ '@import(?=\\s+|.*,\\s*)', // attempt the previous line with assertions
/**/ ')', //
/**/ '(?:', //
Expand Down Expand Up @@ -146,7 +155,7 @@ exports.init = function (grunt) {
return replacements;
}

function asRegexString(str) {
function regexEscape(str) {
return (str + '').replace(/(["'\*\.\-\?\$\{}])/g, '\\$1');
}

Expand All @@ -160,7 +169,7 @@ exports.init = function (grunt) {
}

function isString(val) {
return val && typeof val === 'string';
return typeof val === 'string';
}

function isRegex(val) {
Expand All @@ -171,5 +180,17 @@ exports.init = function (grunt) {
return typeof val === 'undefined';
}

var log = lineLogger(grunt.log.writeln);
var bla = lineLogger(grunt.verbose.writeln);
var err = lineLogger(grunt.log.errorlns);

function lineLogger(fn) {
return function logLines() {
[].slice.call(arguments).forEach(function (line) {
fn(line);
});
};
}

return exports;
};
12 changes: 12 additions & 0 deletions test/expected/variables/regex-name
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
$my-var: "1000000000000";
$my-default-var: "foo" !default;
$my_var: "1000000000000";
$my_default_var: "foo" !default;
$myVar: "1000000000000";
$myDefaultVar: "foo" !default;
$my-num-var: -1;
$my-num-default-var: 10 !default;
$my_num_var: -1;
$my_num_default_var: 3.333 !default;
$myNumVar: -1;
$myNumDefaultVar: 10px !default;
10 changes: 10 additions & 0 deletions test/sass-replace-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ exports['sass-replace'] = {
test.done();
},

variablesRegexName: function (test) {
test.expect(1);

var actual = grunt.file.read('tmp/variables/regex-name');
var expected = grunt.file.read('test/expected/variables/regex-name');
test.equal(actual, expected, 'should replace sass variable values, filtering by variable name as regex');

test.done();
},

variablesNoop: function (test) {
test.expect(1);

Expand Down

0 comments on commit 12acab7

Please sign in to comment.