123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- #!/usr/bin/env node
- /*
- The MIT License (MIT)
- Copyright (c) 2007-2018 Einar Lielmanis, Liam Newman, and 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.
- Js-Beautify Command-line for node.js
- -------------------------------------
- Written by Daniel Stockman (daniel.stockman@gmail.com)
- */
- /*jshint strict:false */
- var debug = process.env.DEBUG_JSBEAUTIFY || process.env.JSBEAUTIFY_DEBUG ? function() {
- console.error.apply(console, arguments);
- } : function() {};
- var fs = require('fs'),
- cc = require('config-chain'),
- beautify = require('../index'),
- nopt = require('nopt'),
- glob = require('glob');
- nopt.invalidHandler = function(key, val) {
- throw new Error(key + " was invalid with value \"" + val + "\"");
- };
- nopt.typeDefs.brace_style = {
- type: "brace_style",
- validate: function(data, key, val) {
- data[key] = val;
- // TODO: expand-strict is obsolete, now identical to expand. Remove in future version
- // TODO: collapse-preserve-inline is obselete, now identical to collapse,preserve-inline = true. Remove in future version
- var validVals = ["collapse", "collapse-preserve-inline", "expand", "end-expand", "expand-strict", "none", "preserve-inline"];
- var valSplit = val.split(/[^a-zA-Z0-9_\-]+/); //Split will always return at least one parameter
- for (var i = 0; i < valSplit.length; i++) {
- if (validVals.indexOf(valSplit[i]) === -1) {
- return false;
- }
- }
- return true;
- }
- };
- var path = require('path'),
- editorconfig = require('editorconfig'),
- knownOpts = {
- // Beautifier
- "indent_size": Number,
- "indent_char": String,
- "eol": String,
- "indent_level": Number,
- "indent_with_tabs": Boolean,
- "preserve_newlines": Boolean,
- "max_preserve_newlines": Number,
- "space_in_paren": Boolean,
- "space_in_empty_paren": Boolean,
- "jslint_happy": Boolean,
- "space_after_anon_function": Boolean,
- "space_after_named_function": Boolean,
- "brace_style": "brace_style", //See above for validation
- "unindent_chained_methods": Boolean,
- "break_chained_methods": Boolean,
- "keep_array_indentation": Boolean,
- "unescape_strings": Boolean,
- "wrap_line_length": Number,
- "wrap_attributes": ["auto", "force", "force-aligned", "force-expand-multiline", "aligned-multiple", "preserve", "preserve-aligned"],
- "wrap_attributes_indent_size": Number,
- "e4x": Boolean,
- "end_with_newline": Boolean,
- "comma_first": Boolean,
- "operator_position": ["before-newline", "after-newline", "preserve-newline"],
- "indent_empty_lines": Boolean,
- "templating": [String, Array],
- // CSS-only
- "selector_separator_newline": Boolean,
- "newline_between_rules": Boolean,
- "space_around_combinator": Boolean,
- //deprecated - replaced with space_around_combinator, remove in future version
- "space_around_selector_separator": Boolean,
- // HTML-only
- "max_char": Number, // obsolete since 1.3.5
- "inline": [String, Array],
- "unformatted": [String, Array],
- "content_unformatted": [String, Array],
- "indent_inner_html": [Boolean],
- "indent_handlebars": [Boolean],
- "indent_scripts": ["keep", "separate", "normal"],
- "extra_liners": [String, Array],
- "unformatted_content_delimiter": String,
- // CLI
- "version": Boolean,
- "help": Boolean,
- "files": [path, Array],
- "outfile": path,
- "replace": Boolean,
- "quiet": Boolean,
- "type": ["js", "css", "html"],
- "config": path,
- "editorconfig": Boolean
- },
- // dasherizeShorthands provides { "indent-size": ["--indent_size"] }
- // translation, allowing more convenient dashes in CLI arguments
- shortHands = dasherizeShorthands({
- // Beautifier
- "s": ["--indent_size"],
- "c": ["--indent_char"],
- "e": ["--eol"],
- "l": ["--indent_level"],
- "t": ["--indent_with_tabs"],
- "p": ["--preserve_newlines"],
- "m": ["--max_preserve_newlines"],
- "P": ["--space_in_paren"],
- "Q": ["--space_in_empty_paren"],
- "j": ["--jslint_happy"],
- "a": ["--space_after_anon_function"],
- "b": ["--brace_style"],
- "u": ["--unindent_chained_methods"],
- "B": ["--break_chained_methods"],
- "k": ["--keep_array_indentation"],
- "x": ["--unescape_strings"],
- "w": ["--wrap_line_length"],
- "X": ["--e4x"],
- "n": ["--end_with_newline"],
- "C": ["--comma_first"],
- "O": ["--operator_position"],
- // CSS-only
- "L": ["--selector_separator_newline"],
- "N": ["--newline_between_rules"],
- // HTML-only
- "A": ["--wrap_attributes"],
- "i": ["--wrap_attributes_indent_size"],
- "W": ["--max_char"], // obsolete since 1.3.5
- "d": ["--inline"],
- "U": ["--unformatted"],
- "T": ["--content_unformatted"],
- "I": ["--indent_inner_html"],
- "H": ["--indent_handlebars"],
- "S": ["--indent_scripts"],
- "E": ["--extra_liners"],
- // non-dasherized hybrid shortcuts
- "good-stuff": [
- "--keep_array_indentation",
- "--keep_function_indentation",
- "--jslint_happy"
- ],
- "js": ["--type", "js"],
- "css": ["--type", "css"],
- "html": ["--type", "html"],
- // CLI
- "v": ["--version"],
- "h": ["--help"],
- "f": ["--files"],
- "file": ["--files"],
- "o": ["--outfile"],
- "r": ["--replace"],
- "q": ["--quiet"]
- // no shorthand for "config"
- // no shorthand for "editorconfig"
- // no shorthand for "indent_empty_lines"
- // not shorthad for "templating"
- });
- function verifyExists(fullPath) {
- return fs.existsSync(fullPath) ? fullPath : null;
- }
- function findRecursive(dir, fileName) {
- var fullPath = path.join(dir, fileName);
- var nextDir = path.dirname(dir);
- var result = verifyExists(fullPath);
- if (!result && (nextDir !== dir)) {
- result = findRecursive(nextDir, fileName);
- }
- return result;
- }
- function getUserHome() {
- var user_home = '';
- try {
- user_home = process.env.USERPROFILE || process.env.HOME || '';
- } catch (ex) {}
- return user_home;
- }
- function set_file_editorconfig_opts(file, config) {
- try {
- var eConfigs = editorconfig.parseSync(file);
- if (eConfigs.indent_style === "tab") {
- config.indent_with_tabs = true;
- } else if (eConfigs.indent_style === "space") {
- config.indent_with_tabs = false;
- }
- if (eConfigs.indent_size) {
- config.indent_size = eConfigs.indent_size;
- }
- if (eConfigs.max_line_length) {
- if (eConfigs.max_line_length === "off") {
- config.wrap_line_length = 0;
- } else {
- config.wrap_line_length = parseInt(eConfigs.max_line_length, 10);
- }
- }
- if (eConfigs.insert_final_newline === true) {
- config.end_with_newline = true;
- } else if (eConfigs.insert_final_newline === false) {
- config.end_with_newline = false;
- }
- if (eConfigs.end_of_line) {
- if (eConfigs.end_of_line === 'cr') {
- config.eol = '\r';
- } else if (eConfigs.end_of_line === 'lf') {
- config.eol = '\n';
- } else if (eConfigs.end_of_line === 'crlf') {
- config.eol = '\r\n';
- }
- }
- } catch (e) {
- debug(e);
- }
- }
- // var cli = require('js-beautify/cli'); cli.interpret();
- var interpret = exports.interpret = function(argv, slice) {
- var parsed;
- try {
- parsed = nopt(knownOpts, shortHands, argv, slice);
- } catch (ex) {
- usage(ex);
- // console.error(ex);
- // console.error('Run `' + getScriptName() + ' -h` for help.');
- process.exit(1);
- }
- if (parsed.version) {
- console.log(require('../../package.json').version);
- process.exit(0);
- } else if (parsed.help) {
- usage();
- process.exit(0);
- }
- var cfg;
- var configRecursive = findRecursive(process.cwd(), '.jsbeautifyrc');
- var configHome = verifyExists(path.join(getUserHome() || "", ".jsbeautifyrc"));
- var configDefault = __dirname + '/../config/defaults.json';
- try {
- cfg = cc(
- parsed,
- cleanOptions(cc.env('jsbeautify_'), knownOpts),
- parsed.config,
- configRecursive,
- configHome,
- configDefault
- ).snapshot;
- } catch (ex) {
- debug(cfg);
- // usage(ex);
- console.error(ex);
- console.error('Error while loading beautifier configuration.');
- console.error('Configuration file chain included:');
- if (parsed.config) {
- console.error(parsed.config);
- }
- if (configRecursive) {
- console.error(configRecursive);
- }
- if (configHome) {
- console.error(configHome);
- }
- console.error(configDefault);
- console.error('Run `' + getScriptName() + ' -h` for help.');
- process.exit(1);
- }
- try {
- // Verify arguments
- checkType(cfg);
- checkFiles(cfg);
- debug(cfg);
- // Process files synchronously to avoid EMFILE error
- cfg.files.forEach(processInputSync, {
- cfg: cfg
- });
- } catch (ex) {
- debug(cfg);
- // usage(ex);
- console.error(ex);
- console.error('Run `' + getScriptName() + ' -h` for help.');
- process.exit(1);
- }
- };
- // interpret args immediately when called as executable
- if (require.main === module) {
- interpret();
- }
- function usage(err) {
- var scriptName = getScriptName();
- var msg = [
- scriptName + '@' + require('../../package.json').version,
- '',
- 'CLI Options:',
- ' -f, --file Input file(s) (Pass \'-\' for stdin)',
- ' -r, --replace Write output in-place, replacing input',
- ' -o, --outfile Write output to file (default stdout)',
- ' --config Path to config file',
- ' --type [js|css|html] ["js"]',
- ' -q, --quiet Suppress logging to stdout',
- ' -h, --help Show this help',
- ' -v, --version Show the version',
- '',
- 'Beautifier Options:',
- ' -s, --indent-size Indentation size [4]',
- ' -c, --indent-char Indentation character [" "]',
- ' -t, --indent-with-tabs Indent with tabs, overrides -s and -c',
- ' -e, --eol Character(s) to use as line terminators.',
- ' [first newline in file, otherwise "\\n]',
- ' -n, --end-with-newline End output with newline',
- ' --indent-empty-lines Keep indentation on empty lines',
- ' --templating List of templating languages (auto,none,django,erb,handlebars,php,smarty) ["auto"] auto = none in JavaScript, all in html',
- ' --editorconfig Use EditorConfig to set up the options'
- ];
- switch (scriptName.split('-').shift()) {
- case "js":
- msg.push(' -l, --indent-level Initial indentation level [0]');
- msg.push(' -p, --preserve-newlines Preserve line-breaks (--no-preserve-newlines disables)');
- msg.push(' -m, --max-preserve-newlines Number of line-breaks to be preserved in one chunk [10]');
- msg.push(' -P, --space-in-paren Add padding spaces within paren, ie. f( a, b )');
- msg.push(' -E, --space-in-empty-paren Add a single space inside empty paren, ie. f( )');
- msg.push(' -j, --jslint-happy Enable jslint-stricter mode');
- msg.push(' -a, --space-after-anon-function Add a space before an anonymous function\'s parens, ie. function ()');
- msg.push(' --space_after_named_function Add a space before a named function\'s parens, ie. function example ()');
- msg.push(' -b, --brace-style [collapse|expand|end-expand|none][,preserve-inline] [collapse,preserve-inline]');
- msg.push(' -u, --unindent-chained-methods Don\'t indent chained method calls');
- msg.push(' -B, --break-chained-methods Break chained method calls across subsequent lines');
- msg.push(' -k, --keep-array-indentation Preserve array indentation');
- msg.push(' -x, --unescape-strings Decode printable characters encoded in xNN notation');
- msg.push(' -w, --wrap-line-length Wrap lines that exceed N characters [0]');
- msg.push(' -X, --e4x Pass E4X xml literals through untouched');
- msg.push(' --good-stuff Warm the cockles of Crockford\'s heart');
- msg.push(' -C, --comma-first Put commas at the beginning of new line instead of end');
- msg.push(' -O, --operator-position Set operator position (before-newline|after-newline|preserve-newline) [before-newline]');
- break;
- case "html":
- msg.push(' -b, --brace-style [collapse|expand|end-expand] ["collapse"]');
- msg.push(' -I, --indent-inner-html Indent body and head sections. Default is false.');
- msg.push(' -H, --indent-handlebars Indent handlebars. Default is false.');
- msg.push(' -S, --indent-scripts [keep|separate|normal] ["normal"]');
- msg.push(' -w, --wrap-line-length Wrap lines that exceed N characters [0]');
- msg.push(' -A, --wrap-attributes Wrap html tag attributes to new lines [auto|force|force-aligned|force-expand-multiline|aligned-multiple|preserve|preserve-aligned] ["auto"]');
- msg.push(' -i, --wrap-attributes-indent-size Indent wrapped tags to after N characters [indent-level]');
- msg.push(' -p, --preserve-newlines Preserve line-breaks (--no-preserve-newlines disables)');
- msg.push(' -m, --max-preserve-newlines Number of line-breaks to be preserved in one chunk [10]');
- msg.push(' -U, --unformatted List of tags (defaults to inline) that should not be reformatted');
- msg.push(' -T, --content_unformatted List of tags (defaults to pre) whose content should not be reformatted');
- msg.push(' -E, --extra_liners List of tags (defaults to [head,body,/html] that should have an extra newline');
- msg.push(' --unformatted_content_delimiter Keep text content together between this string [""]');
- break;
- case "css":
- msg.push(' -b, --brace-style [collapse|expand] ["collapse"]');
- msg.push(' -L, --selector-separator-newline Add a newline between multiple selectors.');
- msg.push(' -N, --newline-between-rules Add a newline between CSS rules.');
- }
- if (err) {
- msg.push(err);
- msg.push('');
- console.error(msg.join('\n'));
- } else {
- console.log(msg.join('\n'));
- }
- }
- // main iterator, {cfg} passed as thisArg of forEach call
- function processInputSync(filepath) {
- var data = null,
- config = this.cfg,
- outfile = config.outfile,
- input;
- // -o passed with no value overwrites
- if (outfile === true || config.replace) {
- outfile = filepath;
- }
- var fileType = getOutputType(outfile, filepath, config.type);
- if (config.editorconfig) {
- var editorconfig_filepath = filepath;
- if (editorconfig_filepath === '-') {
- if (outfile) {
- editorconfig_filepath = outfile;
- } else {
- editorconfig_filepath = 'stdin.' + fileType;
- }
- }
- debug("EditorConfig is enabled for ", editorconfig_filepath);
- config = cc(config).snapshot;
- set_file_editorconfig_opts(editorconfig_filepath, config);
- debug(config);
- }
- if (filepath === '-') {
- input = process.stdin;
- input.setEncoding('utf8');
- input.on('error', function() {
- throw 'Must pipe input or define at least one file.';
- });
- input.on('data', function(chunk) {
- data = data || '';
- data += chunk;
- });
- input.on('end', function() {
- if (data === null) {
- throw 'Must pipe input or define at least one file.';
- }
- makePretty(fileType, data, config, outfile, writePretty); // Where things get beautified
- });
- input.resume();
- } else {
- data = fs.readFileSync(filepath, 'utf8');
- makePretty(fileType, data, config, outfile, writePretty);
- }
- }
- function makePretty(fileType, code, config, outfile, callback) {
- try {
- var pretty = beautify[fileType](code, config);
- callback(null, pretty, outfile, config);
- } catch (ex) {
- callback(ex);
- }
- }
- function writePretty(err, pretty, outfile, config) {
- debug('writing ' + outfile);
- if (err) {
- console.error(err);
- process.exit(1);
- }
- if (outfile) {
- fs.mkdirSync(path.dirname(outfile), { recursive: true });
- if (isFileDifferent(outfile, pretty)) {
- try {
- fs.writeFileSync(outfile, pretty, 'utf8');
- logToStdout('beautified ' + path.relative(process.cwd(), outfile), config);
- } catch (ex) {
- onOutputError(ex);
- }
- } else {
- logToStdout('beautified ' + path.relative(process.cwd(), outfile) + ' - unchanged', config);
- }
- } else {
- process.stdout.write(pretty);
- }
- }
- function isFileDifferent(filePath, expected) {
- try {
- return fs.readFileSync(filePath, 'utf8') !== expected;
- } catch (ex) {
- // failing to read is the same as different
- return true;
- }
- }
- // workaround the fact that nopt.clean doesn't return the object passed in :P
- function cleanOptions(data, types) {
- nopt.clean(data, types);
- return data;
- }
- // error handler for output stream that swallows errors silently,
- // allowing the loop to continue over unwritable files.
- function onOutputError(err) {
- if (err.code === 'EACCES') {
- console.error(err.path + " is not writable. Skipping!");
- } else {
- console.error(err);
- process.exit(0);
- }
- }
- // turn "--foo_bar" into "foo-bar"
- function dasherizeFlag(str) {
- return str.replace(/^\-+/, '').replace(/_/g, '-');
- }
- // translate weird python underscored keys into dashed argv,
- // avoiding single character aliases.
- function dasherizeShorthands(hash) {
- // operate in-place
- Object.keys(hash).forEach(function(key) {
- // each key value is an array
- var val = hash[key][0];
- // only dasherize one-character shorthands
- if (key.length === 1 && val.indexOf('_') > -1) {
- hash[dasherizeFlag(val)] = val;
- }
- });
- return hash;
- }
- function getOutputType(outfile, filepath, configType) {
- if (outfile && /\.(js|css|html)$/.test(outfile)) {
- return outfile.split('.').pop();
- } else if (filepath !== '-' && /\.(js|css|html)$/.test(filepath)) {
- return filepath.split('.').pop();
- } else if (configType) {
- return configType;
- } else {
- throw 'Could not determine appropriate beautifier from file paths: ' + filepath;
- }
- }
- function getScriptName() {
- return path.basename(process.argv[1]);
- }
- function checkType(parsed) {
- var scriptType = getScriptName().split('-').shift();
- if (!/^(js|css|html)$/.test(scriptType)) {
- scriptType = null;
- }
- debug("executable type:", scriptType);
- var parsedType = parsed.type;
- debug("parsed type:", parsedType);
- if (!parsedType) {
- debug("type defaulted:", scriptType);
- parsed.type = scriptType;
- }
- }
- function checkFiles(parsed) {
- var argv = parsed.argv;
- var isTTY = true;
- var file_params = parsed.files || [];
- var hadGlob = false;
- try {
- isTTY = process.stdin.isTTY;
- } catch (ex) {
- debug("error querying for isTTY:", ex);
- }
- debug('isTTY: ' + isTTY);
- // assume any remaining args are files
- file_params = file_params.concat(argv.remain);
- parsed.files = [];
- // assume any remaining args are files
- file_params.forEach(function(f) {
- // strip stdin path eagerly added by nopt in '-f -' case
- if (f === '-') {
- return;
- }
- var foundFiles = [];
- var isGlob = glob.hasMagic(f);
- // Input was a glob
- if (isGlob) {
- hadGlob = true;
- foundFiles = glob(f, {
- sync: true,
- absolute: true,
- ignore: ['**/node_modules/**', '**/.git/**']
- });
- } else {
- // Input was not a glob, add it to an array so we are able to handle it in the same loop below
- try {
- testFilePath(f);
- } catch (err) {
- // if file is not found, and the resolved path indicates stdin marker
- if (path.parse(f).base === '-') {
- f = '-';
- } else {
- throw err;
- }
- }
- foundFiles = [f];
- }
- if (foundFiles && foundFiles.length) {
- // Add files to the parsed.files if it didn't exist in the array yet
- foundFiles.forEach(function(file) {
- var filePath = path.resolve(file);
- if (file === '-') { // case of stdin
- parsed.files.push(file);
- } else if (parsed.files.indexOf(filePath) === -1) {
- parsed.files.push(filePath);
- }
- });
- }
- });
- if ('string' === typeof parsed.outfile && isTTY && !parsed.files.length) {
- testFilePath(parsed.outfile);
- // use outfile as input when no other files passed in args
- parsed.files.push(parsed.outfile);
- // operation is now an implicit overwrite
- parsed.replace = true;
- }
- if (hadGlob || parsed.files.length > 1) {
- parsed.replace = true;
- }
- if (!parsed.files.length && !hadGlob) {
- // read stdin by default
- parsed.files.push('-');
- }
- debug('files.length ' + parsed.files.length);
- if (parsed.files.indexOf('-') > -1 && isTTY && !hadGlob) {
- throw 'Must pipe input or define at least one file.';
- }
- return parsed;
- }
- function testFilePath(filepath) {
- try {
- if (filepath !== "-") {
- fs.statSync(filepath);
- }
- } catch (err) {
- throw 'Unable to open path "' + filepath + '"';
- }
- }
- function logToStdout(str, config) {
- if (typeof config.quiet === "undefined" || !config.quiet) {
- console.log(str);
- }
- }
|