diff --git a/src/scripts/pyolian/README.md b/src/scripts/pyolian/README.md new file mode 100644 index 0000000000..61bf0dc52d --- /dev/null +++ b/src/scripts/pyolian/README.md @@ -0,0 +1,386 @@ + +Pyolian: Eolian python bindings and a template-based Eolian generator +===================================================================== + +If you need to generate something based on the Eolian database you are in the +right place! Pyolian is the python bindings for Eolian and provide a clean and +pythonic API to Eolian; as a bonus it also provide a template-based +generator to render custom template files, filling them with a full eolian +context available. + +The template engine is really powerfull and provide all the functionalities you +would expect from a full blow template engine. It is comparable in syntax and +features to [Jinja2](http://jinja.pocoo.org/) or the Django template system. If +you are interested on the implementation detail this generator is bases on the +great [Pyratemp](https://www.simple-is-better.org/template/pyratemp.html). + + +Installation +============ + +There is nothing to install to use the bindings or the generator, everything is +included in the efl source tree and it is intended to work directly inside the +tree, usually at efl compilation time (before the install step). + +The only requirement is that **the source tree must be already built** (not +installed) because pyolian search the eolian .so/.dll inside the source tree. + + +Command line usage +================== +The simplest way to use the generator is from the command line, using the +`src/scripts/pyolian/generator.py` command, the `--help` option state: + +``` +usage: generator.py [-h] [--output FILE] [--cls CLASS_NAME] [--ns NAMESPACE] + [--struct STRUCT_NAME] [--enum ENUM_NAME] + [--alias ALIAS_NAME] + template + +Pyolian template based generator. + +positional arguments: + template The template file to use. (REQUIRED) + +optional arguments: + -h, --help show this help message and exit + --output FILE, -o FILE + Where to write the rendered output. If not given will + print to stdout. + --cls CLASS_NAME The full name of the class to render, ex: + Efl.Loop.Timer + --ns NAMESPACE The namespace to render, ex: Efl.Loop + --struct STRUCT_NAME The name of the struct to render, ex: + Efl.Loop.Arguments + --enum ENUM_NAME The name of the enum to render, ex: + Efl.Loop.Handler.Flags + --alias ALIAS_NAME The name of the alias to render, ex: Efl.Font.Size +``` + +Basically you just need a template file, then call the generator with at least +the template file and one of the context options (cls, ns, struct, enum or alias) + +Two example templates can be found in the `efl/src/scripts/pyolian` folder, +from that directory you can run: + +``` +./generator.py test_gen_class.template --cls Efl.Loop.Timer +``` + +This will rendere the template test_gen_class.template with Efl.Loop.Timer as +**cls** available in the template, you can of course pass any other class you +want. + +As we did not pass the --output-file parameter the rendered text is printed +on standard out. + + +Python usage +============ + +To use pyolian from python code you need to import **eolian** and/or the +**Template** class from the pyolian directory. As this python package is not +installed you need a little hack to import the package, something like this +should work: + +``` +# Use pyolian from source (not installed) +pyolian_path = os.path.join(, 'src', 'scripts') +sys.path.insert(0, pyolian_path) +from pyolian import eolian +from pyolian.generator import Template +``` + +Now you can use the full eolian library defined in eolian.py (read the file +to see all available classes and their properties) or just the Template class +like this: + +``` +t = Template('test_gen_class.template') +t.render('output.txt', cls='Efl.Loop.Timer', verbose=True) +``` + + +Template syntax +=============== + +A template is a normal text file, containing some special placeholders and +control-structures. there are 3 syntax blocks available: + + +Expressions +----------- + +A template needs some kind of "programming-language" to access variables, +calculate things, format data, check conditions etc. inside the template. So, +you could invent and implement an own language therefore -- or you could use an +already existing and well designed language: Python. + +The template engine uses embedded Python-expressions. An expression is +everything which evaluates to a value, e.g. variables, arithmetics, +comparisons, boolean expressions, function/method calls, list comprehensions +etc. And such Python expressions can be directly used in the template. + +``` +${expression}$ +``` + +**expression** can be any expression (e.g. a variable-name, an arithmetic +calculation, a function-/macro-call etc.), and is evaluated when the template +is rendered. Whitespace after `${` and before `}$` is ignored. + +Generic examples: + + * numbers, strings, lists, tuples, dictionaries, ...: 12.34, "hello", [1,2,3], ... + * variable-access: var, mylist[i], mydict["key"], myclass.attr + * function/method call: myfunc(...), "foobar".upper() + * comparison: (i < 0 or j > 1024) + * arithmetics: 1+2 + +For details, please read the Python-documentation about Python expressions. + +Examples (for a template rendered with cls='Efl.Loop.Timer'): + + * `${cls.full_name}$` => 'Efl.Loop.Timer' + * `${cls.full_name.replace('.', '_').lower()}$` => 'efl_loop_timer' + * `${cls.base_class.full_name if cls.base_class else None}$` => 'Efl.Loop.Consumer' or None + * `${', '.join([i.full_name for i in cls.hierarchy])}$` => 'Efl.Loop.Consumer, Efl.Object' + +note the incredible flexibility of **expression**, they are basically small +pieces of python code, so you can use quite all the python syntax in them. + +Also note that the context of the template (**cls** in this example) is always +an instance of a class defined in eolian.py (eolian.Class in this example), you +must read this file to understand all the available classes with their +properties and methods. + + +Comments +-------- + +Comments can be used in a template, and they do not appear in the result. They +are especially useful for (a) documenting the template, (b) temporarily +disabling parts of the template or (c) suppressing whitespace. + + * `#!...!#` - single-line comment with start and end tag + * `#!...` - single-line-comment until end-of-line, incl. newline! + +The second version also comments out the newline, and so can be used at the end +of a line to remove that newline in the result. + +Comments can contain anything, but comments may not appear inside substitutions +or block-tags. + + +Blocks +------ + +The control structures, macros etc. have a special syntax, which consists of a +start tag (which is named according to the block), optional additional tags, +and an end-tag: + +``` + #! start tag + .. + .. + #! optional additional tags (e.g. for elif) + .. + .. + #! end tag +``` + +All tags must stand on their own line, and no code (except a `#!...` comment) is +allowed after the tag. + +All tags which belong to the same block **must have the same indent!** The +contents of the block does not need to be indented, although it might improve +the readability (e.g. in HTML) to indent the contents as well. + +Nesting blocks is possible, but the tags of nested blocks must have a different +indent than the tags of the enclosing blocks. Note that you should either use +spaces or tabs for indentation. Since the template distinguishes between spaces +and tabs, if you mix spaces and tabs, two indentations might look the same +(e.g. 8 spaces or 1 tab) but still be different, which might lead to unexpected +errors. + +There's also a single-line version of a block, which does not need to stand on +its own line, but can be inserted anywhere in the template. But note that this +version does not support nesting : + +``` +............ +``` + +if/elif/else +------------ + +``` + +... + +... + +... + +``` + +The `elif` and `else` branches are optional, and there can be any number of +`elif` branches. + + +for/else +-------- + +``` + +... + +... + +``` + +VARS can be a single variable name (e.g. myvar) or a comma-separated list of +variable-names (e.g. i,val). + +The `else` branch is optional, and is executed only if the for loop doesn't +iterate at all. + + +macro +----- + +Macros are user-defined "sub-templates", and so can contain anything a template +itself can contain. They can have parameters and are normally used to +encapsulate parts of a template and to create user-defined "functions". Macros +can be used in expressions, just like a normal variable or function. + +``` + +... + +``` + +Note that the last newline (before ``) is removed from the macro, so +that defining and using a macro does not add additional empty lines. + +Usage in expressions: + + * `MACRONAME` + * `MACRONAME(KEYWORD_ARGS)` + +KEYWORD_ARGS can be any number of comma-separated name-value-pairs (name=value, +...), and these names then will be locally defined inside the macro, in +addition to those already defined for the whole template. + + +raw +--- + +``` + +... + +``` + +Everything inside a `raw` block is passed verbatim to the result. + + +include +------- + +``` +FILENAME +``` + +Include another template-file. Only a single filename (+whitespace) is allowed +inside of the block; if you want to include several files, use several +include-blocks. + +Note that inclusion of other templates is only supported when loading the +template from a file. For simplicity and security, FILENAME may not contain a +path, and only files which are in the same directory as the template itself can +be included. + + + +Template context +================ + +The following Python-built-in values/functions are available by default in the +template: + + * `True` + * `False` + * `None` + * `abs()` + * `chr()` + * `divmod()` + * `hash()` + * `hex()` + * `isinstance()` + * `len()` + * `max()` + * `min()` + * `oct()` + * `ord()` + * `pow()` + * `range()` + * `round()` + * `sorted()` + * `sum()` + * `unichr()` + * `zip()` + * `bool()` + * `bytes()` + * `complex()` + * `dict()` + * `enumerate()` + * `float()` + * `int()` + * `list()` + * `long()` + * `reversed()` + * `set()` + * `str()` + * `tuple()` + * `unicode()` + * `dir()` + +Additionally, the functions exists(), default(), setvar() and escape() are +defined as follows: + + * `exists("varname")` Test if a variable (or any other object) with the given name exists + * `default("expr", default=None)` Tries to evaluate the expression expr. + If the evaluation succeeds and the result is not None, its value is returned; + otherwise, if the expression contains undefined variables/attributes, the + default-value is returned instead. Note that expr has to be quoted. + * `setvar("name", "expr")` Although there is often a more elegant way, + sometimes it is useful or necessary to set variables in the template. + Can also be used to capture the output of e.g. an evaluated macro. + +Moreover all the Eolian classes and enums (as defined in eolian.py) is available +in the template, fe: + + * `Function` eolian.Function (class) + * `Eolian_Class_Type` eolian.Eolian_Class_Type (enum) + +And some other general utilities: + + * `template_file` name of the template used to generate the output + * `date` python datetime object (generation time) + + +Where to find more info +======================= + + * read the eolian.py file (it declare the full eolian API) + * read the generator.py file (it's super simple) + * read the original [pyratemp docs](https://www.simple-is-better.org/template/pyratemp.html) + + +Note +==== + +This markdown file is mirrored in efl src tree (src/scripts/pyolian) and in +phab wiki (phab.enlightenment.org/w/pyolian). Don't forget to update the other +if you change one!