Skip to content

Commit 93a655d

Browse files
ischoeglspeth
authored andcommitted
[sourcegen] Use Jinja templates
1 parent 713ada2 commit 93a655d

File tree

6 files changed

+163
-185
lines changed

6 files changed

+163
-185
lines changed

interfaces/sourcegen/sourcegen/_helpers.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
# at https://cantera.org/license.txt for license and copyright information.
33

44
from dataclasses import fields
5-
import inspect
65
from pathlib import Path
7-
import textwrap
86
try:
97
from ruamel import yaml
108
except ImportError:
@@ -25,30 +23,3 @@ def with_unpack_iter(cls: type) -> type:
2523
cls.__iter__ = lambda self: (getattr(self, f.name) for f in fields(self))
2624

2725
return cls
28-
29-
30-
def hanging_text(text: str, spaces: int) -> str:
31-
ret = ("\n" + " "*spaces).join(text.split("\n"))
32-
return "\n".join([line.rstrip() for line in ret.split("\n")])
33-
34-
35-
def normalize_indent(code: str) -> str:
36-
code = textwrap.dedent(code).strip()
37-
38-
call_line = inspect.stack()[1].code_context[0]
39-
40-
indent = len(call_line) - len(call_line.lstrip())
41-
42-
# If called inside a string interpolation, indent to the rest of the block.
43-
# Look for the opening brace for the interpolation, which isn"t perfect, but works.
44-
# This will fire for lines such as “ {normalize(my_str)}”
45-
if call_line[indent] == "{":
46-
code = textwrap.indent(code, " " * indent)
47-
48-
code = code[indent:]
49-
50-
return code
51-
52-
53-
def get_preamble() -> str:
54-
return Path(__file__).parent.joinpath("preamble.txt").read_text("utf-8").strip()

interfaces/sourcegen/sourcegen/_orchestrate.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ def generate_source(lang: str, out_dir: str=""):
3434

3535
module = importlib.import_module(__package__ + "." + lang)
3636
root = Path(module.__file__).parent
37-
config = read_config(root.joinpath("config.yaml"))
37+
config = read_config(root / "config.yaml")
38+
templates = read_config(root / "templates.yaml")
3839
ignore_files: List[str] = config.pop("ignore_files", [])
3940
ignore_funcs: Dict[str, List[str]] = config.pop("ignore_funcs", {})
4041

@@ -43,7 +44,7 @@ def generate_source(lang: str, out_dir: str=""):
4344
# find and instantiate the language-specific SourceGenerator
4445
_, scaffolder_type = inspect.getmembers(module,
4546
lambda m: inspect.isclass(m) and issubclass(m, SourceGenerator))[0]
46-
scaffolder: SourceGenerator = scaffolder_type(out_dir, config)
47+
scaffolder: SourceGenerator = scaffolder_type(out_dir, config, templates)
4748

4849
scaffolder.generate_source(files)
4950
_logger.info("Done.")

interfaces/sourcegen/sourcegen/csharp/_CSharpSourceGenerator.py

Lines changed: 57 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,26 @@
11
# This file is part of Cantera. See License.txt in the top-level directory or
22
# at https://cantera.org/license.txt for license and copyright information.
33

4-
from itertools import starmap
54
from pathlib import Path
65
import sys
76
import logging
87
from typing import List, Dict
98
import re
10-
import textwrap
9+
10+
from jinja2 import Environment, BaseLoader
1111

1212
from ._dataclasses import CsFunc
1313
from ._Config import Config
14-
from .._helpers import normalize_indent, hanging_text
1514
from .._dataclasses import Func, Param, HeaderFile, ArgList
1615
from .._SourceGenerator import SourceGenerator
1716

1817

1918
_logger = logging.getLogger()
19+
_loader = Environment(loader=BaseLoader)
2020

2121
class CSharpSourceGenerator(SourceGenerator):
2222
"""The SourceGenerator for scaffolding C# files for the .NET interface"""
2323

24-
def _get_interop_func_text(self, func: CsFunc) -> str:
25-
ret = f"{self._config.func_prolog} "
26-
if func.unsafe():
27-
ret += "unsafe "
28-
ret += f"{func.declaration()};" # function text
29-
return ret
30-
31-
@staticmethod
32-
def _get_base_handle_text(class_name: str, release_func_name: str) -> str:
33-
handle = normalize_indent(f"""
34-
class {class_name} : CanteraHandle
35-
{{
36-
protected override bool ReleaseHandle() =>
37-
LibCantera.{release_func_name}(Value) == InteropConsts.Success;
38-
}}
39-
""")
40-
41-
return handle
42-
43-
@staticmethod
44-
def _get_derived_handle_text(derived_class_name: str, base_class_name: str) -> str:
45-
derived_text = f"""class {derived_class_name} : {base_class_name} {{ }}"""
46-
47-
return derived_text
48-
49-
5024
def _get_property_text(self, clib_area: str, c_name: str, cs_name: str,
5125
known_funcs: Dict[str, CsFunc]) -> str:
5226
getter = known_funcs.get(clib_area + "_" + c_name)
@@ -61,58 +35,37 @@ def _get_property_text(self, clib_area: str, c_name: str, cs_name: str,
6135
# from which we determine the appropriate C# type
6236
prop_type = self._config.prop_type_crosswalk[getter.arglist[-1].p_type]
6337

64-
setter = known_funcs.get(clib_area + "_set" + c_name.capitalize())
38+
setter = known_funcs.get(
39+
clib_area + "_set" + c_name.capitalize(),
40+
CsFunc("", "", "", "", ""))
6541

6642
if prop_type in ["int", "double"]:
67-
text = f"""
68-
public {prop_type} {cs_name}
69-
{{
70-
get => InteropUtil.CheckReturn(
71-
LibCantera.{getter.name}(_handle));"""
72-
73-
if setter:
74-
text += f"""
75-
set => InteropUtil.CheckReturn(
76-
LibCantera.{setter.name}(_handle, value));"""
77-
78-
text += """
79-
}
80-
"""
81-
elif prop_type == "string":
82-
p_type = getter.arglist[1].p_type
43+
template = _loader.from_string(self._templates["csharp-property-int-double"])
44+
return template.render(
45+
prop_type=prop_type, cs_name=cs_name,
46+
getter=getter.name, setter=setter.name)
8347

48+
if prop_type == "string":
8449
# for get-string type functions we need to look up the type of the second
8550
# (index 1) param for a cast because sometimes it"s an int and other times
8651
# its a nuint (size_t)
87-
text = f"""
88-
public unsafe string {cs_name}
89-
{{
90-
get => InteropUtil.GetString(40, (length, buffer) =>
91-
LibCantera.{getter.name}(_handle, ({p_type}) length, buffer));
92-
"""
93-
94-
if setter:
95-
text += f"""
96-
set => InteropUtil.CheckReturn(
97-
LibCantera.{setter.name}(_handle, value));"""
98-
99-
text += """
100-
}
101-
"""
102-
else:
103-
_logger.critical(f"Unable to scaffold properties of type {prop_type!r}!")
104-
sys.exit(1)
52+
template = _loader.from_string(self._templates["csharp-property-string"])
53+
return template.render(
54+
cs_name=cs_name, p_type=getter.arglist[1].p_type,
55+
getter=getter.name, setter=setter.name)
10556

106-
return normalize_indent(text)
57+
_logger.critical(f"Unable to scaffold properties of type {prop_type!r}!")
58+
sys.exit(1)
10759

108-
def __init__(self, out_dir: str, config: dict):
60+
def __init__(self, out_dir: str, config: dict, templates: dict):
10961
if not out_dir:
11062
_logger.critical("Non-empty string identifying output path required.")
11163
sys.exit(1)
11264
self._out_dir = Path(out_dir)
11365

11466
# use the typed config
11567
self._config = Config.from_parsed(**config)
68+
self._templates = templates
11669

11770
def _get_wrapper_class_name(self, clib_area: str) -> str:
11871
return self._config.class_crosswalk[clib_area]
@@ -197,94 +150,59 @@ def _convert_func(self, parsed: Func) -> CsFunc:
197150

198151
return func
199152

200-
def _write_file(self, filename: str, contents: str):
201-
_logger.info(f" writing {filename!r}")
153+
def _write_file(self, file_name: str, template_name: str, **kwargs) -> None:
154+
_logger.info(f" writing {file_name!r}")
155+
template = _loader.from_string(self._templates["csharp-preamble"])
156+
preamble = template.render(file_name=file_name)
202157

203-
self._out_dir.joinpath(filename).write_text(contents, encoding="utf-8")
158+
template = _loader.from_string(self._templates[template_name])
159+
contents = template.render(preamble=preamble, **kwargs)
204160

205-
def _scaffold_interop(self, header_file_path: Path, cs_funcs: List[CsFunc]):
206-
functions_text = "\n\n".join(map(self._get_interop_func_text, cs_funcs))
207-
208-
interop_text = textwrap.dedent(f"""
209-
{hanging_text(self._config.preamble, 12)}
210-
211-
using System.Runtime.InteropServices;
212-
213-
namespace Cantera.Interop;
161+
self._out_dir.joinpath(file_name).write_text(contents, encoding="utf-8")
214162

215-
static partial class LibCantera
216-
{{
217-
{hanging_text(functions_text, 16)}
218-
}}
219-
""").lstrip()
163+
def _scaffold_interop(self, header_file_path: Path, cs_funcs: List[CsFunc]):
164+
template = _loader.from_string(self._templates["csharp-interop-func"])
165+
function_list = [
166+
template.render(unsafe=func.unsafe(), declaration=func.declaration())
167+
for func in cs_funcs]
220168

221-
self._write_file("Interop.LibCantera." + header_file_path.name + ".g.cs",
222-
interop_text)
169+
file_name = "Interop.LibCantera." + header_file_path.name + ".g.cs"
170+
self._write_file(
171+
file_name, "csharp-scaffold-interop", cs_functions=function_list)
223172

224173
def _scaffold_handles(self, header_file_path: Path, handles: Dict[str, str]):
225-
handles_text = "\n\n".join(starmap(self._get_base_handle_text, handles.items()))
226-
227-
handles_text = textwrap.dedent(f"""
228-
{hanging_text(self._config.preamble, 12)}
229-
230-
namespace Cantera.Interop;
231-
232-
{hanging_text(handles_text, 12)}
233-
""").lstrip()
174+
template = _loader.from_string(self._templates["csharp-base-handle"])
175+
handle_list = [
176+
template.render(class_name=key, release_func_name=val)
177+
for key, val in handles.items()]
234178

235-
self._write_file("Interop.Handles." + header_file_path.name + ".g.cs",
236-
handles_text)
179+
file_name = "Interop.Handles." + header_file_path.name + ".g.cs"
180+
self._write_file(
181+
file_name, "csharp-scaffold-handles", cs_handles=handle_list)
237182

238183
def _scaffold_derived_handles(self):
239-
derived_handles = "\n\n".join(starmap(self._get_derived_handle_text,
240-
self._config.derived_handles.items()))
184+
template = _loader.from_string(self._templates["csharp-derived-handle"])
185+
handle_list = [
186+
template.render(derived_class_name=key, base_class_name=val)
187+
for key, val in self._config.derived_handles.items()]
241188

242-
derived_handles_text = textwrap.dedent(f"""
243-
{hanging_text(self._config.preamble, 12)}
244-
245-
namespace Cantera.Interop;
246-
247-
{hanging_text(derived_handles, 12)}
248-
""").lstrip()
249-
250-
self._write_file("Interop.Handles.g.cs", derived_handles_text)
189+
file_name = "Interop.Handles.g.cs"
190+
self._write_file(
191+
file_name, "csharp-scaffold-handles", cs_handles=handle_list)
251192

252193
def _scaffold_wrapper_class(self, clib_area: str, props: Dict[str, str],
253194
known_funcs: Dict[str, CsFunc]):
254-
wrapper_class_name = self._get_wrapper_class_name(clib_area)
255-
handle_class_name = self._get_handle_class_name(clib_area)
256-
257-
properties_text = "\n\n".join(
195+
property_list = [
258196
self._get_property_text(clib_area, c_name, cs_name, known_funcs)
259-
for (c_name, cs_name) in props.items())
260-
261-
wrapper_class_text = textwrap.dedent(f"""
262-
{hanging_text(self._config.preamble, 12)}
263-
264-
using Cantera.Interop;
197+
for c_name, cs_name in props.items()]
265198

266-
namespace Cantera;
267-
268-
public partial class {wrapper_class_name} : IDisposable
269-
{{
270-
readonly {handle_class_name} _handle;
271-
272-
#pragma warning disable CS1591
273-
274-
{hanging_text(properties_text, 16)}
275-
276-
#pragma warning restore CS1591
277-
278-
/// <summary>
279-
/// Frees the underlying resources used by the
280-
/// native Cantera library for this instance.
281-
/// </summary>
282-
public void Dispose() =>
283-
_handle.Dispose();
284-
}}
285-
""").lstrip()
286-
287-
self._write_file(wrapper_class_name + ".g.cs", wrapper_class_text)
199+
wrapper_class_name = self._get_wrapper_class_name(clib_area)
200+
handle_class_name = self._get_handle_class_name(clib_area)
201+
file_name = wrapper_class_name + ".g.cs"
202+
self._write_file(
203+
file_name, "csharp-scaffold-wrapper-class",
204+
wrapper_class_name=wrapper_class_name, handle_class_name=handle_class_name,
205+
cs_properties=property_list)
288206

289207
def generate_source(self, headers_files: List[HeaderFile]):
290208
self._out_dir.mkdir(parents=True, exist_ok=True)

interfaces/sourcegen/sourcegen/csharp/_Config.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,12 @@
44
from dataclasses import dataclass
55
from typing import Dict
66

7-
from .._helpers import get_preamble, normalize_indent
8-
97

108
@dataclass(frozen=True)
119
class Config:
1210
"""Provides configuration info for the CSharpSourceGenerator class"""
1311

1412

15-
func_prolog = normalize_indent("""
16-
[DllImport(LibFile)]
17-
public static extern
18-
""")
19-
2013
ret_type_crosswalk = {
2114
"const char*": "string",
2215
"const double*": "double[]",
@@ -30,9 +23,6 @@ class Config:
3023
"double*": "double[]"
3124
}
3225

33-
# Reformat preamble to standard comment block
34-
preamble = "\n * ".join(["/*"] + get_preamble().split("\n")) + "\n */"
35-
3626
# These we load from the parsed YAML config file
3727
class_crosswalk: Dict[str, str]
3828

0 commit comments

Comments
 (0)