Skip to content

Commit 3bcb47b

Browse files
LM-robynkasveinse
authored andcommitted
Add support for custom code generators (#66)
1 parent ebaf773 commit 3bcb47b

6 files changed

Lines changed: 70 additions & 3 deletions

File tree

‎src/objdictgen/__main__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None):
141141
help="Store in internal format (json only)")
142142
subp.add_argument('--no-sort', action="store_true",
143143
help="Don't order of parameters in output OD")
144+
subp.add_argument('--generate-with', type=str, help="Pass a .py file for custom code generation")
144145

145146
# -- DIFF --
146147
subp = subparser.add_parser('diff', parents=[common_opts], help="""
@@ -263,7 +264,7 @@ def main(debugopts: DebugOpts, args: Sequence[str]|None = None):
263264
od.DumpFile(opts.out,
264265
filetype=opts.type,
265266
# These additional options are only used for JSON output
266-
sort=not opts.no_sort, internal=opts.internal, validate=not opts.novalidate
267+
sort=not opts.no_sort, internal=opts.internal, validate=not opts.novalidate, custom_genfile=opts.generate_with
267268
)
268269

269270

‎src/objdictgen/node.py‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
import copy
2323
import logging
24+
import importlib.util
25+
import sys
26+
import os
27+
2428
from pathlib import Path
2529
from typing import Any, Generator, Iterable, Iterator
2630

@@ -33,6 +37,26 @@
3337
log = logging.getLogger('objdictgen')
3438

3539

40+
def executeCustomGenerator(filename: str, filepath: TPath, node: NodeProtocol) -> None:
41+
"""Execute custom python file for code generation"""
42+
parent_dir = os.path.dirname(os.path.abspath(filename))
43+
sys.path.append(parent_dir)
44+
spec = importlib.util.spec_from_file_location("CustomGenerator", filename)
45+
customModule = importlib.util.module_from_spec(spec)
46+
sys.modules["CustomGenerator"] = customModule
47+
# Handle errors coming from custom code
48+
try:
49+
spec.loader.exec_module(customModule)
50+
except Exception as e:
51+
print(f"error executing {filename}: {e}")
52+
return
53+
54+
if hasattr(customModule, 'GenerateFile'):
55+
customModule.GenerateFile(filepath, node)
56+
else:
57+
raise AttributeError(f"{filename} does not have a 'GenerateFile' function.")
58+
59+
3660
# ------------------------------------------------------------------------------
3761
# Definition of Node Object
3862
# ------------------------------------------------------------------------------
@@ -192,7 +216,7 @@ def LoadJson(contents: str, validate=True) -> Node:
192216
""" Import a new Node from a JSON string """
193217
return jsonod.generate_node(contents, validate=validate)
194218

195-
def DumpFile(self, filepath: TPath, filetype: str|None = "jsonc", **kwargs):
219+
def DumpFile(self, filepath: TPath, filetype: str|None = "jsonc", custom_genfile: TPath|None = None, **kwargs):
196220
""" Save node into file """
197221

198222
# Attempt to determine the filetype from the filepath
@@ -223,6 +247,15 @@ def DumpFile(self, filepath: TPath, filetype: str|None = "jsonc", **kwargs):
223247
with open(filepath, "w", encoding="utf-8") as f:
224248
f.write(jdata)
225249
return
250+
251+
if custom_genfile is not None:
252+
log.debug("Invoking custom generator: %s", custom_genfile)
253+
try:
254+
executeCustomGenerator(custom_genfile, filepath, self)
255+
return
256+
except (ImportError, ModuleNotFoundError):
257+
log.debug("Failed to load custom generator: %s", custom_genfile)
258+
raise
226259

227260
if filetype == 'c':
228261
log.debug("Writing C files '%s'", filepath)

‎tests/conftest.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ def basepath():
282282
""" Fixture returning the base of the project """
283283
return (HERE / '..').resolve()
284284

285+
@pytest.fixture
286+
def testspath():
287+
""" Fixture returning the path to the tests directory """
288+
return (HERE).resolve()
289+
285290

286291
@pytest.fixture
287292
def fn():
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Example of an invalid .py file you could pass with the --generate-with arg.
2+
In this case the function name does not match the expected name"""
3+
4+
from objdictgen.typing import NodeProtocol, TPath
5+
6+
def Generate_File(filepath: TPath, node: NodeProtocol):
7+
return ""

‎tests/test_generators/generator.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Example of a valid .py file you could pass with the --generate-with arg"""
2+
3+
from objdictgen.typing import NodeProtocol, TPath
4+
5+
def GenerateFile(filepath: TPath, node: NodeProtocol):
6+
print("success")

‎tests/test_node.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
"""Tests for node.py"""
22
import pytest
33

4-
from objdictgen.node import Node
4+
from objdictgen.node import Node, NodeProtocol, executeCustomGenerator
55

66
@pytest.mark.parametrize("file", ['master', 'slave'])
77
def test_node_LoadFile(odpath, file):
88

99
od = Node.LoadFile(odpath / (file + '.json'))
1010
assert isinstance(od, Node)
1111
assert od.Name == 'master' if file == 'master' else 'slave'
12+
13+
@pytest.mark.parametrize("filename", ["this_file_does_not_exist.py", "bad_generator.py", "generator.py"])
14+
def test_node_execute_custom_generator(filename: str, testspath):
15+
full_path = testspath.joinpath("test_generators").joinpath(filename)
16+
mock_path = "/mock"
17+
if filename == "bad_generator.py":
18+
with pytest.raises(AttributeError):
19+
executeCustomGenerator(full_path, mock_path, NodeProtocol)
20+
elif filename == "this_file_does_not_exist.py":
21+
executeCustomGenerator(full_path, mock_path, NodeProtocol)
22+
elif filename == "generator.py":
23+
try:
24+
executeCustomGenerator(full_path, mock_path, NodeProtocol) == "success"
25+
except Exception:
26+
pytest.fail("failed to execute generator.py")

0 commit comments

Comments
 (0)