Hapify Syntax
Why use a specific syntax?
We have designed a syntax able to manipulate the model object injected in the templates. This syntax is optimized to play with the properties of this model object using short words. This allows to handle complex ideas with simple sentences.
For example, this loop in JavaScript :
for (let field of root.fields.filter(f => f.searchable && f.type === 'entity')) {
out += ' Do something';
}
will be written like this with the Hapify syntax:
<<for Fields searchable and entity field>>
Do something
<<endfor>>
<<@ F se*tE f>>
Do something
<<@>>
Long and short syntaxes
Hapify templates can be written with a long or a short syntax.
Both have their advantages:
- The short syntax does not interfere with the target code when reading the template, thanks to a shorter meta-code.
- The long syntax is explicit and can be read easily.
In the same template, you can mix both syntaxes.
Note
All code examples below are translated into JavaScript equivalent for your information. During generation, the Hapify syntax is converted to similar JavaScript code.
Tags
Hapify syntax blocks are wrapped by two tags:
- opening:
<<
. - closing:
>>
.
Escaping
Generally used for binary operations, these tags can be escaped.
The escaped tags <<` (and
>>) are replaced by
<<(and
>>`) during generation.
Names
Data model names
In a template of type one model
:
// Create a new <<Model lower>>
const <<Model camel>> = new <<Model pascal>>();
// Create a new <<M a>>
const <<M aA>> = new <<M AA>>();
out += `// Create a new ${root.names.lower}
const ${root.names.camel} = new ${root.names.pascal}();`;
For a data model named user group
, the result will be as follows:
// Create a new user group
const userGroup = new UserGroup();
Field names
List all fields of a data model:
<?php
$fields = array(
<<for Fields field>>
'<<field camel>>',
<<endfor>>
);
<?php
$fields = array(
<<@ F f>>
'<<f aA>>',
<<@>>
);
out += `<?php
$fields = array(
${root.fields.list.map(f => "'"+f.names.camel+"'").join(",\n\t")}
);`;
For a data model with name
, created at
and role
fields:
<?php
$fields = array(
'name',
'createdAt',
'role',
);
Cases
The available cases are:
camel
(alias :aA
) forcamelCase
pascal
(alias :AA
) forPascalCase
lower
(alias :a
) forlower case
capital
(alias :A
) forCapital Case
kebab
(alias :a-a
) forkebab-case
header
(alias :A-A
) forHeader-Case
snake
(alias :a_a
) forsnake_case
constant
(alias :A_A
) forCONSTANT_CASE
compact
(alias :aa
) forcompactcase
raw
(alias :R
) (raw) for the original name
Conditions
Simple condition
const utils = require('utils');
<<if Fields entity>>
const mongoDb = require('mongodb');
<<endif>>
const utils = require('utils');
<<? F tE>>
const mongoDb = require('mongodb');
<<?>>
out += `const utils = require('utils');`;
if (root.fields.filter(f => f.type === 'entity').length > 0) {
out += `\nconst mongoDb = require('mongodb');`;
}
For a data model that contains at least one field of type entity
, the result will be as follows:
const utils = require('utils');
const mongoDb = require('mongodb');
For a data model that does not contain a field of type entity
, the result will be as follows:
const utils = require('utils');
Without filter
Field filtering is optional.
<<if Fields>>
// this model has at least one field
<<endif>>
<<? F>>
// this model has at least one field
<<?>>
if (root.fields.list.length > 0) {
out += ' // this model has at least one field';
}
Alternative conditions
<<if Fields entity>>
// At least one entity field
<<elseif Fields hidden>>
// No entity field and at least one hidden field
<<else>>
// No entity field and no hidden field
<<endif>>
<<? F tE>>
// At least one entity field
<<?? F hd>>
// No entity field and at least one hidden field
<<??>>
// No entity field and no hidden field
<<?>>
if (root.fields.filter(f => f.type === 'entity').length > 0) {
out += ' // At least one entity field';
} else if (root.fields.filter(f => f.hidden).length > 0) {
out += ' // No entity field and at least one hidden field';
} else {
out += ' // No entity field and no hidden field';
}
Complex conditions
Operators
The operators available for conditions are :
and
- alias*
or&&
or
- alias+
or||
and not
- aliasandNot
,/
or&& !
or not
- aliasorNot
,-
or|| !
Example
<<if Fields (entity and hidden) or (unique and not multiple)>>
// This code block is reached if the model has at least one field that validates this conditions:
// (type entity AND hidden) OR (unique AND NOT multiple)
<<endif>>
<<? F (tE*hd)+(un/ml)>>
// This code block is reached if the model has at least one field that validates this conditions:
// (type entity AND hidden) OR (unique AND NOT multiple)
<<?>>
for (let field of root.fields.filter(f => (f.type === 'entity' && f.hidden) || (f.unique && !f.multiple))) {
out += ' // ...';
}
Conditions can also be written with native operators. Let's rewrite this last condition:
<<if Fields (entity && hidden) || (unique && !multiple) >>
// This code block is reached if the model has at least one field that validates this conditions:
// (type entity AND hidden) OR (unique AND NOT multiple)
<<endif>>
<<? F (tE && hd) || (un && !ml) >>
// This code block is reached if the model has at least one field that validates this conditions:
// (type entity AND hidden) OR (unique AND NOT multiple)
<<?>>
for (let field of root.fields.filter(f => (f.type === 'entity' && f.hidden) || (f.unique && !f.multiple))) {
out += ' // ...';
}
Conditions on the number of occurrences
By specifying a number after the if
, we can add a condition on the minimum number of required elements. In this case, the fields :
<<if4 Fields hidden>>
// This model has at least 4 hidden fields
<<elseif2 Fields label or boolean>>
// This model has at least 2 label or boolean fields
<<else>>
// Something else
<<endif>>
<<?4 F hd>>
// This model has at least 4 hidden fields
<<??2 F lb+tB>>
// This model has at least 2 label or boolean fields
<<??>>
// Something else
<<?>>
if (root.fields.filter(f => f.hidden).length >= 4) {
out += ' // This model has at least 4 hidden fields';
} else if (root.fields.filter(f => f.label || f.type === 'boolean').length >= 2) {
out += ' // This model has at least 2 label or boolean fields';
} else {
out += ' // Something else';
}
Conditions on the data models
Test a single data model
In a template of type one model
:
<<if Model isGeolocated>>
// This block is reached if the model is geolocated.
// that's means it has at least one latitude field and one longitude field
<<endif>>
<<? M pGeo>>
// This block is reached if the model is geolocated.
// that's means it has at least one latitude field and one longitude field
<<?>>
if (root.properties.isGeolocated) {
out += ' // ...';
}
Test a list of data models
In a template of type all models
:
<<if Models not onlyGuest>>
// This block is reached if at least one model has not only guest actions
import 'session-service';
<<endif>>
<<? M !pOGs>>
// This block is reached if at least one model has not only guest actions
import 'session-service';
<<?>>
if (root.filter(m => !m.accesses.properties.onlyGuest).length > 0) {
out += " import 'session-service';";
}
Available objects and filters
Root object
Model
or Models
( short: M
) refer to the main object:
- the data model in a template of type
one model
- the array of data models in a template of type
all models
.
Filterable and testable objects
In the case of a template of type one model
:
Fields
(alias:F
) is the list of fieldsDependencies
(alias:D
) is the list of dependencies (list of data models)ReferencedIn
(alias:RefModels
,R
) is the list of data models that depend on itPrimaryField
(alias:P
) is the primary field of the modelAccesses
(alias:A
) is the list of accessesCreateAccess
(alias:Ac
) is the access to the create actionReadAccess
(alias:Ar
) is the access to the read actionUpdateAccess
(alias:Au
) is the access to the update actionRemoveAccess
(alias:Ad
) is the access to the delete actionSearchAccess
(alias:As
) is the access to the search actionCountAccess
(alias:An
) is the access to the count action
Filtering on field attributes
Available attributes for a field:
primary
(short:pr
) for booleanprimary
unique
(short:un
) for booleanunique
label
(short:lb
) for booleanlabel
nullable
(short:nu
) for booleannullable
multiple
(short:ml
) for booleanmultiple
embedded
(short:em
) for booleanembedded
searchable
(short:se
) for booleansearchable
sortable
(short:so
) for booleansortable
hidden
(short:hd
) for booleanhidden
internal
(short:in
) for booleaninternal
restricted
(short:rs
) for booleanrestricted
ownership
(short:os
) for booleanownership
string
(short:tS
) for typestring
email
(short:tSe
) for typestring
and subtypeemail
password
(short:tSp
) for typestring
and subtypepassword
url
(short:tSu
) for typestring
and subtypeurl
text
(short:tSt
) for typestring
and subtypetext
richText
(alias:rich
, short:tSr
) for typestring
and subtyperich
number
(short:tN
) for typenumber
integer
(short:tNi
) for typenumber
and subtypeinteger
float
(short:tNf
) for typenumber
and subtypefloat
latitude
(short:tNt
) for typenumber
and subtypelatitude
longitude
(short:tNg
) for typenumber
and subtypelongitude
boolean
(short:tB
) for typeboolean
datetime
(short:tD
) for typedatetime
date
(short:tDd
) for typedatetime
and subtypedate
time
(short:tDt
) for typedatetime
and subtypetime
enum
(short:tU
) for typeenum
entity
(short:tE
) for typeentity
oneOne
(short:tEoo
) for typeentity
and subtypeoneOne
oneMany
(short:tEom
) for typeentity
and subtypeoneMany
manyOne
(short:tEmo
) for typeentity
and subtypemanyOne
manyMany
(short:tEmm
) for typeentity
and subtypemanyMany
object
(short:tO
) for typeobject
file
(short:tF
) for typefile
image
(short:tFi
) for typefile
and subtypeimage
video
(short:tFv
) for typefile
and subtypevideo
audio
(short:tFa
) for typefile
and subtypeaudio
document
(short:tFd
) for typefile
and subtypedocument
Example
<<if Fields (restricted or internal) and not number>>
// Current model has at least one field matching to the condition
<<endif>>
<<? F (rs+in)/tN>>
// Current model has at least one field matching to the condition
<<?>>
if (root.fields.filter(f => (f.restricted || f.internal) && !f.number).length > 0) {
out += " // ...";
}
Filtering on data model properties
Properties available for a data model:
mainlyHidden
(short:pMHd
) most of the fields are hidden (strictly)mainlyInternal
(short:pMIn
) most of the fields are internal (strictly)isGeolocated
(short:pGeo
) the model contains at least onelatitude
field and onelongitude
fieldisGeoSearchable
(short:pGSe
) the model contains at least one searchablelatitude
field and one searchablelongitude
field
Example
<<if Model isGeolocated>>
// This model contains at least one latitude field and one longitude field.
<<endif>>
<<? M pGeo>>
// This model contains at least one latitude field and one longitude field.
<<?>>
if (root.properties.isGeolocated) {
out += " // ...";
}
Access properties available for a data model:
onlyAdmin
(short:pOAd
) the model only contains actions restricted toadmin
onlyOwner
(short:pOOw
) the model only contains actions restricted toowner
onlyAuth
(short:pOAu
) the model only contains actions restricted toauthenticated
onlyGuest
(short:pOGs
) the model only contains actions restricted toguest
maxAdmin
(short:pMAd
) the most permissive access isadmin
maxOwner
(short:pMOw
) the most permissive access isowner
maxAuth
(short:pMAu
) the most permissive access isauthenticated
maxGuest
(short:pMGs
) the most permissive access isguest
noAdmin
(short:pNAd
) there is no action restricted toadmin
noOwner
(short:pNOw
) there is no action restricted toowner
noAuth
(short:pNAu
) there is no action restricted toauthenticated
noGuest
(short:pNGs
) there is no action restricted toguest
hasAdmin
(short:pHAd
) at least one action is restricted toadmin
hasOwner
(short:pHOw
) at least one action is restricted toowner
hasAuth
(short:pHAu
) at least one action is restricted toauthenticated
hasGuest
(short:pHGs
) at least one action is restricted toguest
Example
<<if Model onlyAdmin>>
// All actions on this model are restricted to admins
<<endif>>
<<? M pOAd>>
// All actions on this model are restricted to admins
<<?>>
if (root.accesses.properties.onlyAdmin) {
out += " // ...";
}
Filtering on data model access
Reminder
guest
is the most permissive access and admin
the least permissive. Therefore admin < owner < authenticated < guest
.
Filters available for action access:
admin
(short:ad
) the access isadmin
owner
(short:ow
) the access isowner
auth
(short:au
) the access isauth
guest
(short:gs
) the access isguest
gteAdmin
(short:[ad
) the access is greater or equal thanadmin
gteOwner
(short:[ow
) the access is greater or equal thanowner
gteAuth
(short:[au
) the access is greater or equal thanauth
gteGuest
(short:[gs
) the access is greater or equal thanguest
lteAdmin
(short:ad]
) the access is less or equal thanadmin
lteOwner
(short:ow]
) the access is less or equal thanowner
lteAuth
(short:au]
) the access is less or equal thanauth
lteGuest
(short:gs]
) the access is less or equal thanguest
Exemples
Tests access for a specific action:
<<if ReadAccess guest>>
// Anyone can read this model
<<endif>>
<<? Ar gs>>
// Anyone can read this model
<<?>>
if (root.accesses.read.guest) {
out += ' // ...';
}
Checks if the update action is restricted to either the administrators or the owner:
<<if UpdateAccess admin or owner>>
// ...
<<endif>>
<<? Au ad+ow>>
// ...
<<?>>
if (root.accesses.update.admin || root.accesses.update.owner) {
out += ' // ...';
}
Tests whether at least one action is restricted to one or fewer authenticated users:
<<if Accesses lteAuth>>
// ...
<<endif>>
<<? A au]>>
// ...
<<?>>
if (root.accesses.filter(a => a.lteAuth).length > 0) {
out += ' // ...';
}
Tip
Conditions can be applied to an object or an array of objects.
If applied to an array, it will test the length of the array filtered by the provided condition.
It can be used on any object containing a filter
method that receives a callback returning a boolean.
For example, in the data model structure, root.dependencies
is an object that contains a filter
method.
Thus, this operator can test whether a model has dependencies that have fields with a specific condition.
Iterations
Iterations use the same filters and operators as conditions.
Simple iteration
Loops over all the fields in a data model that are not hidden and assigns them to the field
variable:
<?php
$fields = array(
<<for Fields not hidden field>>
'<<field camel>>',
<<endfor>>
);
<?php
$fields = array(
<<@ F !hd f>>
'<<f aA>>',
<<@>>
);
out += `<?php
$fields = array(
${
root.fields
.filter(f => !f.hidden)
.map(f => "'"+f.names.camel+"'")
.join(",\n\t")
}
);`;
Example for a data model with fields name
, created at
and role
, where role
is hidden:
<?php
$fields = array(
'name',
'createdAt',
);
Loops over the entity
and searchable fields of the data model:
<<for Fields searchable and entity field>>
// ...
<<endfor>>
<<@ F se*tE f>>
// ...
<<@>>
for (let field of root.fields.filter(f => f.searchable && f.type === 'entity')) {
out += ' // ...';
}
Loop without filtering
This operation allows you to loop through all the fields:
<<for Fields field>>
// ...
<<endfor>>
<<@ F f>>
// ...
<<@>>
for (let field of root.fields.list) {
out += ' // ...';
}
Loop through data models
In a template of type all models
, this loops through all the data models that are geo-located:
<<for Models isGeolocated model>>
// ...
<<endfor>>
<<@ M pGeo m>>
// ...
<<@>>
for (let model of root.filter(i => i.properties.isGeolocated)) {
out += ' // ...';
}
Loop through dependencies.
In a template of type one model
, this loops through the dependencies whose referent field is searchable:
<<for Dependencies searchable dep>>
// ...
<<endfor>>
<<@ D se d>>
// ...
<<@>>
for (let dep of root.dependencies.filter(f => f.searchable)) {
out += ' // ...';
}
Tip
In the case of a self-referencing data model, Dependencies
excludes this self-dependency.
To include it use the following code:
<<< for (let dep of root.dependencies.filter(f => f, false)) { >>>
// ...
<<< } >>>
Warning
Filtering of Dependencies
is performed only on the fields of the current data model that carry the reference.
Filtering is not performed on the fields of the target data model.
Loop through referring data models
In a template of type one model
, this loops through the data models that have a dependency on it and that are geo-located:
<<for ReferencedIn isGeolocated referrer>>
// ...
<<endfor>>
<<@ R pGeo r>>
// ...
<<@>>
for (let referrer of root.referencedIn.filter(m => m.properties.isGeolocated)) {
out += ' // ...';
}
Tip
The filter is optional. You can get all the referring data models like this:
<<for ReferencedIn referrer>>
// ...
<<endfor>>
Warning
Only referenced entity fields are defined in these referring data models.
Loop through the accesses of the data model
Loops through all accesses restricted to an administrator or owner and displays the name of the action:
<<for Accesses admin or owner access>>
<<=access.action>>
<<endfor>>
<<@ A ad+ow a>>
<<=a.action>>
<<@>>
for (let access of root.accesses.filter(a => a.admin || a.owner)) {
out += ` ${access.action}\n`;
}
Shortened iteration
Loop through the first 2 fields of a data model :
<?php
$fields = array(
<<for2 Fields field>>
'<<field camel>>',
<<endfor>>
);
<?php
$fields = array(
<<@2 F f>>
'<<f aA>>',
<<@>>
);
out += `<?php
$fields = array(
${
root.fields.list
.slice(0, 2)
.map(f => "'"+f.names.camel+"'")
.join(",\n\t")
}
);`;
For a data model with fields name
, email
and role
:
<?php
$fields = array(
'name',
'email',
);
Nested iterations
Loop through the enums
In a template of type one model
, this block defines a TypeScript type containing the enums of a field:
<<for Fields enum field>>
type <<field pascal>> =<<for field.enum e>> | '<<e snake>>'<<endfor>>;
<<endfor>>
<<@ F tU f>>
type <<f AA>> =<<@ f.e e>> | '<<e a_a>>'<<@>>;
<<@>>
for (let field of root.fields.filter(f => f.type === 'enum')) {
out += `type ${field.names.pascal} = ${field.enum.map(e => "'"+e.names.snake+"'").join(' | ')};`;
}
type Role = | 'admin' | 'user' | 'customer';
Loop through the fields of all data models
In a template of type all models
, this block loops through all the fields of all the data models:
const models = {
<<for Models model>>
<<m camel>>: [
<<for model.fields field>>
'<<field camel>>',
<<endfor>>
],
<<endfor>>
}
const models = {
<<@ M m>>
<<m aA>>: [
<<@ m.f f>>
'<<f aA>>',
<<@>>
],
<<@>>
}
const models = {
user: [
'id',
'createdAt',
'email',
'name',
],
place: [
'id',
'name',
'category',
],
placeCategory: [
'id',
'createdAt',
'email',
'name',
],
}
Raw input and interpolation
This operator allows you to write pure JavaScript.
Custom variable
Defines a custom variable and adds it to the output:
<<< const length = root.fields.length; >>>
// This model has <<=length>> fields
Custom function
Defines a custom function and calls it:
<<<
function fieldName(field) {
return field.names.snake.replace('_', ':');
}
>>>
<<for Fields field>>
<<=fieldName(field)>>
<<endfor>>
<<<
function fieldName(f) {
return f.names.snake.replace('_', ':');
}
>>>
<<@ F f>>
<<=fieldName(f)>>
<<@>>
id
created:at
place:category
Custom condition or iteration
This block allows you to write a condition that is not handled by the Hapify syntax:
<<< if (root.fields.hidden.length < 3 || root.properties.mainlyInternal) { >>>
// ...
<<< } >>>
Tip
In a Hapify template of type one model
, the root
variable refers to the data model.
In a Hapify template of type all models
, the root
variable refers to the array of data models.
See also
For detailed information on the structure of the data model, see the model object.
Notes
You can retrieve notes left by the user on a field or model:
<<if Model hasNotes>>// <<! Model>><<endif>>
export class <<Model pascal>> {
<<for Fields field>>
public <<field camel>>; <<if field hasNotes>>// <<! field>><<endif>>
<<endfor>>
}
<<? M hN>>// <<! M>><<?>>
export class <<M AA>> {
<<@ F f>>
public <<f aA>>; <<? f hN>>// <<! f>><<?>>
<<@>>
}
// A user can only list its own bookmarks
export class Bookmark {
public id;
public owner; // Current user when creating the bookmark
public place;
}
It is also possible to use interpolation to display the notes:
<<if Model hasNotes>>// <<=root.notes>><<endif>>
export class <<Model pascal>> {
<<for Fields field>>
public <<field camel>>; <<if field hasNotes>>// <<=field.notes>><<endif>>
<<endfor>>
}
<<? M hN>>// <<=root.notes>><<?>>
export class <<M AA>> {
<<@ F f>>
public <<f aA>>; <<? f hN>>// <<=field.notes>><<?>>
<<@>>
}
Error
Do not write this: <<= JSON.stringify(root) >>
.
The root
object has recursive properties. Therefore, this command will lead to an infinite loop.
Comments
This syntax writes a comment to the template without any output to the generated file.
<<# This is just a comment>>
Escaping
It is possible to escape the tags of the Hapify syntax with the character \
:
$val = 4;
$res = $val \<\< 3;
$res = 4 \>\> $val;
$val = 4;
$res = $val << 3;
$res = 4 >> $val;
Formatting
Empty lines and lines containing only condition or iteration meta-code are automatically deleted after generation. To force the generator to keep an empty line, insert one or more spaces at the beginning of it.
Warning
Hapify does not format the generated code, since the formatting rules are specific to each language or framework. We strongly recommend you to use a code formatter after the generation.
Reserved words
The following list of words cannot be used to name variables.
A
, Ac
, Accesses
, ad
, Ad
, admin
, An
, and
, andNot
, Ar
, As
, au
, Au
, audio
, auth
, boolean
, CountAccess
, CreateAccess
, D
, date
, datetime
, Dependencies
, document
, else
, elseif
, em
, email
, embedded
, endfor
, endif
, entity
, enum
, F
, Fields
, file
, float
, for
, gs
, gteAdmin
, gteAuth
, gteGuest
, gteOwner
, guest
, hasAdmin
, hasAuth
, hasGuest
, hasNotes
, hasOwner
, hd
, hidden
, hN
, if
, image
, in
, integer
, internal
, isGeolocated
, isGeoSearchable
, label
, latitude
, lb
, longitude
, lteAdmin
, lteAuth
, lteGuest
, lteOwner
, M
, mainlyHidden
, mainlyInternal
, manyMany
, manyOne
, maxAdmin
, maxAuth
, maxGuest
, maxOwner
, ml
, Model
, Models
, multiple
, noAdmin
, noAuth
, noGuest
, noOwner
, not
, nu
, nullable
, number
, object
, oneMany
, oneOne
, onlyAdmin
, onlyAuth
, onlyGuest
, onlyOwner
, or
, orNot
, os
, out
, ow
, owner
, ownership
, P
, password
, pGeo
, pGSe
, pHAd
, pHAu
, pHGs
, pHOw
, pMAd
, pMAu
, pMGs
, pMHd
, pMIn
, pMOw
, pNAd
, pNAu
, pNGs
, pNOw
, pOAd
, pOAu
, pOGs
, pOOw
, pr
, primary
, PrimaryField
, R
, ReadAccess
, ReferencedIn
, RefModels
, RemoveAccess
, restricted
, rich
, richText
, root
, rs
, se
, searchable
, SearchAccess
, so
, sortable
, string
, tB
, tD
, tDd
, tDt
, tE
, tEmm
, tEmo
, tEom
, tEoo
, text
, tF
, tFa
, tFd
, tFi
, tFv
, time
, tN
, tNf
, tNg
, tNi
, tNt
, tO
, tS
, tSe
, tSp
, tSr
, tSt
, tSu
, tU
, un
, unique
, UpdateAccess
, url
, video
.