summaryrefslogtreecommitdiff
path: root/plugins/compressor/js_minifer.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/compressor/js_minifer.py')
-rw-r--r--plugins/compressor/js_minifer.py171
1 files changed, 171 insertions, 0 deletions
diff --git a/plugins/compressor/js_minifer.py b/plugins/compressor/js_minifer.py
new file mode 100644
index 0000000..6654b76
--- /dev/null
+++ b/plugins/compressor/js_minifer.py
@@ -0,0 +1,171 @@
+# -*- 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()