diff options
Diffstat (limited to 'jsmin/__init__.py')
-rw-r--r-- | jsmin/__init__.py | 226 |
1 files changed, 130 insertions, 96 deletions
diff --git a/jsmin/__init__.py b/jsmin/__init__.py index 23bed60..ed79e8e 100644 --- a/jsmin/__init__.py +++ b/jsmin/__init__.py @@ -37,10 +37,10 @@ else: __all__ = ['jsmin', 'JavascriptMinify'] -__version__ = '2.0.9' +__version__ = '2.2.0' -def jsmin(js): +def jsmin(js, **kwargs): """ returns a minified version of the javascript string """ @@ -55,7 +55,7 @@ def jsmin(js): klass = io.StringIO ins = klass(js) outs = klass() - JavascriptMinify(ins, outs).minify() + JavascriptMinify(ins, outs, **kwargs).minify() return outs.getvalue() @@ -65,9 +65,10 @@ class JavascriptMinify(object): to an output stream """ - def __init__(self, instream=None, outstream=None): + def __init__(self, instream=None, outstream=None, quote_chars="'\""): self.ins = instream self.outs = outstream + self.quote_chars = quote_chars def minify(self, instream=None, outstream=None): if instream and outstream: @@ -82,6 +83,8 @@ class JavascriptMinify(object): if char in 'return': self.return_buf += char self.is_return = self.return_buf == 'return' + else: + self.return_buf = '' self.outs.write(char) if self.is_return: self.return_buf = '' @@ -90,70 +93,26 @@ class JavascriptMinify(object): space_strings = "abcdefghijklmnopqrstuvwxyz"\ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\" - starters, enders = '{[(+-', '}])+-"\'' - newlinestart_strings = starters + space_strings - newlineend_strings = enders + space_strings + self.space_strings = space_strings + starters, enders = '{[(+-', '}])+-/' + self.quote_chars + newlinestart_strings = starters + space_strings + self.quote_chars + newlineend_strings = enders + space_strings + self.quote_chars + self.newlinestart_strings = newlinestart_strings + self.newlineend_strings = newlineend_strings + do_newline = False do_space = False escape_slash_count = 0 - doing_single_comment = False - previous_before_comment = '' - doing_multi_comment = False - in_re = False in_quote = '' quote_buf = [] - - previous = read(1) - if previous == '\\': - escape_slash_count += 1 + + previous = ';' + previous_non_space = ';' next1 = read(1) - if previous == '/': - if next1 == '/': - doing_single_comment = True - elif next1 == '*': - doing_multi_comment = True - previous = next1 - next1 = read(1) - 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 1: + while next1: next2 = read(1) - if not next2: - last = next1.strip() - if not (doing_single_comment or doing_multi_comment)\ - and last not in ('', '/'): - if in_quote: - write(''.join(quote_buf)) - 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: + if in_quote: quote_buf.append(next1) if next1 == in_quote: @@ -167,19 +126,9 @@ class JavascriptMinify(object): in_quote = '' write(''.join(quote_buf)) elif next1 in '\r\n': - if previous_non_space in newlineend_strings \ - or previous_non_space > '~': - while 1: - if next2 < '!': - next2 = read(1) - if not next2: - break - else: - if next2 in newlinestart_strings \ - or next2 > '~' or next2 == '/': - do_newline = True - break - elif next1 < '!' and not in_re: + next2, do_newline = self.newline( + previous_non_space, next2, do_newline) + elif next1 < '!': if (previous_non_space in space_strings \ or previous_non_space > '~') \ and (next2 in space_strings or next2 > '~'): @@ -193,41 +142,126 @@ class JavascriptMinify(object): elif next1 == '/': if do_space: write(' ') - if in_re: - if previous != '\\' or (not escape_slash_count % 2) or next2 in 'gimy': - in_re = False - write('/') - elif next2 == '/': - doing_single_comment = True - previous_before_comment = previous_non_space + if next2 == '/': + # Line comment: treat it as a newline, but skip it + next2 = self.line_comment(next1, next2) + next1 = '\n' + next2, do_newline = self.newline( + previous_non_space, next2, do_newline) elif next2 == '*': - doing_multi_comment = True - previous = next1 - next1 = next2 + self.block_comment(next1, next2) next2 = read(1) + if previous_non_space in space_strings: + do_space = True + next1 = previous else: - in_re = previous_non_space in '(,=:[?!&|' or self.is_return # literal regular expression - write('/') + if previous_non_space in '{(,=:[?!&|;' or self.is_return: + self.regex_literal(next1, next2) + # hackish: after regex literal next1 is still / + # (it was the initial /, now it's the last /) + next2 = read(1) + else: + write('/') else: - if do_space: - do_space = False - write(' ') if do_newline: write('\n') do_newline = False - + do_space = False + if do_space: + do_space = False + write(' ') + write(next1) - if not in_re and next1 in "'\"": + if next1 in self.quote_chars: in_quote = next1 quote_buf = [] - previous = next1 - next1 = next2 + if next1 >= '!': + previous_non_space = next1 - if previous >= '!': - previous_non_space = previous - - if previous == '\\': + if next1 == '\\': escape_slash_count += 1 else: escape_slash_count = 0 + + previous = next1 + next1 = next2 + + def regex_literal(self, next1, next2): + assert next1 == '/' # otherwise we should not be called! + + self.return_buf = '' + + read = self.ins.read + write = self.outs.write + + in_char_class = False + + write('/') + + next = next2 + while next != '/' or in_char_class: + write(next) + if next == '\\': + write(read(1)) # whatever is next is escaped + elif next == '[': + write(read(1)) # character class cannot be empty + in_char_class = True + elif next == ']': + in_char_class = False + next = read(1) + + write('/') + + def line_comment(self, next1, next2): + assert next1 == next2 == '/' + + read = self.ins.read + + while next1 and next1 not in '\r\n': + next1 = read(1) + while next1 and next1 in '\r\n': + next1 = read(1) + + return next1 + + def block_comment(self, next1, next2): + assert next1 == '/' + assert next2 == '*' + + read = self.ins.read + + # Skip past first /* and avoid catching on /*/...*/ + next1 = read(1) + next2 = read(1) + + comment_buffer = '/*' + while next1 != '*' or next2 != '/': + comment_buffer += next1 + next1 = next2 + next2 = read(1) + + if comment_buffer.startswith("/*!"): + # comment needs preserving + self.outs.write(comment_buffer) + self.outs.write("*/\n") + + + def newline(self, previous_non_space, next2, do_newline): + read = self.ins.read + + if previous_non_space and ( + previous_non_space in self.newlineend_strings + or previous_non_space > '~'): + while 1: + if next2 < '!': + next2 = read(1) + if not next2: + break + else: + if next2 in self.newlinestart_strings \ + or next2 > '~' or next2 == '/': + do_newline = True + break + + return next2, do_newline |