Skip to content

Commit 8d17c4c

Browse files
committed
scripts: cogeno: add inline code generation
Cogeno is a file generation tool. It uses pieces of Python or Jinja2 script code in the source file as generators to generate whatever text is needed. Cogeno is based on Cog. All files taken from Cog are modified. The files are marked by the Cog author's copyright: Copyright 2004-2016, Ned Batchelder. http://nedbatchelder.com/code/cog SPDX-License-Identifier: MIT Origin: https://pypi.python.org/pypi/cogapp Status: 2.5.1 Description: A code generator for executing Python snippets in source files. Dependencies: python URL: https://pypi.python.org/pypi/cogapp commit: 2.5.1 Maintained-by: External License: MIT License Link: URL: https://pypi.python.org/pypi/cogapp Signed-off-by: Bobby Noelte <[email protected]>
1 parent ccad9d0 commit 8d17c4c

25 files changed

+3048
-0
lines changed

scripts/cogeno/.gitignore

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
_build/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
share/python-wheels/
25+
*.egg-info/
26+
.installed.cfg
27+
*.egg
28+
MANIFEST
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
.hypothesis/
51+
.pytest_cache/
52+
53+
# Translations
54+
*.mo
55+
*.pot
56+
57+
# Django stuff:
58+
*.log
59+
local_settings.py
60+
db.sqlite3
61+
62+
# Flask stuff:
63+
instance/
64+
.webassets-cache
65+
66+
# Scrapy stuff:
67+
.scrapy
68+
69+
# Sphinx documentation
70+
docs/_build/
71+
72+
# PyBuilder
73+
target/
74+
75+
# Jupyter Notebook
76+
.ipynb_checkpoints
77+
78+
# IPython
79+
profile_default/
80+
ipython_config.py
81+
82+
# pyenv
83+
.python-version
84+
85+
# celery beat schedule file
86+
celerybeat-schedule
87+
88+
# SageMath parsed files
89+
*.sage.py
90+
91+
# Environments
92+
.env
93+
.venv
94+
env/
95+
venv/
96+
ENV/
97+
env.bak/
98+
venv.bak/
99+
100+
# Spyder project settings
101+
.spyderproject
102+
.spyproject
103+
104+
# Rope project settings
105+
.ropeproject
106+
107+
# mkdocs documentation
108+
/site
109+
110+
# mypy
111+
.mypy_cache/
112+
.dmypy.json
113+
dmypy.json
114+
115+
# Pyre type checker
116+
.pyre/

scripts/cogeno/cogeno/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#
2+
# Copyright (c) 2017 Bobby Noelte
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
# Empty to allow all modules to be imported

scripts/cogeno/cogeno/cmake.py

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Copyright (c) 2018 Open Source Foundries Limited.
2+
# Copyright (c) 2018 Bobby Noelte.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
# CMakeCacheEntry and CMakeCache are taken from scripts/zephyr_run.py.
7+
#
8+
9+
import os
10+
import sys
11+
import re
12+
from collections import OrderedDict
13+
from pathlib import Path
14+
15+
16+
class CMakeCacheEntry:
17+
'''Represents a CMake cache entry.
18+
This class understands the type system in a CMakeCache.txt, and
19+
converts the following cache types to Python types:
20+
Cache Type Python type
21+
---------- -------------------------------------------
22+
FILEPATH str
23+
PATH str
24+
STRING str OR list of str (if ';' is in the value)
25+
BOOL bool
26+
INTERNAL str OR list of str (if ';' is in the value)
27+
---------- -------------------------------------------
28+
'''
29+
30+
# Regular expression for a cache entry.
31+
#
32+
# CMake variable names can include escape characters, allowing a
33+
# wider set of names than is easy to match with a regular
34+
# expression. To be permissive here, use a non-greedy match up to
35+
# the first colon (':'). This breaks if the variable name has a
36+
# colon inside, but it's good enough.
37+
CACHE_ENTRY = re.compile(
38+
r'''(?P<name>.*?) # name
39+
:(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
40+
=(?P<value>.*) # value
41+
''', re.X)
42+
43+
@classmethod
44+
def _to_bool(cls, val):
45+
# Convert a CMake BOOL string into a Python bool.
46+
#
47+
# "True if the constant is 1, ON, YES, TRUE, Y, or a
48+
# non-zero number. False if the constant is 0, OFF, NO,
49+
# FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
50+
# the suffix -NOTFOUND. Named boolean constants are
51+
# case-insensitive. If the argument is not one of these
52+
# constants, it is treated as a variable."
53+
#
54+
# https://cmake.org/cmake/help/v3.0/command/if.html
55+
val = val.upper()
56+
if val in ('ON', 'YES', 'TRUE', 'Y'):
57+
return True
58+
elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
59+
return False
60+
elif val.endswith('-NOTFOUND'):
61+
return False
62+
else:
63+
try:
64+
v = int(val)
65+
return v != 0
66+
except ValueError as exc:
67+
raise ValueError('invalid bool {}'.format(val)) from exc
68+
69+
@classmethod
70+
def from_line(cls, line, line_no):
71+
# Comments can only occur at the beginning of a line.
72+
# (The value of an entry could contain a comment character).
73+
if line.startswith('//') or line.startswith('#'):
74+
return None
75+
76+
# Whitespace-only lines do not contain cache entries.
77+
if not line.strip():
78+
return None
79+
80+
m = cls.CACHE_ENTRY.match(line)
81+
if not m:
82+
return None
83+
84+
name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
85+
if type_ == 'BOOL':
86+
try:
87+
value = cls._to_bool(value)
88+
except ValueError as exc:
89+
args = exc.args + ('on line {}: {}'.format(line_no, line),)
90+
raise ValueError(args) from exc
91+
elif type_ == 'STRING' or type_ == 'INTERNAL':
92+
# If the value is a CMake list (i.e. is a string which
93+
# contains a ';'), convert to a Python list.
94+
if ';' in value:
95+
value = value.split(';')
96+
97+
return CMakeCacheEntry(name, value)
98+
99+
def __init__(self, name, value):
100+
self.name = name
101+
self.value = value
102+
103+
def __str__(self):
104+
fmt = 'CMakeCacheEntry(name={}, value={})'
105+
return fmt.format(self.name, self.value)
106+
107+
108+
class CMakeCache:
109+
'''Parses and represents a CMake cache file.'''
110+
111+
def __init__(self, cache_file):
112+
self.load(cache_file)
113+
114+
def load(self, cache_file):
115+
entries = []
116+
with open(str(cache_file), 'r') as cache:
117+
for line_no, line in enumerate(cache):
118+
entry = CMakeCacheEntry.from_line(line, line_no)
119+
if entry:
120+
entries.append(entry)
121+
self._entries = OrderedDict((e.name, e) for e in entries)
122+
123+
def get(self, name, default=None):
124+
entry = self._entries.get(name)
125+
if entry is not None:
126+
return entry.value
127+
else:
128+
return default
129+
130+
def get_list(self, name, default=None):
131+
if default is None:
132+
default = []
133+
entry = self._entries.get(name)
134+
if entry is not None:
135+
value = entry.value
136+
if isinstance(value, list):
137+
return value
138+
elif isinstance(value, str):
139+
return [value]
140+
else:
141+
msg = 'invalid value {} type {}'
142+
raise RuntimeError(msg.format(value, type(value)))
143+
else:
144+
return default
145+
146+
def __getitem__(self, name):
147+
return self._entries[name].value
148+
149+
def __setitem__(self, name, entry):
150+
if not isinstance(entry, CMakeCacheEntry):
151+
msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
152+
raise TypeError(msg.format(type(entry), entry))
153+
self._entries[name] = entry
154+
155+
def __delitem__(self, name):
156+
del self._entries[name]
157+
158+
def __iter__(self):
159+
return iter(self._entries.values())
160+
161+
162+
class CMakeMixin(object):
163+
__slots__ = []
164+
165+
_cmake_cache = None
166+
167+
def cmake_variable(self, variable_name, default="<unset>"):
168+
variable_value = self.options.defines.get(variable_name, default)
169+
if variable_value == "<unset>":
170+
raise self._get_error_exception(
171+
"CMake variable '{}' not defined.".format(variable_name), 1)
172+
return variable_value
173+
174+
def cmake_cache_variable(self, variable_name, default="<unset>"):
175+
if self._cmake_cache is None:
176+
cache_file = Path(self.options.cmakecache_file)
177+
if not cache_file.is_file():
178+
raise self._get_error_exception(
179+
"CMake cache file '{}' does not exist or is no file.".
180+
format(cache_file), 1)
181+
self._cmake_cache = CMakeCache(cache_file)
182+
try:
183+
return self._cmake_cache.get(variable_name)
184+
except:
185+
if default == "<unset>":
186+
raise self._get_error_exception(
187+
"CMake variable '{}' not defined in cache file.".
188+
format(variable_name), 1)
189+
return default
190+

scripts/cogeno/cogeno/cogeno.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#
2+
# Copyright (c) 2018 Bobby Noelte.
3+
#
4+
# SPDX-License-Identifier: MIT
5+
6+
import sys
7+
8+
##
9+
# Make relative import work also with __main__
10+
if __package__ is None or __package__ == '':
11+
# use current directory visibility
12+
from generator import CodeGenerator
13+
from redirectable import Redirectable
14+
from options import Options
15+
from context import Context
16+
else:
17+
# use current package visibility
18+
from .generator import CodeGenerator
19+
from .redirectable import Redirectable
20+
from .options import Options
21+
from .context import Context
22+
23+
##
24+
# @brief The code generation processor
25+
#
26+
class Cogeno(Redirectable):
27+
28+
def __init__(self):
29+
Redirectable.__init__(self)
30+
31+
##
32+
# @brief Is this a trailing line after an end spec marker.
33+
#
34+
# @todo Make trailing end spec line detection dependent on
35+
# type of text or file type.
36+
#
37+
# @param s line
38+
#
39+
def _is_end_spec_trailer(self, s):
40+
return '*/' in s
41+
42+
def callable_main(self, argv):
43+
""" All of command-line cogen, but in a callable form.
44+
This is used by main.
45+
argv is the equivalent of sys.argv.
46+
"""
47+
options = Options()
48+
options.parse_args(argv[1:])
49+
50+
generator = CodeGenerator()
51+
context = Context(generator, options = options)
52+
53+
ret = generator._evaluate_context(context)
54+
return ret
55+
56+
57+
def main():
58+
ret = Cogeno().callable_main(sys.argv)
59+
sys.exit(ret)
60+
61+
if __name__ == '__main__':
62+
main()

0 commit comments

Comments
 (0)