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.bazel57
-rw-r--r--tools/interface_generator/bin/interface_definition.py47
-rwxr-xr-xtools/interface_generator/bin/interface_generator.py84
-rw-r--r--tools/interface_generator/bin/templates.py12
-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
-rwxr-xr-xtools/interface_generator/bin/templates_unittest.py26
12 files changed, 311 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..c593b93
--- /dev/null
+++ b/tools/interface_generator/bin/BUILD.bazel
@@ -0,0 +1,57 @@
+load("@pip//:requirements.bzl", "requirement")
+load("//private:defs.bzl", "py_pytest")
+
+""" interface definition """
+
+py_library(
+ name = "interface_definition",
+ srcs = ["interface_definition.py"],
+)
+
+""" templates """
+
+py_library(
+ name = "templates",
+ srcs = ["templates.py"],
+ data = glob(["templates/*"]),
+)
+
+py_test(
+ name = "templates_unittest",
+ srcs = ["templates_unittest.py"],
+ imports = ["."],
+ deps = [":templates"],
+)
+
+""" interface_generator """
+
+py_binary(
+ name = "interface_generator",
+ srcs = [
+ "interface_generator.py",
+ "templates.py",
+ ],
+ data = ["//:LICENSE.md"],
+ imports = ["."],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":interface_definition",
+ ":templates",
+ requirement("mako"),
+ ],
+)
+
+""" pytest """
+
+py_pytest(
+ name = "pytest",
+ srcs = [
+ "interface_definition.py",
+ "interface_generator.py",
+ "templates.py",
+ ],
+ deps = [
+ ":interface_generator",
+ requirement("mako"),
+ ],
+)
diff --git a/tools/interface_generator/bin/interface_definition.py b/tools/interface_generator/bin/interface_definition.py
new file mode 100644
index 0000000..3b9c4a3
--- /dev/null
+++ b/tools/interface_generator/bin/interface_definition.py
@@ -0,0 +1,47 @@
+"""
+interface_definition.py
+"""
+
+
+def __read_license(path):
+ """read and starrify a license"""
+ license_text = ""
+ with open(path, encoding="utf-8") as license_file:
+ license_text = "".join(
+ [
+ f" * { line.rstrip().ljust(72)[:72] } * \n"
+ for line in license_file.readlines()
+ ]
+ ).rstrip()
+
+ return license_text
+
+
+def parse(args):
+ """return a mock interface definition"""
+
+ interface_dict = {
+ "name": "kstdio",
+ "license": __read_license(args.license),
+ "system_includes": ["stdarg.h"],
+ "types": [
+ {
+ "name": "File",
+ "members": [
+ "int fd",
+ "int (*putc)(const struct kstdio_File*, const char)",
+ "int (*puts)(const struct kstdio_File*, const char*)",
+ ],
+ },
+ ],
+ "functions": [
+ {
+ "name": "printf",
+ "return": "int",
+ "arguments": ["const char* format"],
+ "argument_names": ["format"],
+ },
+ ],
+ }
+
+ return interface_dict
diff --git a/tools/interface_generator/bin/interface_generator.py b/tools/interface_generator/bin/interface_generator.py
new file mode 100755
index 0000000..90d03ba
--- /dev/null
+++ b/tools/interface_generator/bin/interface_generator.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+
+"""
+interface_generator.py
+"""
+
+from argparse import ArgumentParser
+from pathlib import Path
+import sys
+from mako.lookup import TemplateLookup
+from interface_definition import parse as parse_interface
+from templates import get_templates
+
+PROG = {
+ "name": "interface_generator",
+ "version": "0.1",
+}
+
+
+def generate_file(template: Path, templates: Path, output, kwargs):
+ """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)))
+ result = mako_template.render(**kwargs, PROG=PROG)
+
+ output_name = template.stem.replace("interface", kwargs["name"])
+ print(f'{kwargs["name"]} via {template.name} => {output_name}')
+
+ 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 definition",
+ )
+ parser.add_argument(
+ "-i",
+ "--interface",
+ type=Path,
+ # required=True,
+ help="path to interface file",
+ )
+ parser.add_argument(
+ "-t",
+ "--templates",
+ type=Path,
+ default=Path(sys.argv[0]).parent / "templates",
+ 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 = parse_interface(args)
+ # 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..d6a6bb5
--- /dev/null
+++ b/tools/interface_generator/bin/templates.py
@@ -0,0 +1,12 @@
+"""template helper functions"""
+
+from pathlib import Path
+import re
+
+
+def get_templates(path: Path):
+ """list templates in given path"""
+ template_pattern = re.compile(r"^[^_]\S+\.mako$")
+ 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..24b0381
--- /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 }
+ ******************************************************************************/
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 100755
index 0000000..feb418a
--- /dev/null
+++ b/tools/interface_generator/bin/templates_unittest.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+
+import sys
+import unittest
+import templates
+from pathlib import Path
+
+
+class Templates(unittest.TestCase):
+ def test_templates_dir_in_argv_0(self):
+ path = Path(sys.argv[0]).parent
+ self.assertTrue("templates" in [ item.name for item in path.iterdir() ])
+
+ def test_get_templates(self):
+ path = Path(sys.argv[0]).parent / "templates"
+ 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())
+
+
+if __name__ == "__main__":
+ unittest.main()