├── jsn.cmd ├── .gitignore ├── .gitattributes ├── import_dir └── test.jsn ├── jsn ├── import.jsn ├── grammar └── jsn │ ├── package.nls.json │ ├── package.json │ ├── language-configuration.json │ └── syntaxes │ └── JSN.tmLanguage.json ├── pypi ├── pyproject.toml └── setup.py ├── .github └── workflows │ ├── tests.yaml │ ├── pypi.yaml │ └── release.yaml ├── license ├── example.jsn ├── example.json ├── readme.md └── jsn.py /jsn.cmd: -------------------------------------------------------------------------------- 1 | py -3 %~dp0/jsn.py %* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .DS_Store 3 | .idea -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.jsn linguist-language=jsonnet 2 | -------------------------------------------------------------------------------- /import_dir/test.jsn: -------------------------------------------------------------------------------- 1 | import import.jsn 2 | { 3 | test_jsn: "included from sub directory" 4 | } -------------------------------------------------------------------------------- /jsn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 3 | python3 "$SCRIPTPATH"/jsn.py "$@" -------------------------------------------------------------------------------- /import.jsn: -------------------------------------------------------------------------------- 1 | { 2 | another_file: { 3 | jsn: "can import content from other files" 4 | } 5 | } -------------------------------------------------------------------------------- /grammar/jsn/package.nls.json: -------------------------------------------------------------------------------- 1 | {"displayName":"JSN","description":"Provides syntax highlighting & bracket matching in JSN files."} -------------------------------------------------------------------------------- /pypi/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # These are the assumed default build requirements from pip: 3 | # https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support 4 | requires = ["setuptools>=40.8.0", "wheel"] 5 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | name: tests 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: checkout 8 | uses: actions/checkout@v2 9 | with: 10 | submodules: "recursive" 11 | - name: pip 12 | run: | 13 | pip install codecov 14 | pip install coverage 15 | - name: test 16 | run: | 17 | coverage run jsn.py -i example.jsn -o test.json -I import_dir 18 | codecov -------------------------------------------------------------------------------- /grammar/jsn/package.json: -------------------------------------------------------------------------------- 1 | {"name":"jsn","displayName":"%displayName%","description":"%description%","version":"1.0.0","publisher":"vscode","license":"MIT","engines":{"vscode":"0.10.x"},"scripts":{"update-grammar":"node ./build/update-grammars.js"},"contributes":{"languages":[{"id":"jsn","aliases":["JSN","jsn"],"extensions":[".jsn"],"filenames":["composer.lock",".watchmanconfig"],"mimetypes":["application/json","application/manifest+json"],"configuration":"./language-configuration.json"}],"grammars":[{"language":"jsn","scopeName":"source.jsn","path":"./syntaxes/JSN.tmLanguage.json"}]}} -------------------------------------------------------------------------------- /grammar/jsn/language-configuration.json: -------------------------------------------------------------------------------- 1 | {"comments":{"lineComment":"//","blockComment":["/*","*/"]},"brackets":[["{","}"],["[","]"]],"autoClosingPairs":[{"open":"{","close":"}","notIn":["string"]},{"open":"[","close":"]","notIn":["string"]},{"open":"(","close":")","notIn":["string"]},{"open":"'","close":"'","notIn":["string"]},{"open":"\"","close":"\"","notIn":["string","comment"]},{"open":"`","close":"`","notIn":["string","comment"]}],"indentationRules":{"increaseIndentPattern":"({+(?=((\\\\.|[^\"\\\\])*\"(\\\\.|[^\"\\\\])*\")*[^\"}]*)$)|(\\[+(?=((\\\\.|[^\"\\\\])*\"(\\\\.|[^\"\\\\])*\")*[^\"\\]]*)$)","decreaseIndentPattern":"^\\s*[}\\]],?\\s*$"}} -------------------------------------------------------------------------------- /pypi/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("readme.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="jsn", 8 | version="1.3.4", 9 | author="Alex Dixon", 10 | author_email="alexandercdixon@gmail.com", 11 | description="A relaxed, user-friendly json-like data format.", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/polymonster/jsn", 15 | packages=setuptools.find_packages(), 16 | python_requires='>=2.6', 17 | classifiers=[ 18 | 'Programming Language :: Python :: 2', 19 | "Programming Language :: Python :: 3", 20 | "Development Status :: 4 - Beta", 21 | "License :: OSI Approved :: MIT License", 22 | "Operating System :: OS Independent" 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alex Dixon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/pypi.yaml: -------------------------------------------------------------------------------- 1 | name: publish-pypi 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | build-n-publish: 8 | name: Build and publish to PyPi 9 | runs-on: ubuntu-18.04 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Set up Python 3.7 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: 3.7 16 | - name: bump_version 17 | run: | 18 | export REFS_VERSION=${{ github.ref }} 19 | export VERSION=$(echo $REFS_VERSION| cut -d'/' -f 3) 20 | echo $VERSION 21 | sed -i -E "1,/[0-9]+.[0-9]+.[0-9]+/ s/[0-9]+.[0-9]+.[0-9]+/$VERSION/g" pypi/setup.py 22 | git config user.name github-actions 23 | git config user.email github-actions@github.com 24 | git add . 25 | git commit -m "- bump pypi/setup.py version to $VERSION for publish" 26 | git push origin HEAD:master 27 | - name: Install pep517 28 | run: >- 29 | python -m 30 | pip install 31 | pep517 32 | --user 33 | - name: Build a binary wheel and a source tarball 34 | run: | 35 | echo ${{github.event.ref}} 36 | cd pypi 37 | mkdir jsn 38 | cd .. 39 | cp jsn.py pypi/jsn/__init__.py 40 | cp readme.md pypi/readme.md 41 | cp license pypi/license 42 | cd pypi 43 | python -m pep517.build --source --binary --out-dir dist/ . 44 | - name: Publish distribution 📦 to Test PyPI 45 | if: startsWith(github.event.ref, 'refs/heads/pypi-test') 46 | uses: pypa/gh-action-pypi-publish@master 47 | with: 48 | password: ${{ secrets.test_pypi_password }} 49 | repository_url: https://test.pypi.org/legacy/ 50 | packages_dir: pypi/dist 51 | - name: Publish distribution 📦 to PyPI 52 | uses: pypa/gh-action-pypi-publish@master 53 | with: 54 | password: ${{ secrets.pypi_password }} 55 | packages_dir: pypi/dist 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*' 5 | name: build-release 6 | jobs: 7 | setup: 8 | name: create release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: create_release 12 | id: create_release 13 | uses: actions/create-release@v1 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | with: 17 | tag_name: ${{ github.ref }} 18 | release_name: Release ${{ github.ref }} 19 | draft: false 20 | prerelease: false 21 | outputs: 22 | upload_url: ${{ steps.create_release.outputs.upload_url }} 23 | windows: 24 | needs: setup 25 | name: windows 26 | runs-on: windows-latest 27 | steps: 28 | - name: checkout 29 | uses: actions/checkout@v2 30 | with: 31 | submodules: "recursive" 32 | - name: pyinstaller 33 | run: | 34 | pip install PyInstaller 35 | pip install requests 36 | - name: build 37 | run: pyinstaller jsn.py --onefile -i NONE --distpath build/dist/ --workpath build/work/ 38 | - name: zip 39 | run: Compress-Archive -Path build/dist/* -DestinationPath build/Windows-x64.zip 40 | - name: upload 41 | id: upload-release-asset 42 | uses: actions/upload-release-asset@v1 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | with: 46 | upload_url: ${{ needs.setup.outputs.upload_url }} 47 | asset_path: ./build/Windows-x64.zip 48 | asset_name: Windows-x64.zip 49 | asset_content_type: application/zip 50 | macos: 51 | needs: setup 52 | name: macos 53 | runs-on: macos-latest 54 | steps: 55 | - name: checkout 56 | uses: actions/checkout@v2 57 | with: 58 | submodules: "recursive" 59 | - name: pyinstaller 60 | uses: actions/setup-python@v5 61 | with: 62 | python-version: '3.9' 63 | cache: 'pip' 64 | - run: | 65 | pip3 install PyInstaller 66 | pip3 install requests 67 | - name: build 68 | run: python3 -m PyInstaller jsn.py -y --onefile -i NONE --distpath build/dist/ --workpath build/work/ 69 | - name: zip 70 | run: zip -rj build/macOS-x64.zip build/dist 71 | - name: upload 72 | id: upload-release-asset 73 | uses: actions/upload-release-asset@v1 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | with: 77 | upload_url: ${{ needs.setup.outputs.upload_url }} 78 | asset_path: ./build/macOS-x64.zip 79 | asset_name: macOS-x64.zip 80 | asset_content_type: application/zip 81 | linux: 82 | needs: setup 83 | name: linux 84 | runs-on: ubuntu-latest 85 | steps: 86 | - name: checkout 87 | uses: actions/checkout@v2 88 | with: 89 | submodules: "recursive" 90 | - name: pyinstaller 91 | run: | 92 | python3 -m pip install PyInstaller 93 | python3 -m pip install requests 94 | - name: build 95 | run: python3 -m PyInstaller jsn.py -y --onefile -i NONE --distpath build/dist/ --workpath build/work/ 96 | - name: zip 97 | run: zip -rj build/linux-x64.zip build/dist 98 | - name: upload 99 | id: upload-release-asset 100 | uses: actions/upload-release-asset@v1 101 | env: 102 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 103 | with: 104 | upload_url: ${{ needs.setup.outputs.upload_url }} 105 | asset_path: ./build/linux-x64.zip 106 | asset_name: linux-x64.zip 107 | asset_content_type: application/zip -------------------------------------------------------------------------------- /example.jsn: -------------------------------------------------------------------------------- 1 | import import.jsn 2 | import test.jsn 3 | { 4 | // sytax highlights quite nicely in most editors with c or c++ syntax 5 | 6 | // allow comments 7 | 8 | /* 9 | multi- 10 | line 11 | comments 12 | */ 13 | 14 | // tabs and any whitespace allowed 15 | 16 | // compatible with json 17 | "json": { 18 | "bool": true, 19 | "int": 1, 20 | "float": 1.0, 21 | "string": "yes", 22 | "array": [1, 2, 3] 23 | }, 24 | 25 | // compatible with json5 26 | json5: { 27 | unquoted: 'and you can quote me on that', 28 | single_quotes: 'I can use "double quotes" here', 29 | hexadecimal: 0xdecaf, 30 | line_breaks: "Look, Mom! No \ 31 | \\n's!", 32 | leading_decimal_point: .8675309, and_trailing: 8675309., 33 | positive_sign: +1, 34 | trailing_comma: 'in objects', and_in: ['arrays',], 35 | }, 36 | 37 | // jsn features 38 | jsn: { 39 | // ditch the quotes and commas! 40 | unquoted_string: without_whitespace // cannot contain whitespace or special chars (see str_test) 41 | unquoted: [strings, in, arrays] 42 | binary_literal: 0b10011 43 | bit_shifts: 1<<16 | 1<<8 44 | 45 | // mixed newline and commas 46 | object_members_separated_by_newline: { 47 | no_commas: yes 48 | with_new_lines: "if you like" 49 | but: "you can", still: "use them here" 50 | } 51 | arrays_elements_separated_by_newline:[ 52 | "no need for commas" 53 | "if you use newlines" 54 | "still", "separate", "using commas", "on the same line" 55 | ] 56 | 57 | // you can define variables to be re-used 58 | jsn_vars: { 59 | data: "path/to/data" 60 | var_str: "hello" 61 | var_int: 10 62 | } 63 | 64 | // evaluate variables with ${} inside quotes.. 65 | variable_data_path: "${data}/subdir" 66 | variable_int: "${var_int}" 67 | array_of_vars: ["${data}", "${var_str}"] 68 | 69 | // subobjects can be merged and inherited recursively see ** inheritence(jsn) 70 | base: "foo" 71 | sub_object: { 72 | one: "1" 73 | two: "2" 74 | nested_var: "${var_int}" // variable comes from outer scope. 75 | } 76 | 77 | // use in angled brackets to conditionally include or exclude keys 78 | // the platform specific keys will be merged into the base key if one exists 79 | platform: { 80 | base: "exists" 81 | } 82 | 83 | // useful for selecting platform specific paths and executables 84 | platform: { 85 | exe: "path/windows/program.exe" 86 | } 87 | 88 | platform: { 89 | exe: "path/mac/program" 90 | } 91 | } 92 | 93 | //** // add object name to inherit inside parenthesis 94 | inheritence(jsn): { 95 | // inheritance adds keys from 'jsn' object 96 | // .. 97 | 98 | // duplicated keys are overridden by the derived object 99 | base: "bar" 100 | 101 | // inheritance on sub-objects continues recursively 102 | sub_object: { 103 | three: "3" 104 | //.. 105 | } 106 | } 107 | 108 | // multiple and hierarchical inheritance 109 | objb: { b: "b" } 110 | 111 | multiple_inheritence(inheritence, objb): { 112 | // vars can also be shadowed / overriden.. 113 | jsn_vars: { 114 | data: "another/path/to/data" 115 | var_int: 22 116 | } 117 | 118 | c: "c" 119 | } 120 | 121 | xxx: { 122 | // some test cases 123 | empty_object: {} 124 | 125 | empty_array: [] 126 | 127 | array_of_objects:[ 128 | {object: 1, other: "value"} 129 | {object: 2, other: "value"} 130 | ] 131 | 132 | array_with_string_commas:[ 133 | "test,2", 134 | "test,3" 135 | ] 136 | 137 | nested_objects: { 138 | yes: true 139 | and: { 140 | deeper: nesting 141 | } 142 | } 143 | 144 | multi_type_arrays: [ 145 | 1, 146 | [2, 3] 147 | ] 148 | 149 | array_of_arrays:[ 150 | [hello, world] 151 | [goodbye, world] 152 | ] 153 | 154 | array_of_values:[ 155 | +255 156 | 0b11111111 157 | 0xff 158 | 1 << 1 159 | 1 << 2 | 1 160 | 1 << 2 | 1 161 | .255 162 | ] 163 | 164 | value: null 165 | array_of_null: [null, null, null] 166 | 167 | jsn_vars: { 168 | va: "path/to/data" 169 | vb: "hello" 170 | } 171 | array_of_array_vars: [ 172 | ["${va}", "${vb}"] 173 | ["${vb}", "non var"] 174 | ] 175 | 176 | multiple_vars: "${va}/${vb}.bin" 177 | 178 | q1: "small 'quotes' inside" 179 | q2: 'double "quotes" inside' 180 | q3: "double escaped \"quotes\" inside" 181 | } 182 | 183 | //** 184 | str_test: ":[{}]'+.,0b0x" // this tests ignoring special chars inside quotes 185 | } -------------------------------------------------------------------------------- /example.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "bool": true, 4 | "int": 1, 5 | "float": 1.0, 6 | "string": "yes", 7 | "array": [ 8 | 1, 9 | 2, 10 | 3 11 | ] 12 | }, 13 | "json5": { 14 | "unquoted": "and you can quote me on that", 15 | "single_quotes": "I can use \"doublequotes\" here", 16 | "hexadecimal": 912559, 17 | "line_breaks": "Look, Mom! No \\n's!", 18 | "leading_decimal_point": 0.8675309, 19 | "and_trailing": 8675309.0, 20 | "positive_sign": 1, 21 | "trailing_comma": "in objects", 22 | "and_in": [ 23 | "arrays" 24 | ] 25 | }, 26 | "jsn": { 27 | "unquoted_string": "without_whitespace", 28 | "unquoted": [ 29 | "strings", 30 | "in", 31 | "arrays" 32 | ], 33 | "binary_literal": 19, 34 | "bit_shifts": 65792, 35 | "object_members_separated_by_newline": { 36 | "no_commas": "yes", 37 | "with_new_lines": "if you like", 38 | "but": "you can", 39 | "still": "use them here" 40 | }, 41 | "arrays_elements_separated_by_newline": [ 42 | "no need for commas", 43 | "if you use newlines", 44 | "still", 45 | "separate", 46 | "using commas", 47 | "on the same line" 48 | ], 49 | "variable_data_path": "path/to/data/subdir", 50 | "variable_int": 10, 51 | "array_of_vars": [ 52 | "path/to/data", 53 | "hello" 54 | ], 55 | "base": "foo", 56 | "sub_object": { 57 | "one": "1", 58 | "two": "2", 59 | "nested_var": 10 60 | }, 61 | "platform": { 62 | "base": "exists", 63 | "exe": "path/mac/program" 64 | } 65 | }, 66 | "inheritence": { 67 | "base": "bar", 68 | "sub_object": { 69 | "three": "3", 70 | "one": "1", 71 | "two": "2", 72 | "nested_var": 10 73 | }, 74 | "unquoted_string": "without_whitespace", 75 | "unquoted": [ 76 | "strings", 77 | "in", 78 | "arrays" 79 | ], 80 | "binary_literal": 19, 81 | "bit_shifts": 65792, 82 | "object_members_separated_by_newline": { 83 | "no_commas": "yes", 84 | "with_new_lines": "if you like", 85 | "but": "you can", 86 | "still": "use them here" 87 | }, 88 | "arrays_elements_separated_by_newline": [ 89 | "no need for commas", 90 | "if you use newlines", 91 | "still", 92 | "separate", 93 | "using commas", 94 | "on the same line" 95 | ], 96 | "variable_data_path": "path/to/data/subdir", 97 | "variable_int": 10, 98 | "array_of_vars": [ 99 | "path/to/data", 100 | "hello" 101 | ], 102 | "platform": { 103 | "base": "exists", 104 | "exe": "path/mac/program" 105 | } 106 | }, 107 | "objb": { 108 | "b": "b" 109 | }, 110 | "multiple_inheritence": { 111 | "c": "c", 112 | "base": "bar", 113 | "sub_object": { 114 | "three": "3", 115 | "one": "1", 116 | "two": "2", 117 | "nested_var": 22 118 | }, 119 | "unquoted_string": "without_whitespace", 120 | "unquoted": [ 121 | "strings", 122 | "in", 123 | "arrays" 124 | ], 125 | "binary_literal": 19, 126 | "bit_shifts": 65792, 127 | "object_members_separated_by_newline": { 128 | "no_commas": "yes", 129 | "with_new_lines": "if you like", 130 | "but": "you can", 131 | "still": "use them here" 132 | }, 133 | "arrays_elements_separated_by_newline": [ 134 | "no need for commas", 135 | "if you use newlines", 136 | "still", 137 | "separate", 138 | "using commas", 139 | "on the same line" 140 | ], 141 | "variable_data_path": "another/path/to/data/subdir", 142 | "variable_int": 22, 143 | "array_of_vars": [ 144 | "another/path/to/data", 145 | "hello" 146 | ], 147 | "platform": { 148 | "base": "exists", 149 | "exe": "path/mac/program" 150 | }, 151 | "b": "b" 152 | }, 153 | "xxx": { 154 | "empty_object": {}, 155 | "empty_array": [], 156 | "array_of_objects": [ 157 | { 158 | "object": 1, 159 | "other": "value" 160 | }, 161 | { 162 | "object": 2, 163 | "other": "value" 164 | } 165 | ], 166 | "array_with_string_commas": [ 167 | "test,2", 168 | "test,3" 169 | ], 170 | "nested_objects": { 171 | "yes": true, 172 | "and": { 173 | "deeper": "nesting" 174 | } 175 | }, 176 | "multi_type_arrays": [ 177 | 1, 178 | [ 179 | 2, 180 | 3 181 | ] 182 | ], 183 | "array_of_arrays": [ 184 | [ 185 | "hello", 186 | "world" 187 | ], 188 | [ 189 | "goodbye", 190 | "world" 191 | ] 192 | ], 193 | "array_of_values": [ 194 | 255, 195 | 255, 196 | 255, 197 | 2, 198 | 5, 199 | 5, 200 | 0.255 201 | ], 202 | "array_of_array_vars": [ 203 | [ 204 | "path/to/data", 205 | "hello" 206 | ], 207 | [ 208 | "hello", 209 | "non var" 210 | ] 211 | ], 212 | "value": null, 213 | "array_of_null": [null, null, null], 214 | "multiple_vars": "path/to/data/hello.bin", 215 | "q1": "small 'quotes' inside", 216 | "q2": "double \"quotes\" inside", 217 | "q3": "double escaped \"quotes\" inside" 218 | }, 219 | "str_test": ":[{}]'+.,0b0x", 220 | "another_file": { 221 | "jsn": "can import content from other files" 222 | }, 223 | "test_jsn": "included from sub directory" 224 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # jsn 2 | [![tests](https://github.com/polymonster/jsn/actions/workflows/tests.yaml/badge.svg)](https://github.com/polymonster/jsn/actions/workflows/tests.yaml) 3 | [![build-release](https://github.com/polymonster/jsn/actions/workflows/release.yaml/badge.svg)](https://github.com/polymonster/jsn/actions/workflows/release.yaml) 4 | [![publish-pypi](https://github.com/polymonster/jsn/actions/workflows/pypi.yaml/badge.svg)](https://github.com/polymonster/jsn/actions/workflows/pypi.yaml) 5 | [![codecov](https://codecov.io/gh/polymonster/jsn/branch/master/graph/badge.svg)](https://codecov.io/gh/polymonster/jsn) 6 | [![PyPI Version](https://img.shields.io/pypi/v/jsn.svg)](https://pypi.org/project/jsn/) 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 8 | 9 | jsn is a user-friendly data format that can be easily read and reliably edited by humans. 10 | 11 | It adds powerful features such as inheritance, variables, includes and syntax improvements to make jsn files more compact and re-usable than a json counterpart, it is an ideal solution for multi-platform build configuration, packaging and content buidling pipelines. 12 | 13 | jsn can be used directly in python as a dictionary, or it can be converted to json to be used with other languages and tools and libraries which have json support. 14 | 15 | If you are using hand edited json in any projects currently, jsn will easily integrate into your existing workflow and improve efficiency and reliability. 16 | 17 | # Features 18 | 19 | - Includes / file import. 20 | - Inheritance (hierarchicle and multiple). 21 | - Environment style scoped variables. 22 | - Quoteless keys. 23 | - Quoteless strings. 24 | - Single quotes. 25 | - Single and multi-line comments. 26 | - Multi-line strings / line breaks. 27 | - Hex, binary, bit shifts, int and float improvements. 28 | - TextMate langauge included for enhanced syntax highlighting. 29 | 30 | # CLI 31 | 32 | You can convert jsn to json using the commandline, clone this repository and add jsn to your path. 33 | 34 | ``` 35 | jsn -i example.jsn -o example.json -I import_dir 36 | ``` 37 | 38 | ``` 39 | jsn -help 40 | -------------------------------------------------------------------------------- 41 | jsn ---------------------------------------------------------------------------- 42 | -------------------------------------------------------------------------------- 43 | commandline arguments: 44 | -help display this message 45 | -i list of input files or directories to process 46 | -o output file or directory 47 | -I list of import directories, to search for imports 48 | -p print output to console 49 | -keep_vars keep jsn_vars in the output json 50 | ``` 51 | 52 | # Python API 53 | 54 | Install via pip: 55 | 56 | ``` 57 | python3 -m pip install jsn 58 | ``` 59 | 60 | Or alternatively copy jsn.py where you need it. 61 | 62 | jsn can be used just like json as a python dictionary. 63 | 64 | ```python 65 | import jsn 66 | json_dict = jsn.loads(open("jsn_file.jsn", "r").read()) 67 | ``` 68 | 69 | # Releases 70 | 71 | You can install `jsn` as a binary release found along with this repository [here](https://github.com/polymonster/jsn/releases) 72 | 73 | ## Example .jsn 74 | 75 | ```jsonnet 76 | import import.jsn 77 | import test.jsn 78 | { 79 | // sytax highlights quite nicely in most editors with c or c++ syntax 80 | // jsonnet highlights nicely in GitHub 81 | // TextMate grammar included for visual studio, vscode or other compatible editors 82 | 83 | // allow comments 84 | 85 | /* 86 | multi- 87 | line 88 | comments 89 | */ 90 | 91 | // tabs and any whitespace allowed 92 | 93 | // compatible with json 94 | "json": 95 | { 96 | "bool": true, 97 | "int": 1, 98 | "float": 1.0, 99 | "string": "yes", 100 | "array": [1, 2, 3] 101 | }, 102 | 103 | // compatible with json5 104 | json5: 105 | { 106 | unquoted: 'and you can quote me on that', 107 | single_quotes: 'I can use "double quotes" here', 108 | hexadecimal: 0xdecaf, 109 | line_breaks: "Look, Mom! No \ 110 | \\n's!", 111 | leading_decimal_point: .8675309, and_trailing: 8675309., 112 | positive_sign: +1, 113 | trailing_comma: 'in objects', and_in: ['arrays',], 114 | }, 115 | 116 | // jsn features 117 | jsn: 118 | { 119 | // ditch the quotes and commas! 120 | unquoted_string: without_whitespace // cannot contain whitespace or special chars (see str_test) 121 | unquoted: [strings, in, arrays] 122 | binary_literal: 0b10011 123 | bit_shifts: 1<<16 | 1<<8 124 | 125 | // mixed newline and commas 126 | object_members_separated_by_newline: { 127 | no_commas: yes 128 | with_new_lines: "if you like" 129 | but: "you can", still: "use them here" 130 | } 131 | arrays_elements_separated_by_newline:[ 132 | "no need for commas" 133 | "if you use newlines" 134 | "still", "separate", "using commas", "on the same line" 135 | ] 136 | 137 | // you can define variables to be re-used 138 | jsn_vars: 139 | { 140 | data: "path/to/data" 141 | var_str: "hello" 142 | var_int: 10 143 | } 144 | 145 | // evaluate variables with ${} inside quotes.. 146 | variable_data_path: "${data}/subdir" 147 | variable_int: "${var_int}" 148 | array_of_vars: ["${data}", "${var_str}"] 149 | 150 | // you can use special variables: 151 | // inject the current script directory into a string 152 | // - this is the directory name of the file this variable is used in 153 | script_directory: "${script_dir}" 154 | 155 | // env vars can also be injected, if no variable is found in the script jsn will fallback to check if an env var exists 156 | env_vars: ${OS_ENV_VAR} 157 | 158 | // subobjects can be merged and inherited recursively see ** inheritence(jsn) 159 | base: "foo" 160 | sub_object: 161 | { 162 | one: "1" 163 | two: "2" 164 | nested_var: "${var_int}" // variable comes from outer scope. 165 | } 166 | 167 | // use in angled brackets to conditionally include or exclude keys 168 | // the platform specific keys will be merged into the base key if one exists 169 | platform: 170 | { 171 | base: "exists" 172 | } 173 | 174 | // useful for selecting platform specific paths and executables 175 | platform: 176 | { 177 | exe: "path/windows/program.exe" 178 | } 179 | 180 | platform: 181 | { 182 | exe: "path/mac/program" 183 | } 184 | }, 185 | 186 | //** 187 | inheritence(jsn): // add object name to inherit inside parenthesis 188 | { 189 | // inheritance adds keys from 'jsn' object 190 | // .. 191 | 192 | // duplicated keys are overridden by the derived object 193 | base: "bar", 194 | 195 | // inheritance on sub-objects continues recursively 196 | sub_object: 197 | { 198 | three: "3" 199 | //.. 200 | } 201 | }, 202 | 203 | // multiple and hierarchical inheritance 204 | objb: { b: "b" }, 205 | 206 | multiple_inheritence(inheritence, objb): 207 | { 208 | // vars can also be shadowed / overriden.. 209 | jsn_vars: 210 | { 211 | data: "another/path/to/data", 212 | var_int: 22 213 | }, 214 | 215 | c: "c" 216 | }, 217 | 218 | xxx: 219 | { 220 | // some test cases 221 | empty_object: {}, 222 | 223 | empty_array: [], 224 | 225 | array_of_objects:[ 226 | {object: 1, other: "value"}, 227 | {object: 2, other: "value"} 228 | ], 229 | 230 | array_with_string_commas:[ 231 | "test,2", 232 | "test,3" 233 | ], 234 | 235 | nested_objects: 236 | { 237 | yes: true, 238 | and: 239 | { 240 | deeper: nesting 241 | } 242 | }, 243 | 244 | multi_type_arrays:[ 245 | 1, 246 | [2, 3] 247 | ], 248 | 249 | array_of_arrays:[ 250 | [hello, world], 251 | [goodbye, world] 252 | ], 253 | 254 | array_of_values:[ 255 | +255, 256 | 0b11111111, 257 | 0xff, 258 | 1 << 1, 259 | 1 << 2 | 1, 260 | 1 << 2 | 1, 261 | .255 262 | ], 263 | 264 | jsn_vars: 265 | { 266 | va: "path/to/data", 267 | vb: "hello", 268 | }, 269 | 270 | value: null, 271 | array_of_null: [null, null, null] 272 | 273 | array_of_array_vars: [ 274 | ["${va}", "${vb}"], 275 | ["${vb}", "non var"] 276 | ], 277 | 278 | multiple_vars: "${va}/${vb}.bin", 279 | 280 | q1: "small 'quotes' inside", 281 | q2: 'double "quotes" inside', 282 | q3: "double escaped \"quotes\" inside" 283 | }, 284 | 285 | //** 286 | str_test: ":[{}]'+.,0b0x" // this tests ignoring special chars inside quotes 287 | } 288 | ``` 289 | -------------------------------------------------------------------------------- /grammar/jsn/syntaxes/JSN.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JSN", 3 | "scopeName": "source.jsn", 4 | "patterns": [ 5 | { 6 | "include": "#value" 7 | }, 8 | { 9 | "include": "#error" 10 | } 11 | ], 12 | "repository": { 13 | "comment": { 14 | "patterns": [ 15 | { 16 | "name": "comment.block.jsn", 17 | "begin": "/\\*", 18 | "end": "\\*/", 19 | "captures": { 20 | "0": { 21 | "name": "punctuation.definition.comment.jsn" 22 | } 23 | } 24 | }, 25 | { 26 | "name": "comment.line.double-slash.jsn", 27 | "match": "(//)[^\\n\\r\\u2028\\u2029]*", 28 | "captures": { 29 | "1": { 30 | "name": "punctuation.definition.comment.jsn" 31 | } 32 | } 33 | } 34 | ] 35 | }, 36 | 37 | "identifier": { 38 | "name": "support.type.property-name.jsn", 39 | "match": "\\((?:[^,()]*,)+[^,()]*\\)|(,[^()]*$)|([a-zA-Z$_()<>-]|\\\\u[0-9A-Fa-f]{4}|[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F\uDFE0]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D])([a-zA-Z0-9$_\\u200C\\u200D]|\\\\u[0-9A-Fa-f]{4}|[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B4\u08B6-\u08BD\u08D4-\u08E1\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFB-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC00-\uDC4A\uDC50-\uDC59\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F\uDFE0]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6\uDD00-\uDD4A\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF])*" 40 | }, 41 | "constant": { 42 | "name": "constant.language.jsn", 43 | "match": "\\b(null|true|false)\\b" 44 | }, 45 | "string": { 46 | "patterns": [ 47 | { 48 | "include": "#double-string" 49 | }, 50 | { 51 | "include": "#single-string" 52 | }, 53 | { 54 | "include": "#noquote-string" 55 | } 56 | ] 57 | }, 58 | "double-string": { 59 | "name": "string.quoted.double.jsn", 60 | "begin": "\"", 61 | "beginCaptures": { 62 | "0": { 63 | "name": "punctuation.definition.string.double.begin.jsn" 64 | } 65 | }, 66 | "end": "\"", 67 | "endCaptures": { 68 | "0": { 69 | "name": "punctuation.definition.string.double.end.jsn" 70 | } 71 | }, 72 | "patterns": [ 73 | { 74 | "include": "#string-content" 75 | } 76 | ] 77 | }, 78 | "single-string": { 79 | "name": "string.quoted.single.jsn", 80 | "begin": "'", 81 | "beginCaptures": { 82 | "0": { 83 | "name": "punctuation.definition.string.single.begin.jsn" 84 | } 85 | }, 86 | "end": "'", 87 | "endCaptures": { 88 | "0": { 89 | "name": "punctuation.definition.string.single.end.jsn" 90 | } 91 | }, 92 | "patterns": [ 93 | { 94 | "include": "#string-content" 95 | } 96 | ] 97 | }, 98 | "noquote-string": { 99 | "name": "string.noquote.jsn", 100 | "begin": "([a-zA-Z])", 101 | "beginCaptures": { 102 | "0": { 103 | "name": "punctuation.definition.string.noquote.begin.jsn" 104 | } 105 | }, 106 | "end": "([0-9A-Za-z_.-]+)", 107 | "endCaptures": { 108 | "0": { 109 | "name": "punctuation.definition.string.noquote.end.jsn" 110 | } 111 | }, 112 | "patterns": [ 113 | { 114 | "include": "#string-content" 115 | } 116 | ] 117 | }, 118 | "string-content": { 119 | "patterns": [ 120 | { 121 | "name": "constant.character.escape.jsn", 122 | "match": "\\\\x[0-9A-Fa-f]{2}|\\\\u[0-9A-Fa-f]{4}|\\\\[^xu]" 123 | }, 124 | { 125 | "name": "invalid.illegal.unrecognized-string-escape.jsn", 126 | "match": "\\\\." 127 | } 128 | ] 129 | }, 130 | "number": { 131 | "patterns": [ 132 | { 133 | "name": "invalid.illegal.octal.jsn", 134 | "match": "[+-]?0[0-9]+" 135 | }, 136 | { 137 | "name": "constant.numeric.jsn", 138 | "match": "[+-]?(Infinity|NaN|0[xX][0-9A-Fa-f]+|((0|[1-9][0-9]*)\\.[0-9]*|\\.[0-9]+|(0|[1-9][0-9]*))([eE][+-]?[0-9]+)?)|(<<|>>|\\|)" 139 | } 140 | ] 141 | }, 142 | "value": { 143 | "patterns": [ 144 | { 145 | "include": "#constant" 146 | }, 147 | { 148 | "include": "#string" 149 | }, 150 | { 151 | "include": "#number" 152 | }, 153 | { 154 | "include": "#object" 155 | }, 156 | { 157 | "include": "#array" 158 | }, 159 | { 160 | "include": "#comment" 161 | } 162 | ] 163 | }, 164 | "object": { 165 | "name": "meta.structure.dictionary.jsn", 166 | "begin": "\\{", 167 | "beginCaptures": { 168 | "0": { 169 | "name": "punctuation.definition.dictionary.begin.jsn" 170 | } 171 | }, 172 | "end": "\\}", 173 | "endCaptures": { 174 | "0": { 175 | "name": "punctuation.definition.dictionary.end.jsn" 176 | } 177 | }, 178 | "patterns": [ 179 | { 180 | "include": "#property-name" 181 | }, 182 | { 183 | "include": "#comment" 184 | }, 185 | { 186 | "name": "meta.structure.dictionary.value.jsn", 187 | "begin": ":", 188 | "beginCaptures": { 189 | "0": { 190 | "name": "punctuation.separator.dictionary.key-value.jsn" 191 | } 192 | }, 193 | "end": "(,)|(?=\\})|(\r\n|\r|\n|\t)", 194 | "endCaptures": { 195 | "1": { 196 | "name": "punctuation.separator.dictionary.pair.jsn" 197 | } 198 | }, 199 | "patterns": [ 200 | { 201 | "include": "#value" 202 | }, 203 | { 204 | "include": "#error" 205 | } 206 | ] 207 | }, 208 | { 209 | "include": "#error" 210 | } 211 | ] 212 | }, 213 | "property-name": { 214 | "name": "support.type.property-name.jsn", 215 | "patterns": [ 216 | { 217 | "include": "#identifier" 218 | }, 219 | { 220 | "name": "support.type.property-name.jsn", 221 | "begin": "\"", 222 | "beginCaptures": { 223 | "0": { 224 | "name": "punctuation.definition.string.double.begin.jsn" 225 | } 226 | }, 227 | "end": "\"", 228 | "endCaptures": { 229 | "0": { 230 | "name": "punctuation.definition.string.double.end.jsn" 231 | } 232 | }, 233 | "patterns": [ 234 | { 235 | "include": "#string-content" 236 | } 237 | ] 238 | }, 239 | { 240 | "name": "support.type.property-name.jsn", 241 | "begin": "'", 242 | "beginCaptures": { 243 | "0": { 244 | "name": "punctuation.definition.string.single.begin.jsn" 245 | } 246 | }, 247 | "end": "'", 248 | "endCaptures": { 249 | "0": { 250 | "name": "punctuation.definition.string.single.end.jsn" 251 | } 252 | }, 253 | "patterns": [ 254 | { 255 | "include": "#string-content" 256 | } 257 | ] 258 | } 259 | ] 260 | }, 261 | "array": { 262 | "name": "meta.structure.array.jsn", 263 | "begin": "\\[", 264 | "beginCaptures": { 265 | "0": { 266 | "name": "punctuation.definition.array.begin.jsn" 267 | } 268 | }, 269 | "end": "\\]", 270 | "endCaptures": { 271 | "0": { 272 | "name": "punctuation.definition.array.end.jsn" 273 | } 274 | }, 275 | "patterns": [ 276 | { 277 | "include": "#value" 278 | }, 279 | { 280 | "match": "(,)|(\r\n|\r|\n)", 281 | "name": "punctuation.separator.array.jsn" 282 | }, 283 | { 284 | "include": "#error" 285 | } 286 | ] 287 | }, 288 | "error": { 289 | "name": "invalid.illegal.jsn", 290 | "match": "[^\\t\\v\\f \u00A0\uFEFF\\n\\r\u2028\u2029\u1680\u2000-\u200A\u202F\u205F\u3000]" 291 | } 292 | } 293 | } -------------------------------------------------------------------------------- /jsn.py: -------------------------------------------------------------------------------- 1 | # lightweight json format without the need for quotes, allowing comments, file importing, inheritence and more 2 | # copyright Alex Dixon 2019: https://github.com/polymonster/jsn/blob/master/license 3 | 4 | import json 5 | import sys 6 | import os 7 | import traceback 8 | import platform 9 | 10 | 11 | # print error with colour 12 | def print_error(msg): 13 | ERROR = '\033[91m' 14 | ENDC = '\033[0m' 15 | print(ERROR + msg + ENDC, flush=True) 16 | 17 | 18 | # print wanring with colour 19 | def print_warning(msg): 20 | WARNING = '\033[93m' 21 | ENDC = '\033[0m' 22 | print(WARNING + msg + ENDC, flush=True) 23 | 24 | 25 | # print error with colour 26 | def print_ok(msg): 27 | OK = '\033[92m' 28 | ENDC = '\033[0m' 29 | print(OK + msg + ENDC, flush=True) 30 | 31 | 32 | # struct to store the build info for jobs from parsed commandline args 33 | class BuildInfo: 34 | inputs = [] # list of files 35 | import_dirs = [] # lst of import directories to search 36 | output_dir = "" # output directory 37 | print_out = False # print out the resulting json from jsn to the console 38 | keep_vars = False # keep jsn_vars int the resulting json, set to false to pop them 39 | 40 | 41 | # parse command line args passed in 42 | def parse_args(): 43 | info = BuildInfo() 44 | if len(sys.argv) == 1: 45 | display_help() 46 | for i in range(1, len(sys.argv)): 47 | if sys.argv[i] == "-i": 48 | j = i + 1 49 | while j < len(sys.argv) and sys.argv[j][0] != '-': 50 | info.inputs.append(sys.argv[j]) 51 | j = j + 1 52 | i = j 53 | elif sys.argv[i] == "-I": 54 | j = i + 1 55 | while j < len(sys.argv) and sys.argv[j][0] != '-': 56 | info.import_dirs.append(sys.argv[j]) 57 | j = j + 1 58 | i = j 59 | elif sys.argv[i] == "-o": 60 | info.output_dir = sys.argv[i + 1] 61 | elif sys.argv[i] == "-p": 62 | info.print_out = True 63 | elif sys.argv[i] == "-keep_vars": 64 | info.keep_vars = True 65 | return info 66 | 67 | 68 | # help 69 | def display_help(): 70 | print("commandline arguments:") 71 | print(" -help display this message") 72 | print(" -i list of input files or directories to process") 73 | print(" -o output file or directory") 74 | print(" -I list of import directories, to search for imports") 75 | print(" -p print output to console") 76 | print(" -keep_vars keep jsn_vars in the output json") 77 | 78 | 79 | # do c like (u32)-1 80 | def us(v): 81 | if v == -1: 82 | return sys.maxsize 83 | return v 84 | 85 | 86 | # return string inside "quotes" to make code gen cleaner 87 | def in_quotes(string): 88 | if len(string) >= 2: 89 | if string[0] == "\"": 90 | return string 91 | return '"' + string + '"' 92 | 93 | 94 | # create a new dir for a file or a folder if it doesnt already exist and not throw an exception 95 | def create_dir(dst_file): 96 | dir = dst_file 97 | if is_file(dir): 98 | dir = os.path.dirname(dir) 99 | if len(dir) == 0: 100 | return 101 | os.makedirs(dir, exist_ok=True) 102 | 103 | 104 | # change extension 105 | def change_ext(file, ext): 106 | return os.path.splitext(file)[0] + ext 107 | 108 | 109 | # is_file 110 | def is_file(file): 111 | if len(os.path.splitext(file)[1]) > 0: 112 | return True 113 | return False 114 | 115 | 116 | # python json style dumps 117 | def format(jsn, indent=4): 118 | nl = ["{", "[", ","] 119 | el = ["}", "]"] 120 | id = ["{", "["] 121 | fmt = "" 122 | cur_indent = 0 123 | str_list = find_strings(jsn) 124 | for c in range(0, len(jsn)): 125 | char = jsn[c] 126 | if is_inside_quotes(str_list, c): 127 | fmt += char 128 | continue 129 | if char in el: 130 | fmt += "\n" 131 | cur_indent -= 4 132 | for i in range(0, cur_indent): 133 | fmt += " " 134 | fmt += char 135 | if char in nl: 136 | fmt += "\n" 137 | if char in id: 138 | cur_indent += 4 139 | for i in range(0, cur_indent): 140 | fmt += " " 141 | if char == ":": 142 | fmt += " " 143 | return fmt 144 | 145 | 146 | # check whether char jsn[pos] is inside quotes or not 147 | def is_inside_quotes(str_list, pos): 148 | for s in str_list: 149 | if pos < s[0]: 150 | break 151 | if s[0] < pos < s[1]: 152 | return s[1]+1 153 | return 0 154 | 155 | 156 | # find all string tokens within jsn source marked by start and end index 157 | def find_strings(jsn): 158 | quote_types = ["\"", "'"] 159 | oq = "" 160 | prev_char = "" 161 | istart = -1 162 | str_list = [] 163 | for ic in range(0, len(jsn)): 164 | c = jsn[ic] 165 | if c in quote_types: 166 | if oq == "": 167 | oq = c 168 | istart = ic 169 | elif oq == c and prev_char != "\\": 170 | oq = "" 171 | str_list.append((istart, ic)) 172 | if prev_char == "\\" and c == "\\": 173 | prev_char = "" 174 | else: 175 | prev_char = c 176 | return str_list 177 | 178 | 179 | # trims whitespace from lines 180 | def trim_whitespace(jsn): 181 | lines = jsn.split('\n') 182 | trimmed = "" 183 | for l in lines: 184 | trimmed += l.strip() + "\n" 185 | return trimmed 186 | 187 | 188 | # remove whitespace and newlines to simplify subsequent ops 189 | def clean_src(jsn): 190 | clean = "" 191 | inside_quotes = False 192 | for char in jsn: 193 | if char == '\"': 194 | inside_quotes = not inside_quotes 195 | if not inside_quotes: 196 | strip_char = char.strip() 197 | else: 198 | strip_char = char 199 | clean += strip_char 200 | return clean 201 | 202 | 203 | # remove comments, taken from https:/github.com/polymonster/stub-format/stub_format.py 204 | def remove_comments(file_data): 205 | lines = file_data.split("\n") 206 | inside_block = False 207 | conditioned = "" 208 | for line in lines: 209 | str_list = find_strings(line) 210 | if inside_block: 211 | ecpos = line.find("*/") 212 | if ecpos != -1: 213 | inside_block = False 214 | line = line[ecpos+2:] 215 | else: 216 | continue 217 | cpos = line.find("//") 218 | mcpos = line.find("/*") 219 | 220 | if is_inside_quotes(str_list, mcpos): 221 | mcpos = -1 222 | 223 | if is_inside_quotes(str_list, cpos): 224 | cpos = -1 225 | 226 | if cpos != -1: 227 | conditioned += line[:cpos] + "\n" 228 | elif mcpos != -1: 229 | conditioned += line[:mcpos] + "\n" 230 | inside_block = True 231 | else: 232 | conditioned += line + "\n" 233 | return conditioned 234 | 235 | 236 | # change single quotes to double quotes to support json5 237 | def change_quotes(jsn): 238 | str_list = find_strings(jsn) 239 | conditioned = "" 240 | prev = "" 241 | for c in range(0, len(jsn)): 242 | if c > 0: 243 | prev = jsn[c-1] 244 | char = jsn[c] 245 | if char == "\"": 246 | if is_inside_quotes(str_list, c): 247 | if prev != "\\": 248 | conditioned += "\\\"" 249 | continue 250 | if char == "'": 251 | if not is_inside_quotes(str_list, c): 252 | conditioned += "\"" 253 | continue 254 | conditioned += char 255 | return conditioned 256 | 257 | 258 | # remove line breaks within strings 259 | def collapse_line_breaks(jsn): 260 | str_list = find_strings(jsn) 261 | conditioned = "" 262 | skip = False 263 | for c in range(0, len(jsn)): 264 | char = jsn[c] 265 | if skip: 266 | skip = False 267 | continue 268 | if char == "\\" and c+1 < len(jsn) and jsn[c+1] == "\n": 269 | if is_inside_quotes(str_list, c): 270 | skip = True 271 | continue 272 | conditioned += char 273 | return conditioned 274 | 275 | 276 | # find first char in chars in string from pos 277 | def find_first(string, pos, chars): 278 | first = us(-1) 279 | for char in chars: 280 | first = min(us(string.find(char, pos)), first) 281 | return first 282 | 283 | 284 | # get value type, object, array, int, float, bool, hex, binary, binary shift 285 | def get_value_type(value): 286 | value = value.strip() 287 | if len(value) > 0: 288 | if value[0] == "\"": 289 | return "string" 290 | if value[0] == "{": 291 | return "object" 292 | if value[0] == "[": 293 | return "array" 294 | if value == 'true' or value == 'false': 295 | return "bool" 296 | if value.find(".") != -1: 297 | try: 298 | float(value) 299 | return "float" 300 | except ValueError: 301 | pass 302 | if value.find("0x") != -1: 303 | try: 304 | int(value[2:], 16) 305 | return "hex" 306 | except ValueError: 307 | pass 308 | if value.find("0b") != -1: 309 | try: 310 | int(value[2:], 2) 311 | return "binary" 312 | except ValueError: 313 | pass 314 | if value.find("<<") != -1 or value.find(">>") != -1: 315 | return "binary_shift" 316 | try: 317 | int(value) 318 | return "int" 319 | except ValueError: 320 | pass 321 | return "string" 322 | 323 | 324 | # find inherits inside unquoted objects - key(inherit_a, inherit_b) 325 | def get_inherits(object_key): 326 | if object_key[0] == "\"": 327 | return object_key, [] 328 | bp = object_key.find("(") 329 | if bp != -1: 330 | ep = object_key.find(")") 331 | i = object_key[bp+1:ep] 332 | ii = i.split(",") 333 | return object_key[:bp], ii 334 | return object_key, [] 335 | 336 | 337 | # finds the end of a pair of brackets, enclosing sub brackets inside them 338 | def enclose_brackets(open, close, string, pos): 339 | start = pos 340 | pos = string.find(open, pos) 341 | stack = [open] 342 | pos += 1 343 | while len(stack) > 0 and pos < len(string): 344 | if string[pos] == open: 345 | stack.append(open) 346 | if string[pos] == close: 347 | stack.pop() 348 | pos += 1 349 | return pos 350 | 351 | 352 | # add quotes and convert values to be json compatible 353 | def quote_value(value, pos, next): 354 | quoted = "" 355 | if get_value_type(value) == "string": 356 | quoted += in_quotes(value) 357 | pos = next 358 | elif get_value_type(value) == "hex": 359 | hex_value = int(value[2:], 16) 360 | quoted = str(hex_value) 361 | pos = next 362 | elif get_value_type(value) == "binary": 363 | bin_value = int(value[2:], 2) 364 | quoted = str(bin_value) 365 | pos = next 366 | elif get_value_type(value) == "binary_shift": 367 | components = value.split("|") 368 | bv = 0 369 | for comp in components: 370 | if comp.find("<<") != -1: 371 | comp = comp.split("<<") 372 | bv |= int(comp[0]) << int(comp[1]) 373 | elif comp.find(">>") != -1: 374 | comp = comp.split(">>") 375 | bv |= int(comp[0]) << int(comp[1]) 376 | else: 377 | bv |= int(comp) 378 | quoted = str(bv) 379 | pos = next 380 | elif get_value_type(value) == "float": 381 | f = value 382 | if f[0] == ".": 383 | f = "0" + f 384 | elif f[len(f)-1] == ".": 385 | f = f + "0" 386 | quoted = f 387 | pos = next 388 | elif get_value_type(value) == "int": 389 | i = value 390 | if i[0] == "+": 391 | i = i[1:] 392 | quoted = i 393 | pos = next 394 | return (quoted, pos) 395 | 396 | 397 | # add quotes to array items 398 | def quote_array(jsn): 399 | if not jsn: 400 | return "[" + jsn + "]" 401 | # arrays can contain mixed data so go element wise 402 | pos = 0 403 | element_wise = "" 404 | while True: 405 | elem_end = jsn.find(",", pos) 406 | if elem_end == -1: 407 | elem_end = len(jsn) 408 | elem = jsn[pos:elem_end].strip() 409 | if len(elem) == 0: 410 | break 411 | if get_value_type(elem) == "object": 412 | elem_end = enclose_brackets("{", "}", jsn, pos) 413 | sub_object = jsn[pos:elem_end] 414 | element_wise += quote_object(sub_object) 415 | elif get_value_type(elem) == "array": 416 | elem_end = enclose_brackets("[", "]", jsn, pos) 417 | sub_array = jsn[pos+1:elem_end-1] 418 | element_wise += quote_array(sub_array) 419 | elif elem[0] == '\"': 420 | elem_end += enclose_brackets("\"", "\"", jsn, pos) 421 | element_wise = jsn[pos:elem_end] 422 | elif elem == "null": 423 | element_wise += "null" 424 | else: 425 | element_wise += quote_value(elem, 0, 0)[0] 426 | if elem_end == len(jsn): 427 | break 428 | pos = elem_end+1 429 | if pos >= len(jsn): 430 | break 431 | element_wise += "," 432 | return "[" + element_wise + "]" 433 | 434 | 435 | # add quotes to unquoted keys, strings and strings in arrays 436 | def quote_object(jsn): 437 | delimiters = [",", "{"] 438 | pos = 0 439 | quoted = "" 440 | str_list = find_strings(jsn) 441 | while True: 442 | cur = pos 443 | pos = jsn.find(":", pos) 444 | if pos == -1: 445 | quoted += jsn[cur:] 446 | break 447 | # ignore : inside quotes 448 | iq = is_inside_quotes(str_list, pos) 449 | if iq: 450 | quoted += jsn[cur:iq] + ":" 451 | pos = iq + 1 452 | continue 453 | delim = 0 454 | for d in delimiters: 455 | dd = jsn[:pos].rfind(d) 456 | if dd != -1: 457 | delim = max(dd, delim) 458 | key = jsn[delim+1:pos].strip() 459 | # make sure we arent inside brackets, for multiple inheritence 460 | if key.find(")") != -1: 461 | bp = us(jsn[:pos].rfind("(")) 462 | ep = jsn.find(")", delim) 463 | if bp < delim < ep: 464 | delim = 0 465 | for d in delimiters: 466 | dd = jsn[:bp].rfind(d) 467 | if dd != -1: 468 | delim = max(dd, delim) 469 | key = jsn[delim + 1:pos].strip() 470 | pos += 1 471 | next = find_first(jsn, pos, [",", "]", "}"]) 472 | while is_inside_quotes(str_list, next): 473 | next = find_first(jsn, next+1, [",", "]", "}"]) 474 | # put key in quotes 475 | value = jsn[pos:next] 476 | inherit = "" 477 | if get_value_type(value) == "object": 478 | inherit = "{" 479 | pos += 1 480 | key, inherit_list = get_inherits(key) 481 | if len(inherit_list) > 0: 482 | inherit += in_quotes("jsn_inherit") + ": [" 483 | p = 0 484 | for i in inherit_list: 485 | if p > 0: 486 | inherit += ", " 487 | inherit += in_quotes(i.strip()) 488 | p += 1 489 | inherit += "]," 490 | qkey = in_quotes(key) 491 | quoted += jsn[cur:delim+1] 492 | quoted += qkey 493 | quoted += ":" 494 | quoted += inherit 495 | if get_value_type(value) == "array": 496 | end = enclose_brackets("[", "]", jsn, pos) 497 | quoted += quote_array(jsn[pos+1:end-1]) 498 | pos = end 499 | elif value == "null": 500 | quoted += "null" 501 | pos = pos + len("null") 502 | else: 503 | value = quote_value(value, pos, next) 504 | quoted += value[0] 505 | pos = value[1] 506 | return quoted 507 | 508 | 509 | # remove trailing commas from objects and arrays 510 | def remove_trailing_commas(jsn): 511 | trail = ["}", "]"] 512 | clean = "" 513 | for i in range(0, len(jsn)): 514 | j = i + 1 515 | char = jsn[i] 516 | if char == "," and j < len(jsn): 517 | if jsn[j] in trail: 518 | continue 519 | clean += char 520 | if clean[len(clean)-1] == ",": 521 | clean = clean[:len(clean)-1] 522 | return clean 523 | 524 | 525 | # inserts commas in place of newlines \n 526 | def add_new_line_commas(jsn): 527 | prev_char = "" 528 | corrected = "" 529 | ignore_previous = [",", ":", "{", "\n", "\\", "["] 530 | for char in jsn: 531 | if char == '\n' and prev_char not in ignore_previous: 532 | corrected += ',' 533 | corrected += char 534 | prev_char = char 535 | return corrected 536 | 537 | 538 | # inherit dict member wise 539 | def inherit_dict(dest, second): 540 | for k, v in second.items(): 541 | if type(v) == dict: 542 | if k not in dest or type(dest[k]) != dict: 543 | dest[k] = dict() 544 | inherit_dict(dest[k], v) 545 | else: 546 | if k not in dest: 547 | dest[k] = v 548 | 549 | 550 | # recursively merge dicts member wise 551 | def inherit_dict_recursive(d, d2): 552 | inherits = [] 553 | for k, v in d.items(): 554 | if k == "jsn_inherit": 555 | for i in v: 556 | inherits.append(i) 557 | if "jsn_inherit" in d.keys(): 558 | d.pop("jsn_inherit", None) 559 | for i in inherits: 560 | if i in d2.keys(): 561 | inherit_dict(d, d2[i]) 562 | else: 563 | print_error("[jsn error] missing key `" + i + "` used by jsn_inherit") 564 | sys.exit(1) 565 | for k, v in d.items(): 566 | if type(v) == dict: 567 | inherit_dict_recursive(v, d) 568 | 569 | 570 | # finds files to import (includes) 571 | def get_imports(jsn, import_dirs): 572 | imports = [] 573 | bp = jsn.find("{") 574 | head = jsn[:bp].split("\n") 575 | has_imports = False 576 | for i in head: 577 | if i.find("import") != -1: 578 | has_imports = True 579 | if not has_imports: 580 | return jsn[bp:], imports 581 | if not import_dirs: 582 | filedir = os.getcwd() 583 | import_dirs = [filedir] 584 | for i in head: 585 | if i.find("import") != -1: 586 | stripped = i[len("import"):].strip().strip("\"").strip() 587 | found = False 588 | for dir in import_dirs: 589 | import_path_dir = os.path.join(dir, stripped) 590 | if os.path.exists(import_path_dir): 591 | imports.append(import_path_dir) 592 | found = True 593 | break 594 | if not found: 595 | print_error("[jsn error]: cannot find import file " + stripped) 596 | sys.exit(1) 597 | return jsn[bp:], imports 598 | 599 | 600 | # finds all '${vars}' within a string returning in list [${va}, ${vb}, ...] 601 | def vars_in_string(string): 602 | pos = 0 603 | variables = [] 604 | while True: 605 | sp = string.find("${", pos) 606 | if sp != -1: 607 | ep = string.find("}", sp) 608 | variables.append(string[sp:ep + 1]) 609 | pos = sp + 2 610 | else: 611 | break 612 | return variables 613 | 614 | 615 | # replaces a ${var} with system environment var if it exists 616 | def get_env_var(var_name): 617 | if var_name in os.environ.keys(): 618 | return os.environ[var_name] 619 | print_error("[jsn error] undefined variable '" + var_name + "'") 620 | sys.exit(1) 621 | 622 | 623 | # resolves "${var}" into a typed value or a token pasted string, handle multiple vars within strings or arrays 624 | def resolve_vars(value, vars): 625 | value_string = str(value) 626 | vv = vars_in_string(value_string) 627 | count = 0 628 | for v in vv: 629 | var_name = v[2:len(v)-1] 630 | if var_name in vars.keys(): 631 | if type(value) == list: 632 | nl = list() 633 | for i in value: 634 | ri = resolve_vars(i, vars) 635 | if ri: 636 | nl.append(resolve_vars(i, vars)) 637 | else: 638 | nl.append(i) 639 | return nl 640 | else: 641 | if type(vars[var_name]) == str: 642 | value = value.replace(v, vars[var_name]) 643 | if len(vv) == count+1: 644 | return value 645 | else: 646 | return vars[var_name] 647 | else: 648 | ev = get_env_var(var_name) 649 | value = value.replace(v, ev) 650 | if len(vv) == count+1: 651 | return value 652 | count += 1 653 | return None 654 | 655 | 656 | # replace ${} with variables in vars 657 | def resolve_vars_recursive(d, vars, keep_vars=False): 658 | stack_vars = vars.copy() 659 | if "jsn_vars" in d.keys(): 660 | for vk in d["jsn_vars"].keys(): 661 | stack_vars[vk] = d["jsn_vars"][vk] 662 | for k in d.keys(): 663 | value = d[k] 664 | if type(value) == dict: 665 | resolve_vars_recursive(d[k], stack_vars, keep_vars) 666 | elif type(value) == list: 667 | resolved_list = [] 668 | for i in value: 669 | ri = resolve_vars(i, stack_vars) 670 | if ri: 671 | resolved_list.append(ri) 672 | else: 673 | resolved_list.append(i) 674 | d[k] = resolved_list 675 | else: 676 | var = resolve_vars(d[k], stack_vars) 677 | if var: 678 | d[k] = var 679 | if "jsn_vars" in d.keys() and not keep_vars: 680 | d.pop("jsn_vars", None) 681 | 682 | 683 | # resolve platform specific keys, merging 684 | def resolve_platform_keys_recursive(d, platform_name): 685 | rm_keys = [] 686 | platform_dict = dict() 687 | for k in d.keys(): 688 | bp = k.find("<") 689 | ep = k.find(">") 690 | if bp != -1 and ep != -1: 691 | key_platform = k[bp+1:ep] 692 | key_base = k[:bp] 693 | if key_platform == platform_name: 694 | platform_dict[key_base] = d[k] 695 | rm_keys.append(k) 696 | value = d[k] 697 | if type(value) == dict: 698 | resolve_platform_keys_recursive(d[k], platform_name) 699 | for k in rm_keys: 700 | d.pop(k) 701 | inherit_dict(d, platform_dict) 702 | 703 | 704 | # check platform name and then recurse through dictionary selecting our current platform keys 705 | def resolve_platform_keys(d): 706 | name_lookup = { 707 | "Linux": "linux", 708 | "Darwin": "mac", 709 | "Windows": "windows" 710 | } 711 | platform_name = "unknown" 712 | if platform.system() in name_lookup: 713 | platform_name = name_lookup[platform.system()] 714 | else: 715 | print_warning("[jsn warning] unknown platform system " + platform.system()) 716 | resolve_platform_keys_recursive(d, platform_name) 717 | 718 | 719 | # load from file 720 | def load_from_file(filepath, import_dirs, keep_vars): 721 | jsn_contents = open(filepath).read() 722 | filepath = os.path.join(os.getcwd(), filepath) 723 | import_dirs.append(os.path.dirname(filepath)) 724 | return loads(jsn_contents, import_dirs, keep_vars=keep_vars) 725 | 726 | 727 | # convert jsn to json 728 | def loads(jsn, import_dirs=None, vars=True, keep_vars=False): 729 | jsn, imports = get_imports(jsn, import_dirs) 730 | jsn = remove_comments(jsn) 731 | jsn = change_quotes(jsn) 732 | jsn = trim_whitespace(jsn) 733 | jsn = add_new_line_commas(jsn) 734 | jsn = collapse_line_breaks(jsn) 735 | jsn = clean_src(jsn) 736 | jsn = quote_object(jsn) 737 | jsn = remove_trailing_commas(jsn) 738 | jsn = format(jsn) 739 | 740 | # validate 741 | try: 742 | j = json.loads(jsn) 743 | except: 744 | jsn_lines = jsn.split("\n") 745 | for l in range(0, len(jsn_lines)): 746 | print(str(l+1) + " " + jsn_lines[l]) 747 | traceback.print_exc() 748 | sys.exit(1) 749 | 750 | # import 751 | for i in imports: 752 | include_dict = loads(open(i, "r").read(), import_dirs, False, keep_vars=keep_vars) 753 | 754 | # nested var lookups, only works currently on special vars 755 | if "jsn_vars" in include_dict.keys(): 756 | vv = json.dumps(include_dict["jsn_vars"]) 757 | vv = vv.replace("${script_dir}", os.path.dirname(i)) 758 | vv = vv.replace("\\", "/") 759 | include_dict["jsn_vars"] = json.loads(vv) 760 | 761 | inherit_dict(j, include_dict) 762 | 763 | # resolve platform specific keys 764 | resolve_platform_keys(j) 765 | 766 | # inherit 767 | inherit_dict_recursive(j, j) 768 | 769 | # resolve vars 770 | if vars: 771 | resolve_vars_recursive(j, dict(), keep_vars) 772 | 773 | return j 774 | 775 | 776 | # return a list of all imports for this file 777 | def get_import_file_list(filepath, import_dirs=None): 778 | jsn = open(filepath, "r").read() 779 | jsn, imports = get_imports(jsn, import_dirs) 780 | for i in imports: 781 | recursive_imports = get_import_file_list(i, import_dirs) 782 | for ri in recursive_imports: 783 | if ri not in imports: 784 | imports.append(ri) 785 | abs_imports = [] 786 | for i in imports: 787 | abs_imports.append(os.path.normpath(os.path.join(os.getcwd(), i))) 788 | return abs_imports 789 | 790 | 791 | # convert jsn to json and write to a file 792 | def convert_jsn(info, input_file, output_file): 793 | print("converting: " + input_file + " to " + output_file) 794 | output_file = open(output_file, "w+") 795 | jdict = load_from_file(input_file, info.import_dirs, info.keep_vars) 796 | if info.print_out: 797 | print(json.dumps(jdict, indent=4)) 798 | output_file.write(json.dumps(jdict, indent=4)) 799 | output_file.close() 800 | 801 | 802 | def main(): 803 | info = parse_args() 804 | if len(info.inputs) == 0 or not info.output_dir: 805 | display_help() 806 | sys.exit(1) 807 | for i in info.inputs: 808 | if os.path.isdir(i): 809 | for root, dirs, files in os.walk(i): 810 | for file in files: 811 | output_file = info.output_dir 812 | if not is_file(output_file): 813 | output_file = os.path.join(info.output_dir, file) 814 | output_file = change_ext(output_file, ".json") 815 | create_dir(output_file) 816 | convert_jsn(info, os.path.join(root, file), output_file) 817 | else: 818 | output_file = info.output_dir 819 | if not is_file(output_file): 820 | output_file = os.path.join(info.output_dir, i) 821 | output_file = change_ext(output_file, ".json") 822 | create_dir(output_file) 823 | convert_jsn(info, i, output_file) 824 | 825 | 826 | # output .jsn files as json, 827 | if __name__ == "__main__": 828 | main() 829 | 830 | --------------------------------------------------------------------------------