Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

executable file 607 lines (526 sloc) 13.341 kb
#!/usr/bin/env node
/**
* Module dependencies.
*/
var fs = require('fs')
, stylus = require('../lib/stylus')
, basename = require('path').basename
, dirname = require('path').dirname
, join = require('path').join;
/**
* Arguments.
*/
var args = process.argv.slice(2);
/**
* Compare flag.
*/
var compare = false;
/**
* Compress flag.
*/
var compress = false;
/**
* CSS conversion flag.
*/
var convertCSS = false;
/**
* Line numbers flag.
*/
var linenos = false;
/**
* Firebug flag
*/
var firebug = false;
/**
* Files to processes.
*/
var files = [];
/**
* Import paths.
*/
var paths = [];
/**
* Destination directory.
*/
var dest;
/**
* Watcher hash.
*/
var watchers;
/**
* Enable REPL.
*/
var interactive;
/**
* Plugins.
*/
var plugins = [];
/**
* Optional url() function.
*/
var urlFunction = false;
/**
* Usage docs.
*/
var usage = [
''
, ' Usage: stylus [options] [command] [< in [> out]]'
, ' [file|dir ...]'
, ''
, ' Commands:'
, ''
, ' help [<type>:]<prop> Opens help info at MDC for <prop> in'
, ' your default browser. Optionally'
, ' searches other resources of <type>:'
, ' safari opera w3c ms caniuse quirksmode'
, ''
, ' Options:'
, ''
, ' -i, --interactive Start interactive REPL'
, ' -u, --use <path> Utilize the stylus plugin at <path>'
, ' -U, --inline Utilize image inlining via data uri support'
, ' -w, --watch Watch file(s) for changes and re-compile'
, ' -o, --out <dir> Output to <dir> when passing files'
, ' -C, --css <src> [dest] Convert css input to stylus'
, ' -I, --include <path> Add <path> to lookup paths'
, ' -c, --compress Compress css output'
, ' -d, --compare Display input along with output'
, ' -f, --firebug Emits debug infos in the generated css that'
, ' can be used by the FireStylus Firebug plugin'
, ' -l, --line-numbers Emits comments in the generated css'
, ' indicating the corresponding stylus line'
, ' -V, --version Display the version of stylus'
, ' -h, --help Display help information'
, ''
].join('\n');
/**
* Handle arguments.
*/
var arg;
while (args.length) {
arg = args.shift();
switch (arg) {
case '-h':
case '--help':
console.error(usage);
process.exit(1);
case '-d':
case '--compare':
compare = true;
break;
case '-c':
case '--compress':
compress = true;
break;
case '-C':
case '--css':
convertCSS = true;
break;
case '-f':
case '--firebug':
firebug = true;
break;
case '-l':
case '--line-numbers':
linenos = true;
break;
case '-V':
case '--version':
console.log(stylus.version);
process.exit(0);
break;
case '-o':
case '--out':
dest = args.shift();
if (!dest) throw new Error('--out <dir> required');
break;
case 'help':
var name = args.shift()
, browser = name.split(':');
if (browser.length > 1) {
name = [].slice.call(browser, 1).join(':');
browser = browser[0];
} else {
name = browser[0];
browser = '';
}
if (!name) throw new Error('help <property> required');
help(name);
break;
case '-i':
case '--repl':
case '--interactive':
interactive = true;
break;
case '-I':
case '--include':
var path = args.shift();
if (!path) throw new Error('--include <path> required');
paths.push(path);
break;
case '-w':
case '--watch':
watchers = {};
break;
case '-U':
case '--inline':
args.unshift('--use', 'url');
break;
case '-u':
case '--use':
var options;
var path = args.shift();
if (!path) throw new Error('--use <path> required');
// options
if ('--with' == args[0]) {
args.shift();
options = args.shift();
if (!options) throw new Error('--with <options> required');
options = eval('(' + options + ')');
}
// url support
if ('url' == path) {
urlFunction = options || {};
} else {
paths.push(dirname(path));
plugins.push({ path: path, options: options });
}
break;
default:
files.push(arg);
}
}
// optional growl support
try {
var growl = require('growl');
} catch (err) {
// ignore
}
// if --watch is used, assume we are
// not working with stdio
if (watchers && !files.length) {
files = fs.readdirSync(process.cwd())
.filter(function(file){
return file.match(/\.styl$/);
});
}
/**
* Open the default browser to the CSS property `name`.
*
* @param {String} name
*/
function help(name) {
var url
, exec = require('child_process').exec
, command;
name = encodeURIComponent(name);
switch (browser) {
case 'safari':
case 'webkit':
url = 'https://developer.apple.com/library/safari/search/?q=' + name;
break;
case 'opera':
url = 'http://dev.opera.com/search/?term=' + name;
break;
case 'w3c':
url = 'http://www.google.com/search?q=site%3Awww.w3.org%2FTR+' + name;
break;
case 'ms':
url = 'http://social.msdn.microsoft.com/search/en-US/ie?query=' + name + '&refinement=59%2c61';
break;
case 'caniuse':
url = 'http://caniuse.com/#search=' + name;
break;
case 'quirksmode':
url = 'http://www.google.com/search?q=site%3Awww.quirksmode.org+' + name;
break;
default:
url = 'https://developer.mozilla.org/en/CSS/' + name;
}
switch (process.platform) {
case 'linux': command = 'x-www-browser'; break;
default: command = 'open';
}
exec(command + ' "' + url + '"', function(){
process.exit(0);
});
}
// Compilation options
var options = {
filename: 'stdin'
, compress: compress
, firebug: firebug
, linenos: linenos
, paths: [process.cwd()].concat(paths)
};
// Buffer stdin
var str = '';
// Convert css to stylus
if (convertCSS) {
switch (files.length) {
case 2:
compileCSSFile(files[0], files[1]);
break;
case 1:
compileCSSFile(files[0], files[0].replace('.css', '.styl'));
break;
default:
var stdin = process.openStdin();
stdin.setEncoding('utf8');
stdin.on('data', function(chunk){ str += chunk; });
stdin.on('end', function(){
var out = stylus.convertCSS(str);
console.log(out);
});
}
} else if (interactive) {
repl();
} else {
if (files.length) {
compileFiles(files);
} else {
compileStdio();
}
}
/**
* Start stylus REPL.
*/
function repl() {
var options = { filename: 'stdin', imports: [join(__dirname, '..', 'lib', 'functions')] }
, parser = new stylus.Parser('', options)
, evaluator = new stylus.Evaluator(parser.parse(), options)
, rl = require('readline')
, repl = rl.createInterface(process.stdin, process.stdout, true)
, global = evaluator.global.scope;
// expose BIFs
evaluator.evaluate();
// readline
repl.setPrompt('> ');
repl.prompt();
// HACK: flat-list auto-complete
repl._tabComplete = function(){
var out = this.output
, line = this.line
, keys = Object.keys(global.locals)
, len = keys.length
, words = line.split(/\s+/)
, word = words.pop()
, names = []
, name
, obj
, key;
// find words that match
for (var i = 0; i < len; ++i) {
key = keys[i];
if (0 == key.indexOf(word)) {
names.push(key);
}
}
// several candidates
if (names.length > 1) {
out.write('\r\n\r\n\033[90m');
names.forEach(function(name){
var node = global.lookup(name);
switch (node.nodeName) {
case 'function':
out.write(' - ' + node + '\r\n');
break;
default:
out.write(' - ' + name + '\r\n');
}
});
out.write('\r\n\033[0m');
this._refreshLine();
// single candidate
} else if (names.length) {
name = names.pop();
obj = global.lookup(name);
name = name.replace(word, '');
switch (obj.nodeName) {
case 'function':
this._insertString(name + '()');
this.cursor--;
break;
default:
this._insertString(name);
}
this._refreshLine();
}
};
repl.on('line', function(line){
if (!line.trim().length) return repl.prompt();
parser = new stylus.Parser(line, options);
parser.state.push('expression');
evaluator.return = true;
try {
var expr = parser.parse();
var ret = evaluator.visit(expr);
ret = ret.nodes[ret.nodes.length - 1];
ret = ret.toString();
if ('(' == ret[0]) ret = ret.replace(/^\(|\)$/g, '');
console.log('\033[90m=> \033[0m' + highlight(ret));
repl.prompt();
} catch (err) {
console.error('\033[31merror: %s\033[0m', err.message || err.stack);
repl.prompt();
}
});
repl.on('SIGINT', function(){
console.log();
process.exit(0);
});
}
/**
* Highlight the given string of stylus.
*/
function highlight(str) {
return str
.replace(/(#)?(\d+(\.\d+)?)/g, function($0, $1, $2){
return $1 ? $0 : '\033[36m' + $2 + '\033[0m';
})
.replace(/(#[\da-fA-F]+)/g, '\033[33m$1\033[0m')
.replace(/('.*?'|".*?")/g, '\033[32m$1\033[0m');
}
/**
* Convert a CSS file to a Styl file
*/
function compileCSSFile(file, fileOut) {
fs.lstat(file, function(err, stat){
if (err) throw err;
if (stat.isFile()) {
fs.readFile(file, 'utf8', function(err, str){
if (err) throw err;
var styl = stylus.convertCSS(str);
fs.writeFile(fileOut, styl, function(err){
if (err) throw err;
});
});
}
});
}
/**
* Compile with stdio.
*/
function compileStdio() {
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk){ str += chunk; });
process.stdin.on('end', function(){
// Compile to css
var style = stylus(str, options);
usePlugins(style);
style.render(function(err, css){
if (err) throw err;
if (compare) {
console.log('\n\x1b[1mInput:\x1b[0m');
console.log(str);
console.log('\n\x1b[1mOutput:\x1b[0m');
}
console.log(css);
if (compare) console.log();
});
}).resume();
}
/**
* Compile the given files.
*/
function compileFiles(files) {
files.forEach(compileFile);
}
/**
* Compile the given file.
*/
function compileFile(file) {
// ensure file exists
fs.lstat(file, function(err, stat){
if (err) throw err;
// file
if (stat.isFile()) {
fs.readFile(file, 'utf8', function(err, str){
if (err) throw err;
options.filename = file;
options._imports = [];
var style = stylus(str, options);
usePlugins(style);
style.render(function(err, css){
watchImports(file, options._imports);
if (err) {
if (watchers) {
console.error(err.stack || err.message);
growl && growl.notify(err.message, { title: 'Stylus error' });
} else {
throw err;
}
} else {
writeFile(file, css);
}
});
});
// directory
} else if (stat.isDirectory()) {
fs.readdir(file, function(err, files){
if (err) throw err;
files.filter(function(path){
return path.match(/\.styl$/);
}).map(function(path){
return join(file, path);
}).forEach(compileFile);
});
}
});
}
/**
* Write the given css output.
*/
function writeFile(file, css) {
// --out support
var path = dest
? join(dest, basename(file, '.styl') + '.css')
: file.replace('.styl', '.css');
fs.writeFile(path, css, function(err){
if (err) throw err;
console.log(' \033[90mcompiled\033[0m %s', path);
// --watch support
watch(file, compileFile);
});
}
/**
* Watch the given `file` and invoke `fn` when modified.
*/
function watch(file, fn) {
// not watching
if (!watchers) return;
// already watched
if (watchers[file]) return;
// watch the file itself
watchers[file] = true;
console.log(' \033[90mwatching\033[0m %s', file);
fs.watchFile(file, { interval: 50 }, function(curr, prev){
if (curr.mtime > prev.mtime) fn(file);
});
}
/**
* Watch `imports`, re-compiling `file` when they change.
*/
function watchImports(file, imports) {
imports.forEach(function(imported){
if (!imported.path) return;
watch(imported.path, function(){
compileFile(file);
});
});
}
/**
* Utilize plugins.
*/
function usePlugins(style) {
plugins.forEach(function(plugin){
var path = plugin.path;
var options = plugin.options;
fn = require(path);
if ('function' != typeof fn) {
throw new Error('plugin ' + path + ' does not export a function');
}
style.use(fn(options));
});
if (urlFunction) style.define('url', stylus.url(urlFunction));
}
Jump to Line
Something went wrong with that request. Please try again.