summaryrefslogtreecommitdiff
path: root/src/scripts/pyolian/generator.py
blob: 67c636fc9c932c4f19f77736f1dc342653b78794 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env python3
# encoding: utf-8
"""

Pyolian template based generator.

This is a really powerfull template-based, output-agnostic eolian generator.
You just need a template file and then you can render it with the
wanted eolian scope (class, namespace, enum, struct, etc...).

For example (from this source folder):

./generator.py test_gen_class.template --cls Efl.Loop.Timer
./generator.py test_gen_namespace.template --ns Efl.Ui

...of course you can pass any other class or namespace to the example above.

The generator is based on the great pyratemp engine (THANKS!), you can find
the full template syntax at: www.simple-is-better.org/template/pyratemp.html

Just keep in mind the syntax is a bit different in this implementation:

    sub_start     = "${"      Was "$!" in original pyratemp (and in docs)
    sub_end       = "}$"      Was "!$" in original pyratemp (and in docs)
    _block_start  = "<!--("
    _block_end    = ")-->"
    comment_start = "#!"
    comment_end   = "!#"


You can also import this module and use the provided Template class if you
are more confortable from within python. To import from outside this directory
you need to hack sys.path in a way that this folder will be available on
PYTHON_PATH, fe:

  pyolian_path = os.path.join(EFL_ROOT_PATH, 'src', 'scripts')
  sys.path.insert(0, pyolian_path)
  from pyolian.generator import Template


"""
import os
import datetime
import atexit

try:
    from . import eolian
    from . import pyratemp
except ImportError:
    import eolian
    import pyratemp


# Use .eo files from the source tree (not the installed ones)
script_path = os.path.dirname(os.path.realpath(__file__))
root_path = os.path.abspath(os.path.join(script_path, '..', '..', '..'))
SCAN_FOLDER = os.path.join(root_path, 'src', 'lib')


# load the whole eolian db
eolian_db = eolian.Eolian_State()
if not isinstance(eolian_db, eolian.Eolian_State):
    raise(RuntimeError('Eolian, failed to create Eolian state'))

if not eolian_db.directory_add(SCAN_FOLDER):
    raise(RuntimeError('Eolian, failed to scan source directory'))

if not eolian_db.all_eot_files_parse():
    raise(RuntimeError('Eolian, failed to parse all EOT files'))
    
if not eolian_db.all_eo_files_parse():
    raise(RuntimeError('Eolian, failed to parse all EO files'))


# cleanup the database on exit
def cleanup_db():
    global eolian_db
    del eolian_db


atexit.register(cleanup_db)


class Template(pyratemp.Template):
    """ Pyolian template based generator.

    You can directly use this class to generate custom outputs based
    on the eolian database and your provided templates.

    Usage is as simple as:
        t = Template(<template_file>)
        t.render(<output_file>, cls=..., ns=..., ...)

    Args:
        filename: Template file to load. (REQUIRED)
        context: User provided context for the template (dict).
    """

    def __init__(self, filename, encoding='utf-8', context=None, escape=None,
                 loader_class=pyratemp.LoaderFile,
                 parser_class=pyratemp.Parser,
                 renderer_class=pyratemp.Renderer,
                 eval_class=pyratemp.EvalPseudoSandbox):

        # Build the global context for the template
        global_ctx = {}
        # user provided context (low pri)
        if context:
            global_ctx.update(context)
        # standard names (not overwritables)
        global_ctx.update({
            # Template info
            'date': datetime.datetime.now(),
            'template_file': os.path.basename(filename),
            # Eolian info
            #  'eolian_version': eolian.__version__,
            #  'eolian_version_info': eolian.__version_info__,
            # Eolian Classes
            'Object': eolian.Object,
            'Class': eolian.Class,
            'Part': eolian.Part,
            'Constructor': eolian.Constructor,
            'Event': eolian.Event,
            'Function': eolian.Function,
            'Function_Parameter': eolian.Function_Parameter,
            'Implement': eolian.Implement,
            'Type': eolian.Type,
            'Typedecl': eolian.Typedecl,
            'Enum_Type_Field': eolian.Enum_Type_Field,
            'Struct_Type_Field': eolian.Struct_Type_Field,
            'Expression': eolian.Expression,
            'Constant': eolian.Constant,
            'Documentation': eolian.Documentation,
            'Documentation_Token': eolian.Documentation_Token,
            'Error': eolian.Error,
            # Eolian Enums
            'Eolian_Object_Type': eolian.Eolian_Object_Type,
            'Eolian_Function_Type': eolian.Eolian_Function_Type,
            'Eolian_Parameter_Direction': eolian.Eolian_Parameter_Direction,
            'Eolian_Class_Type': eolian.Eolian_Class_Type,
            'Eolian_Object_Scope': eolian.Eolian_Object_Scope,
            'Eolian_Typedecl_Type': eolian.Eolian_Typedecl_Type,
            'Eolian_Type_Type': eolian.Eolian_Type_Type,
            'Eolian_Type_Builtin_Type': eolian.Eolian_Type_Builtin_Type,
            # 'Eolian_C_Type_Type': eolian.Eolian_C_Type_Type,
            'Eolian_Expression_Type': eolian.Eolian_Expression_Type,
            'Eolian_Expression_Mask': eolian.Eolian_Expression_Mask,
            'Eolian_Binary_Operator': eolian.Eolian_Binary_Operator,
            'Eolian_Unary_Operator': eolian.Eolian_Unary_Operator,
            'Eolian_Doc_Token_Type': eolian.Eolian_Doc_Token_Type,
        })

        # Call the parent __init__ func
        self.template_filename = filename
        pyratemp.Template.__init__(self, filename=filename, encoding=encoding,
                                   data=global_ctx, escape=escape,
                                   loader_class=loader_class,
                                   parser_class=parser_class,
                                   renderer_class=renderer_class,
                                   eval_class=eval_class)

    def render(self, filename=None, verbose=True, cls=None, ns=None,
               struct=None, enum=None, alias=None, **kargs):
        # Build the context for the template
        ctx = {}
        if kargs:
            ctx.update(kargs)
        if cls:
            ctx['cls'] = eolian_db.class_by_name_get(cls)
        if ns:
            ctx['namespace'] = eolian_db.namespace_get_by_name(ns)
        if struct:
            ctx['struct'] = eolian_db.struct_by_name_get(struct)
        if enum:
            ctx['enum'] = eolian_db.enum_by_name_get(enum)
        if alias:
            ctx['alias'] = eolian_db.alias_by_name_get(alias)

        if verbose and filename:
            print("rendering: {} => {}".format(
                  self.template_filename, filename))

        # render with the augmented context
        output = self(**ctx)

        if filename is not None:
            # create directory tree if needed
            folder = os.path.dirname(filename)
            if folder and not os.path.isdir(folder):
                os.makedirs(folder)
            # write to file
            with open(filename, "w") as f:
                f.write(output)
        else:
            # or print to stdout
            print(output)


if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser(description='Pyolian template based generator.')
    parser.add_argument('template',
                        help='The template file to use. (REQUIRED)')
    parser.add_argument('--output', '-o', metavar='FILE', default=None,
                        help='Where to write the rendered output. '
                             'If not given will print to stdout.')
    parser.add_argument('--cls', metavar='CLASS_NAME', default=None,
                        help='The full name of the class to render, ex: Efl.Loop.Timer')
    parser.add_argument('--ns', metavar='NAMESPACE', default=None,
                        help='The namespace to render, ex: Efl.Loop')
    parser.add_argument('--struct', metavar='STRUCT_NAME', default=None,
                        help='The name of the struct to render, ex: Efl.Loop.Arguments')
    parser.add_argument('--enum', metavar='ENUM_NAME', default=None,
                        help='The name of the enum to render, ex: Efl.Loop.Handler.Flags')
    parser.add_argument('--alias', metavar='ALIAS_NAME', default=None,
                        help='The name of the alias to render, ex: Efl.Font.Size')
    args = parser.parse_args()

    t = Template(args.template)
    t.render(args.output, cls=args.cls, ns=args.ns,
             struct=args.struct, enum=args.enum, alias=args.alias)