Last active
August 19, 2023 09:26
-
-
Save samthor/c45f0de665e9788c6dc6 to your computer and use it in GitHub Desktop.
Shell interface to Closure compiler (in Python)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import json | |
import sys | |
try: # Py3 | |
from urllib.parse import urlencode | |
from urllib.request import urlopen | |
from urllib.request import Request | |
except ImportError: # Py2 | |
from urllib import urlencode | |
from urllib2 import urlopen | |
from urllib2 import Request | |
URL = 'https://closure-compiler.appspot.com/compile' | |
def main(): | |
import argparse | |
parser = argparse.ArgumentParser() | |
parser.add_argument('files', type=str, nargs='+', | |
help='.js files to cat and compile together') | |
parser.add_argument('--advanced', '-a', action='store_true', | |
help='whether to use advanced compilation') | |
parser.add_argument('--strict', '-s', action='store_true', | |
help='whether to run in strict mode') | |
parser.add_argument('--closure', '-c', action='store_true', | |
help='whether to include closure library') | |
parser.add_argument('--externs', '-e', type=str, nargs='*', | |
default=[], help='externs .js files') | |
parser.add_argument('--ignore', '-i', type=str, action='append', | |
help='warning/error to ignore') | |
parser.add_argument('--exports', '-x', action='store_true', | |
help='whether to generate exports') | |
parser.add_argument('--named', '-n', action='store_true', | |
help='send named files to compiler') | |
parser.add_argument('--iife', '-f', action='store_true', | |
help='whether to wrap in IIFE') | |
args = parser.parse_args() | |
# Read content of all files. | |
def read(path): | |
with open(path) as f: | |
return f.read() | |
content = [(x, read(x)) for x in args.files] | |
externs = [read(x) for x in args.externs] | |
# Build CompilerOptions, for compile() call. | |
options = CompilerOptions() | |
options.advancedLevel = args.advanced | |
options.closureLibrary = args.closure | |
options.strictMode = args.strict | |
options.generateExports = args.exports | |
options.namedFiles = args.named | |
# Run compile step, and render output code! | |
out = compile(content, externs, options) | |
if 'compiledCode' in out: | |
code = out['compiledCode'] | |
if args.iife: | |
code = '(function(){' + code + '}())' | |
print(code) | |
# Render each valid response message to stderr. | |
modes = (('errors', bcolors.FAIL), ('warnings', bcolors.WARNING)) | |
for category, color in modes: | |
for msg in out.get(category, []): | |
if msg['file']: | |
msg['file'] = filepath(args.files, msg['file']) | |
if args.ignore and msg['type'] in args.ignore: | |
continue | |
writemsg(sys.stderr, msg, color) | |
# Look for server errors. | |
if 'serverErrors' in out: | |
# TODO: Format this so it doesn't look like Java has failed locally (!). | |
for err in out['serverErrors']: | |
sys.stderr.write(err['error']) | |
# Return non-zero only if there is no code. | |
if 'compiledCode' not in out: | |
sys.exit(1) | |
class bcolors: | |
HEADER = '\033[95m' | |
OKBLUE = '\033[94m' | |
OKGREEN = '\033[92m' | |
WARNING = '\033[93m' | |
FAIL = '\033[91m' | |
ENDC = '\033[0m' | |
def writemsg(out, msg, color=bcolors.WARNING): | |
if not msg['file'] and msg['lineno'] < 0: | |
out.write('{}\n'.format(msg['type'])) | |
else: | |
out.write('{}:{} ({})\n'.format(msg['file'], msg['lineno'], msg['type'])) | |
if 'warning' in msg: | |
out.write('{}\n'.format(msg['warning'])) | |
if 'error' in msg: | |
out.write('{}\n'.format(msg['error'])) | |
if msg['line']: | |
out.write('{}{}\n'.format(color, msg['line'])) | |
out.write('{}{}^\n'.format(bcolors.OKGREEN, msg['charno'] * ' ')) | |
out.write('{}\n'.format(bcolors.ENDC)) | |
out.flush() | |
def filepath(files, response): | |
"""Finds the filepath from an array of files and a response. | |
Args: | |
files: contains a list of paths | |
response: contains either 'Input_<n>' or other text | |
Returns: name of file | |
""" | |
if not response: | |
return '' | |
PREFIX = 'Input_' | |
if response.startswith(PREFIX): | |
index = int(response[len(PREFIX):]) | |
return files[index] | |
return response | |
class CompilerOptions: | |
language = 6 | |
languageOut = 5 | |
advancedLevel = False | |
closureLibrary = False | |
strictMode = False | |
generateExports = False | |
namedFiles = False | |
def compile(content, externs, options): | |
level = (options.advancedLevel and 'ADVANCED' or 'SIMPLE') + '_OPTIMIZATIONS' | |
params = [ | |
('compilation_level', level), | |
('output_format', 'json'), | |
('output_info', 'compiled_code'), | |
('output_info', 'warnings'), | |
('output_info', 'errors'), | |
('warning_level', 'verbose'), | |
('generate_exports', options.generateExports), | |
('use_closure_library', options.closureLibrary and 'true' or 'false'), | |
] | |
for name, data in content: | |
if options.namedFiles and len(name): | |
params.append(('js_code:' + name, data)) | |
else: | |
params.append(('js_code', data)) | |
for key, lang in [('language', options.language), ('language_out', options.languageOut)]: | |
s = 'ES{}'.format(lang) | |
if options.strictMode: | |
s += '_STRICT' | |
params.append((key, s)) | |
if externs: | |
externs = '\n'.join(externs) | |
params.append(('js_externs', externs)) | |
request = Request(URL) | |
request.add_header('Content-Type', | |
'application/x-www-form-urlencoded;charset=utf-8') | |
data = urlencode(params).encode('utf-8') | |
response = urlopen(request, data) | |
if response.getcode() != 200: | |
raise Exception('response status was: ' + response.status) | |
raw = response.read().decode('utf-8') | |
return json.loads(raw) | |
if __name__ == '__main__': | |
main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment