|
| 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