Skip to content

Commit 8ba8b25

Browse files
committed
documentation
1 parent e06f0f7 commit 8ba8b25

File tree

3 files changed

+139
-6
lines changed

3 files changed

+139
-6
lines changed

generator.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ def generate(self, model, outfolder):
4040

4141

4242
class Task:
43-
"""File generation task applied to a set of model elements."""
43+
"""
44+
File generation task applied to a set of model elements.
45+
46+
Attributes:
47+
formatter: Callable converting this generator tasks raw output into a nicely formatted
48+
string.
49+
"""
4450

4551
def __init__(self, formatter=None, **kwargs):
4652
if kwargs:

jinja.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ def create_environment(self, **kwargs):
3030

3131

3232
class JinjaTask(TemplateFileTask):
33-
"""Base class for Jinja2 based code generator tasks."""
34-
35-
def __init__(self, **kwargs):
36-
super().__init__(**kwargs)
37-
self.environment = None
33+
"""
34+
Base class for Jinja2 based code generator tasks.
35+
36+
Attributes:
37+
environment: Jinja2 environment, to be set by generator.
38+
"""
39+
40+
environment = None
3841

3942
def generate_file(self, element, filepath):
4043
template = self.environment.get_template(self.template_name)

pygen.rst

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
pygen - Generate code from Ecore models
2+
=======================================
3+
4+
If you have a meta-model represented as an instance of Pyecore, you can use this generator to
5+
produce static code from it. The package currently consists of these parts:
6+
7+
* ``generator.py`` and ``formatter.py`` form a basic framework that can be put on top of any
8+
template-based code-generator that produce single-file outputs (like Jinja or Cheetah) in order to
9+
manage outputting multiple files in a certain folder structure. These modules are independent of
10+
Jinja and Pyecore.
11+
* ``jinja.py`` refines the generic classes to be used with Jinja2 as file generator.
12+
* ``ecore.py`` applies the Jinja2-based generator to create Pyecore Python classes from an Ecore
13+
meta model instantiated with Pyecore.
14+
15+
All modules except ``ecore.py`` should be moved into a separate distribution on PyPI to make it
16+
available also outside of Pyecore.
17+
18+
19+
Using the Ecore generator
20+
-------------------------
21+
22+
The generator assumes reasonable defaults, so using it is straightforward. Assuming you load an
23+
Ecore model with Pyecore like this:
24+
25+
.. code-block:: python
26+
27+
rset = ResourceSet()
28+
resource = rset.get_resource(URI('library.ecore'))
29+
library_model = resource.contents[0]
30+
rset.metamodel_registry[library_model.nsURI] = library_model
31+
32+
Now ``library_model`` holds the root package of the loaded meta-model. From this instance the
33+
corresponding Python classes are generated like this:
34+
35+
.. code-block:: python
36+
37+
generator = EcoreGenerator()
38+
generator.generate(library_model, 'output-folder')
39+
40+
After this the ``output-folder`` will contain the generated package files, in this case a sub-folder
41+
``library`` that can be directly imported into using Python code. Note that the generated classes
42+
obviously depend on the Pyecore infrastructure to work. Follow the general documentation on how to
43+
use static model classes in your application.
44+
45+
46+
The pygen framework
47+
-------------------
48+
49+
The modules ``generator``, ``formatter`` and ``jinja`` are generic and not limited to be used with
50+
Pyecore. The overall design follows a very simple workflow pattern.
51+
52+
The ``Generator`` base class controls sets up and runs the worklow. The different steps of a
53+
generation workflow are modeled as instances of base class ``Task``. A concrete generator will
54+
override the empty ``tasks`` collection of the base class to set up the tasks to be executed
55+
sequentially. The following is how the Pyecore generator does this:
56+
57+
.. code-block:: python
58+
59+
class EcoreGenerator(JinjaGenerator):
60+
"""Generation of static ecore model classes."""
61+
62+
tasks = [
63+
EcorePackageInitTask(formatter=format_autopep8),
64+
EcorePackageModuleTask(formatter=format_autopep8),
65+
]
66+
67+
...
68+
69+
In this example ``EcoreGenerator`` defines a generator workflow consisting of two tasks:
70+
71+
* ``EcorePackageInitTask`` generates the ``__init__.py`` file for a model package.
72+
* ``EcorePackageModuleTask`` generates the corresponding module Python file.
73+
74+
The code also shows specific generator and task classes being instantiated here. The inheritance
75+
"trees" are simple linear lines. The generator inheritance chain looks like this (from general to
76+
specific):
77+
78+
1. ``generator.Generator``: base class offering a method to generate files into a target folder from
79+
any kind of model. The model is not limited to Pyecore, but could be anything, like dictionaries
80+
or lists as well. The generate method implementation realizes the core workflow by calling all
81+
tasks of contained in its ``tasks`` attribute.
82+
2. ``generator.TemplateGenerator``: adds a static attribute to specify a relative path to the
83+
directory where input templates can be found.
84+
3. ``jinja.JinjaGenerator``: configures the Jinja2 environment, which specifies all kinds of options
85+
for the code generation with Jinja.
86+
4. ``ecore.EcoreGenerator``: the concrete generator translating Pyecore models into Python code with
87+
Jinja2 as code generator.
88+
89+
This hierarchy allows for customization at al levels: If you want to generate text from models, but
90+
without templates, because it can be somehow hardcoded, derive directly from ``Generator``. If you
91+
want to use a templating engine, but not Jinja2, derive from ``TemplateGenerator``. If you indeed
92+
want to use Jinja2, but not generate files from Pyecore models, or use completely different
93+
templates, derive from ``JinjaGenerator``. Deriving from ``EcoreGenerator`` may be useful in some
94+
cases, but this class does provide Jinja2 filters and tests that are required by the used template
95+
files. So the class is rather specific but of course can be used as a template when writing your
96+
own concrete generator, e.g. to generate SQL from model.
97+
98+
Tasks also add functionality incrementally to support reuse and customization:
99+
100+
1. ``generator.Task``: base class for all generator tasks. It supports the generator workflow by
101+
exposing a ``run`` method that in turn calls various abstract methods to be implemented in
102+
derived classes. The class defines an API required to generate output files in a certain
103+
target directory. It exposes a model element filter to select the elements to execute this
104+
task for. Optionally, the constructor accepts a ``formatter`` argument, which has to be a
105+
callable, converting raw text generator output into whatever nicely formatted form you choose.
106+
The Pyecore generation tasks for instance are being passed ``formatter.format_autopep8``.
107+
2. ``generator.TemplateFileTask``: adds a (relative) path to the template to be used and API to
108+
pass context data to the template.
109+
3. ``jinja.JinjaTask``: holds the actual calls to Jinja2 to generate textual output and optionally
110+
applies the configured formatter. It uses the context passed in from the calling generator to
111+
pass data to the templates.
112+
4. ``ecore.EcoreTask``: implements the model element filter by finding all elements of a certain
113+
Ecore type. It also determines file and folder names from those.
114+
115+
The two derived Ecore task classes mentioned in the above example are the leafs of the inheritance
116+
line, implementing the remaining abstract methods depending on the concrete template being used.
117+
They also provide additional context information to the template in use.
118+
119+
Both, generators and tasks pass configuration data in different stages:
120+
121+
* Static (class) attributes and instance attributes are used for configuration parameters that are
122+
specific for a certain *type* of generator.
123+
* Parameters that affect how a specific *run* of the generator translates a particular model are
124+
passed as function arguments to the various workflow methods (like ``generate`` or ``run``).

0 commit comments

Comments
 (0)