Pyolian template-based generator.

This is a really powerfull tool that can be used to generate anything eolian
releted  just providing a template file. You can then render the template
with the wanted scope (class, namespace, enum, etc)

For example give a try at this (from the src/srcipts/pyolian folder):
./generator.py test_gen_class.template --cls Efl.Loop.Timer

or ./generator.py -h for the full help

Next step: maybe generate the new efl API doc using this tool?
@andy I think this will make your life much easier :)
This commit is contained in:
Davide Andreoli 2017-12-28 16:10:04 +01:00
parent 455f241463
commit 15c7bbf5fa
5 changed files with 1556 additions and 2 deletions

View File

@ -512,6 +512,7 @@ class Class(EolianBaseObject):
def functions_get(self, ftype):
return Iterator(Function, lib.eolian_class_functions_get(self._obj, ftype))
@property
def methods(self):
return self.functions_get(Eolian_Function_Type.METHOD)
@ -886,9 +887,10 @@ class Type(EolianBaseObject): # OK (4 eolian issue)
return Eolian_Type_Builtin_Type(lib.eolian_type_builtin_type_get(self._obj))
# TODO FIXME STRANGE API (need Eolian_Unit*)
# @property
# def c_type(self):
@property
def c_type(self):
# return _str_to_py(lib.eolian_type_c_type_get(self._obj))
return 'FIXME'
# TODO FIXME STRANGE API (need Eolian_Unit*)
# @property

206
src/scripts/pyolian/generator.py Executable file
View File

@ -0,0 +1,206 @@
#!/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.
You can also import this module and use the provided Template class if you
are more confortable from within python.
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 = "!#"
"""
import os
import datetime
import eolian
import pyratemp
# logging utils
be_verbose = True
def ERR(*args): print(*(('PYOLIANGEN ERROR:', ) + args))
def WRN(*args): print(*(('PYOLIANGEN WARNING:', ) + args))
def INF(*args): print(*(('PYOLIANGEN ', ) + args))
# 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()
if not isinstance(eolian_db, eolian.Eolian):
raise(RuntimeError('Eolian, failed to create Eolian state'))
if not eolian_db.directory_scan(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
import atexit
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)
data: User provided context for the template.
"""
def __init__(self, filename, encoding='utf-8', data=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 data:
global_ctx.update(data)
# 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 Enums
'Eolian_Function_Type': eolian.Eolian_Function_Type,
'Eolian_Parameter_Dir': eolian.Eolian_Parameter_Dir,
'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_Variable_Type': eolian.Eolian_Variable_Type,
'Eolian_Binary_Operator': eolian.Eolian_Binary_Operator,
'Eolian_Unary_Operator': eolian.Eolian_Unary_Operator,
'Eolian_Declaration_Type': eolian.Eolian_Declaration_Type,
'Eolian_Doc_Token_Type': eolian.Eolian_Doc_Token_Type,
'Eolian_Doc_Ref_Type': eolian.Eolian_Doc_Ref_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, 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_get_by_name(cls)
if struct:
ctx['struct'] = eolian_db.typedecl_struct_get_by_name(struct)
if enum:
ctx['enum'] = eolian_db.typedecl_enum_get_by_name(enum)
if alias:
ctx['alias'] = eolian_db.typedecl_alias_get_by_name(alias)
if ns:
ctx['namespace'] = ns
ctx['namespaces'] = ns.split('.')
ctx['classes'] = [ c for c in eolian_db.all_classes
if c.full_name.startswith(ns + '.') ]
ctx['aliases'] = [ a for a in eolian_db.typedecl_all_aliases
if a.full_name.startswith(ns + '.') ]
ctx['structs'] = [ s for s in eolian_db.typedecl_all_structs
if s.full_name.startswith(ns + '.') ]
ctx['enums'] = [ e for e in eolian_db.typedecl_all_enums
if e.full_name.startswith(ns + '.') ]
if filename is not None:
INF('generating "%s" from template "%s"' % (
filename, self.template_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 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)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
================================================================================
Class: ${cls.full_name}$
================================================================================
Class type: ${cls.type}$
Base Class: ${cls.base_class.full_name if cls.base_class else None}$
Inherits: ${list(cls.inherits)}$
InheritsFull: ${cls.inherits_full}$
Namespaces: ${list(cls.namespaces)}$
File: ${cls.file}$
Ctor enable: ${cls.ctor_enable}$
Dtor enable: ${cls.dtor_enable}$
Constructors:
=============
<!--(for ctor in cls.constructors)-->
* ${ctor}$
<!--(else)-->
no constructors available
<!--(end)-->
Methods:
========
<!--(for func in cls.methods)-->
* ${func.name}$(...) ${func.method_scope}$
<!--(else)-->
no methods available
<!--(end)-->
Properties:
===========
<!--(for func in cls.properties)-->
* ${func.name}$ (<!--(for v in func.getter_values)-->${v.type.c_type}$ ${v.name}$, <!--(end)-->)
<!--(else)-->
no properties available
<!--(end)-->
Events:
=======
<!--(for event in cls.events)-->
* ${event.name}$ -> ${event.c_name}$
<!--(else)-->
no events available
<!--(end)-->

View File

@ -0,0 +1,43 @@
================================================================================
Namespace: ${namespace}$ ${namespaces}$
================================================================================
Classes:
========
<!--(for cls in classes)-->
* ${cls.full_name}$ (${cls.type}$)
<!--(else)-->
no classes available
<!--(end)-->
Aliases:
========
<!--(for typedecl in aliases)-->
* ${typedecl.full_name}$
<!--(else)-->
no alias available
<!--(end)-->
Structs:
========
<!--(for typedecl in structs)-->
* ${typedecl.full_name}$
<!--(for field in typedecl.struct_fields)-->
${field}$
<!--(end)-->
<!--(else)-->
no structs available
<!--(end)-->
Enums:
======
<!--(for typedecl in enums)-->
* ${typedecl.full_name}$
<!--(for field in typedecl.enum_fields)-->
${field}$
<!--(end)-->
<!--(else)-->
no enums available
<!--(end)-->