wireforge 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.
Files changed (102) hide show
  1. wireforge-0.1.0/LICENSE +98 -0
  2. wireforge-0.1.0/MANIFEST.in +8 -0
  3. wireforge-0.1.0/PKG-INFO +91 -0
  4. wireforge-0.1.0/README.md +51 -0
  5. wireforge-0.1.0/cmake/package/WireForgeConfig.cmake +29 -0
  6. wireforge-0.1.0/cmake/package/WireForgeConfigVersion.cmake +15 -0
  7. wireforge-0.1.0/cmake/package/WireForgeGenerate.cmake +64 -0
  8. wireforge-0.1.0/codegen/wireforge/__init__.py +3 -0
  9. wireforge-0.1.0/codegen/wireforge/bundle.py +177 -0
  10. wireforge-0.1.0/codegen/wireforge/ci_check.py +155 -0
  11. wireforge-0.1.0/codegen/wireforge/cli.py +617 -0
  12. wireforge-0.1.0/codegen/wireforge/codec.py +897 -0
  13. wireforge-0.1.0/codegen/wireforge/compat.py +536 -0
  14. wireforge-0.1.0/codegen/wireforge/coverage_points.py +64 -0
  15. wireforge-0.1.0/codegen/wireforge/cpp.py +273 -0
  16. wireforge-0.1.0/codegen/wireforge/cpp_codec.py +440 -0
  17. wireforge-0.1.0/codegen/wireforge/cpp_interface.py +874 -0
  18. wireforge-0.1.0/codegen/wireforge/cpp_tests.py +1993 -0
  19. wireforge-0.1.0/codegen/wireforge/differential.py +586 -0
  20. wireforge-0.1.0/codegen/wireforge/docs_markdown.py +72 -0
  21. wireforge-0.1.0/codegen/wireforge/expr.py +598 -0
  22. wireforge-0.1.0/codegen/wireforge/findings.py +187 -0
  23. wireforge-0.1.0/codegen/wireforge/fmt.py +351 -0
  24. wireforge-0.1.0/codegen/wireforge/generate.py +644 -0
  25. wireforge-0.1.0/codegen/wireforge/golden.py +253 -0
  26. wireforge-0.1.0/codegen/wireforge/hlr_import.py +112 -0
  27. wireforge-0.1.0/codegen/wireforge/impact.py +249 -0
  28. wireforge-0.1.0/codegen/wireforge/importers.py +402 -0
  29. wireforge-0.1.0/codegen/wireforge/issues.py +27 -0
  30. wireforge-0.1.0/codegen/wireforge/layout.py +681 -0
  31. wireforge-0.1.0/codegen/wireforge/lifecycle.py +1266 -0
  32. wireforge-0.1.0/codegen/wireforge/lifecycle_results.py +205 -0
  33. wireforge-0.1.0/codegen/wireforge/lifecycle_rules.py +78 -0
  34. wireforge-0.1.0/codegen/wireforge/lifecycle_runtime.py +375 -0
  35. wireforge-0.1.0/codegen/wireforge/llr.py +693 -0
  36. wireforge-0.1.0/codegen/wireforge/llr_ids.py +104 -0
  37. wireforge-0.1.0/codegen/wireforge/loader.py +422 -0
  38. wireforge-0.1.0/codegen/wireforge/mcp_server.py +357 -0
  39. wireforge-0.1.0/codegen/wireforge/model.py +322 -0
  40. wireforge-0.1.0/codegen/wireforge/plain.py +15 -0
  41. wireforge-0.1.0/codegen/wireforge/py.typed +0 -0
  42. wireforge-0.1.0/codegen/wireforge/reqif.py +289 -0
  43. wireforge-0.1.0/codegen/wireforge/resources.py +43 -0
  44. wireforge-0.1.0/codegen/wireforge/results.py +377 -0
  45. wireforge-0.1.0/codegen/wireforge/runtime_reqs.py +147 -0
  46. wireforge-0.1.0/codegen/wireforge/schema.py +151 -0
  47. wireforge-0.1.0/codegen/wireforge/server/__init__.py +1 -0
  48. wireforge-0.1.0/codegen/wireforge/server/app.py +619 -0
  49. wireforge-0.1.0/codegen/wireforge/server/oidc.py +99 -0
  50. wireforge-0.1.0/codegen/wireforge/server/storage.py +146 -0
  51. wireforge-0.1.0/codegen/wireforge/service.py +1354 -0
  52. wireforge-0.1.0/codegen/wireforge/templates/command/project.yaml +38 -0
  53. wireforge-0.1.0/codegen/wireforge/templates/raw/project.yaml +28 -0
  54. wireforge-0.1.0/codegen/wireforge/templates/telemetry/project.yaml +51 -0
  55. wireforge-0.1.0/codegen/wireforge/test_cases.py +475 -0
  56. wireforge-0.1.0/codegen/wireforge/test_project.py +478 -0
  57. wireforge-0.1.0/codegen/wireforge/trace.py +442 -0
  58. wireforge-0.1.0/codegen/wireforge/validate.py +1241 -0
  59. wireforge-0.1.0/codegen/wireforge/vcs.py +44 -0
  60. wireforge-0.1.0/codegen/wireforge/vectors.py +222 -0
  61. wireforge-0.1.0/codegen/wireforge/versioning.py +137 -0
  62. wireforge-0.1.0/codegen/wireforge/wireshark.py +792 -0
  63. wireforge-0.1.0/codegen/wireforge/wireshark_runtime.lua +725 -0
  64. wireforge-0.1.0/codegen/wireforge.egg-info/PKG-INFO +91 -0
  65. wireforge-0.1.0/codegen/wireforge.egg-info/SOURCES.txt +100 -0
  66. wireforge-0.1.0/codegen/wireforge.egg-info/dependency_links.txt +1 -0
  67. wireforge-0.1.0/codegen/wireforge.egg-info/entry_points.txt +2 -0
  68. wireforge-0.1.0/codegen/wireforge.egg-info/requires.txt +33 -0
  69. wireforge-0.1.0/codegen/wireforge.egg-info/top_level.txt +1 -0
  70. wireforge-0.1.0/pyproject.toml +79 -0
  71. wireforge-0.1.0/runtime/LICENSE +21 -0
  72. wireforge-0.1.0/runtime/include/wireforge/array_access.hpp +35 -0
  73. wireforge-0.1.0/runtime/include/wireforge/bit_utils.hpp +226 -0
  74. wireforge-0.1.0/runtime/include/wireforge/byte_io.hpp +387 -0
  75. wireforge-0.1.0/runtime/include/wireforge/checksum.hpp +201 -0
  76. wireforge-0.1.0/runtime/include/wireforge/codec_support.hpp +412 -0
  77. wireforge-0.1.0/runtime/include/wireforge/config.hpp +41 -0
  78. wireforge-0.1.0/runtime/include/wireforge/delegate.hpp +101 -0
  79. wireforge-0.1.0/runtime/include/wireforge/dispatcher.hpp +339 -0
  80. wireforge-0.1.0/runtime/include/wireforge/expr_support.hpp +38 -0
  81. wireforge-0.1.0/runtime/include/wireforge/frame_decoder.hpp +262 -0
  82. wireforge-0.1.0/runtime/include/wireforge/frame_layout.hpp +20 -0
  83. wireforge-0.1.0/runtime/include/wireforge/link.hpp +223 -0
  84. wireforge-0.1.0/runtime/include/wireforge/lock.hpp +146 -0
  85. wireforge-0.1.0/runtime/include/wireforge/ring_buffer.hpp +226 -0
  86. wireforge-0.1.0/runtime/include/wireforge/span.hpp +116 -0
  87. wireforge-0.1.0/runtime/include/wireforge/static_vector.hpp +264 -0
  88. wireforge-0.1.0/runtime/include/wireforge/status.hpp +75 -0
  89. wireforge-0.1.0/runtime/include/wireforge/transport.hpp +95 -0
  90. wireforge-0.1.0/runtime/include/wireforge/validation.hpp +153 -0
  91. wireforge-0.1.0/runtime/include/wireforge/version.hpp +22 -0
  92. wireforge-0.1.0/runtime/requirements.yaml +438 -0
  93. wireforge-0.1.0/schema/deviations.schema.json +47 -0
  94. wireforge-0.1.0/schema/fragment.schema.json +79 -0
  95. wireforge-0.1.0/schema/llr_ids.schema.json +25 -0
  96. wireforge-0.1.0/schema/project.schema.json +1240 -0
  97. wireforge-0.1.0/schema/runtime_requirements.schema.json +34 -0
  98. wireforge-0.1.0/setup.cfg +4 -0
  99. wireforge-0.1.0/setup.py +45 -0
  100. wireforge-0.1.0/ui/dist/assets/index-6Pomu6U4.js +44 -0
  101. wireforge-0.1.0/ui/dist/assets/index-GVdDyUAG.css +1 -0
  102. wireforge-0.1.0/ui/dist/index.html +13 -0
@@ -0,0 +1,98 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: Oguzhan KAYNAK
6
+ Licensed Work: WireForge (this repository), excluding the contents of the
7
+ `runtime/` directory, which are separately licensed under
8
+ the MIT License (see runtime/LICENSE). The Licensed Work
9
+ is (c) 2026 Oguzhan KAYNAK.
10
+ Additional Use Grant: You may use the Licensed Work for development, testing,
11
+ evaluation, and other non-production purposes free of
12
+ charge. Any production use of the Licensed Work requires
13
+ a commercial license from the Licensor.
14
+ Change Date: 2036-10-17
15
+ Change License: MIT
16
+
17
+ For information about alternative licensing arrangements for the Licensed Work,
18
+ please contact the Licensor.
19
+
20
+ Notice
21
+
22
+ The Business Source License (this document, or the "License") is not an Open
23
+ Source license. However, the Licensed Work will eventually be made available
24
+ under an Open Source License, as stated in this License.
25
+
26
+ License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
27
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
28
+
29
+ -----------------------------------------------------------------------------
30
+
31
+ Business Source License 1.1
32
+
33
+ Terms
34
+
35
+ The Licensor hereby grants you the right to copy, modify, create derivative
36
+ works, redistribute, and make non-production use of the Licensed Work. The
37
+ Licensor may make an Additional Use Grant, above, permitting limited
38
+ production use.
39
+
40
+ Effective on the Change Date, or the fourth anniversary of the first publicly
41
+ available distribution of a specific version of the Licensed Work under this
42
+ License, whichever comes first, the Licensor hereby grants you rights under
43
+ the terms of the Change License, and the rights granted in the paragraph
44
+ above terminate.
45
+
46
+ If your use of the Licensed Work does not comply with the requirements
47
+ currently in effect as described in this License, you must purchase a
48
+ commercial license from the Licensor, its affiliated entities, or authorized
49
+ resellers, or you must refrain from using the Licensed Work.
50
+
51
+ All copies of the original and modified Licensed Work, and derivative works
52
+ of the Licensed Work, are subject to this License. This License applies
53
+ separately for each version of the Licensed Work and the Change Date may vary
54
+ for each version of the Licensed Work released by Licensor.
55
+
56
+ You must conspicuously display this License on each original or modified copy
57
+ of the Licensed Work. If you receive the Licensed Work in original or
58
+ modified form from a third party, the terms and conditions set forth in this
59
+ License apply to your use of that work.
60
+
61
+ Any use of the Licensed Work in violation of this License will automatically
62
+ terminate your rights under this License for the current and all other
63
+ versions of the Licensed Work.
64
+
65
+ This License does not grant you any right in any trademark or logo of
66
+ Licensor or its affiliates (provided that you may use a trademark or logo of
67
+ Licensor as expressly required by this License).
68
+
69
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
70
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
71
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
72
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
73
+ TITLE.
74
+
75
+ MariaDB hereby grants you permission to use this License's text to license
76
+ your works, and to refer to it using the trademark "Business Source License",
77
+ as long as you comply with the Covenants of Licensor below.
78
+
79
+ Covenants of Licensor
80
+
81
+ In consideration of the right to use this License's text and the "Business
82
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
83
+ other recipients of the licensed work to be provided by Licensor:
84
+
85
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
86
+ or a license that is compatible with GPL Version 2.0 or a later version,
87
+ where "compatible" means that software provided under the Change License
88
+ can be included in a program with software provided under GPL Version 2.0
89
+ or a later version. Licensor may specify additional Change Licenses
90
+ without limitation.
91
+
92
+ 2. To either: (a) specify an additional grant of rights to use that does not
93
+ impose any additional restriction on the right granted in this License, as
94
+ the Additional Use Grant; or (b) insert the text "None".
95
+
96
+ 3. To specify a Change Date.
97
+
98
+ 4. Not to modify this License in any other way.
@@ -0,0 +1,8 @@
1
+ # What the source distribution carries so that a wheel can be built from it (setup.py copies these
2
+ # directories into the wheel).
3
+ include LICENSE README.md
4
+ include runtime/LICENSE runtime/requirements.yaml
5
+ recursive-include schema *.json
6
+ recursive-include runtime/include *.hpp
7
+ recursive-include cmake/package *
8
+ recursive-include ui/dist *
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: wireforge
3
+ Version: 0.1.0
4
+ Summary: Schema-driven wire-format toolkit: C++17 code generation, validation, server and MCP.
5
+ Author: Oguzhan KAYNAK
6
+ License-Expression: BUSL-1.1 AND MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ License-File: runtime/LICENSE
11
+ Requires-Dist: ruamel.yaml>=0.18
12
+ Requires-Dist: jsonschema>=4.21
13
+ Requires-Dist: referencing>=0.30
14
+ Requires-Dist: typing_extensions>=4.4
15
+ Provides-Extra: server
16
+ Requires-Dist: fastapi>=0.110; extra == "server"
17
+ Requires-Dist: uvicorn>=0.29; extra == "server"
18
+ Provides-Extra: oidc
19
+ Requires-Dist: joserfc>=1.0; extra == "oidc"
20
+ Provides-Extra: mcp
21
+ Requires-Dist: mcp>=2.3; extra == "mcp"
22
+ Provides-Extra: import
23
+ Requires-Dist: openpyxl>=3.1; extra == "import"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8; extra == "dev"
26
+ Requires-Dist: ruff<0.18,>=0.17; extra == "dev"
27
+ Requires-Dist: mypy<3,>=2.4; extra == "dev"
28
+ Requires-Dist: types-jsonschema; extra == "dev"
29
+ Requires-Dist: gcovr>=7; extra == "dev"
30
+ Requires-Dist: clang-tidy==18.1.8; extra == "dev"
31
+ Requires-Dist: fastapi>=0.110; extra == "dev"
32
+ Requires-Dist: uvicorn>=0.29; extra == "dev"
33
+ Requires-Dist: httpx2>=2.13; extra == "dev"
34
+ Requires-Dist: mcp>=2.3; extra == "dev"
35
+ Requires-Dist: openpyxl>=3.1; extra == "dev"
36
+ Requires-Dist: joserfc>=1.0; extra == "dev"
37
+ Requires-Dist: types-openpyxl; extra == "dev"
38
+ Requires-Dist: pymodbus==3.16.1; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ # WireForge
42
+
43
+ Schema-driven wire-format toolkit for C++17: describe binary messages in YAML (by hand, in the web UI, through the CLI
44
+ or an AI agent) and generate allocation-free C++ serializers, validators, framers and dispatchers together with a
45
+ GoogleTest project that proves them against a reference codec and gates the generated code at 100 % line, branch and
46
+ function coverage.
47
+
48
+ - The wire format is imposed by the remote party: generated code never adds bytes.
49
+ - Runtime and generated code use only pre-allocated storage: no heap, no exceptions, no RTTI required.
50
+ - One service layer behind the `wireforge` CLI, the Python API, the REST server with its web UI and the MCP server.
51
+ - A generated Wireshark Lua dissector shows every interface's frames, with the same checks as the decoder.
52
+ - Zero warnings from every compiler and analyzer; tested on 32/64 bit, little and big endian, x86 and ARM, on Linux,
53
+ Windows, macOS, Android and FreeRTOS; differential testing and fuzzing.
54
+
55
+ The way in is short: `wireforge init` gives a working first project, you change it to your protocol, generate the C++
56
+ code, run the tests and read the results. Nothing about requirements or standards is needed for that.
57
+
58
+ ## For teams with a development or assurance process
59
+ Optionally, `wireforge docs` turns the same model into requirements, design, test and traceability documents with test
60
+ and coverage results, change impact between revisions and ReqIF/CSV exchange with requirements tools (docs/LIFECYCLE.md,
61
+ docs/integration/). For projects that apply standards such as DO-178C, ISO 26262, IEC 61508 or IEC 62304, this evidence
62
+ supports your processes and your tool assessment; it does not by itself give conformity, certification or tool
63
+ qualification, which depend on the whole project and its assessor (docs/DO178C.md).
64
+
65
+ **Start with docs/GUIDE.md** (install, 5-minute quickstart, examples). Reference:
66
+
67
+ | Topic | Document |
68
+ |-------|----------|
69
+ | Definition format | docs/SCHEMA_SPEC.md, `schema/`, `examples/` |
70
+ | Command line and Python API | docs/CLI.md |
71
+ | Generated C++ and test project | docs/CODEGEN.md, docs/RUNTIME.md |
72
+ | Layout analysis / ICD export | docs/LAYOUT.md |
73
+ | Wireshark dissector (Lua plugin) | docs/WIRESHARK.md |
74
+ | Requirements, design and test documents | docs/LIFECYCLE.md |
75
+ | Fitting into a DO-178C process, tool integration | docs/DO178C.md, docs/integration/ |
76
+ | Server, web UI, MCP | docs/SERVER.md, docs/UI.md, docs/MCP.md |
77
+ | Secure deployment and operation | docs/SERVER.md, "Deployment and operation" |
78
+ | Importers (CSV/Excel ICD, C headers) | docs/IMPORT.md |
79
+ | Quality, safety, traceability | quality/QUALITY_POLICY.md, docs/SAFETY.md, docs/CODING_RULES.md |
80
+ | Architecture and design decisions | docs/ARCHITECTURE.md, docs/DECISIONS.md |
81
+
82
+ Development: see CONTRIBUTING.md (`pip install -e ".[dev]"`, `cmake --preset dev && cmake --build --preset dev && ctest
83
+ --preset dev`, all gates: `quality/run_all.sh`).
84
+
85
+ ## License
86
+ The `runtime/` library is MIT-licensed (see runtime/LICENSE): code generated by WireForge belongs to you and depends
87
+ only on this MIT-licensed runtime, so shipping it in your product carries no restriction.
88
+
89
+ Everything else (the CLI, code generator, server, web UI, and MCP server) is licensed under the Business Source
90
+ License 1.1 (see LICENSE): free for development, testing, and evaluation; a commercial license is required for
91
+ production use. It converts to MIT on 2036-10-17. Contact the Licensor for commercial licensing.
@@ -0,0 +1,51 @@
1
+ # WireForge
2
+
3
+ Schema-driven wire-format toolkit for C++17: describe binary messages in YAML (by hand, in the web UI, through the CLI
4
+ or an AI agent) and generate allocation-free C++ serializers, validators, framers and dispatchers together with a
5
+ GoogleTest project that proves them against a reference codec and gates the generated code at 100 % line, branch and
6
+ function coverage.
7
+
8
+ - The wire format is imposed by the remote party: generated code never adds bytes.
9
+ - Runtime and generated code use only pre-allocated storage: no heap, no exceptions, no RTTI required.
10
+ - One service layer behind the `wireforge` CLI, the Python API, the REST server with its web UI and the MCP server.
11
+ - A generated Wireshark Lua dissector shows every interface's frames, with the same checks as the decoder.
12
+ - Zero warnings from every compiler and analyzer; tested on 32/64 bit, little and big endian, x86 and ARM, on Linux,
13
+ Windows, macOS, Android and FreeRTOS; differential testing and fuzzing.
14
+
15
+ The way in is short: `wireforge init` gives a working first project, you change it to your protocol, generate the C++
16
+ code, run the tests and read the results. Nothing about requirements or standards is needed for that.
17
+
18
+ ## For teams with a development or assurance process
19
+ Optionally, `wireforge docs` turns the same model into requirements, design, test and traceability documents with test
20
+ and coverage results, change impact between revisions and ReqIF/CSV exchange with requirements tools (docs/LIFECYCLE.md,
21
+ docs/integration/). For projects that apply standards such as DO-178C, ISO 26262, IEC 61508 or IEC 62304, this evidence
22
+ supports your processes and your tool assessment; it does not by itself give conformity, certification or tool
23
+ qualification, which depend on the whole project and its assessor (docs/DO178C.md).
24
+
25
+ **Start with docs/GUIDE.md** (install, 5-minute quickstart, examples). Reference:
26
+
27
+ | Topic | Document |
28
+ |-------|----------|
29
+ | Definition format | docs/SCHEMA_SPEC.md, `schema/`, `examples/` |
30
+ | Command line and Python API | docs/CLI.md |
31
+ | Generated C++ and test project | docs/CODEGEN.md, docs/RUNTIME.md |
32
+ | Layout analysis / ICD export | docs/LAYOUT.md |
33
+ | Wireshark dissector (Lua plugin) | docs/WIRESHARK.md |
34
+ | Requirements, design and test documents | docs/LIFECYCLE.md |
35
+ | Fitting into a DO-178C process, tool integration | docs/DO178C.md, docs/integration/ |
36
+ | Server, web UI, MCP | docs/SERVER.md, docs/UI.md, docs/MCP.md |
37
+ | Secure deployment and operation | docs/SERVER.md, "Deployment and operation" |
38
+ | Importers (CSV/Excel ICD, C headers) | docs/IMPORT.md |
39
+ | Quality, safety, traceability | quality/QUALITY_POLICY.md, docs/SAFETY.md, docs/CODING_RULES.md |
40
+ | Architecture and design decisions | docs/ARCHITECTURE.md, docs/DECISIONS.md |
41
+
42
+ Development: see CONTRIBUTING.md (`pip install -e ".[dev]"`, `cmake --preset dev && cmake --build --preset dev && ctest
43
+ --preset dev`, all gates: `quality/run_all.sh`).
44
+
45
+ ## License
46
+ The `runtime/` library is MIT-licensed (see runtime/LICENSE): code generated by WireForge belongs to you and depends
47
+ only on this MIT-licensed runtime, so shipping it in your product carries no restriction.
48
+
49
+ Everything else (the CLI, code generator, server, web UI, and MCP server) is licensed under the Business Source
50
+ License 1.1 (see LICENSE): free for development, testing, and evaluation; a commercial license is required for
51
+ production use. It converts to MIT on 2036-10-17. Contact the Licensor for commercial licensing.
@@ -0,0 +1,29 @@
1
+ # CMake package of WireForge: find_package(WireForge) gives
2
+ # - wireforge::runtime: the header-only C++17 runtime;
3
+ # - wireforge_generate(): generates C++ code from a WireForge project at configure time.
4
+ # Works from an installed tree (<prefix>/lib/cmake/WireForge) and from the Python package
5
+ # (`wireforge cmake-dir` prints this directory).
6
+ include_guard(GLOBAL)
7
+
8
+ set(WIREFORGE_INCLUDE_DIR "")
9
+ foreach(candidate "${CMAKE_CURRENT_LIST_DIR}/../runtime/include" "${CMAKE_CURRENT_LIST_DIR}/../../../include"
10
+ "${CMAKE_CURRENT_LIST_DIR}/../../runtime/include")
11
+ if(NOT WIREFORGE_INCLUDE_DIR AND EXISTS "${candidate}/wireforge/status.hpp")
12
+ get_filename_component(WIREFORGE_INCLUDE_DIR "${candidate}" ABSOLUTE)
13
+ endif()
14
+ endforeach()
15
+ if(NOT WIREFORGE_INCLUDE_DIR)
16
+ set(WireForge_FOUND FALSE)
17
+ set(WireForge_NOT_FOUND_MESSAGE "the WireForge runtime headers were not found next to ${CMAKE_CURRENT_LIST_DIR}")
18
+ return()
19
+ endif()
20
+
21
+ if(NOT TARGET wireforge::runtime)
22
+ add_library(wireforge::runtime INTERFACE IMPORTED)
23
+ set_target_properties(wireforge::runtime PROPERTIES
24
+ INTERFACE_INCLUDE_DIRECTORIES "${WIREFORGE_INCLUDE_DIR}"
25
+ INTERFACE_COMPILE_FEATURES cxx_std_17)
26
+ endif()
27
+
28
+ include("${CMAKE_CURRENT_LIST_DIR}/WireForgeGenerate.cmake")
29
+ set(WireForge_FOUND TRUE)
@@ -0,0 +1,15 @@
1
+ # Version of the WireForge CMake package; compatible within the same major version.
2
+ set(PACKAGE_VERSION "0.1.0")
3
+ if(PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION)
4
+ set(PACKAGE_VERSION_COMPATIBLE FALSE)
5
+ else()
6
+ string(REGEX MATCH "^[0-9]+" _wireforge_major "${PACKAGE_VERSION}")
7
+ if(PACKAGE_FIND_VERSION_MAJOR STREQUAL "" OR PACKAGE_FIND_VERSION_MAJOR STREQUAL _wireforge_major)
8
+ set(PACKAGE_VERSION_COMPATIBLE TRUE)
9
+ else()
10
+ set(PACKAGE_VERSION_COMPATIBLE FALSE)
11
+ endif()
12
+ if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
13
+ set(PACKAGE_VERSION_EXACT TRUE)
14
+ endif()
15
+ endif()
@@ -0,0 +1,64 @@
1
+ # wireforge_generate(<target> PROJECT <dir> [OUTPUT <dir>] [PLUGINS <file>] [TESTS])
2
+ #
3
+ # Runs `wireforge generate` at configure time (again whenever a model file changes) and creates
4
+ # the INTERFACE library <target> with the generated headers, the project's user include
5
+ # directories and wireforge::runtime. With TESTS the GoogleTest project is generated too
6
+ # (<OUTPUT>/<project>/generated_tests). The generator is the `wireforge` command, or
7
+ # `python -m wireforge.cli` when only the Python package is found (WIREFORGE_COMMAND overrides).
8
+ include_guard(GLOBAL)
9
+
10
+ function(_wireforge_command result)
11
+ if(WIREFORGE_COMMAND)
12
+ set(${result} ${WIREFORGE_COMMAND} PARENT_SCOPE)
13
+ return()
14
+ endif()
15
+ find_program(WIREFORGE_EXECUTABLE wireforge)
16
+ if(WIREFORGE_EXECUTABLE)
17
+ set(${result} "${WIREFORGE_EXECUTABLE}" PARENT_SCOPE)
18
+ return()
19
+ endif()
20
+ find_package(Python3 REQUIRED COMPONENTS Interpreter)
21
+ set(${result} "${Python3_EXECUTABLE}" -m wireforge.cli PARENT_SCOPE)
22
+ endfunction()
23
+
24
+ function(wireforge_generate target)
25
+ cmake_parse_arguments(PARSE_ARGV 1 arg "TESTS" "PROJECT;OUTPUT;PLUGINS" "")
26
+ if(NOT arg_PROJECT)
27
+ message(FATAL_ERROR "wireforge_generate(${target}): PROJECT is required")
28
+ endif()
29
+ get_filename_component(project "${arg_PROJECT}" ABSOLUTE)
30
+ set(output "${CMAKE_CURRENT_BINARY_DIR}/wireforge_${target}")
31
+ if(arg_OUTPUT)
32
+ get_filename_component(output "${arg_OUTPUT}" ABSOLUTE)
33
+ endif()
34
+ _wireforge_command(command)
35
+ set(arguments generate "${project}" -o "${output}")
36
+ if(arg_TESTS)
37
+ list(APPEND arguments --tests)
38
+ endif()
39
+ if(arg_PLUGINS)
40
+ list(APPEND arguments --plugins "${arg_PLUGINS}")
41
+ endif()
42
+ execute_process(COMMAND ${command} ${arguments} RESULT_VARIABLE failed OUTPUT_VARIABLE log ERROR_VARIABLE log)
43
+ if(failed)
44
+ message(FATAL_ERROR "wireforge_generate(${target}) failed:\n${log}")
45
+ endif()
46
+ file(GLOB_RECURSE model_files CONFIGURE_DEPENDS "${project}/*.yaml")
47
+ set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${model_files})
48
+ execute_process(COMMAND ${command} get "${project}" /user_includes/include_dirs --json
49
+ RESULT_VARIABLE missing OUTPUT_VARIABLE answer ERROR_QUIET)
50
+ set(include_dirs "")
51
+ if(NOT missing)
52
+ string(JSON count LENGTH "${answer}" data value)
53
+ if(count GREATER 0)
54
+ math(EXPR last "${count} - 1")
55
+ foreach(index RANGE ${last})
56
+ string(JSON directory GET "${answer}" data value ${index})
57
+ list(APPEND include_dirs "${project}/${directory}")
58
+ endforeach()
59
+ endif()
60
+ endif()
61
+ add_library(${target} INTERFACE)
62
+ target_include_directories(${target} INTERFACE "${output}" ${include_dirs})
63
+ target_link_libraries(${target} INTERFACE wireforge::runtime)
64
+ endfunction()
@@ -0,0 +1,3 @@
1
+ """WireForge: schema-driven wire-format toolkit."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,177 @@
1
+ """Bundle export and import.
2
+
3
+ A bundle is one ordinary single-file project (no `include`) stamped with the current format plus
4
+ `bundle: {content_hash, source_files}`. The sha256 covers the definition content only: stamps, the
5
+ bundle block and key order do not change it.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import io
12
+ import json
13
+ from pathlib import Path
14
+ from typing import Any, cast
15
+
16
+ from ruamel.yaml import YAML
17
+ from ruamel.yaml.error import YAMLError
18
+
19
+ from wireforge import schema, vectors, versioning
20
+ from wireforge.issues import Issue, errors_only
21
+ from wireforge.loader import SECTIONS, load_project
22
+ from wireforge.plain import to_plain
23
+ from wireforge.validate import Validator, validate_project
24
+
25
+ STAMP_KEYS = frozenset({"schema_version", "schema_minor", "generator", "bundle"})
26
+ ROOT_ONLY_KEYS = ("project", "version", "description", "namespace", "defaults", "user_includes")
27
+ # Standard layout written by import: section -> directory/file (interfaces get one file each).
28
+ LAYOUT = {
29
+ "requirements": "requirements/requirements.yaml",
30
+ "profiles": "profiles/profiles.yaml",
31
+ "messages": "messages/messages.yaml",
32
+ "vectors": "vectors/vectors.yaml",
33
+ }
34
+ TYPE_SECTIONS = ("enums", "types")
35
+ ROOT_SECTIONS = ("defines", "checksums")
36
+
37
+
38
+ class BundleError(Exception):
39
+ """Export or import refused; `issues` explains why."""
40
+
41
+ def __init__(self, message: str, issues: list[Issue] | None = None) -> None:
42
+ """Keep the summary and the detailed issues."""
43
+ super().__init__(message)
44
+ self.issues = issues or []
45
+
46
+
47
+ def content_hash(document: dict[str, Any]) -> str:
48
+ """sha256 over the definition content (stamps and key order excluded)."""
49
+ content = {key: value for key, value in document.items() if key not in STAMP_KEYS}
50
+ canonical = json.dumps(content, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
51
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
52
+
53
+
54
+ def _yaml() -> YAML:
55
+ yaml = YAML(typ="rt")
56
+ yaml.default_flow_style = False
57
+ yaml.width = 120
58
+ return yaml
59
+
60
+
61
+ def _dump(document: dict[str, Any]) -> str:
62
+ stream = io.StringIO()
63
+ _yaml().dump(document, stream)
64
+ return stream.getvalue()
65
+
66
+
67
+ def _stamped(content: dict[str, Any]) -> dict[str, Any]:
68
+ stamp: dict[str, object] = {}
69
+ versioning.stamp(stamp)
70
+ return {**stamp, **content}
71
+
72
+
73
+ def build_bundle(location: Path) -> dict[str, Any]:
74
+ """Merge a valid project into one bundle document; raises BundleError for invalid projects."""
75
+ result = load_project(location)
76
+ issues = list(result.issues)
77
+ if result.project is not None:
78
+ issues += Validator(result.project).run()
79
+ if not errors_only(issues):
80
+ issues += vectors.check(result.project)
81
+ if result.project is None or errors_only(issues):
82
+ message = f"{location}: export refused, the project has errors"
83
+ raise BundleError(message, errors_only(issues))
84
+ root_file = Path(result.project.source.file)
85
+ root = cast("dict[str, Any]", to_plain(result.documents[str(root_file)]))
86
+ content: dict[str, Any] = {key: root[key] for key in ROOT_ONLY_KEYS if key in root}
87
+ for section in SECTIONS:
88
+ merged: dict[str, Any] = {}
89
+ for document in result.documents.values():
90
+ merged.update(cast("dict[str, Any]", to_plain(document.get(section, {}))))
91
+ if merged:
92
+ content[section] = merged
93
+ sources = [Path(file).relative_to(root_file.parent).as_posix() for file in result.documents]
94
+ bundle = _stamped(content)
95
+ bundle["bundle"] = {"content_hash": content_hash(content), "source_files": sources}
96
+ return bundle
97
+
98
+
99
+ def export_bundle(location: Path, output: Path) -> None:
100
+ """Write the bundle of `location` to `output`."""
101
+ output.write_text(_dump(build_bundle(location)), encoding="utf-8")
102
+
103
+
104
+ def _load_bundle(path: Path) -> dict[str, Any]:
105
+ try:
106
+ loaded = YAML(typ="safe").load(path.read_text(encoding="utf-8"))
107
+ except (YAMLError, OSError) as error:
108
+ message = f"{path}: cannot read bundle: {error}"
109
+ raise BundleError(message) from error
110
+ if not isinstance(loaded, dict):
111
+ message = f"{path}: a bundle must be a mapping"
112
+ raise BundleError(message)
113
+ return loaded
114
+
115
+
116
+ def _verify(document: dict[str, Any], path: Path) -> None:
117
+ info = document.get("bundle")
118
+ expected = info.get("content_hash") if isinstance(info, dict) else None
119
+ if expected is None:
120
+ message = f"{path}: not a bundle (no bundle.content_hash); use --no-verify to import it"
121
+ raise BundleError(message)
122
+ actual = content_hash(document)
123
+ if actual != expected:
124
+ message = f"{path}: content hash mismatch (bundle says {expected}, content is {actual})"
125
+ raise BundleError(message)
126
+
127
+
128
+ def _migrate(document: dict[str, Any], path: Path) -> None:
129
+ try:
130
+ versioning.migrate_document(document)
131
+ except (TypeError, ValueError) as error:
132
+ message = f"{path}: {error}"
133
+ raise BundleError(message) from error
134
+
135
+
136
+ def _split(content: dict[str, Any]) -> dict[str, dict[str, Any]]:
137
+ """Distribute a single-file project over the standard layout (relative path -> document)."""
138
+ root = {key: content[key] for key in (*ROOT_ONLY_KEYS, *ROOT_SECTIONS) if key in content}
139
+ files: dict[str, dict[str, Any]] = {}
140
+ types = {section: content[section] for section in TYPE_SECTIONS if section in content}
141
+ if types:
142
+ files["types/types.yaml"] = types
143
+ for section, relative in LAYOUT.items():
144
+ if section in content:
145
+ files[relative] = {section: content[section]}
146
+ for name, interface in content.get("interfaces", {}).items():
147
+ files[f"interfaces/{name}.yaml"] = {"interfaces": {name: interface}}
148
+ directories = sorted({relative.split("/")[0] for relative in files})
149
+ if directories:
150
+ root["include"] = [f"{directory}/*.yaml" for directory in directories]
151
+ result = {schema.ROOT_FILE_NAME: _stamped(root)}
152
+ result.update({relative: _stamped(body) for relative, body in files.items()})
153
+ return result
154
+
155
+
156
+ def import_bundle(
157
+ bundle: Path, destination: Path, *, verify: bool = True, single_file: bool = False
158
+ ) -> list[Issue]:
159
+ """Unpack a bundle into `destination` and return the validation issues of the result."""
160
+ if destination.exists() and any(destination.iterdir()):
161
+ message = f"{destination}: destination is not empty"
162
+ raise BundleError(message)
163
+ document = _load_bundle(bundle)
164
+ if verify:
165
+ _verify(document, bundle)
166
+ _migrate(document, bundle)
167
+ content = {key: value for key, value in document.items() if key not in STAMP_KEYS}
168
+ issues = schema.validate_document(_stamped(content), "project.schema.json", file=str(bundle))
169
+ if errors_only(issues):
170
+ message = f"{bundle}: the bundle is not a valid project"
171
+ raise BundleError(message, issues)
172
+ files = {schema.ROOT_FILE_NAME: _stamped(content)} if single_file else _split(content)
173
+ for relative, body in files.items():
174
+ target = destination / relative
175
+ target.parent.mkdir(parents=True, exist_ok=True)
176
+ target.write_text(_dump(body), encoding="utf-8")
177
+ return validate_project(destination)