jsmn-tools 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jsmn_tools-0.1.0/.clang-format +14 -0
- jsmn_tools-0.1.0/.clangd +2 -0
- jsmn_tools-0.1.0/.cmake-format.yaml +16 -0
- jsmn_tools-0.1.0/.coveragerc +7 -0
- jsmn_tools-0.1.0/.github/workflows/ci.yml +50 -0
- jsmn_tools-0.1.0/.github/workflows/publish.yml +17 -0
- jsmn_tools-0.1.0/.gitignore +61 -0
- jsmn_tools-0.1.0/.importlinter +10 -0
- jsmn_tools-0.1.0/.mypy.ini +16 -0
- jsmn_tools-0.1.0/.prettierrc.yaml +8 -0
- jsmn_tools-0.1.0/CLAUDE.md +47 -0
- jsmn_tools-0.1.0/LICENSE +21 -0
- jsmn_tools-0.1.0/PKG-INFO +9 -0
- jsmn_tools-0.1.0/README.md +43 -0
- jsmn_tools-0.1.0/cmake/modules/FindUnity.cmake +47 -0
- jsmn_tools-0.1.0/cmake/modules/JsmnToolsConfig.cmake +45 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/__init__.py +1 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/__main__.py +5 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/bundle.py +128 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/cli.py +105 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/__init__.py +21 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/descriptor.py +282 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/filters.py +210 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/flatten.py +245 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/ir.py +202 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/mangle.py +68 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/primitives.py +16 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/render.py +285 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/runtime/jsmn.h +471 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/runtime/runtime.c +761 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/runtime/runtime.h +136 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/templates/jsmn_generated.c +149 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/lang/jsmn/templates/jsmn_generated.h +83 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/node/__init__.py +44 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/node/behavior.py +52 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/node/location.py +40 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/node/node.py +132 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/node/ref.py +42 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/spec/__init__.py +26 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/spec/json_schema.py +68 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/spec/openapi_3_1.py +171 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/walk/__init__.py +32 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/walk/diff.py +71 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/walk/join.py +90 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/walk/merge.py +109 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/walk/normalize.py +39 -0
- jsmn_tools-0.1.0/codegen/src/jsmn_tools/walk/walk.py +33 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/CMakeLists.txt +79 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/README.md +9 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/gen_test.h +92 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/listify.h +217 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/templates/jsmn.h.jinja2 +1 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/templates/jsmn_generated.c.jinja2 +2 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/templates/jsmn_generated.h.jinja2 +4 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/templates/runtime.c.jinja2 +3 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/templates/runtime.h.jinja2 +3 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/test_cursor.c +98 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/test_descriptor.c +110 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/test_fmt.c +124 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/test_generated.c +410 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/test_tok.c +123 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/test_tok_ex.c +112 -0
- jsmn_tools-0.1.0/codegen/tests/e2e/util.h +30 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/flatten/README.md +49 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/flatten/spec_a.yaml +205 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/flatten/spec_b.yaml +39 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/merge_conflict_a.yaml +14 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/merge_conflict_b.yaml +14 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/merge_union_a.yaml +14 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/merge_union_b.yaml +19 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/openapi_sets_a.yaml +29 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/openapi_sets_b.yaml +29 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/ordered_a.yaml +13 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/ordered_b.yaml +13 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/refs.yaml +23 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/schema_dispatch_a.yaml +28 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/schema_dispatch_b.yaml +28 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/schema_sets_a.yaml +21 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/join/schema_sets_b.yaml +21 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/location/pointer_resolve.yaml +37 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/render/jsmn-every-type.openapi.yaml +24 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/render/jsmn-vla.openapi.yaml +122 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/render/jsmn.openapi.yaml +53 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/runtime/arrays.yaml +159 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/runtime/objects.yaml +51 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/runtime/optionals.yaml +50 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/runtime/primitives.yaml +47 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/bool.h +12 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/bool.yml +2 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/bugfix-dependency-order.h +72 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/bugfix-dependency-order.yml +13 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/bugfix-null-property.h +53 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/bugfix-null-property.yml +6 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/generic.h +80 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/generic.yml +47 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/null.h +12 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/null.yml +2 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/number.h +54 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/number.yml +24 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/object.h +103 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/object.yml +69 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/object_optionals.h +201 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/object_optionals.yml +57 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/object_optionals_prefix.h +201 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/object_optionals_prefix.yml +57 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/object_prefix.h +103 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/object_prefix.yml +69 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/string.h +12 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/string.yml +4 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/variable_length_arrays.h +97 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/variable_length_arrays.yml +114 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/variable_length_arrays_optionals.h +178 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/templates/variable_length_arrays_optionals.yml +113 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/walk/components.yaml +24 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/walk/paths.yaml +43 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/network/.jsmn-tools.yaml +10 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/network/schemas/ethernet.openapi.yaml +34 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/network/schemas/wifi.openapi.yaml +29 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/sdk/.jsmn-tools.yaml +10 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/sdk/schemas/auth.openapi.yaml +31 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/sdk/schemas/common.openapi.yaml +30 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/sensors/.jsmn-tools.yaml +10 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/sensors/schemas/humidity.openapi.yaml +30 -0
- jsmn_tools-0.1.0/codegen/tests/fixtures/workspace/sensors/schemas/temperature.openapi.yaml +30 -0
- jsmn_tools-0.1.0/codegen/tests/integration/__init__.py +0 -0
- jsmn_tools-0.1.0/codegen/tests/integration/__snapshots__/test_jsmn_render.ambr +2065 -0
- jsmn_tools-0.1.0/codegen/tests/integration/test_flatten_openapi_3_1.py +149 -0
- jsmn_tools-0.1.0/codegen/tests/integration/test_join_openapi_3_1.py +122 -0
- jsmn_tools-0.1.0/codegen/tests/integration/test_jsmn_render.py +68 -0
- jsmn_tools-0.1.0/codegen/tests/integration/test_normalize_openapi_3_1.py +49 -0
- jsmn_tools-0.1.0/codegen/tests/integration/test_spec_location.py +60 -0
- jsmn_tools-0.1.0/codegen/tests/integration/test_walk_openapi_3_1.py +44 -0
- jsmn_tools-0.1.0/codegen/tests/unit/__init__.py +0 -0
- jsmn_tools-0.1.0/codegen/tests/unit/test_bundle.py +29 -0
- jsmn_tools-0.1.0/codegen/tests/unit/test_lang_jsmn.py +147 -0
- jsmn_tools-0.1.0/codegen/tests/unit/test_spec_diff.py +91 -0
- jsmn_tools-0.1.0/codegen/tests/unit/test_spec_location.py +147 -0
- jsmn_tools-0.1.0/codegen/tests/unit/test_spec_ref.py +46 -0
- jsmn_tools-0.1.0/docs/_static/custom.css +267 -0
- jsmn_tools-0.1.0/docs/_static/logo.png +0 -0
- jsmn_tools-0.1.0/docs/_templates/base.html +12 -0
- jsmn_tools-0.1.0/docs/ai/audits/audits.md +1 -0
- jsmn_tools-0.1.0/docs/ai/index.md +12 -0
- jsmn_tools-0.1.0/docs/ai/prompts/prompts.md +1 -0
- jsmn_tools-0.1.0/docs/conf.py +91 -0
- jsmn_tools-0.1.0/docs/index.md +11 -0
- jsmn_tools-0.1.0/pyproject.toml +45 -0
- jsmn_tools-0.1.0/pyrightconfig.json +14 -0
- jsmn_tools-0.1.0/pytest.ini +2 -0
- jsmn_tools-0.1.0/radon.cfg +9 -0
- jsmn_tools-0.1.0/rebrand.md +135 -0
- jsmn_tools-0.1.0/ruff.toml +32 -0
- jsmn_tools-0.1.0/schema-tools-compare.md +99 -0
- jsmn_tools-0.1.0/uv.lock +1419 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
BasedOnStyle: LLVM
|
|
2
|
+
ColumnLimit: 80
|
|
3
|
+
# Return type on its own line, brace on new line for functions
|
|
4
|
+
BreakAfterReturnType: All
|
|
5
|
+
BreakBeforeBraces: Linux
|
|
6
|
+
BinPackParameters: false
|
|
7
|
+
BinPackArguments: false
|
|
8
|
+
AllowAllParametersOfDeclarationOnNextLine: false
|
|
9
|
+
InsertBraces: true
|
|
10
|
+
IndentWidth: 4
|
|
11
|
+
AlignConsecutiveDeclarations:
|
|
12
|
+
Enabled: true
|
|
13
|
+
AcrossEmptyLines: false
|
|
14
|
+
AcrossComments: true
|
jsmn_tools-0.1.0/.clangd
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
line_width: 100
|
|
2
|
+
tab_size: 4
|
|
3
|
+
enable_markup: false
|
|
4
|
+
max_subgroups_hwrap: 3
|
|
5
|
+
max_pargs_hwrap: 6
|
|
6
|
+
separate_ctrl_name_with_space: false
|
|
7
|
+
separate_fn_name_with_space: false
|
|
8
|
+
dangle_parens: true
|
|
9
|
+
dangle_align: prefix
|
|
10
|
+
min_prefix_chars: 4
|
|
11
|
+
max_prefix_chars: 10
|
|
12
|
+
max_lines_hwrap: 2
|
|
13
|
+
command_case: canonical
|
|
14
|
+
keyword_case: upper
|
|
15
|
+
enable_sort: true
|
|
16
|
+
autosort: true
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: Tests & Quality
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v4
|
|
15
|
+
- run: uv sync
|
|
16
|
+
|
|
17
|
+
- name: Pytest with coverage
|
|
18
|
+
run: uv run pytest --cov=jsmn_tools --cov-report=term-missing --cov-fail-under=90
|
|
19
|
+
|
|
20
|
+
- name: Radon complexity
|
|
21
|
+
run: |
|
|
22
|
+
output=$(uv run radon cc codegen/src/jsmn_tools -n D --no-assert)
|
|
23
|
+
if echo "$output" | grep -qE "^\s+[FMCD]"; then
|
|
24
|
+
echo "::error::Complexity check failed — blocks rated D or worse:"
|
|
25
|
+
echo "$output"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
echo "Radon check passed (threshold: D)"
|
|
29
|
+
|
|
30
|
+
e2e:
|
|
31
|
+
name: E2E (${{ matrix.os }})
|
|
32
|
+
runs-on: ${{ matrix.os }}
|
|
33
|
+
strategy:
|
|
34
|
+
matrix:
|
|
35
|
+
os: [ubuntu-latest, windows-latest]
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: astral-sh/setup-uv@v4
|
|
39
|
+
- uses: ilammy/msvc-dev-cmd@v1
|
|
40
|
+
if: runner.os == 'Windows'
|
|
41
|
+
- run: uv sync
|
|
42
|
+
|
|
43
|
+
- name: Configure
|
|
44
|
+
run: uv run cmake -B build -S codegen/tests/e2e -G Ninja
|
|
45
|
+
|
|
46
|
+
- name: Build
|
|
47
|
+
run: cmake --build build
|
|
48
|
+
|
|
49
|
+
- name: Test
|
|
50
|
+
run: ctest --test-dir build --output-on-failure
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v4
|
|
16
|
+
- run: uv build
|
|
17
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build outputs - TypeScript
|
|
5
|
+
dist/
|
|
6
|
+
*.tsbuildinfo
|
|
7
|
+
|
|
8
|
+
# Build outputs - Python
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
*$py.class
|
|
12
|
+
*.so
|
|
13
|
+
.Python
|
|
14
|
+
build/
|
|
15
|
+
develop-eggs/
|
|
16
|
+
eggs/
|
|
17
|
+
*.egg-info/
|
|
18
|
+
*.egg
|
|
19
|
+
.installed.cfg
|
|
20
|
+
pip-log.txt
|
|
21
|
+
pip-delete-this-directory.txt
|
|
22
|
+
|
|
23
|
+
# Build outputs - CMake
|
|
24
|
+
CMakeCache.txt
|
|
25
|
+
CMakeFiles/
|
|
26
|
+
cmake_install.cmake
|
|
27
|
+
compile_commands.json
|
|
28
|
+
Makefile
|
|
29
|
+
!**/CMakeLists.txt
|
|
30
|
+
.cache
|
|
31
|
+
|
|
32
|
+
# Virtual environments
|
|
33
|
+
.venv/
|
|
34
|
+
venv/
|
|
35
|
+
ENV/
|
|
36
|
+
|
|
37
|
+
# IDE / Editor
|
|
38
|
+
.vscode/
|
|
39
|
+
.idea/
|
|
40
|
+
*.swp
|
|
41
|
+
*.swo
|
|
42
|
+
*~
|
|
43
|
+
|
|
44
|
+
# OS
|
|
45
|
+
.DS_Store
|
|
46
|
+
Thumbs.db
|
|
47
|
+
|
|
48
|
+
# Testing / Coverage
|
|
49
|
+
coverage/
|
|
50
|
+
.coverage
|
|
51
|
+
htmlcov/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
|
|
54
|
+
# Sphinx
|
|
55
|
+
docs/_build/
|
|
56
|
+
|
|
57
|
+
# Claude
|
|
58
|
+
.claude/
|
|
59
|
+
|
|
60
|
+
# AI local artifacts (conversations, drafts with PII)
|
|
61
|
+
docs/ai/local/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[mypy]
|
|
2
|
+
python_version = 3.12
|
|
3
|
+
files = codegen/src, codegen/tests
|
|
4
|
+
exclude = codegen/tests/e2e
|
|
5
|
+
strict = True
|
|
6
|
+
warn_return_any = True
|
|
7
|
+
warn_unused_ignores = True
|
|
8
|
+
disallow_untyped_defs = True
|
|
9
|
+
disallow_incomplete_defs = True
|
|
10
|
+
check_untyped_defs = True
|
|
11
|
+
no_implicit_optional = True
|
|
12
|
+
warn_redundant_casts = True
|
|
13
|
+
warn_unused_configs = True
|
|
14
|
+
show_error_codes = True
|
|
15
|
+
disallow_any_generics = False
|
|
16
|
+
warn_unreachable = True
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Jsmn Tools
|
|
2
|
+
|
|
3
|
+
## Intro
|
|
4
|
+
|
|
5
|
+
jsmn-tools is a code generation project. jsmn-tools generates code by
|
|
6
|
+
crawling OpenAPI specifications and AsyncAPI specifications for "JSON Schemas".
|
|
7
|
+
Code generation must be explicitly declared via OpenAPI and AsyncAPI extension.
|
|
8
|
+
|
|
9
|
+
More details are in the design document. You should only need to consult the
|
|
10
|
+
design document when you are assigned a task.
|
|
11
|
+
|
|
12
|
+
## Design Pushback
|
|
13
|
+
|
|
14
|
+
If you identify a design flaw in what I'm asking for, stop and flag it before
|
|
15
|
+
implementing. Explain the concern and propose an alternative. Don't implement
|
|
16
|
+
something you know is wrong just because I asked for it.
|
|
17
|
+
|
|
18
|
+
Specific things to watch for:
|
|
19
|
+
- Adding indirection (wrappers, abstractions, registries) without a concrete
|
|
20
|
+
second consumer
|
|
21
|
+
- Premature generalization — solving hypothetical future requirements instead of
|
|
22
|
+
the actual task
|
|
23
|
+
- Layering workarounds on top of a bad foundation instead of fixing the
|
|
24
|
+
foundation
|
|
25
|
+
- Duplicating logic that should be factored, or factoring logic that only has
|
|
26
|
+
one call site
|
|
27
|
+
|
|
28
|
+
When in doubt, push back with a short explanation and an alternative. I'd rather
|
|
29
|
+
have a 30-second conversation than undo a bad design later.
|
|
30
|
+
|
|
31
|
+
## Code Structure
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
codegen/
|
|
35
|
+
src/jsmn_tools/
|
|
36
|
+
cli.py # CLI entry point
|
|
37
|
+
bundle.py # Bundle logic for code generation
|
|
38
|
+
node/ # Node types, behaviors, locations, refs
|
|
39
|
+
spec/ # Spec structure definitions (OpenAPI 3.1, JSON Schema)
|
|
40
|
+
walk/ # Tree operations (walk, normalize, diff, merge, join)
|
|
41
|
+
lang/jsmn/ # C code generation (IR types, flatten, templates, runtime)
|
|
42
|
+
tests/
|
|
43
|
+
fixtures/ # Test fixtures (specs, templates)
|
|
44
|
+
unit/ # Unit tests
|
|
45
|
+
integration/ # Integration tests
|
|
46
|
+
e2e/ # Full end to end C integration test (via CMake)
|
|
47
|
+
```
|
jsmn_tools-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Altronix
|
|
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.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# jsmn-tools
|
|
2
|
+
|
|
3
|
+
[](https://github.com/TomzBench/jsmn-tools/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A code generator for JSON parsing in C.
|
|
6
|
+
|
|
7
|
+
## Testing
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv run pytest # unit + integration
|
|
11
|
+
uv run pytest --cov=jsmn_tools # with coverage
|
|
12
|
+
uv run pytest codegen/tests/unit # unit only
|
|
13
|
+
uv run radon cc codegen/src -a -nb # cyclomatic complexity
|
|
14
|
+
uv run radon mi codegen/src -nb # maintainability index
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
# Building the docs
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- Python >= 3.11
|
|
22
|
+
- [uv](https://docs.astral.sh/uv/)
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv venv
|
|
28
|
+
uv sync --extra docs
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Build
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv run sphinx-build -b html docs docs/_build
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Preview
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv run python -m http.server 8000 --directory docs/_build
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Open [http://localhost:8000](http://localhost:8000).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# FindUnity.cmake
|
|
2
|
+
#
|
|
3
|
+
# Find or fetch the ThrowTheSwitch Unity test framework.
|
|
4
|
+
#
|
|
5
|
+
# First attempts find_package in CONFIG mode (for system-installed Unity).
|
|
6
|
+
# Falls back to FetchContent from GitHub if not found.
|
|
7
|
+
#
|
|
8
|
+
# Options:
|
|
9
|
+
# UNITY_VERSION - Git tag to fetch (default: v2.6.1)
|
|
10
|
+
# UNITY_FIND_REQUIRED - If TRUE, failure is fatal (standard find_package behavior)
|
|
11
|
+
#
|
|
12
|
+
# Imported targets:
|
|
13
|
+
# unity::framework - The target to link against
|
|
14
|
+
|
|
15
|
+
if(TARGET unity::framework)
|
|
16
|
+
return()
|
|
17
|
+
endif()
|
|
18
|
+
|
|
19
|
+
# Try system-installed Unity first (exports unity::framework)
|
|
20
|
+
find_package(unity CONFIG QUIET)
|
|
21
|
+
if(unity_FOUND)
|
|
22
|
+
set(Unity_FOUND TRUE)
|
|
23
|
+
return()
|
|
24
|
+
endif()
|
|
25
|
+
|
|
26
|
+
# Fall back to FetchContent
|
|
27
|
+
include(FetchContent)
|
|
28
|
+
|
|
29
|
+
if(NOT DEFINED UNITY_VERSION)
|
|
30
|
+
set(UNITY_VERSION "v2.6.1")
|
|
31
|
+
endif()
|
|
32
|
+
|
|
33
|
+
FetchContent_Declare(
|
|
34
|
+
unity
|
|
35
|
+
GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git
|
|
36
|
+
GIT_TAG ${UNITY_VERSION}
|
|
37
|
+
GIT_SHALLOW TRUE
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
FetchContent_MakeAvailable(unity)
|
|
41
|
+
|
|
42
|
+
# Suppress MSVC Spectre mitigation warnings (C5045) on Unity sources
|
|
43
|
+
if(MSVC AND TARGET unity)
|
|
44
|
+
target_compile_options(unity PRIVATE /wd5045)
|
|
45
|
+
endif()
|
|
46
|
+
|
|
47
|
+
set(Unity_FOUND TRUE)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
set(JsmnTools_FOUND TRUE)
|
|
2
|
+
set(JsmnTools_VERSION "0.1.0")
|
|
3
|
+
find_program(JsmnTools_EXECUTABLE jsmn-tools-codegen REQUIRED)
|
|
4
|
+
|
|
5
|
+
macro(_append_if cond var)
|
|
6
|
+
if(${cond})
|
|
7
|
+
list(APPEND ${var} ${ARGN})
|
|
8
|
+
endif()
|
|
9
|
+
endmacro()
|
|
10
|
+
|
|
11
|
+
function(jsmn_tools_generate)
|
|
12
|
+
cmake_parse_arguments(ARG "" "TARGET;PREFIX" "SPECS;TEMPLATES;ENV" ${ARGN})
|
|
13
|
+
|
|
14
|
+
# Build --template pairs and collect outputs
|
|
15
|
+
list(LENGTH ARG_TEMPLATES templates_len)
|
|
16
|
+
math(EXPR templates_end "${templates_len} - 1")
|
|
17
|
+
foreach(i RANGE 0 ${templates_end} 2)
|
|
18
|
+
math(EXPR j "${i} + 1")
|
|
19
|
+
list(GET ARG_TEMPLATES ${i} tpl)
|
|
20
|
+
list(GET ARG_TEMPLATES ${j} out)
|
|
21
|
+
list(APPEND _template_args --template "${tpl}" "${out}")
|
|
22
|
+
list(APPEND _depends "${tpl}")
|
|
23
|
+
list(APPEND _outputs "${out}")
|
|
24
|
+
endforeach()
|
|
25
|
+
|
|
26
|
+
# Build --env args (user-defined template variables)
|
|
27
|
+
foreach(e IN LISTS ARG_ENV)
|
|
28
|
+
list(APPEND _env_args --env "${e}")
|
|
29
|
+
endforeach()
|
|
30
|
+
|
|
31
|
+
# Build command args
|
|
32
|
+
set(_args ${ARG_SPECS} ${_template_args} ${_env_args})
|
|
33
|
+
_append_if(ARG_PREFIX _args --prefix "${ARG_PREFIX}")
|
|
34
|
+
|
|
35
|
+
add_custom_command(
|
|
36
|
+
OUTPUT ${_outputs}
|
|
37
|
+
COMMAND ${JsmnTools_EXECUTABLE} generate ${_args}
|
|
38
|
+
DEPENDS ${ARG_SPECS} ${_depends}
|
|
39
|
+
COMMENT "jsmn-tools: generating outputs"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if(ARG_TARGET)
|
|
43
|
+
add_custom_target(${ARG_TARGET} DEPENDS ${_outputs})
|
|
44
|
+
endif()
|
|
45
|
+
endfunction()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""jsmn-tools code generator."""
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Workspace parsing utilities"""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import NamedTuple, TypedDict, cast
|
|
6
|
+
|
|
7
|
+
from jsonschema import validate # type: ignore[import-untyped]
|
|
8
|
+
from referencing import Registry, Resource
|
|
9
|
+
from referencing.jsonschema import DRAFT202012
|
|
10
|
+
from ruamel.yaml import YAML
|
|
11
|
+
|
|
12
|
+
# safe yaml loader for schemas
|
|
13
|
+
yaml = YAML(typ="safe")
|
|
14
|
+
|
|
15
|
+
# regex for finding jsmn-tools file
|
|
16
|
+
RE_CONFIG = re.compile(r"^\.?(jsmnTools|JsmnTools|jsmn-tools).ya?ml$")
|
|
17
|
+
|
|
18
|
+
# regex for validating $id URIs: scheme://module/resource/vN
|
|
19
|
+
RE_URI = re.compile(
|
|
20
|
+
r"^[a-zA-Z][a-zA-Z0-9-]*://[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+/v\d+$"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# validator support
|
|
25
|
+
SCHEMA = yaml.load("""
|
|
26
|
+
type: object
|
|
27
|
+
required: [resources]
|
|
28
|
+
properties:
|
|
29
|
+
resources:
|
|
30
|
+
type: array
|
|
31
|
+
items:
|
|
32
|
+
type: object
|
|
33
|
+
required: [name, version]
|
|
34
|
+
properties:
|
|
35
|
+
name:
|
|
36
|
+
type: string
|
|
37
|
+
version:
|
|
38
|
+
type: number
|
|
39
|
+
openapi:
|
|
40
|
+
type: array
|
|
41
|
+
items:
|
|
42
|
+
type: string
|
|
43
|
+
asyncapi:
|
|
44
|
+
type: array
|
|
45
|
+
items:
|
|
46
|
+
type: string
|
|
47
|
+
if:
|
|
48
|
+
type: array
|
|
49
|
+
items:
|
|
50
|
+
type: string
|
|
51
|
+
""")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ResourceConfig(TypedDict):
|
|
55
|
+
name: str
|
|
56
|
+
version: int
|
|
57
|
+
openapi: list[Path]
|
|
58
|
+
asyncapi: list[Path]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Config(TypedDict):
|
|
62
|
+
name: str
|
|
63
|
+
resources: list[ResourceConfig]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Uri(NamedTuple):
|
|
67
|
+
module: str
|
|
68
|
+
resource: str
|
|
69
|
+
version: int
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _parse_workspace(path: Path | str, config: Path) -> Config:
|
|
73
|
+
root = Path(path).absolute()
|
|
74
|
+
name = root.name.split(".")[0]
|
|
75
|
+
doc = yaml.load(root / config)
|
|
76
|
+
validate(instance=doc, schema=SCHEMA)
|
|
77
|
+
doc["name"] = name
|
|
78
|
+
for res in doc["resources"]:
|
|
79
|
+
if "openapi" in res:
|
|
80
|
+
res["openapi"] = [root / path for path in res["openapi"]]
|
|
81
|
+
else:
|
|
82
|
+
res["openapi"] = []
|
|
83
|
+
if "asyncapi" in res:
|
|
84
|
+
res["asyncapi"] = [root / path for path in res["asyncapi"]]
|
|
85
|
+
else:
|
|
86
|
+
res["asyncapi"] = []
|
|
87
|
+
return cast("Config", doc)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class InvalidUriError(Exception):
|
|
91
|
+
"""Raised when a schema $id does not match the expected URI format."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _read_resource(
|
|
95
|
+
loc: str | Path,
|
|
96
|
+
scheme: str,
|
|
97
|
+
module: str,
|
|
98
|
+
res: str,
|
|
99
|
+
version: int,
|
|
100
|
+
) -> Resource:
|
|
101
|
+
content = yaml.load(loc)
|
|
102
|
+
if "$id" not in content:
|
|
103
|
+
content["$id"] = f"{scheme}://{module}/{res}/v{version}"
|
|
104
|
+
elif not RE_URI.match(content["$id"]):
|
|
105
|
+
# TODO need to collect errors instead of failing fast
|
|
106
|
+
raise InvalidUriError("Invalid $id: \"content['$id']\"")
|
|
107
|
+
return Resource.from_contents(content, default_specification=DRAFT202012)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def bundle(scheme: str, workspace: list[Path] | list[str]) -> Registry:
|
|
111
|
+
""" """
|
|
112
|
+
|
|
113
|
+
# iterate over workspace directories, parsing specifications
|
|
114
|
+
configs = [
|
|
115
|
+
_parse_workspace(module, path)
|
|
116
|
+
for module in workspace
|
|
117
|
+
for path in Path(module).iterdir()
|
|
118
|
+
if RE_CONFIG.match(path.name)
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
# flatten resources into a keyable registry
|
|
122
|
+
content = [
|
|
123
|
+
_read_resource(spec, scheme, cfg["name"], res["name"], res["version"])
|
|
124
|
+
for cfg in configs
|
|
125
|
+
for res in cfg["resources"]
|
|
126
|
+
for spec in res["openapi"]
|
|
127
|
+
]
|
|
128
|
+
return content @ Registry()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""CLI entry point for jsmn tools cli"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from importlib.resources import files
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from referencing import Registry, Resource
|
|
10
|
+
from referencing.jsonschema import DRAFT202012
|
|
11
|
+
from ruamel.yaml import YAML
|
|
12
|
+
|
|
13
|
+
from jsmn_tools.lang.jsmn.flatten import flatten_with_resolver
|
|
14
|
+
from jsmn_tools.lang.jsmn.render import Renderer
|
|
15
|
+
|
|
16
|
+
JSMN_RUNTIME_DIR = files("jsmn_tools").joinpath("lang", "jsmn", "runtime")
|
|
17
|
+
RUNTIME_FILES = ("runtime.c", "runtime.h", "jsmn.h")
|
|
18
|
+
|
|
19
|
+
yaml = YAML(typ="safe")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_spec(p: str) -> Resource:
|
|
23
|
+
y = yaml.load(Path(p))
|
|
24
|
+
return Resource.from_contents(y, default_specification=DRAFT202012)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _die(fmt: str, *args: object) -> None:
|
|
28
|
+
print(f"error: {fmt % args}" if args else f"error: {fmt}", file=sys.stderr)
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _cmake_dir() -> str:
|
|
33
|
+
pkg = files("jsmn_tools").joinpath("cmake")
|
|
34
|
+
if Path(str(pkg)).is_dir():
|
|
35
|
+
return str(pkg)
|
|
36
|
+
# Editable install: cmake/ lives at repo root, not inside the package.
|
|
37
|
+
repo = Path(__file__).resolve().parents[3]
|
|
38
|
+
fallback = repo / "cmake" / "modules"
|
|
39
|
+
if fallback.is_dir():
|
|
40
|
+
return str(fallback)
|
|
41
|
+
raise FileNotFoundError("Cannot locate jsmn tools cmake modules")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _generate(args: argparse.Namespace) -> None:
|
|
45
|
+
resources = [parse_spec(s) for s in args.specs]
|
|
46
|
+
extra_env = dict(e.split("=", 1) for e in args.env)
|
|
47
|
+
registry: Registry[Any] = resources @ Registry()
|
|
48
|
+
specs = [r.contents for r in registry.values()]
|
|
49
|
+
compiled = flatten_with_resolver(*specs, resolver=registry.resolver())
|
|
50
|
+
opts: dict[str, Any] = {"extra_env": extra_env}
|
|
51
|
+
if args.prefix:
|
|
52
|
+
opts["prefix"] = args.prefix
|
|
53
|
+
renderer = Renderer(compiled.decls, **opts)
|
|
54
|
+
for src, out in args.templates:
|
|
55
|
+
tpl = Path(src).read_text(encoding="utf-8")
|
|
56
|
+
Path(out).write_text(renderer.render(tpl), encoding="utf-8")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main() -> None:
|
|
60
|
+
parser = argparse.ArgumentParser(prog="jsmn-tools", description="jsmn tools")
|
|
61
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
62
|
+
|
|
63
|
+
# Top level commands
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--cmake-dir",
|
|
66
|
+
action="store_true",
|
|
67
|
+
help="Print cmake module install path",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Code generator sub command
|
|
71
|
+
gen_parser = subparsers.add_parser("generate", help="template renderer")
|
|
72
|
+
gen_parser.add_argument("specs", nargs="*", help="YAML spec files")
|
|
73
|
+
gen_parser.add_argument(
|
|
74
|
+
"--template",
|
|
75
|
+
nargs=2,
|
|
76
|
+
action="append",
|
|
77
|
+
metavar=("TEMPLATE", "OUTPUT"),
|
|
78
|
+
dest="templates",
|
|
79
|
+
default=[],
|
|
80
|
+
help="Template and output filename pair",
|
|
81
|
+
)
|
|
82
|
+
gen_parser.add_argument(
|
|
83
|
+
"--env",
|
|
84
|
+
action="append",
|
|
85
|
+
metavar="KEY=VALUE",
|
|
86
|
+
default=[],
|
|
87
|
+
help="User-defined template variable",
|
|
88
|
+
)
|
|
89
|
+
gen_parser.add_argument(
|
|
90
|
+
"--prefix",
|
|
91
|
+
help="Function/type prefix (default: jsmn_)",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
args = parser.parse_args()
|
|
95
|
+
if args.cmake_dir:
|
|
96
|
+
print(_cmake_dir())
|
|
97
|
+
elif args.command == "generate":
|
|
98
|
+
_generate(args)
|
|
99
|
+
else:
|
|
100
|
+
parser.print_help()
|
|
101
|
+
_die("no command specified")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
main()
|