Tuesday, June 26, 2007

Using ports for fast templating...

I find myself many times searching for fast templating system, and I always stop my search by developing one myself. ( the one is done using flex generated code, which means that it is very fast... )

Here, I need some fast (rather simple) template system that gives me some data to send later to an SMTP server (the body of the mail).

First the code:
get(Template, Args) ->
NewArgs = list_to_shell(Args),
Cmd = lists:flatten([ ?engine, $ , "conf/template/", Template, $ , NewArgs ]),
Port = open_port({spawn, Cmd}, [ {cd, code:lib_dir(mmailer)}, stream, binary]),
process_flag(trap_exit, true),
You'll find some unknown notation: '?engine' and $.
  • ?engine is a macro, defined like this -define(engine, "templater").
  • $ is the character notation, the character that follows the dollar sign is the character I want. Actually I need the space character...
Now the function list_to_shell:
list_to_shell(Args) ->
L = lists:map(fun(X) -> [X, $ ] end, Args),
This function add a space after after element of the array passed as parameter. I construct the shell command this way.

The call to open_port is the main part of the code:
Port = open_port({spawn, Cmd}, [ {cd, code:lib_dir(mmailer)}, stream, binary]),
The following describe what the above line does:
  • spawn the command 'Cmd'
  • in the directory code:lib_dir(mmailer),
  • this command will write data as a stream
  • and I want erlang to give me a binary stream.

(code:lib_dir(mmailer) gives me the directory where the mmailer module is located)

More about my engine... My engine takes as firts arguments the template file to parse and every other parameter is saved as $1, or $2, or $3 etc.

Example of a template 'test.template'
Hello $1 $2 !
We have a good news for you, join us at url/$3

So calling:
templater test.template Mister John connect?John
Will compute the following:
Hello Mister John !
We have a good news for you, join us at url/connect?John

Conclusion, you can use whatever template system efficiently with the open_port function, since you can dynamically build the command line.

NB: I've not secure the line generated by list_to_shell, any '; rm -rf *' in the command line will do nasty things with your stuff :)

No comments: