123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- /*!
- * Stylus - utils
- * Copyright (c) Automattic <developer.wordpress.com>
- * MIT Licensed
- */
- /**
- * Module dependencies.
- */
- var nodes = require('./nodes')
- , basename = require('path').basename
- , relative = require('path').relative
- , join = require('path').join
- , isAbsolute = require('path').isAbsolute
- , glob = require('glob')
- , fs = require('fs');
- /**
- * Check if `path` looks absolute.
- *
- * @param {String} path
- * @return {Boolean}
- * @api private
- */
- exports.absolute = isAbsolute || function(path){
- // On Windows the path could start with a drive letter, i.e. a:\\ or two leading backslashes.
- // Also on Windows, the path may have been normalized to forward slashes, so check for this too.
- return path.substr(0, 2) == '\\\\' || '/' === path.charAt(0) || /^[a-z]:[\\\/]/i.test(path);
- };
- /**
- * Attempt to lookup `path` within `paths` from tail to head.
- * Optionally a path to `ignore` may be passed.
- *
- * @param {String} path
- * @param {String} paths
- * @param {String} ignore
- * @return {String}
- * @api private
- */
- exports.lookup = function(path, paths, ignore){
- var lookup
- , i = paths.length;
- // Absolute
- if (exports.absolute(path)) {
- try {
- fs.statSync(path);
- return path;
- } catch (err) {
- // Ignore, continue on
- // to trying relative lookup.
- // Needed for url(/images/foo.png)
- // for example
- }
- }
- // Relative
- while (i--) {
- try {
- lookup = join(paths[i], path);
- if (ignore == lookup) continue;
- fs.statSync(lookup);
- return lookup;
- } catch (err) {
- // Ignore
- }
- }
- };
- /**
- * Like `utils.lookup` but uses `glob` to find files.
- *
- * @param {String} path
- * @param {String} paths
- * @param {String} ignore
- * @return {Array}
- * @api private
- */
- exports.find = function(path, paths, ignore) {
- var lookup
- , found
- , i = paths.length;
- // Absolute
- if (exports.absolute(path)) {
- if ((found = glob.sync(path)).length) {
- return found;
- }
- }
- // Relative
- while (i--) {
- lookup = join(paths[i], path);
- if (ignore == lookup) continue;
- if ((found = glob.sync(lookup)).length) {
- return found;
- }
- }
- };
- /**
- * Lookup index file inside dir with given `name`.
- *
- * @param {String} name
- * @return {Array}
- * @api private
- */
- exports.lookupIndex = function(name, paths, filename){
- // foo/index.styl
- var found = exports.find(join(name, 'index.styl'), paths, filename);
- if (!found) {
- // foo/foo.styl
- found = exports.find(join(name, basename(name).replace(/\.styl/i, '') + '.styl'), paths, filename);
- }
- if (!found && !~name.indexOf('node_modules')) {
- // node_modules/foo/.. or node_modules/foo.styl/..
- found = lookupPackage(join('node_modules', name));
- }
- return found;
- function lookupPackage(dir) {
- var pkg = exports.lookup(join(dir, 'package.json'), paths, filename);
- if (!pkg) {
- return /\.styl$/i.test(dir) ? exports.lookupIndex(dir, paths, filename) : lookupPackage(dir + '.styl');
- }
- var main = require(relative(__dirname, pkg)).main;
- if (main) {
- found = exports.find(join(dir, main), paths, filename);
- } else {
- found = exports.lookupIndex(dir, paths, filename);
- }
- return found;
- }
- };
- /**
- * Format the given `err` with the given `options`.
- *
- * Options:
- *
- * - `filename` context filename
- * - `context` context line count [8]
- * - `lineno` context line number
- * - `column` context column number
- * - `input` input string
- *
- * @param {Error} err
- * @param {Object} options
- * @return {Error}
- * @api private
- */
- exports.formatException = function(err, options){
- var lineno = options.lineno
- , column = options.column
- , filename = options.filename
- , str = options.input
- , context = options.context || 8
- , context = context / 2
- , lines = ('\n' + str).split('\n')
- , start = Math.max(lineno - context, 1)
- , end = Math.min(lines.length, lineno + context)
- , pad = end.toString().length;
- var context = lines.slice(start, end).map(function(line, i){
- var curr = i + start;
- return ' '
- + Array(pad - curr.toString().length + 1).join(' ')
- + curr
- + '| '
- + line
- + (curr == lineno
- ? '\n' + Array(curr.toString().length + 5 + column).join('-') + '^'
- : '');
- }).join('\n');
- err.message = filename
- + ':' + lineno
- + ':' + column
- + '\n' + context
- + '\n\n' + err.message + '\n'
- + (err.stylusStack ? err.stylusStack + '\n' : '');
- // Don't show JS stack trace for Stylus errors
- if (err.fromStylus) err.stack = 'Error: ' + err.message;
- return err;
- };
- /**
- * Assert that `node` is of the given `type`, or throw.
- *
- * @param {Node} node
- * @param {Function} type
- * @param {String} param
- * @api public
- */
- exports.assertType = function(node, type, param){
- exports.assertPresent(node, param);
- if (node.nodeName == type) return;
- var actual = node.nodeName
- , msg = 'expected '
- + (param ? '"' + param + '" to be a ' : '')
- + type + ', but got '
- + actual + ':' + node;
- throw new Error('TypeError: ' + msg);
- };
- /**
- * Assert that `node` is a `String` or `Ident`.
- *
- * @param {Node} node
- * @param {String} param
- * @api public
- */
- exports.assertString = function(node, param){
- exports.assertPresent(node, param);
- switch (node.nodeName) {
- case 'string':
- case 'ident':
- case 'literal':
- return;
- default:
- var actual = node.nodeName
- , msg = 'expected string, ident or literal, but got ' + actual + ':' + node;
- throw new Error('TypeError: ' + msg);
- }
- };
- /**
- * Assert that `node` is a `RGBA` or `HSLA`.
- *
- * @param {Node} node
- * @param {String} param
- * @api public
- */
- exports.assertColor = function(node, param){
- exports.assertPresent(node, param);
- switch (node.nodeName) {
- case 'rgba':
- case 'hsla':
- return;
- default:
- var actual = node.nodeName
- , msg = 'expected rgba or hsla, but got ' + actual + ':' + node;
- throw new Error('TypeError: ' + msg);
- }
- };
- /**
- * Assert that param `name` is given, aka the `node` is passed.
- *
- * @param {Node} node
- * @param {String} name
- * @api public
- */
- exports.assertPresent = function(node, name){
- if (node) return;
- if (name) throw new Error('"' + name + '" argument required');
- throw new Error('argument missing');
- };
- /**
- * Unwrap `expr`.
- *
- * Takes an expressions with length of 1
- * such as `((1 2 3))` and unwraps it to `(1 2 3)`.
- *
- * @param {Expression} expr
- * @return {Node}
- * @api public
- */
- exports.unwrap = function(expr){
- // explicitly preserve the expression
- if (expr.preserve) return expr;
- if ('arguments' != expr.nodeName && 'expression' != expr.nodeName) return expr;
- if (1 != expr.nodes.length) return expr;
- if ('arguments' != expr.nodes[0].nodeName && 'expression' != expr.nodes[0].nodeName) return expr;
- return exports.unwrap(expr.nodes[0]);
- };
- /**
- * Coerce JavaScript values to their Stylus equivalents.
- *
- * @param {Mixed} val
- * @param {Boolean} [raw]
- * @return {Node}
- * @api public
- */
- exports.coerce = function(val, raw){
- switch (typeof val) {
- case 'function':
- return val;
- case 'string':
- return new nodes.String(val);
- case 'boolean':
- return new nodes.Boolean(val);
- case 'number':
- return new nodes.Unit(val);
- default:
- if (null == val) return nodes.null;
- if (Array.isArray(val)) return exports.coerceArray(val, raw);
- if (val.nodeName) return val;
- return exports.coerceObject(val, raw);
- }
- };
- /**
- * Coerce a javascript `Array` to a Stylus `Expression`.
- *
- * @param {Array} val
- * @param {Boolean} [raw]
- * @return {Expression}
- * @api private
- */
- exports.coerceArray = function(val, raw){
- var expr = new nodes.Expression;
- val.forEach(function(val){
- expr.push(exports.coerce(val, raw));
- });
- return expr;
- };
- /**
- * Coerce a javascript object to a Stylus `Expression` or `Object`.
- *
- * For example `{ foo: 'bar', bar: 'baz' }` would become
- * the expression `(foo 'bar') (bar 'baz')`. If `raw` is true
- * given `obj` would become a Stylus hash object.
- *
- * @param {Object} obj
- * @param {Boolean} [raw]
- * @return {Expression|Object}
- * @api public
- */
- exports.coerceObject = function(obj, raw){
- var node = raw ? new nodes.Object : new nodes.Expression
- , val;
- for (var key in obj) {
- val = exports.coerce(obj[key], raw);
- key = new nodes.Ident(key);
- if (raw) {
- node.set(key, val);
- } else {
- node.push(exports.coerceArray([key, val]));
- }
- }
- return node;
- };
- /**
- * Return param names for `fn`.
- *
- * @param {Function} fn
- * @return {Array}
- * @api private
- */
- exports.params = function(fn){
- return fn
- .toString()
- .match(/\(([^)]*)\)/)[1].split(/ *, */);
- };
- /**
- * Merge object `b` with `a`.
- *
- * @param {Object} a
- * @param {Object} b
- * @param {Boolean} [deep]
- * @return {Object} a
- * @api private
- */
- exports.merge = function(a, b, deep) {
- for (var k in b) {
- if (deep && a[k]) {
- var nodeA = exports.unwrap(a[k]).first
- , nodeB = exports.unwrap(b[k]).first;
- if ('object' == nodeA.nodeName && 'object' == nodeB.nodeName) {
- a[k].first.vals = exports.merge(nodeA.vals, nodeB.vals, deep);
- } else {
- a[k] = b[k];
- }
- } else {
- a[k] = b[k];
- }
- }
- return a;
- };
- /**
- * Returns an array with unique values.
- *
- * @param {Array} arr
- * @return {Array}
- * @api private
- */
- exports.uniq = function(arr){
- var obj = {}
- , ret = [];
- for (var i = 0, len = arr.length; i < len; ++i) {
- if (arr[i] in obj) continue;
- obj[arr[i]] = true;
- ret.push(arr[i]);
- }
- return ret;
- };
- /**
- * Compile selector strings in `arr` from the bottom-up
- * to produce the selector combinations. For example
- * the following Stylus:
- *
- * ul
- * li
- * p
- * a
- * color: red
- *
- * Would return:
- *
- * [ 'ul li a', 'ul p a' ]
- *
- * @param {Array} arr
- * @param {Boolean} leaveHidden
- * @return {Array}
- * @api private
- */
- exports.compileSelectors = function(arr, leaveHidden){
- var selectors = []
- , Parser = require('./selector-parser')
- , indent = (this.indent || '')
- , buf = [];
- function parse(selector, buf) {
- var parts = [selector.val]
- , str = new Parser(parts[0], parents, parts).parse().val
- , parents = [];
- if (buf.length) {
- for (var i = 0, len = buf.length; i < len; ++i) {
- parts.push(buf[i]);
- parents.push(str);
- var child = new Parser(buf[i], parents, parts).parse();
- if (child.nested) {
- str += ' ' + child.val;
- } else {
- str = child.val;
- }
- }
- }
- return str.trim();
- }
- function compile(arr, i) {
- if (i) {
- arr[i].forEach(function(selector){
- if (!leaveHidden && selector.isPlaceholder) return;
- if (selector.inherits) {
- buf.unshift(selector.val);
- compile(arr, i - 1);
- buf.shift();
- } else {
- selectors.push(indent + parse(selector, buf));
- }
- });
- } else {
- arr[0].forEach(function(selector){
- if (!leaveHidden && selector.isPlaceholder) return;
- var str = parse(selector, buf);
- if (str) selectors.push(indent + str);
- });
- }
- }
- compile(arr, arr.length - 1);
- // Return the list with unique selectors only
- return exports.uniq(selectors);
- };
- /**
- * Attempt to parse string.
- *
- * @param {String} str
- * @return {Node}
- * @api private
- */
- exports.parseString = function(str){
- var Parser = require('./parser')
- , parser
- , ret;
- try {
- parser = new Parser(str);
- ret = parser.list();
- } catch (e) {
- ret = new nodes.Literal(str);
- }
- return ret;
- };
|