441 lines
17 KiB
Python
441 lines
17 KiB
Python
from __future__ import print_function
|
|
import sys
|
|
import os
|
|
import platform
|
|
import io
|
|
import getopt
|
|
import re
|
|
import string
|
|
import errno
|
|
import copy
|
|
import glob
|
|
from jsbeautifier.__version__ import __version__
|
|
from jsbeautifier.javascript.options import BeautifierOptions
|
|
from jsbeautifier.javascript.beautifier import Beautifier
|
|
|
|
#
|
|
# 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.
|
|
#
|
|
# Originally written by Einar Lielmanis et al.,
|
|
# Conversion to python by Einar Lielmanis, einar@beautifier.io,
|
|
# Parsing improvement for brace-less and semicolon-less statements
|
|
# by Liam Newman <bitwiseman@beautifier.io>
|
|
# Python is not my native language, feel free to push things around.
|
|
#
|
|
# Use either from command line (script displays its usage when run
|
|
# without any parameters),
|
|
#
|
|
#
|
|
# or, alternatively, use it as a module:
|
|
#
|
|
# import jsbeautifier
|
|
# res = jsbeautifier.beautify('your javascript string')
|
|
# res = jsbeautifier.beautify_file('some_file.js')
|
|
#
|
|
# you may specify some options:
|
|
#
|
|
# opts = jsbeautifier.default_options()
|
|
# opts.indent_size = 2
|
|
# res = jsbeautifier.beautify('some javascript', opts)
|
|
#
|
|
#
|
|
# Here are the available options: (read source)
|
|
|
|
|
|
class MissingInputStreamError(Exception):
|
|
pass
|
|
|
|
def default_options():
|
|
return BeautifierOptions()
|
|
|
|
|
|
def beautify(string, opts=default_options()):
|
|
b = Beautifier()
|
|
return b.beautify(string, opts)
|
|
|
|
|
|
def set_file_editorconfig_opts(filename, js_options):
|
|
from editorconfig import get_properties, EditorConfigError
|
|
try:
|
|
_ecoptions = get_properties(os.path.abspath(filename))
|
|
|
|
if _ecoptions.get("indent_style") == "tab":
|
|
js_options.indent_with_tabs = True
|
|
elif _ecoptions.get("indent_style") == "space":
|
|
js_options.indent_with_tabs = False
|
|
|
|
if _ecoptions.get("indent_size"):
|
|
js_options.indent_size = int(_ecoptions["indent_size"])
|
|
|
|
if _ecoptions.get("max_line_length"):
|
|
if _ecoptions.get("max_line_length") == "off":
|
|
js_options.wrap_line_length = 0
|
|
else:
|
|
js_options.wrap_line_length = int(
|
|
_ecoptions["max_line_length"])
|
|
|
|
if _ecoptions.get("insert_final_newline") == 'true':
|
|
js_options.end_with_newline = True
|
|
elif _ecoptions.get("insert_final_newline") == 'false':
|
|
js_options.end_with_newline = False
|
|
|
|
if _ecoptions.get("end_of_line"):
|
|
if _ecoptions["end_of_line"] == "cr":
|
|
js_options.eol = '\r'
|
|
elif _ecoptions["end_of_line"] == "lf":
|
|
js_options.eol = '\n'
|
|
elif _ecoptions["end_of_line"] == "crlf":
|
|
js_options.eol = '\r\n'
|
|
|
|
except EditorConfigError:
|
|
# do not error on bad editor config
|
|
print("Error loading EditorConfig. Ignoring.", file=sys.stderr)
|
|
|
|
def beautify_file(file_name, opts=default_options()):
|
|
input_string = ''
|
|
if file_name == '-': # stdin
|
|
if sys.stdin.isatty():
|
|
raise MissingInputStreamError()
|
|
|
|
stream = sys.stdin
|
|
if platform.platform().lower().startswith('windows'):
|
|
if sys.version_info.major >= 3:
|
|
# for python 3 on windows this prevents conversion
|
|
stream = io.TextIOWrapper(sys.stdin.buffer, newline='')
|
|
elif platform.architecture()[0] == '32bit':
|
|
# for python 2 x86 on windows this prevents conversion
|
|
import msvcrt
|
|
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
|
|
else:
|
|
raise Exception('Pipe to stdin not supported on Windows with Python 2.x 64-bit.')
|
|
|
|
input_string = stream.read()
|
|
|
|
# if you pipe an empty string, that is a failure
|
|
if input_string == '':
|
|
raise MissingInputStreamError()
|
|
else:
|
|
stream = io.open(file_name, 'rt', newline='', encoding='UTF-8')
|
|
input_string = stream.read()
|
|
|
|
return beautify(input_string, opts)
|
|
|
|
|
|
def usage(stream=sys.stdout):
|
|
|
|
print("jsbeautifier.py@" + __version__ + """
|
|
|
|
Javascript beautifier (https://beautifier.io/)
|
|
|
|
Usage: jsbeautifier.py [options] <infile>
|
|
|
|
<infile> can be "-", which means stdin.
|
|
|
|
Input options:
|
|
|
|
-i, --stdin Read input from stdin
|
|
|
|
Output options:
|
|
|
|
-s, --indent-size=NUMBER Indentation size. (default 4).
|
|
-c, --indent-char=CHAR Character to indent with. (default space).
|
|
-e, --eol=STRING Character(s) to use as line terminators.
|
|
(default first newline in file, otherwise "\\n")
|
|
-t, --indent-with-tabs Indent with tabs, overrides -s and -c
|
|
-d, --disable-preserve-newlines Do not preserve existing line breaks.
|
|
-P, --space-in-paren Add padding spaces within paren, ie. f( a, b )
|
|
-E, --space-in-empty-paren Add a single space inside empty paren, ie. f( )
|
|
-j, --jslint-happy More jslint-compatible output
|
|
-a, --space-after-anon-function Add a space before an anonymous function's parens, ie. function ()
|
|
--space-after-named-function Add a space before a named function's parens, i.e. function example ()
|
|
-b, --brace-style=collapse Brace style (collapse, expand, end-expand, none)(,preserve-inline)
|
|
-k, --keep-array-indentation Keep array indentation.
|
|
-r, --replace Write output in-place, replacing input
|
|
-o, --outfile=FILE Specify a file to output to (default stdout)
|
|
-f, --keep-function-indentation Do not re-indent function bodies defined in var lines.
|
|
-x, --unescape-strings Decode printable chars encoded in \\xNN notation.
|
|
-X, --e4x Pass E4X xml literals through untouched
|
|
-C, --comma-first Put commas at the beginning of new line instead of end.
|
|
-O, --operator-position=STRING Set operator position (before-newline, after-newline, preserve-newline)
|
|
-w, --wrap-line-length Attempt to wrap line when it exceeds this length.
|
|
NOTE: Line continues until next wrap point is found.
|
|
-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) ["auto"] auto = none in JavaScript, all in html
|
|
--editorconfig Enable setting configuration from EditorConfig
|
|
|
|
Rarely needed options:
|
|
|
|
--eval-code evaluate code if a JS interpreter is
|
|
installed. May be useful with some obfuscated
|
|
script but poses a potential security issue.
|
|
|
|
-l, --indent-level=NUMBER Initial indentation level. (default 0).
|
|
|
|
-h, --help, --usage Prints this help statement.
|
|
-v, --version Show the version
|
|
|
|
""", file=stream)
|
|
if stream == sys.stderr:
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
|
|
def mkdir_p(path):
|
|
try:
|
|
if path:
|
|
os.makedirs(path)
|
|
except OSError as exc: # Python >2.5
|
|
if exc.errno == errno.EEXIST and os.path.isdir(path):
|
|
pass
|
|
else:
|
|
raise Exception()
|
|
|
|
|
|
def isFileDifferent(filepath, expected):
|
|
try:
|
|
return (
|
|
''.join(
|
|
io.open(
|
|
filepath,
|
|
'rt',
|
|
newline='').readlines()) != expected)
|
|
except BaseException:
|
|
return True
|
|
|
|
|
|
def main():
|
|
|
|
argv = sys.argv[1:]
|
|
|
|
try:
|
|
opts, args = getopt.getopt(argv, "f:s:c:e:o:rdEPjab:kil:xhtvXnCO:w:m:",
|
|
['file=', 'indent-size=', 'indent-char=', 'eol=', 'outfile=', 'replace', 'disable-preserve-newlines',
|
|
'space-in-paren', 'space-in-empty-paren', 'jslint-happy', 'space-after-anon-function',
|
|
'brace-style=', 'indent-level=', 'unescape-strings',
|
|
'help', 'usage', 'stdin', 'eval-code', 'indent-with-tabs', 'keep-function-indentation', 'version',
|
|
'e4x', 'end-with-newline', 'comma-first', 'operator-position=', 'wrap-line-length', 'editorconfig', 'space-after-named-function',
|
|
'keep-array-indentation', 'indent-empty-lines', 'templating'])
|
|
except getopt.GetoptError as ex:
|
|
print(ex, file=sys.stderr)
|
|
return usage(sys.stderr)
|
|
|
|
js_options = default_options()
|
|
|
|
filepath_params = []
|
|
filepath_params.extend(args)
|
|
|
|
outfile_param = 'stdout'
|
|
replace = False
|
|
|
|
for opt, arg in opts:
|
|
if opt in ('--file', '-f'):
|
|
filepath_params.append(arg)
|
|
elif opt in ('--keep-array-indentation', '-k'):
|
|
js_options.keep_array_indentation = True
|
|
elif opt in ('--keep-function-indentation'):
|
|
js_options.keep_function_indentation = True
|
|
elif opt in ('--outfile', '-o'):
|
|
outfile_param = arg
|
|
elif opt in ('--replace', '-r'):
|
|
replace = True
|
|
elif opt in ('--indent-size', '-s'):
|
|
js_options.indent_size = int(arg)
|
|
elif opt in ('--indent-char', '-c'):
|
|
js_options.indent_char = arg
|
|
elif opt in ('--eol', '-e'):
|
|
js_options.eol = arg
|
|
elif opt in ('--indent-with-tabs', '-t'):
|
|
js_options.indent_with_tabs = True
|
|
elif opt in ('--disable-preserve-newlines', '-d'):
|
|
js_options.preserve_newlines = False
|
|
elif opt in ('--max-preserve-newlines', '-m'):
|
|
js_options.max_preserve_newlines = int(arg)
|
|
elif opt in ('--space-in-paren', '-P'):
|
|
js_options.space_in_paren = True
|
|
elif opt in ('--space-in-empty-paren', '-E'):
|
|
js_options.space_in_empty_paren = True
|
|
elif opt in ('--jslint-happy', '-j'):
|
|
js_options.jslint_happy = True
|
|
elif opt in ('--space-after-anon-function', '-a'):
|
|
js_options.space_after_anon_function = True
|
|
elif opt in ('--space-after-named-function'):
|
|
js_options.space_after_named_function = True
|
|
elif opt in ('--eval-code'):
|
|
js_options.eval_code = True
|
|
elif opt in ('--brace-style', '-b'):
|
|
js_options.brace_style = arg
|
|
elif opt in ('--unescape-strings', '-x'):
|
|
js_options.unescape_strings = True
|
|
elif opt in ('--e4x', '-X'):
|
|
js_options.e4x = True
|
|
elif opt in ('--end-with-newline', '-n'):
|
|
js_options.end_with_newline = True
|
|
elif opt in ('--comma-first', '-C'):
|
|
js_options.comma_first = True
|
|
elif opt in ('--operator-position', '-O'):
|
|
js_options.operator_position = arg
|
|
elif opt in ('--wrap-line-length ', '-w'):
|
|
js_options.wrap_line_length = int(arg)
|
|
elif opt in ('--indent-empty-lines'):
|
|
js_options.indent_empty_lines = True
|
|
elif opt in ('--templating'):
|
|
js_options.templating = arg.split(',')
|
|
elif opt in ('--stdin', '-i'):
|
|
# stdin is the default if no files are passed
|
|
filepath_params = []
|
|
elif opt in ('--editorconfig'):
|
|
js_options.editorconfig = True
|
|
elif opt in ('--version', '-v'):
|
|
return print(__version__)
|
|
elif opt in ('--help', '--usage', '-h'):
|
|
return usage()
|
|
|
|
try:
|
|
filepaths = []
|
|
if not filepath_params or (
|
|
len(filepath_params) == 1 and filepath_params[0] == '-'):
|
|
# default to stdin
|
|
filepath_params = []
|
|
filepaths.append('-')
|
|
|
|
|
|
for filepath_param in filepath_params:
|
|
# ignore stdin setting if files are specified
|
|
if '-' == filepath_param:
|
|
continue
|
|
|
|
# Check if each literal filepath exists
|
|
if os.path.isfile(filepath_param):
|
|
filepaths.append(filepath_param)
|
|
elif '*' in filepath_param or '?' in filepath_param:
|
|
# handle globs
|
|
# empty result is okay
|
|
if sys.version_info.major == 2 or (
|
|
sys.version_info.major == 3 and
|
|
sys.version_info.minor <= 4):
|
|
if '**' in filepath_param:
|
|
raise Exception('Recursive globs not supported on Python <= 3.4.')
|
|
filepaths.extend(glob.glob(filepath_param))
|
|
else:
|
|
filepaths.extend(glob.glob(filepath_param, recursive=True))
|
|
else:
|
|
# not a glob and not a file
|
|
raise OSError(errno.ENOENT, os.strerror(errno.ENOENT),
|
|
filepath_param)
|
|
|
|
if len(filepaths) > 1:
|
|
replace = True
|
|
elif filepaths and filepaths[0] == '-':
|
|
replace = False
|
|
|
|
# remove duplicates
|
|
filepaths = set(filepaths)
|
|
|
|
for filepath in filepaths:
|
|
if not replace:
|
|
outfile = outfile_param
|
|
else:
|
|
outfile = filepath
|
|
|
|
# Editorconfig used only on files, not stdin
|
|
if getattr(js_options, 'editorconfig'):
|
|
editorconfig_filepath = filepath
|
|
|
|
if editorconfig_filepath == '-':
|
|
if outfile != 'stdout':
|
|
editorconfig_filepath = outfile
|
|
else:
|
|
fileType = 'js'
|
|
editorconfig_filepath = 'stdin.' + fileType
|
|
|
|
# debug("EditorConfig is enabled for ", editorconfig_filepath);
|
|
js_options = copy.copy(js_options)
|
|
set_file_editorconfig_opts(editorconfig_filepath, js_options)
|
|
|
|
pretty = beautify_file(filepath, js_options)
|
|
|
|
if outfile == 'stdout':
|
|
stream = sys.stdout
|
|
|
|
# python automatically converts newlines in text to "\r\n" when on windows
|
|
# switch to binary to prevent this
|
|
if platform.platform().lower().startswith('windows'):
|
|
if sys.version_info.major >= 3:
|
|
# for python 3 on windows this prevents conversion
|
|
stream = io.TextIOWrapper(sys.stdout.buffer, newline='')
|
|
elif platform.architecture()[0] == '32bit':
|
|
# for python 2 x86 on windows this prevents conversion
|
|
import msvcrt
|
|
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
|
else:
|
|
raise Exception('Pipe to stdout not supported on Windows with Python 2.x 64-bit.')
|
|
|
|
stream.write(pretty)
|
|
else:
|
|
if isFileDifferent(outfile, pretty):
|
|
mkdir_p(os.path.dirname(outfile))
|
|
|
|
# python automatically converts newlines in text to "\r\n" when on windows
|
|
# set newline to empty to prevent this
|
|
with io.open(outfile, 'wt', newline='', encoding='UTF-8') as f:
|
|
print('beautified ' + outfile, file=sys.stdout)
|
|
try:
|
|
f.write(pretty)
|
|
except TypeError:
|
|
# This is not pretty, but given how we did the version import
|
|
# it is the only way to do this without having setup.py
|
|
# fail on a missing six dependency.
|
|
six = __import__("six")
|
|
f.write(six.u(pretty))
|
|
else:
|
|
print('beautified ' + outfile + ' - unchanged', file=sys.stdout)
|
|
|
|
|
|
except MissingInputStreamError:
|
|
print(
|
|
"Must pipe input or define at least one file.\n",
|
|
file=sys.stderr)
|
|
usage(sys.stderr)
|
|
return 1
|
|
|
|
except UnicodeError as ex:
|
|
print("Error while decoding input or encoding output:",
|
|
file=sys.stderr)
|
|
print(ex, file=sys.stderr)
|
|
return 1
|
|
|
|
except Exception as ex:
|
|
print(ex, file=sys.stderr)
|
|
return 1
|
|
|
|
# Success
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|