untangle the mess of dependencies and remove all circular dependencies. Remove un-necessary dependency injection.

This commit is contained in:
Luke Page
2014-08-24 17:55:46 +01:00
parent 119c930fa6
commit 16746e9b1e
47 changed files with 2620 additions and 2680 deletions

View File

@@ -309,7 +309,7 @@ function loadStyles(modifyVars) {
for (var i = 0; i < styles.length; i++) {
style = styles[i];
if (style.type.match(typePattern)) {
var env = new less.tree.parseEnv(options),
var env = new less.contexts.parseEnv(options),
lessText = style.innerHTML || '';
env.filename = document.location.href.replace(/#.*$/, '');
@@ -340,7 +340,7 @@ function loadStyles(modifyVars) {
function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
var env = new less.tree.parseEnv(options);
var env = new less.contexts.parseEnv(options);
env.mime = sheet.type;
if (modifyVars || options.globalVars) {

View File

@@ -1,138 +1,139 @@
module.exports = function (tree) {
var contexts = {};
module.exports = contexts;
var parseCopyProperties = [
'paths', // option - unmodified - paths to search for imports on
'files', // list of files that have been imported, used for import-once
'contents', // map - filename to contents of all the files
'contentsIgnoredChars', // map - filename to lines at the begining of each file to ignore
'relativeUrls', // option - whether to adjust URL's to be relative
'rootpath', // option - rootpath to append to URL's
'strictImports', // option -
'insecure', // option - whether to allow imports from insecure ssl hosts
'dumpLineNumbers', // option - whether to dump line numbers
'compress', // option - whether to compress
'processImports', // option - whether to process imports. if false then imports will not be imported
'syncImport', // option - whether to import synchronously
'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true
'chunkInput', // option - whether to chunk input. more performant but causes parse issues.
'mime', // browser only - mime type for sheet import
'useFileCache', // browser only - whether to use the per file session cache
'currentFileInfo' // information about the current file - for error reporting and importing and making urls relative etc.
var copyFromOriginal = function copyFromOriginal(original, destination, propertiesToCopy) {
if (!original) { return; }
for(var i = 0; i < propertiesToCopy.length; i++) {
if (original.hasOwnProperty(propertiesToCopy[i])) {
destination[propertiesToCopy[i]] = original[propertiesToCopy[i]];
}
}
};
var parseCopyProperties = [
'paths', // option - unmodified - paths to search for imports on
'files', // list of files that have been imported, used for import-once
'contents', // map - filename to contents of all the files
'contentsIgnoredChars', // map - filename to lines at the begining of each file to ignore
'relativeUrls', // option - whether to adjust URL's to be relative
'rootpath', // option - rootpath to append to URL's
'strictImports', // option -
'insecure', // option - whether to allow imports from insecure ssl hosts
'dumpLineNumbers', // option - whether to dump line numbers
'compress', // option - whether to compress
'processImports', // option - whether to process imports. if false then imports will not be imported
'syncImport', // option - whether to import synchronously
'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true
'chunkInput', // option - whether to chunk input. more performant but causes parse issues.
'mime', // browser only - mime type for sheet import
'useFileCache', // browser only - whether to use the per file session cache
'currentFileInfo' // information about the current file - for error reporting and importing and making urls relative etc.
];
//currentFileInfo = {
// 'relativeUrls' - option - whether to adjust URL's to be relative
// 'filename' - full resolved filename of current file
// 'rootpath' - path to append to normal URLs for this node
// 'currentDirectory' - path to the current file, absolute
// 'rootFilename' - filename of the base file
// 'entryPath' - absolute path to the entry file
// 'reference' - whether the file should not be output and only output parts that are referenced
contexts.parseEnv = function(options) {
copyFromOriginal(options, this, parseCopyProperties);
if (!this.contents) { this.contents = {}; }
if (!this.contentsIgnoredChars) { this.contentsIgnoredChars = {}; }
if (!this.files) { this.files = {}; }
if (typeof this.paths === "string") { this.paths = [this.paths]; }
if (!this.currentFileInfo) {
var filename = (options && options.filename) || "input";
var entryPath = filename.replace(/[^\/\\]*$/, "");
if (options) {
options.filename = null;
}
this.currentFileInfo = {
filename: filename,
relativeUrls: this.relativeUrls,
rootpath: (options && options.rootpath) || "",
currentDirectory: entryPath,
entryPath: entryPath,
rootFilename: filename
};
}
};
var evalCopyProperties = [
'silent', // whether to swallow errors and warnings
'verbose', // whether to log more activity
'compress', // whether to compress
'yuicompress', // whether to compress with the outside tool yui compressor
'ieCompat', // whether to enforce IE compatibility (IE8 data-uri)
'strictMath', // whether math has to be within parenthesis
'strictUnits', // whether units need to evaluate correctly
'cleancss', // whether to compress with clean-css
'sourceMap', // whether to output a source map
'importMultiple', // whether we are currently importing multiple copies
'urlArgs' // whether to add args into url tokens
];
//currentFileInfo = {
// 'relativeUrls' - option - whether to adjust URL's to be relative
// 'filename' - full resolved filename of current file
// 'rootpath' - path to append to normal URLs for this node
// 'currentDirectory' - path to the current file, absolute
// 'rootFilename' - filename of the base file
// 'entryPath' - absolute path to the entry file
// 'reference' - whether the file should not be output and only output parts that are referenced
tree.parseEnv = function(options) {
copyFromOriginal(options, this, parseCopyProperties);
if (!this.contents) { this.contents = {}; }
if (!this.contentsIgnoredChars) { this.contentsIgnoredChars = {}; }
if (!this.files) { this.files = {}; }
if (typeof this.paths === "string") { this.paths = [this.paths]; }
if (!this.currentFileInfo) {
var filename = (options && options.filename) || "input";
var entryPath = filename.replace(/[^\/\\]*$/, "");
if (options) {
options.filename = null;
}
this.currentFileInfo = {
filename: filename,
relativeUrls: this.relativeUrls,
rootpath: (options && options.rootpath) || "",
currentDirectory: entryPath,
entryPath: entryPath,
rootFilename: filename
};
}
};
var evalCopyProperties = [
'silent', // whether to swallow errors and warnings
'verbose', // whether to log more activity
'compress', // whether to compress
'yuicompress', // whether to compress with the outside tool yui compressor
'ieCompat', // whether to enforce IE compatibility (IE8 data-uri)
'strictMath', // whether math has to be within parenthesis
'strictUnits', // whether units need to evaluate correctly
'cleancss', // whether to compress with clean-css
'sourceMap', // whether to output a source map
'importMultiple', // whether we are currently importing multiple copies
'urlArgs' // whether to add args into url tokens
];
tree.evalEnv = function(options, frames) {
copyFromOriginal(options, this, evalCopyProperties);
this.frames = frames || [];
};
tree.evalEnv.prototype.inParenthesis = function () {
if (!this.parensStack) {
this.parensStack = [];
}
this.parensStack.push(true);
};
tree.evalEnv.prototype.outOfParenthesis = function () {
this.parensStack.pop();
};
tree.evalEnv.prototype.isMathOn = function () {
return this.strictMath ? (this.parensStack && this.parensStack.length) : true;
};
tree.evalEnv.prototype.isPathRelative = function (path) {
return !/^(?:[a-z-]+:|\/)/.test(path);
};
tree.evalEnv.prototype.normalizePath = function( path ) {
var
segments = path.split("/").reverse(),
segment;
path = [];
while (segments.length !== 0 ) {
segment = segments.pop();
switch( segment ) {
case ".":
break;
case "..":
if ((path.length === 0) || (path[path.length - 1] === "..")) {
path.push( segment );
} else {
path.pop();
}
break;
default:
path.push( segment );
break;
}
}
return path.join("/");
};
//todo - do the same for the toCSS env
//tree.toCSSEnv = function (options) {
//};
var copyFromOriginal = function(original, destination, propertiesToCopy) {
if (!original) { return; }
for(var i = 0; i < propertiesToCopy.length; i++) {
if (original.hasOwnProperty(propertiesToCopy[i])) {
destination[propertiesToCopy[i]] = original[propertiesToCopy[i]];
}
}
};
contexts.evalEnv = function(options, frames) {
copyFromOriginal(options, this, evalCopyProperties);
this.frames = frames || [];
};
contexts.evalEnv.prototype.inParenthesis = function () {
if (!this.parensStack) {
this.parensStack = [];
}
this.parensStack.push(true);
};
contexts.evalEnv.prototype.outOfParenthesis = function () {
this.parensStack.pop();
};
contexts.evalEnv.prototype.isMathOn = function () {
return this.strictMath ? (this.parensStack && this.parensStack.length) : true;
};
contexts.evalEnv.prototype.isPathRelative = function (path) {
return !/^(?:[a-z-]+:|\/)/.test(path);
};
contexts.evalEnv.prototype.normalizePath = function( path ) {
var
segments = path.split("/").reverse(),
segment;
path = [];
while (segments.length !== 0 ) {
segment = segments.pop();
switch( segment ) {
case ".":
break;
case "..":
if ((path.length === 0) || (path[path.length - 1] === "..")) {
path.push( segment );
} else {
path.pop();
}
break;
default:
path.push( segment );
break;
}
}
return path.join("/");
};
//todo - do the same for the toCSS env
//tree.toCSSEnv = function (options) {
//};

View File

@@ -1,25 +1,23 @@
module.exports = function(functions, tree) {
var defaultFunc = {
eval: function () {
var v = this.value_, e = this.error_;
if (e) {
throw e;
}
if (v != null) {
return v ? tree.True : tree.False;
}
},
value: function (v) {
this.value_ = v;
},
error: function (e) {
this.error_ = e;
},
reset: function () {
this.value_ = this.error_ = null;
}
};
var Keyword = require("../tree/keyword.js");
functions.functionRegistry.add("default", defaultFunc.eval.bind(defaultFunc));
tree.defaultFunc = defaultFunc;
var defaultFunc = {
eval: function () {
var v = this.value_, e = this.error_;
if (e) {
throw e;
}
if (v != null) {
return v ? Keyword.True : Keyword.False;
}
},
value: function (v) {
this.value_ = v;
},
error: function (e) {
this.error_ = e;
},
reset: function () {
this.value_ = this.error_ = null;
}
};
module.exports = defaultFunc;

View File

@@ -7,7 +7,11 @@ module.exports = function(less, tree) {
require("./color.js")(functions, tree);
require("./color-blending.js")(functions, tree);
require("./data-uri.js")(functions, tree, less);
require("./default.js")(functions, tree);
var defaultFunc = require("./default.js");
functions.functionRegistry.add("default", defaultFunc.eval.bind(defaultFunc));
tree.defaultFunc = defaultFunc;
require("./math.js")(functions, tree);
require("./number.js")(functions, tree);
require("./string.js")(functions, tree);

View File

@@ -1,9 +1,10 @@
var Keyword = require("../tree/keyword.js");
module.exports = function(functions, tree) {
var isa = function (n, Type) {
return (n instanceof Type) ? tree.True : tree.False;
return (n instanceof Type) ? Keyword.True : Keyword.False;
},
isunit = function (n, unit) {
return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? tree.True : tree.False;
return (n instanceof tree.Dimension) && n.unit.is(unit.value || unit) ? Keyword.True : Keyword.False;
};
functions.functionRegistry.addMultiple({
iscolor: function (n) {

View File

@@ -5,11 +5,11 @@ var less = {
}
};
less.tree = (require('./tree'))(less, less.data);
less.tree = require('./tree');
less.visitor = require('./visitor/index.js')(less, less.tree);
less.Parser = (require('./parser'))(less, less.tree, less.visitor);
less.functions = (require('./functions/index.js'))(less, less.tree);
require('./env')(less.tree);
less.contexts = require("./env.js");
less.tree.sourceMapOutput = require('./source-map-output.js')(less);

View File

@@ -1,5 +1,6 @@
var chunker = require('./chunker.js');
var LessError = require('./less-error.js');
var contexts = require("./env.js");
module.exports = function(less, tree, visitor) {
//
@@ -51,8 +52,8 @@ var Parser = function Parser(env) {
// Top parser on an import tree must be sure there is one "env"
// which will then be passed around by reference.
if (!(env instanceof tree.parseEnv)) {
env = new tree.parseEnv(env);
if (!(env instanceof contexts.parseEnv)) {
env = new contexts.parseEnv(env);
}
var imports = this.imports = {
@@ -109,7 +110,7 @@ var Parser = function Parser(env) {
}
newFileInfo.filename = resolvedFilename;
var newEnv = new tree.parseEnv(env);
var newEnv = new contexts.parseEnv(env);
newEnv.currentFileInfo = newFileInfo;
newEnv.processImports = false;
@@ -451,7 +452,7 @@ var Parser = function Parser(env) {
options = options || {};
var evaldRoot,
css,
evalEnv = new tree.evalEnv(options);
evalEnv = new contexts.evalEnv(options);
//
// Allows setting variables with a hash, so:

View File

@@ -1,144 +1,40 @@
module.exports = function (less, data) {
var tree = {};
tree.debugInfo = function(env, ctx, lineSeperator) {
var result="";
if (env.dumpLineNumbers && !env.compress) {
switch(env.dumpLineNumbers) {
case 'comments':
result = tree.debugInfo.asComment(ctx);
break;
case 'mediaquery':
result = tree.debugInfo.asMediaQuery(ctx);
break;
case 'all':
result = tree.debugInfo.asComment(ctx) + (lineSeperator || "") + tree.debugInfo.asMediaQuery(ctx);
break;
}
}
return result;
};
tree.debugInfo.asComment = function(ctx) {
return '/* line ' + ctx.debugInfo.lineNumber + ', ' + ctx.debugInfo.fileName + ' */\n';
};
tree.debugInfo.asMediaQuery = function(ctx) {
return '@media -sass-debug-info{filename{font-family:' +
('file://' + ctx.debugInfo.fileName).replace(/([.:\/\\])/g, function (a) {
if (a == '\\') {
a = '\/';
}
return '\\' + a;
}) +
'}line{font-family:\\00003' + ctx.debugInfo.lineNumber + '}}\n';
};
tree.find = function (obj, fun) {
for (var i = 0, r; i < obj.length; i++) {
r = fun.call(obj, obj[i]);
if (r) { return r; }
}
return null;
};
tree.jsify = function (obj) {
if (Array.isArray(obj.value) && (obj.value.length > 1)) {
return '[' + obj.value.map(function (v) { return v.toCSS(); }).join(', ') + ']';
} else {
return obj.toCSS();
}
};
tree.toCSS = function (env) {
var strs = [];
this.genCSS(env, {
add: function(chunk, fileInfo, index) {
strs.push(chunk);
},
isEmpty: function () {
return strs.length === 0;
}
});
return strs.join('');
};
tree.outputRuleset = function (env, output, rules) {
var ruleCnt = rules.length, i;
env.tabLevel = (env.tabLevel | 0) + 1;
// Compressed
if (env.compress) {
output.add('{');
for (i = 0; i < ruleCnt; i++) {
rules[i].genCSS(env, output);
}
output.add('}');
env.tabLevel--;
return;
}
// Non-compressed
var tabSetStr = '\n' + Array(env.tabLevel).join(" "), tabRuleStr = tabSetStr + " ";
if (!ruleCnt) {
output.add(" {" + tabSetStr + '}');
} else {
output.add(" {" + tabRuleStr);
rules[0].genCSS(env, output);
for (i = 1; i < ruleCnt; i++) {
output.add(tabRuleStr);
rules[i].genCSS(env, output);
}
output.add(tabSetStr + '}');
}
env.tabLevel--;
};
tree.Alpha = require('./tree/alpha')(tree);
tree.Color = require('./tree/color')(data, tree);
tree.Directive = require('./tree/directive')(tree);
tree.DetachedRuleset = require('./tree/detached-ruleset')(tree);
tree.Operation = require('./tree/operation')(tree);
tree.Dimension = require('./tree/dimension')(tree, require('./tree/unit-conversions')); //todo move conversions
tree.Unit = require('./tree/unit')(tree, require('./tree/unit-conversions'));
tree.Keyword = require('./tree/keyword')(tree);
tree.Variable = require('./tree/variable')(tree);
tree.Ruleset = require('./tree/ruleset')(tree);
tree.Element = require('./tree/element')(tree);
tree.Attribute = require('./tree/attribute')(tree);
tree.Combinator = require('./tree/combinator')(tree);
tree.Selector = require('./tree/selector')(tree);
tree.Quoted = require('./tree/quoted')(tree);
tree.Expression = require('./tree/expression')(tree);
tree.Rule = require('./tree/rule')(tree);
tree.Call = require('./tree/call')(tree, less);
tree.URL = require('./tree/url')(tree);
tree.Import = require('./tree/import')(tree);
tree.Alpha = require('./tree/alpha');
tree.Color = require('./tree/color');
tree.Directive = require('./tree/directive');
tree.DetachedRuleset = require('./tree/detached-ruleset');
tree.Operation = require('./tree/operation');
tree.Dimension = require('./tree/dimension');
tree.Unit = require('./tree/unit');
tree.Keyword = require('./tree/keyword');
tree.Variable = require('./tree/variable');
tree.Ruleset = require('./tree/ruleset');
tree.Element = require('./tree/element');
tree.Attribute = require('./tree/attribute');
tree.Combinator = require('./tree/combinator');
tree.Selector = require('./tree/selector');
tree.Quoted = require('./tree/quoted');
tree.Expression = require('./tree/expression');
tree.Rule = require('./tree/rule');
tree.Call = require('./tree/call');
tree.URL = require('./tree/url');
tree.Import = require('./tree/import');
tree.mixin = {
Call: require('./tree/mixin-call')(tree),
Definition: require('./tree/mixin-definition')(tree)
Call: require('./tree/mixin-call'),
Definition: require('./tree/mixin-definition')
};
tree.Comment = require('./tree/comment')(tree);
tree.Anonymous = require('./tree/anonymous')(tree);
tree.Value = require('./tree/value')(tree);
tree.JavaScript = require('./tree/javascript')(tree);
tree.Assignment = require('./tree/assignment')(tree);
tree.Condition = require('./tree/condition')(tree);
tree.Paren = require('./tree/paren')(tree);
tree.Media = require('./tree/media')(tree);
tree.UnicodeDescriptor = require('./tree/unicode-descriptor')(tree);
tree.Negative = require('./tree/negative')(tree);
tree.Extend = require('./tree/extend')(tree);
tree.RulesetCall = require('./tree/ruleset-call')(tree);
tree.Comment = require('./tree/comment');
tree.Anonymous = require('./tree/anonymous');
tree.Value = require('./tree/value');
tree.JavaScript = require('./tree/javascript');
tree.Assignment = require('./tree/assignment');
tree.Condition = require('./tree/condition');
tree.Paren = require('./tree/paren');
tree.Media = require('./tree/media');
tree.UnicodeDescriptor = require('./tree/unicode-descriptor');
tree.Negative = require('./tree/negative');
tree.Extend = require('./tree/extend');
tree.RulesetCall = require('./tree/ruleset-call');
tree.fround = function(env, value) {
var precision = env && env.numPrecision;
//add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded...
return (precision == null) ? value : Number((value + 2e-16).toFixed(precision));
};
return tree;
};
module.exports = tree;

View File

@@ -1,29 +1,28 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Alpha = function (val) {
this.value = val;
};
Alpha.prototype = {
type: "Alpha",
accept: function (visitor) {
this.value = visitor.visit(this.value);
},
eval: function (env) {
if (this.value.eval) { return new Alpha(this.value.eval(env)); }
return this;
},
genCSS: function (env, output) {
output.add("alpha(opacity=");
Alpha.prototype = new Node();
Alpha.prototype.type = "Alpha";
if (this.value.genCSS) {
this.value.genCSS(env, output);
} else {
output.add(this.value);
}
Alpha.prototype.accept = function (visitor) {
this.value = visitor.visit(this.value);
};
Alpha.prototype.eval = function (env) {
if (this.value.eval) { return new Alpha(this.value.eval(env)); }
return this;
};
Alpha.prototype.genCSS = function (env, output) {
output.add("alpha(opacity=");
output.add(")");
},
toCSS: tree.toCSS
};
return Alpha;
if (this.value.genCSS) {
this.value.genCSS(env, output);
} else {
output.add(this.value);
}
output.add(")");
};
module.exports = Alpha;

View File

@@ -1,4 +1,4 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike) {
this.value = value;
@@ -7,32 +7,29 @@ var Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike)
this.currentFileInfo = currentFileInfo;
this.rulesetLike = (typeof rulesetLike === 'undefined')? false : rulesetLike;
};
Anonymous.prototype = {
type: "Anonymous",
eval: function () {
return new Anonymous(this.value, this.index, this.currentFileInfo, this.mapLines, this.rulesetLike);
},
compare: function (x) {
if (!x.toCSS) {
return -1;
}
var left = this.toCSS(),
right = x.toCSS();
if (left === right) {
return 0;
}
return left < right ? -1 : 1;
},
isRulesetLike: function() {
return this.rulesetLike;
},
genCSS: function (env, output) {
output.add(this.value, this.currentFileInfo, this.index, this.mapLines);
},
toCSS: tree.toCSS
Anonymous.prototype = new Node();
Anonymous.prototype.type = "Anonymous";
Anonymous.prototype.eval = function () {
return new Anonymous(this.value, this.index, this.currentFileInfo, this.mapLines, this.rulesetLike);
};
return Anonymous;
Anonymous.prototype.compare = function (x) {
if (!x.toCSS) {
return -1;
}
var left = this.toCSS(),
right = x.toCSS();
if (left === right) {
return 0;
}
return left < right ? -1 : 1;
};
Anonymous.prototype.isRulesetLike = function() {
return this.rulesetLike;
};
Anonymous.prototype.genCSS = function (env, output) {
output.add(this.value, this.currentFileInfo, this.index, this.mapLines);
};
module.exports = Anonymous;

View File

@@ -1,29 +1,28 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Assignment = function (key, val) {
this.key = key;
this.value = val;
};
Assignment.prototype = {
type: "Assignment",
accept: function (visitor) {
this.value = visitor.visit(this.value);
},
eval: function (env) {
if (this.value.eval) {
return new(Assignment)(this.key, this.value.eval(env));
}
return this;
},
genCSS: function (env, output) {
output.add(this.key + '=');
if (this.value.genCSS) {
this.value.genCSS(env, output);
} else {
output.add(this.value);
}
},
toCSS: tree.toCSS
Assignment.prototype = new Node();
Assignment.prototype.type = "Assignment";
Assignment.prototype.accept = function (visitor) {
this.value = visitor.visit(this.value);
};
return Assignment;
Assignment.prototype.eval = function (env) {
if (this.value.eval) {
return new(Assignment)(this.key, this.value.eval(env));
}
return this;
};
Assignment.prototype.genCSS = function (env, output) {
output.add(this.key + '=');
if (this.value.genCSS) {
this.value.genCSS(env, output);
} else {
output.add(this.value);
}
};
module.exports = Assignment;

View File

@@ -1,29 +1,27 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Attribute = function (key, op, value) {
this.key = key;
this.op = op;
this.value = value;
};
Attribute.prototype = {
type: "Attribute",
eval: function (env) {
return new(Attribute)(this.key.eval ? this.key.eval(env) : this.key,
this.op, (this.value && this.value.eval) ? this.value.eval(env) : this.value);
},
genCSS: function (env, output) {
output.add(this.toCSS(env));
},
toCSS: function (env) {
var value = this.key.toCSS ? this.key.toCSS(env) : this.key;
Attribute.prototype = new Node();
Attribute.prototype.type = "Attribute";
Attribute.prototype.eval = function (env) {
return new(Attribute)(this.key.eval ? this.key.eval(env) : this.key,
this.op, (this.value && this.value.eval) ? this.value.eval(env) : this.value);
};
Attribute.prototype.genCSS = function (env, output) {
output.add(this.toCSS(env));
};
Attribute.prototype.toCSS = function (env) {
var value = this.key.toCSS ? this.key.toCSS(env) : this.key;
if (this.op) {
value += this.op;
value += (this.value.toCSS ? this.value.toCSS(env) : this.value);
}
return '[' + value + ']';
if (this.op) {
value += this.op;
value += (this.value.toCSS ? this.value.toCSS(env) : this.value);
}
return '[' + value + ']';
};
return Attribute;
};
module.exports = Attribute;

View File

@@ -1,5 +1,4 @@
module.exports = function (tree, less) {
var Node = require("./node.js");
//
// A function call node.
//
@@ -9,61 +8,57 @@ var Call = function (name, args, index, currentFileInfo) {
this.index = index;
this.currentFileInfo = currentFileInfo;
};
Call.prototype = {
type: "Call",
accept: function (visitor) {
if (this.args) {
this.args = visitor.visitArray(this.args);
}
},
//
// When evaluating a function call,
// we either find the function in `less.functions` [1],
// in which case we call it, passing the evaluated arguments,
// if this returns null or we cannot find the function, we
// simply print it out as it appeared originally [2].
//
// The *functions.js* file contains the built-in functions.
//
// The reason why we evaluate the arguments, is in the case where
// we try to pass a variable to a function, like: `saturate(@color)`.
// The function should receive the value, not the variable.
//
eval: function (env) {
var args = this.args.map(function (a) { return a.eval(env); }),
result, funcCaller = new less.functions.functionCaller(this.name, env, this.currentFileInfo);
if (funcCaller.isValid()) { // 1.
try {
result = funcCaller.call(args);
if (result != null) {
return result;
}
} catch (e) {
throw { type: e.type || "Runtime",
message: "error evaluating function `" + this.name + "`" +
(e.message ? ': ' + e.message : ''),
index: this.index, filename: this.currentFileInfo.filename };
}
}
return new Call(this.name, args, this.index, this.currentFileInfo);
},
genCSS: function (env, output) {
output.add(this.name + "(", this.currentFileInfo, this.index);
for(var i = 0; i < this.args.length; i++) {
this.args[i].genCSS(env, output);
if (i + 1 < this.args.length) {
output.add(", ");
}
}
output.add(")");
},
toCSS: tree.toCSS
Call.prototype = new Node();
Call.prototype.type = "Call";
Call.prototype.accept = function (visitor) {
if (this.args) {
this.args = visitor.visitArray(this.args);
}
};
return Call;
//
// When evaluating a function call,
// we either find the function in `less.functions` [1],
// in which case we call it, passing the evaluated arguments,
// if this returns null or we cannot find the function, we
// simply print it out as it appeared originally [2].
//
// The *functions.js* file contains the built-in functions.
//
// The reason why we evaluate the arguments, is in the case where
// we try to pass a variable to a function, like: `saturate(@color)`.
// The function should receive the value, not the variable.
//
Call.prototype.eval = function (env) {
var args = this.args.map(function (a) { return a.eval(env); }),
FunctionCaller = require("../non-node-index.js").functions.functionCaller, //TODO! Move out
result, funcCaller = new FunctionCaller(this.name, env, this.currentFileInfo);
if (funcCaller.isValid()) { // 1.
try {
result = funcCaller.call(args);
if (result != null) {
return result;
}
} catch (e) {
throw { type: e.type || "Runtime",
message: "error evaluating function `" + this.name + "`" +
(e.message ? ': ' + e.message : ''),
index: this.index, filename: this.currentFileInfo.filename };
}
}
return new Call(this.name, args, this.index, this.currentFileInfo);
};
Call.prototype.genCSS = function (env, output) {
output.add(this.name + "(", this.currentFileInfo, this.index);
for(var i = 0; i < this.args.length; i++) {
this.args[i].genCSS(env, output);
if (i + 1 < this.args.length) {
output.add(", ");
}
}
output.add(")");
};
module.exports = Call;

View File

@@ -1,4 +1,6 @@
module.exports = function (data, tree) {
var Node = require("./node.js"),
colors = require("../data/colors.js");
//
// RGB Colors - #ff0014, #eee
//
@@ -23,150 +25,157 @@ var Color = function (rgb, a) {
this.alpha = typeof(a) === 'number' ? a : 1;
};
Color.prototype = {
type: "Color",
eval: function () { return this; },
luma: function () {
var r = this.rgb[0] / 255,
g = this.rgb[1] / 255,
b = this.rgb[2] / 255;
Color.prototype = new Node();
Color.prototype.type = "Color";
r = (r <= 0.03928) ? r / 12.92 : Math.pow(((r + 0.055) / 1.055), 2.4);
g = (g <= 0.03928) ? g / 12.92 : Math.pow(((g + 0.055) / 1.055), 2.4);
b = (b <= 0.03928) ? b / 12.92 : Math.pow(((b + 0.055) / 1.055), 2.4);
function clamp(v, max) {
return Math.min(Math.max(v, 0), max);
}
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
},
function toHex(v) {
return '#' + v.map(function (c) {
c = clamp(Math.round(c), 255);
return (c < 16 ? '0' : '') + c.toString(16);
}).join('');
}
genCSS: function (env, output) {
output.add(this.toCSS(env));
},
toCSS: function (env, doNotCompress) {
var compress = env && env.compress && !doNotCompress, color, alpha;
Color.prototype.luma = function () {
var r = this.rgb[0] / 255,
g = this.rgb[1] / 255,
b = this.rgb[2] / 255;
// `keyword` is set if this color was originally
// converted from a named color string so we need
// to respect this and try to output named color too.
if (this.keyword) {
return this.keyword;
}
r = (r <= 0.03928) ? r / 12.92 : Math.pow(((r + 0.055) / 1.055), 2.4);
g = (g <= 0.03928) ? g / 12.92 : Math.pow(((g + 0.055) / 1.055), 2.4);
b = (b <= 0.03928) ? b / 12.92 : Math.pow(((b + 0.055) / 1.055), 2.4);
// If we have some transparency, the only way to represent it
// is via `rgba`. Otherwise, we use the hex representation,
// which has better compatibility with older browsers.
// Values are capped between `0` and `255`, rounded and zero-padded.
alpha = tree.fround(env, this.alpha);
if (alpha < 1) {
return "rgba(" + this.rgb.map(function (c) {
return clamp(Math.round(c), 255);
}).concat(clamp(alpha, 1))
.join(',' + (compress ? '' : ' ')) + ")";
}
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
Color.prototype.genCSS = function (env, output) {
output.add(this.toCSS(env));
};
Color.prototype.toCSS = function (env, doNotCompress) {
var compress = env && env.compress && !doNotCompress, color, alpha;
color = this.toRGB();
if (compress) {
var splitcolor = color.split('');
// Convert color to short format
if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) {
color = '#' + splitcolor[1] + splitcolor[3] + splitcolor[5];
}
}
return color;
},
//
// Operations have to be done per-channel, if not,
// channels will spill onto each other. Once we have
// our result, in the form of an integer triplet,
// we create a new Color node to hold the result.
//
operate: function (env, op, other) {
var rgb = [];
var alpha = this.alpha * (1 - other.alpha) + other.alpha;
for (var c = 0; c < 3; c++) {
rgb[c] = tree.operate(env, op, this.rgb[c], other.rgb[c]);
}
return new(Color)(rgb, alpha);
},
toRGB: function () {
return toHex(this.rgb);
},
toHSL: function () {
var r = this.rgb[0] / 255,
g = this.rgb[1] / 255,
b = this.rgb[2] / 255,
a = this.alpha;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2, d = max - min;
if (max === min) {
h = s = 0;
} else {
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h * 360, s: s, l: l, a: a };
},
//Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
toHSV: function () {
var r = this.rgb[0] / 255,
g = this.rgb[1] / 255,
b = this.rgb[2] / 255,
a = this.alpha;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, v = max;
var d = max - min;
if (max === 0) {
s = 0;
} else {
s = d / max;
}
if (max === min) {
h = 0;
} else {
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h * 360, s: s, v: v, a: a };
},
toARGB: function () {
return toHex([this.alpha * 255].concat(this.rgb));
},
compare: function (x) {
if (!x.rgb) {
return -1;
}
return (x.rgb[0] === this.rgb[0] &&
x.rgb[1] === this.rgb[1] &&
x.rgb[2] === this.rgb[2] &&
x.alpha === this.alpha) ? 0 : -1;
// `keyword` is set if this color was originally
// converted from a named color string so we need
// to respect this and try to output named color too.
if (this.keyword) {
return this.keyword;
}
// If we have some transparency, the only way to represent it
// is via `rgba`. Otherwise, we use the hex representation,
// which has better compatibility with older browsers.
// Values are capped between `0` and `255`, rounded and zero-padded.
alpha = this.fround(env, this.alpha);
if (alpha < 1) {
return "rgba(" + this.rgb.map(function (c) {
return clamp(Math.round(c), 255);
}).concat(clamp(alpha, 1))
.join(',' + (compress ? '' : ' ')) + ")";
}
color = this.toRGB();
if (compress) {
var splitcolor = color.split('');
// Convert color to short format
if (splitcolor[1] === splitcolor[2] && splitcolor[3] === splitcolor[4] && splitcolor[5] === splitcolor[6]) {
color = '#' + splitcolor[1] + splitcolor[3] + splitcolor[5];
}
}
return color;
};
//
// Operations have to be done per-channel, if not,
// channels will spill onto each other. Once we have
// our result, in the form of an integer triplet,
// we create a new Color node to hold the result.
//
Color.prototype.operate = function (env, op, other) {
var rgb = [];
var alpha = this.alpha * (1 - other.alpha) + other.alpha;
for (var c = 0; c < 3; c++) {
rgb[c] = this._operate(env, op, this.rgb[c], other.rgb[c]);
}
return new(Color)(rgb, alpha);
};
Color.prototype.toRGB = function () {
return toHex(this.rgb);
};
Color.prototype.toHSL = function () {
var r = this.rgb[0] / 255,
g = this.rgb[1] / 255,
b = this.rgb[2] / 255,
a = this.alpha;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2, d = max - min;
if (max === min) {
h = s = 0;
} else {
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h * 360, s: s, l: l, a: a };
};
//Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
Color.prototype.toHSV = function () {
var r = this.rgb[0] / 255,
g = this.rgb[1] / 255,
b = this.rgb[2] / 255,
a = this.alpha;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, v = max;
var d = max - min;
if (max === 0) {
s = 0;
} else {
s = d / max;
}
if (max === min) {
h = 0;
} else {
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return { h: h * 360, s: s, v: v, a: a };
};
Color.prototype.toARGB = function () {
return toHex([this.alpha * 255].concat(this.rgb));
};
Color.prototype.compare = function (x) {
if (!x.rgb) {
return -1;
}
return (x.rgb[0] === this.rgb[0] &&
x.rgb[1] === this.rgb[1] &&
x.rgb[2] === this.rgb[2] &&
x.alpha === this.alpha) ? 0 : -1;
};
Color.fromKeyword = function(keyword) {
var c, key = keyword.toLowerCase();
if (data.colors.hasOwnProperty(key)) {
c = new(Color)(data.colors[key].slice(1));
if (colors.hasOwnProperty(key)) {
c = new(Color)(colors[key].slice(1));
}
else if (key === "transparent") {
c = new(Color)([0, 0, 0], 0);
@@ -177,16 +186,4 @@ Color.fromKeyword = function(keyword) {
return c;
}
};
function toHex(v) {
return '#' + v.map(function (c) {
c = clamp(Math.round(c), 255);
return (c < 16 ? '0' : '') + c.toString(16);
}).join('');
}
function clamp(v, max) {
return Math.min(Math.max(v, 0), max);
}
return Color;
};
module.exports = Color;

View File

@@ -1,4 +1,4 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Combinator = function (value) {
if (value === ' ') {
@@ -7,18 +7,15 @@ var Combinator = function (value) {
this.value = value ? value.trim() : "";
}
};
Combinator.prototype = {
type: "Combinator",
_noSpaceCombinators: {
'': true,
' ': true,
'|': true
},
genCSS: function (env, output) {
var spaceOrEmpty = (env.compress || this._noSpaceCombinators[this.value]) ? '' : ' ';
output.add(spaceOrEmpty + this.value + spaceOrEmpty);
},
toCSS: tree.toCSS
Combinator.prototype = new Node();
Combinator.prototype.type = "Combinator";
var _noSpaceCombinators = {
'': true,
' ': true,
'|': true
};
return Combinator;
Combinator.prototype.genCSS = function (env, output) {
var spaceOrEmpty = (env.compress || _noSpaceCombinators[this.value]) ? '' : ' ';
output.add(spaceOrEmpty + this.value + spaceOrEmpty);
};
module.exports = Combinator;

View File

@@ -1,28 +1,28 @@
module.exports = function (tree) {
var Node = require("./node.js"),
getDebugInfo = require("./debug-info.js");
var Comment = function (value, isLineComment, index, currentFileInfo) {
this.value = value;
this.isLineComment = isLineComment;
this.currentFileInfo = currentFileInfo;
};
Comment.prototype = {
type: "Comment",
genCSS: function (env, output) {
if (this.debugInfo) {
output.add(tree.debugInfo(env, this), this.currentFileInfo, this.index);
}
output.add(this.value);
},
toCSS: tree.toCSS,
isSilent: function(env) {
var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced),
isCompressed = env.compress && this.value[2] !== "!";
return this.isLineComment || isReference || isCompressed;
},
eval: function () { return this; },
markReferenced: function () {
this.isReferenced = true;
Comment.prototype = new Node();
Comment.prototype.type = "Comment";
Comment.prototype.genCSS = function (env, output) {
if (this.debugInfo) {
output.add(getDebugInfo(env, this), this.currentFileInfo, this.index);
}
output.add(this.value);
};
return Comment;
Comment.prototype.isSilent = function(env) {
var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced),
isCompressed = env.compress && this.value[2] !== "!";
return this.isLineComment || isReference || isCompressed;
};
Comment.prototype.markReferenced = function () {
this.isReferenced = true;
};
Comment.prototype.isRulesetLike = function(root) {
return Boolean(root);
};
module.exports = Comment;

View File

@@ -1,4 +1,4 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Condition = function (op, l, r, i, negate) {
this.op = op.trim();
@@ -7,43 +7,41 @@ var Condition = function (op, l, r, i, negate) {
this.index = i;
this.negate = negate;
};
Condition.prototype = {
type: "Condition",
accept: function (visitor) {
this.lvalue = visitor.visit(this.lvalue);
this.rvalue = visitor.visit(this.rvalue);
},
eval: function (env) {
var a = this.lvalue.eval(env),
b = this.rvalue.eval(env);
var i = this.index, result;
result = (function (op) {
switch (op) {
case 'and':
return a && b;
case 'or':
return a || b;
default:
if (a.compare) {
result = a.compare(b);
} else if (b.compare) {
result = b.compare(a);
} else {
throw { type: "Type",
message: "Unable to perform comparison",
index: i };
}
switch (result) {
case -1: return op === '<' || op === '=<' || op === '<=';
case 0: return op === '=' || op === '>=' || op === '=<' || op === '<=';
case 1: return op === '>' || op === '>=';
}
}
})(this.op);
return this.negate ? !result : result;
}
Condition.prototype = new Node();
Condition.prototype.type = "Condition";
Condition.prototype.accept = function (visitor) {
this.lvalue = visitor.visit(this.lvalue);
this.rvalue = visitor.visit(this.rvalue);
};
return Condition;
Condition.prototype.eval = function (env) {
var a = this.lvalue.eval(env),
b = this.rvalue.eval(env);
var i = this.index, result;
result = (function (op) {
switch (op) {
case 'and':
return a && b;
case 'or':
return a || b;
default:
if (a.compare) {
result = a.compare(b);
} else if (b.compare) {
result = b.compare(a);
} else {
throw { type: "Type",
message: "Unable to perform comparison",
index: i };
}
switch (result) {
case -1: return op === '<' || op === '=<' || op === '<=';
case 0: return op === '=' || op === '>=' || op === '=<' || op === '<=';
case 1: return op === '>' || op === '>=';
}
}
})(this.op);
return this.negate ? !result : result;
};
module.exports = Condition;

View File

@@ -0,0 +1,34 @@
var debugInfo = function(env, ctx, lineSeperator) {
var result="";
if (env.dumpLineNumbers && !env.compress) {
switch(env.dumpLineNumbers) {
case 'comments':
result = debugInfo.asComment(ctx);
break;
case 'mediaquery':
result = debugInfo.asMediaQuery(ctx);
break;
case 'all':
result = debugInfo.asComment(ctx) + (lineSeperator || "") + debugInfo.asMediaQuery(ctx);
break;
}
}
return result;
};
debugInfo.asComment = function(ctx) {
return '/* line ' + ctx.debugInfo.lineNumber + ', ' + ctx.debugInfo.fileName + ' */\n';
};
debugInfo.asMediaQuery = function(ctx) {
return '@media -sass-debug-info{filename{font-family:' +
('file://' + ctx.debugInfo.fileName).replace(/([.:\/\\])/g, function (a) {
if (a == '\\') {
a = '\/';
}
return '\\' + a;
}) +
'}line{font-family:\\00003' + ctx.debugInfo.lineNumber + '}}\n';
};
module.exports = debugInfo;

View File

@@ -1,21 +1,21 @@
module.exports = function (tree) {
var Node = require("./node.js"),
contexts = require("../env.js");
var DetachedRuleset = function (ruleset, frames) {
this.ruleset = ruleset;
this.frames = frames;
};
DetachedRuleset.prototype = {
type: "DetachedRuleset",
accept: function (visitor) {
this.ruleset = visitor.visit(this.ruleset);
},
eval: function (env) {
var frames = this.frames || env.frames.slice(0);
return new DetachedRuleset(this.ruleset, frames);
},
callEval: function (env) {
return this.ruleset.eval(this.frames ? new(tree.evalEnv)(env, this.frames.concat(env.frames)) : env);
}
DetachedRuleset.prototype = new Node();
DetachedRuleset.prototype.type = "DetachedRuleset";
DetachedRuleset.prototype.evalFirst = true;
DetachedRuleset.prototype.accept = function (visitor) {
this.ruleset = visitor.visit(this.ruleset);
};
return DetachedRuleset;
DetachedRuleset.prototype.eval = function (env) {
var frames = this.frames || env.frames.slice(0);
return new DetachedRuleset(this.ruleset, frames);
};
DetachedRuleset.prototype.callEval = function (env) {
return this.ruleset.eval(this.frames ? new(contexts.evalEnv)(env, this.frames.concat(env.frames)) : env);
};
module.exports = DetachedRuleset;

View File

@@ -1,167 +1,164 @@
module.exports = function (tree, unitConversions) {
var Node = require("./node.js"),
unitConversions = require("../data/unit-conversions.js"),
Unit = require("./unit.js"),
Color = require("./color.js");
//
// A number with a unit
//
var Dimension = function (value, unit) {
this.value = parseFloat(value);
this.unit = (unit && unit instanceof tree.Unit) ? unit :
new(tree.Unit)(unit ? [unit] : undefined);
this.unit = (unit && unit instanceof Unit) ? unit :
new(Unit)(unit ? [unit] : undefined);
};
Dimension.prototype = {
type: "Dimension",
accept: function (visitor) {
this.unit = visitor.visit(this.unit);
},
eval: function (env) {
return this;
},
toColor: function () {
return new(tree.Color)([this.value, this.value, this.value]);
},
genCSS: function (env, output) {
if ((env && env.strictUnits) && !this.unit.isSingular()) {
throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());
Dimension.prototype = new Node();
Dimension.prototype.type = "Dimension";
Dimension.prototype.accept = function (visitor) {
this.unit = visitor.visit(this.unit);
};
Dimension.prototype.eval = function (env) {
return this;
};
Dimension.prototype.toColor = function () {
return new(Color)([this.value, this.value, this.value]);
};
Dimension.prototype.genCSS = function (env, output) {
if ((env && env.strictUnits) && !this.unit.isSingular()) {
throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());
}
var value = this.fround(env, this.value),
strValue = String(value);
if (value !== 0 && value < 0.000001 && value > -0.000001) {
// would be output 1e-6 etc.
strValue = value.toFixed(20).replace(/0+$/, "");
}
if (env && env.compress) {
// Zero values doesn't need a unit
if (value === 0 && this.unit.isLength()) {
output.add(strValue);
return;
}
var value = tree.fround(env, this.value),
strValue = String(value);
if (value !== 0 && value < 0.000001 && value > -0.000001) {
// would be output 1e-6 etc.
strValue = value.toFixed(20).replace(/0+$/, "");
// Float values doesn't need a leading zero
if (value > 0 && value < 1) {
strValue = (strValue).substr(1);
}
}
if (env && env.compress) {
// Zero values doesn't need a unit
if (value === 0 && this.unit.isLength()) {
output.add(strValue);
return;
}
output.add(strValue);
this.unit.genCSS(env, output);
};
// Float values doesn't need a leading zero
if (value > 0 && value < 1) {
strValue = (strValue).substr(1);
}
}
// In an operation between two Dimensions,
// we default to the first Dimension's unit,
// so `1px + 2` will yield `3px`.
Dimension.prototype.operate = function (env, op, other) {
/*jshint noempty:false */
var value = this._operate(env, op, this.value, other.value),
unit = this.unit.clone();
output.add(strValue);
this.unit.genCSS(env, output);
},
toCSS: tree.toCSS,
// In an operation between two Dimensions,
// we default to the first Dimension's unit,
// so `1px + 2` will yield `3px`.
operate: function (env, op, other) {
/*jshint noempty:false */
var value = tree.operate(env, op, this.value, other.value),
unit = this.unit.clone();
if (op === '+' || op === '-') {
if (unit.numerator.length === 0 && unit.denominator.length === 0) {
unit.numerator = other.unit.numerator.slice(0);
unit.denominator = other.unit.denominator.slice(0);
} else if (other.unit.numerator.length === 0 && unit.denominator.length === 0) {
// do nothing
} else {
other = other.convertTo(this.unit.usedUnits());
if(env.strictUnits && other.unit.toString() !== unit.toString()) {
throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() +
"' and '" + other.unit.toString() + "'.");
}
value = tree.operate(env, op, this.value, other.value);
}
} else if (op === '*') {
unit.numerator = unit.numerator.concat(other.unit.numerator).sort();
unit.denominator = unit.denominator.concat(other.unit.denominator).sort();
unit.cancel();
} else if (op === '/') {
unit.numerator = unit.numerator.concat(other.unit.denominator).sort();
unit.denominator = unit.denominator.concat(other.unit.numerator).sort();
unit.cancel();
}
return new(Dimension)(value, unit);
},
compare: function (other) {
if (other instanceof Dimension) {
var a, b,
aValue, bValue;
if (this.unit.isEmpty() || other.unit.isEmpty()) {
a = this;
b = other;
} else {
a = this.unify();
b = other.unify();
if (a.unit.compare(b.unit) !== 0) {
return -1;
}
}
aValue = a.value;
bValue = b.value;
if (bValue > aValue) {
return -1;
} else if (bValue < aValue) {
return 1;
} else {
return 0;
}
if (op === '+' || op === '-') {
if (unit.numerator.length === 0 && unit.denominator.length === 0) {
unit.numerator = other.unit.numerator.slice(0);
unit.denominator = other.unit.denominator.slice(0);
} else if (other.unit.numerator.length === 0 && unit.denominator.length === 0) {
// do nothing
} else {
return -1;
}
},
other = other.convertTo(this.unit.usedUnits());
unify: function () {
return this.convertTo({ length: 'px', duration: 's', angle: 'rad' });
},
convertTo: function (conversions) {
var value = this.value, unit = this.unit.clone(),
i, groupName, group, targetUnit, derivedConversions = {}, applyUnit;
if (typeof conversions === 'string') {
for(i in unitConversions) {
if (unitConversions[i].hasOwnProperty(conversions)) {
derivedConversions = {};
derivedConversions[i] = conversions;
}
}
conversions = derivedConversions;
}
applyUnit = function (atomicUnit, denominator) {
/*jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit)) {
if (denominator) {
value = value / (group[atomicUnit] / group[targetUnit]);
} else {
value = value * (group[atomicUnit] / group[targetUnit]);
}
return targetUnit;
if(env.strictUnits && other.unit.toString() !== unit.toString()) {
throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '" + unit.toString() +
"' and '" + other.unit.toString() + "'.");
}
return atomicUnit;
};
for (groupName in conversions) {
if (conversions.hasOwnProperty(groupName)) {
targetUnit = conversions[groupName];
group = unitConversions[groupName];
unit.map(applyUnit);
}
value = this._operate(env, op, this.value, other.value);
}
} else if (op === '*') {
unit.numerator = unit.numerator.concat(other.unit.numerator).sort();
unit.denominator = unit.denominator.concat(other.unit.denominator).sort();
unit.cancel();
} else if (op === '/') {
unit.numerator = unit.numerator.concat(other.unit.denominator).sort();
unit.denominator = unit.denominator.concat(other.unit.numerator).sort();
unit.cancel();
}
return new(Dimension)(value, unit);
};
Dimension.prototype.compare = function (other) {
if (other instanceof Dimension) {
var a, b,
aValue, bValue;
return new(Dimension)(value, unit);
if (this.unit.isEmpty() || other.unit.isEmpty()) {
a = this;
b = other;
} else {
a = this.unify();
b = other.unify();
if (a.unit.compare(b.unit) !== 0) {
return -1;
}
}
aValue = a.value;
bValue = b.value;
if (bValue > aValue) {
return -1;
} else if (bValue < aValue) {
return 1;
} else {
return 0;
}
} else {
return -1;
}
};
return Dimension;
Dimension.prototype.unify = function () {
return this.convertTo({ length: 'px', duration: 's', angle: 'rad' });
};
Dimension.prototype.convertTo = function (conversions) {
var value = this.value, unit = this.unit.clone(),
i, groupName, group, targetUnit, derivedConversions = {}, applyUnit;
if (typeof conversions === 'string') {
for(i in unitConversions) {
if (unitConversions[i].hasOwnProperty(conversions)) {
derivedConversions = {};
derivedConversions[i] = conversions;
}
}
conversions = derivedConversions;
}
applyUnit = function (atomicUnit, denominator) {
/*jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit)) {
if (denominator) {
value = value / (group[atomicUnit] / group[targetUnit]);
} else {
value = value * (group[atomicUnit] / group[targetUnit]);
}
return targetUnit;
}
return atomicUnit;
};
for (groupName in conversions) {
if (conversions.hasOwnProperty(groupName)) {
targetUnit = conversions[groupName];
group = unitConversions[groupName];
unit.map(applyUnit);
}
}
unit.cancel();
return new(Dimension)(value, unit);
};
module.exports = Dimension;

View File

@@ -1,4 +1,5 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Ruleset = require("./ruleset.js");
var Directive = function (name, value, rules, index, currentFileInfo, debugInfo) {
this.name = name;
@@ -12,64 +13,92 @@ var Directive = function (name, value, rules, index, currentFileInfo, debugInfo)
this.debugInfo = debugInfo;
};
Directive.prototype = {
type: "Directive",
accept: function (visitor) {
var value = this.value, rules = this.rules;
if (rules) {
rules = visitor.visit(rules);
}
if (value) {
value = visitor.visit(value);
}
},
isRulesetLike: function() {
return !this.isCharset();
},
isCharset: function() {
return "@charset" === this.name;
},
genCSS: function (env, output) {
var value = this.value, rules = this.rules;
output.add(this.name, this.currentFileInfo, this.index);
if (value) {
output.add(' ');
value.genCSS(env, output);
}
if (rules) {
tree.outputRuleset(env, output, [rules]);
} else {
output.add(';');
}
},
toCSS: tree.toCSS,
eval: function (env) {
var value = this.value, rules = this.rules;
if (value) {
value = value.eval(env);
}
if (rules) {
rules = rules.eval(env);
rules.root = true;
}
return new(Directive)(this.name, value, rules,
this.index, this.currentFileInfo, this.debugInfo);
},
variable: function (name) { if (this.rules) return tree.Ruleset.prototype.variable.call(this.rules, name); },
find: function () { if (this.rules) return tree.Ruleset.prototype.find.apply(this.rules, arguments); },
rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
markReferenced: function () {
var i, rules;
this.isReferenced = true;
if (this.rules) {
rules = this.rules.rules;
for (i = 0; i < rules.length; i++) {
if (rules[i].markReferenced) {
rules[i].markReferenced();
}
Directive.prototype = new Node();
Directive.prototype.type = "Directive";
Directive.prototype.accept = function (visitor) {
var value = this.value, rules = this.rules;
if (rules) {
rules = visitor.visit(rules);
}
if (value) {
value = visitor.visit(value);
}
};
Directive.prototype.isRulesetLike = function() {
return this.rules || !this.isCharset();
};
Directive.prototype.isCharset = function() {
return "@charset" === this.name;
};
Directive.prototype.genCSS = function (env, output) {
var value = this.value, rules = this.rules;
output.add(this.name, this.currentFileInfo, this.index);
if (value) {
output.add(' ');
value.genCSS(env, output);
}
if (rules) {
this.outputRuleset(env, output, [rules]);
} else {
output.add(';');
}
};
Directive.prototype.eval = function (env) {
var value = this.value, rules = this.rules;
if (value) {
value = value.eval(env);
}
if (rules) {
rules = rules.eval(env);
rules.root = true;
}
return new(Directive)(this.name, value, rules,
this.index, this.currentFileInfo, this.debugInfo);
};
Directive.prototype.variable = function (name) { if (this.rules) return Ruleset.prototype.variable.call(this.rules, name); };
Directive.prototype.find = function () { if (this.rules) return Ruleset.prototype.find.apply(this.rules, arguments); };
Directive.prototype.rulesets = function () { if (this.rules) return Ruleset.prototype.rulesets.apply(this.rules); };
Directive.prototype.markReferenced = function () {
var i, rules;
this.isReferenced = true;
if (this.rules) {
rules = this.rules.rules;
for (i = 0; i < rules.length; i++) {
if (rules[i].markReferenced) {
rules[i].markReferenced();
}
}
}
};
return Directive;
Directive.prototype.outputRuleset = function (env, output, rules) {
var ruleCnt = rules.length, i;
env.tabLevel = (env.tabLevel | 0) + 1;
// Compressed
if (env.compress) {
output.add('{');
for (i = 0; i < ruleCnt; i++) {
rules[i].genCSS(env, output);
}
output.add('}');
env.tabLevel--;
return;
}
// Non-compressed
var tabSetStr = '\n' + Array(env.tabLevel).join(" "), tabRuleStr = tabSetStr + " ";
if (!ruleCnt) {
output.add(" {" + tabSetStr + '}');
} else {
output.add(" {" + tabRuleStr);
rules[0].genCSS(env, output);
for (i = 1; i < ruleCnt; i++) {
output.add(tabRuleStr);
rules[i].genCSS(env, output);
}
output.add(tabSetStr + '}');
}
env.tabLevel--;
};
module.exports = Directive;

View File

@@ -1,8 +1,9 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Combinator = require("./combinator.js");
var Element = function (combinator, value, index, currentFileInfo) {
this.combinator = combinator instanceof tree.Combinator ?
combinator : new(tree.Combinator)(combinator);
this.combinator = combinator instanceof Combinator ?
combinator : new(Combinator)(combinator);
if (typeof(value) === 'string') {
this.value = value.trim();
@@ -14,32 +15,30 @@ var Element = function (combinator, value, index, currentFileInfo) {
this.index = index;
this.currentFileInfo = currentFileInfo;
};
Element.prototype = {
type: "Element",
accept: function (visitor) {
var value = this.value;
this.combinator = visitor.visit(this.combinator);
if (typeof value === "object") {
this.value = visitor.visit(value);
}
},
eval: function (env) {
return new(Element)(this.combinator,
this.value.eval ? this.value.eval(env) : this.value,
this.index,
this.currentFileInfo);
},
genCSS: function (env, output) {
output.add(this.toCSS(env), this.currentFileInfo, this.index);
},
toCSS: function (env) {
var value = (this.value.toCSS ? this.value.toCSS(env) : this.value);
if (value === '' && this.combinator.value.charAt(0) === '&') {
return '';
} else {
return this.combinator.toCSS(env || {}) + value;
}
Element.prototype = new Node();
Element.prototype.type = "Element";
Element.prototype.accept = function (visitor) {
var value = this.value;
this.combinator = visitor.visit(this.combinator);
if (typeof value === "object") {
this.value = visitor.visit(value);
}
};
return Element;
Element.prototype.eval = function (env) {
return new(Element)(this.combinator,
this.value.eval ? this.value.eval(env) : this.value,
this.index,
this.currentFileInfo);
};
Element.prototype.genCSS = function (env, output) {
output.add(this.toCSS(env), this.currentFileInfo, this.index);
};
Element.prototype.toCSS = function (env) {
var value = (this.value.toCSS ? this.value.toCSS(env) : this.value);
if (value === '' && this.combinator.value.charAt(0) === '&') {
return '';
} else {
return this.combinator.toCSS(env || {}) + value;
}
};
module.exports = Element;

View File

@@ -1,54 +1,53 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Paren = require("./paren.js"),
Comment = require("./comment.js");
var Expression = function (value) { this.value = value; };
Expression.prototype = {
type: "Expression",
accept: function (visitor) {
if (this.value) {
this.value = visitor.visitArray(this.value);
}
},
eval: function (env) {
var returnValue,
inParenthesis = this.parens && !this.parensInOp,
doubleParen = false;
if (inParenthesis) {
env.inParenthesis();
}
if (this.value.length > 1) {
returnValue = new(Expression)(this.value.map(function (e) {
return e.eval(env);
}));
} else if (this.value.length === 1) {
if (this.value[0].parens && !this.value[0].parensInOp) {
doubleParen = true;
}
returnValue = this.value[0].eval(env);
} else {
returnValue = this;
}
if (inParenthesis) {
env.outOfParenthesis();
}
if (this.parens && this.parensInOp && !(env.isMathOn()) && !doubleParen) {
returnValue = new(tree.Paren)(returnValue);
}
return returnValue;
},
genCSS: function (env, output) {
for(var i = 0; i < this.value.length; i++) {
this.value[i].genCSS(env, output);
if (i + 1 < this.value.length) {
output.add(" ");
}
}
},
toCSS: tree.toCSS,
throwAwayComments: function () {
this.value = this.value.filter(function(v) {
return !(v instanceof tree.Comment);
});
Expression.prototype = new Node();
Expression.prototype.type = "Expression";
Expression.prototype.accept = function (visitor) {
if (this.value) {
this.value = visitor.visitArray(this.value);
}
};
return Expression;
Expression.prototype.eval = function (env) {
var returnValue,
inParenthesis = this.parens && !this.parensInOp,
doubleParen = false;
if (inParenthesis) {
env.inParenthesis();
}
if (this.value.length > 1) {
returnValue = new(Expression)(this.value.map(function (e) {
return e.eval(env);
}));
} else if (this.value.length === 1) {
if (this.value[0].parens && !this.value[0].parensInOp) {
doubleParen = true;
}
returnValue = this.value[0].eval(env);
} else {
returnValue = this;
}
if (inParenthesis) {
env.outOfParenthesis();
}
if (this.parens && this.parensInOp && !(env.isMathOn()) && !doubleParen) {
returnValue = new(Paren)(returnValue);
}
return returnValue;
};
Expression.prototype.genCSS = function (env, output) {
for(var i = 0; i < this.value.length; i++) {
this.value[i].genCSS(env, output);
if (i + 1 < this.value.length) {
output.add(" ");
}
}
};
Expression.prototype.throwAwayComments = function () {
this.value = this.value.filter(function(v) {
return !(v instanceof Comment);
});
};
module.exports = Expression;

View File

@@ -1,4 +1,4 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Extend = function Extend(selector, option, index) {
this.selector = selector;
@@ -20,34 +20,32 @@ var Extend = function Extend(selector, option, index) {
};
Extend.next_id = 0;
Extend.prototype = {
type: "Extend",
accept: function (visitor) {
this.selector = visitor.visit(this.selector);
},
eval: function (env) {
return new(Extend)(this.selector.eval(env), this.option, this.index);
},
clone: function (env) {
return new(Extend)(this.selector, this.option, this.index);
},
findSelfSelectors: function (selectors) {
var selfElements = [],
i,
selectorElements;
Extend.prototype = new Node();
Extend.prototype.type = "Extend";
Extend.prototype.accept = function (visitor) {
this.selector = visitor.visit(this.selector);
};
Extend.prototype.eval = function (env) {
return new(Extend)(this.selector.eval(env), this.option, this.index);
};
Extend.prototype.clone = function (env) {
return new(Extend)(this.selector, this.option, this.index);
};
Extend.prototype.findSelfSelectors = function (selectors) {
var selfElements = [],
i,
selectorElements;
for(i = 0; i < selectors.length; i++) {
selectorElements = selectors[i].elements;
// duplicate the logic in genCSS function inside the selector node.
// future TODO - move both logics into the selector joiner visitor
if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === "") {
selectorElements[0].combinator.value = ' ';
}
selfElements = selfElements.concat(selectors[i].elements);
for(i = 0; i < selectors.length; i++) {
selectorElements = selectors[i].elements;
// duplicate the logic in genCSS function inside the selector node.
// future TODO - move both logics into the selector joiner visitor
if (i > 0 && selectorElements.length && selectorElements[0].combinator.value === "") {
selectorElements[0].combinator.value = ' ';
}
this.selfSelectors = [{ elements: selfElements }];
selfElements = selfElements.concat(selectors[i].elements);
}
this.selfSelectors = [{ elements: selfElements }];
};
return Extend;
};
module.exports = Extend;

View File

@@ -1,4 +1,10 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Media = require("./media.js"),
URL = require("./url.js"),
Quoted = require("./quoted.js"),
Ruleset = require("./ruleset.js"),
Anonymous = require("./anonymous.js");
//
// CSS @import node
//
@@ -37,88 +43,85 @@ var Import = function (path, features, options, index, currentFileInfo) {
// we end up with a flat structure, which can easily be imported in the parent
// ruleset.
//
Import.prototype = {
type: "Import",
accept: function (visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
}
this.path = visitor.visit(this.path);
if (!this.options.inline && this.root) {
this.root = visitor.visit(this.root);
}
},
genCSS: function (env, output) {
if (this.css) {
output.add("@import ", this.currentFileInfo, this.index);
this.path.genCSS(env, output);
if (this.features) {
output.add(" ");
this.features.genCSS(env, output);
}
output.add(';');
}
},
toCSS: tree.toCSS,
getPath: function () {
if (this.path instanceof tree.Quoted) {
var path = this.path.value;
return (this.css !== undefined || /(\.[a-z]*$)|([\?;].*)$/.test(path)) ? path : path + '.less';
} else if (this.path instanceof tree.URL) {
return this.path.value.value;
}
return null;
},
evalForImport: function (env) {
return new(Import)(this.path.eval(env), this.features, this.options, this.index, this.currentFileInfo);
},
evalPath: function (env) {
var path = this.path.eval(env);
var rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
if (!(path instanceof tree.URL)) {
if (rootpath) {
var pathValue = path.value;
// Add the base path if the import is relative
if (pathValue && env.isPathRelative(pathValue)) {
path.value = rootpath +pathValue;
}
}
path.value = env.normalizePath(path.value);
}
return path;
},
eval: function (env) {
var ruleset, features = this.features && this.features.eval(env);
if (this.skip) {
if (typeof this.skip === "function") {
this.skip = this.skip();
}
if (this.skip) {
return [];
}
}
if (this.options.inline) {
//todo needs to reference css file not import
var contents = new(tree.Anonymous)(this.root, 0, {filename: this.importedFilename}, true, true);
return this.features ? new(tree.Media)([contents], this.features.value) : [contents];
} else if (this.css) {
var newImport = new(Import)(this.evalPath(env), features, this.options, this.index);
if (!newImport.css && this.error) {
throw this.error;
}
return newImport;
} else {
ruleset = new(tree.Ruleset)(null, this.root.rules.slice(0));
ruleset.evalImports(env);
return this.features ? new(tree.Media)(ruleset.rules, this.features.value) : ruleset.rules;
}
Import.prototype = new Node();
Import.prototype.type = "Import";
Import.prototype.accept = function (visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
}
this.path = visitor.visit(this.path);
if (!this.options.inline && this.root) {
this.root = visitor.visit(this.root);
}
};
return Import;
Import.prototype.genCSS = function (env, output) {
if (this.css) {
output.add("@import ", this.currentFileInfo, this.index);
this.path.genCSS(env, output);
if (this.features) {
output.add(" ");
this.features.genCSS(env, output);
}
output.add(';');
}
};
Import.prototype.getPath = function () {
if (this.path instanceof Quoted) {
var path = this.path.value;
return (this.css !== undefined || /(\.[a-z]*$)|([\?;].*)$/.test(path)) ? path : path + '.less';
} else if (this.path instanceof URL) {
return this.path.value.value;
}
return null;
};
Import.prototype.evalForImport = function (env) {
return new(Import)(this.path.eval(env), this.features, this.options, this.index, this.currentFileInfo);
};
Import.prototype.evalPath = function (env) {
var path = this.path.eval(env);
var rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
if (!(path instanceof URL)) {
if (rootpath) {
var pathValue = path.value;
// Add the base path if the import is relative
if (pathValue && env.isPathRelative(pathValue)) {
path.value = rootpath +pathValue;
}
}
path.value = env.normalizePath(path.value);
}
return path;
};
Import.prototype.eval = function (env) {
var ruleset, features = this.features && this.features.eval(env);
if (this.skip) {
if (typeof this.skip === "function") {
this.skip = this.skip();
}
if (this.skip) {
return [];
}
}
if (this.options.inline) {
//todo needs to reference css file not import
var contents = new(Anonymous)(this.root, 0, {filename: this.importedFilename}, true, true);
return this.features ? new(Media)([contents], this.features.value) : [contents];
} else if (this.css) {
var newImport = new(Import)(this.evalPath(env), features, this.options, this.index);
if (!newImport.css && this.error) {
throw this.error;
}
return newImport;
} else {
ruleset = new(Ruleset)(null, this.root.rules.slice(0));
ruleset.evalImports(env);
return this.features ? new(Media)(ruleset.rules, this.features.value) : ruleset.rules;
}
};
module.exports = Import;

View File

@@ -1,57 +1,27 @@
module.exports = function (tree) {
var JsEvalNode = require("./js-eval-node.js"),
Dimension = require("./dimension.js"),
Quoted = require("./quoted.js"),
Anonymous = require("./anonymous.js");
var JavaScript = function (string, index, escaped) {
this.escaped = escaped;
this.expression = string;
this.index = index;
};
JavaScript.prototype = {
type: "JavaScript",
eval: function (env) {
var result,
that = this,
context = {};
JavaScript.prototype = new JsEvalNode();
JavaScript.prototype.type = "JavaScript";
JavaScript.prototype.eval = function(env) {
var result = this.evaluateJavaScript(this.expression, env);
var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) {
return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env));
});
try {
expression = new(Function)('return (' + expression + ')');
} catch (e) {
throw { message: "JavaScript evaluation error: " + e.message + " from `" + expression + "`" ,
index: this.index };
}
var variables = env.frames[0].variables();
for (var k in variables) {
if (variables.hasOwnProperty(k)) {
/*jshint loopfunc:true */
context[k.slice(1)] = {
value: variables[k].value,
toJS: function () {
return this.value.eval(env).toCSS();
}
};
}
}
try {
result = expression.call(context);
} catch (e) {
throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" ,
index: this.index };
}
if (typeof(result) === 'number') {
return new(tree.Dimension)(result);
} else if (typeof(result) === 'string') {
return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index);
} else if (Array.isArray(result)) {
return new(tree.Anonymous)(result.join(', '));
} else {
return new(tree.Anonymous)(result);
}
if (typeof(result) === 'number') {
return new(Dimension)(result);
} else if (typeof(result) === 'string') {
return new(Quoted)('"' + result + '"', result, this.escaped, this.index);
} else if (Array.isArray(result)) {
return new(Anonymous)(result.join(', '));
} else {
return new(Anonymous)(result);
}
};
return JavaScript;
};
module.exports = JavaScript;

View File

@@ -0,0 +1,53 @@
var Node = require("./node.js"),
Variable = require("./variable.js");
var jsEvalNode = function() {
};
jsEvalNode.prototype = new Node();
jsEvalNode.prototype.evaluateJavaScript = function (expression, env) {
var result,
that = this,
context = {};
expression = expression.replace(/@\{([\w-]+)\}/g, function (_, name) {
return that.jsify(new(Variable)('@' + name, that.index).eval(env));
});
try {
expression = new(Function)('return (' + expression + ')');
} catch (e) {
throw { message: "JavaScript evaluation error: " + e.message + " from `" + expression + "`" ,
index: this.index };
}
var variables = env.frames[0].variables();
for (var k in variables) {
if (variables.hasOwnProperty(k)) {
/*jshint loopfunc:true */
context[k.slice(1)] = {
value: variables[k].value,
toJS: function () {
return this.value.eval(env).toCSS();
}
};
}
}
try {
result = expression.call(context);
} catch (e) {
throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" ,
index: this.index };
}
return result;
};
jsEvalNode.prototype.jsify = function (obj) {
if (Array.isArray(obj.value) && (obj.value.length > 1)) {
return '[' + obj.value.map(function (v) { return v.toCSS(); }).join(', ') + ']';
} else {
return obj.toCSS();
}
};
module.exports = jsEvalNode;

View File

@@ -1,27 +1,21 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Keyword = function (value) { this.value = value; };
Keyword.prototype = {
type: "Keyword",
eval: function () { return this; },
genCSS: function (env, output) {
if (this.value === '%') { throw { type: "Syntax", message: "Invalid % without number" }; }
output.add(this.value);
},
toCSS: tree.toCSS,
compare: function (other) {
if (other instanceof Keyword) {
return other.value === this.value ? 0 : 1;
} else {
return -1;
}
Keyword.prototype = new Node();
Keyword.prototype.type = "Keyword";
Keyword.prototype.genCSS = function (env, output) {
if (this.value === '%') { throw { type: "Syntax", message: "Invalid % without number" }; }
output.add(this.value);
};
Keyword.prototype.compare = function (other) {
if (other instanceof Keyword) {
return other.value === this.value ? 0 : 1;
} else {
return -1;
}
};
//TODO move?
tree.True = new(Keyword)('true');
tree.False = new(Keyword)('false');
Keyword.True = new(Keyword)('true');
Keyword.False = new(Keyword)('false');
return Keyword;
};
module.exports = Keyword;

View File

@@ -1,4 +1,10 @@
module.exports = function (tree) {
var Ruleset = require("./ruleset.js"),
Value = require("./value.js"),
Element = require("./element.js"),
Selector = require("./selector.js"),
Anonymous = require("./anonymous.js"),
Expression = require("./expression.js"),
Directive = require("./directive.js");
var Media = function (value, features, index, currentFileInfo) {
this.index = index;
@@ -6,152 +12,150 @@ var Media = function (value, features, index, currentFileInfo) {
var selectors = this.emptySelectors();
this.features = new(tree.Value)(features);
this.rules = [new(tree.Ruleset)(selectors, value)];
this.features = new(Value)(features);
this.rules = [new(Ruleset)(selectors, value)];
this.rules[0].allowImports = true;
};
Media.prototype = {
type: "Media",
accept: function (visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
}
if (this.rules) {
this.rules = visitor.visitArray(this.rules);
}
},
genCSS: function (env, output) {
output.add('@media ', this.currentFileInfo, this.index);
this.features.genCSS(env, output);
tree.outputRuleset(env, output, this.rules);
},
toCSS: tree.toCSS,
eval: function (env) {
if (!env.mediaBlocks) {
env.mediaBlocks = [];
env.mediaPath = [];
}
var media = new(Media)(null, [], this.index, this.currentFileInfo);
if(this.debugInfo) {
this.rules[0].debugInfo = this.debugInfo;
media.debugInfo = this.debugInfo;
}
var strictMathBypass = false;
if (!env.strictMath) {
strictMathBypass = true;
env.strictMath = true;
}
try {
media.features = this.features.eval(env);
}
finally {
if (strictMathBypass) {
env.strictMath = false;
}
}
env.mediaPath.push(media);
env.mediaBlocks.push(media);
env.frames.unshift(this.rules[0]);
media.rules = [this.rules[0].eval(env)];
env.frames.shift();
env.mediaPath.pop();
return env.mediaPath.length === 0 ? media.evalTop(env) :
media.evalNested(env);
},
variable: function (name) { return tree.Ruleset.prototype.variable.call(this.rules[0], name); },
find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); },
rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); },
emptySelectors: function() {
var el = new(tree.Element)('', '&', this.index, this.currentFileInfo),
sels = [new(tree.Selector)([el], null, null, this.index, this.currentFileInfo)];
sels[0].mediaEmpty = true;
return sels;
},
markReferenced: function () {
var i, rules = this.rules[0].rules;
this.rules[0].markReferenced();
this.isReferenced = true;
for (i = 0; i < rules.length; i++) {
if (rules[i].markReferenced) {
rules[i].markReferenced();
}
}
},
evalTop: function (env) {
var result = this;
// Render all dependent Media blocks.
if (env.mediaBlocks.length > 1) {
var selectors = this.emptySelectors();
result = new(tree.Ruleset)(selectors, env.mediaBlocks);
result.multiMedia = true;
}
delete env.mediaBlocks;
delete env.mediaPath;
return result;
},
evalNested: function (env) {
var i, value,
path = env.mediaPath.concat([this]);
// Extract the media-query conditions separated with `,` (OR).
for (i = 0; i < path.length; i++) {
value = path[i].features instanceof tree.Value ?
path[i].features.value : path[i].features;
path[i] = Array.isArray(value) ? value : [value];
}
// Trace all permutations to generate the resulting media-query.
//
// (a, b and c) with nested (d, e) ->
// a and d
// a and e
// b and c and d
// b and c and e
this.features = new(tree.Value)(this.permute(path).map(function (path) {
path = path.map(function (fragment) {
return fragment.toCSS ? fragment : new(tree.Anonymous)(fragment);
});
for(i = path.length - 1; i > 0; i--) {
path.splice(i, 0, new(tree.Anonymous)("and"));
}
return new(tree.Expression)(path);
}));
// Fake a tree-node that doesn't output anything.
return new(tree.Ruleset)([], []);
},
permute: function (arr) {
if (arr.length === 0) {
return [];
} else if (arr.length === 1) {
return arr[0];
} else {
var result = [];
var rest = this.permute(arr.slice(1));
for (var i = 0; i < rest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push([arr[0][j]].concat(rest[i]));
}
}
return result;
}
},
bubbleSelectors: function (selectors) {
if (!selectors)
return;
this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])];
Media.prototype = new Directive();
Media.prototype.type = "Media";
Media.prototype.isRulesetLike = true;
Media.prototype.accept = function (visitor) {
if (this.features) {
this.features = visitor.visit(this.features);
}
if (this.rules) {
this.rules = visitor.visitArray(this.rules);
}
};
return Media;
Media.prototype.genCSS = function (env, output) {
output.add('@media ', this.currentFileInfo, this.index);
this.features.genCSS(env, output);
this.outputRuleset(env, output, this.rules);
};
Media.prototype.eval = function (env) {
if (!env.mediaBlocks) {
env.mediaBlocks = [];
env.mediaPath = [];
}
var media = new(Media)(null, [], this.index, this.currentFileInfo);
if(this.debugInfo) {
this.rules[0].debugInfo = this.debugInfo;
media.debugInfo = this.debugInfo;
}
var strictMathBypass = false;
if (!env.strictMath) {
strictMathBypass = true;
env.strictMath = true;
}
try {
media.features = this.features.eval(env);
}
finally {
if (strictMathBypass) {
env.strictMath = false;
}
}
env.mediaPath.push(media);
env.mediaBlocks.push(media);
env.frames.unshift(this.rules[0]);
media.rules = [this.rules[0].eval(env)];
env.frames.shift();
env.mediaPath.pop();
return env.mediaPath.length === 0 ? media.evalTop(env) :
media.evalNested(env);
};
//TODO merge with directive
Media.prototype.variable = function (name) { return Ruleset.prototype.variable.call(this.rules[0], name); };
Media.prototype.find = function () { return Ruleset.prototype.find.apply(this.rules[0], arguments); };
Media.prototype.rulesets = function () { return Ruleset.prototype.rulesets.apply(this.rules[0]); };
Media.prototype.emptySelectors = function() {
var el = new(Element)('', '&', this.index, this.currentFileInfo),
sels = [new(Selector)([el], null, null, this.index, this.currentFileInfo)];
sels[0].mediaEmpty = true;
return sels;
};
Media.prototype.markReferenced = function () {
var i, rules = this.rules[0].rules;
this.rules[0].markReferenced();
this.isReferenced = true;
for (i = 0; i < rules.length; i++) {
if (rules[i].markReferenced) {
rules[i].markReferenced();
}
}
};
Media.prototype.evalTop = function (env) {
var result = this;
// Render all dependent Media blocks.
if (env.mediaBlocks.length > 1) {
var selectors = this.emptySelectors();
result = new(Ruleset)(selectors, env.mediaBlocks);
result.multiMedia = true;
}
delete env.mediaBlocks;
delete env.mediaPath;
return result;
};
Media.prototype.evalNested = function (env) {
var i, value,
path = env.mediaPath.concat([this]);
// Extract the media-query conditions separated with `,` (OR).
for (i = 0; i < path.length; i++) {
value = path[i].features instanceof Value ?
path[i].features.value : path[i].features;
path[i] = Array.isArray(value) ? value : [value];
}
// Trace all permutations to generate the resulting media-query.
//
// (a, b and c) with nested (d, e) ->
// a and d
// a and e
// b and c and d
// b and c and e
this.features = new(Value)(this.permute(path).map(function (path) {
path = path.map(function (fragment) {
return fragment.toCSS ? fragment : new(Anonymous)(fragment);
});
for(i = path.length - 1; i > 0; i--) {
path.splice(i, 0, new(Anonymous)("and"));
}
return new(Expression)(path);
}));
// Fake a tree-node that doesn't output anything.
return new(Ruleset)([], []);
};
Media.prototype.permute = function (arr) {
if (arr.length === 0) {
return [];
} else if (arr.length === 1) {
return arr[0];
} else {
var result = [];
var rest = this.permute(arr.slice(1));
for (var i = 0; i < rest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push([arr[0][j]].concat(rest[i]));
}
}
return result;
}
};
Media.prototype.bubbleSelectors = function (selectors) {
if (!selectors)
return;
this.rules = [new(Ruleset)(selectors.slice(0), [this.rules[0]])];
};
module.exports = Media;

View File

@@ -1,153 +1,154 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Selector = require("./selector.js"),
MixinDefinition = require("./mixin-definition.js"),
defaultFunc = require("../functions/default.js");
var Call = function (elements, args, index, currentFileInfo, important) {
this.selector = new(tree.Selector)(elements);
var MixinCall = function (elements, args, index, currentFileInfo, important) {
this.selector = new(Selector)(elements);
this.arguments = (args && args.length) ? args : null;
this.index = index;
this.currentFileInfo = currentFileInfo;
this.important = important;
};
Call.prototype = {
type: "MixinCall",
accept: function (visitor) {
if (this.selector) {
this.selector = visitor.visit(this.selector);
}
if (this.arguments) {
this.arguments = visitor.visitArray(this.arguments);
}
},
eval: function (env) {
var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule,
candidates = [], candidate, conditionResult = [], defaultFunc = tree.defaultFunc,
defaultResult, defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset;
args = this.arguments && this.arguments.map(function (a) {
return { name: a.name, value: a.value.eval(env) };
});
for (i = 0; i < env.frames.length; i++) {
if ((mixins = env.frames[i].find(this.selector)).length > 0) {
isOneFound = true;
// To make `default()` function independent of definition order we have two "subpasses" here.
// At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
// and build candidate list with corresponding flags. Then, when we know all possible matches,
// we make a final decision.
for (m = 0; m < mixins.length; m++) {
mixin = mixins[m];
isRecursive = false;
for(f = 0; f < env.frames.length; f++) {
if ((!(mixin instanceof tree.mixin.Definition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) {
isRecursive = true;
break;
}
}
if (isRecursive) {
continue;
}
if (mixin.matchArgs(args, env)) {
candidate = {mixin: mixin, group: defNone};
if (mixin.matchCondition) {
for (f = 0; f < 2; f++) {
defaultFunc.value(f);
conditionResult[f] = mixin.matchCondition(args, env);
}
if (conditionResult[0] || conditionResult[1]) {
if (conditionResult[0] != conditionResult[1]) {
candidate.group = conditionResult[1] ?
defTrue : defFalse;
}
candidates.push(candidate);
}
}
else {
candidates.push(candidate);
}
match = true;
}
}
defaultFunc.reset();
count = [0, 0, 0];
for (m = 0; m < candidates.length; m++) {
count[candidates[m].group]++;
}
if (count[defNone] > 0) {
defaultResult = defFalse;
} else {
defaultResult = defTrue;
if ((count[defTrue] + count[defFalse]) > 1) {
throw { type: 'Runtime',
message: 'Ambiguous use of `default()` found when matching for `'
+ this.format(args) + '`',
index: this.index, filename: this.currentFileInfo.filename };
}
}
for (m = 0; m < candidates.length; m++) {
candidate = candidates[m].group;
if ((candidate === defNone) || (candidate === defaultResult)) {
try {
mixin = candidates[m].mixin;
if (!(mixin instanceof tree.mixin.Definition)) {
originalRuleset = mixin.originalRuleset || mixin;
mixin = new tree.mixin.Definition("", [], mixin.rules, null, false);
mixin.originalRuleset = originalRuleset;
}
Array.prototype.push.apply(
rules, mixin.evalCall(env, args, this.important).rules);
} catch (e) {
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
}
}
}
if (match) {
if (!this.currentFileInfo || !this.currentFileInfo.reference) {
for (i = 0; i < rules.length; i++) {
rule = rules[i];
if (rule.markReferenced) {
rule.markReferenced();
}
}
}
return rules;
}
}
}
if (isOneFound) {
throw { type: 'Runtime',
message: 'No matching definition was found for `' + this.format(args) + '`',
index: this.index, filename: this.currentFileInfo.filename };
} else {
throw { type: 'Name',
message: this.selector.toCSS().trim() + " is undefined",
index: this.index, filename: this.currentFileInfo.filename };
}
},
format: function (args) {
return this.selector.toCSS().trim() + '(' +
(args ? args.map(function (a) {
var argValue = "";
if (a.name) {
argValue += a.name + ":";
}
if (a.value.toCSS) {
argValue += a.value.toCSS();
} else {
argValue += "???";
}
return argValue;
}).join(', ') : "") + ")";
MixinCall.prototype = new Node();
MixinCall.prototype.type = "MixinCall";
MixinCall.prototype.accept = function (visitor) {
if (this.selector) {
this.selector = visitor.visit(this.selector);
}
if (this.arguments) {
this.arguments = visitor.visitArray(this.arguments);
}
};
return Call;
MixinCall.prototype.eval = function (env) {
var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule,
candidates = [], candidate, conditionResult = [],
defaultResult, defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset;
args = this.arguments && this.arguments.map(function (a) {
return { name: a.name, value: a.value.eval(env) };
});
for (i = 0; i < env.frames.length; i++) {
if ((mixins = env.frames[i].find(this.selector)).length > 0) {
isOneFound = true;
// To make `default()` function independent of definition order we have two "subpasses" here.
// At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
// and build candidate list with corresponding flags. Then, when we know all possible matches,
// we make a final decision.
for (m = 0; m < mixins.length; m++) {
mixin = mixins[m];
isRecursive = false;
for(f = 0; f < env.frames.length; f++) {
if ((!(mixin instanceof MixinDefinition)) && mixin === (env.frames[f].originalRuleset || env.frames[f])) {
isRecursive = true;
break;
}
}
if (isRecursive) {
continue;
}
if (mixin.matchArgs(args, env)) {
candidate = {mixin: mixin, group: defNone};
if (mixin.matchCondition) {
for (f = 0; f < 2; f++) {
defaultFunc.value(f);
conditionResult[f] = mixin.matchCondition(args, env);
}
if (conditionResult[0] || conditionResult[1]) {
if (conditionResult[0] != conditionResult[1]) {
candidate.group = conditionResult[1] ?
defTrue : defFalse;
}
candidates.push(candidate);
}
}
else {
candidates.push(candidate);
}
match = true;
}
}
defaultFunc.reset();
count = [0, 0, 0];
for (m = 0; m < candidates.length; m++) {
count[candidates[m].group]++;
}
if (count[defNone] > 0) {
defaultResult = defFalse;
} else {
defaultResult = defTrue;
if ((count[defTrue] + count[defFalse]) > 1) {
throw { type: 'Runtime',
message: 'Ambiguous use of `default()` found when matching for `'
+ this.format(args) + '`',
index: this.index, filename: this.currentFileInfo.filename };
}
}
for (m = 0; m < candidates.length; m++) {
candidate = candidates[m].group;
if ((candidate === defNone) || (candidate === defaultResult)) {
try {
mixin = candidates[m].mixin;
if (!(mixin instanceof MixinDefinition)) {
originalRuleset = mixin.originalRuleset || mixin;
mixin = new MixinDefinition("", [], mixin.rules, null, false);
mixin.originalRuleset = originalRuleset;
}
Array.prototype.push.apply(
rules, mixin.evalCall(env, args, this.important).rules);
} catch (e) {
throw { message: e.message, index: this.index, filename: this.currentFileInfo.filename, stack: e.stack };
}
}
}
if (match) {
if (!this.currentFileInfo || !this.currentFileInfo.reference) {
for (i = 0; i < rules.length; i++) {
rule = rules[i];
if (rule.markReferenced) {
rule.markReferenced();
}
}
}
return rules;
}
}
}
if (isOneFound) {
throw { type: 'Runtime',
message: 'No matching definition was found for `' + this.format(args) + '`',
index: this.index, filename: this.currentFileInfo.filename };
} else {
throw { type: 'Name',
message: this.selector.toCSS().trim() + " is undefined",
index: this.index, filename: this.currentFileInfo.filename };
}
};
MixinCall.prototype.format = function (args) {
return this.selector.toCSS().trim() + '(' +
(args ? args.map(function (a) {
var argValue = "";
if (a.name) {
argValue += a.name + ":";
}
if (a.value.toCSS) {
argValue += a.value.toCSS();
} else {
argValue += "???";
}
return argValue;
}).join(', ') : "") + ")";
};
module.exports = MixinCall;

View File

@@ -1,8 +1,13 @@
module.exports = function (tree) {
var Selector = require("./selector.js"),
Element = require("./element.js"),
Ruleset = require("./ruleset.js"),
Rule = require("./rule.js"),
Expression = require("./expression.js"),
contexts = require("../env.js");
var Definition = function (name, params, rules, condition, variadic, frames) {
this.name = name;
this.selectors = [new(tree.Selector)([new(tree.Element)(null, name, this.index, this.currentFileInfo)])];
this.selectors = [new(Selector)([new(Element)(null, name, this.index, this.currentFileInfo)])];
this.params = params;
this.condition = condition;
this.variadic = variadic;
@@ -13,153 +18,146 @@ var Definition = function (name, params, rules, condition, variadic, frames) {
if (!p.name || (p.name && !p.value)) { return count + 1; }
else { return count; }
}, 0);
this.parent = tree.Ruleset.prototype;
this.frames = frames;
};
Definition.prototype = {
type: "MixinDefinition",
accept: function (visitor) {
if (this.params && this.params.length) {
this.params = visitor.visitArray(this.params);
}
this.rules = visitor.visitArray(this.rules);
if (this.condition) {
this.condition = visitor.visit(this.condition);
}
},
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); },
evalParams: function (env, mixinEnv, args, evaldArguments) {
/*jshint boss:true */
var frame = new(tree.Ruleset)(null, null),
varargs, arg,
params = this.params.slice(0),
i, j, val, name, isNamedFound, argIndex, argsLength = 0;
mixinEnv = new tree.evalEnv(mixinEnv, [frame].concat(mixinEnv.frames));
if (args) {
args = args.slice(0);
argsLength = args.length;
for(i = 0; i < argsLength; i++) {
arg = args[i];
if (name = (arg && arg.name)) {
isNamedFound = false;
for(j = 0; j < params.length; j++) {
if (!evaldArguments[j] && name === params[j].name) {
evaldArguments[j] = arg.value.eval(env);
frame.prependRule(new(tree.Rule)(name, arg.value.eval(env)));
isNamedFound = true;
break;
}
}
if (isNamedFound) {
args.splice(i, 1);
i--;
continue;
} else {
throw { type: 'Runtime', message: "Named argument for " + this.name +
' ' + args[i].name + ' not found' };
}
}
}
}
argIndex = 0;
for (i = 0; i < params.length; i++) {
if (evaldArguments[i]) { continue; }
arg = args && args[argIndex];
if (name = params[i].name) {
if (params[i].variadic) {
varargs = [];
for (j = argIndex; j < argsLength; j++) {
varargs.push(args[j].value.eval(env));
}
frame.prependRule(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env)));
} else {
val = arg && arg.value;
if (val) {
val = val.eval(env);
} else if (params[i].value) {
val = params[i].value.eval(mixinEnv);
frame.resetCache();
} else {
throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
' (' + argsLength + ' for ' + this.arity + ')' };
}
frame.prependRule(new(tree.Rule)(name, val));
evaldArguments[i] = val;
}
}
if (params[i].variadic && args) {
for (j = argIndex; j < argsLength; j++) {
evaldArguments[j] = args[j].value.eval(env);
}
}
argIndex++;
}
return frame;
},
eval: function (env) {
return new tree.mixin.Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || env.frames.slice(0));
},
evalCall: function (env, args, important) {
var _arguments = [],
mixinFrames = this.frames ? this.frames.concat(env.frames) : env.frames,
frame = this.evalParams(env, new(tree.evalEnv)(env, mixinFrames), args, _arguments),
rules, ruleset;
frame.prependRule(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
rules = this.rules.slice(0);
ruleset = new(tree.Ruleset)(null, rules);
ruleset.originalRuleset = this;
ruleset = ruleset.eval(new(tree.evalEnv)(env, [this, frame].concat(mixinFrames)));
if (important) {
ruleset = this.parent.makeImportant.apply(ruleset);
}
return ruleset;
},
matchCondition: function (args, env) {
if (this.condition && !this.condition.eval(
new(tree.evalEnv)(env,
[this.evalParams(env, new(tree.evalEnv)(env, this.frames ? this.frames.concat(env.frames) : env.frames), args, [])] // the parameter variables
.concat(this.frames) // the parent namespace/mixin frames
.concat(env.frames)))) { // the current environment frames
return false;
}
return true;
},
matchArgs: function (args, env) {
var argsLength = (args && args.length) || 0, len;
if (! this.variadic) {
if (argsLength < this.required) { return false; }
if (argsLength > this.params.length) { return false; }
} else {
if (argsLength < (this.required - 1)) { return false; }
}
len = Math.min(argsLength, this.arity);
for (var i = 0; i < len; i++) {
if (!this.params[i].name && !this.params[i].variadic) {
if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
return false;
}
}
}
return true;
Definition.prototype = new Ruleset();
Definition.prototype.type = "MixinDefinition";
Definition.prototype.evalFirst = true;
Definition.prototype.accept = function (visitor) {
if (this.params && this.params.length) {
this.params = visitor.visitArray(this.params);
}
this.rules = visitor.visitArray(this.rules);
if (this.condition) {
this.condition = visitor.visit(this.condition);
}
};
return Definition;
Definition.prototype.evalParams = function (env, mixinEnv, args, evaldArguments) {
/*jshint boss:true */
var frame = new(Ruleset)(null, null),
varargs, arg,
params = this.params.slice(0),
i, j, val, name, isNamedFound, argIndex, argsLength = 0;
mixinEnv = new contexts.evalEnv(mixinEnv, [frame].concat(mixinEnv.frames));
if (args) {
args = args.slice(0);
argsLength = args.length;
for(i = 0; i < argsLength; i++) {
arg = args[i];
if (name = (arg && arg.name)) {
isNamedFound = false;
for(j = 0; j < params.length; j++) {
if (!evaldArguments[j] && name === params[j].name) {
evaldArguments[j] = arg.value.eval(env);
frame.prependRule(new(Rule)(name, arg.value.eval(env)));
isNamedFound = true;
break;
}
}
if (isNamedFound) {
args.splice(i, 1);
i--;
continue;
} else {
throw { type: 'Runtime', message: "Named argument for " + this.name +
' ' + args[i].name + ' not found' };
}
}
}
}
argIndex = 0;
for (i = 0; i < params.length; i++) {
if (evaldArguments[i]) { continue; }
arg = args && args[argIndex];
if (name = params[i].name) {
if (params[i].variadic) {
varargs = [];
for (j = argIndex; j < argsLength; j++) {
varargs.push(args[j].value.eval(env));
}
frame.prependRule(new(Rule)(name, new(Expression)(varargs).eval(env)));
} else {
val = arg && arg.value;
if (val) {
val = val.eval(env);
} else if (params[i].value) {
val = params[i].value.eval(mixinEnv);
frame.resetCache();
} else {
throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
' (' + argsLength + ' for ' + this.arity + ')' };
}
frame.prependRule(new(Rule)(name, val));
evaldArguments[i] = val;
}
}
if (params[i].variadic && args) {
for (j = argIndex; j < argsLength; j++) {
evaldArguments[j] = args[j].value.eval(env);
}
}
argIndex++;
}
return frame;
};
Definition.prototype.eval = function (env) {
return new Definition(this.name, this.params, this.rules, this.condition, this.variadic, this.frames || env.frames.slice(0));
};
Definition.prototype.evalCall = function (env, args, important) {
var _arguments = [],
mixinFrames = this.frames ? this.frames.concat(env.frames) : env.frames,
frame = this.evalParams(env, new(contexts.evalEnv)(env, mixinFrames), args, _arguments),
rules, ruleset;
frame.prependRule(new(Rule)('@arguments', new(Expression)(_arguments).eval(env)));
rules = this.rules.slice(0);
ruleset = new(Ruleset)(null, rules);
ruleset.originalRuleset = this;
ruleset = ruleset.eval(new(contexts.evalEnv)(env, [this, frame].concat(mixinFrames)));
if (important) {
ruleset = this.makeImportant.apply(ruleset);
}
return ruleset;
};
Definition.prototype.matchCondition = function (args, env) {
if (this.condition && !this.condition.eval(
new(contexts.evalEnv)(env,
[this.evalParams(env, new(contexts.evalEnv)(env, this.frames ? this.frames.concat(env.frames) : env.frames), args, [])] // the parameter variables
.concat(this.frames) // the parent namespace/mixin frames
.concat(env.frames)))) { // the current environment frames
return false;
}
return true;
};
Definition.prototype.matchArgs = function (args, env) {
var argsLength = (args && args.length) || 0, len;
if (! this.variadic) {
if (argsLength < this.required) { return false; }
if (argsLength > this.params.length) { return false; }
} else {
if (argsLength < (this.required - 1)) { return false; }
}
len = Math.min(argsLength, this.arity);
for (var i = 0; i < len; i++) {
if (!this.params[i].name && !this.params[i].variadic) {
if (args[i].value.eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
return false;
}
}
}
return true;
};
module.exports = Definition;

View File

@@ -1,24 +1,20 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Operation = require("./operation.js"),
Dimension = require("./dimension.js");
var Negative = function (node) {
this.value = node;
};
Negative.prototype = {
type: "Negative",
accept: function (visitor) {
this.value = visitor.visit(this.value);
},
genCSS: function (env, output) {
output.add('-');
this.value.genCSS(env, output);
},
toCSS: tree.toCSS,
eval: function (env) {
if (env.isMathOn()) {
return (new(tree.Operation)('*', [new(tree.Dimension)(-1), this.value])).eval(env);
}
return new(Negative)(this.value.eval(env));
Negative.prototype = new Node();
Negative.prototype.type = "Negative";
Negative.prototype.genCSS = function (env, output) {
output.add('-');
this.value.genCSS(env, output);
};
Negative.prototype.eval = function (env) {
if (env.isMathOn()) {
return (new(Operation)('*', [new(Dimension)(-1), this.value])).eval(env);
}
return new(Negative)(this.value.eval(env));
};
return Negative;
};
module.exports = Negative;

35
lib/less/tree/node.js Normal file
View File

@@ -0,0 +1,35 @@
var Node = function() {
};
Node.prototype.toCSS = function (env) {
var strs = [];
this.genCSS(env, {
add: function(chunk, fileInfo, index) {
strs.push(chunk);
},
isEmpty: function () {
return strs.length === 0;
}
});
return strs.join('');
};
Node.prototype.genCSS = function (env, output) {
output.add(this.value);
};
Node.prototype.accept = function (visitor) {
this.value = visitor.visit(this.value);
};
Node.prototype.eval = function () { return this; };
Node.prototype._operate = function (env, op, a, b) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return a / b;
}
};
Node.prototype.fround = function(env, value) {
var precision = env && env.numPrecision;
//add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded...
return (precision == null) ? value : Number((value + 2e-16).toFixed(precision));
};
module.exports = Node;

View File

@@ -1,60 +1,48 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Color = require("./color.js"),
Dimension = require("./dimension.js");
var Operation = function (op, operands, isSpaced) {
this.op = op.trim();
this.operands = operands;
this.isSpaced = isSpaced;
};
Operation.prototype = {
type: "Operation",
accept: function (visitor) {
this.operands = visitor.visit(this.operands);
},
eval: function (env) {
var a = this.operands[0].eval(env),
b = this.operands[1].eval(env);
if (env.isMathOn()) {
if (a instanceof tree.Dimension && b instanceof tree.Color) {
a = a.toColor();
}
if (b instanceof tree.Dimension && a instanceof tree.Color) {
b = b.toColor();
}
if (!a.operate) {
throw { type: "Operation",
message: "Operation on an invalid type" };
}
return a.operate(env, this.op, b);
} else {
return new(Operation)(this.op, [a, b], this.isSpaced);
}
},
genCSS: function (env, output) {
this.operands[0].genCSS(env, output);
if (this.isSpaced) {
output.add(" ");
}
output.add(this.op);
if (this.isSpaced) {
output.add(" ");
}
this.operands[1].genCSS(env, output);
},
toCSS: tree.toCSS
Operation.prototype = new Node();
Operation.prototype.type = "Operation";
Operation.prototype.accept = function (visitor) {
this.operands = visitor.visit(this.operands);
};
Operation.prototype.eval = function (env) {
var a = this.operands[0].eval(env),
b = this.operands[1].eval(env);
// todo move!
tree.operate = function (env, op, a, b) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return a / b;
if (env.isMathOn()) {
if (a instanceof Dimension && b instanceof Color) {
a = a.toColor();
}
if (b instanceof Dimension && a instanceof Color) {
b = b.toColor();
}
if (!a.operate) {
throw { type: "Operation",
message: "Operation on an invalid type" };
}
return a.operate(env, this.op, b);
} else {
return new(Operation)(this.op, [a, b], this.isSpaced);
}
};
return Operation;
Operation.prototype.genCSS = function (env, output) {
this.operands[0].genCSS(env, output);
if (this.isSpaced) {
output.add(" ");
}
output.add(this.op);
if (this.isSpaced) {
output.add(" ");
}
this.operands[1].genCSS(env, output);
};
module.exports = Operation;

View File

@@ -1,22 +1,16 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Paren = function (node) {
this.value = node;
};
Paren.prototype = {
type: "Paren",
accept: function (visitor) {
this.value = visitor.visit(this.value);
},
genCSS: function (env, output) {
output.add('(');
this.value.genCSS(env, output);
output.add(')');
},
toCSS: tree.toCSS,
eval: function (env) {
return new(Paren)(this.value.eval(env));
}
Paren.prototype = new Node();
Paren.prototype.type = "Paren";
Paren.prototype.genCSS = function (env, output) {
output.add('(');
this.value.genCSS(env, output);
output.add(')');
};
return Paren;
Paren.prototype.eval = function (env) {
return new(Paren)(this.value.eval(env));
};
module.exports = Paren;

View File

@@ -1,4 +1,5 @@
module.exports = function (tree) {
var JsEvalNode = require("./js-eval-node.js"),
Variable = require("./variable.js");
var Quoted = function (str, content, escaped, index, currentFileInfo) {
this.escaped = escaped;
@@ -7,50 +8,47 @@ var Quoted = function (str, content, escaped, index, currentFileInfo) {
this.index = index;
this.currentFileInfo = currentFileInfo;
};
Quoted.prototype = {
type: "Quoted",
genCSS: function (env, output) {
if (!this.escaped) {
output.add(this.quote, this.currentFileInfo, this.index);
}
output.add(this.value);
if (!this.escaped) {
output.add(this.quote);
}
},
toCSS: tree.toCSS,
eval: function (env) {
var that = this;
var value = this.value.replace(/`([^`]+)`/g, function (_, exp) {
return new(tree.JavaScript)(exp, that.index, true).eval(env).value;
}).replace(/@\{([\w-]+)\}/g, function (_, name) {
var v = new(tree.Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true);
return (v instanceof tree.Quoted) ? v.value : v.toCSS();
});
return new(tree.Quoted)(this.quote + value + this.quote, value, this.escaped, this.index, this.currentFileInfo);
},
compare: function (x) {
if (!x.toCSS) {
return -1;
}
var left, right;
// when comparing quoted strings allow the quote to differ
if (x.type === "Quoted" && !this.escaped && !x.escaped) {
left = x.value;
right = this.value;
} else {
left = this.toCSS();
right = x.toCSS();
}
if (left === right) {
return 0;
}
return left < right ? -1 : 1;
Quoted.prototype = new JsEvalNode();
Quoted.prototype.type = "Quoted";
Quoted.prototype.genCSS = function (env, output) {
if (!this.escaped) {
output.add(this.quote, this.currentFileInfo, this.index);
}
output.add(this.value);
if (!this.escaped) {
output.add(this.quote);
}
};
return Quoted;
Quoted.prototype.eval = function (env) {
var that = this;
var value = this.value.replace(/`([^`]+)`/g, function (_, exp) {
return String(that.evaluateJavaScript(exp, env));
}).replace(/@\{([\w-]+)\}/g, function (_, name) {
var v = new(Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true);
return (v instanceof Quoted) ? v.value : v.toCSS();
});
return new(Quoted)(this.quote + value + this.quote, value, this.escaped, this.index, this.currentFileInfo);
};
Quoted.prototype.compare = function (x) {
if (!x.toCSS) {
return -1;
}
var left, right;
// when comparing quoted strings allow the quote to differ
if (x.type === "Quoted" && !this.escaped && !x.escaped) {
left = x.value;
right = this.value;
} else {
left = this.toCSS();
right = x.toCSS();
}
if (left === right) {
return 0;
}
return left < right ? -1 : 1;
};
module.exports = Quoted;

View File

@@ -1,8 +1,10 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Value = require("./value.js"),
Keyword = require("./keyword.js");
var Rule = function (name, value, important, merge, index, currentFileInfo, inline) {
this.name = name;
this.value = (value instanceof tree.Value || value instanceof tree.Ruleset) ? value : new(tree.Value)([value]);
this.value = (value instanceof Node) ? value : new(Value)([value]); //value instanceof tree.Value || value instanceof tree.Ruleset ??
this.important = important ? ' ' + important.trim() : '';
this.merge = merge;
this.index = index;
@@ -11,73 +13,6 @@ var Rule = function (name, value, important, merge, index, currentFileInfo, inli
this.variable = name.charAt && (name.charAt(0) === '@');
};
Rule.prototype = {
type: "Rule",
accept: function (visitor) {
this.value = visitor.visit(this.value);
},
genCSS: function (env, output) {
output.add(this.name + (env.compress ? ':' : ': '), this.currentFileInfo, this.index);
try {
this.value.genCSS(env, output);
}
catch(e) {
e.index = this.index;
e.filename = this.currentFileInfo.filename;
throw e;
}
output.add(this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";"), this.currentFileInfo, this.index);
},
toCSS: tree.toCSS,
eval: function (env) {
var strictMathBypass = false, name = this.name, evaldValue;
if (typeof name !== "string") {
// expand 'primitive' name directly to get
// things faster (~10% for benchmark.less):
name = (name.length === 1)
&& (name[0] instanceof tree.Keyword)
? name[0].value : evalName(env, name);
}
if (name === "font" && !env.strictMath) {
strictMathBypass = true;
env.strictMath = true;
}
try {
evaldValue = this.value.eval(env);
if (!this.variable && evaldValue.type === "DetachedRuleset") {
throw { message: "Rulesets cannot be evaluated on a property.",
index: this.index, filename: this.currentFileInfo.filename };
}
return new(Rule)(name,
evaldValue,
this.important,
this.merge,
this.index, this.currentFileInfo, this.inline);
}
catch(e) {
if (typeof e.index !== 'number') {
e.index = this.index;
e.filename = this.currentFileInfo.filename;
}
throw e;
}
finally {
if (strictMathBypass) {
env.strictMath = false;
}
}
},
makeImportant: function () {
return new(Rule)(this.name,
this.value,
"!important",
this.merge,
this.index, this.currentFileInfo, this.inline);
}
};
function evalName(env, name) {
var value = "", i, n = name.length,
output = {add: function (s) {value += s;}};
@@ -86,7 +21,67 @@ function evalName(env, name) {
}
return value;
}
return Rule;
Rule.prototype = new Node();
Rule.prototype.type = "Rule";
Rule.prototype.genCSS = function (env, output) {
output.add(this.name + (env.compress ? ':' : ': '), this.currentFileInfo, this.index);
try {
this.value.genCSS(env, output);
}
catch(e) {
e.index = this.index;
e.filename = this.currentFileInfo.filename;
throw e;
}
output.add(this.important + ((this.inline || (env.lastRule && env.compress)) ? "" : ";"), this.currentFileInfo, this.index);
};
Rule.prototype.eval = function (env) {
var strictMathBypass = false, name = this.name, evaldValue;
if (typeof name !== "string") {
// expand 'primitive' name directly to get
// things faster (~10% for benchmark.less):
name = (name.length === 1)
&& (name[0] instanceof Keyword)
? name[0].value : evalName(env, name);
}
if (name === "font" && !env.strictMath) {
strictMathBypass = true;
env.strictMath = true;
}
try {
evaldValue = this.value.eval(env);
if (!this.variable && evaldValue.type === "DetachedRuleset") {
throw { message: "Rulesets cannot be evaluated on a property.",
index: this.index, filename: this.currentFileInfo.filename };
}
return new(Rule)(name,
evaldValue,
this.important,
this.merge,
this.index, this.currentFileInfo, this.inline);
}
catch(e) {
if (typeof e.index !== 'number') {
e.index = this.index;
e.filename = this.currentFileInfo.filename;
}
throw e;
}
finally {
if (strictMathBypass) {
env.strictMath = false;
}
}
};
Rule.prototype.makeImportant = function () {
return new(Rule)(this.name,
this.value,
"!important",
this.merge,
this.index, this.currentFileInfo, this.inline);
};
module.exports = Rule;

View File

@@ -1,16 +1,13 @@
module.exports = function (tree) {
var Node = require("./node.js"),
Variable = require("./variable.js");
var RulesetCall = function (variable) {
this.variable = variable;
};
RulesetCall.prototype = {
type: "RulesetCall",
accept: function (visitor) {
},
eval: function (env) {
var detachedRuleset = new(tree.Variable)(this.variable).eval(env);
return detachedRuleset.callEval(env);
}
};
return RulesetCall;
RulesetCall.prototype = new Node();
RulesetCall.prototype.type = "RulesetCall";
RulesetCall.prototype.eval = function (env) {
var detachedRuleset = new(Variable)(this.variable).eval(env);
return detachedRuleset.callEval(env);
};
module.exports = RulesetCall;

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Selector = function (elements, extendList, condition, index, currentFileInfo, isReferenced) {
this.elements = elements;
@@ -10,120 +10,117 @@ var Selector = function (elements, extendList, condition, index, currentFileInfo
this.evaldCondition = true;
}
};
Selector.prototype = {
type: "Selector",
accept: function (visitor) {
if (this.elements) {
this.elements = visitor.visitArray(this.elements);
}
if (this.extendList) {
this.extendList = visitor.visitArray(this.extendList);
}
if (this.condition) {
this.condition = visitor.visit(this.condition);
}
},
createDerived: function(elements, extendList, evaldCondition) {
evaldCondition = (evaldCondition != null) ? evaldCondition : this.evaldCondition;
var newSelector = new(Selector)(elements, extendList || this.extendList, null, this.index, this.currentFileInfo, this.isReferenced);
newSelector.evaldCondition = evaldCondition;
newSelector.mediaEmpty = this.mediaEmpty;
return newSelector;
},
match: function (other) {
var elements = this.elements,
len = elements.length,
olen, i;
other.CacheElements();
olen = other._elements.length;
if (olen === 0 || len < olen) {
return 0;
} else {
for (i = 0; i < olen; i++) {
if (elements[i].value !== other._elements[i]) {
return 0;
}
}
}
return olen; // return number of matched elements
},
CacheElements: function(){
var css = '', len, v, i;
if( !this._elements ){
len = this.elements.length;
for(i = 0; i < len; i++){
v = this.elements[i];
css += v.combinator.value;
if( !v.value.value ){
css += v.value;
continue;
}
if( typeof v.value.value !== "string" ){
css = '';
break;
}
css += v.value.value;
}
this._elements = css.match(/[,&#\*\.\w-]([\w-]|(\\.))*/g);
if (this._elements) {
if (this._elements[0] === "&") {
this._elements.shift();
}
} else {
this._elements = [];
}
}
},
isJustParentSelector: function() {
return !this.mediaEmpty &&
this.elements.length === 1 &&
this.elements[0].value === '&' &&
(this.elements[0].combinator.value === ' ' || this.elements[0].combinator.value === '');
},
eval: function (env) {
var evaldCondition = this.condition && this.condition.eval(env),
elements = this.elements, extendList = this.extendList;
elements = elements && elements.map(function (e) { return e.eval(env); });
extendList = extendList && extendList.map(function(extend) { return extend.eval(env); });
return this.createDerived(elements, extendList, evaldCondition);
},
genCSS: function (env, output) {
var i, element;
if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") {
output.add(' ', this.currentFileInfo, this.index);
}
if (!this._css) {
//TODO caching? speed comparison?
for(i = 0; i < this.elements.length; i++) {
element = this.elements[i];
element.genCSS(env, output);
}
}
},
toCSS: tree.toCSS,
markReferenced: function () {
this.isReferenced = true;
},
getIsReferenced: function() {
return !this.currentFileInfo.reference || this.isReferenced;
},
getIsOutput: function() {
return this.evaldCondition;
Selector.prototype = new Node();
Selector.prototype.type = "Selector";
Selector.prototype.accept = function (visitor) {
if (this.elements) {
this.elements = visitor.visitArray(this.elements);
}
if (this.extendList) {
this.extendList = visitor.visitArray(this.extendList);
}
if (this.condition) {
this.condition = visitor.visit(this.condition);
}
};
return Selector;
Selector.prototype.createDerived = function(elements, extendList, evaldCondition) {
evaldCondition = (evaldCondition != null) ? evaldCondition : this.evaldCondition;
var newSelector = new(Selector)(elements, extendList || this.extendList, null, this.index, this.currentFileInfo, this.isReferenced);
newSelector.evaldCondition = evaldCondition;
newSelector.mediaEmpty = this.mediaEmpty;
return newSelector;
};
Selector.prototype.match = function (other) {
var elements = this.elements,
len = elements.length,
olen, i;
other.CacheElements();
olen = other._elements.length;
if (olen === 0 || len < olen) {
return 0;
} else {
for (i = 0; i < olen; i++) {
if (elements[i].value !== other._elements[i]) {
return 0;
}
}
}
return olen; // return number of matched elements
};
Selector.prototype.CacheElements = function(){
var css = '', len, v, i;
if( !this._elements ){
len = this.elements.length;
for(i = 0; i < len; i++){
v = this.elements[i];
css += v.combinator.value;
if( !v.value.value ){
css += v.value;
continue;
}
if( typeof v.value.value !== "string" ){
css = '';
break;
}
css += v.value.value;
}
this._elements = css.match(/[,&#\*\.\w-]([\w-]|(\\.))*/g);
if (this._elements) {
if (this._elements[0] === "&") {
this._elements.shift();
}
} else {
this._elements = [];
}
}
};
Selector.prototype.isJustParentSelector = function() {
return !this.mediaEmpty &&
this.elements.length === 1 &&
this.elements[0].value === '&' &&
(this.elements[0].combinator.value === ' ' || this.elements[0].combinator.value === '');
};
Selector.prototype.eval = function (env) {
var evaldCondition = this.condition && this.condition.eval(env),
elements = this.elements, extendList = this.extendList;
elements = elements && elements.map(function (e) { return e.eval(env); });
extendList = extendList && extendList.map(function(extend) { return extend.eval(env); });
return this.createDerived(elements, extendList, evaldCondition);
};
Selector.prototype.genCSS = function (env, output) {
var i, element;
if ((!env || !env.firstSelector) && this.elements[0].combinator.value === "") {
output.add(' ', this.currentFileInfo, this.index);
}
if (!this._css) {
//TODO caching? speed comparison?
for(i = 0; i < this.elements.length; i++) {
element = this.elements[i];
element.genCSS(env, output);
}
}
};
Selector.prototype.markReferenced = function () {
this.isReferenced = true;
};
Selector.prototype.getIsReferenced = function() {
return !this.currentFileInfo.reference || this.isReferenced;
};
Selector.prototype.getIsOutput = function() {
return this.evaldCondition;
};
module.exports = Selector;

View File

@@ -1,15 +1,9 @@
module.exports = function (tree) {
var Node = require("./node.js");
var UnicodeDescriptor = function (value) {
this.value = value;
};
UnicodeDescriptor.prototype = {
type: "UnicodeDescriptor",
genCSS: function (env, output) {
output.add(this.value);
},
toCSS: tree.toCSS,
eval: function () { return this; }
};
return UnicodeDescriptor;
};
UnicodeDescriptor.prototype = new Node();
UnicodeDescriptor.prototype.type = "UnicodeDescriptor";
module.exports = UnicodeDescriptor;

View File

@@ -1,4 +1,5 @@
module.exports = function(tree, unitConversions) {
var Node = require("./node.js"),
unitConversions = require("../data/unit-conversions.js");
var Unit = function (numerator, denominator, backupUnit) {
this.numerator = numerator ? numerator.slice(0).sort() : [];
@@ -6,132 +7,120 @@ var Unit = function (numerator, denominator, backupUnit) {
this.backupUnit = backupUnit;
};
Unit.prototype = {
type: "Unit",
clone: function () {
return new Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit);
},
genCSS: function (env, output) {
if (this.numerator.length >= 1) {
output.add(this.numerator[0]);
} else
if (this.denominator.length >= 1) {
output.add(this.denominator[0]);
} else
if ((!env || !env.strictUnits) && this.backupUnit) {
output.add(this.backupUnit);
}
},
toCSS: tree.toCSS,
Unit.prototype = new Node();
Unit.prototype.type = "Unit";
Unit.prototype.clone = function () {
return new Unit(this.numerator.slice(0), this.denominator.slice(0), this.backupUnit);
};
Unit.prototype.genCSS = function (env, output) {
if (this.numerator.length >= 1) {
output.add(this.numerator[0]);
} else
if (this.denominator.length >= 1) {
output.add(this.denominator[0]);
} else
if ((!env || !env.strictUnits) && this.backupUnit) {
output.add(this.backupUnit);
}
};
Unit.prototype.toString = function () {
var i, returnStr = this.numerator.join("*");
for (i = 0; i < this.denominator.length; i++) {
returnStr += "/" + this.denominator[i];
}
return returnStr;
};
Unit.prototype.compare = function (other) {
return this.is(other.toString()) ? 0 : -1;
};
Unit.prototype.is = function (unitString) {
return this.toString() === unitString;
};
Unit.prototype.isLength = function () {
return Boolean(this.toCSS().match(/px|em|%|in|cm|mm|pc|pt|ex/));
};
Unit.prototype.isEmpty = function () {
return this.numerator.length === 0 && this.denominator.length === 0;
};
Unit.prototype.isSingular = function() {
return this.numerator.length <= 1 && this.denominator.length === 0;
};
Unit.prototype.map = function(callback) {
var i;
toString: function () {
var i, returnStr = this.numerator.join("*");
for (i = 0; i < this.denominator.length; i++) {
returnStr += "/" + this.denominator[i];
}
return returnStr;
},
for (i = 0; i < this.numerator.length; i++) {
this.numerator[i] = callback(this.numerator[i], false);
}
compare: function (other) {
return this.is(other.toString()) ? 0 : -1;
},
for (i = 0; i < this.denominator.length; i++) {
this.denominator[i] = callback(this.denominator[i], true);
}
};
Unit.prototype.usedUnits = function() {
var group, result = {}, mapUnit;
is: function (unitString) {
return this.toString() === unitString;
},
isLength: function () {
return Boolean(this.toCSS().match(/px|em|%|in|cm|mm|pc|pt|ex/));
},
isEmpty: function () {
return this.numerator.length === 0 && this.denominator.length === 0;
},
isSingular: function() {
return this.numerator.length <= 1 && this.denominator.length === 0;
},
map: function(callback) {
var i;
for (i = 0; i < this.numerator.length; i++) {
this.numerator[i] = callback(this.numerator[i], false);
mapUnit = function (atomicUnit) {
/*jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit) && !result[groupName]) {
result[groupName] = atomicUnit;
}
for (i = 0; i < this.denominator.length; i++) {
this.denominator[i] = callback(this.denominator[i], true);
return atomicUnit;
};
for (var groupName in unitConversions) {
if (unitConversions.hasOwnProperty(groupName)) {
group = unitConversions[groupName];
this.map(mapUnit);
}
},
}
usedUnits: function() {
var group, result = {}, mapUnit;
return result;
};
Unit.prototype.cancel = function () {
var counter = {}, atomicUnit, i, backup;
mapUnit = function (atomicUnit) {
/*jshint loopfunc:true */
if (group.hasOwnProperty(atomicUnit) && !result[groupName]) {
result[groupName] = atomicUnit;
}
return atomicUnit;
};
for (var groupName in unitConversions) {
if (unitConversions.hasOwnProperty(groupName)) {
group = unitConversions[groupName];
this.map(mapUnit);
}
for (i = 0; i < this.numerator.length; i++) {
atomicUnit = this.numerator[i];
if (!backup) {
backup = atomicUnit;
}
counter[atomicUnit] = (counter[atomicUnit] || 0) + 1;
}
return result;
},
cancel: function () {
var counter = {}, atomicUnit, i, backup;
for (i = 0; i < this.numerator.length; i++) {
atomicUnit = this.numerator[i];
if (!backup) {
backup = atomicUnit;
}
counter[atomicUnit] = (counter[atomicUnit] || 0) + 1;
for (i = 0; i < this.denominator.length; i++) {
atomicUnit = this.denominator[i];
if (!backup) {
backup = atomicUnit;
}
counter[atomicUnit] = (counter[atomicUnit] || 0) - 1;
}
for (i = 0; i < this.denominator.length; i++) {
atomicUnit = this.denominator[i];
if (!backup) {
backup = atomicUnit;
}
counter[atomicUnit] = (counter[atomicUnit] || 0) - 1;
}
this.numerator = [];
this.denominator = [];
this.numerator = [];
this.denominator = [];
for (atomicUnit in counter) {
if (counter.hasOwnProperty(atomicUnit)) {
var count = counter[atomicUnit];
for (atomicUnit in counter) {
if (counter.hasOwnProperty(atomicUnit)) {
var count = counter[atomicUnit];
if (count > 0) {
for (i = 0; i < count; i++) {
this.numerator.push(atomicUnit);
}
} else if (count < 0) {
for (i = 0; i < -count; i++) {
this.denominator.push(atomicUnit);
}
if (count > 0) {
for (i = 0; i < count; i++) {
this.numerator.push(atomicUnit);
}
} else if (count < 0) {
for (i = 0; i < -count; i++) {
this.denominator.push(atomicUnit);
}
}
}
if (this.numerator.length === 0 && this.denominator.length === 0 && backup) {
this.backupUnit = backup;
}
this.numerator.sort();
this.denominator.sort();
}
if (this.numerator.length === 0 && this.denominator.length === 0 && backup) {
this.backupUnit = backup;
}
this.numerator.sort();
this.denominator.sort();
};
return Unit;
};
module.exports = Unit;

View File

@@ -1,53 +1,50 @@
module.exports = function (tree) {
var Node = require("./node.js");
var URL = function (val, currentFileInfo, isEvald) {
this.value = val;
this.currentFileInfo = currentFileInfo;
this.isEvald = isEvald;
};
URL.prototype = {
type: "Url",
accept: function (visitor) {
this.value = visitor.visit(this.value);
},
genCSS: function (env, output) {
output.add("url(");
this.value.genCSS(env, output);
output.add(")");
},
toCSS: tree.toCSS,
eval: function (ctx) {
var val = this.value.eval(ctx),
rootpath;
URL.prototype = new Node();
URL.prototype.type = "Url";
URL.prototype.accept = function (visitor) {
this.value = visitor.visit(this.value);
};
URL.prototype.genCSS = function (env, output) {
output.add("url(");
this.value.genCSS(env, output);
output.add(")");
};
URL.prototype.eval = function (ctx) {
var val = this.value.eval(ctx),
rootpath;
if (!this.isEvald) {
// Add the base path if the URL is relative
rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
if (rootpath && typeof val.value === "string" && ctx.isPathRelative(val.value)) {
if (!val.quote) {
rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; });
}
val.value = rootpath + val.value;
if (!this.isEvald) {
// Add the base path if the URL is relative
rootpath = this.currentFileInfo && this.currentFileInfo.rootpath;
if (rootpath && typeof val.value === "string" && ctx.isPathRelative(val.value)) {
if (!val.quote) {
rootpath = rootpath.replace(/[\(\)'"\s]/g, function(match) { return "\\"+match; });
}
val.value = rootpath + val.value;
}
val.value = ctx.normalizePath(val.value);
val.value = ctx.normalizePath(val.value);
// Add url args if enabled
if (ctx.urlArgs) {
if (!val.value.match(/^\s*data:/)) {
var delimiter = val.value.indexOf('?') === -1 ? '?' : '&';
var urlArgs = delimiter + ctx.urlArgs;
if (val.value.indexOf('#') !== -1) {
val.value = val.value.replace('#', urlArgs + '#');
} else {
val.value += urlArgs;
}
// Add url args if enabled
if (ctx.urlArgs) {
if (!val.value.match(/^\s*data:/)) {
var delimiter = val.value.indexOf('?') === -1 ? '?' : '&';
var urlArgs = delimiter + ctx.urlArgs;
if (val.value.indexOf('#') !== -1) {
val.value = val.value.replace('#', urlArgs + '#');
} else {
val.value += urlArgs;
}
}
}
return new(URL)(val, this.currentFileInfo, true);
}
return new(URL)(val, this.currentFileInfo, true);
};
return URL;
};
module.exports = URL;

View File

@@ -1,34 +1,31 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Value = function (value) {
this.value = value;
};
Value.prototype = {
type: "Value",
accept: function (visitor) {
if (this.value) {
this.value = visitor.visitArray(this.value);
}
},
eval: function (env) {
if (this.value.length === 1) {
return this.value[0].eval(env);
} else {
return new(Value)(this.value.map(function (v) {
return v.eval(env);
}));
}
},
genCSS: function (env, output) {
var i;
for(i = 0; i < this.value.length; i++) {
this.value[i].genCSS(env, output);
if (i+1 < this.value.length) {
output.add((env && env.compress) ? ',' : ', ');
}
}
},
toCSS: tree.toCSS
Value.prototype = new Node();
Value.prototype.type = "Value";
Value.prototype.accept = function (visitor) {
if (this.value) {
this.value = visitor.visitArray(this.value);
}
};
return Value;
Value.prototype.eval = function (env) {
if (this.value.length === 1) {
return this.value[0].eval(env);
} else {
return new(Value)(this.value.map(function (v) {
return v.eval(env);
}));
}
};
Value.prototype.genCSS = function (env, output) {
var i;
for(i = 0; i < this.value.length; i++) {
this.value[i].genCSS(env, output);
if (i+1 < this.value.length) {
output.add((env && env.compress) ? ',' : ', ');
}
}
};
module.exports = Value;

View File

@@ -1,44 +1,49 @@
module.exports = function (tree) {
var Node = require("./node.js");
var Variable = function (name, index, currentFileInfo) {
this.name = name;
this.index = index;
this.currentFileInfo = currentFileInfo || {};
};
Variable.prototype = {
type: "Variable",
eval: function (env) {
var variable, name = this.name;
Variable.prototype = new Node();
Variable.prototype.type = "Variable";
Variable.prototype.eval = function (env) {
var variable, name = this.name;
if (name.indexOf('@@') === 0) {
name = '@' + new(Variable)(name.slice(1)).eval(env).value;
}
if (this.evaluating) {
throw { type: 'Name',
message: "Recursive variable definition for " + name,
filename: this.currentFileInfo.file,
index: this.index };
}
this.evaluating = true;
variable = tree.find(env.frames, function (frame) {
var v = frame.variable(name);
if (v) {
return v.value.eval(env);
}
});
if (variable) {
this.evaluating = false;
return variable;
} else {
throw { type: 'Name',
message: "variable " + name + " is undefined",
filename: this.currentFileInfo.filename,
index: this.index };
if (name.indexOf('@@') === 0) {
name = '@' + new(Variable)(name.slice(1)).eval(env).value;
}
if (this.evaluating) {
throw { type: 'Name',
message: "Recursive variable definition for " + name,
filename: this.currentFileInfo.file,
index: this.index };
}
this.evaluating = true;
variable = this.find(env.frames, function (frame) {
var v = frame.variable(name);
if (v) {
return v.value.eval(env);
}
});
if (variable) {
this.evaluating = false;
return variable;
} else {
throw { type: 'Name',
message: "variable " + name + " is undefined",
filename: this.currentFileInfo.filename,
index: this.index };
}
};
return Variable;
Variable.prototype.find = function (obj, fun) {
for (var i = 0, r; i < obj.length; i++) {
r = fun.call(obj, obj[i]);
if (r) { return r; }
}
return null;
};
module.exports = Variable;

View File

@@ -1,9 +1,10 @@
var contexts = require("../env.js");
module.exports = function (visitor, tree) {
var ImportVisitor = function(importer, finish, evalEnv, onceFileDetectionMap, recursionDetector) {
this._visitor = new visitor(this);
this._importer = importer;
this._finish = finish;
this.env = evalEnv || new tree.evalEnv();
this.env = evalEnv || new contexts.evalEnv();
this.importCount = 0;
this.onceFileDetectionMap = onceFileDetectionMap || {};
this.recursionDetector = {};
@@ -54,7 +55,7 @@ module.exports = function (visitor, tree) {
if (evaldImportNode && (!evaldImportNode.css || inlineCSS)) {
importNode = evaldImportNode;
this.importCount++;
var env = new tree.evalEnv(this.env, this.env.frames.slice(0));
var env = new contexts.evalEnv(this.env, this.env.frames.slice(0));
if (importNode.options.multiple) {
env.importMultiple = true;