aboutsummaryrefslogtreecommitdiff
path: root/tools/interface_generator/bin
diff options
context:
space:
mode:
Diffstat (limited to 'tools/interface_generator/bin')
-rw-r--r--tools/interface_generator/bin/BUILD.bazel45
-rw-r--r--tools/interface_generator/bin/interface_declaration.py58
-rw-r--r--tools/interface_generator/bin/interface_declaration_unittest.py19
-rwxr-xr-xtools/interface_generator/bin/interface_generator.py86
-rw-r--r--tools/interface_generator/bin/templates.py19
-rw-r--r--tools/interface_generator/bin/templates/__c_functions.mako4
-rw-r--r--tools/interface_generator/bin/templates/__c_system_include.mako4
-rw-r--r--tools/interface_generator/bin/templates/__c_types.mako6
-rw-r--r--tools/interface_generator/bin/templates/__header.mako5
-rw-r--r--tools/interface_generator/bin/templates/interface.h.mako8
-rw-r--r--tools/interface_generator/bin/templates/interface_mock.cpp.mako32
-rw-r--r--tools/interface_generator/bin/templates/interface_mock.hpp.mako26
-rw-r--r--tools/interface_generator/bin/templates_unittest.py30
13 files changed, 342 insertions, 0 deletions
diff --git a/tools/interface_generator/bin/BUILD.bazel b/tools/interface_generator/bin/BUILD.bazel
new file mode 100644
index 0000000..04843b9
--- /dev/null
+++ b/tools/interface_generator/bin/BUILD.bazel
@@ -0,0 +1,45 @@
+load("@pip//:requirements.bzl", "requirement")
+load("//private:defs.bzl", "py_pytest")
+
+""" interface declaration """
+
+py_library(
+ name = "interface_declaration",
+ srcs = ["interface_declaration.py"],
+)
+
+""" templates """
+
+py_library(
+ name = "templates",
+ srcs = ["templates.py"],
+ data = glob(["templates/*"]),
+)
+
+""" interface_generator """
+
+py_binary(
+ name = "interface_generator",
+ srcs = [
+ "interface_generator.py",
+ "templates.py",
+ ],
+ imports = ["."],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":interface_declaration",
+ ":templates",
+ requirement("mako"),
+ ],
+)
+
+""" pytest """
+
+py_pytest(
+ name = "pytest",
+ srcs = glob(["*.py"]),
+ data = glob(["templates/*"]),
+ deps = [
+ requirement("mako"),
+ ],
+)
diff --git a/tools/interface_generator/bin/interface_declaration.py b/tools/interface_generator/bin/interface_declaration.py
new file mode 100644
index 0000000..4560bbd
--- /dev/null
+++ b/tools/interface_generator/bin/interface_declaration.py
@@ -0,0 +1,58 @@
+"""
+interface_declaration.py
+"""
+
+from dataclasses import dataclass, asdict
+from pathlib import Path
+
+
+@dataclass
+class InterfaceDeclaration:
+ """interface declaration class"""
+
+ name: str
+ license_hdr: str
+ system_includes: list[str]
+ types: list[dict]
+ functions: list[dict]
+
+ def read_license(self, path: Path):
+ """read and starrify a license"""
+ if path is None:
+ self.license_hdr = ""
+ return
+
+ with open(path, encoding="utf-8") as license_file:
+ self.license_hdr = "".join(
+ [
+ f" * { line.rstrip().ljust(72)[:72] } * \n"
+ for line in license_file.readlines()
+ ]
+ ).rstrip()
+
+ def __init__(self, name: str, license_path: Path):
+ self.name = name if name is not None else "kstdio"
+ self.read_license(license_path)
+ self.system_includes = ["stdarg.h"]
+ self.types = [
+ {
+ "name": "File",
+ "members": [
+ "int fd",
+ "int (*putc)(const struct kstdio_File*, const char)",
+ "int (*puts)(const struct kstdio_File*, const char*)",
+ ],
+ },
+ ]
+ self.functions = [
+ {
+ "name": "printf",
+ "return": "int",
+ "arguments": ["const char* format"],
+ "argument_names": ["format"],
+ },
+ ]
+
+ def into_dict(self) -> dict:
+ """create a dictionary for use in mako"""
+ return asdict(self)
diff --git a/tools/interface_generator/bin/interface_declaration_unittest.py b/tools/interface_generator/bin/interface_declaration_unittest.py
new file mode 100644
index 0000000..b2ad80c
--- /dev/null
+++ b/tools/interface_generator/bin/interface_declaration_unittest.py
@@ -0,0 +1,19 @@
+"""
+interface declaration unit tests
+"""
+
+import unittest
+from interface_declaration import InterfaceDeclaration
+
+
+class InterfaceDeclarationUnittest(unittest.TestCase):
+ """interface_declaration unit tests"""
+
+ def test_interfacedeclaration_class_asdict(self):
+ """test mock interface"""
+ interface = InterfaceDeclaration("kstdio", None)
+ interface_dict = interface.into_dict()
+
+ self.assertEqual(interface.name, "kstdio")
+ self.assertEqual(len(interface.license_hdr), 0)
+ self.assertEqual(interface_dict["name"], "kstdio")
diff --git a/tools/interface_generator/bin/interface_generator.py b/tools/interface_generator/bin/interface_generator.py
new file mode 100755
index 0000000..eab93d7
--- /dev/null
+++ b/tools/interface_generator/bin/interface_generator.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+
+"""
+interface_generator.py
+"""
+
+from argparse import ArgumentParser
+from pathlib import Path
+import sys
+from mako.lookup import TemplateLookup
+from interface_declaration import InterfaceDeclaration
+from templates import get_templates, get_templates_dir
+
+PROG = {
+ "name": "interface_generator",
+ "version": "0.1",
+}
+
+
+def generate_file(
+ template: Path, templates: Path, output, interface: InterfaceDeclaration
+):
+ """generate file using a tempalte and write it to the output location"""
+ lookup = TemplateLookup(directories=[".", templates.absolute()])
+ mako_template = lookup.get_template(str(template.relative_to(templates)))
+ output_name = template.stem.replace("interface", interface.name)
+ print(f"{interface.name} via {template.name} => {output_name}")
+
+ result = mako_template.render(**interface.into_dict(), PROG=PROG)
+
+ if isinstance(output, Path):
+ # print(f"writing to {(output / output_name).absolute()}")
+ with open(output / output_name, "w", encoding="utf-8") as output_file:
+ print(result, file=output_file)
+ else:
+ print(result, file=output)
+
+
+def main():
+ """main function"""
+ parser = ArgumentParser(
+ prog="interface_generator",
+ description="Generate C header and mock files from an interface declaration",
+ )
+ parser.add_argument(
+ "-i",
+ "--interface",
+ type=Path,
+ # required=True,
+ help="path to interface file",
+ )
+ parser.add_argument(
+ "-t",
+ "--templates",
+ type=Path,
+ default=get_templates_dir(),
+ help="templates location",
+ )
+ parser.add_argument(
+ "-l",
+ "--license",
+ type=Path,
+ required=True,
+ help="path to license file",
+ )
+ parser.add_argument(
+ "-o",
+ "--output",
+ type=Path,
+ default=sys.stdout,
+ help="path to output, stdout by default",
+ )
+
+ args = parser.parse_args()
+ # print(args)
+
+ interface = InterfaceDeclaration(args.interface, args.license)
+ # print(interface)
+
+ for template in get_templates(args.templates):
+ # print(template)
+ generate_file(template, args.templates, args.output, interface)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/interface_generator/bin/templates.py b/tools/interface_generator/bin/templates.py
new file mode 100644
index 0000000..9f7539e
--- /dev/null
+++ b/tools/interface_generator/bin/templates.py
@@ -0,0 +1,19 @@
+"""template helper functions"""
+
+from pathlib import Path
+import re
+
+
+TEMPLATE_PATTERN = re.compile(r"^[^_]\S+\.mako$")
+
+
+def get_templates_dir(name="templates") -> Path:
+ """get the templates directory"""
+ return Path(__file__).parent / name
+
+
+def get_templates(path: Path) -> list[Path]:
+ """list templates in given path"""
+ result = list(path.glob("*.mako"))
+ result = [item for item in result if TEMPLATE_PATTERN.match(item.name)]
+ return result
diff --git a/tools/interface_generator/bin/templates/__c_functions.mako b/tools/interface_generator/bin/templates/__c_functions.mako
new file mode 100644
index 0000000..2e40119
--- /dev/null
+++ b/tools/interface_generator/bin/templates/__c_functions.mako
@@ -0,0 +1,4 @@
+/* Functions */
+% for fn in functions:
+${fn['return']} ${name}_${fn['name']}(${ ", ".join(fn['arguments']) });
+% endfor
diff --git a/tools/interface_generator/bin/templates/__c_system_include.mako b/tools/interface_generator/bin/templates/__c_system_include.mako
new file mode 100644
index 0000000..d6a9d09
--- /dev/null
+++ b/tools/interface_generator/bin/templates/__c_system_include.mako
@@ -0,0 +1,4 @@
+/* System includes */
+% for path in system_includes:
+#include <${path}>
+% endfor
diff --git a/tools/interface_generator/bin/templates/__c_types.mako b/tools/interface_generator/bin/templates/__c_types.mako
new file mode 100644
index 0000000..ce6b6b5
--- /dev/null
+++ b/tools/interface_generator/bin/templates/__c_types.mako
@@ -0,0 +1,6 @@
+/* Types */
+% for type in types:
+typedef struct ${name}_${type['name']} {
+${ "\n".join([ " {};".format(member) for member in type['members'] ]) }
+} ${name}_${type['name']};
+% endfor
diff --git a/tools/interface_generator/bin/templates/__header.mako b/tools/interface_generator/bin/templates/__header.mako
new file mode 100644
index 0000000..43ce1e7
--- /dev/null
+++ b/tools/interface_generator/bin/templates/__header.mako
@@ -0,0 +1,5 @@
+/* This file is generated by ${PROG['name']} v${PROG['version']} */
+
+/******************************************************************************
+${ license_hdr }
+ ******************************************************************************/
diff --git a/tools/interface_generator/bin/templates/interface.h.mako b/tools/interface_generator/bin/templates/interface.h.mako
new file mode 100644
index 0000000..47ea940
--- /dev/null
+++ b/tools/interface_generator/bin/templates/interface.h.mako
@@ -0,0 +1,8 @@
+<%include file="__header.mako" />
+#ifndef ${ name.upper() }
+#define ${ name.upper() }
+
+<%include file="__c_system_include.mako" />
+<%include file="__c_types.mako" />
+<%include file="__c_functions.mako" />
+#endif /* ${ name.upper() } */
diff --git a/tools/interface_generator/bin/templates/interface_mock.cpp.mako b/tools/interface_generator/bin/templates/interface_mock.cpp.mako
new file mode 100644
index 0000000..721f2c4
--- /dev/null
+++ b/tools/interface_generator/bin/templates/interface_mock.cpp.mako
@@ -0,0 +1,32 @@
+<%include file="__header.mako" />
+
+#include <stdexcept>
+#include "${name}_mock.hpp"
+
+static I${name}_mock *s_instance = nullptr;
+
+I${name}_mock::I${name}_mock()
+{
+ if(s_instance != nullptr)
+ {
+ throw std::runtime_error("Creating a second instance of mock object");
+ }
+ s_instance = this;
+}
+
+I${name}_mock::~I${name}_mock()
+{
+ // destructors shouldn't throw exceptions
+ s_instance = nullptr;
+}
+
+% for fn in functions:
+${fn['return']} ${name}_${fn['name']}(${ ", ".join(fn['arguments']) })
+{
+ if(s_instance == nullptr)
+ {
+ throw std::runtime_error("No mock created to handle function ${name}_${fn['name']}");
+ }
+ return s_instance->${fn['name']}(${ ", ".join(fn['argument_names']) });
+}
+% endfor
diff --git a/tools/interface_generator/bin/templates/interface_mock.hpp.mako b/tools/interface_generator/bin/templates/interface_mock.hpp.mako
new file mode 100644
index 0000000..e33d50e
--- /dev/null
+++ b/tools/interface_generator/bin/templates/interface_mock.hpp.mako
@@ -0,0 +1,26 @@
+<%include file="__header.mako" />
+#ifndef ${ name.upper() }_MOCK
+#define ${ name.upper() }_MOCK
+
+extern "C" {
+#include "${ name }.h"
+}
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+class I${name}_mock
+{
+public:
+ I${name}_mock();
+ ~I${name}_mock();
+
+ /* Functions */
+% for fn in functions:
+ MOCK_METHOD(${fn['return']}, ${fn['name']}, (${ ", ".join(fn['arguments']) }));
+% endfor
+};
+
+using ${name}_mock = ::testing::NiceMock<I${name}_mock>;
+
+#endif /* ${ name.upper() }_MOCK */
diff --git a/tools/interface_generator/bin/templates_unittest.py b/tools/interface_generator/bin/templates_unittest.py
new file mode 100644
index 0000000..acb1685
--- /dev/null
+++ b/tools/interface_generator/bin/templates_unittest.py
@@ -0,0 +1,30 @@
+"""
+templates unit tests
+"""
+
+import unittest
+from pathlib import Path
+import templates
+
+
+class TemplatesUnittest(unittest.TestCase):
+ """templates unit tests"""
+
+ def test_get_templates_dir_is_valid(self):
+ """verify that default templates dir is valid"""
+ path = templates.get_templates_dir()
+
+ self.assertTrue(isinstance(path, Path))
+ self.assertTrue(path.exists())
+ self.assertTrue(path.is_dir())
+
+ def test_get_templates(self):
+ """verify that templates are available by default"""
+ path = templates.get_templates_dir()
+ result = templates.get_templates(path)
+
+ self.assertGreater(len(result), 0)
+ for template in result:
+ self.assertTrue(isinstance(template, Path))
+ self.assertTrue(template.exists())
+ self.assertTrue(template.is_file())