CodeGen
Manual
For the impatient
local CodeGen = require 'CodeGen'
tmpl = CodeGen { -- instanciation
tarball = "${name}-${version}.tar.gz",
name = 'lua',
}
tmpl.version = 5.1
output = tmpl 'tarball' -- interpolation
print(output) --> lua-5.1.tar.gz
The instanciation
The instanciation of a template is done by the call of CodeGen
with optional parameters. This first parameter is a table.
This table uses only string as key and could contains 3 kinds of value :
- chunk of template which is a string
and could contains primitives i.e.
${...}
- data which gives access to the data model
- formatter which is a function which accepts a string as parameter and returns it after a transformation. The typical usage is for escape sequence.
The other parameters allow inheritance (ie. access to field) from other templates or simple tables.
A common pattern is to put this step in an external file.
-- file: tarball.tmpl
return CodeGen {
tarball = "${name}-${version}.tar.gz",
}
tmpl = dofile 'tarball.tmpl'
Setting data and other alteration
After the instanciation and before the interpolation, all member of the template are accessible and modifiable like in a table.
Typically, data from the model are added after the instanciation.
The interpolation
The interpolation is done by calling the template with one string parameter which is the keyname of the entry point template.
The interpolation returns a string as result and an optional string which contains some error messages.
The 4 primitives in template
The data
could be in the form of foo.bar.baz
.
1. Attribute reference
The syntax is ${data[; separator='sep'][; format=name]}
.
An undefined data
produces an empty string.
The option format
allows to specify a formatter function,
which is a value of the template referred by the key name
.
The default behavior is given by the standard Lua function tostring
.
When data
is a table, the option separator
is used
as parameter of table.concat(data, sep)
.
This parameter could be simple quoted or double quoted,
and it handles escape sequence like Lua.
The characters {
and }
are forbidden,
there must be represented by a decimal escape sequence \ddd
.
local CodeGen = require 'CodeGen'
tmpl = CodeGen {
call = "${name}(${parameters; separator=', '});",
}
tmpl.name = 'print'
tmpl.parameters = { 1, 2, 3 }
output = tmpl 'call'
print(output) --> print(1, 2, 3);
2. Template include
The syntax is ${name()}
where name
is the keyname of a chunk template.
If name
is not the keyname of a valid chunk,
there are no substitution and an error is reported.
3. Conditional include
The if syntax is ${data?name1()}
and the if/else syntax is ${data?name1()!name2()}
where name1
and name2
are the keyname of a chunk template
and data
is evaluated as a boolean.
4. Template application
The syntax is ${data/name()[; separator='sep']}
where data
must be a table.
The template name
is called for each item of the array data
,
and the result is concatened with an optional separator
.
The template has a direct access in the item,
and inherits access from the caller.
If the item is not a table, it is accessible via the key it
.
Examples
99 Bottles of Beer
Yet another generation of the song 99 Bottles of Beer.
local CodeGen = require 'CodeGen'
local function bootle (n)
if n == 0 then
return 'No more bottles of beer'
elseif n == 1 then
return '1 bottle of beer'
else
return tostring(n) .. ' bottles of beer'
end
end
local function action (n)
if n == 0 then
return 'Go to the store and buy some more'
else
return 'Take one down and pass it around'
end
end
local function populate ()
local t = {}
for i = 99, 0, -1 do
table.insert(t, i)
end
return t
end
local tmpl = CodeGen {
numbers = populate(), -- { 99, 98, ..., 1, 0 }
lyrics = [[
${numbers/stanza(); separator='\n'}
]],
stanza = [[
${it; format=bootle} on the wall, ${it; format=bootle_lower}.
${it; format=action}, ${it; format=bootle_next} on the wall.
]],
bootle = bootle,
bootle_lower = function (n)
return bootle(n):lower()
end,
bootle_next = function (n)
return bootle((n == 0) and 99 or (n - 1)):lower()
end,
action = action,
}
print(tmpl 'lyrics') -- let's sing the song
Java class
Java getter/setter generation with external template:
-- file: java.tmpl
return require'CodeGen'{
class = [[
public class ${_name} {
${_attrs/decl()}
${_attrs/getter_setter(); separator='\n'}
}
]],
decl = [[
private ${_type} ${_name};
]],
getter_setter = [[
public void set${_name; format=firstuc}(${_type} ${_name}) {
this.${_name} = ${_name};
}
public ${_type} get${_name; format=firstuc}() {
return this.${_name};
}
]],
firstuc = function (s)
return s:sub(1, 1):upper() .. s:sub(2)
end,
}
local tmpl = dofile 'java.tmpl' -- load the template
-- populate with data
tmpl._name = 'Person'
tmpl._attrs = {
{ _name = 'name', _type = 'String' },
{ _name = 'age', _type = 'Integer' },
{ _name = 'address', _type = 'String' },
}
print(tmpl 'class') -- interpolation
The output is :
public class Person {
private String name;
private Integer age;
private String address;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return this.age;
}
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return this.address;
}
}
Rockspec
A generic template for rockspec.
-- file: rockspec.tmpl
return CodeGen {
rockspec = [[
package = '${name}'
version = '${version}-${revision}'
${_source()}
${_description()}
${_dependencies()}
]],
_source = [[
source = {
url = ${_url()},
md5 = '${md5}',
dir = '${name}-${version}',
},
]],
_description = [[
description = {
${desc.summary?_summary()}
${desc.homepage?_homepage()}
${desc.maintainer?_maintainer()}
${desc.license?_license()}
},
]],
_summary = 'summary = "${desc.summary}",',
_homepage = 'homepage = "${desc.homepage}",',
_maintainer = 'maintainer = "${desc.maintainer}",',
_license = 'license = "${desc.license}",',
_dependencies = [[
dependencies = {
${dependencies/_depend()}
}
]],
_depend = [[
'${name} >= ${version}',
]],
}
A specialization for all my projects.
-- file: my_rockspec.tmpl
local parent = dofile 'rockspec.tmpl'
return CodeGen({
lower = string.lower,
_tarball = "${name; format=lower}-${version}.tar.gz",
_url = "'https://framagit.org/fperrad/${name}/raw/releases/${_tarball()}'",
_homepage = 'homepage = "https://fperrad.frama.io/${name}",',
desc = {
homepage = true,
maintainer = "Francois Perrad",
license = "MIT/X11",
},
}, parent)
And finally, an use for this project.
CodeGen = require 'CodeGen'
local rs = dofile 'my_rockspec.tmpl'
rs.name = 'lua-CodeGen'
rs.version = '0.1.0'
rs.revision = 1
rs.md5 = 'XxX'
rs.desc.summary = "a template engine"
rs.dependencies = {
{ name = 'lua', version = 5.1 },
{ name = 'lua-testmore', version = '0.2.1' },
}
print(rs 'rockspec')
The output is :
package = 'lua-CodeGen'
version = '0.1.0-1'
source = {
url = 'https://framagit.org/fperrad/lua-CodeGen/raw/releases/lua-codegen-0.1.0.tar.gz',
md5 = 'XxX',
dir = 'lua-CodeGen-0.1.0',
},
description = {
summary = "a template engine",
homepage = "https://fperrad.frama.io/lua-CodeGen",
maintainer = "Francois Perrad",
license = "MIT/X11",
},
dependencies = {
'lua >= 5.1',
'lua-testmore >= 0.2.1',
}