Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jsmin/__init__.py: 70%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# vim: set fileencoding=utf-8 :
3# This code is original from jsmin by Douglas Crockford, it was translated to
4# Python by Baruch Even. It was rewritten by Dave St.Germain for speed.
5#
6# The MIT License (MIT)
7#
8# Copyright (c) 2013 Dave St.Germain
9#
10# Permission is hereby granted, free of charge, to any person obtaining a copy
11# of this software and associated documentation files (the "Software"), to deal
12# in the Software without restriction, including without limitation the rights
13# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14# copies of the Software, and to permit persons to whom the Software is
15# furnished to do so, subject to the following conditions:
16#
17# The above copyright notice and this permission notice shall be included in
18# all copies or substantial portions of the Software.
19#
20# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26# THE SOFTWARE.
29import io
31__all__ = ['jsmin', 'JavascriptMinify']
32__version__ = '3.0.1'
35def jsmin(js, **kwargs):
36 """
37 returns a minified version of the javascript string
38 """
39 klass = io.StringIO
40 ins = klass(js)
41 outs = klass()
42 JavascriptMinify(ins, outs, **kwargs).minify()
43 return outs.getvalue()
46class JavascriptMinify(object):
47 """
48 Minify an input stream of javascript, writing
49 to an output stream
50 """
52 def __init__(self, instream=None, outstream=None, quote_chars="'\""):
53 self.ins = instream
54 self.outs = outstream
55 self.quote_chars = quote_chars
57 def minify(self, instream=None, outstream=None):
58 if instream and outstream:
59 self.ins, self.outs = instream, outstream
61 self.is_return = False
62 self.return_buf = ''
64 def write(char):
65 # all of this is to support literal regular expressions.
66 # sigh
67 if char in 'return':
68 self.return_buf += char
69 self.is_return = self.return_buf == 'return'
70 else:
71 self.return_buf = ''
72 self.is_return = self.is_return and char < '!'
73 self.outs.write(char)
74 if self.is_return:
75 self.return_buf = ''
77 read = self.ins.read
79 space_strings = "abcdefghijklmnopqrstuvwxyz"\
80 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\"
81 self.space_strings = space_strings
82 starters, enders = '{[(+-', '}])+-/' + self.quote_chars
83 newlinestart_strings = starters + space_strings + self.quote_chars
84 newlineend_strings = enders + space_strings + self.quote_chars
85 self.newlinestart_strings = newlinestart_strings
86 self.newlineend_strings = newlineend_strings
88 do_newline = False
89 do_space = False
90 escape_slash_count = 0
91 in_quote = ''
92 quote_buf = []
94 previous = ';'
95 previous_non_space = ';'
96 next1 = read(1)
98 while next1:
99 next2 = read(1)
100 if in_quote:
101 quote_buf.append(next1)
103 if next1 == in_quote:
104 numslashes = 0
105 for c in reversed(quote_buf[:-1]):
106 if c != '\\':
107 break
108 else:
109 numslashes += 1
110 if numslashes % 2 == 0:
111 in_quote = ''
112 write(''.join(quote_buf))
113 elif next1 in '\r\n':
114 next2, do_newline = self.newline(
115 previous_non_space, next2, do_newline)
116 elif next1 < '!':
117 if (previous_non_space in space_strings \
118 or previous_non_space > '~') \
119 and (next2 in space_strings or next2 > '~'):
120 do_space = True
121 elif previous_non_space in '-+' and next2 == previous_non_space:
122 # protect against + ++ or - -- sequences
123 do_space = True
124 elif self.is_return and next2 == '/':
125 # returning a regex...
126 write(' ')
127 elif next1 == '/':
128 if do_space:
129 write(' ')
130 if next2 == '/':
131 # Line comment: treat it as a newline, but skip it
132 next2 = self.line_comment(next1, next2)
133 next1 = '\n'
134 next2, do_newline = self.newline(
135 previous_non_space, next2, do_newline)
136 elif next2 == '*':
137 self.block_comment(next1, next2)
138 next2 = read(1)
139 if previous_non_space in space_strings:
140 do_space = True
141 next1 = previous
142 else:
143 if previous_non_space in '{(,=:[?!&|;' or self.is_return:
144 self.regex_literal(next1, next2)
145 # hackish: after regex literal next1 is still /
146 # (it was the initial /, now it's the last /)
147 next2 = read(1)
148 else:
149 write('/')
150 else:
151 if do_newline:
152 write('\n')
153 do_newline = False
154 do_space = False
155 if do_space:
156 do_space = False
157 write(' ')
159 write(next1)
160 if next1 in self.quote_chars:
161 in_quote = next1
162 quote_buf = []
164 if next1 >= '!':
165 previous_non_space = next1
167 if next1 == '\\':
168 escape_slash_count += 1
169 else:
170 escape_slash_count = 0
172 previous = next1
173 next1 = next2
175 def regex_literal(self, next1, next2):
176 assert next1 == '/' # otherwise we should not be called!
178 self.return_buf = ''
180 read = self.ins.read
181 write = self.outs.write
183 in_char_class = False
185 write('/')
187 next = next2
188 while next and (next != '/' or in_char_class):
189 write(next)
190 if next == '\\':
191 write(read(1)) # whatever is next is escaped
192 elif next == '[':
193 write(read(1)) # character class cannot be empty
194 in_char_class = True
195 elif next == ']':
196 in_char_class = False
197 next = read(1)
199 write('/')
201 def line_comment(self, next1, next2):
202 assert next1 == next2 == '/'
204 read = self.ins.read
206 while next1 and next1 not in '\r\n':
207 next1 = read(1)
208 while next1 and next1 in '\r\n':
209 next1 = read(1)
211 return next1
213 def block_comment(self, next1, next2):
214 assert next1 == '/'
215 assert next2 == '*'
217 read = self.ins.read
219 # Skip past first /* and avoid catching on /*/...*/
220 next1 = read(1)
221 next2 = read(1)
223 comment_buffer = '/*'
224 while next1 != '*' or next2 != '/':
225 comment_buffer += next1
226 next1 = next2
227 next2 = read(1)
229 if comment_buffer.startswith("/*!"):
230 # comment needs preserving
231 self.outs.write(comment_buffer)
232 self.outs.write("*/\n")
235 def newline(self, previous_non_space, next2, do_newline):
236 read = self.ins.read
238 if previous_non_space and (
239 previous_non_space in self.newlineend_strings
240 or previous_non_space > '~'):
241 while 1:
242 if next2 < '!':
243 next2 = read(1)
244 if not next2:
245 break
246 else:
247 if next2 in self.newlinestart_strings \
248 or next2 > '~' or next2 == '/':
249 do_newline = True
250 break
252 return next2, do_newline