# -*- coding: utf-8 -*- """JavaScript Minifier functions for CSS-JS-Minify.""" import re from io import StringIO # pure-Python StringIO supports unicode. from .css_minifer import condense_semicolons __all__ = ('js_minify', ) def remove_commented_lines(js): """Force remove commented out lines from Javascript.""" result = "" for line in js.splitlines(): line = re.sub(r"/\*.*\*/", "", line) # (/*COMMENT */) line = re.sub(r"//.*", "", line) # (//COMMENT) result += '\n'+line return result def simple_replacer_js(js): """Force strip simple replacements from Javascript.""" return condense_semicolons(js.replace("debugger;", ";").replace( ";}", "}").replace("; ", ";").replace(" ;", ";").rstrip("\n;")) def js_minify_keep_comments(js): """Return a minified version of the Javascript string.""" ins, outs = StringIO(js), StringIO() JavascriptMinify(ins, outs).minify() return force_single_line_js(outs.getvalue()) def force_single_line_js(js): """Force Javascript to a single line, even if need to add semicolon.""" return ";".join(js.splitlines()) if len(js.splitlines()) > 1 else js class JavascriptMinify(object): """Minify an input stream of Javascript, writing to an output stream.""" def __init__(self, instream=None, outstream=None): """Init class.""" self.ins, self.outs = instream, outstream def minify(self, instream=None, outstream=None): """Minify Javascript using StringIO.""" if instream and outstream: self.ins, self.outs = instream, outstream write, read = self.outs.write, self.ins.read space_strings = ("abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\") starters, enders = '{[(+-', '}])+-"\'' newlinestart_strings = starters + space_strings newlineend_strings = enders + space_strings do_newline, do_space = False, False doing_single_comment, doing_multi_comment = False, False previous_before_comment, in_quote = '', '' in_re, quote_buf = False, [] previous = read(1) next1 = read(1) if previous == '/': if next1 == '/': doing_single_comment = True elif next1 == '*': doing_multi_comment = True else: write(previous) elif not previous: return elif previous >= '!': if previous in "'\"": in_quote = previous write(previous) previous_non_space = previous else: previous_non_space = ' ' if not next1: return while True: next2 = read(1) if not next2: last = next1.strip() conditional_1 = (doing_single_comment or doing_multi_comment) if not conditional_1 and last not in ('', '/'): write(last) break if doing_multi_comment: if next1 == '*' and next2 == '/': doing_multi_comment = False next2 = read(1) elif doing_single_comment: if next1 in '\r\n': doing_single_comment = False while next2 in '\r\n': next2 = read(1) if not next2: break if previous_before_comment in ')}]': do_newline = True elif previous_before_comment in space_strings: write('\n') elif in_quote: quote_buf.append(next1) if next1 == in_quote: numslashes = 0 for c in reversed(quote_buf[:-1]): if c != '\\': break else: numslashes += 1 if numslashes % 2 == 0: in_quote = '' write(''.join(quote_buf)) elif next1 in '\r\n': conditional_2 = previous_non_space in newlineend_strings if conditional_2 or previous_non_space > '~': while 1: if next2 < '!': next2 = read(1) if not next2: break else: conditional_3 = next2 in newlinestart_strings if conditional_3 or next2 > '~' or next2 == '/': do_newline = True break elif next1 < '!' and not in_re: conditional_4 = next2 in space_strings or next2 > '~' conditional_5 = previous_non_space in space_strings conditional_6 = previous_non_space > '~' if (conditional_5 or conditional_6) and (conditional_4): do_space = True elif next1 == '/': if in_re: if previous != '\\': in_re = False write('/') elif next2 == '/': doing_single_comment = True previous_before_comment = previous_non_space elif next2 == '*': doing_multi_comment = True else: in_re = previous_non_space in '(,=:[?!&|' write('/') else: if do_space: do_space = False write(' ') if do_newline: write('\n') do_newline = False write(next1) if not in_re and next1 in "'\"": in_quote = next1 quote_buf = [] previous = next1 next1 = next2 if previous >= '!': previous_non_space = previous def js_minify(js): """Minify a JavaScript string.""" js = remove_commented_lines(js) js = js_minify_keep_comments(js) return js.strip()