diff --git a/NOTICE.txt b/NOTICE.txt index 7efbfd53d36..7883d1bba33 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -2734,4 +2734,32 @@ one at http://mozilla.org/MPL/2.0/. @(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ +--------------------------------------------------------- + +--------------------------------------------------------- + +jsmin 2.2.2 - MIT + + +Copyright (c) 2013 Dave St.Germain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + --------------------------------------------------------- diff --git a/src/azure-cli/azure/cli/command_modules/resource/_json_handler.py b/src/azure-cli/azure/cli/command_modules/resource/_json_handler.py new file mode 100644 index 00000000000..71cfc16f9ad --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/resource/_json_handler.py @@ -0,0 +1,221 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +# The jsmin code from https://github.com/tikitu/jsmin/blob/release-2.2.2/jsmin/__init__.py is referenced +def json_min(js, **kwargs): + """ + returns a minified version of the json string + """ + import io + klass = io.StringIO + ins = klass(js) + outs = klass() + JsonMinify(ins, outs, **kwargs).minify() + return outs.getvalue() + + +# pylint: disable=attribute-defined-outside-init, too-many-branches, too-many-statements, too-many-instance-attributes +class JsonMinify: + """ + Minify an input stream of json, writing to an output stream + """ + + 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: + self.ins, self.outs = instream, outstream + + self.is_return = False + self.return_buf = '' + + def write(char): + # all of this is to support literal regular expressions. + # sigh + if char in 'return': + self.return_buf += char + self.is_return = self.return_buf == 'return' + else: + self.return_buf = '' + self.is_return = self.is_return and char < '!' + self.outs.write(char) + if self.is_return: + self.return_buf = '' + + read = self.ins.read + + space_strings = "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\" + 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 + in_quote = '' + quote_buf = [] + + previous = ';' + previous_non_space = ';' + next1 = read(1) + + while next1: + next2 = read(1) + if in_quote: + quote_buf.append(next1) + + if next1 == in_quote: + numslashes = 0 + for c in reversed(quote_buf[:-1]): + if c != '\\': + break + numslashes += 1 + if numslashes % 2 == 0: + in_quote = '' + write(''.join(quote_buf)) + elif next1 in '\r\n': + 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 > '~'): + do_space = True + elif previous_non_space in '-+' and next2 == previous_non_space: + # protect against + ++ or - -- sequences + do_space = True + elif self.is_return and next2 == '/': + # returning a regex... + write(' ') + elif next1 == '/': + if do_space: + write(' ') + 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 == '*': + self.block_comment(next1, next2) + next2 = read(1) + if previous_non_space in space_strings: + do_space = True + next1 = previous + else: + 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_newline: + write('\n') + do_newline = False + do_space = False + if do_space: + do_space = False + write(' ') + + write(next1) + if next1 in self.quote_chars: + in_quote = next1 + quote_buf = [] + + if next1 >= '!': + previous_non_space = next1 + + 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_char = next2 + while next_char and (next_char != '/' or in_char_class): + write(next_char) + if next_char == '\\': + write(read(1)) # whatever is next is escaped + elif next_char == '[': + write(read(1)) # character class cannot be empty + in_char_class = True + elif next_char == ']': + in_char_class = False + next_char = 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 diff --git a/src/azure-cli/azure/cli/command_modules/resource/_packing_engine.py b/src/azure-cli/azure/cli/command_modules/resource/_packing_engine.py index 197e3072b3f..9581bf5cbbd 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/_packing_engine.py +++ b/src/azure-cli/azure/cli/command_modules/resource/_packing_engine.py @@ -27,12 +27,15 @@ def __init__(self, root_template_directory): # pylint: disable=redefined-outer-name def process_template(template, preserve_order=True, file_path=None): - from jsmin import jsmin + from ._json_handler import json_min # When commenting at the bottom of all elements in a JSON object, jsmin has a bug that will wrap lines. - # It will affect the subsequent multi-line processing logic, so deal with this situation in advance here. + # It will affect the subsequent multi-line processing logic, so remove those comments in advance here. + # Related issue: https://github.com/Azure/azure-cli/issues/11995, the sample is in the additional context of it. template = re.sub(r'(^[\t ]*//[\s\S]*?\n)|(^[\t ]*/\*{1,2}[\s\S]*?\*/)', '', template, flags=re.M) - minified = jsmin(template) + + # In order to solve the package conflict introduced by jsmin, the jsmin code is referenced into json_min + minified = json_min(template) # Remove extra spaces, compress multiline string(s) result = re.sub(r'\s\s+', ' ', minified, flags=re.DOTALL) diff --git a/src/azure-cli/azure/cli/command_modules/resource/custom.py b/src/azure-cli/azure/cli/command_modules/resource/custom.py index f1dbf7a1ea7..647508b0f40 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/custom.py +++ b/src/azure-cli/azure/cli/command_modules/resource/custom.py @@ -286,12 +286,15 @@ def _urlretrieve(url): # pylint: disable=redefined-outer-name def _remove_comments_from_json(template, preserve_order=True, file_path=None): - from jsmin import jsmin + from ._json_handler import json_min # When commenting at the bottom of all elements in a JSON object, jsmin has a bug that will wrap lines. - # It will affect the subsequent multi-line processing logic, so deal with this situation in advance here. + # It will affect the subsequent multi-line processing logic, so remove those comments in advance here. + # Related issue: https://github.com/Azure/azure-cli/issues/11995, the sample is in the additional context of it. template = re.sub(r'(^[\t ]*//[\s\S]*?\n)|(^[\t ]*/\*{1,2}[\s\S]*?\*/)', '', template, flags=re.M) - minified = jsmin(template) + + # In order to solve the package conflict introduced by jsmin, the jsmin code is referenced into json_min + minified = json_min(template) try: return shell_safe_json_parse(minified, preserve_order, strict=False) # use strict=False to allow multiline strings except CLIError: diff --git a/src/azure-cli/requirements.py3.Darwin.txt b/src/azure-cli/requirements.py3.Darwin.txt index 8c649e5fccf..1ca0484cea1 100644 --- a/src/azure-cli/requirements.py3.Darwin.txt +++ b/src/azure-cli/requirements.py3.Darwin.txt @@ -106,7 +106,6 @@ invoke==1.2.0 isodate==0.6.0 Jinja2==2.11.3 jmespath==0.9.5 -jsmin==2.2.2 knack==0.8.2 MarkupSafe==1.1.1 msal==1.10.0 diff --git a/src/azure-cli/requirements.py3.Linux.txt b/src/azure-cli/requirements.py3.Linux.txt index c2009372cfc..08b7de24d25 100644 --- a/src/azure-cli/requirements.py3.Linux.txt +++ b/src/azure-cli/requirements.py3.Linux.txt @@ -107,7 +107,6 @@ invoke==1.2.0 isodate==0.6.0 Jinja2==2.11.3 jmespath==0.9.5 -jsmin==2.2.2 knack==0.8.2 MarkupSafe==1.1.1 msal==1.10.0 diff --git a/src/azure-cli/requirements.py3.windows.txt b/src/azure-cli/requirements.py3.windows.txt index 6a63c610ebe..b648f7d726b 100644 --- a/src/azure-cli/requirements.py3.windows.txt +++ b/src/azure-cli/requirements.py3.windows.txt @@ -105,7 +105,6 @@ invoke==1.2.0 isodate==0.6.0 Jinja2==2.11.3 jmespath==0.9.5 -jsmin==2.2.2 knack==0.8.2 MarkupSafe==1.1.1 msal==1.10.0 diff --git a/src/azure-cli/setup.py b/src/azure-cli/setup.py index e2df1623096..5683dee8b82 100644 --- a/src/azure-cli/setup.py +++ b/src/azure-cli/setup.py @@ -137,7 +137,6 @@ 'azure-synapse-managedprivateendpoints~=0.3.0', 'fabric~=2.4', 'javaproperties==0.5.1', - 'jsmin~=2.2.2', 'jsondiff==1.2.0', 'packaging~=20.9', 'PyGithub~=1.38',