hscript_template

Little run-time and compile-time template system based on hscript


Keywords
cross, hscript, template, template-engine, haxe, compile-time, run-time, tink-template, hscript-template
License
MIT
Install
haxelib install hscript_template 1.1.0

Documentation

hscript-template

Little run-time and compile-time template system based on https://github.com/HaxeFoundation/hscript

This is a single simple class that “generates” a “haxe source” which you can use with hscript to get a template system working like https://github.com/haxetink/tink_template.

The synthax is almost the same as in tink_template and it supports expressions output, if, else, elseif, switch, case, while, break, for statements, “do” and comments.

It works the same way on run-time and compile-time.

The main function parse just parses a string (that can come from a file) and converts it to a string concatenation. Then you can give the result to hscript manually that will interpret it or use the execute function that will do the job automatically.

The helper function execute, available for run-time, will do the link with hscript automatically and give you the result or throw an TemplateError with the line number and the error that occured.

For compile-time I added another helper macro functions buildFromFile, buildFromString and buildTemplates. The 2 first that you can use manually, the second that automatically generates all templates in your project. Take a look at the Compile-Time paragraph.

Installation

Haxelib

You can use Haxelib package manager like that : haxelib install hscript_template. Then, put that in your haxe build file :

-lib hscript
-lib hscript_template

You can also add expra params in order to get run-time and/or compile-time errors reporting. Then you can use it in "automatic" or "manual" mode for compile-time. Take a look at Compile-Time paragraph.

From sources

Download the sources. Put it to your project. Build and take a beer.

Example of a working template :

Hello "::recipient.name::", your main company is : ::recipient.companies[ 0 ].name::
::if( !recipient.male )::Bonjour Madame !::else::Bonjour Monsieur !::end::
You work in these companies : ::recipient.companies.map( function( c ) return c.name ).join( ', ' )::
Here are your companies :
::do var rand = Math.rand()::
::for( company in recipient.companies )::
	::if( rand < .2 )::
		::company.name.toLowerCase()::
	::elseif( rand > .7 )::
		::company.name.toUpperCase()::
	::else::
		::company.name::
	::end::
::end::

You can also customize the sign used to delimitate expressions and the keywords as if, else, for, end, switch, case and do. So now you can write templates like that (like in the awful WINDEV-FR 🤣 ) :

Hello "**recipient.name**", your main company is : **recipient.companies[ 0 ].name**
**si( !recipient.male )**Bonjour Madame !**sinon**Bonjour Monsieur !**fin**
You work in these companies : **recipient.companies.map( function( c ) return c.name ).join( ', ' )**
Here are your companies :
**pose var rand = Math.random()**
**boucle( company in recipient.companies )**
	**si( rand < .2 )**
		**company.name.toLowerCase()**
	**ou_si( rand > .7 )**
		**company.name.toUpperCase()**
	**sinon**
		**company.name**
	**fin**
**fin**

This is the result you get and give to eat to hscript :

var __s__="";__s__+="Hello \"";__s__+=recipient.name;__s__+="\", your main company is : ";__s__+=recipient.companies[ 0 ].name;__s__+="
      ";if(( !recipient.male )){__s__+="Bonjour Madame !";}else{__s__+="Bonjour Monsieur !";}__s__+="
      You work in these companies : ";__s__+=recipient.companies.map( function( c ) return c.name ).join( ', ' );__s__+="
      Here are your companies :
      ";var rand = Math.random();__s__+="
      ";for( company in recipient.companies ){__s__+="
        ";if(( rand < .2 )){__s__+="
		";__s__+=company.name.toLowerCase();__s__+="
	";}else if(( rand > .7 )){__s__+="
		";__s__+=company.name.toUpperCase();__s__+="
	";}else{__s__+="
		";__s__+=company.name;__s__+="
	";}__s__+="
      ";}__s__+="";return __s__;

At the begining I've written a "pretty" mode to output with indents and newlines but I don't see the need at the end because it's to be given to eat to hscript, not to masturbate in front of it, so I removed that.

Run-time

Here is a full example https://try.haxe.org/#602fC0f8 :

class Test {
	static function main() {
		var s = "Hello \"**recipient.name**\", your main company is : **recipient.companies[ 0 ].name**
		**si( !recipient.male )**Bonjour Madame !**sinon**Bonjour Monsieur !**fin**
		You work in these companies : **recipient.companies.map( function( c ) return c.name ).join( ', ' )**
		Here are your companies :
		**pose var rand = Math.random()**
		**boucle( company in recipient.companies )**
		**si( rand < .2 )**
		**company.name.toLowerCase()**
		**ou_si( rand > .7 )**
		**company.name.toUpperCase()**
		**sinon**
		**company.name**
		**fin**
		**fin**";

		var tpl			= new Template();      
		tpl.SIGN		= "*";
		tpl.DO			= "pose";
		tpl.IF			= "si";
		tpl.ELSEIF		= "ou_si";
		tpl.ELSE		= "sinon";
		tpl.FOR			= "boucle";
		tpl.END			= "fin";
		tpl.parse( s );
		trace( tpl.out );

		var ctx = {
			recipient: {
				name		: "Mrs. Annie Cordy",
				male		: false,
				companies	: [
					{ name: "Company 1" },
					{ name: "Company 2" }
				]
			}
		}

		var ret	= tpl.execute( ctx );
	}
}

Error handling

For example if the template has an error like that (line 9) :

1.  Hello "::recipient.name::", your main company is : ::recipient.companies[ 0 ].name::
2.  ::if( !recipient.male )::Bonjour Madame !::else::Bonjour Monsieur !::end::
3.  You work in these companies : ::recipient.companies.map( function( c ) return c.name ).join( ', ' )::
4.  Here are your companies :
5.  ::do var rand = Math.rand()::
6.  ::for( company in recipient.companies )::
7.  	::if( rand < .2 )::
8.  		::company.name.toLowerCase()::
9.  	::elseif(() rand > .7 )::
10.  		::company.name.toUpperCase()::
11.  	::else::
12.  		::company.name::
13.  	::end::
14.  ::end::

With this code :

try{
	return tpl.execute( ctx );
}catch( e : ftk.format.Template.TemplateError ){
	trace( e );
}

You will see Line 9 : Unexpected token: ")" : ::elseif(() rand > .7 )::

Note : You have to add -D hscriptPos to your build file in order to get error position

Compile-time

The easiest way to use it for compile-time is to add this to your build file :

--macro ftk.format.Template.buildTemplates()
# And if you want to get template error position :
-D hscriptPos
-D templatePos

Then the render function is the same as in tink_template, for example :

@:template( "my/path/to/templateFile" ) public function render( arg1, arg2... );

Note : With the automatic build, the source file path is relative to the class file. With manual you can specify if it's relative to the class or not by adjusting the isFullPath argument. Extension isn't important. You can also specify another template meta that will be used to detect template functions to generate. By default @:template() is used but if you want to use cheese just do that :

--macro ftk.format.Template.buildTemplates( null, "cheese" )

So you'll have that as templates functions :

@:cheese( "my/path/to/templateFile" ) public function render( arg1, arg2... );

It will just parse your template file, convert it to a string concatenation, transform it into Haxe macro expressions and populate the body function with these macro expressions. So the result is a function that you can call anywhere and get the string concatenation.

You can also use the macro buildFromFile function manually like that :

public function render( arg1, arg2... ){
	var x	= "foo";
	...
	ftk.format.Template.buildFromFile( "my/path/to/templateFile" );
}

Same for the buildFromString function except that you directly put the string to be treated as template. So you can mix some manipulations and the resulting template.

Note : Don't forget to add that in your build file in order to get the right error's line :

-D hscriptPos
-D templatePos

String interpolation

Because by default hscript doesn't manage string interpolation even in macro mode, another work is needed to get string interpolation working (only in compile-time). For that by default the token $ is used. But if you use the $ sign somewhere in your template that has nothing to do with string interpolation, i.e. some javascript scripts use the $ sign as variables names, you have to specify another token for the real string interpolation token like that :

--macro ftk.format.Template.buildTemplates( "$$" )

And this will be correctly interpolated:

::do var n = 4::
::"foo$$n"::

Here I use $$ but you can specify what you want.

Note : You don't need to use single quote '. All the strings that has $$ inside it will be interpolated. That is in fact why you need to specify antoher string interpolation token if inside your template there is i.e. a javascript that use the $ sign in the variable's name...

How does it work ?

All the template is turned into a string concatenation. The basic text (or html) is concatened into text and all the expressions are just evaluated and then concatened into this same string, as you can see in the examples above.

"Do" statement

With the "do" statement, you can do everything you want. Since all the expressions are just evaluated, you can write any Haxe valid expression like let variables, functions and everything that can be evaluated at the place the template is rendered.

On compile-time, the template is rendered so you get a string concatenation that is "injected" in the body of the function. This function just returns this string with the basic text and all the evaluated expressions...

Last word

This little class is simplier to use that the explanation with my wonderful english in this Readme file to read and understand 🤣