specificity-graph
Version:
Generate an interactive Specificity Graph for your CSS.
2,226 lines (1,780 loc) • 485 kB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.specificityGraph=e()}}(function(){var _defi_,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
'use strict';
var generateCssData = _dereq_('./generateCssData');
var lineChart = _dereq_('./lineChart');
var _create = function(css, opts){
var data = generateCssData(css);
lineChart.create(data,opts);
}
var _update = function(css, opts){
var data = generateCssData(css);
lineChart.update(data);
}
module.exports = {
create: _create,
update: _update,
draw: lineChart.create,
nextFocus: lineChart.nextFocus,
prevFocus: lineChart.prevFocus
};
},{"./generateCssData":2,"./lineChart":3}],2:[function(_dereq_,module,exports){
'use strict';
var cssParse = _dereq_('css-parse');
var unminify = _dereq_('cssbeautify');
var specificity = _dereq_('specificity');
var generateCssData = function(origCss) {
var testCSS = unminify(origCss);
var astObj = cssParse(testCSS, {silent: true});
var results = [],
selectorIndex = 0;
var specSum = function(selector){
var specs = specificity.calculate(selector)[0].specificity.split(',');
var sum = (parseInt(specs[0])*1000) + (parseInt(specs[1])*100) + (parseInt(specs[2])*10) + (parseInt(specs[3]));
return sum;
}
astObj.stylesheet.rules.map(function(rule){
var selectors = rule.selectors;
if(typeof selectors === "undefined") {
return;
}
var line = rule.position.start.line;
selectors.forEach(function(selector){
if(selector.length === 0) return;
results.push({
selectorIndex: selectorIndex,
line: line,
specificity: specSum(selector),
selectors: selector
});
selectorIndex++;
});
});
return results;
}
module.exports = generateCssData;
},{"css-parse":7,"cssbeautify":30,"specificity":32}],3:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('d3');
if (typeof window === "undefined") {
console.log('d3 requires a browser to run. EXITING');
throw new Error;
}
var _state = {
data: {},
vis: undefined,
focus : undefined,
width: 1000,
height: 400,
padding: {
top: 40,
right: 60,
bottom: 40,
left: 60
},
min_val: 0, //same for x/y
max_val_y: 100,
d3_x : undefined,
d3_y : undefined,
data_attribute_name_x : 'selectorIndex',
data_attribute_name_y : 'specificity',
active_index : 0
};
var lineFunc = d3.svg.line()
.x(function(d,idx) {
return _state.d3_x(d[_state.data_attribute_name_x]);
})
.y(function(d) {
return _state.d3_y(d[_state.data_attribute_name_y]);
})
.interpolate('linear');
var _updateFocus = function(index){
var index = Math.min(Math.max(0, index), _state.data.length-1);
_state.active_index = index;
_state.focus.style('display', null);
var d = _state.data[_state.active_index];
_state.focus.attr('transform', 'translate(' + _state.d3_x(d[_state.data_attribute_name_x]) + ',' + _state.d3_y(d[_state.data_attribute_name_y]) + ')');
var t = _state.focus.select('.js-focus-text');
t.text(d.selectors + ': ' + d.specificity);
var w = t[0][0].getBBox().width + 20;
_state.focus.select('.js-focus-text-background')
.attr('width', w)
.attr('x', -w/2);
}
var _create = function(data, opts){
_state.data = data;
var opts = opts || {};
_state.width = opts.width || _state.width,
_state.height = opts.height || _state.height,
_state.data_attribute_name_x = opts.xProp || _state.data_attribute_name_x;
_state.data_attribute_name_y = opts.yProp || _state.data_attribute_name_y;
var svgSelector = opts.svgSelector || '.js-graph';
_state.vis = d3.select(svgSelector);
_update(_state.data);
// below elements don't change based on data
var xAxis = d3.svg.axis()
.scale(_state.d3_x)
.tickSize(0);
var yAxis = d3.svg.axis()
.scale(_state.d3_y)
.tickSize(0)
.orient('left');
_state.vis.append('svg:g')
.attr('class', 'axis' + (opts.showTicks ? '--show-ticks' : ''))
.attr('transform', 'translate(0,' + (_state.height - _state.padding.bottom) + ')')
.call(xAxis)
// Move the ticks out from the axis line
.selectAll("text")
.attr("transform", 'translate(0,' + (opts.showTicks ? 4 : 0) + ')');
_state.vis.append('svg:g')
.attr('class', 'axis' + (opts.showTicks ? '--show-ticks' : ''))
.attr('transform', 'translate(' + (_state.padding.left) + ',0)')
.call(yAxis)
// Move the ticks out from the axis line
.selectAll("text")
.attr("transform", 'translate(' + (opts.showTicks ? -4 : 0) + ', 0)');
// x domain label
_state.vis.append('svg:text')
.attr('class', 'domain-label')
.attr('text-anchor', 'middle')
.attr('x', (_state.width/2))
.attr('y', _state.height)
.attr('transform', 'translate(0,' + (-_state.padding.bottom+(opts.showTicks ? 34 : 24)) + ')')
.text('Location in stylesheet');
// y domain label
_state.vis.append('svg:text')
.attr('class', 'domain-label')
.attr('text-anchor', 'middle')
.attr('transform', 'translate('+(_state.padding.left-(opts.showTicks ? 34 : 16)) + ','+(_state.height/2)+')rotate(-90)')
.text('Specificity');
// handle on mouseover focus circle and info text
_state.focus = _state.vis.append('svg:g')
.attr('class', 'focus')
.style('display', 'none');
_state.focus.append('svg:circle')
.attr('r', 4.5);
_state.focus.append('svg:rect')
.attr('class', 'focus-text-background js-focus-text-background')
.attr('width',300)
.attr('height',20)
.attr('y', '-30')
.attr('ry', '14')
.attr('rx', '4');
_state.focus.append('svg:text')
.attr('class', 'focus-text js-focus-text')
.attr('text-anchor', 'middle')
.attr('dy', '0.35em')
.attr('y', '-20');
_state.vis.append('svg:rect')
.attr('class', 'overlay')
.attr('width', _state.width)
.attr('height', _state.height)
.on('mouseout', function() { _state.focus.style('display', 'none'); })
.on('mousemove', mousemove);
var bisectX = d3.bisector(function(d) {
return d[_state.data_attribute_name_x];
}).right;
function mousemove() {
var x0 = _state.d3_x.invert(d3.mouse(this)[0]),
i = bisectX(_state.data, x0),
d0 = _state.data[i - 1],
d1 = _state.data[i],
newIndex;
//check which value we're closer to (if within bounds)
if(typeof d0 === 'undefined'){
if(typeof d1 === 'undefined'){
return;
}
newIndex = i;
} else if(typeof d1 ==='undefined'){
newIndex = i -1;
} else {
if(x0 - d0[_state.data_attribute_name_x] > d1[_state.data_attribute_name_x] - x0){
newIndex = i;
} else {
newIndex = i -1;
}
}
_updateFocus(newIndex);
}
}
var _update = function(data){
_state.data = data;
_state.max_val_y = Math.max(100, d3.max(_state.data, function(d) {
return d[_state.data_attribute_name_y];
}));
_state.d3_x = d3.scale.linear()
.range([_state.padding.left, _state.width - _state.padding.right])
.domain([_state.min_val, d3.max(_state.data, function(d,idx) {
return d[_state.data_attribute_name_x];
})]);
_state.d3_y = d3.scale.linear()
.range([_state.height - _state.padding.top, _state.padding.bottom])
.domain([_state.min_val, _state.max_val_y]);
//TODO: transition path, instead of remove hack
if(document.querySelectorAll('.line-path').length === 0) {
_state.vis.append('svg:path')
.attr('d', lineFunc(_state.data))
.attr('class', 'line-path');
} else {
_state.vis.insert('svg:path', '.line-path')
.attr('d', lineFunc(_state.data))
.attr('class', 'line-path');
document.querySelector('.line-path:last-of-type').remove();
}
}
var _nextFocus = function(){
_updateFocus(_state.active_index + 1);
}
var _prevFocus = function(){
_updateFocus(_state.active_index - 1);
}
module.exports = {
create: _create,
update: _update,
nextFocus: _nextFocus,
prevFocus: _prevFocus
};
},{"d3":31}],4:[function(_dereq_,module,exports){
},{}],5:[function(_dereq_,module,exports){
(function (process){
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
// if the path tries to go above the root, `up` ends up > 0
var up = 0;
for (var i = parts.length - 1; i >= 0; i--) {
var last = parts[i];
if (last === '.') {
parts.splice(i, 1);
} else if (last === '..') {
parts.splice(i, 1);
up++;
} else if (up) {
parts.splice(i, 1);
up--;
}
}
// if the path is allowed to go above the root, restore leading ..s
if (allowAboveRoot) {
for (; up--; up) {
parts.unshift('..');
}
}
return parts;
}
// Split a filename into [root, dir, basename, ext], unix version
// 'root' is just a slash, or nothing.
var splitPathRe =
/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
var splitPath = function(filename) {
return splitPathRe.exec(filename).slice(1);
};
// path.resolve([from ...], to)
// posix version
exports.resolve = function() {
var resolvedPath = '',
resolvedAbsolute = false;
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
var path = (i >= 0) ? arguments[i] : process.cwd();
// Skip empty and invalid entries
if (typeof path !== 'string') {
throw new TypeError('Arguments to path.resolve must be strings');
} else if (!path) {
continue;
}
resolvedPath = path + '/' + resolvedPath;
resolvedAbsolute = path.charAt(0) === '/';
}
// At this point the path should be resolved to a full absolute path, but
// handle relative paths to be safe (might happen when process.cwd() fails)
// Normalize the path
resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
return !!p;
}), !resolvedAbsolute).join('/');
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
};
// path.normalize(path)
// posix version
exports.normalize = function(path) {
var isAbsolute = exports.isAbsolute(path),
trailingSlash = substr(path, -1) === '/';
// Normalize the path
path = normalizeArray(filter(path.split('/'), function(p) {
return !!p;
}), !isAbsolute).join('/');
if (!path && !isAbsolute) {
path = '.';
}
if (path && trailingSlash) {
path += '/';
}
return (isAbsolute ? '/' : '') + path;
};
// posix version
exports.isAbsolute = function(path) {
return path.charAt(0) === '/';
};
// posix version
exports.join = function() {
var paths = Array.prototype.slice.call(arguments, 0);
return exports.normalize(filter(paths, function(p, index) {
if (typeof p !== 'string') {
throw new TypeError('Arguments to path.join must be strings');
}
return p;
}).join('/'));
};
// path.relative(from, to)
// posix version
exports.relative = function(from, to) {
from = exports.resolve(from).substr(1);
to = exports.resolve(to).substr(1);
function trim(arr) {
var start = 0;
for (; start < arr.length; start++) {
if (arr[start] !== '') break;
}
var end = arr.length - 1;
for (; end >= 0; end--) {
if (arr[end] !== '') break;
}
if (start > end) return [];
return arr.slice(start, end - start + 1);
}
var fromParts = trim(from.split('/'));
var toParts = trim(to.split('/'));
var length = Math.min(fromParts.length, toParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (fromParts[i] !== toParts[i]) {
samePartsLength = i;
break;
}
}
var outputParts = [];
for (var i = samePartsLength; i < fromParts.length; i++) {
outputParts.push('..');
}
outputParts = outputParts.concat(toParts.slice(samePartsLength));
return outputParts.join('/');
};
exports.sep = '/';
exports.delimiter = ':';
exports.dirname = function(path) {
var result = splitPath(path),
root = result[0],
dir = result[1];
if (!root && !dir) {
// No dirname whatsoever
return '.';
}
if (dir) {
// It has a dirname, strip trailing slash
dir = dir.substr(0, dir.length - 1);
}
return root + dir;
};
exports.basename = function(path, ext) {
var f = splitPath(path)[2];
// TODO: make this comparison case-insensitive on windows?
if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length);
}
return f;
};
exports.extname = function(path) {
return splitPath(path)[3];
};
function filter (xs, f) {
if (xs.filter) return xs.filter(f);
var res = [];
for (var i = 0; i < xs.length; i++) {
if (f(xs[i], i, xs)) res.push(xs[i]);
}
return res;
}
// String.prototype.substr - negative index don't work in IE8
var substr = 'ab'.substr(-1) === 'b'
? function (str, start, len) { return str.substr(start, len) }
: function (str, start, len) {
if (start < 0) start = str.length + start;
return str.substr(start, len);
}
;
}).call(this,_dereq_('_process'))
},{"_process":6}],6:[function(_dereq_,module,exports){
// shim for using process in browser
var process = module.exports = {};
process.nextTick = (function () {
var canSetImmediate = typeof window !== 'undefined'
&& window.setImmediate;
var canMutationObserver = typeof window !== 'undefined'
&& window.MutationObserver;
var canPost = typeof window !== 'undefined'
&& window.postMessage && window.addEventListener
;
if (canSetImmediate) {
return function (f) { return window.setImmediate(f) };
}
var queue = [];
if (canMutationObserver) {
var hiddenDiv = document.createElement("div");
var observer = new MutationObserver(function () {
var queueList = queue.slice();
queue.length = 0;
queueList.forEach(function (fn) {
fn();
});
});
observer.observe(hiddenDiv, { attributes: true });
return function nextTick(fn) {
if (!queue.length) {
hiddenDiv.setAttribute('yes', 'no');
}
queue.push(fn);
};
}
if (canPost) {
window.addEventListener('message', function (ev) {
var source = ev.source;
if ((source === window || source === null) && ev.data === 'process-tick') {
ev.stopPropagation();
if (queue.length > 0) {
var fn = queue.shift();
fn();
}
}
}, true);
return function nextTick(fn) {
queue.push(fn);
window.postMessage('process-tick', '*');
};
}
return function nextTick(fn) {
setTimeout(fn, 0);
};
})();
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
// TODO(shtylman)
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
},{}],7:[function(_dereq_,module,exports){
module.exports = _dereq_('css').parse;
},{"css":8}],8:[function(_dereq_,module,exports){
exports.parse = _dereq_('./lib/parse');
exports.stringify = _dereq_('./lib/stringify');
},{"./lib/parse":9,"./lib/stringify":13}],9:[function(_dereq_,module,exports){
// http://www.w3.org/TR/CSS21/grammar.html
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g
module.exports = function(css, options){
options = options || {};
/**
* Positional.
*/
var lineno = 1;
var column = 1;
/**
* Update lineno and column based on `str`.
*/
function updatePosition(str) {
var lines = str.match(/\n/g);
if (lines) lineno += lines.length;
var i = str.lastIndexOf('\n');
column = ~i ? str.length - i : column + str.length;
}
/**
* Mark position and patch `node.position`.
*/
function position() {
var start = { line: lineno, column: column };
return function(node){
node.position = new Position(start);
whitespace();
return node;
};
}
/**
* Store position information for a node
*/
function Position(start) {
this.start = start;
this.end = { line: lineno, column: column };
this.source = options.source;
}
/**
* Non-enumerable source string
*/
Position.prototype.content = css;
/**
* Error `msg`.
*/
function error(msg) {
if (options.silent === true) {
return false;
}
var err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg);
err.reason = msg;
err.filename = options.source;
err.line = lineno;
err.column = column;
err.source = css;
throw err;
}
/**
* Parse stylesheet.
*/
function stylesheet() {
return {
type: 'stylesheet',
stylesheet: {
rules: rules()
}
};
}
/**
* Opening brace.
*/
function open() {
return match(/^{\s*/);
}
/**
* Closing brace.
*/
function close() {
return match(/^}/);
}
/**
* Parse ruleset.
*/
function rules() {
var node;
var rules = [];
whitespace();
comments(rules);
while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) {
if (node !== false) {
rules.push(node);
comments(rules);
}
}
return rules;
}
/**
* Match `re` and return captures.
*/
function match(re) {
var m = re.exec(css);
if (!m) return;
var str = m[0];
updatePosition(str);
css = css.slice(str.length);
return m;
}
/**
* Parse whitespace.
*/
function whitespace() {
match(/^\s*/);
}
/**
* Parse comments;
*/
function comments(rules) {
var c;
rules = rules || [];
while (c = comment()) {
if (c !== false) {
rules.push(c);
}
}
return rules;
}
/**
* Parse comment.
*/
function comment() {
var pos = position();
if ('/' != css.charAt(0) || '*' != css.charAt(1)) return;
var i = 2;
while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i;
i += 2;
if ("" === css.charAt(i-1)) {
return error('End of comment missing');
}
var str = css.slice(2, i - 2);
column += 2;
updatePosition(str);
css = css.slice(i);
column += 2;
return pos({
type: 'comment',
comment: str
});
}
/**
* Parse selector.
*/
function selector() {
var m = match(/^([^{]+)/);
if (!m) return;
/* @fix Remove all comments from selectors
* http://ostermiller.org/findcomment.html */
return trim(m[0])
.replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')
.replace(/(?:"[^"]*"|'[^']*')/g, function(m) {
return m.replace(/,/g, '\u200C');
})
.split(/\s*(?![^(]*\)),\s*/)
.map(function(s) {
return s.replace(/\u200C/g, ',');
});
}
/**
* Parse declaration.
*/
function declaration() {
var pos = position();
// prop
var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
if (!prop) return;
prop = trim(prop[0]);
// :
if (!match(/^:\s*/)) return error("property missing ':'");
// val
var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);
var ret = pos({
type: 'declaration',
property: prop.replace(commentre, ''),
value: val ? trim(val[0]).replace(commentre, '') : ''
});
// ;
match(/^[;\s]*/);
return ret;
}
/**
* Parse declarations.
*/
function declarations() {
var decls = [];
if (!open()) return error("missing '{'");
comments(decls);
// declarations
var decl;
while (decl = declaration()) {
if (decl !== false) {
decls.push(decl);
comments(decls);
}
}
if (!close()) return error("missing '}'");
return decls;
}
/**
* Parse keyframe.
*/
function keyframe() {
var m;
var vals = [];
var pos = position();
while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) {
vals.push(m[1]);
match(/^,\s*/);
}
if (!vals.length) return;
return pos({
type: 'keyframe',
values: vals,
declarations: declarations()
});
}
/**
* Parse keyframes.
*/
function atkeyframes() {
var pos = position();
var m = match(/^@([-\w]+)?keyframes */);
if (!m) return;
var vendor = m[1];
// identifier
var m = match(/^([-\w]+)\s*/);
if (!m) return error("@keyframes missing name");
var name = m[1];
if (!open()) return error("@keyframes missing '{'");
var frame;
var frames = comments();
while (frame = keyframe()) {
frames.push(frame);
frames = frames.concat(comments());
}
if (!close()) return error("@keyframes missing '}'");
return pos({
type: 'keyframes',
name: name,
vendor: vendor,
keyframes: frames
});
}
/**
* Parse supports.
*/
function atsupports() {
var pos = position();
var m = match(/^@supports *([^{]+)/);
if (!m) return;
var supports = trim(m[1]);
if (!open()) return error("@supports missing '{'");
var style = comments().concat(rules());
if (!close()) return error("@supports missing '}'");
return pos({
type: 'supports',
supports: supports,
rules: style
});
}
/**
* Parse host.
*/
function athost() {
var pos = position();
var m = match(/^@host */);
if (!m) return;
if (!open()) return error("@host missing '{'");
var style = comments().concat(rules());
if (!close()) return error("@host missing '}'");
return pos({
type: 'host',
rules: style
});
}
/**
* Parse media.
*/
function atmedia() {
var pos = position();
var m = match(/^@media *([^{]+)/);
if (!m) return;
var media = trim(m[1]);
if (!open()) return error("@media missing '{'");
var style = comments().concat(rules());
if (!close()) return error("@media missing '}'");
return pos({
type: 'media',
media: media,
rules: style
});
}
/**
* Parse custom-media.
*/
function atcustommedia() {
var pos = position();
var m = match(/^@custom-media (--[^\s]+) *([^{;]+);/);
if (!m) return;
return pos({
type: 'custom-media',
name: trim(m[1]),
media: trim(m[2])
});
}
/**
* Parse paged media.
*/
function atpage() {
var pos = position();
var m = match(/^@page */);
if (!m) return;
var sel = selector() || [];
if (!open()) return error("@page missing '{'");
var decls = comments();
// declarations
var decl;
while (decl = declaration()) {
decls.push(decl);
decls = decls.concat(comments());
}
if (!close()) return error("@page missing '}'");
return pos({
type: 'page',
selectors: sel,
declarations: decls
});
}
/**
* Parse document.
*/
function atdocument() {
var pos = position();
var m = match(/^@([-\w]+)?document *([^{]+)/);
if (!m) return;
var vendor = trim(m[1]);
var doc = trim(m[2]);
if (!open()) return error("@document missing '{'");
var style = comments().concat(rules());
if (!close()) return error("@document missing '}'");
return pos({
type: 'document',
document: doc,
vendor: vendor,
rules: style
});
}
/**
* Parse font-face.
*/
function atfontface() {
var pos = position();
var m = match(/^@font-face */);
if (!m) return;
if (!open()) return error("@font-face missing '{'");
var decls = comments();
// declarations
var decl;
while (decl = declaration()) {
decls.push(decl);
decls = decls.concat(comments());
}
if (!close()) return error("@font-face missing '}'");
return pos({
type: 'font-face',
declarations: decls
});
}
/**
* Parse import
*/
var atimport = _compileAtrule('import');
/**
* Parse charset
*/
var atcharset = _compileAtrule('charset');
/**
* Parse namespace
*/
var atnamespace = _compileAtrule('namespace');
/**
* Parse non-block at-rules
*/
function _compileAtrule(name) {
var re = new RegExp('^@' + name + ' *([^;\\n]+);');
return function() {
var pos = position();
var m = match(re);
if (!m) return;
var ret = { type: name };
ret[name] = m[1].trim();
return pos(ret);
}
}
/**
* Parse at rule.
*/
function atrule() {
if (css[0] != '@') return;
return atkeyframes()
|| atmedia()
|| atcustommedia()
|| atsupports()
|| atimport()
|| atcharset()
|| atnamespace()
|| atdocument()
|| atpage()
|| athost()
|| atfontface();
}
/**
* Parse rule.
*/
function rule() {
var pos = position();
var sel = selector();
if (!sel) return error('selector missing');
comments();
return pos({
type: 'rule',
selectors: sel,
declarations: declarations()
});
}
return addParent(stylesheet());
};
/**
* Trim `str`.
*/
function trim(str) {
return str ? str.replace(/^\s+|\s+$/g, '') : '';
}
/**
* Adds non-enumerable parent node reference to each node.
*/
function addParent(obj, parent) {
var isNode = obj && typeof obj.type === 'string';
var childParent = isNode ? obj : parent;
for (var k in obj) {
var value = obj[k];
if (Array.isArray(value)) {
value.forEach(function(v) { addParent(v, childParent); });
} else if (value && typeof value === 'object') {
addParent(value, childParent);
}
}
if (isNode) {
Object.defineProperty(obj, 'parent', {
configurable: true,
writable: true,
enumerable: false,
value: parent || null
});
}
return obj;
}
},{}],10:[function(_dereq_,module,exports){
/**
* Expose `Compiler`.
*/
module.exports = Compiler;
/**
* Initialize a compiler.
*
* @param {Type} name
* @return {Type}
* @api public
*/
function Compiler(opts) {
this.options = opts || {};
}
/**
* Emit `str`
*/
Compiler.prototype.emit = function(str) {
return str;
};
/**
* Visit `node`.
*/
Compiler.prototype.visit = function(node){
return this[node.type](node);
};
/**
* Map visit over array of `nodes`, optionally using a `delim`
*/
Compiler.prototype.mapVisit = function(nodes, delim){
var buf = '';
delim = delim || '';
for (var i = 0, length = nodes.length; i < length; i++) {
buf += this.visit(nodes[i]);
if (delim && i < length - 1) buf += this.emit(delim);
}
return buf;
};
},{}],11:[function(_dereq_,module,exports){
/**
* Module dependencies.
*/
var Base = _dereq_('./compiler');
var inherits = _dereq_('inherits');
/**
* Expose compiler.
*/
module.exports = Compiler;
/**
* Initialize a new `Compiler`.
*/
function Compiler(options) {
Base.call(this, options);
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Compiler, Base);
/**
* Compile `node`.
*/
Compiler.prototype.compile = function(node){
return node.stylesheet
.rules.map(this.visit, this)
.join('');
};
/**
* Visit comment node.
*/
Compiler.prototype.comment = function(node){
return this.emit('', node.position);
};
/**
* Visit import node.
*/
Compiler.prototype.import = function(node){
return this.emit('@import ' + node.import + ';', node.position);
};
/**
* Visit media node.
*/
Compiler.prototype.media = function(node){
return this.emit('@media ' + node.media, node.position)
+ this.emit('{')
+ this.mapVisit(node.rules)
+ this.emit('}');
};
/**
* Visit document node.
*/
Compiler.prototype.document = function(node){
var doc = '@' + (node.vendor || '') + 'document ' + node.document;
return this.emit(doc, node.position)
+ this.emit('{')
+ this.mapVisit(node.rules)
+ this.emit('}');
};
/**
* Visit charset node.
*/
Compiler.prototype.charset = function(node){
return this.emit('@charset ' + node.charset + ';', node.position);
};
/**
* Visit namespace node.
*/
Compiler.prototype.namespace = function(node){
return this.emit('@namespace ' + node.namespace + ';', node.position);
};
/**
* Visit supports node.
*/
Compiler.prototype.supports = function(node){
return this.emit('@supports ' + node.supports, node.position)
+ this.emit('{')
+ this.mapVisit(node.rules)
+ this.emit('}');
};
/**
* Visit keyframes node.
*/
Compiler.prototype.keyframes = function(node){
return this.emit('@'
+ (node.vendor || '')
+ 'keyframes '
+ node.name, node.position)
+ this.emit('{')
+ this.mapVisit(node.keyframes)
+ this.emit('}');
};
/**
* Visit keyframe node.
*/
Compiler.prototype.keyframe = function(node){
var decls = node.declarations;
return this.emit(node.values.join(','), node.position)
+ this.emit('{')
+ this.mapVisit(decls)
+ this.emit('}');
};
/**
* Visit page node.
*/
Compiler.prototype.page = function(node){
var sel = node.selectors.length
? node.selectors.join(', ')
: '';
return this.emit('@page ' + sel, node.position)
+ this.emit('{')
+ this.mapVisit(node.declarations)
+ this.emit('}');
};
/**
* Visit font-face node.
*/
Compiler.prototype['font-face'] = function(node){
return this.emit('@font-face', node.position)
+ this.emit('{')
+ this.mapVisit(node.declarations)
+ this.emit('}');
};
/**
* Visit host node.
*/
Compiler.prototype.host = function(node){
return this.emit('@host', node.position)
+ this.emit('{')
+ this.mapVisit(node.rules)
+ this.emit('}');
};
/**
* Visit custom-media node.
*/
Compiler.prototype['custom-media'] = function(node){
return this.emit('@custom-media ' + node.name + ' ' + node.media + ';', node.position);
};
/**
* Visit rule node.
*/
Compiler.prototype.rule = function(node){
var decls = node.declarations;
if (!decls.length) return '';
return this.emit(node.selectors.join(','), node.position)
+ this.emit('{')
+ this.mapVisit(decls)
+ this.emit('}');
};
/**
* Visit declaration node.
*/
Compiler.prototype.declaration = function(node){
return this.emit(node.property + ':' + node.value, node.position) + this.emit(';');
};
},{"./compiler":10,"inherits":15}],12:[function(_dereq_,module,exports){
/**
* Module dependencies.
*/
var Base = _dereq_('./compiler');
var inherits = _dereq_('inherits');
/**
* Expose compiler.
*/
module.exports = Compiler;
/**
* Initialize a new `Compiler`.
*/
function Compiler(options) {
options = options || {};
Base.call(this, options);
this.indentation = options.indent;
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Compiler, Base);
/**
* Compile `node`.
*/
Compiler.prototype.compile = function(node){
return this.stylesheet(node);
};
/**
* Visit stylesheet node.
*/
Compiler.prototype.stylesheet = function(node){
return this.mapVisit(node.stylesheet.rules, '\n\n');
};
/**
* Visit comment node.
*/
Compiler.prototype.comment = function(node){
return this.emit(this.indent() + '/*' + node.comment + '*/', node.position);
};
/**
* Visit import node.
*/
Compiler.prototype.import = function(node){
return this.emit('@import ' + node.import + ';', node.position);
};
/**
* Visit media node.
*/
Compiler.prototype.media = function(node){
return this.emit('@media ' + node.media, node.position)
+ this.emit(
' {\n'
+ this.indent(1))
+ this.mapVisit(node.rules, '\n\n')
+ this.emit(
this.indent(-1)
+ '\n}');
};
/**
* Visit document node.
*/
Compiler.prototype.document = function(node){
var doc = '@' + (node.vendor || '') + 'document ' + node.document;
return this.emit(doc, node.position)
+ this.emit(
' '
+ ' {\n'
+ this.indent(1))
+ this.mapVisit(node.rules, '\n\n')
+ this.emit(
this.indent(-1)
+ '\n}');
};
/**
* Visit charset node.
*/
Compiler.prototype.charset = function(node){
return this.emit('@charset ' + node.charset + ';', node.position);
};
/**
* Visit namespace node.
*/
Compiler.prototype.namespace = function(node){
return this.emit('@namespace ' + node.namespace + ';', node.position);
};
/**
* Visit supports node.
*/
Compiler.prototype.supports = function(node){
return this.emit('@supports ' + node.supports, node.position)
+ this.emit(
' {\n'
+ this.indent(1))
+ this.mapVisit(node.rules, '\n\n')
+ this.emit(
this.indent(-1)
+ '\n}');
};
/**
* Visit keyframes node.
*/
Compiler.prototype.keyframes = function(node){
return this.emit('@' + (node.vendor || '') + 'keyframes ' + node.name, node.position)
+ this.emit(
' {\n'
+ this.indent(1))
+ this.mapVisit(node.keyframes, '\n')
+ this.emit(
this.indent(-1)
+ '}');
};
/**
* Visit keyframe node.
*/
Compiler.prototype.keyframe = function(node){
var decls = node.declarations;
return this.emit(this.indent())
+ this.emit(node.values.join(', '), node.position)
+ this.emit(
' {\n'
+ this.indent(1))
+ this.mapVisit(decls, '\n')
+ this.emit(
this.indent(-1)
+ '\n'
+ this.indent() + '}\n');
};
/**
* Visit page node.
*/
Compiler.prototype.page = function(node){
var sel = node.selectors.length
? node.selectors.join(', ') + ' '
: '';
return this.emit('@page ' + sel, node.position)
+ this.emit('{\n')
+ this.emit(this.indent(1))
+ this.mapVisit(node.declarations, '\n')
+ this.emit(this.indent(-1))
+ this.emit('\n}');
};
/**
* Visit font-face node.
*/
Compiler.prototype['font-face'] = function(node){
return this.emit('@font-face ', node.position)
+ this.emit('{\n')
+ this.emit(this.indent(1))
+ this.mapVisit(node.declarations, '\n')
+ this.emit(this.indent(-1))
+ this.emit('\n}');
};
/**
* Visit host node.
*/
Compiler.prototype.host = function(node){
return this.emit('@host', node.position)
+ this.emit(
' {\n'
+ this.indent(1))
+ this.mapVisit(node.rules, '\n\n')
+ this.emit(
this.indent(-1)
+ '\n}');
};
/**
* Visit custom-media node.
*/
Compiler.prototype['custom-media'] = function(node){
return this.emit('@custom-media ' + node.name + ' ' + node.media + ';', node.position);
};
/**
* Visit rule node.
*/
Compiler.prototype.rule = function(node){
var indent = this.indent();
var decls = node.declarations;
if (!decls.length) return '';
return this.emit(node.selectors.map(function(s){ return indent + s }).join(',\n'), node.position)
+ this.emit(' {\n')
+ this.emit(this.indent(1))
+ this.mapVisit(decls, '\n')
+ this.emit(this.indent(-1))
+ this.emit('\n' + this.indent() + '}');
};
/**
* Visit declaration node.
*/
Compiler.prototype.declaration = function(node){
return this.emit(this.indent())
+ this.emit(node.property + ': ' + node.value, node.position)
+ this.emit(';');
};
/**
* Increase, decrease or return current indentation.
*/
Compiler.prototype.indent = function(level) {
this.level = this.level || 1;
if (null != level) {
this.level += level;
return '';
}
return Array(this.level).join(this.indentation || ' ');
};
},{"./compiler":10,"inherits":15}],13:[function(_dereq_,module,exports){
/**
* Module dependencies.
*/
var Compressed = _dereq_('./compress');
var Identity = _dereq_('./identity');
/**
* Stringfy the given AST `node`.
*
* Options:
*
* - `compress` space-optimized output
* - `sourcemap` return an object with `.code` and `.map`
*
* @param {Object} node
* @param {Object} [options]
* @return {String}
* @api public
*/
module.exports = function(node, options){
options = options || {};
var compiler = options.compress
? new Compressed(options)
: new Identity(options);
// source maps
if (options.sourcemap) {
var sourcemaps = _dereq_('./source-map-support');
sourcemaps(compiler);
var code = compiler.compile(node);
compiler.applySourceMaps();
var map = options.sourcemap === 'generator'
? compiler.map
: compiler.map.toJSON();
return { code: code, map: map };
}
var code = compiler.compile(node);
return code;
};
},{"./compress":11,"./identity":12,"./source-map-support":14}],14:[function(_dereq_,module,exports){
/**
* Module dependencies.
*/
var SourceMap = _dereq_('source-map').SourceMapGenerator;
var SourceMapConsumer = _dereq_('source-map').SourceMapConsumer;
var sourceMapResolve = _dereq_('source-map-resolve');
var urix = _dereq_('urix');
var fs = _dereq_('fs');
var path = _dereq_('path');
/**
* Expose `mixin()`.
*/
module.exports = mixin;
/**
* Mixin source map support into `compiler`.
*
* @param {Compiler} compiler
* @api public
*/
function mixin(compiler) {
compiler._comment = compiler.comment;
compiler.map = new SourceMap();
compiler.position = { line: 1, column: 1 };
compiler.files = {};
for (var k in exports) compiler[k] = exports[k];
}
/**
* Update position.
*
* @param {String} str
* @api private
*/
exports.updatePosition = function(str) {
var lines = str.match(/\n/g);
if (lines) this.position.line += lines.length;
var i = str.lastIndexOf('\n');
this.position.column = ~i ? str.length - i : this.position.column + str.length;
};
/**
* Emit `str`.
*
* @param {String} str
* @param {Object} [pos]
* @return {String}
* @api private
*/
exports.emit = function(str, pos) {
if (pos) {
var sourceFile = urix(pos.source || 'source.css');
this.map.addMapping({
source: sourceFile,
generated: {
line: this.position.line,
column: Math.max(this.position.column - 1, 0)
},
original: {
line: pos.start.line,
column: pos.start.column - 1
}
});
this.addFile(sourceFile, pos);
}
this.updatePosition(str);
return str;
};
/**
* Adds a file to the source map output if it has not already been added
* @param {String} file
* @param {Object} pos
*/
exports.addFile = function(file, pos) {
if (typeof pos.content !== 'string') return;
if (Object.prototype.hasOwnProperty.call(this.files, file)) return;
this.files[file] = pos.content;
};
/**
* Applies any original source maps to the output and embeds the source file
* contents in the source map.
*/
exports.applySourceMaps = function() {
Object.keys(this.files).forEach(function(file) {
var content = this.files[file];
this.map.setSourceContent(file, content);
if (this.options.inputSourcemaps !== false) {
var originalMap = sourceMapResolve.resolveSync(
content, file, fs.readFileSync);
if (originalMap) {
var map = new SourceMapConsumer(originalMap.map);
var relativeTo = originalMap.sourcesRelativeTo;
this.map.applySourceMap(map, file, urix(path.dirname(relativeTo)));
}
}
}, this);
};
/**
* Process comments, drops sourceMap comments.
* @param {Object} node
*/
exports.comment = function(node) {
if (/^# sourceMappingURL=/.test(node.comment))
return this.emit('', node.position);
else
return this._comment(node);
};
},{"fs":4,"path":5,"source-map":19,"source-map-resolve":18,"urix":29}],15:[function(_dereq_,module,exports){
if (typeof Object.create === 'function') {
// implementation from standard node.js 'util' module
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
} else {
// old school shim for old browsers
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
var TempCtor = function () {}
TempCtor.prototype = superCtor.prototype
ctor.prototype = new TempCtor()
ctor.prototype.constructor = ctor
}
}
},{}],16:[function(_dereq_,module,exports){
// Copyright 2014 Simon Lydell
// X11 (“MIT”) Licensed. (See LICENSE.)
void (function(root, factory) {
if (typeof _defi_ === "function" && _defi_.amd) {
_defi_(factory)
} else if (typeof exports === "object") {
module.exports = factory()
} else {
root.resolveUrl = factory()
}
}(this, function() {
function resolveUrl(/* ...urls */) {
var numUrls = arguments.length
if (numUrls === 0) {
throw new Error("resolveUrl requires at least one argument; got none.")
}
var base = document.createElement("base")
base.href = arguments[0]
if (numUrls === 1) {
return base.href
}
var head = document.getElementsByTagName("head")[0]
head.insertBefore(base, head.firstChild)
var a = document.createElement("a")
var resolved
for (var index = 1; index < numUrls; index++) {
a.href = arguments[index]
resolved = a.href
base.href = resolved
}
head.removeChild(base)
return resolved
}
return resolveUrl
}));
},{}],17:[function(_dereq_,module,exports){
// Copyright 2014 Simon Lydell
// X11 (“MIT”) Licensed. (See LICENSE.)
void (function(root, factory) {
if (typeof _defi_ === "function" && _defi_.amd) {
_defi_(factory)
} else if (typeof exports === "object") {
module.exports = factory()
} else {
root.sourceMappingURL = factory()
}
}(this, function() {
var innerRegex = /[#@] sourceMappingURL=([^\s'"]*)/
var regex = RegExp(
"(?:" +
"/\\*" +
"(?:\\s*\r?\n(?://)?)?" +
"(?:" + innerRegex.source + ")" +
"\\s*" +
"\\*/" +
"|" +
"//(?:" + innerRegex.source + ")" +
")" +
"\\s*$"
)
return {
regex: regex,
_innerRegex: innerRegex,
getFrom: function(code) {
var match = code.match(regex)
return (match ? match[1] || match[2] || "" : null)
},
existsIn: function(code) {
return regex.test(code)
},
removeFrom: function(code) {
return code.replace(regex, "")
},
insertBefore: function(code, string) {
var match = code.match(regex)
if (match) {
return code.slice(0, match.index) + string + code.slice(match.index)
} else {
return code + string
}
}
}
}));
},{}],18:[function(_dereq_,module,exports){
// Copyright 2014 Simon Lydell
// X11 (“MIT”) Licensed. (See LICENSE.)
// Note: source-map-resolve.js is generated from source-map-resolve-node.js and
// source-map-resolve-template.js. Only edit the two latter files, _not_
// source-map-resolve.js!
void (function(root, factory) {
if (typeof _defi_ === "function" && _defi_.amd) {
_defi_(["source-map-url", "resolve-url"], factory)
} else if (typeof exports === "object") {
var sourceMappingURL = _dereq_("source-map-url")
var resolveUrl = _dereq_("resolve-url")
module.exports = factory(sourceMappingURL, resolveUrl)
} else {
root.sourceMapResolve = factory(root.sourceMappingURL, root.resolveUrl)
}
}(this, function(sourceMappingURL, resolveUrl) {
function callbackAsync(callback, error, result) {
setImmediate(function() { callback(error, result) })
}
function parseMapToJSON(string) {
return JSON.parse(string.replace(/^\)\]\}'/, ""))
}
function resolveSourceMap(code, codeUrl, read, callback) {
var mapData
try {
mapData = resolveSourceMapHelper(code, codeUrl)
} catch (error) {
return callbackAsync(callback, error)
}
if (!mapData || mapData.map) {
return callbackAsync(callback, null, mapData)
}
read(mapData.url, function(error, result) {
if (error) {
return callback(error)
}
try {
mapData.map = parseMapToJSON(String(result))
} catch (error) {
return callback(error)
}
callback(null, mapData)
})
}
function resolveSourceMapSync(code, codeUrl, read) {
var mapData = resolveSourceMapHelper(code, codeUrl)
if (!mapData || mapData.map) {
return mapData
}
mapData.map = parseMapToJSON(String(read(mapData.url)))
return mapData
}
var dataUriRegex = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/
var jsonMimeTypeRegex = /^(?:application|text)\/json$/
function resolveSourceMapHelper(code, codeUrl) {
var url = sourceMappingURL.getFrom(code)
if (!url) {
return null
}
var dataUri = url.match(dataUriRegex)
if (dataUri) {
var mimeType = dataUri[1]
var lastParameter = dataUri[2]
var encoded = dataUri[3]
if (!jsonMimeTypeRegex.test(mimeType)) {
throw new Error("Unuseful data uri mime type: " + (mimeType || "text/plain"))
}
return {
sourceMappingURL: url,
url: null,
sourcesRelativeTo: codeUrl,
map: parseMapToJSON(lastParameter === ";base64" ? atob(encoded) : decodeURIComponent(encoded))
}
}
var mapUrl = resolveUrl(codeUrl, url)
return {
sourceMappingURL: url,
url: mapUrl,
sourcesRelativeTo: mapUrl,
map: null
}
}
function resolveSources(map, mapUrl, read, options, callback) {
if (typeof options === "function") {
callback = options
options = {}
}
var pending = map.sources.length
var errored = false
var result = {
sourcesResolved: [],
sourcesContent: []
}
var done = function(error) {
if (errored) {
return
}
if (error) {
errored = true
return callback(error)
}
pending--
if (pending === 0) {
callback(null, result)
}
}
resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) {
result.sourcesResolved[index] = fullUrl
if (typeof sourceContent === "string") {
result.sourcesContent[index] = sourceContent
callbackAsync(done, null)
} else {
read(fullUrl, function(error, source) {
result.sourcesContent[index] = String(source)
done(error)
})
}
})
}
function resolveSourcesSync(map, mapUrl, read, options) {
var result = {
sourcesResolved: [],
sourcesContent: []
}
resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) {
result.sourcesResolved[index] = fullUrl
if (read !== null) {
if (typeof sourceContent === "string") {
result.sourcesContent[index] = sourceContent
} else {
result.sourcesContent[index] = String(read(fullUrl))
}
}
})
return result
}
var endingSlash = /\/?$/
function resolveSourcesHelper(map, mapUrl, options, fn) {
options = options || {}
var fullUrl
var sourceContent
for (var index = 0, len = map.sources.length; index < len; index++) {
if (map.sourceRoot && !options.ignoreSourceRoot) {
// Make sure that the sourceRoot ends with a slash, so that `/scripts/subdir` becomes
// `/scripts/subdir/<source>`, not `/scripts/<source>`. Pointing to a file as source root
// does not make sense.
fullUrl = resolveUrl(mapUrl, map.sourceRoot.replace(endingSlash, "/"), map.sources[index])
} else {
fullUrl = resolveUrl(mapUrl, map.sources[index])
}
sourceContent = (map.sourcesContent || [])[index]
fn(fullUrl, sourceContent, index)
}
}
function resolve(code, codeUrl, read, options, callback) {
if (typeof options === "function") {
callback = options
options = {}
}
resolveSourceMap(code, codeUrl, read