(function (tree) { tree.Ruleset = function (selectors, rules, strictImports) { this.selectors = selectors; this.rules = rules; this._lookups = {}; this.strictImports = strictImports; }; tree.Ruleset.prototype = { eval: function (env) { var selectors = this.selectors && this.selectors.map(function (s) { return s.eval(env) }); var ruleset = new(tree.Ruleset)(selectors, this.rules.slice(0), this.strictImports); ruleset.root = this.root; ruleset.allowImports = this.allowImports; // push the current ruleset to the frames stack env.frames.unshift(ruleset); // Evaluate imports if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) { for (var i = 0; i < ruleset.rules.length; i++) { if (ruleset.rules[i] instanceof tree.Import) { Array.prototype.splice .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); } } } // Store the frames around mixin definitions, // so they can be evaluated like closures when the time comes. for (var i = 0; i < ruleset.rules.length; i++) { if (ruleset.rules[i] instanceof tree.mixin.Definition) { ruleset.rules[i].frames = env.frames.slice(0); } } // Evaluate mixin calls. for (var i = 0; i < ruleset.rules.length; i++) { if (ruleset.rules[i] instanceof tree.mixin.Call) { Array.prototype.splice .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); } } // Evaluate everything else for (var i = 0, rule; i < ruleset.rules.length; i++) { rule = ruleset.rules[i]; if (! (rule instanceof tree.mixin.Definition)) { ruleset.rules[i] = rule.eval ? rule.eval(env) : rule; } } // Pop the stack env.frames.shift(); return ruleset; }, match: function (args) { return !args || args.length === 0; }, variables: function () { if (this._variables) { return this._variables } else { return this._variables = this.rules.reduce(function (hash, r) { if (r instanceof tree.Rule && r.variable === true) { hash[r.name] = r; } return hash; }, {}); } }, variable: function (name) { return this.variables()[name]; }, rulesets: function () { if (this._rulesets) { return this._rulesets } else { return this._rulesets = this.rules.filter(function (r) { return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition); }); } }, find: function (selector, self) { self = self || this; var rules = [], rule, match, key = selector.toCSS(); if (key in this._lookups) { return this._lookups[key] } this.rulesets().forEach(function (rule) { if (rule !== self) { for (var j = 0; j < rule.selectors.length; j++) { if (match = selector.match(rule.selectors[j])) { if (selector.elements.length > rule.selectors[j].elements.length) { Array.prototype.push.apply(rules, rule.find( new(tree.Selector)(selector.elements.slice(1)), self)); } else { rules.push(rule); } break; } } } }); return this._lookups[key] = rules; }, // // Entry point for code generation // // `context` holds an array of arrays. // toCSS: function (context, env) { var css = [], // The CSS output rules = [], // node.Rule instances rulesets = [], // node.Ruleset instances paths = [], // Current selectors selector, // The fully rendered selector rule; if (! this.root) { if (context.length === 0) { paths = this.selectors.map(function (s) { return [s] }); } else { this.joinSelectors(paths, context, this.selectors); } } // Compile rules and rulesets for (var i = 0; i < this.rules.length; i++) { rule = this.rules[i]; if (rule.rules || (rule instanceof tree.Directive) || (rule instanceof tree.Media)) { rulesets.push(rule.toCSS(paths, env)); } else if (rule instanceof tree.Comment) { if (!rule.silent) { if (this.root) { rulesets.push(rule.toCSS(env)); } else { rules.push(rule.toCSS(env)); } } } else { if (rule.toCSS && !rule.variable) { rules.push(rule.toCSS(env)); } else if (rule.value && !rule.variable) { rules.push(rule.value.toString()); } } } rulesets = rulesets.join(''); // If this is the root node, we don't render // a selector, or {}. // Otherwise, only output if this ruleset has rules. if (this.root) { css.push(rules.join(env.compress ? '' : '\n')); } else { if (rules.length > 0) { selector = paths.map(function (p) { return p.map(function (s) { return s.toCSS(env); }).join('').trim(); }).join( env.compress ? ',' : ',\n'); css.push(selector, (env.compress ? '{' : ' {\n ') + rules.join(env.compress ? '' : '\n ') + (env.compress ? '}' : '\n}\n')); } } css.push(rulesets); return css.join('') + (env.compress ? '\n' : ''); }, joinSelectors: function (paths, context, selectors) { for (var s = 0; s < selectors.length; s++) { this.joinSelector(paths, context, selectors[s]); } }, joinSelector: function (paths, context, selector) { var before = [], after = [], beforeElements = [], afterElements = [], hasParentSelector = false, el; for (var i = 0; i < selector.elements.length; i++) { el = selector.elements[i]; if (el.combinator.value.charAt(0) === '&') { hasParentSelector = true; } if (hasParentSelector) afterElements.push(el); else beforeElements.push(el); } if (! hasParentSelector) { afterElements = beforeElements; beforeElements = []; } if (beforeElements.length > 0) { before.push(new(tree.Selector)(beforeElements)); } if (afterElements.length > 0) { after.push(new(tree.Selector)(afterElements)); } for (var c = 0; c < context.length; c++) { paths.push(before.concat(context[c]).concat(after)); } } }; })(require('../tree'));