├── src
├── code_generation
│ ├── version.py
│ ├── html
│ │ ├── __init__.py
│ │ └── html_generator.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── code_style.py
│ │ └── code_generator.py
│ ├── __init__.py
│ └── cpp
│ │ ├── __init__.py
│ │ ├── cpp_enum.py
│ │ ├── cpp_function.py
│ │ ├── cpp_array.py
│ │ ├── cpp_generator.py
│ │ ├── cpp_variable.py
│ │ └── cpp_class.py
└── example.py
├── tests
├── test_assets
│ ├── factorial.cpp
│ ├── factorial.h
│ ├── var.cpp
│ ├── array.cpp
│ ├── func.cpp
│ ├── func.h
│ ├── enum.cpp
│ ├── class.cpp
│ └── class.h
├── test_html_writer.py
├── test_cpp_function_writer.py
├── test_cpp_variable_writer.py
├── test_cpp_file.py
└── create_assets.py
├── pyproject.toml
├── setup.cfg
├── RELEASE_NOTES.json
├── LICENSE
├── .github
└── workflows
│ └── python-app.yml
├── setup.py
├── .gitignore
├── README.md
└── release_package.py
/src/code_generation/version.py:
--------------------------------------------------------------------------------
1 | VERSION = "1.1.x"
2 |
--------------------------------------------------------------------------------
/src/code_generation/html/__init__.py:
--------------------------------------------------------------------------------
1 | from . import html_generator
2 |
--------------------------------------------------------------------------------
/src/code_generation/core/__init__.py:
--------------------------------------------------------------------------------
1 | from . import code_generator
2 | from . import code_style
3 |
--------------------------------------------------------------------------------
/src/code_generation/__init__.py:
--------------------------------------------------------------------------------
1 | from . import core
2 | from . import cpp
3 | from . import html
4 |
--------------------------------------------------------------------------------
/tests/test_assets/factorial.cpp:
--------------------------------------------------------------------------------
1 | constexpr int factorial(int n)
2 | {
3 | return n < 1 ? 1 : (n * factorial(n - 1));
4 | }
5 |
--------------------------------------------------------------------------------
/tests/test_assets/factorial.h:
--------------------------------------------------------------------------------
1 | constexpr int factorial(int n)
2 | {
3 | return n < 1 ? 1 : (n * factorial(n - 1));
4 | }
5 |
--------------------------------------------------------------------------------
/tests/test_assets/var.cpp:
--------------------------------------------------------------------------------
1 | const char* var1 = 0;
2 | static int var2 = 0;
3 | std::string var3;
4 | // A number
5 | int var4;
6 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = [
3 | "setuptools>=42",
4 | "wheel"
5 | ]
6 | build-backend = "setuptools.build_meta"
7 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = code_generation
3 | version = 2.4.0
4 |
5 | [options]
6 | packages = find:
7 | python_requires = >=3.8
8 |
9 | [options.packages.find]
10 | where = src
11 |
--------------------------------------------------------------------------------
/tests/test_assets/array.cpp:
--------------------------------------------------------------------------------
1 | const int array1[5] = {1, 2, 3};
2 | const const char* array2[] = {"Item1", "Item2"};
3 | const const char* array3[] =
4 | {
5 | "ItemNewline1",
6 | "ItemNewline2"
7 | };
8 |
--------------------------------------------------------------------------------
/src/code_generation/cpp/__init__.py:
--------------------------------------------------------------------------------
1 | from . import cpp_array
2 | from . import cpp_class
3 | from . import cpp_enum
4 | from . import cpp_function
5 | from . import cpp_generator
6 | from . import cpp_variable
7 |
--------------------------------------------------------------------------------
/tests/test_assets/func.cpp:
--------------------------------------------------------------------------------
1 | int GetParam()
2 | {
3 | }
4 | void Calculate()
5 | {
6 | }
7 | int GetAnswer()
8 | {
9 | return 42;
10 | }
11 | /// Returns the help documentation.
12 | char * Help()
13 | {
14 | }
15 |
--------------------------------------------------------------------------------
/tests/test_assets/func.h:
--------------------------------------------------------------------------------
1 | int GetParam()
2 | {
3 | }
4 | void Calculate()
5 | {
6 | }
7 | int GetAnswer()
8 | {
9 | return 42;
10 | }
11 | char * Help()
12 | {
13 | }
14 | int GetParam();
15 | void Calculate();
16 | int GetAnswer();
17 | char * Help();
18 |
--------------------------------------------------------------------------------
/tests/test_assets/enum.cpp:
--------------------------------------------------------------------------------
1 | enum Items
2 | {
3 | eChair = 0,
4 | eTable = 1,
5 | eShelve = 2,
6 | eItemsCount = 3
7 | };
8 | enum Items
9 | {
10 | itChair = 0,
11 | itTable = 1,
12 | itShelve = 2,
13 | itItemsCount = 3
14 | };
15 | enum Items
16 | {
17 | Chair = 0,
18 | Table = 1,
19 | Shelve = 2,
20 | ItemsCount = 3
21 | };
22 | enum Items
23 | {
24 | Chair = 0,
25 | Table = 1,
26 | Shelve = 2,
27 | };
28 | enum Items
29 | {
30 | eChair = 0,
31 | eTable = 1,
32 | eShelve = 2,
33 | };
34 |
--------------------------------------------------------------------------------
/tests/test_assets/class.cpp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | static const char* MyClass::array2[] = {"Item1", "Item2"};
5 |
6 | static const char* MyClass::array3[] =
7 | {
8 | "ItemNewline1",
9 | "ItemNewline2"
10 | };
11 |
12 | const size_t MyClass::Nested::m_gcAnswer = 42;
13 |
14 |
15 | int MyClass::GetParam() const
16 | {
17 | return m_var1;
18 | }
19 |
20 | /*virtual*/int MyClass::VirtualMethod()
21 | {
22 | }
23 |
24 | /*virtual*/void MyClass::PureVirtualMethod() = 0
25 | {
26 | }
27 |
28 | const size_t MyClass::Nested::m_gcAnswer = 42;
29 |
30 |
31 |
32 |
33 | /**
34 | * Example multiline documentation.
35 | */
36 | void Example::DoNothing()
37 | {
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/tests/test_html_writer.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import io
3 |
4 | from code_generation.html.html_generator import *
5 |
6 | __doc__ = """
7 | Unit tests for HTML code generator
8 | """
9 |
10 |
11 | class TestHTMLFunctionGenerator(unittest.TestCase):
12 |
13 | def test_is_constexpr_render_to_string(self):
14 | writer = io.StringIO()
15 | html = HtmlFile(None, writer=writer)
16 | with html.block(element='p', id='id1', name='name1'):
17 | html('Text')
18 | print(writer.getvalue())
19 | result = """
\n Text\n
\n"""
20 | self.assertIn(result, writer.getvalue())
21 |
22 |
23 | if __name__ == "__main__":
24 | unittest.main()
25 |
--------------------------------------------------------------------------------
/RELEASE_NOTES.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": {
3 | "download_link": "https://{package_name_dash}-package.s3.amazonaws.com/server/{package_name}-{version}-py3-none-any.whl"
4 | },
5 | "releases": {
6 | "2.x.x": {
7 | "release_notes": [
8 | "Create subdirectories for core, cpp and html generators"
9 | ]
10 | },
11 | "2.3.0": {
12 | "release_notes": [
13 | "Migration to Python 3.8",
14 | "Split `Cpp*` generators implementation into multiple files",
15 | "Implemented HTML code generator"
16 | ]
17 | },
18 | "2.1.1": {
19 | "release_notes": [
20 | "Application is now available as a package",
21 | "Auto-upload to S3 bucket"
22 | ]
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/example.py:
--------------------------------------------------------------------------------
1 | from code_generation import code_generator
2 | from code_generation import cpp_generator
3 |
4 | # Create a new code file
5 | cpp = code_generator.CodeFile('example.cpp')
6 | cpp('int i = 0;')
7 |
8 | # Create a new variable 'x'
9 | x_variable = cpp_generator.CppVariable(
10 | name='x',
11 | type='int const&',
12 | is_static=True,
13 | is_constexpr=True,
14 | initialization_value='42')
15 | x_variable.render_to_string(cpp)
16 |
17 | # Create a new variable 'name'
18 | name_variable = cpp_generator.CppVariable(
19 | name='name',
20 | type='char*',
21 | is_extern=True)
22 | name_variable.render_to_string(cpp)
23 |
24 |
25 | # Generated C++ code
26 | """
27 | int i = 0;
28 | static constexpr int const& x = 42;
29 | extern char* name;
30 | """
31 |
--------------------------------------------------------------------------------
/tests/test_assets/class.h:
--------------------------------------------------------------------------------
1 | class MyClass
2 | {
3 | public:
4 | enum Items
5 | {
6 | wdOne = 0,
7 | wdTwo = 1,
8 | wdThree = 2,
9 | wdItemsCount = 3
10 | };
11 |
12 | struct Nested
13 | {
14 |
15 | static const size_t m_gcAnswer;
16 |
17 | };
18 |
19 | int GetParam() const ;
20 |
21 | virtual int VirtualMethod();
22 |
23 | virtual void PureVirtualMethod() = 0;
24 |
25 |
26 | private:
27 | int m_var1;
28 |
29 | int* m_var2;
30 |
31 | static constexpr int m_var3 = 42;
32 |
33 | static const char* array2[];
34 |
35 | static const char* array3[];
36 |
37 | };
38 | /// An example
39 | /// class with
40 | /// multiline documentation
41 | class Example
42 | {
43 | public:
44 | void DoNothing();
45 |
46 |
47 | private:
48 | /// A number.
49 | int m_var1;
50 |
51 | };
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Yurii Cherkasov, Erich Schroeter
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/python-app.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Python Code Generator
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up Python 3.9
20 | uses: actions/setup-python@v2
21 | with:
22 | python-version: 3.9
23 | - name: Install dependencies
24 | run: |
25 | python -m pip install --upgrade pip
26 | pip install flake8 pytest
27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28 | - name: Lint with flake8
29 | run: |
30 | # stop the build if there are Python syntax errors or undefined names
31 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
32 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
33 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
34 | - name: Run test
35 | run: |
36 | python release_package.py --mode install
37 | python tests/test_cpp_file.py
38 | python tests/test_cpp_function_writer.py
39 | python tests/test_cpp_variable_writer.py
40 | python tests/test_html_writer.py
41 |
--------------------------------------------------------------------------------
/src/code_generation/html/html_generator.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from code_generation.core.code_style import HTMLStyle
3 |
4 |
5 | class HtmlFile:
6 |
7 | Formatter = HTMLStyle
8 |
9 | def __init__(self, filename, writer=None):
10 | self.current_indent = 0
11 | self.last = None
12 | self.filename = filename
13 | if writer:
14 | self.out = writer
15 | else:
16 | self.out = open(filename, "w")
17 |
18 | def close(self):
19 | """
20 | File created, just close the handle
21 | """
22 | self.out.close()
23 | self.out = None
24 |
25 | def write(self, text, indent=0, endline=True):
26 | """
27 | Write a new line with line ending
28 | """
29 | self.out.write('{0}{1}{2}'.format(self.Formatter.indent * (self.current_indent+indent),
30 | text,
31 | self.Formatter.endline if endline else ''))
32 |
33 | def append(self, x):
34 | """
35 | Append to the existing line without line ending
36 | """
37 | self.out.write(x)
38 |
39 | def __call__(self, text, indent=0, endline=True):
40 | """
41 | Supports 'object()' semantic, i.e.
42 | cpp('#include ')
43 | inserts appropriate line
44 | """
45 | self.write(text, indent, endline)
46 |
47 | def block(self, element, **attributes):
48 | """
49 | Returns a stub for HTML element
50 | Supports 'with' semantic, i.e.
51 | html.block(element='p', id='id1', name='name1'):
52 | """
53 | return self.Formatter(self, element=element, **attributes)
54 |
55 | def endline(self, count=1):
56 | """
57 | Insert an endline
58 | """
59 | self.write(self.Formatter.endline * count, endline=False)
60 |
61 | def newline(self, n=1):
62 | """
63 | Insert one or several empty lines
64 | """
65 | for _ in range(n):
66 | self.write('')
67 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import pathlib
3 | from setuptools import find_packages, setup
4 | from configparser import ConfigParser
5 |
6 | cfg = ConfigParser()
7 | cfg.read(filenames=['setup.cfg'])
8 | VERSION = cfg.get('metadata', 'version')
9 | PACKAGE_NAME = cfg.get('metadata', 'name')
10 | PYTHON_REQUIRES = cfg.get('options', 'python_requires')
11 |
12 | # The directory containing this file
13 | HERE = pathlib.Path(__file__).parent
14 |
15 | # The text of the README file
16 | README = (HERE / "README.md").read_text(encoding='utf8')
17 |
18 | # Add future dependencies here
19 | DEPENDENCIES = ["pathlib"]
20 |
21 | setup(
22 | name=PACKAGE_NAME,
23 | version=VERSION,
24 | author="Yurii Cherkasov",
25 | author_email="strategarius@protonmail.com",
26 | description="Provides functionality of generating source code programmatically",
27 | long_description=README,
28 | long_description_content_type="text/markdown",
29 | url="https://github.com/yuchdev/code_generator",
30 | project_urls={
31 | "Bug Tracker": "https://github.com/yuchdev/code_generator/issues",
32 | },
33 | classifiers=[
34 | # How mature is this project? Common values are
35 | # 3 - Alpha
36 | # 4 - Beta
37 | # 5 - Production/Stable
38 | 'Development Status :: 5 - Production/Stable',
39 |
40 | # Indicate who your project is intended for
41 | 'Intended Audience :: Developers',
42 |
43 | # Specify the Python versions you support here. In particular, ensure
44 | # that you indicate you support Python 3. These classifiers are *not*
45 | # checked by 'pip install'. See instead 'python_requires' below.
46 | 'Programming Language :: Python :: 3',
47 | 'License :: OSI Approved :: MIT License',
48 | 'Operating System :: OS Independent',
49 | ],
50 | packages=find_packages(where=str(HERE / 'src')),
51 | package_dir={'': 'src'},
52 | package_data={PACKAGE_NAME: ['defaults/*']},
53 | python_requires=PYTHON_REQUIRES,
54 | include_package_data=True,
55 | install_requires=DEPENDENCIES,
56 | )
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
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 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
139 |
140 | #vim files
141 | .*.swp
142 |
--------------------------------------------------------------------------------
/tests/test_cpp_function_writer.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import io
3 | from textwrap import dedent
4 |
5 | from code_generation.core.code_generator import CppFile
6 | from code_generation.cpp.cpp_function import CppFunction
7 |
8 | __doc__ = """
9 | Unit tests for C++ code generator
10 | """
11 |
12 |
13 | def handle_to_factorial(_, cpp):
14 | cpp('return n < 1 ? 1 : (n * factorial(n - 1));')
15 |
16 |
17 | class TestCppFunctionStringIo(unittest.TestCase):
18 | """
19 | Test C++ function generation by writing to StringIO
20 | """
21 |
22 | def test_is_constexpr_no_implementation_raises(self):
23 | writer = io.StringIO()
24 | cpp = CppFile(None, writer=writer)
25 | func = CppFunction(name="factorial", ret_type="int", is_constexpr=True)
26 | self.assertRaises(ValueError, func.render_to_string, cpp)
27 |
28 | def test_is_constexpr_render_to_string(self):
29 | writer = io.StringIO()
30 | cpp = CppFile(None, writer=writer)
31 | func = CppFunction(name="factorial", ret_type="int",
32 | implementation_handle=handle_to_factorial, is_constexpr=True)
33 | func.add_argument('int n')
34 | func.render_to_string(cpp)
35 | self.assertIn(dedent("""\
36 | constexpr int factorial(int n)
37 | {
38 | \treturn n < 1 ? 1 : (n * factorial(n - 1));
39 | }"""), writer.getvalue())
40 |
41 | def test_is_constexpr_render_to_string_declaration(self):
42 | writer = io.StringIO()
43 | cpp = CppFile(None, writer=writer)
44 | func = CppFunction(name="factorial", ret_type="int",
45 | implementation_handle=handle_to_factorial, is_constexpr=True)
46 | func.add_argument('int n')
47 | func.render_to_string_declaration(cpp)
48 | self.assertIn(dedent("""\
49 | constexpr int factorial(int n)
50 | {
51 | \treturn n < 1 ? 1 : (n * factorial(n - 1));
52 | }"""), writer.getvalue())
53 |
54 | def test_docstring_example(self):
55 | writer = io.StringIO()
56 | cpp = CppFile(None, writer=writer)
57 | factorial_function = CppFunction(name='factorial', ret_type='int', is_constexpr=True,
58 | implementation_handle=handle_to_factorial,
59 | documentation='/// Calculates and returns the factorial of p @n.')
60 | factorial_function.add_argument('int n')
61 | factorial_function.render_to_string(cpp)
62 | self.assertIn(dedent("""\
63 | /// Calculates and returns the factorial of p @n.
64 | constexpr int factorial(int n)
65 | {
66 | \treturn n < 1 ? 1 : (n * factorial(n - 1));
67 | }"""), writer.getvalue())
68 |
69 |
70 | if __name__ == "__main__":
71 | unittest.main()
72 |
--------------------------------------------------------------------------------
/tests/test_cpp_variable_writer.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import io
3 |
4 | from code_generation.core.code_generator import CppFile
5 | from code_generation.cpp.cpp_variable import CppVariable
6 |
7 | __doc__ = """Unit tests for C++ code generator
8 | """
9 |
10 |
11 | class TestCppVariableStringIo(unittest.TestCase):
12 | """
13 | Test C++ variable generation by writing to StringIO
14 | """
15 |
16 | def test_cpp_var(self):
17 | writer = io.StringIO()
18 | cpp = CppFile(None, writer=writer)
19 | variables = CppVariable(name="var1",
20 | type="char*",
21 | is_class_member=False,
22 | is_static=False,
23 | is_const=True,
24 | initialization_value='0')
25 | variables.render_to_string(cpp)
26 | print(writer.getvalue())
27 | self.assertEqual('const char* var1 = 0;\n', writer.getvalue())
28 |
29 | def test_is_constexpr_const_raises(self):
30 | writer = io.StringIO()
31 | cpp = CppFile(None, writer=writer)
32 | var = CppVariable(name="COUNT", type="int", is_class_member=True, is_const=True,
33 | is_constexpr=True, initialization_value='0')
34 | self.assertRaises(ValueError, var.render_to_string, cpp)
35 |
36 | def test_is_constexpr_no_implementation_raises(self):
37 | writer = io.StringIO()
38 | cpp = CppFile(None, writer=writer)
39 | var = CppVariable(name="COUNT", type="int", is_class_member=True, is_constexpr=True)
40 | self.assertRaises(ValueError, var.render_to_string, cpp)
41 |
42 | def test_is_constexpr_render_to_string(self):
43 | writer = io.StringIO()
44 | cpp = CppFile(None, writer=writer)
45 | variables = CppVariable(name="COUNT",
46 | type="int",
47 | is_class_member=False,
48 | is_constexpr=True,
49 | initialization_value='0')
50 | variables.render_to_string(cpp)
51 | self.assertIn('constexpr int COUNT = 0;', writer.getvalue())
52 |
53 | def test_is_constexpr_render_to_string_declaration(self):
54 | writer = io.StringIO()
55 | cpp = CppFile(None, writer=writer)
56 | variables = CppVariable(name="COUNT",
57 | type="int",
58 | is_class_member=True,
59 | is_constexpr=True,
60 | initialization_value='0')
61 | variables.render_to_string_declaration(cpp)
62 | self.assertIn('constexpr int COUNT = 0;', writer.getvalue())
63 |
64 | def test_is_extern_static_raises(self):
65 | writer = io.StringIO()
66 | cpp = CppFile(None, writer=writer)
67 | var = CppVariable(name="var1", type="char*", is_static=True, is_extern=True)
68 | self.assertRaises(ValueError, var.render_to_string, cpp)
69 |
70 | def test_is_extern_render_to_string(self):
71 | writer = io.StringIO()
72 | cpp = CppFile(None, writer=writer)
73 | v = CppVariable(name="var1", type="char*", is_extern=True)
74 | v.render_to_string(cpp)
75 | self.assertIn('extern char* var1;', writer.getvalue())
76 |
77 |
78 | if __name__ == "__main__":
79 | unittest.main()
80 |
--------------------------------------------------------------------------------
/src/code_generation/core/code_style.py:
--------------------------------------------------------------------------------
1 | __doc__ = """Formatters for different styles of code generation
2 | """
3 |
4 |
5 | class ANSICodeStyle:
6 | """
7 | Class represents C++ {} close and its formatting style.
8 | It supports ANSI C style with braces on the new lines, like that:
9 | // C++ code
10 | {
11 | // C++ code
12 | };
13 | finishing postfix is optional (e.g. necessary for classes, unnecessary for namespaces)
14 | """
15 |
16 | # EOL symbol
17 | endline = "\n"
18 |
19 | # Tab (indentation) symbol
20 | indent = "\t"
21 |
22 | def __init__(self, owner, text, postfix):
23 | """
24 | @param: owner - CodeFile where text is written to
25 | @param: text - text opening C++ close
26 | @param: postfix - optional terminating symbol (e.g. ; for classes)
27 | """
28 | self.owner = owner
29 | if self.owner.last is not None:
30 | with self.owner.last:
31 | pass
32 | self.owner.write("".join(text))
33 | self.owner.last = self
34 | self.postfix = postfix
35 |
36 | def __enter__(self):
37 | """
38 | Open code block
39 | """
40 | self.owner.write("{")
41 | self.owner.current_indent += 1
42 | self.owner.last = None
43 |
44 | def __exit__(self, *_):
45 | """
46 | Close code block
47 | """
48 | if self.owner.last is not None:
49 | with self.owner.last:
50 | pass
51 | self.owner.current_indent -= 1
52 | self.owner.write("}" + self.postfix)
53 |
54 |
55 | class HTMLStyle:
56 | """
57 | Class representing HTML close and its formatting style.
58 | It supports HTML DOM-tree style, like that:
59 | // HTML code
60 |
61 | // HTML content
62 |
63 | """
64 | # EOL symbol
65 | endline = "\n"
66 |
67 | # Tab (indentation) symbol is 2 spaces
68 | indent = " "
69 |
70 | def __init__(self, owner, element, *attrs, **kwattrs):
71 | """
72 | @param: owner - CodeFile where text is written to
73 | @param: element - HTML element name
74 | @param: attrs - optional opening tag content, like attributes ['class="class1"', 'id="id1"']
75 | @param: kwattrs - optional opening tag attributes, like class="class1", id="id1"
76 | """
77 | self.owner = owner
78 | if self.owner.last is not None:
79 | with self.owner.last:
80 | pass
81 | self.element = element
82 | attributes = "".join(f' {attr}' for attr in attrs)
83 | attributes += "".join(f' {key}="{value}"' for key, value in kwattrs.items())
84 | self.attributes = attributes
85 | self.owner.last = self
86 |
87 | def __enter__(self):
88 | """
89 | Open code block
90 | """
91 | self.owner.write(f"<{self.element}{self.attributes}>")
92 | self.owner.current_indent += 1
93 | self.owner.last = None
94 |
95 | def __exit__(self, *_):
96 | """
97 | Close code block
98 | """
99 | if self.owner.last is not None:
100 | with self.owner.last:
101 | pass
102 | self.owner.current_indent -= 1
103 | self.owner.write(f"{self.element}>")
104 |
--------------------------------------------------------------------------------
/src/code_generation/cpp/cpp_enum.py:
--------------------------------------------------------------------------------
1 | from code_generation.cpp.cpp_generator import CppLanguageElement
2 |
3 | __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives:
4 | classes, methods and functions, variables, enums.
5 | Every C++ element could render its current state to a string that could be evaluated as
6 | a legal C++ construction.
7 |
8 | Some elements could be rendered to a pair of representations (i.e. declaration and definition)
9 |
10 | Example:
11 | # Python code
12 | cpp_class = CppClass(name = 'MyClass', is_struct = True)
13 | cpp_class.add_variable(CppVariable(name = "m_var",
14 | type = 'size_t',
15 | is_static = True,
16 | is_const = True,
17 | initialization_value = 255))
18 |
19 | // Generated C++ declaration
20 | struct MyClass
21 | {
22 | static const size_t m_var;
23 | }
24 |
25 | // Generated C++ definition
26 | const size_t MyClass::m_var = 255;
27 |
28 |
29 | That module uses and highly depends on code_generator.py as it uses
30 | code generating and formatting primitives implemented there.
31 |
32 | The main object referenced from code_generator.py is CppFile,
33 | which is passed as a parameter to render_to_string(cpp) Python method
34 |
35 | It could also be used for composing more complicated C++ code,
36 | that does not supported by cpp_generator
37 |
38 | It support:
39 |
40 | - functional calls:
41 | cpp('int a = 10;')
42 |
43 | - 'with' semantic:
44 | with cpp.block('class MyClass', ';')
45 | class_definition(cpp)
46 |
47 | - append code to the last string without EOL:
48 | cpp.append(', p = NULL);')
49 |
50 | - empty lines:
51 | cpp.newline(2)
52 |
53 | For detailed information see code_generator.py documentation.
54 | """
55 |
56 |
57 | class CppEnum(CppLanguageElement):
58 | """
59 | The Python class that generates string representation for C++ enum
60 | All enum elements are explicitly initialized with incremented values
61 |
62 | Available properties:
63 | prefix - string, prefix added to every enum element, 'e' by default ('eItem1')
64 | add_counter - boolean, terminating value that shows count of enum elements added, 'True' by default.
65 |
66 | Example of usage:
67 | # Python code
68 | enum_elements = CppEnum(name = 'Items')
69 | for item in ['Chair', 'Table', 'Shelve']:
70 | enum_elements.add_item(enum_elements.name)
71 |
72 | // Generated C++ code
73 | enum Items
74 | {
75 | eChair = 0,
76 | eTable = 1,
77 | eShelve = 2,
78 | eItemsCount = 3
79 | }
80 | """
81 | availablePropertiesNames = {'prefix',
82 | 'enum_class',
83 | 'add_counter'} | CppLanguageElement.availablePropertiesNames
84 |
85 | def __init__(self, **properties):
86 | self.enum_class = False
87 | # check properties
88 | input_property_names = set(properties.keys())
89 | self.check_input_properties_names(input_property_names)
90 | super(CppEnum, self).__init__(properties)
91 |
92 | self.init_class_properties(current_class_properties=self.availablePropertiesNames,
93 | input_properties_dict=properties)
94 |
95 | # place enum items here
96 | self.enum_items = []
97 |
98 | def _render_class(self):
99 | return 'class ' if self.enum_class else ''
100 |
101 | def add_item(self, item):
102 | """
103 | @param: item - string representation for the enum element
104 | """
105 | self.enum_items.append(item)
106 |
107 | def add_items(self, items):
108 | """
109 | @param: items - list of strings
110 | """
111 | self.enum_items.extend(items)
112 |
113 | # noinspection PyUnresolvedReferences
114 | def render_to_string(self, cpp):
115 | """
116 | Generates a string representation for the enum
117 | It always contains a terminating value that shows count of enum elements
118 | enum MyEnum
119 | {
120 | eItem1 = 0,
121 | eItem2 = 1,
122 | eMyEnumCount = 2
123 | }
124 | """
125 | counter = 0
126 | final_prefix = self.prefix if self.prefix is not None else 'e'
127 | with cpp.block(f'enum {self._render_class()}{self.name}', postfix=';'):
128 | for item in self.enum_items:
129 | cpp(f'{final_prefix}{item} = {counter},')
130 | counter += 1
131 | if self.add_counter in [None, True]:
132 | last_element = f'{final_prefix}{self.name}Count = {counter}'
133 | cpp(last_element)
134 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | C++ Code Generator
2 | ==============
3 |
4 | Simple and straightforward code generator for creating C++ code. It also could be used for generating code in any programming language. Written in Python, works both with Python 2 and 3
5 |
6 | Every C++ element could render its current state to a string that could be evaluated as
7 | a legal C++ construction.
8 | Some elements could be rendered to a pair of representations (C++ classes and functions declaration and implementation)
9 |
10 | ### Special thanks
11 |
12 | Thanks to Eric Reynolds, the idea of this project mainly based on his article published on
13 | http://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python
14 |
15 | However, this solution has been both simplified and extended compared to the initial idea.
16 |
17 | ## Usage examples
18 |
19 | ### Generate C++ code from Python code
20 |
21 | #### Creating variables
22 |
23 | ##### Python code
24 | ```python
25 | cpp = CodeFile('example.cpp')
26 | cpp('int i = 0;')
27 |
28 | x_variable = CppVariable(name='x', type='int const&', is_static=True, is_constexpr=True, initialization_value='42')
29 | x_variable.render_to_string(cpp)
30 |
31 | name_variable = CppVariable(name='name', type='char*', is_extern=True)
32 | name_variable.render_to_string(cpp)
33 | ```
34 |
35 | ##### Generated C++ code
36 | ```c++
37 | int i = 0;
38 | static constexpr int const& x = 42;
39 | extern char* name;
40 | ```
41 |
42 | #### Creating functions
43 |
44 | ##### Python code
45 | ```python
46 | def handle_to_factorial(self, cpp):
47 | cpp('return n < 1 ? 1 : (n * factorial(n - 1));')
48 |
49 | cpp = CodeFile('example.cpp')
50 |
51 | factorial_function = CppFunction(name='factorial',
52 | ret_type='int',
53 | is_constexpr=True,
54 | implementation_handle=handle_to_factorial,
55 | documentation='/// Calculates and returns the factorial of \p n.')
56 | factorial_function.add_argument('int n')
57 | factorial_function.render_to_string(cpp)
58 | ```
59 |
60 | ##### Generated C++ code
61 | ```c++
62 | /// Calculates and returns the factorial of \p n.
63 | constexpr int factorial(int n)
64 | {
65 | return n <= 1 ? 1 : (n * factorial(n - 1));
66 | }
67 | ```
68 |
69 | #### Creating classes and structures
70 |
71 | ##### Python code
72 | ```python
73 | cpp = CppFile('example.cpp')
74 | with cpp.block('class A', ';'):
75 | cpp.label('public:')
76 | cpp('int m_classMember1;')
77 | cpp('double m_classMember2;')
78 | ```
79 |
80 | ##### Generated C++ code
81 | ```c++
82 | class A
83 | {
84 | public:
85 | int m_classMember1;
86 | double m_classMember2;
87 | };
88 | ```
89 |
90 | #### Rendering `CppClass` objects to C++ declaration and implementation
91 |
92 | ##### Python code
93 |
94 | ```python
95 | cpp_class = CppClass(name = 'MyClass', is_struct = True)
96 | cpp_class.add_variable(CppVariable(name = "m_var",
97 | type = 'size_t',
98 | is_static = True,
99 | is_const = True,
100 | initialization_value = 255))
101 | ```
102 |
103 | ##### Generated C++ declaration
104 |
105 | ```c++
106 | struct MyClass
107 | {
108 | static const size_t m_var;
109 | }
110 | ```
111 |
112 | #### Generated C++ implementation
113 | ```c++
114 | const size_t MyClass::m_var = 255;
115 | ```
116 |
117 | Module `cpp_generator.py` highly depends on parent `code_generator.py`, as it uses
118 | code generating and formatting primitives implemented there.
119 |
120 | The main object referenced from `code_generator.py` is `CppFile`,
121 | which is passed as a parameter to `render_to_string(cpp)` Python method
122 |
123 | It could also be used for composing more complicated C++ code,
124 | that does not supported by `cpp_generator`
125 |
126 | Class `ANSICodeStyle` is responsible for code formatting. Re-implement it if you wish to apply any other formatting style.
127 |
128 |
129 | It support:
130 |
131 | - functional calls:
132 | ```python
133 | cpp('int a = 10;')
134 | ```
135 |
136 | - `with` semantic:
137 | ```python
138 | with cpp.block('class MyClass', ';')
139 | class_definition(cpp)
140 | ```
141 |
142 | - append code to the last string without EOL:
143 | ```python
144 | cpp.append(', p = NULL);')
145 | ```
146 |
147 | - empty lines:
148 | ```python
149 | cpp.newline(2)
150 | ```
151 |
152 | ## Maintainers
153 |
154 | ### Executing unit tests
155 | The following command will execute the unit tests.
156 |
157 | ```bash
158 | python -m unittest cpp_generator_tests.py
159 | ```
160 |
161 | ### Updating unit tests fixed data
162 | After changing a unit test the fixed data needs to be updated to successfully pass the unit tests.
163 |
164 | ```bash
165 | python -c 'from test_cpp_generator import generate_reference_code; generate_reference_code()'
166 | ```
167 |
168 | After executing that command, the fixed data under `tests/test_assets` will be updated and will need to be committed to git.
169 |
170 |
--------------------------------------------------------------------------------
/src/code_generation/core/code_generator.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from code_generation.core.code_style import ANSICodeStyle
3 |
4 | __doc__ = """
5 | Simple and straightforward code generator that could be used for generating code
6 | on any programming language and to be a 'building block' for creating more complicated
7 | code generator.
8 | Thanks to Eric Reynolds, the code mainly based on his article published on
9 | https://www.codeproject.com/Articles/571645/Really-simple-Cplusplus-code-generation-in-Python
10 | However, it was both significantly extended since then and also simplified.
11 |
12 | Used under the Code Project Open License
13 | https://www.codeproject.com/info/cpol10.aspx
14 |
15 | Examples of usage:
16 |
17 | 1.
18 | # Python code
19 | cpp = CodeFile('example.cpp')
20 | cpp('int i = 0;')
21 |
22 | // Generated C++ code
23 | int i = 0;
24 |
25 | 2.
26 | # Python code
27 | cpp = CppFile('example.cpp')
28 | with cpp.block('class A', ';'):
29 | cpp.label('public')
30 | cpp('int m_classMember1;')
31 | cpp('double m_classMember2;')
32 |
33 | // Generated C++ code
34 | class A
35 | {
36 | public:
37 | int m_classMember1;
38 | double m_classMember2;
39 | };
40 |
41 | Class `ANSICodeStyle` is responsible for code formatting.
42 | Re-implement it if you wish to apply any other formatting style.
43 |
44 | """
45 |
46 |
47 | class CodeFile:
48 | """
49 | The class is a main instrument of code generation
50 |
51 | It can generate plain strings using functional calls
52 | Ex:
53 | code = CodeFile(python_src_file)
54 | code('import os, sys')
55 |
56 | Is supports 'with' semantic for indentation blocks creation
57 | Ex:
58 | # Python code
59 | with code('for i in range(0, 5):'):
60 | code('lst.append(i*i)')
61 |
62 | # Generated code:
63 | for i in range(0, 5):
64 | lst.append(i*i)
65 |
66 | It can append code to the last line:
67 | Ex.
68 | # Python code
69 | cpp = CodeFile('ex.cpp')
70 | cpp('class Derived')
71 | cpp.append(' : public Base')
72 |
73 | // Generated code
74 | class Derived : public Base
75 |
76 | And finally, it can insert a number of empty lines
77 | cpp.newline(3)
78 | """
79 | # Current formatting style (assigned as a class attribute to generate all files uniformly)
80 | Formatter = ANSICodeStyle
81 |
82 | def __init__(self, filename, writer=None):
83 | """
84 | Creates a new source file
85 | @param: filename source file to create (rewrite if exists)
86 | @param: writer optional writer to write output to
87 | """
88 | self.current_indent = 0
89 | self.last = None
90 | self.filename = filename
91 | if writer:
92 | self.out = writer
93 | else:
94 | self.out = open(filename, "w")
95 |
96 | def close(self):
97 | """
98 | File created, just close the handle
99 | """
100 | self.out.close()
101 | self.out = None
102 |
103 | def write(self, text, indent=0, endline=True):
104 | """
105 | Write a new line with line ending
106 | """
107 | self.out.write('{0}{1}{2}'.format(self.Formatter.indent * (self.current_indent+indent),
108 | text,
109 | self.Formatter.endline if endline else ''))
110 |
111 | def append(self, x):
112 | """
113 | Append to the existing line without line ending
114 | """
115 | self.out.write(x)
116 |
117 | def __call__(self, text, indent=0, endline=True):
118 | """
119 | Supports 'object()' semantic, i.e.
120 | cpp('#include ')
121 | inserts appropriate line
122 | """
123 | self.write(text, indent, endline)
124 |
125 | def block(self, text, postfix=''):
126 | """
127 | Returns a stub for C++ {} close
128 | Supports 'with' semantic, i.e.
129 | cpp.block(class_name, ';'):
130 | """
131 | return CodeFile.Formatter(self, text, postfix)
132 |
133 | def endline(self, count=1):
134 | """
135 | Insert an endline
136 | """
137 | self.write(CodeFile.Formatter.endline * count, endline=False)
138 |
139 | def newline(self, n=1):
140 | """
141 | Insert one or several empty lines
142 | """
143 | for _ in range(n):
144 | self.write('')
145 |
146 |
147 | class CppFile(CodeFile):
148 | """
149 | This class extends CodeFile class with some specific C++ constructions
150 | """
151 | def __init__(self, filename, writer=None):
152 | """
153 | Create C++ source file
154 | """
155 | CodeFile.__init__(self, filename, writer)
156 |
157 | def label(self, text):
158 | """
159 | Could be used for access specifiers or ANSI C labels, e.g.
160 | private:
161 | a:
162 | """
163 | self.write('{0}:'.format(text), -1)
164 |
--------------------------------------------------------------------------------
/src/code_generation/cpp/cpp_function.py:
--------------------------------------------------------------------------------
1 | from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation
2 | from textwrap import dedent
3 |
4 |
5 | class CppFunction(CppLanguageElement):
6 | """
7 | The Python class that generates string representation for C++ function (not method!)
8 | Parameters are passed as plain strings('int a', 'void p = NULL' etc.)
9 | Available properties:
10 | ret_type - string, return value for the method ('void', 'int'). Could not be set for constructors
11 | is_constexpr - boolean, const method prefix
12 | documentation - string, '/// Example doxygen'
13 | implementation_handle - reference to a function that receives 'self' and C++ code generator handle
14 | (see code_generator.cpp) and generates method body without braces
15 | Ex.
16 | #Python code
17 | def functionBody(self, cpp): cpp('return 42;')
18 | f1 = CppFunction(name = 'GetAnswer',
19 | ret_type = 'int',
20 | documentation = '// Generated code',
21 | implementation_handle = functionBody)
22 |
23 | // Generated code
24 | int GetAnswer()
25 | {
26 | return 42;
27 | }
28 | """
29 | availablePropertiesNames = {'ret_type',
30 | 'is_constexpr',
31 | 'implementation_handle',
32 | 'documentation'} | CppLanguageElement.availablePropertiesNames
33 |
34 | def __init__(self, **properties):
35 | # arguments are plain strings
36 | # e.g. 'int* a', 'const string& s', 'size_t sz = 10'
37 | self.arguments = []
38 | self.ret_type = None
39 | self.implementation_handle = None
40 | self.documentation = None
41 | self.is_constexpr = False
42 |
43 | # check properties
44 | input_property_names = set(properties.keys())
45 | self.check_input_properties_names(input_property_names)
46 | super(CppFunction, self).__init__(properties)
47 | self.init_class_properties(current_class_properties=self.availablePropertiesNames,
48 | input_properties_dict=properties)
49 |
50 | def _sanity_check(self):
51 | """
52 | Check whether attributes compose a correct C++ code
53 | """
54 | if self.is_constexpr and self.implementation_handle is None:
55 | raise ValueError(f'Constexpr function {self.name} must have implementation')
56 |
57 | def _render_constexpr(self):
58 | """
59 | Before function name, declaration only
60 | Constexpr functions can't be const, virtual or pure virtual
61 | """
62 | return 'constexpr ' if self.is_constexpr else ''
63 |
64 | def args(self):
65 | """
66 | @return: string arguments
67 | """
68 | return ", ".join(self.arguments)
69 |
70 | def add_argument(self, argument):
71 | """
72 | @param: argument string representation of the C++ function argument ('int a', 'void p = NULL' etc)
73 | """
74 | self.arguments.append(argument)
75 |
76 | def implementation(self, cpp):
77 | """
78 | The method calls Python function that creates C++ method body if handle exists
79 | """
80 | if self.implementation_handle is not None:
81 | self.implementation_handle(self, cpp)
82 |
83 | def declaration(self):
84 | """
85 | @return: CppDeclaration wrapper, that could be used
86 | for declaration rendering using render_to_string(cpp) interface
87 | """
88 | return CppDeclaration(self)
89 |
90 | def definition(self):
91 | """
92 | @return: CppImplementation wrapper, that could be used
93 | for definition rendering using render_to_string(cpp) interface
94 | """
95 | return CppImplementation(self)
96 |
97 | def render_to_string(self, cpp):
98 | """
99 | By function method is rendered as a declaration together with implementation
100 | void f()
101 | {
102 | ...
103 | }
104 | """
105 | # check all properties for the consistency
106 | self._sanity_check()
107 | if self.documentation:
108 | cpp(dedent(self.documentation))
109 | with cpp.block(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})'):
110 | self.implementation(cpp)
111 |
112 | def render_to_string_declaration(self, cpp):
113 | """
114 | Special case for a function declaration string representation.
115 | Generates just a function signature terminated by ';'
116 | Example:
117 | int GetX();
118 | """
119 | # check all properties for the consistency
120 | if self.is_constexpr:
121 | if self.documentation:
122 | cpp(dedent(self.documentation))
123 | self.render_to_string(cpp)
124 | else:
125 | cpp(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()});')
126 |
127 | def render_to_string_implementation(self, cpp):
128 | """
129 | Special case for a function implementation string representation.
130 | Generates function string in the form
131 | Example:
132 | int GetX() const
133 | {
134 | ...
135 | }
136 | Generates method body if self.implementation_handle property exists
137 | """
138 | if self.implementation_handle is None:
139 | raise RuntimeError(f'No implementation handle for the function {self.name}')
140 |
141 | # check all properties for the consistency
142 | if self.documentation and not self.is_constexpr:
143 | cpp(dedent(self.documentation))
144 | with cpp.block(f'{self._render_constexpr()}{self.ret_type} {self.name}({self.args()})'):
145 | self.implementation(cpp)
146 |
--------------------------------------------------------------------------------
/src/code_generation/cpp/cpp_array.py:
--------------------------------------------------------------------------------
1 | from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation
2 |
3 |
4 | # noinspection PyUnresolvedReferences
5 | class CppArray(CppLanguageElement):
6 | """
7 | The Python class that generates string representation for C++ array (automatic or class member)
8 | For example:
9 |
10 | int arr[] = {1,2,2};
11 | double doubles[5] = {1.0,2.0};
12 |
13 | class MyClass
14 | {
15 | int m_arr1[10];
16 | static double m_arr2[];
17 | ...
18 | }
19 | Available properties:
20 |
21 | type - string, variable type
22 | (is_)static - boolean, 'static' prefix
23 | (is_)const - boolean, 'const' prefix
24 | (is_)class_member - boolean, for appropriate definition/declaration rendering
25 | array_size - integer, size of array if required
26 | newline_align - in the array definition rendering place every item on the new string
27 |
28 | NOTE: versions 2.0+ of CodeGenerator support boolean properties without "is_" suffix,
29 | but old versions preserved for backward compatibility
30 | """
31 | availablePropertiesNames = {'type',
32 | 'is_static',
33 | 'static',
34 | 'is_const',
35 | 'const',
36 | 'is_class_member',
37 | 'class_member',
38 | 'array_size',
39 | 'newline_align'} | CppLanguageElement.availablePropertiesNames
40 |
41 | def __init__(self, **properties):
42 | self.is_static = False
43 | self.is_const = False
44 | self.is_class_member = False
45 | self.array_size = 0
46 | self.newline_align = None
47 |
48 | # array elements
49 | self.items = []
50 |
51 | input_property_names = set(properties.keys())
52 | self.check_input_properties_names(input_property_names)
53 | super(CppArray, self).__init__(properties)
54 | self.init_class_properties(current_class_properties=self.availablePropertiesNames,
55 | input_properties_dict=properties)
56 |
57 | def _render_static(self):
58 | """
59 | @return: 'static' prefix if required
60 | """
61 | return 'static ' if self.is_static else ''
62 |
63 | def _render_const(self):
64 | """
65 | @return: 'const' prefix if required
66 | """
67 | return 'const ' if self.is_const else ''
68 |
69 | def _render_size(self):
70 | """
71 | @return: array size
72 | """
73 | return self.array_size if self.array_size else ''
74 |
75 | def _render_content(self):
76 | """
77 | @return: array items if any
78 | """
79 | return ', '.join(self.items) if self.items else 'nullptr'
80 |
81 | def _render_value(self, cpp):
82 | """
83 | Render to string array items
84 | """
85 | if not self.items:
86 | raise RuntimeError('Empty arrays do not supported')
87 | for item in self.items[:-1]:
88 | cpp('{0},'.format(item))
89 | cpp('{0}'.format(self.items[-1]))
90 |
91 | def declaration(self):
92 | """
93 | @return: CppDeclaration wrapper, that could be used
94 | for declaration rendering using render_to_string(cpp) interface
95 | """
96 | return CppDeclaration(self)
97 |
98 | def definition(self):
99 | """
100 | @return: CppImplementation wrapper, that could be used
101 | for definition rendering using render_to_string(cpp) interface
102 | """
103 | return CppImplementation(self)
104 |
105 | def add_array_item(self, item):
106 | """
107 | If variable is an array it could contain a number of items
108 | @param: item - string
109 | """
110 | self.items.append(item)
111 |
112 | def add_array_items(self, items):
113 | """
114 | If variable is an array it could contain a number of items
115 | @param: items - list of strings
116 | """
117 | self.items.extend(items)
118 |
119 | def render_to_string(self, cpp):
120 | """
121 | Generates definition for the C++ array.
122 | Output depends on the array type
123 |
124 | Generates something like
125 | int my_array[5] = {1, 2, 0};
126 | const char* my_array[] = {"Hello", "World"};
127 |
128 | That method is used for generating automatic (non-class members) arrays
129 | For class members use render_to_string_declaration/render_to_string_implementation methods
130 | """
131 | if self.is_class_member and not (self.is_static and self.is_const):
132 | raise RuntimeError('For class member variables use definition() and declaration() methods')
133 |
134 | # newline-formatting of array elements makes sense only if array is not empty
135 | if self.newline_align and self.items:
136 | with cpp.block(f'{self._render_static()}{self._render_const()}{self.type} '
137 | f'{self.name}[{self._render_size()}] = ', ';'):
138 | # render array items
139 | self._render_value(cpp)
140 | else:
141 | cpp(f'{self._render_static()}{self._render_const()}{self.type} '
142 | f'{self.name}[{self._render_size()}] = {{{self._render_content()}}};')
143 |
144 | def render_to_string_declaration(self, cpp):
145 | """
146 | Generates declaration for the C++ array.
147 | Non-static arrays-class members do not supported
148 |
149 | Example:
150 | static int my_class_member_array[];
151 | """
152 | if not self.is_class_member:
153 | raise RuntimeError('For automatic variable use its render_to_string() method')
154 | cpp(f'{self._render_static()}{self._render_const()}{self.type} {self.name}[{self._render_size()}];')
155 |
156 | def render_to_string_implementation(self, cpp):
157 | """
158 | Generates definition for the C++ array.
159 | Output depends on the array type
160 |
161 | Example:
162 | int MyClass::m_my_static_array[] =
163 | {
164 | ...
165 | };
166 |
167 | Non-static arrays-class members do not supported
168 | """
169 | if not self.is_class_member:
170 | raise RuntimeError('For automatic variable use its render_to_string() method')
171 |
172 | # generate definition for the static class member arrays only
173 | # other types are not supported
174 | if not self.is_static:
175 | raise RuntimeError('Only static arrays as class members are supported')
176 |
177 | # newline-formatting of array elements makes sense only if array is not empty
178 | if self.newline_align and self.items:
179 | with cpp.block(f'{self._render_static()}{self._render_const()}{self.type} '
180 | f'{self.name}[{self._render_size()}] = ', ';'):
181 | # render array items
182 | self._render_value(cpp)
183 | else:
184 | cpp(f'{self._render_static()}{self._render_const()}{self.type} '
185 | f'{self.name}[{self._render_size()}] = {{{self._render_content()}}};')
186 |
--------------------------------------------------------------------------------
/src/code_generation/cpp/cpp_generator.py:
--------------------------------------------------------------------------------
1 | __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives:
2 | classes, methods and functions, variables, enums.
3 | Every C++ element could render its current state to a string that could be evaluated as
4 | a legal C++ construction.
5 |
6 | Some elements could be rendered to a pair of representations (i.e. declaration and definition)
7 |
8 | Example:
9 | # Python code
10 | cpp_class = CppClass(name = 'MyClass', is_struct = True)
11 | cpp_class.add_variable(CppVariable(name = "m_var",
12 | type = 'size_t',
13 | is_static = True,
14 | is_const = True,
15 | initialization_value = 255))
16 |
17 | // Generated C++ declaration
18 | struct MyClass
19 | {
20 | static const size_t m_var;
21 | }
22 |
23 | // Generated C++ definition
24 | const size_t MyClass::m_var = 255;
25 |
26 |
27 | That module uses and highly depends on code_generator.py as it uses
28 | code generating and formatting primitives implemented there.
29 |
30 | The main object referenced from code_generator.py is CppFile,
31 | which is passed as a parameter to render_to_string(cpp) Python method
32 |
33 | It could also be used for composing more complicated C++ code,
34 | that does not supported by cpp_generator
35 |
36 | It support:
37 |
38 | - functional calls:
39 | cpp('int a = 10;')
40 |
41 | - 'with' semantic:
42 | with cpp.block('class MyClass', ';')
43 | class_definition(cpp)
44 |
45 | - append code to the last string without EOL:
46 | cpp.append(', p = NULL);')
47 |
48 | - empty lines:
49 | cpp.newline(2)
50 |
51 | For detailed information see code_generator.py documentation.
52 | """
53 |
54 |
55 | ###########################################################################
56 | # declaration/Implementation helpers
57 | class CppDeclaration(object):
58 | """
59 | declaration/Implementation pair is used to split one element code generation to
60 | declaration and implementation parts
61 | E.g. method declaration
62 | struct Obj
63 | {
64 | int GetItem() const;
65 | }
66 |
67 | ... and method implementation
68 | int Obj::GetItem() const {...}
69 |
70 | That could be necessary to use unified render_to_string() interface, that is impossible for
71 | C++ primitives having two string representations (i.e. declaration and definition)
72 | """
73 |
74 | def __init__(self, cpp_element):
75 | self.cpp_element = cpp_element
76 |
77 | def render_to_string(self, cpp):
78 | self.cpp_element.render_to_string_declaration(cpp)
79 |
80 |
81 | class CppImplementation(object):
82 | """
83 | See declaration description
84 | """
85 |
86 | def __init__(self, cpp_element):
87 | self.cpp_element = cpp_element
88 |
89 | def render_to_string(self, cpp):
90 | self.cpp_element.render_to_string_implementation(cpp)
91 |
92 |
93 | # C++ language element generators
94 | class CppLanguageElement(object):
95 | """
96 | The base class for all C++ language elements.
97 | Contains dynamic storage for element properties
98 | (e.g. is_static for the variable is_virtual for the class method etc)
99 | """
100 | availablePropertiesNames = {'name', 'ref_to_parent'}
101 |
102 | def __init__(self, properties):
103 | """
104 | @param: properties - Basic C++ element properties (name, ref_to_parent)
105 | class is a parent for method or a member variable
106 | """
107 | self.name = properties.get('name')
108 | self.ref_to_parent = properties.get('ref_to_parent')
109 |
110 | def check_input_properties_names(self, input_property_names):
111 | """
112 | Ensure that all properties that passed to the CppLanguageElement are recognized.
113 | Raise an exception otherwise
114 | """
115 | unknown_properties = input_property_names.difference(self.availablePropertiesNames)
116 | if unknown_properties:
117 | raise AttributeError(
118 | f'Error: try to initialize {self.__class__.__name__} with unknown property: {repr(unknown_properties)}')
119 |
120 | def init_class_properties(self, current_class_properties, input_properties_dict, default_property_value=None):
121 | """
122 | @param: current_class_properties - all available properties for the C++ element to be generated
123 | @param: input_properties_dict - values for the initialized properties (e.g. is_const=True)
124 | @param: default_property_value - value for properties that are not initialized
125 | (None by default, because of same as False semantic)
126 | """
127 | # Set all available properties to DefaultValue
128 | for propertyName in current_class_properties:
129 | if propertyName not in CppLanguageElement.availablePropertiesNames:
130 | setattr(self, propertyName, default_property_value)
131 |
132 | # Set all defined properties values (all undefined will be left with defaults)
133 | for (propertyName, propertyValue) in input_properties_dict.items():
134 | if propertyName not in CppLanguageElement.availablePropertiesNames:
135 | setattr(self, propertyName, propertyValue)
136 |
137 | def process_boolean_properties(self, properties):
138 | """
139 | For every boolean property starting from 'is_' prefix generate a property without 'is_' prefix
140 | """
141 | res = {**properties}
142 | for prop in self.availablePropertiesNames:
143 | if prop.startswith("is_"):
144 | res[prop.replace("is_", "")] = properties.get(prop, False)
145 | return res
146 |
147 | def init_boolean_properties(self, current_class_properties, input_properties_dict):
148 | """
149 | Check if input properties contain either 'is_' prefixed properties or non-prefixed properties
150 | If so, initialize prefixed properties with non-prefixed values
151 | """
152 | for prop in self.availablePropertiesNames:
153 | if prop.startswith("is_"):
154 | non_prefixed = prop.replace("is_", "")
155 | if non_prefixed in input_properties_dict:
156 | setattr(self, prop, input_properties_dict[non_prefixed])
157 | current_class_properties.update(input_properties_dict)
158 |
159 | def render_to_string(self, cpp):
160 | """
161 | @param: cpp - handle that supports code generation interface (see code_generator.py)
162 | Typically it is passed to all child elements so that render their content
163 | """
164 | raise NotImplementedError('CppLanguageElement is an abstract class')
165 |
166 | def parent_qualifier(self):
167 | """
168 | Generate string for class name qualifiers
169 | Should be used for methods implementation and static class members definition.
170 | Ex.
171 | void MyClass::MyMethod()
172 | int MyClass::m_staticVar = 0;
173 |
174 | Supports for nested classes, e.g.
175 | void MyClass::NestedClass::
176 | """
177 | full_parent_qualifier = ''
178 | parent = self.ref_to_parent
179 | # walk through all existing parents
180 | while parent:
181 | full_parent_qualifier = f'{parent.name}::{full_parent_qualifier}'
182 | parent = parent.ref_to_parent
183 | return full_parent_qualifier
184 |
185 | def fully_qualified_name(self):
186 | """
187 | Generate string for fully qualified name of the element
188 | Ex.
189 | MyClass::NestedClass::Method()
190 | """
191 | return f'{self.parent_qualifier()}{self.name}'
192 |
--------------------------------------------------------------------------------
/src/code_generation/cpp/cpp_variable.py:
--------------------------------------------------------------------------------
1 | from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation
2 | from textwrap import dedent
3 |
4 | __doc__ = """The module encapsulates C++ code generation logics for main C++ language primitives:
5 | classes, methods and functions, variables, enums.
6 | Every C++ element could render its current state to a string that could be evaluated as
7 | a legal C++ construction.
8 |
9 | Some elements could be rendered to a pair of representations (i.e. declaration and definition)
10 |
11 | Example:
12 | # Python code
13 | cpp_class = CppClass(name = 'MyClass', is_struct = True)
14 | cpp_class.add_variable(CppVariable(name = "m_var",
15 | type = 'size_t',
16 | is_static = True,
17 | is_const = True,
18 | initialization_value = 255))
19 |
20 | // Generated C++ declaration
21 | struct MyClass
22 | {
23 | static const size_t m_var;
24 | }
25 |
26 | // Generated C++ definition
27 | const size_t MyClass::m_var = 255;
28 |
29 |
30 | That module uses and highly depends on code_generator.py as it uses
31 | code generating and formatting primitives implemented there.
32 |
33 | The main object referenced from code_generator.py is CppFile,
34 | which is passed as a parameter to render_to_string(cpp) Python method
35 |
36 | It could also be used for composing more complicated C++ code,
37 | that does not supported by cpp_generator
38 |
39 | It support:
40 |
41 | - functional calls:
42 | cpp('int a = 10;')
43 |
44 | - 'with' semantic:
45 | with cpp.block('class MyClass', ';')
46 | class_definition(cpp)
47 |
48 | - append code to the last string without EOL:
49 | cpp.append(', p = NULL);')
50 |
51 | - empty lines:
52 | cpp.newline(2)
53 |
54 | For detailed information see code_generator.py documentation.
55 | """
56 |
57 |
58 | # noinspection PyUnresolvedReferences
59 | class CppVariable(CppLanguageElement):
60 | """
61 | The Python class that generates string representation for C++ variable (automatic or class member)
62 | For example:
63 | class MyClass
64 | {
65 | int m_var1;
66 | double m_var2;
67 | ...
68 | }
69 | Available properties:
70 | type - string, variable type
71 | is_static - boolean, 'static' prefix
72 | is_extern - boolean, 'extern' prefix
73 | is_const - boolean, 'const' prefix
74 | is_constexpr - boolean, 'constexpr' prefix
75 | initialization_value - string, initialization_value to be initialized with.
76 | 'a = initialization_value;' for automatic variables, 'a(initialization_value)' for the class member
77 | documentation - string, '/// Example doxygen'
78 | is_class_member - boolean, for appropriate definition/declaration rendering
79 | """
80 | availablePropertiesNames = {'type',
81 | 'is_static',
82 | 'is_extern',
83 | 'is_const',
84 | 'is_constexpr',
85 | 'initialization_value',
86 | 'documentation',
87 | 'is_class_member'} | CppLanguageElement.availablePropertiesNames
88 |
89 | def __init__(self, **properties):
90 | input_property_names = set(properties.keys())
91 | self.check_input_properties_names(input_property_names)
92 | super(CppVariable, self).__init__(properties)
93 | self.init_class_properties(current_class_properties=self.availablePropertiesNames,
94 | input_properties_dict=properties)
95 |
96 | def _sanity_check(self):
97 | """
98 | @raise: ValueError, if some properties are not valid
99 | """
100 | if self.is_const and self.is_constexpr:
101 | raise ValueError("Variable object can be either 'const' or 'constexpr', not both")
102 | if self.is_constexpr and not self.initialization_value:
103 | raise ValueError("Variable object must be initialized when 'constexpr'")
104 | if self.is_static and self.is_extern:
105 | raise ValueError("Variable object can be either 'extern' or 'static', not both")
106 |
107 | def _render_static(self):
108 | """
109 | @return: 'static' prefix, can't be used with 'extern'
110 | """
111 | return 'static ' if self.is_static else ''
112 |
113 | def _render_extern(self):
114 | """
115 | @return: 'extern' prefix, can't be used with 'static'
116 | """
117 | return 'extern ' if self.is_extern else ''
118 |
119 | def _render_const(self):
120 | """
121 | @return: 'const' prefix, can't be used with 'constexpr'
122 | """
123 | return 'const ' if self.is_const else ''
124 |
125 | def _render_constexpr(self):
126 | """
127 | @return: 'constexpr' prefix, can't be used with 'const'
128 | """
129 | return 'constexpr ' if self.is_constexpr else ''
130 |
131 | def _render_init_value(self):
132 | """
133 | @return: string, initialization_value to be initialized with
134 | """
135 | return self.initialization_value if self.initialization_value else ''
136 |
137 | def assignment(self, value):
138 | """
139 | Generates assignment statement for the variable, e.g.
140 | a = 10;
141 | b = 20;
142 | """
143 | return f'{self.name} = {value}'
144 |
145 | def declaration(self):
146 | """
147 | @return: CppDeclaration wrapper, that could be used
148 | for declaration rendering using render_to_string(cpp) interface
149 | """
150 | return CppDeclaration(self)
151 |
152 | def definition(self):
153 | """
154 | @return: CppImplementation wrapper, that could be used
155 | for definition rendering using render_to_string(cpp) interface
156 | """
157 | return CppImplementation(self)
158 |
159 | def render_to_string(self, cpp):
160 | """
161 | Only automatic variables or static const class members could be rendered using this method
162 | Generates complete variable definition, e.g.
163 | int a = 10;
164 | const double b = M_PI;
165 | """
166 | self._sanity_check()
167 | if self.is_class_member and not (self.is_static and self.is_const):
168 | raise RuntimeError('For class member variables use definition() and declaration() methods')
169 | elif self.is_extern:
170 | cpp(f'{self._render_extern()}{self.type} {self.name};')
171 | else:
172 | if self.documentation:
173 | cpp(dedent(self.documentation))
174 | cpp(f'{self._render_static()}{self._render_const()}{self._render_constexpr()}'
175 | f'{self.type} {self.assignment(self._render_init_value())};')
176 |
177 | def render_to_string_declaration(self, cpp):
178 | """
179 | Generates declaration for the class member variables, for example
180 | int m_var;
181 | """
182 | if not self.is_class_member:
183 | raise RuntimeError('For automatic variable use its render_to_string() method')
184 |
185 | if self.documentation and self.is_class_member:
186 | cpp(dedent(self.documentation))
187 | cpp(f'{self._render_static()}{self._render_extern()}{self._render_const()}{self._render_constexpr()}'
188 | f'{self.type} {self.name if not self.is_constexpr else self.assignment(self._render_init_value())};')
189 |
190 | def render_to_string_implementation(self, cpp):
191 | """
192 | Generates definition for the class member variable.
193 | Output depends on the variable type
194 |
195 | Generates something like
196 | int MyClass::m_my_static_var = 0;
197 |
198 | for static class members, and
199 | m_var(0)
200 | for non-static class members.
201 | That string could be used in constructor initialization string
202 | """
203 | if not self.is_class_member:
204 | raise RuntimeError('For automatic variable use its render_to_string() method')
205 |
206 | # generate definition for the static class member
207 | if not self.is_constexpr:
208 | if self.is_static:
209 | cpp(f'{self._render_const()}{self._render_constexpr()}'
210 | f'{self.type} {self.fully_qualified_name()} = {self._render_init_value()};')
211 | # generate definition for non-static static class member, e.g. m_var(0)
212 | # (string for the constructor initialization list)
213 | else:
214 | cpp(f'{self.name}({self._render_init_value()})')
215 |
--------------------------------------------------------------------------------
/tests/test_cpp_file.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 | import filecmp
4 |
5 | from code_generation.core.code_generator import CppFile
6 | from code_generation.cpp.cpp_variable import CppVariable
7 | from code_generation.cpp.cpp_enum import CppEnum
8 | from code_generation.cpp.cpp_array import CppArray
9 | from code_generation.cpp.cpp_function import CppFunction
10 | from code_generation.cpp.cpp_class import CppClass
11 |
12 | __doc__ = """
13 | Unit tests for C++ code generator
14 | """
15 |
16 |
17 | class TestCppFileIo(unittest.TestCase):
18 | """
19 | Test C++ code generation by writing to file
20 | """
21 |
22 | def test_cpp_array(self):
23 | """
24 | Test C++ variables generation
25 | """
26 | cpp = CppFile('array.cpp')
27 | arrays = []
28 | a1 = CppArray(name='array1', type='int', is_const=True, array_size=5)
29 | a1.add_array_items(['1', '2', '3'])
30 | a2 = CppArray(name='array2', type='const char*', is_const=True)
31 | a2.add_array_item('"Item1"')
32 | a2.add_array_item('"Item2"')
33 | a3 = CppArray(name='array3', type='const char*', is_const=True, newline_align=True)
34 | a3.add_array_item('"ItemNewline1"')
35 | a3.add_array_item('"ItemNewline2"')
36 |
37 | arrays.append(a1)
38 | arrays.append(a2)
39 | arrays.append(a3)
40 |
41 | for arr in arrays:
42 | arr.render_to_string(cpp)
43 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'array.cpp'))
44 | cpp.close()
45 | self.assertTrue(os.path.exists('array.cpp'))
46 | if os.path.exists('array.cpp'):
47 | os.remove('array.cpp')
48 |
49 | def test_cpp_class(self):
50 | """
51 | Test C++ classes generation
52 | """
53 | my_class_cpp = CppFile('class.cpp')
54 | my_class_h = CppFile('class.h')
55 | my_class = CppClass(name='MyClass')
56 |
57 | enum_elements = CppEnum(name='Items', prefix='wd')
58 | for item in ['One', 'Two', 'Three']:
59 | enum_elements.add_item(item)
60 | my_class.add_enum(enum_elements)
61 |
62 | nested_class = CppClass(name='Nested', is_struct=True)
63 | nested_class.add_variable(CppVariable(name="m_gcAnswer",
64 | type="size_t",
65 | is_class_member=True,
66 | is_static=True,
67 | is_const=True,
68 | initialization_value='42'))
69 | my_class.add_internal_class(nested_class)
70 |
71 | my_class.add_variable(CppVariable(name="m_var1",
72 | type="int",
73 | initialization_value='1'))
74 |
75 | my_class.add_variable(CppVariable(name="m_var2",
76 | type="int*"))
77 |
78 | a2 = CppArray(name='array2', type='char*', is_const=True, is_static=True, )
79 | a2.add_array_item('"Item1"')
80 | a2.add_array_item('"Item2"')
81 | a3 = CppArray(name='array3', type='char*', is_static=True, is_const=True, newline_align=True)
82 | a3.add_array_item('"ItemNewline1"')
83 | a3.add_array_item('"ItemNewline2"')
84 |
85 | my_class.add_array(a2)
86 | my_class.add_array(a3)
87 |
88 | def const_method_body(_, cpp):
89 | cpp('return m_var1;')
90 |
91 | def virtual_method_body(_, cpp):
92 | cpp('return 0;')
93 |
94 | def static_method_body(_, cpp):
95 | cpp('return 0;')
96 |
97 | my_class.add_method(CppClass.CppMethod(name="GetParam",
98 | ret_type="int",
99 | is_const=True,
100 | implementation_handle=const_method_body))
101 |
102 | my_class.add_method(CppClass.CppMethod(name="VirtualMethod",
103 | ret_type="int",
104 | is_virtual=True,
105 | implementation_handle=virtual_method_body))
106 |
107 | my_class.add_method(CppClass.CppMethod(name="PureVirtualMethod",
108 | ret_type="void",
109 | is_virtual=True,
110 | is_pure_virtual=True))
111 |
112 | my_class.add_method(CppClass.CppMethod(name="StaticMethodMethod",
113 | ret_type="int",
114 | is_static=True,
115 | implementation_handle=static_method_body))
116 |
117 | my_class.declaration().render_to_string(my_class_h)
118 | my_class.definition().render_to_string(my_class_cpp)
119 |
120 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'class.cpp'))
121 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'class.h'))
122 | my_class_cpp.close()
123 | my_class_h.close()
124 | self.assertTrue(os.path.exists('class.cpp'))
125 | if os.path.exists('class.cpp'):
126 | os.remove('class.cpp')
127 | self.assertTrue(os.path.exists('class.h'))
128 | if os.path.exists('class.h'):
129 | os.remove('class.h')
130 |
131 | def test_cpp_enum(self):
132 | """
133 | Test C++ enums generation
134 | """
135 | cpp = CppFile('enum.cpp')
136 | enum_elements = CppEnum(name='Items')
137 | for item in ['Chair', 'Table', 'Shelve']:
138 | enum_elements.add_item(item)
139 | enum_elements.render_to_string(cpp)
140 |
141 | enum_elements_custom = CppEnum(name='Items', prefix='it')
142 | for item in ['Chair', 'Table', 'Shelve']:
143 | enum_elements_custom.add_item(item)
144 | enum_elements_custom.render_to_string(cpp)
145 |
146 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'enum.cpp'))
147 | cpp.close()
148 | if os.path.exists('enum.cpp'):
149 | os.remove('enum.cpp')
150 |
151 | def test_cpp_function(self):
152 | """
153 | Test C++ functions generation
154 | """
155 | cpp = CppFile('func.cpp')
156 | hpp = CppFile('func.h')
157 |
158 | def function_body(_, cpp1):
159 | cpp1('return 42;')
160 |
161 | functions = [CppFunction(name='GetParam', ret_type='int'),
162 | CppFunction(name='Calculate', ret_type='void'),
163 | CppFunction(name='GetAnswer', ret_type='int', implementation_handle=function_body)]
164 |
165 | for func in functions:
166 | func.render_to_string(hpp)
167 | for func in functions:
168 | func.render_to_string_declaration(hpp)
169 | functions[2].render_to_string_implementation(cpp)
170 |
171 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'func.cpp'))
172 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'func.h'))
173 | cpp.close()
174 | hpp.close()
175 | if os.path.exists('func.cpp'):
176 | os.remove('func.cpp')
177 | if os.path.exists('func.h'):
178 | os.remove('func.h')
179 |
180 | def test_cpp_variable(self):
181 | """
182 | Test C++ variables generation
183 | """
184 | cpp = CppFile('var.cpp')
185 | variables = [CppVariable(name="var1",
186 | type="char*",
187 | is_class_member=False,
188 | is_static=False,
189 | is_const=True,
190 | initialization_value='0'),
191 | CppVariable(name="var2",
192 | type="int",
193 | is_class_member=False,
194 | is_static=True,
195 | is_const=False,
196 | initialization_value='0'),
197 | CppVariable(name="var3",
198 | type="std::string",
199 | is_class_member=False,
200 | is_static=False,
201 | is_const=False),
202 | CppVariable(name="var3",
203 | type="std::string",
204 | is_class_member=False,
205 | is_static=False,
206 | is_const=False)]
207 |
208 | for var in variables:
209 | var.render_to_string(cpp)
210 | self.assertTrue(filecmp.cmpfiles('.', 'tests', 'var.cpp'))
211 | cpp.close()
212 | if os.path.exists('var.cpp'):
213 | os.remove('var.cpp')
214 |
215 |
216 | if __name__ == "__main__":
217 | unittest.main()
218 |
--------------------------------------------------------------------------------
/tests/create_assets.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from code_generation.core.code_generator import CppFile
4 | from code_generation.cpp.cpp_variable import CppVariable
5 | from code_generation.cpp.cpp_enum import CppEnum
6 | from code_generation.cpp.cpp_array import CppArray
7 | from code_generation.cpp.cpp_function import CppFunction
8 | from code_generation.cpp.cpp_class import CppClass
9 |
10 | __doc__ = """Do not call this script unless generator logic is changed
11 | """
12 |
13 | PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
14 |
15 |
16 | def generate_enum(output_dir='.'):
17 | """
18 | Generate model data (C++ enum)
19 | Do not call unless generator logic is changed
20 | """
21 | cpp = CppFile(os.path.join(output_dir, 'enum.cpp'))
22 | enum_elements = CppEnum(name='Items')
23 | for item in ['Chair', 'Table', 'Shelve']:
24 | enum_elements.add_item(item)
25 | enum_elements.render_to_string(cpp)
26 |
27 | enum_elements_custom = CppEnum(name='Items', prefix='it')
28 | for item in ['Chair', 'Table', 'Shelve']:
29 | enum_elements_custom.add_item(item)
30 | enum_elements_custom.render_to_string(cpp)
31 |
32 | enum_elements_custom = CppEnum(name='Items', prefix='')
33 | for item in ['Chair', 'Table', 'Shelve']:
34 | enum_elements_custom.add_item(item)
35 | enum_elements_custom.render_to_string(cpp)
36 |
37 | enum_elements_custom = CppEnum(name='Items', prefix='', add_counter=False)
38 | for item in ['Chair', 'Table', 'Shelve']:
39 | enum_elements_custom.add_item(item)
40 | enum_elements_custom.render_to_string(cpp)
41 |
42 | enum_elements_custom = CppEnum(name='Items', add_counter=False)
43 | for item in ['Chair', 'Table', 'Shelve']:
44 | enum_elements_custom.add_item(item)
45 | enum_elements_custom.render_to_string(cpp)
46 |
47 | cpp.close()
48 |
49 |
50 | def generate_var(output_dir='.'):
51 | """
52 | Generate model data (C++ variables)
53 | Do not call unless generator logic is changed
54 | """
55 | cpp = CppFile(os.path.join(output_dir, 'var.cpp'))
56 | variables = [CppVariable(name="var1",
57 | type="char*",
58 | is_class_member=False,
59 | is_static=False,
60 | is_const=True,
61 | initialization_value='0'),
62 | CppVariable(name="var2",
63 | type="int",
64 | is_class_member=False,
65 | is_static=True,
66 | is_const=False,
67 | initialization_value='0'),
68 | CppVariable(name="var3",
69 | type="std::string",
70 | is_class_member=False,
71 | is_static=False,
72 | is_const=False),
73 | CppVariable(name="var4",
74 | type="int",
75 | documentation='// A number',
76 | is_class_member=False,
77 | is_static=False,
78 | is_const=False),
79 | ]
80 |
81 | for var in variables:
82 | var.render_to_string(cpp)
83 |
84 | cpp.close()
85 |
86 |
87 | def generate_array(output_dir='.'):
88 | cpp = CppFile(os.path.join(output_dir, 'array.cpp'))
89 | arrays = []
90 | a1 = CppArray(name='array1', type='int', is_const=True, array_size=5)
91 | a1.add_array_items(['1', '2', '3'])
92 | a2 = CppArray(name='array2', type='const char*', is_const=True)
93 | a2.add_array_item('"Item1"')
94 | a2.add_array_item('"Item2"')
95 | a3 = CppArray(name='array3', type='const char*', is_const=True, newline_align=True)
96 | a3.add_array_item('"ItemNewline1"')
97 | a3.add_array_item('"ItemNewline2"')
98 |
99 | arrays.append(a1)
100 | arrays.append(a2)
101 | arrays.append(a3)
102 |
103 | for arr in arrays:
104 | arr.render_to_string(cpp)
105 |
106 | cpp.close()
107 |
108 |
109 | def generate_func(output_dir='.'):
110 | """
111 | Generate model data (C++ functions)
112 | Do not call unless generator logic is changed
113 | """
114 | cpp = CppFile(os.path.join(output_dir, 'func.cpp'))
115 | hpp = CppFile(os.path.join(output_dir, 'func.h'))
116 |
117 | def function_body(_, cpp1):
118 | cpp1('return 42;')
119 |
120 | functions = [CppFunction(name='GetParam', ret_type='int'),
121 | CppFunction(name='Calculate', ret_type='void'),
122 | CppFunction(name='GetAnswer', ret_type='int', implementation_handle=function_body),
123 | CppFunction(name='Help', ret_type='char *', documentation='/// Returns the help documentation.'),
124 | ]
125 | for func in functions:
126 | func.render_to_string(hpp)
127 | for func in functions:
128 | func.render_to_string_declaration(hpp)
129 | for func in functions:
130 | func.render_to_string_implementation(cpp)
131 | cpp.close()
132 | hpp.close()
133 |
134 |
135 | def generate_class(output_dir='.'):
136 | """
137 | Generate model data (C++ classes)
138 | Do not call unless generator logic is changed
139 | """
140 | my_class_cpp = CppFile(os.path.join(output_dir, 'class.cpp'))
141 | my_class_h = CppFile(os.path.join(output_dir, 'class.h'))
142 | my_class = CppClass(name='MyClass', ref_to_parent=None)
143 | example_class = CppClass(
144 | name='Example',
145 | documentation="""\
146 | /// An example
147 | /// class with
148 | /// multiline documentation""",
149 | )
150 |
151 | enum_elements = CppEnum(name='Items', prefix='wd')
152 | for item in ['One', 'Two', 'Three']:
153 | enum_elements.add_item(item)
154 | my_class.add_enum(enum_elements)
155 |
156 | nested_class = CppClass(name='Nested', is_struct=True)
157 | nested_class.add_variable(CppVariable(name="m_gcAnswer",
158 | type="size_t",
159 | is_class_member=True,
160 | is_static=True,
161 | is_const=True,
162 | initialization_value='42'))
163 | my_class.add_internal_class(nested_class)
164 |
165 | my_class.add_variable(CppVariable(name="m_var1",
166 | type="int",
167 | initialization_value='1'))
168 |
169 | my_class.add_variable(CppVariable(name="m_var2",
170 | type="int*"))
171 |
172 | my_class.add_variable(CppVariable(name="m_var3",
173 | type="int",
174 | is_constexpr=True,
175 | is_static=True,
176 | is_class_member=True,
177 | initialization_value=42))
178 |
179 | example_class.add_variable(CppVariable(
180 | name="m_var1",
181 | documentation="/// A number.",
182 | type="int"),
183 | )
184 |
185 | a2 = CppArray(name='array2', type='char*', is_const=True, is_static=True, )
186 | a2.add_array_item('"Item1"')
187 | a2.add_array_item('"Item2"')
188 | a3 = CppArray(name='array3', type='char*', is_static=True, is_const=True, newline_align=True)
189 | a3.add_array_item('"ItemNewline1"')
190 | a3.add_array_item('"ItemNewline2"')
191 |
192 | my_class.add_array(a2)
193 | my_class.add_array(a3)
194 |
195 | def method_body(_, cpp1):
196 | cpp1('return m_var1;')
197 |
198 | my_class.add_method(CppFunction(name="GetParam",
199 | ret_type="int",
200 | is_method=True,
201 | is_const=True,
202 | implementation_handle=method_body))
203 |
204 | my_class.add_method(CppFunction(name="VirtualMethod",
205 | ret_type="int",
206 | is_method=True,
207 | is_virtual=True))
208 |
209 | my_class.add_method(CppFunction(name="PureVirtualMethod",
210 | ret_type="void",
211 | is_method=True,
212 | is_virtual=True,
213 | is_pure_virtual=True))
214 |
215 | example_class.add_method(CppFunction(
216 | name="DoNothing",
217 | documentation="""\
218 | /**
219 | * Example multiline documentation.
220 | */""",
221 | ret_type="void"),
222 | )
223 |
224 | my_class.declaration().render_to_string(my_class_h)
225 | example_class.declaration().render_to_string(my_class_h)
226 | my_class.definition().render_to_string(my_class_cpp)
227 | example_class.definition().render_to_string(my_class_cpp)
228 | my_class_cpp.close()
229 | my_class_h.close()
230 |
231 |
232 | def generate_factorial(output_dir='.'):
233 | cpp = CppFile(os.path.join(output_dir, 'factorial.cpp'))
234 | h = CppFile(os.path.join(output_dir, 'factorial.h'))
235 |
236 | def handle_to_factorial(_, cpp_file):
237 | cpp_file('return n < 1 ? 1 : (n * factorial(n - 1));')
238 |
239 | func = CppFunction(name="factorial", ret_type="int",
240 | implementation_handle=handle_to_factorial,
241 | is_constexpr=True)
242 | func.add_argument('int n')
243 | func.render_to_string(cpp)
244 | func.render_to_string_declaration(h)
245 | cpp.close()
246 | h.close()
247 |
248 |
249 | def generate_reference_code():
250 | """
251 | Generate model data for C++ generator
252 | Do not call unless generator logic is changed
253 | """
254 | asset_dir = os.path.join(PROJECT_DIR, 'new_assets')
255 | generate_enum(output_dir=asset_dir)
256 | generate_var(output_dir=asset_dir)
257 | generate_array(output_dir=asset_dir)
258 | generate_func(output_dir=asset_dir)
259 | generate_class(output_dir=asset_dir)
260 | generate_factorial(output_dir=asset_dir)
261 |
262 |
263 | if __name__ == "__main__":
264 | generate_reference_code()
265 |
--------------------------------------------------------------------------------
/release_package.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | import sys
4 | import argparse
5 | import platform
6 | import pathlib
7 | import json
8 | from subprocess import run
9 | from configparser import ConfigParser
10 |
11 | cfg = ConfigParser()
12 | cfg.read(filenames=['setup.cfg'])
13 | VERSION = cfg.get('metadata', 'version')
14 |
15 | # Home dir
16 | HOME = pathlib.Path.home()
17 |
18 | # Name with underscore (wheel filename)
19 | PACKAGE_NAME = cfg.get('metadata', 'name')
20 |
21 | # Name with dash (pip name, URL, S3 bucket)
22 | PACKAGE_NAME_DASH = PACKAGE_NAME.replace('_', '-')
23 |
24 | PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__))))
25 | PYTHON = "python3"
26 | PIP = "pip3"
27 |
28 |
29 | def is_linux():
30 | """
31 | :return: True if system is Linux, False otherwise
32 | """
33 | return platform.system() == 'Linux'
34 |
35 |
36 | def is_macos():
37 | """
38 | :return: True if system is MacOS, False otherwise
39 | """
40 | return platform.system() == 'Darwin'
41 |
42 |
43 | def is_windows():
44 | """
45 | :return: True if system is Windows, False otherwise
46 | """
47 | return platform.system() == 'Windows'
48 |
49 |
50 | if is_macos() or is_linux():
51 | PYTHON = "python3"
52 | PIP = "pip3"
53 | elif is_windows():
54 | PYTHON = "python"
55 | PIP = "pip"
56 |
57 |
58 | def executable_exists(executable):
59 | """
60 | :param executable: Name of the executable
61 | :return: True if executable exists, False otherwise
62 | """
63 | try:
64 | run([executable, '--version'])
65 | return True
66 | except FileNotFoundError:
67 | return False
68 |
69 |
70 | def sanity_check(args):
71 | """
72 | Check if all required executables and configs are available
73 | """
74 | if not os.path.isdir(os.path.join(PROJECT_DIR, 'src', PACKAGE_NAME)):
75 | print(f'Cannot find src/{PACKAGE_NAME}')
76 | sys.exit(1)
77 | if args.create_release and not release_version_exists(VERSION):
78 | print(f'No release notes found for version {VERSION}')
79 | sys.exit(1)
80 | if args.upload_s3 and not executable_exists('aws'):
81 | print('awscli not installed')
82 | sys.exit(1)
83 | if args.create_release and not executable_exists('gh'):
84 | print('GitHub CLI not installed')
85 | sys.exit(1)
86 | if args.publish_pypi and not executable_exists('twine'):
87 | print('twine not installed')
88 | sys.exit(1)
89 | if args.publish_pypi and not os.path.isfile(os.path.join(HOME, '.pypirc')):
90 | print('No ~/.pypirc file found')
91 | sys.exit(1)
92 |
93 |
94 | def wheel_path():
95 | """
96 | :return: Path to the wheel file
97 | """
98 | return os.path.join(PROJECT_DIR, 'dist', f'{PACKAGE_NAME}-{VERSION}-py3-none-any.whl')
99 |
100 |
101 | def uninstall_wheel():
102 | """
103 | pip.exe uninstall -y {PACKAGE_NAME_DASH}
104 | """
105 | run([PIP, 'uninstall', '-y', PACKAGE_NAME_DASH])
106 |
107 |
108 | def publish_pypi():
109 | f"""
110 | Publish the package to PyPI
111 | Example:
112 | twine upload dist/{PACKAGE_NAME}-2.9.34-py3-none-any.whl
113 | """
114 | run([PYTHON, '-m', 'pip', 'install', '--upgrade', 'build', 'twine'])
115 | run(['twine', 'check', 'dist/*'])
116 | run(['twine', 'upload', 'dist/*'])
117 |
118 |
119 | def build_wheel():
120 | """
121 | python.exe -m pip install --upgrade pip
122 | python.exe -m pip install --upgrade build
123 | python.exe -m build
124 | """
125 | run([PYTHON, '-m', 'pip', 'install', '--upgrade', 'pip'])
126 | run([PYTHON, '-m', 'pip', 'install', '--upgrade', 'build'])
127 | run([PYTHON, '-m', 'build'])
128 |
129 |
130 | def install_wheel():
131 | """
132 | pip.exe install ./dist/{PACKAGE_NAME}-{VERSION}-py3-none-any.whl
133 | """
134 | run([PIP, 'install', wheel_path()])
135 |
136 |
137 | def install_wheel_devmode():
138 | f"""
139 | pip.exe install -e ./dist/{PACKAGE_NAME}-{VERSION}-py3-none-any.whl
140 | """
141 | run([PIP, 'install', '-e', '.'])
142 |
143 |
144 | def cleanup_old_wheels():
145 | f"""
146 | Remove all previous {PACKAGE_NAME}-{VERSION}-py3-none-any.whl in dist
147 | """
148 | if os.path.isdir(os.path.join(PROJECT_DIR, 'dist')):
149 | for file in os.listdir(os.path.join(PROJECT_DIR, 'dist')):
150 | if file.startswith(f'{PACKAGE_NAME}-'):
151 | os.remove(os.path.join(PROJECT_DIR, 'dist', file))
152 |
153 |
154 | def upload_s3():
155 | f"""
156 | Upload the package to S3
157 | Example:
158 | aws s3 cp {PACKAGE_NAME}-2.9.31-py3-none-any.whl s3://{PACKAGE_NAME_DASH}/packages/
159 | aws s3api put-object-acl
160 | --bucket {PACKAGE_NAME_DASH}
161 | --key packages/{PACKAGE_NAME}-2.9.31-py3-none-any.whl
162 | --acl public-read
163 | """
164 | run(['aws', 's3', 'cp', wheel_path(), f's3://{PACKAGE_NAME_DASH}/packages/'])
165 | run(['aws', 's3api', 'put-object-acl',
166 | '--bucket', f'{PACKAGE_NAME_DASH}',
167 | '--key', f'packages/{PACKAGE_NAME}-{VERSION}-py3-none-any.whl',
168 | '--acl', 'public-read'])
169 |
170 |
171 | def tag_release():
172 | """
173 | Tag the release on GitHub
174 | Example:
175 | git tag -a release.2.9.34 -m"Release 2.9.34"
176 | git push origin --tags master
177 | """
178 | run(['git', 'tag', '-a', 'release.{}'.format(VERSION), '-m', 'Release {}'.format(VERSION)])
179 | run(['git', 'push', 'origin', '--tags', 'master'])
180 |
181 |
182 | def create_release(release_file):
183 | f"""
184 | Create a release on GitHub
185 | Example:
186 | gh release create release.2.9.34 dist/{PACKAGE_NAME}-2.9.34-py3-none-any.whl --title 2.9.34 --notes-file RELEASE.md
187 | """
188 | run(['gh', 'release', 'create', 'release.{}'.format(VERSION), wheel_path(),
189 | '--title', '{}'.format(VERSION),
190 | '--notes-file', release_file])
191 |
192 |
193 | def release_version_exists(version):
194 | """
195 | Check if version with respective release notes exists in RELEASE_NOTES.json
196 | :param version: Version to check in format 2.9.34
197 | :return: True if version exists, False otherwise
198 | """
199 | with open(os.path.join(PROJECT_DIR, 'RELEASE_NOTES.json'), 'r') as release_json:
200 | release_notes = json.load(release_json)
201 | return True if version in release_notes['releases'] else False
202 |
203 |
204 | def tmp_release_notes():
205 | """
206 | Read the last release notes in JSON format from release_notes.json and create a temporary release notes file
207 | :return: Path to the temporary release notes file
208 | """
209 | # read release_notes.json as dict
210 | release_md = 'RELEASE.md'
211 | with open('RELEASE_NOTES.json', 'r') as release_json:
212 | release_notes = json.load(release_json)
213 |
214 | if not release_version_exists(VERSION):
215 | print(f'No release notes found for version {VERSION}')
216 | sys.exit(1)
217 |
218 | last_release = release_notes['releases'][VERSION]['release_notes']
219 | url_template = release_notes['release']['download_link']
220 | release_url = url_template.format(version=VERSION,
221 | package_name=PACKAGE_NAME,
222 | package_name_dash=PACKAGE_NAME_DASH)
223 | # create a temporary release notes file
224 | with open(release_md, 'w') as release_tmp:
225 | release_tmp.write('## Release notes\n')
226 | for note in last_release:
227 | release_tmp.write('* {}\n'.format(note))
228 | release_tmp.write('## Staging Area Download URL\n')
229 | release_tmp.write('[Wheel Package {} on AWS S3]({})\n'.format(VERSION, release_url))
230 | return os.path.abspath(release_md)
231 |
232 |
233 | def increment_minor_version(file_path: str):
234 | """
235 | Increment minor package version number, e.g. 2.2.9 -> 2.2.10
236 | :return: New version number
237 | """
238 | config = ConfigParser()
239 | config.read(file_path)
240 | version = config['metadata']['version']
241 | major, minor, patch = [int(v) for v in version.split('.')]
242 | patch += 1
243 | new_version = f"{major}.{minor}.{patch}"
244 | config['metadata']['version'] = new_version
245 | with open(file_path, 'w') as f:
246 | config.write(f)
247 | return new_version
248 |
249 |
250 | def main():
251 | parser = argparse.ArgumentParser(description='Command-line params')
252 | parser.add_argument('--mode',
253 | help='What to do with the package',
254 | choices=["build", "install", "dev-mode", "reinstall", "uninstall"],
255 | default="reinstall",
256 | required=False)
257 | parser.add_argument('--upload-s3',
258 | help='Upload the package to S3',
259 | action='store_true',
260 | required=False)
261 | parser.add_argument('--create-release',
262 | help='Create a release on GitHub',
263 | action='store_true',
264 | required=False)
265 | parser.add_argument('--publish-pypi',
266 | help='Publish the package to PyPI server',
267 | action='store_true',
268 | default=False,
269 | required=False)
270 | parser.add_argument('--increment-version',
271 | help='Increment minor version number, e.g. 2.2.9 -> 2.2.10',
272 | action='store_true',
273 | default=False,
274 | required=False)
275 | args = parser.parse_args()
276 |
277 | global VERSION
278 | print(f'Package name: {PACKAGE_NAME}')
279 | print(f'Package name2: {PACKAGE_NAME_DASH}')
280 | print(f'Version: {VERSION}')
281 | sanity_check(args)
282 |
283 | if args.increment_version:
284 | new_version = increment_minor_version('setup.cfg')
285 | VERSION = new_version
286 | print(f'Increment version to {new_version}')
287 |
288 | if args.mode == "build":
289 | build_wheel()
290 | elif args.mode == "install":
291 | cleanup_old_wheels()
292 | build_wheel()
293 | install_wheel()
294 | elif args.mode == "dev-mode":
295 | cleanup_old_wheels()
296 | build_wheel()
297 | install_wheel_devmode()
298 | elif args.mode == "reinstall":
299 | cleanup_old_wheels()
300 | uninstall_wheel()
301 | build_wheel()
302 | install_wheel()
303 | elif args.mode == "uninstall":
304 | uninstall_wheel()
305 | else:
306 | print("Unknown mode")
307 |
308 | if args.upload_s3 and args.mode != "uninstall":
309 | upload_s3()
310 |
311 | if args.create_release and args.mode != "uninstall":
312 | release_file = tmp_release_notes()
313 | tag_release()
314 | create_release(release_file=release_file)
315 | os.remove(release_file)
316 |
317 | if args.publish_pypi and args.mode != "uninstall":
318 | publish_pypi()
319 |
320 | return 0
321 |
322 |
323 | if __name__ == '__main__':
324 | sys.exit(main())
325 |
--------------------------------------------------------------------------------
/src/code_generation/cpp/cpp_class.py:
--------------------------------------------------------------------------------
1 | from code_generation.cpp.cpp_generator import CppLanguageElement, CppDeclaration, CppImplementation
2 | from code_generation.cpp.cpp_function import CppFunction
3 | from textwrap import dedent
4 |
5 |
6 | class CppClass(CppLanguageElement):
7 | """
8 | The Python class that generates string representation for C++ class or struct.
9 | Usually contains a number of child elements - internal classes, enums, methods and variables.
10 | Available properties:
11 | is_struct - boolean, use 'struct' keyword for class declaration, 'class' otherwise
12 | documentation - string, '/// Example doxygen'
13 |
14 | Example of usage:
15 |
16 | # Python code
17 | cpp_class = CppClass(name = 'MyClass', is_struct = True)
18 | cpp_class.add_variable(CppVariable(name = "m_var",
19 | type = 'size_t',
20 | is_static = True,
21 | is_const = True,
22 | initialization_value = 255))
23 |
24 | def handle(cpp): cpp('return m_var;')
25 |
26 | cpp_class.add_method(CppFunction(name = "GetVar",
27 | ret_type = 'size_t',
28 | is_static = True,
29 | implementation_handle = handle))
30 |
31 | // Generated C++ declaration
32 | struct MyClass
33 | {
34 | static const size_t m_var;
35 | static size_t GetVar();
36 | }
37 |
38 | // Generated C++ definition
39 | const size_t MyClass::m_var = 255;
40 |
41 | size_t MyClass::GetVar()
42 | {
43 | return m_var;
44 | }
45 | """
46 | availablePropertiesNames = {'is_struct',
47 | 'documentation',
48 | 'parent_class'} | CppLanguageElement.availablePropertiesNames
49 |
50 | class CppMethod(CppFunction):
51 | """
52 | The Python class that generates string representation for C++ method
53 | Parameters are passed as plain strings('int a', 'void p = NULL' etc.)
54 | Available properties:
55 | ret_type - string, return value for the method ('void', 'int'). Could not be set for constructors
56 | is_static - boolean, static method prefix
57 | is_const - boolean, const method prefix, could not be static
58 | is_virtual - boolean, virtual method postfix, could not be static
59 | is_pure_virtual - boolean, ' = 0' method postfix, could not be static
60 | documentation - string, '/// Example doxygen'
61 | implementation_handle - reference to a function that receives 'self' and C++ code generator handle
62 | (see code_generator.cpp) and generates method body without braces
63 | Ex.
64 | #Python code
65 | def functionBody(self, cpp): cpp('return 42;')
66 | f1 = CppFunction(name = 'GetAnswer',
67 | ret_type = 'int',
68 | documentation = '// Generated code',
69 | implementation_handle = functionBody)
70 |
71 | // Generated code
72 | int MyClass::GetAnswer()
73 | {
74 | return 42;
75 | }
76 | """
77 | availablePropertiesNames = {'ret_type',
78 | 'is_static',
79 | 'is_constexpr',
80 | 'is_virtual',
81 | 'is_inline',
82 | 'is_pure_virtual',
83 | 'is_const',
84 | 'is_override',
85 | 'is_final',
86 | 'implementation_handle',
87 | 'documentation'} | CppLanguageElement.availablePropertiesNames
88 |
89 | def __init__(self, **properties):
90 | # arguments are plain strings
91 | # e.g. 'int* a', 'const string& s', 'size_t sz = 10'
92 | self.is_static = False
93 | self.is_constexpr = False
94 | self.is_virtual = False
95 | self.is_inline = False
96 | self.is_pure_virtual = False
97 | self.is_const = False
98 | self.is_override = False
99 | self.is_final = False
100 | self.arguments = []
101 | self.implementation_handle = properties.get('implementation_handle')
102 | self.documentation = properties.get('documentation')
103 |
104 | # check properties
105 | input_property_names = set(properties.keys())
106 | self.check_input_properties_names(input_property_names)
107 | super().__init__(**properties)
108 | self.init_class_properties(current_class_properties=self.availablePropertiesNames,
109 | input_properties_dict=properties)
110 |
111 | def _render_static(self):
112 | """
113 | Before function name, declaration only
114 | Static functions can't be const, virtual or pure virtual
115 | """
116 | return 'static ' if self.is_static else ''
117 |
118 | def _render_constexpr(self):
119 | """
120 | Before function name, declaration only
121 | Constexpr functions can't be const, virtual or pure virtual
122 | """
123 | return 'constexpr ' if self.is_constexpr else ''
124 |
125 | def _render_virtual(self):
126 | """
127 | Before function name, could be in declaration or definition
128 | Virtual functions can't be static or constexpr
129 | """
130 | return 'virtual ' if self.is_virtual else ''
131 |
132 | def _render_inline(self):
133 | """
134 | Before function name, could be in declaration or definition
135 | Inline functions can't be static, virtual or constexpr
136 | """
137 | return 'inline ' if self.is_inline else ''
138 |
139 | def _render_ret_type(self):
140 | """
141 | Return type, could be in declaration or definition
142 | """
143 | return self.ret_type if self.ret_type else ''
144 |
145 | def _render_pure(self):
146 | """
147 | After function name, declaration only
148 | Pure virtual functions must be virtual
149 | """
150 | return ' = 0' if self.is_pure_virtual else ''
151 |
152 | def _render_const(self):
153 | """
154 | After function name, could be in declaration or definition
155 | Const functions can't be static, virtual or constexpr
156 | """
157 | return ' const' if self.is_const else ''
158 |
159 | def _render_override(self):
160 | """
161 | After function name, could be in declaration or definition
162 | Override functions must be virtual
163 | """
164 | return ' override' if self.is_override else ''
165 |
166 | def _render_final(self):
167 | """
168 | After function name, could be in declaration or definition
169 | Final functions must be virtual
170 | """
171 | return ' final' if self.is_final else ''
172 |
173 | def _sanity_check(self):
174 | """
175 | Check whether attributes compose a correct C++ code
176 | """
177 | if self.is_inline and (self.is_virtual or self.is_pure_virtual):
178 | raise ValueError(f'Inline method {self.name} could not be virtual')
179 | if self.is_constexpr and (self.is_virtual or self.is_pure_virtual):
180 | raise ValueError(f'Constexpr method {self.name} could not be virtual')
181 | if self.is_const and self.is_static:
182 | raise ValueError(f'Static method {self.name} could not be const')
183 | if self.is_const and self.is_virtual:
184 | raise ValueError(f'Virtual method {self.name} could not be const')
185 | if self.is_const and self.is_pure_virtual:
186 | raise ValueError(f'Pure virtual method {self.name} could not be const')
187 | if self.is_override and not self.is_virtual:
188 | raise ValueError(f'Override method {self.name} should be virtual')
189 | if self.is_inline and (self.is_virtual or self.is_pure_virtual):
190 | raise ValueError(f'Inline method {self.name} could not be virtual')
191 | if self.is_final and not self.is_virtual:
192 | raise ValueError(f'Final method {self.name} should be virtual')
193 | if self.is_static and self.is_virtual:
194 | raise ValueError(f'Static method {self.name} could not be virtual')
195 | if self.is_pure_virtual and not self.is_virtual:
196 | raise ValueError(f'Pure virtual method {self.name} is also a virtual method')
197 | if not self.ref_to_parent:
198 | raise ValueError(f'Method {self.name} object must be a child of CppClass')
199 | if self.is_constexpr and self.implementation_handle is None:
200 | raise ValueError(f'Method {self.name} object must be initialized when "constexpr"')
201 | if self.is_pure_virtual and self.implementation_handle is not None:
202 | raise ValueError(f'Pure virtual method {self.name} could not be implemented')
203 |
204 | def add_argument(self, argument):
205 | """
206 | @param: argument string representation of the C++ function argument ('int a', 'void p = NULL' etc)
207 | """
208 | self.arguments.append(argument)
209 |
210 | def args(self):
211 | """
212 | @return: string arguments
213 | """
214 | return ", ".join(self.arguments)
215 |
216 | def implementation(self, cpp):
217 | """
218 | The method calls Python function that creates C++ method body if handle exists
219 | """
220 | if self.implementation_handle is not None:
221 | self.implementation_handle(self, cpp)
222 |
223 | def declaration(self):
224 | """
225 | @return: CppDeclaration wrapper, that could be used
226 | for declaration rendering using render_to_string(cpp) interface
227 | """
228 | return CppDeclaration(self)
229 |
230 | def definition(self):
231 | """
232 | @return: CppImplementation wrapper, that could be used
233 | for definition rendering using render_to_string(cpp) interface
234 | """
235 | return CppImplementation(self)
236 |
237 | def render_to_string(self, cpp):
238 | """
239 | By default, method is rendered as a declaration together with implementation,
240 | like the method is implemented within the C++ class body, e.g.
241 | class A
242 | {
243 | void f()
244 | {
245 | ...
246 | }
247 | }
248 | """
249 | # check all properties for the consistency
250 | self._sanity_check()
251 | if self.documentation:
252 | cpp(dedent(self.documentation))
253 | with cpp.block(f'{self._render_static()}{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}'
254 | f'{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})'
255 | f'{self._render_const()}'
256 | f'{self._render_override()}'
257 | f'{self._render_final()}'
258 | f'{self._render_pure()}'):
259 | self.implementation(cpp)
260 |
261 | def render_to_string_declaration(self, cpp):
262 | """
263 | Special case for a method declaration string representation.
264 | Generates just a function signature terminated by ';'
265 | Example:
266 | int GetX() const;
267 | """
268 | # check all properties for the consistency
269 | self._sanity_check()
270 | if self.is_constexpr:
271 | if self.documentation:
272 | cpp(dedent(self.documentation))
273 | self.render_to_string(cpp)
274 | else:
275 | cpp(f'{self._render_static()}{self._render_virtual()}{self._render_inline()}'
276 | f'{self._render_ret_type()} {self.name}({self.args()})'
277 | f'{self._render_const()}'
278 | f'{self._render_override()}'
279 | f'{self._render_final()}'
280 | f'{self._render_pure()};')
281 |
282 | def render_to_string_implementation(self, cpp):
283 | """
284 | Special case for a method implementation string representation.
285 | Generates method string in the form
286 | Example:
287 | int MyClass::GetX() const
288 | {
289 | ...
290 | }
291 | Generates method body if self.implementation_handle property exists
292 | """
293 | # check all properties for the consistency
294 | self._sanity_check()
295 |
296 | if self.implementation_handle is None:
297 | raise RuntimeError(f'No implementation handle for the method {self.name}')
298 |
299 | if self.documentation and not self.is_constexpr:
300 | cpp(dedent(self.documentation))
301 | with cpp.block(f'{self._render_virtual()}{self._render_constexpr()}{self._render_inline()}'
302 | f'{self._render_ret_type()} {self.fully_qualified_name()}({self.args()})'
303 | f'{self._render_const()}'
304 | f'{self._render_override()}'
305 | f'{self._render_final()}'
306 | f'{self._render_pure()}'):
307 | self.implementation(cpp)
308 |
309 | def __init__(self, **properties):
310 | self.is_struct = False
311 | self.documentation = None
312 | self.parent_class = None
313 | input_property_names = set(properties.keys())
314 | self.check_input_properties_names(input_property_names)
315 | super(CppClass, self).__init__(properties)
316 | self.init_class_properties(current_class_properties=self.availablePropertiesNames,
317 | input_properties_dict=properties)
318 |
319 | # aggregated classes
320 | self.internal_class_elements = []
321 |
322 | # class members
323 | self.internal_variable_elements = []
324 |
325 | # array class members
326 | self.internal_array_elements = []
327 |
328 | # class methods
329 | self.internal_method_elements = []
330 |
331 | # class enums
332 | self.internal_enum_elements = []
333 |
334 | def _parent_class(self):
335 | """
336 | @return: parent class object
337 | """
338 | return self.parent_class if self.parent_class else ""
339 |
340 | def inherits(self):
341 | """
342 | @return: string representation of the inheritance
343 | """
344 | return f' : public {self._parent_class()}' if self.parent_class else ''
345 |
346 | ########################################
347 | # ADD CLASS MEMBERS
348 | def add_enum(self, enum):
349 | """
350 | @param: enum CppEnum instance
351 | """
352 | enum.ref_to_parent = self
353 | self.internal_enum_elements.append(enum)
354 |
355 | def add_variable(self, cpp_variable):
356 | """
357 | @param: cpp_variable CppVariable instance
358 | """
359 | cpp_variable.ref_to_parent = self
360 | cpp_variable.is_class_member = True
361 | self.internal_variable_elements.append(cpp_variable)
362 |
363 | def add_array(self, cpp_variable):
364 | """
365 | @param: cpp_variable CppVariable instance
366 | """
367 | cpp_variable.ref_to_parent = self
368 | cpp_variable.is_class_member = True
369 | self.internal_array_elements.append(cpp_variable)
370 |
371 | def add_internal_class(self, cpp_class):
372 | """
373 | Add nested class
374 | @param: cpp_class CppClass instance
375 | """
376 | cpp_class.ref_to_parent = self
377 | self.internal_class_elements.append(cpp_class)
378 |
379 | def add_method(self, method):
380 | """
381 | @param: method CppFunction instance
382 | """
383 | method.ref_to_parent = self
384 | method.is_method = True
385 | self.internal_method_elements.append(method)
386 |
387 | ########################################
388 | # RENDER CLASS MEMBERS
389 | def _render_internal_classes_declaration(self, cpp):
390 | """
391 | Generates section of nested classes
392 | Could be placed both in 'private:' or 'public:' sections
393 | Method is protected as it is used by CppClass only
394 | """
395 | for classItem in self.internal_class_elements:
396 | classItem.declaration().render_to_string(cpp)
397 | cpp.newline()
398 |
399 | def _render_enum_section(self, cpp):
400 | """
401 | Render to string all contained enums
402 | Method is protected as it is used by CppClass only
403 | """
404 | for enumItem in self.internal_enum_elements:
405 | enumItem.render_to_string(cpp)
406 | cpp.newline()
407 |
408 | def _render_variables_declaration(self, cpp):
409 | """
410 | Render to string all contained variable class members
411 | Method is protected as it is used by CppClass only
412 | """
413 | for varItem in self.internal_variable_elements:
414 | varItem.declaration().render_to_string(cpp)
415 | cpp.newline()
416 |
417 | def _render_array_declaration(self, cpp):
418 | """
419 | Render to string all contained array class members
420 | Method is protected as it is used by CppClass only
421 | """
422 | for arrItem in self.internal_array_elements:
423 | arrItem.declaration().render_to_string(cpp)
424 | cpp.newline()
425 |
426 | def _render_methods_declaration(self, cpp):
427 | """
428 | Generates all class methods declaration
429 | Should be placed in 'public:' section
430 | Method is protected as it is used by CppClass only
431 | """
432 | for funcItem in self.internal_method_elements:
433 | funcItem.render_to_string_declaration(cpp)
434 | cpp.newline()
435 |
436 | def render_static_members_implementation(self, cpp):
437 | """
438 | Generates definition for all static class variables
439 | Method is public, as it could be used for nested classes
440 | int MyClass::my_static_array[] = {}
441 | """
442 | # generate definition for static variables
443 | static_vars = [variable for variable in self.internal_variable_elements if variable.is_static]
444 | for varItem in static_vars:
445 | varItem.definition().render_to_string(cpp)
446 | cpp.newline()
447 | for arrItem in self.internal_array_elements:
448 | arrItem.definition().render_to_string(cpp)
449 | cpp.newline()
450 |
451 | # do the same for nested classes
452 | for classItem in self.internal_class_elements:
453 | classItem.render_static_members_implementation(cpp)
454 | cpp.newline()
455 |
456 | def render_methods_implementation(self, cpp):
457 | """
458 | Generates all class methods declaration
459 | Should be placed in 'public:' section
460 | Method is public, as it could be used for nested classes
461 | """
462 | # generate methods implementation section
463 | for funcItem in self.internal_method_elements:
464 | if not funcItem.is_pure_virtual:
465 | funcItem.render_to_string_implementation(cpp)
466 | cpp.newline()
467 | # do the same for nested classes
468 | for classItem in self.internal_class_elements:
469 | classItem.render_static_members_implementation(cpp)
470 | cpp.newline()
471 |
472 | ########################################
473 | # GROUP GENERATED SECTIONS
474 | def class_interface(self, cpp):
475 | """
476 | Generates section that generally used as an 'open interface'
477 | Generates string representation for enums, internal classes and methods
478 | Should be placed in 'public:' section
479 | """
480 | self._render_enum_section(cpp)
481 | self._render_internal_classes_declaration(cpp)
482 | self._render_methods_declaration(cpp)
483 |
484 | def private_class_members(self, cpp):
485 | """
486 | Generates section of class member variables.
487 | Should be placed in 'private:' section
488 | """
489 | self._render_variables_declaration(cpp)
490 | self._render_array_declaration(cpp)
491 |
492 | def render_to_string(self, cpp):
493 | """
494 | Render to string both declaration and definition.
495 | A rare case enough, because the only code generator handle is used.
496 | Typically class declaration is rendered to *.h file, and definition to *.cpp
497 | """
498 | self.render_to_string_declaration(cpp)
499 | self.render_to_string_implementation(cpp)
500 |
501 | def render_to_string_declaration(self, cpp):
502 | """
503 | Render to string class declaration.
504 | Typically handle to header should be passed as 'cpp' param
505 | """
506 | if self.documentation:
507 | cpp(dedent(self.documentation))
508 |
509 | with cpp.block(f'{self._render_class_type()} {self.name} {self.inherits()}', postfix=';'):
510 |
511 | # in case of struct all members meant to be public
512 | if not self.is_struct:
513 | cpp.label('public')
514 | self.class_interface(cpp)
515 | cpp.newline()
516 |
517 | # in case of struct all members meant to be public
518 | if not self.is_struct:
519 | cpp.label('private')
520 | self.private_class_members(cpp)
521 |
522 | def _render_class_type(self):
523 | """
524 | @return: 'class' or 'struct' keyword
525 | """
526 | return 'struct' if self.is_struct else 'class'
527 |
528 | def render_to_string_implementation(self, cpp):
529 | """
530 | Render to string class definition.
531 | Typically handle to *.cpp file should be passed as 'cpp' param
532 | """
533 | cpp.newline(2)
534 | self.render_static_members_implementation(cpp)
535 | self.render_methods_implementation(cpp)
536 |
537 | def declaration(self):
538 | """
539 | @return: CppDeclaration wrapper, that could be used
540 | for declaration rendering using render_to_string(cpp) interface
541 | """
542 | return CppDeclaration(self)
543 |
544 | def definition(self):
545 | """
546 | @return: CppImplementation wrapper, that could be used
547 | for definition rendering using render_to_string(cpp) interface
548 | """
549 | return CppImplementation(self)
550 |
--------------------------------------------------------------------------------