228 lines
8.7 KiB
Python
228 lines
8.7 KiB
Python
import json
|
|
import os
|
|
import plistlib
|
|
import xml.parsers.expat
|
|
import uuid
|
|
import re
|
|
|
|
import sublime_plugin
|
|
|
|
from sublime_lib.path import root_at_packages
|
|
from sublime_lib.view import (in_one_edit, coorded_substr)
|
|
|
|
|
|
JSON_TMLANGUAGE_SYNTAX = 'Packages/AAAPackageDev/Support/Sublime JSON Syntax Definition.tmLanguage'
|
|
|
|
|
|
# XXX: Move this to a txt file. Let user define his own under User too.
|
|
def get_syntax_def_boilerplate():
|
|
JSON_TEMPLATE = """{ "name": "${1:Syntax Name}",
|
|
"scopeName": "source.${2:syntax_name}",
|
|
"fileTypes": ["$3"],
|
|
"patterns": [$0
|
|
],
|
|
"uuid": "%s"
|
|
}"""
|
|
|
|
actual_tmpl = JSON_TEMPLATE % str(uuid.uuid4())
|
|
return actual_tmpl
|
|
|
|
|
|
class NewSyntaxDefCommand(sublime_plugin.WindowCommand):
|
|
"""Creates a new syntax definition file for Sublime Text in JSON format
|
|
with some boilerplate text.
|
|
"""
|
|
def run(self):
|
|
target = self.window.new_file()
|
|
|
|
target.settings().set('default_dir', root_at_packages('User'))
|
|
target.settings().set('syntax', JSON_TMLANGUAGE_SYNTAX)
|
|
|
|
target.run_command('insert_snippet',
|
|
{'contents': get_syntax_def_boilerplate()})
|
|
|
|
|
|
class NewSyntaxDefFromBufferCommand(sublime_plugin.TextCommand):
|
|
"""Inserts boilerplate text for syntax defs into current view.
|
|
"""
|
|
def is_enabled(self):
|
|
# Don't mess up a non-empty buffer.
|
|
return self.view.size() == 0
|
|
|
|
def run(self, edit):
|
|
self.view.settings().set('default_dir', root_at_packages('User'))
|
|
self.view.settings().set('syntax', JSON_TMLANGUAGE_SYNTAX)
|
|
|
|
with in_one_edit(self.view):
|
|
self.view.run_command('insert_snippet',
|
|
{'contents': get_syntax_def_boilerplate()})
|
|
|
|
|
|
# XXX: Why is this a WindowCommand? Wouldn't it work otherwise in build-systems?
|
|
class JsonToPlistCommand(sublime_plugin.WindowCommand):
|
|
"""
|
|
Parses ``.json`` files and writes them into corresponding ``.plist``.
|
|
Source file ``.JSON-XXX`` will generate a plist file named ``.XXX``.
|
|
Pretty useful with ``.JSON-tmLanguage`` but works with almost any other name.
|
|
"""
|
|
ext_regexp = re.compile(r'\.json(?:-([^\.]+))?$', flags=re.I)
|
|
|
|
def is_enabled(self):
|
|
v = self.window.active_view()
|
|
return (v and (self.get_file_ext(v.file_name()) is not None))
|
|
|
|
def get_file_ext(self, file_name):
|
|
ret = self.ext_regexp.search(file_name)
|
|
if ret is None:
|
|
return None
|
|
return '.' + (ret.group(1) or 'plist')
|
|
|
|
def run(self, **kwargs):
|
|
v = self.window.active_view()
|
|
path = v.file_name()
|
|
ext = self.get_file_ext(path)
|
|
if not os.path.exists(path):
|
|
print "[AAAPackageDev] File does not exists. (%s)" % path
|
|
return
|
|
if ext is None:
|
|
print "[AAAPackageDev] Not a valid JSON file, please check extension. (%s)" % path
|
|
return
|
|
|
|
self.json_to_plist(path, ext)
|
|
|
|
def json_to_plist(self, json_file, new_ext):
|
|
path, fname = os.path.split(json_file)
|
|
fbase, old_ext = os.path.splitext(fname)
|
|
file_regex = r"Error parsing JSON:\s+'(.*?)'\s+.*?\s+line\s+(\d+)\s+column\s+(\d+)"
|
|
|
|
if not hasattr(self, 'output_view'):
|
|
# Try not to call get_output_panel until the regexes are assigned
|
|
self.output_view = self.window.get_output_panel("aaa_package_dev")
|
|
|
|
# FIXME: Can't get error navigation to work.
|
|
self.output_view.settings().set("result_file_regex", file_regex)
|
|
self.output_view.settings().set("result_base_dir", path)
|
|
|
|
# Call get_output_panel a second time after assigning the above
|
|
# settings, so that it'll be picked up as a result buffer
|
|
self.window.get_output_panel("aaa_package_dev")
|
|
|
|
with in_one_edit(self.output_view) as edit:
|
|
try:
|
|
with open(json_file) as json_content:
|
|
tmlanguage = json.load(json_content)
|
|
except ValueError, e:
|
|
self.output_view.insert(edit, 0, "Error parsing JSON: '%s' %s" % (json_file, str(e)))
|
|
else:
|
|
target = os.path.join(path, fbase + new_ext)
|
|
self.output_view.insert(edit, 0, "Writing plist... (%s)" % target)
|
|
plistlib.writePlist(tmlanguage, target)
|
|
|
|
self.window.run_command("show_panel", {"panel": "output.aaa_package_dev"})
|
|
|
|
|
|
class PlistToJsonCommand(sublime_plugin.WindowCommand):
|
|
"""
|
|
Parses ``.plist`` files and writes them into corresponding ``.json``.
|
|
A source file has ``<!DOCTYPE plist`` at the beginning of the first two lines
|
|
and uses the extension ``.XXX`` will generate a json file named ``.JSON-XXX``.
|
|
This is pretty much the reverse of the json_to_plist command.
|
|
|
|
Accepts parameters for ``json.dump()`` as arguments which override the default
|
|
settings. Use this only if you know what you're doing!
|
|
|
|
Please note:
|
|
- Be careful with non-native JSON data types like <date> or <data>, they
|
|
result in unpredictable behavior!
|
|
- Floats of <float> or <real> tend to lose precision when being cast into Python
|
|
data types. ``32.1`` (plist) will likely result in ``32.100000000000001`` (json).
|
|
"""
|
|
DOCTYPE = "<!DOCTYPE plist"
|
|
|
|
def is_enabled(self):
|
|
v = self.window.active_view()
|
|
return (self.get_file_ext(v) is not None)
|
|
|
|
def get_file_ext(self, v):
|
|
if not v:
|
|
return None
|
|
fn = v.file_name()
|
|
ext = os.path.splitext(fn)[1]
|
|
|
|
# check for ".plist" ext
|
|
if ext == ".plist":
|
|
return ".json"
|
|
|
|
# check for plist namespace (doctype) in first two lines
|
|
for i in range(2):
|
|
text = coorded_substr(v, (i, 0), (i, len(self.DOCTYPE)))
|
|
print "i: %d, text: %s" % (i, text)
|
|
if text == self.DOCTYPE:
|
|
return ".JSON-" + ext[1:]
|
|
|
|
# not a plist file, 99% likely
|
|
return None
|
|
|
|
def run(self, **kwargs):
|
|
v = self.window.active_view()
|
|
path = v.file_name()
|
|
if not os.path.exists(path):
|
|
print "[AAAPackageDev] File does not exists. (%s)" % path
|
|
return
|
|
new_ext = self.get_file_ext(v)
|
|
if new_ext is None:
|
|
print "[AAAPackageDev] Not a valid Property List. (%s)" % path
|
|
return
|
|
|
|
self.plist_to_json(path, new_ext, **kwargs)
|
|
|
|
def plist_to_json(self, plist_file, new_ext, **kwargs):
|
|
path, fname = os.path.split(plist_file)
|
|
fbase, old_ext = os.path.splitext(fname)
|
|
target = os.path.join(path, fbase + new_ext)
|
|
|
|
debug_base = "Error parsing Plist (%s): %s; line (%s) column (%s)"
|
|
file_regex = re.escape(debug_base).replace(r'\%', '%') % (r'(.*?)', r'.*?', r'(\d+)', r'(\d+)')
|
|
|
|
# Define default parameters
|
|
json_params = dict(
|
|
skipkeys=True,
|
|
check_circular=False, # there won't be references here
|
|
indent=4,
|
|
sort_keys=True
|
|
)
|
|
json_params.update(kwargs)
|
|
print json_params
|
|
|
|
# Handle the output
|
|
if not hasattr(self, 'output_view'):
|
|
# Try not to call get_output_panel until the regexes are assigned
|
|
self.output_view = self.window.get_output_panel("aaa_package_dev")
|
|
|
|
# FIXME: Can't get error navigation to work.
|
|
self.output_view.settings().set("result_file_regex", file_regex)
|
|
self.output_view.settings().set("result_base_dir", path)
|
|
|
|
# Call get_output_panel a second time after assigning the above
|
|
# settings, so that it'll be picked up as a result buffer
|
|
self.window.get_output_panel("aaa_package_dev")
|
|
|
|
with in_one_edit(self.output_view) as edit:
|
|
try:
|
|
pl = plistlib.readPlist(plist_file)
|
|
except xml.parsers.expat.ExpatError, e:
|
|
self.output_view.insert(edit, 0,
|
|
debug_base % (plist_file,
|
|
xml.parsers.expat.ErrorString(e.code),
|
|
e.lineno,
|
|
e.offset)
|
|
)
|
|
else:
|
|
self.output_view.insert(edit, 0, "Writing json... (%s)" % target)
|
|
try:
|
|
with open(target, "w") as f:
|
|
json.dump(pl, f, **json_params)
|
|
except Exception, e:
|
|
print "[AAAPackageDev] Error writing json: %s" % str(e)
|
|
|
|
self.window.run_command("show_panel", {"panel": "output.aaa_package_dev"})
|