Merge pull request #112 from dzhibas/fix/st3/issue-109

Fix/st3/issue 109
This commit is contained in:
Terminal 2020-04-17 18:39:48 -04:00 committed by GitHub
commit 6fd83df6b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 127 additions and 85 deletions

View File

View File

@ -1,16 +1,18 @@
import sublime
import sublime_plugin
import decimal import decimal
import sys
import os import os
import re import re
from xml.etree import ElementTree import subprocess
import sys
from xml.dom import minidom from xml.dom import minidom
from xml.etree import ElementTree
import sublime
import sublime_plugin
try: try:
basestring basestring
except NameError: except NameError:
basestring = str basestring = str
try: try:
# python 3 / Sublime Text 3 # python 3 / Sublime Text 3
@ -26,14 +28,15 @@ SUBLIME_MAJOR_VERSION = int(sublime.version()) / 1000
jq_exits = False jq_exits = False
jq_init = False jq_init = False
import subprocess
""" for OSX we need to manually add brew bin path so jq can be found """ """ for OSX we need to manually add brew bin path so jq can be found """
if sys.platform != 'win32' and '/usr/local/bin' not in os.environ['PATH']: if sys.platform != "win32" and "/usr/local/bin" not in os.environ["PATH"]:
os.environ["PATH"] += os.pathsep + '/usr/local/bin' os.environ["PATH"] += os.pathsep + "/usr/local/bin"
""" defer jq presence check until the user tries to use it, include Package "Fix Mac Path" to resolve """ defer jq presence check until the user tries to use it, include Package "Fix Mac Path" to resolve
all homebrew issues (https://github.com/int3h/SublimeFixMacPath) """ all homebrew issues (https://github.com/int3h/SublimeFixMacPath) """
def check_jq(): def check_jq():
global jq_exits global jq_exits
global jq_init global jq_init
@ -42,10 +45,12 @@ def check_jq():
jq_init = True jq_init = True
try: try:
# checking if ./jq tool is available so we can use it # checking if ./jq tool is available so we can use it
s = subprocess.Popen(["jq", "--version"], s = subprocess.Popen(
stdin=subprocess.PIPE, ["jq", "--version"],
stderr=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE) stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)
out, err = s.communicate() out, err = s.communicate()
jq_exits = True jq_exits = True
except OSError: except OSError:
@ -63,9 +68,9 @@ class PrettyJsonBaseCommand:
@staticmethod @staticmethod
def json_loads(selection): def json_loads(selection):
return json.loads(selection, return json.loads(
object_pairs_hook=OrderedDict, selection, object_pairs_hook=OrderedDict, parse_float=decimal.Decimal
parse_float=decimal.Decimal) )
@staticmethod @staticmethod
def json_dumps(obj): def json_dumps(obj):
@ -77,12 +82,14 @@ class PrettyJsonBaseCommand:
line_separator = s.get("line_separator", ",") line_separator = s.get("line_separator", ",")
value_separator = s.get("value_separator", ": ") value_separator = s.get("value_separator", ": ")
output_json = json.dumps(obj, output_json = json.dumps(
indent=s.get("indent", 2), obj,
ensure_ascii=s.get("ensure_ascii", False), indent=s.get("indent", 2),
sort_keys=sort_keys, ensure_ascii=s.get("ensure_ascii", False),
separators=(line_separator, value_separator), sort_keys=sort_keys,
use_decimal=True) separators=(line_separator, value_separator),
use_decimal=True,
)
# do we need try and shuffle things around ? # do we need try and shuffle things around ?
post_process = s.get("keep_arrays_single_line", False) post_process = s.get("keep_arrays_single_line", False)
@ -113,16 +120,18 @@ class PrettyJsonBaseCommand:
if PrettyJsonBaseCommand.force_sorting: if PrettyJsonBaseCommand.force_sorting:
sort_keys = True sort_keys = True
return json.dumps(obj, return json.dumps(
ensure_ascii=s.get("ensure_ascii", False), obj,
sort_keys=sort_keys, ensure_ascii=s.get("ensure_ascii", False),
separators=(line_separator.strip(), value_separator.strip()), sort_keys=sort_keys,
use_decimal=True) separators=(line_separator.strip(), value_separator.strip()),
use_decimal=True,
)
def highlight_error(self, message): def highlight_error(self, message):
self.view.erase_regions('json_errors') self.view.erase_regions("json_errors")
self.view.erase_status('json_errors') self.view.erase_status("json_errors")
m = self.json_error_matcher.search(message) m = self.json_error_matcher.search(message)
if m: if m:
@ -130,22 +139,33 @@ class PrettyJsonBaseCommand:
# sometime we need to highlight one line above # sometime we need to highlight one line above
if "','" in message and "delimiter" in message: if "','" in message and "delimiter" in message:
line_content = self.view.substr(self.view.full_line(self.view.text_point(line - 1, 0))) line_content = self.view.substr(
if line_content.strip()[-1] != ',' and line_content.strip() != '{' and line_content.strip() != '}': self.view.full_line(self.view.text_point(line - 1, 0))
)
if (
line_content.strip()[-1] != ","
and line_content.strip() != "{"
and line_content.strip() != "}"
):
line -= 1 line -= 1
if "control character '\\n'" in message: if "control character '\\n'" in message:
line_content = self.view.substr(self.view.full_line(self.view.text_point(line - 1, 0))) line_content = self.view.substr(
self.view.full_line(self.view.text_point(line - 1, 0))
)
quotes = re.findall(r"\"", line_content) quotes = re.findall(r"\"", line_content)
if len(quotes) % 2 != 0 and len(quotes) != 0: if len(quotes) % 2 != 0 and len(quotes) != 0:
line -= 1 line -= 1
regions = [self.view.full_line(self.view.text_point(line, 0)), ] regions = [
self.view.full_line(self.view.text_point(line, 0)),
]
self.view.add_regions('json_errors', regions, 'invalid', 'dot', self.view.add_regions(
sublime.DRAW_OUTLINED) "json_errors", regions, "invalid", "dot", sublime.DRAW_OUTLINED
)
self.view.show(regions[0]) self.view.show(regions[0])
self.view.set_status('json_errors', message) self.view.set_status("json_errors", message)
def show_exception(self): def show_exception(self):
exc = sys.exc_info()[1] exc = sys.exc_info()[1]
@ -154,23 +174,24 @@ class PrettyJsonBaseCommand:
def change_syntax(self): def change_syntax(self):
""" Changes syntax to JSON if its in plain text """ """ Changes syntax to JSON if its in plain text """
if "Plain text" in self.view.settings().get('syntax'): if "Plain text" in self.view.settings().get("syntax"):
self.view.set_syntax_file("Packages/JavaScript/JSON.tmLanguage") self.view.set_syntax_file("Packages/JavaScript/JSON.tmLanguage")
class PrettyJsonValidate(PrettyJsonBaseCommand, sublime_plugin.TextCommand): class PrettyJsonValidate(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
def run(self, edit): def run(self, edit):
self.view.erase_regions('json_errors') self.view.erase_regions("json_errors")
for region in self.view.sel(): regions = self.view.sel()
# If no selection, use the entire file as the selection for region in regions:
if region.empty() and s.get("use_entire_file_if_no_selection", True): if region.empty() and len(regions) > 1:
continue
elif region.empty() and s.get("use_entire_file_if_no_selection", True):
selection = sublime.Region(0, self.view.size()) selection = sublime.Region(0, self.view.size())
selected_entire_file = True
else: else:
selection = region selection = region
try: try:
obj = self.json_loads(self.view.substr(selection)) self.json_loads(self.view.substr(selection))
sublime.message_dialog("JSON is Valid") sublime.message_dialog("JSON is Valid")
except Exception: except Exception:
self.show_exception() self.show_exception()
@ -180,14 +201,15 @@ class PrettyJsonValidate(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand): class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
""" Pretty Print JSON """ """ Pretty Print JSON """
def run(self, edit): def run(self, edit):
self.view.erase_regions('json_errors') self.view.erase_regions("json_errors")
for region in self.view.sel(): regions = self.view.sel()
for region in regions:
selected_entire_file = False selected_entire_file = False
if region.empty() and len(regions) > 1:
# If no selection, use the entire file as the selection continue
if region.empty() and s.get("use_entire_file_if_no_selection", True): elif region.empty() and s.get("use_entire_file_if_no_selection", True):
selection = sublime.Region(0, self.view.size()) selection = sublime.Region(0, self.view.size())
selected_entire_file = True selected_entire_file = True
else: else:
@ -202,23 +224,33 @@ class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
self.change_syntax() self.change_syntax()
except Exception: except Exception:
amount_of_single_quotes = re.findall(r"(\'[^\']+\'?)", selection_text) try:
amount_of_double_quotes = re.findall(r"(\"[^\"]+\"?)", selection_text) amount_of_single_quotes = re.findall(
r"(\'[^\']+\'?)", selection_text
)
amount_of_double_quotes = re.findall(
r"(\"[^\"]+\"?)", selection_text
)
if len(amount_of_single_quotes) >= len(amount_of_double_quotes): if len(amount_of_single_quotes) >= len(amount_of_double_quotes):
selection_text_modified = re.sub(r"(?:\'([^\']+)\'?)", r'"\1"', selection_text) selection_text_modified = re.sub(
obj = self.json_loads(selection_text_modified) r"(?:\'([^\']+)\'?)", r'"\1"', selection_text
self.view.replace(edit, selection, self.json_dumps(obj)) )
obj = self.json_loads(selection_text_modified)
self.view.replace(edit, selection, self.json_dumps(obj))
if selected_entire_file: if selected_entire_file:
self.change_syntax() self.change_syntax()
else: else:
self.show_exception()
except:
self.show_exception() self.show_exception()
class PrettyJsonAndSortCommand(PrettyJsonCommand, sublime_plugin.TextCommand): class PrettyJsonAndSortCommand(PrettyJsonCommand, sublime_plugin.TextCommand):
""" Pretty print json with forced sorting """ """ Pretty print json with forced sorting """
def run(self, edit): def run(self, edit):
PrettyJsonBaseCommand.force_sorting = True PrettyJsonBaseCommand.force_sorting = True
PrettyJsonCommand.run(self, edit) PrettyJsonCommand.run(self, edit)
@ -228,14 +260,15 @@ class PrettyJsonAndSortCommand(PrettyJsonCommand, sublime_plugin.TextCommand):
class UnPrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand): class UnPrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
""" Compress/minify JSON - it makes json as one-liner """ """ Compress/minify JSON - it makes json as one-liner """
def run(self, edit): def run(self, edit):
self.view.erase_regions('json_errors') self.view.erase_regions("json_errors")
for region in self.view.sel(): regions = self.view.sel()
for region in regions:
selected_entire_file = False selected_entire_file = False
if region.empty() and len(regions) > 1:
# If no selection, use the entire file as the selection continue
if region.empty() and s.get("use_entire_file_if_no_selection", True): elif region.empty() and s.get("use_entire_file_if_no_selection", True):
selection = sublime.Region(0, self.view.size()) selection = sublime.Region(0, self.view.size())
selected_entire_file = True selected_entire_file = True
else: else:
@ -256,13 +289,17 @@ class JqPrettyJson(sublime_plugin.WindowCommand):
""" """
Allows work with ./jq Allows work with ./jq
""" """
def run(self): def run(self):
check_jq() check_jq()
if jq_exits: if jq_exits:
self.window.show_input_panel("Enter ./jq filter expression", ".", self.window.show_input_panel(
self.done, None, None) "Enter ./jq filter expression", ".", self.done, None, None
)
else: else:
sublime.status_message('./jq tool is not available on your system. http://stedolan.github.io/jq') sublime.status_message(
"./jq tool is not available on your system. http://stedolan.github.io/jq"
)
def get_content(self): def get_content(self):
""" returns content of active view or selected region """ """ returns content of active view or selected region """
@ -278,18 +315,20 @@ class JqPrettyJson(sublime_plugin.WindowCommand):
def done(self, query): def done(self, query):
try: try:
p = subprocess.Popen(["jq", query], p = subprocess.Popen(
stdout=subprocess.PIPE, ["jq", query],
stderr=subprocess.PIPE, stdout=subprocess.PIPE,
stdin=subprocess.PIPE) stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
)
raw_json = self.get_content() raw_json = self.get_content()
if SUBLIME_MAJOR_VERSION < 3: if SUBLIME_MAJOR_VERSION < 3:
if sys.platform != 'win32': if sys.platform != "win32":
out, err = p.communicate(bytes(raw_json)) out, err = p.communicate(bytes(raw_json))
else: else:
out, err = p.communicate(unicode(raw_json).encode('utf-8')) out, err = p.communicate(unicode(raw_json).encode("utf-8"))
else: else:
out, err = p.communicate(bytes(raw_json, "utf-8")) out, err = p.communicate(bytes(raw_json, "utf-8"))
output = out.decode("UTF-8").replace(os.linesep, "\n").strip() output = out.decode("UTF-8").replace(os.linesep, "\n").strip()
@ -307,9 +346,10 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
""" """
converts Json to XML converts Json to XML
""" """
def run(self, edit): def run(self, edit):
""" overwriting base class run function to remove intent """ """ overwriting base class run function to remove intent """
self.view.erase_regions('json_errors') self.view.erase_regions("json_errors")
for region in self.view.sel(): for region in self.view.sel():
selected_entire_file = False selected_entire_file = False
@ -341,7 +381,9 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
# for some reason python 2.6 shipped with ST2 # for some reason python 2.6 shipped with ST2
# does not have pyexpat # does not have pyexpat
if SUBLIME_MAJOR_VERSION >= 3: if SUBLIME_MAJOR_VERSION >= 3:
xml_string = minidom.parseString(xml_string).toprettyxml(encoding="UTF-8") xml_string = minidom.parseString(xml_string).toprettyxml(
encoding="UTF-8"
)
if type(xml_string) is bytes: if type(xml_string) is bytes:
xml_string = xml_string.decode("utf-8") xml_string = xml_string.decode("utf-8")
@ -356,14 +398,14 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
def indent_for_26(self, elem, level=0): def indent_for_26(self, elem, level=0):
""" intent of ElementTree in case it's py26 without minidom/pyexpat """ """ intent of ElementTree in case it's py26 without minidom/pyexpat """
i = "\n" + level*" " i = "\n" + level * " "
if len(elem): if len(elem):
if not elem.text or not elem.text.strip(): if not elem.text or not elem.text.strip():
elem.text = i + " " elem.text = i + " "
if not elem.tail or not elem.tail.strip(): if not elem.tail or not elem.tail.strip():
elem.tail = i elem.tail = i
for elem in elem: for elem in elem:
self.indent_for_26(elem, level+1) self.indent_for_26(elem, level + 1)
if not elem.tail or not elem.tail.strip(): if not elem.tail or not elem.tail.strip():
elem.tail = i elem.tail = i
else: else:
@ -377,12 +419,12 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
e = ElementTree.Element(i) e = ElementTree.Element(i)
element.append(self.traverse(e, json_dict[i])) element.append(self.traverse(e, json_dict[i]))
elif type(json_dict) is list: elif type(json_dict) is list:
e_items = ElementTree.Element('items') e_items = ElementTree.Element("items")
for i in json_dict: for i in json_dict:
e_items.append(self.traverse(ElementTree.Element('item'), i)) e_items.append(self.traverse(ElementTree.Element("item"), i))
element.append(e_items) element.append(e_items)
else: else:
element.set('value', str(json_dict)) element.set("value", str(json_dict))
return element return element
@ -392,7 +434,7 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
class JqPrettyJsonOut(sublime_plugin.TextCommand): class JqPrettyJsonOut(sublime_plugin.TextCommand):
def run(self, edit, jq_output=''): def run(self, edit, jq_output=""):
self.view.insert(edit, 0, jq_output) self.view.insert(edit, 0, jq_output)
@ -405,7 +447,7 @@ class PrettyJsonGotoSymbolCommand(PrettyJsonBaseCommand, sublime_plugin.TextComm
try: try:
json_data = self.json_loads(content) json_data = self.json_loads(content)
self.generate_items(json_data, '') self.generate_items(json_data, "")
sublime.active_window().show_quick_panel(self.items, self.goto) sublime.active_window().show_quick_panel(self.items, self.goto)
except Exception: except Exception:
self.show_exception() self.show_exception()
@ -413,14 +455,14 @@ class PrettyJsonGotoSymbolCommand(PrettyJsonBaseCommand, sublime_plugin.TextComm
def generate_items(self, json_data, root_key): def generate_items(self, json_data, root_key):
if isinstance(json_data, OrderedDict): if isinstance(json_data, OrderedDict):
for key in json_data: for key in json_data:
new_key_name = root_key + '.' + key new_key_name = root_key + "." + key
self.items.append('%s' % new_key_name) self.items.append("%s" % new_key_name)
self.goto_items.append('"%s"' % key) self.goto_items.append('"%s"' % key)
self.generate_items(json_data[key], new_key_name) self.generate_items(json_data[key], new_key_name)
elif isinstance(json_data, list): elif isinstance(json_data, list):
for index, item in enumerate(json_data): for index, item in enumerate(json_data):
if isinstance(item, basestring): if isinstance(item, basestring):
self.items.append('%s' % root_key + '.' + item) self.items.append("%s" % root_key + "." + item)
self.goto_items.append('"%s"' % item) self.goto_items.append('"%s"' % item)
def goto(self, pos): def goto(self, pos):