| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847 |
- #!/usr/bin/env node
- /**
- * Module dependencies.
- */
- var fs = require('fs')
- , stylus = require('../lib/stylus')
- , basename = require('path').basename
- , dirname = require('path').dirname
- , extname = require('path').extname
- , resolve = require('path').resolve
- , join = require('path').join
- , isWindows = process.platform === 'win32'
- , semver = require('semver')
- , mkdirSync = semver.satisfies(process.version, '>=10.12.0') ? fs.mkdirSync : require('mkdirp').sync;
- /**
- * 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;
- /**
- * CSS class prefix.
- */
- var prefix = '';
- /**
- * Print to stdout flag.
- */
- var print = false;
- /**
- * Firebug flag
- */
- var firebug = false;
- /**
- * Quiet flag
- */
- var quiet = false;
- /**
- * Sourcemap flag
- */
- var sourcemap = 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;
- /**
- * Include CSS on import.
- */
- var includeCSS = false;
- /**
- * Set file imports.
- */
- var imports = [];
- /**
- * Resolve relative urls
- */
- var resolveURL = false;
- /**
- * Disable cache.
- */
- var disableCache = false;
- /**
- * Display dependencies flag.
- */
- var deps = false;
- /**
- * Hoist at-rules.
- */
- var hoist = false;
- /**
- * Specify custom file extension.
- */
- var ext = '.css';
- /**
- * Usage docs.
- */
- var usage = [
- ''
- , ' Usage: stylus [options] [command] [< in [> out]]'
- , ' [file|dir ...]'
- , ''
- , ' Commands:'
- , ''
- , ' help [<type>:]<prop> Opens help info at MDN 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'
- , ' -m, --sourcemap Generates a sourcemap in sourcemaps v3 format'
- , ' -q, --quiet Less noisy output'
- , ' --sourcemap-inline Inlines sourcemap with full source text in base64 format'
- , ' --sourcemap-root <url> "sourceRoot" property of the generated sourcemap'
- , ' --sourcemap-base <path> Base <path> from which sourcemap and all sources are relative'
- , ' -P, --prefix [prefix] prefix all css classes'
- , ' -p, --print Print out the compiled CSS'
- , ' --import <file> Import stylus <file>'
- , ' --include-css Include regular CSS on @import'
- , ' --ext Specify custom file extension for compiled file, default .css'
- , ' -D, --deps Display dependencies of the compiled file'
- , ' --disable-cache Disable caching'
- , ' --hoist-atrules Move @import and @charset to the top'
- , ' -r, --resolve-url Resolve relative urls inside imports'
- , ' --resolve-url-nocheck Like --resolve-url but without file existence check'
- , ' -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);
- return;
- 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 '-m':
- case '--sourcemap':
- sourcemap = {};
- break;
- case '-q':
- case '--quiet':
- quiet = true;
- break;
- case '--sourcemap-inline':
- sourcemap = sourcemap || {};
- sourcemap.inline = true;
- break;
- case '--sourcemap-root':
- var url = args.shift();
- if (!url) throw new Error('--sourcemap-root <url> required');
- sourcemap = sourcemap || {};
- sourcemap.sourceRoot = url;
- break;
- case '--sourcemap-base':
- var path = args.shift();
- if (!path) throw new Error('--sourcemap-base <path> required');
- sourcemap = sourcemap || {};
- sourcemap.basePath = path;
- break;
- case '-P':
- case '--prefix':
- prefix = args.shift();
- if (!prefix) throw new Error('--prefix <prefix> required');
- break;
- case '-p':
- case '--print':
- print = true;
- break;
- case '-V':
- case '--version':
- console.log(stylus.version);
- return;
- 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 '--include-css':
- includeCSS = true;
- break;
- case '--ext':
- ext = args.shift();
- if (!ext) throw new Error('--ext <ext> required');
- break;
- case '--disable-cache':
- disableCache = true;
- break;
- case '--hoist-atrules':
- hoist = true;
- 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;
- case '--import':
- var file = args.shift();
- if (!file) throw new Error('--import <file> required');
- imports.push(file);
- break;
- case '-r':
- case '--resolve-url':
- resolveURL = {};
- break;
- case '--resolve-url-nocheck':
- resolveURL = { nocheck: true };
- break;
- case '-D':
- case '--deps':
- deps = true;
- 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$/);
- });
- }
- // --sourcemap flag is not working with stdio
- if (sourcemap && !files.length) sourcemap = false;
- /**
- * 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
- if (files.length > 1 && isCSS(dest)) {
- dest = dirname(dest);
- }
- var options = {
- filename: 'stdin'
- , compress: compress
- , firebug: firebug
- , linenos: linenos
- , sourcemap: sourcemap
- , paths: [process.cwd()].concat(paths)
- , prefix: prefix
- , dest: dest
- , 'hoist atrules': hoist
- };
- // Buffer stdin
- var str = '';
- // Convert CSS to Stylus
- if (convertCSS) {
- switch (files.length) {
- case 2:
- compileCSSFile(files[0], files[1]);
- break;
- case 1:
- var file = files[0];
- compileCSSFile(file, join(dirname(file), basename(file, extname(file))) + '.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 (deps) {
- // if --deps is used, just display list of the dependencies
- // not working with stdio and dirs
- displayDeps();
- } else {
- if (files.length) {
- compileFiles(files);
- } else {
- compileStdio();
- }
- }
- /**
- * Start Stylus REPL.
- */
- function repl() {
- var options = { cache: false, 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, autocomplete)
- , global = evaluator.global.scope;
- // expose BIFs
- evaluator.evaluate();
- // readline
- repl.setPrompt('> ');
- repl.prompt();
- // HACK: flat-list auto-complete
- function autocomplete(line){
- var out = process.stdout
- , keys = Object.keys(global.locals)
- , len = keys.length
- , words = line.split(/\s+/)
- , word = words.pop()
- , names = []
- , name
- , node
- , key;
- // find words that match
- for (var i = 0; i < len; ++i) {
- key = keys[i];
- if (0 == key.indexOf(word)) {
- node = global.lookup(key);
- switch (node.nodeName) {
- case 'function':
- names.push(node.toString());
- break;
- default:
- names.push(key);
- }
- }
- }
- return [names, line];
- };
- 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();
- evaluator.root = expr;
- var ret = evaluator.evaluate();
- var node;
- while (node = ret.nodes.pop()) {
- if (!node.suppress) {
- var str = node.toString();
- if ('(' == str[0]) str = str.replace(/^\(|\)$/g, '');
- console.log('\033[90m=> \033[0m' + highlight(str));
- break;
- }
- }
- 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.stat(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);
- if (includeCSS) style.set('include css', true);
- if (disableCache) style.set('cache', false);
- usePlugins(style);
- importFiles(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);
- }
- /**
- * Display dependencies of the compiled files.
- */
- function displayDeps() {
- files.forEach(function(file){
- // ensure file exists
- fs.stat(file, function(err, stat){
- if (err) throw err;
- fs.readFile(file, 'utf8', function(err, str){
- if (err) throw err;
- options.filename = file;
- var style = stylus(str, options);
- usePlugins(style);
- importFiles(style);
- console.log(style.deps().join('\n'));
- });
- });
- });
- }
- /**
- * Compile the given file.
- */
- function compileFile(file) {
- // ensure file exists
- fs.stat(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);
- if (includeCSS) style.set('include css', true);
- if (disableCache) style.set('cache', false);
- if (sourcemap) style.set('sourcemap', sourcemap);
- usePlugins(style);
- importFiles(style);
- style.render(function(err, css){
- watchImports(file, options._imports);
- if (err) {
- if (watchers) {
- console.error(err.stack || err.message);
- } else {
- throw err;
- }
- } else {
- writeFile(file, css);
- // write sourcemap
- if (sourcemap && !sourcemap.inline) {
- writeSourcemap(file, style.sourcemap);
- }
- }
- });
- });
- // 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 createPath(file, sourceMap) {
- var out;
- if (files.length === 1 && isCSS(dest)) {
- return [dest, sourceMap ? '.map' : ''].join('');
- }
- // --out support
- out = [basename(file, extname(file)), sourceMap ? ext + '.map' : ext].join('');
- return dest
- ? join(dest, out)
- : join(dirname(file), out);
- }
- /**
- * Check if the given path is a CSS file.
- */
- function isCSS(file) {
- return file && '.css' === extname(file);
- }
- function writeFile(file, css) {
- // --print support
- if (print) return process.stdout.write(css);
- var path = createPath(file);
- mkdirSync(dirname(path), { recursive: true })
- fs.writeFile(path, css, function(err){
- if (err) throw err;
- console.log(' \033[90mcompiled\033[0m %s', path);
- // --watch support
- watch(file, file);
- });
- }
- /**
- * Write the given sourcemap.
- */
- function writeSourcemap(file, sourcemap) {
- var path = createPath(file, true);
- mkdirSync(dirname(path), { recursive: true })
- fs.writeFile(path, JSON.stringify(sourcemap), function(err){
- if (err) throw err;
- // don't output log message if --print is present
- if (!print) console.log(' \033[90mgenerated\033[0m %s', path);
- });
- }
- /**
- * Watch the given `file` and recompiling `rootFile` when modified.
- */
- function watch(file, rootFile) {
- // not watching
- if (!watchers) return;
- // already watched
- if (watchers[file]) {
- watchers[file][rootFile] = true;
- return;
- }
- // watch the file itself
- watchers[file] = {};
- watchers[file][rootFile] = true;
- if (print) {
- console.error('Stylus CLI Error: Watch and print cannot be used together');
- process.exit(1);
- }
- if(!quiet){
- console.log(' \033[90mwatching\033[0m %s', file);
- }
- // if is windows use fs.watch api instead
- // TODO: remove watchFile when fs.watch() works on osx etc
- if (isWindows) {
- fs.watch(file, compile);
- } else {
- fs.watchFile(file, { interval: 300 }, function(curr, prev) {
- if (curr.mtime > prev.mtime) compile();
- });
- }
- function compile() {
- for (var rootFile in watchers[file]) {
- compileFile(rootFile);
- }
- }
- }
- /**
- * Watch `imports`, re-compiling `file` when they change.
- */
- function watchImports(file, imports) {
- imports.forEach(function(imported){
- if (!imported.path) return;
- watch(imported.path, file);
- });
- }
- /**
- * Utilize plugins.
- */
- function usePlugins(style) {
- plugins.forEach(function(plugin){
- var path = plugin.path;
- var options = plugin.options;
- fn = require(/^\.+\//.test(path) ? resolve(path) : 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));
- } else if (resolveURL) {
- style.define('url', stylus.resolver(resolveURL));
- }
- }
- /**
- * Imports the indicated files.
- */
- function importFiles(style) {
- imports.forEach(function(file) {
- style.import(file);
- });
- }
|