Skip to content

Instantly share code, notes, and snippets.

@samthor
Last active August 19, 2023 09:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samthor/c45f0de665e9788c6dc6 to your computer and use it in GitHub Desktop.
Save samthor/c45f0de665e9788c6dc6 to your computer and use it in GitHub Desktop.
Shell interface to Closure compiler (in Python)
#!/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