fix: formatting and logic

remove: Sublime Text 2 Support
fix: JSON syntax package location for ST4
fix: empty region logic
This commit is contained in:
TheSecEng 2020-04-14 14:10:38 -04:00
parent 46cfff357e
commit 6f190adf62
No known key found for this signature in database
GPG Key ID: A7C3BA459E8C5C4E
1 changed files with 131 additions and 96 deletions

View File

@ -1,57 +1,62 @@
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
from . import simplejson as json
from .simplejson import OrderedDict
try: try:
basestring basestring
except NameError: except NameError:
basestring = str basestring = str
try:
# python 3 / Sublime Text 3
from . import simplejson as json
from .simplejson import OrderedDict
except ValueError:
# python 2 / Sublime Text 2
import simplejson as json
from simplejson import OrderedDict
SUBLIME_MAJOR_VERSION = int(sublime.version()) / 1000 SUBLIME_MAJOR_VERSION = int(sublime.version()) / 1000
jq_exits = False xml_syntax = "Packages/XML/XML.tmLanguage"
json_syntax = "Packages/JSON/JSON.tmLanguage"
if SUBLIME_MAJOR_VERSION < 4:
json_syntax = "Packages/JavaScript/JSON.tmLanguage"
jq_exists = 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_exists
global jq_init global jq_init
if not jq_init: if not jq_init:
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_exists = True
except OSError: except OSError:
os_exception = sys.exc_info()[1] os_exception = sys.exc_info()[1]
print(str(os_exception)) print(str(os_exception))
jq_exits = False jq_exists = False
s = sublime.load_settings("Pretty JSON.sublime-settings") s = sublime.load_settings("Pretty JSON.sublime-settings")
@ -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)
@ -111,16 +118,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:
@ -128,48 +137,60 @@ 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]
sublime.status_message(str(exc)) sublime.status_message(str(exc))
self.highlight_error(str(exc)) self.highlight_error(str(exc))
def change_syntax(self): def syntax_to_json(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(json_syntax)
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.status_message("JSON is Valid")
except Exception: except Exception:
self.show_exception() self.show_exception()
sublime.message_dialog("Invalid JSON") sublime.message_dialog("Invalid JSON")
@ -178,14 +199,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:
@ -197,19 +219,21 @@ class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
self.view.replace(edit, selection, self.json_dumps(obj)) self.view.replace(edit, selection, self.json_dumps(obj))
if selected_entire_file: if selected_entire_file:
self.change_syntax() self.syntax_to_json()
except Exception: except Exception:
amount_of_single_quotes = re.findall(r"(\'[^\']+\'?)", selection_text) amount_of_single_quotes = re.findall(r"(\'[^\']+\'?)", selection_text)
amount_of_double_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(
r"(?:\'([^\']+)\'?)", r'"\1"', selection_text
)
obj = self.json_loads(selection_text_modified) obj = self.json_loads(selection_text_modified)
self.view.replace(edit, selection, self.json_dumps(obj)) self.view.replace(edit, selection, self.json_dumps(obj))
if selected_entire_file: if selected_entire_file:
self.change_syntax() self.syntax_to_json()
else: else:
self.show_exception() self.show_exception()
@ -217,6 +241,7 @@ class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
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)
@ -226,8 +251,9 @@ 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(): for region in self.view.sel():
selected_entire_file = False selected_entire_file = False
@ -244,7 +270,7 @@ class UnPrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
self.view.replace(edit, selection, self.json_dumps_minified(obj)) self.view.replace(edit, selection, self.json_dumps_minified(obj))
if selected_entire_file: if selected_entire_file:
self.change_syntax() self.syntax_to_xml()
except Exception: except Exception:
self.show_exception() self.show_exception()
@ -254,13 +280,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_exists:
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 """
@ -276,25 +306,27 @@ 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").strip() output = out.decode("UTF-8").strip()
if output: if output:
view = self.window.new_file() view = self.window.new_file()
view.run_command("jq_pretty_json_out", {"jq_output": output}) view.run_command("jq_pretty_json_out", {"jq_output": output})
view.set_syntax_file("Packages/JavaScript/JSON.tmLanguage") view.set_syntax_file(json_syntax)
except OSError: except OSError:
exc = sys.exc_info()[1] exc = sys.exc_info()[1]
@ -305,9 +337,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
@ -339,7 +372,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")
@ -347,21 +382,21 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand):
self.view.replace(edit, selection, xml_string) self.view.replace(edit, selection, xml_string)
if selected_entire_file: if selected_entire_file:
self.change_syntax() self.syntax_to_xml()
except Exception: except Exception:
self.show_exception() self.show_exception()
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:
@ -375,22 +410,22 @@ 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
def change_syntax(self): def syntax_to_xml(self):
""" change syntax to xml """ """ change syntax to xml """
self.view.set_syntax_file("Packages/XML/XML.tmLanguage") self.view.set_syntax_file(xml_syntax)
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)
@ -403,7 +438,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()
@ -411,14 +446,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):