From d42fd51f357e74907dacc9197d658ce17e41b098 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 21 Mar 2020 15:10:07 +0000 Subject: [PATCH] Support c-style comments in JSON Debate rages about whether JSON should have comments. The specification says it shouldn't but the author of JSON suggested that if you want to use JSON for configuration, then pipe it through jsmin before parsing. So that's what we do, using a tiny JSON minifier from https://github.com/getify/JSON.minify/tree/python Closes #135 --- README.md | 5 + docs/configuration.md | 24 ++++- install_gadget.py | 3 +- python3/vimspector/debug_session.py | 6 +- python3/vimspector/installer.py | 1 + python3/vimspector/vendor/json_minify.py | 97 +++++++++++++++++++ .../python/simple_python/.vimspector.json | 3 +- tox.ini | 1 + 8 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 python3/vimspector/vendor/json_minify.py diff --git a/README.md b/README.md index 5170aec..c77b22b 100644 --- a/README.md +++ b/README.md @@ -609,6 +609,11 @@ the Getting Started section of the [Vimspector website][website]. For full explanation, including how to use variables, substitutions and how to specify exception breakpoints, see [the docs][vimspector-ref]. +The JSON configuration file allows C-style comments: + +* `// comment to end of line ...` +* `/* inline comment ... */` + Current tested with the following debug adapters. ## C, C++, Rust, etc. diff --git a/docs/configuration.md b/docs/configuration.md index 67e7665..4f67739 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -110,14 +110,15 @@ But for now, consider the following example snippet: { "configurations": { "example-debug-configuration": { + // This is a single-line comment explaining the purpose "adapter": "example-adapter-name", "variables": { - "SecretToken": { + "SecretToken": { // Variables should start with upper-case letters "shell" : [ "cat", "${HOME}/.secret_token" ] } }, "configuration": { - "request": "launch", + "request": "launch" /* or it could be "attach" */, "program": [ "${fileBasenameNoExtension}", "-c", "configuration_file.cfg", @@ -364,6 +365,25 @@ The following variables are provided: * `${fileExtname}` - the current opened file's extension * `${cwd}` - the current working directory of the active window on launch +## Appendix: Configuration file format + +The configuration files are text files which must be UTF-8 encoded. They +contain a single JSON object, along with optional comments. + +Comments are "c-style", i.e.: + +* `// single line comment ...` +* `/* inline comment */` + +There is much debate about whether JSON files should contain comments. I have +added them because they are useful in the context of configuration files. +Unforutnately this may mean your editor doesn't like them (they are strictly +invalid JSON) so it's up to you if you use them. + +Technically, Vimspector uses [JSON +minify](https://github.com/getify/JSON.minify) to strip comments before parsing +the JSON. + ## Appendix: Editor configuration If you would like some assistance with writing the JSON files, and your editor diff --git a/install_gadget.py b/install_gadget.py index 4ecc4b6..5d474c4 100755 --- a/install_gadget.py +++ b/install_gadget.py @@ -36,6 +36,7 @@ sys.path.insert( 1, os.path.join( os.path.dirname( __file__ ), 'python3' ) ) from vimspector import install, installer +from vimspector.vendor.json_minify import minify GADGETS = { 'vscode-cpptools': { @@ -634,7 +635,7 @@ for custom_file_name in functools.reduce( operator.add, args.custom_gadget_file, custom_files ): with open( custom_file_name, 'r' ) as custom_file: - CUSTOM_GADGETS.update( json.load( custom_file ) ) + CUSTOM_GADGETS.update( json.loads( minify( custom_file.read() ) ) ) failed = [] diff --git a/python3/vimspector/debug_session.py b/python3/vimspector/debug_session.py index 8212ab1..fb03f41 100644 --- a/python3/vimspector/debug_session.py +++ b/python3/vimspector/debug_session.py @@ -31,6 +31,7 @@ from vimspector import ( breakpoints, stack_trace, utils, variables ) +from vimspector.vendor.json_minify import minify VIMSPECTOR_HOME = os.path.abspath( os.path.join( os.path.dirname( __file__ ), '..', @@ -96,7 +97,8 @@ class DebugSession( object ): continue with open( gadget_config_file, 'r' ) as f: - adapters.update( json.load( f ).get( 'adapters' ) or {} ) + a = json.loads( minify( f.read() ) ).get( 'adapters' ) or {} + adapters.update( a ) for launch_config_file in PathsToAllConfigFiles( VIMSPECTOR_HOME, current_file, @@ -106,7 +108,7 @@ class DebugSession( object ): continue with open( launch_config_file, 'r' ) as f: - database = json.load( f ) + database = json.loads( minify( f.read() ) ) adapters.update( database.get( 'adapters' ) or {} ) configurations.update( database.get( 'configurations' or {} ) ) diff --git a/python3/vimspector/installer.py b/python3/vimspector/installer.py index 1553098..bca5084 100644 --- a/python3/vimspector/installer.py +++ b/python3/vimspector/installer.py @@ -221,6 +221,7 @@ def MakeSymlink( in_folder, link, pointing_to ): else: os.symlink( pointing_to_relative, link_path ) + def CloneRepoTo( url, ref, destination ): RemoveIfExists( destination ) git_in_repo = [ 'git', '-C', destination ] diff --git a/python3/vimspector/vendor/json_minify.py b/python3/vimspector/vendor/json_minify.py new file mode 100644 index 0000000..6b78901 --- /dev/null +++ b/python3/vimspector/vendor/json_minify.py @@ -0,0 +1,97 @@ +# Copyright © 2020 Kyle Simpson +# +# 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. +# + +"""A port of the `JSON-minify` utility to the Python language. + +Based on JSON.minify.js: https://github.com/getify/JSON.minify + +Contributers: + - Gerald Storer + - Contributed original version + - Felipe Machado + - Performance optimization + - Pradyun S. Gedam + - Conditions and variable names changed + - Reformatted tests and moved to separate file + - Made into a PyPI Package +""" + +import re + + +# Vimspector modification: strip_space defaults to False; we don't actually want +# to minify - we just want to strip comments. +def minify(string, strip_space=False): + tokenizer = re.compile('"|(/\*)|(\*/)|(//)|\n|\r') + end_slashes_re = re.compile(r'(\\)*$') + + in_string = False + in_multi = False + in_single = False + + new_str = [] + index = 0 + + for match in re.finditer(tokenizer, string): + + if not (in_multi or in_single): + tmp = string[index:match.start()] + if not in_string and strip_space: + # replace white space as defined in standard + tmp = re.sub('[ \t\n\r]+', '', tmp) + new_str.append(tmp) + elif not strip_space: + # Replace comments with white space so that the JSON parser reports + # the correct column numbers on parsing errors. + new_str.append(' ' * (match.start() - index)) + + index = match.end() + val = match.group() + + if val == '"' and not (in_multi or in_single): + escaped = end_slashes_re.search(string, 0, match.start()) + + # start of string or unescaped quote character to end string + if not in_string or (escaped is None or len(escaped.group()) % 2 == 0): # noqa + in_string = not in_string + index -= 1 # include " character in next catch + elif not (in_string or in_multi or in_single): + if val == '/*': + in_multi = True + elif val == '//': + in_single = True + elif val == '*/' and in_multi and not (in_string or in_single): + in_multi = False + if not strip_space: + new_str.append(' ' * len(val)) + elif val in '\r\n' and not (in_multi or in_string) and in_single: + in_single = False + elif not ((in_multi or in_single) or (val in ' \r\n\t' and strip_space)): # noqa + new_str.append(val) + + if not strip_space: + if val in '\r\n': + new_str.append(val) + elif in_multi or in_single: + new_str.append(' ' * len(val)) + + new_str.append(string[index:]) + return ''.join(new_str) diff --git a/support/test/python/simple_python/.vimspector.json b/support/test/python/simple_python/.vimspector.json index 5cb036c..f585ca6 100644 --- a/support/test/python/simple_python/.vimspector.json +++ b/support/test/python/simple_python/.vimspector.json @@ -1,7 +1,8 @@ { "configurations": { + // This is a comment. "run legacy vscode-python": { - "adapter": "vscode-python", + "adapter": "vscode-python" /* coment goes here too */, "configuration": { "request": "launch", "type": "python", diff --git a/tox.ini b/tox.ini index dbd9626..7a6c06e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,3 +1,4 @@ [flake8] ignore = E111,E114,E121,E125,E126,E127,E128,E129,E131,E133,E201,E202,E203,E211,E221,E222,E241,E251,E261,E303,E402,W503,W504 max-line-length = 80 +exclude = python3/vimspector/vendor