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 502 lines (437 sloc) 10.724 kb
#!/usr/bin/env node
/**
* Module dependencies.
*/
var fs = require('fs')
, stylus = require('../')
, basename = require('path').basename
, dirname = require('path').dirname
, join = require('path').join
, growl = require('growl');
/**
* Arguments.
*/
var args = process.argv.slice(2);
/**
* Compare flag.
*/
var compare = false;
/**
* Compress flag.
*/
var compress = false;
/**
* CSS conversion flag.
*/
var convertCSS = 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 = [];
/**
* Usage docs.
*/
var usage = [
''
, ' Usage: stylus [options] [command] [< in [> out]]'
, ' [file|dir ...]'
, ''
, ' Commands:'
, ''
, ' help <prop> Opens help info for <prop> in'
, ' your default browser. (osx only)'
, ''
, ' Options:'
, ''
, ' -i, --interactive Start interactive REPL'
, ' -u, --use <path> Utilize the stylus plugin at <path>'
, ' -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'
, ' -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.log(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 '-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();
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 '--use':
var path = args.shift();
if (!path) throw new Error('--use <path> required');
paths.push(dirname(path));
plugins.push(path);
break;
default:
files.push(arg);
}
}
// 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 = 'https://developer.mozilla.org/en/CSS/' + name
, exec = require('child_process').exec;
exec('open "' + url + '"', function(){
process.exit(0);
});
}
// Compilation options
var options = {
filename: 'stdin'
, compress: compress
, 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: [__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.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 file + '/' + path;
}).forEach(compileFile);
});
}
});
}
/**
* Write the given css output.
*/
function writeFile(file, css) {
// --out support
var path = dest
? 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(import){
if (!import.path) return;
watch(import.path, function(){
compileFile(file);
});
});
}
/**
* Utilize plugins.
*/
function usePlugins(style) {
var cwd = process.cwd();
plugins.forEach(function(path){
path = join(cwd, path);
fn = require(path);
if ('function' != typeof fn) {
throw new Error('plugin ' + path + ' does not export a function');
}
style.use(fn());
});
}
Jump to Line
Something went wrong with that request. Please try again.