From e36080a3ff7e30a622375ee34a65378dae2b06ef Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 8 Jul 2010 19:04:36 +0200 Subject: [PATCH] preliminary support for evaluating JavaScript code inside LESS --- lib/less/index.js | 2 +- lib/less/parser.js | 19 +++++++++++++++++-- lib/less/tree/javascript.js | 32 ++++++++++++++++++++++++++++++++ lib/less/tree/mixin.js | 9 +++++---- lib/less/tree/ruleset.js | 11 +++++++---- test/css/javascript.css | 9 +++++++++ test/less/javascript.less | 10 ++++++++++ 7 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 lib/less/tree/javascript.js create mode 100644 test/css/javascript.css create mode 100644 test/less/javascript.less diff --git a/lib/less/index.js b/lib/less/index.js index 115619fc..5f283e26 100644 --- a/lib/less/index.js +++ b/lib/less/index.js @@ -78,7 +78,7 @@ var less = { 'keyword', 'variable', 'ruleset', 'element', 'selector', 'quoted', 'expression', 'rule', 'call', 'url', 'alpha', 'import', - 'mixin', 'comment', 'anonymous', 'value' + 'mixin', 'comment', 'anonymous', 'value', 'javascript' ].forEach(function (n) { require(path.join('less', 'tree', n)); }); diff --git a/lib/less/parser.js b/lib/less/parser.js index 3c4aa818..83c56fde 100644 --- a/lib/less/parser.js +++ b/lib/less/parser.js @@ -574,6 +574,21 @@ less.Parser = function Parser(env) { if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm)?/)) { return new(tree.Dimension)(value[1], value[2]); } + }, + + // + // JavaScript code to be evaluated + // + // `window.location.href` + // + javascript: function () { + var str; + + if (input.charAt(i) !== '`') { return } + + if (str = $(/^`([^`]*)`/)) { + return new(tree.JavaScript)(str[1], i); + } } }, @@ -699,7 +714,7 @@ less.Parser = function Parser(env) { // entity: function () { return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) || - $(this.entities.call) || $(this.entities.keyword); + $(this.entities.call) || $(this.entities.keyword) || $(this.entities.javascript); }, // @@ -858,7 +873,7 @@ less.Parser = function Parser(env) { if (c === '.' || c === '#' || c === '&') { return } if (name = $(this.variable) || $(this.property)) { - if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*(;{}-]*);/.exec(chunks[j]))) { + if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) { i += match[0].length - 1; value = new(tree.Anonymous)(match[1]); } else if (name === "font") { diff --git a/lib/less/tree/javascript.js b/lib/less/tree/javascript.js new file mode 100644 index 00000000..46eb783d --- /dev/null +++ b/lib/less/tree/javascript.js @@ -0,0 +1,32 @@ +(function (tree) { + +tree.JavaScript = function (string, index) { + this.expression = string; + this.index = index; +}; +tree.JavaScript.prototype = { + toCSS: function () { + return this.evaluated; + }, + eval: function (env) { + var result, + expression = new(Function)('return (' + this.expression + ')'), + context = {}; + + for (var k in env.frames[0].variables()) { + context[k.slice(1)] = env.frames[0].variables()[k].value.eval(env).toCSS(); + } + + try { + result = expression.call(context); + this.evaluated = JSON.stringify(result); + } catch (e) { + throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" , + index: this.index }; + } + return this; + } +}; + +})(require('less/tree')); + diff --git a/lib/less/tree/mixin.js b/lib/less/tree/mixin.js index 3c5cd1b7..7f85f88e 100644 --- a/lib/less/tree/mixin.js +++ b/lib/less/tree/mixin.js @@ -55,10 +55,11 @@ tree.mixin.Definition = function (name, params, rules) { this.frames = []; }; tree.mixin.Definition.prototype = { - toCSS: function () { return "" }, - variable: function (name) { return this.parent.variable.call(this, name) }, - find: function () { return this.parent.find.apply(this, arguments) }, - rulesets: function () { return this.parent.rulesets.apply(this) }, + toCSS: function () { return "" }, + variable: function (name) { return this.parent.variable.call(this, name) }, + variables: function () { return this.parent.variables.call(this) }, + find: function () { return this.parent.find.apply(this, arguments) }, + rulesets: function () { return this.parent.rulesets.apply(this) }, eval: function (env, args) { var frame = new(tree.Ruleset)(null, []), context; diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 2ab21dee..61ad1bdc 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -56,17 +56,20 @@ tree.Ruleset.prototype = { match: function (args) { return !args || args.length === 0; }, - variable: function (name) { - if (this._variables) { return this._variables[name] } + variables: function () { + if (this._variables) { return this._variables } else { - return (this._variables = this.rules.reduce(function (hash, r) { + return this._variables = this.rules.reduce(function (hash, r) { if (r instanceof tree.Rule && r.variable === true) { hash[r.name] = r; } return hash; - }, {}))[name]; + }, {}); } }, + variable: function (name) { + return this.variables()[name]; + }, rulesets: function () { if (this._rulesets) { return this._rulesets } else { diff --git a/test/css/javascript.css b/test/css/javascript.css new file mode 100644 index 00000000..0cf9eb05 --- /dev/null +++ b/test/css/javascript.css @@ -0,0 +1,9 @@ +.eval { + js: 42; + js: 2; + js: "hello world"; + sigkill: 9; +} +.scope { + var: 42; +} diff --git a/test/less/javascript.less b/test/less/javascript.less new file mode 100644 index 00000000..d5532833 --- /dev/null +++ b/test/less/javascript.less @@ -0,0 +1,10 @@ +.eval { + js: `42`; + js: `1 + 1`; + js: `"hello world"`; + sigkill: `process.SIGKILL`; +} +.scope { + @foo: 42; + var: `this.foo`; +}