Inverse Templates

Hackathon project – Coming soon….

[Start Brief]
Writing open source software is fun, but to get recognition and feedback you need to finish and promote it. Todd, founder of Alivate, has completed most of the initial parts of a new open source project “Inverse Templates”, including most of the content below, and will work with this week’s hackathon group to publish it as an isolated open source project, and NuGet package.

Skills to learn: Code Templating, Code Repositories, NuGet Packages, Lambda, Text Parsing.
Who: Anyone from High School and up is encouraged to come.

We will also be able to discuss future hackathon topics and schedule. Don’t forget to invite all of your hacker friends!

Yes, there will be Coke and Pizza, donated by Alivate.
[End Brief]

The Problem

Many template engines re-invent the wheel. Supporting looping logic, sub-template and many other features. Any control code is also awkward, and extensive use makes template files look confusing to first-time-users.

So why have yet another template engine, when instead you can simply leverage the coding language of your choice, and your skills and experience hard fought?

The Solution

Normal template engines have output content (HTML for example) as the first class citizen, with variables and control code being second class. Inverse Template systems are about reversing this. By using the block commenting feature, of at least C-Like languages, Inverse Template systems let you leverage the full power of your programming language.

At the moment we only have a library for C# Inverse Templates. (Search for the NuGet Package, or Download and reference the latest stable DLL)

Need a loop? Then use a for, foreach, while, and more.
Sub-templating? Call a function, whether it’s in the same code-file, in another object, static or something more exotic.

Introductory Examples

Example T4:

Introductions:
<# foreach (var Person in People) { #>
Hello <#= Person.Name #>, great to iterate you!
<# } #>

Example Inverse Template:

/*Introductions:*/
foreach (var Person in People) {
/*
Hello */w(Person.Name);/*, great to iterate you!*/
}

As you can see, we have a function named w, which simply writes to the output file. More functions are defined for tabbing, and being an Inverse Template you can inherit the InverseTemplate object and extend as you need! These functions are named with a single character, so they aren’t too imposing.

Pre-Processing
As with T4 pre-processing, Inverse Template files are also pre-processed, converting comment blocks into code, then saved as a new file which can be compiled and debugged. Pre-processing as opposed to interpreted templates are required, as we rely on the compiler to compile the control code. Furthermore, there are performance benefits to pre-processed (and compiled) templates, as opposed to interpreted.

Example pre-processed Inverse Template:

l("Introductions:");
foreach (var Person in People) {
  n("");
  t("Hello ");w(Person.Name);
}

Function l, will output any tabbing, then content, then line-ending
Function n, will output the content followed by line-ending
Function t, will prefix any tabbing followed by content

The pre-processor will find and process all files in a given folder hierarchy ending with “.ct.cs”. The pre-processor is an external console application, so that it will even work with Express editions of Visual Studio.

You should:

  • Put all of your Definitions into folder .\InverseTemplates\Definitions\, sub-folders are ok
  • Actively exclude and then re-include the generated .\InverseTemplates\Processed\, folder after pre-processing
  • Exclude the Definitions folder before you compile/run your project

Not the answer to all your problems

I’m not claiming the Inverse Templates are the ultimate solution. They’re simply not. If you have a content heavy templates with no control code and minimal variable merging, then perhaps you want to just use T4.

Also, you may find that you’re more comfortable using all of the InverseTemplate functions directly {l,n,w,t}, instead of using comment blocks. In some cases this can look more visually appealing, and then you can bypass the pre-processing step. This could be particularly true of templates where you have lots of control code and minimal content.

But then again, keep in mind that your code-editor will be able to display a different colour for comment blocks. And perhaps in the future your code-editor may support InverseTemplates using a different syntax highlighter inside your comment blocks.

For a lot of work I do, I’ll be using Inverse Templates. I will have the full power of the C# programming language, and won’t need to learn the syntax of another template engine.

I’m even thinking of using it as a dynamic rendering engine for web, but that’s more of a curiosity than anything.

Advanced Example – Difference between Function, Generate and FactoryGet

class TemplateA : InverseTemplate {
  public override Generate() {
    /*This will be output first, no line-break here.*/
    FunctionC(); //A simple function call, I suggest using these most often, mainly to simplify your cohesive template, when function re-use is unlikely.
    Generate(); //This is useful when there is some function re-use, or perhaps you want to contain your generation into specific files in a particular structure
    IMySpecial s = FactoryGet("TemplateD"); //This is useful for more advanced situations which require either a search by interface implementation, with optional selection of a specific implementation by class name.
    s.SpecificFunction("third");
  }
  private void FunctionC() {
    /*
    After a line-break, the is now the second line, with a line-break.
    */
  }
}
class TemplateB : InverseTemplate {
  public override Generate() {
    /*This will be the third line.*/
  }
}
interface IMySpecial
{
  void SpecificFunction(string SpecificParameter);
}
class TemplateD : InverseTemplate, IMySpecial
{
  public SpecificFunction(string SpecificParameter) {
    /* This will follow on from the the */w(SpecificParameter);/*line.
    */
  }
}
class TemplateF : InverseTemplate, IMySpecial
{
  //Just to illustrate that there could be multiple classes implementing the specialised interface
}

Advanced – Indenting

All indent is handled as spaces, and is tracked using a stack structure.

pushIndent(Amount) will increase the indent by the amount you specify, if no parameter is specified, the default is 4 spaces.
popIndent will pop the amount of indent on the stack, the last amount pushed.
withIndent(Amount, Action) will increase the indent only for the duration of the specified action.

Example:

withIndent(8, () => {
  /*This will be indented by 8 spaces.
  And so will this, on the next line.
  I recommend you only use this when calling a function.*/
});
/*This will not be indented.*/
/*Within a single function you should
    control your indent manually with spaces.*/
if (1 == 1) {
/*
    it will be easier to see compared to calls to any of the indent functions {pushIndent, withIndent, etc..}*/
  if (2 == 2) {
/*
    just keep your open-comment-block marker anchored in-line with the rest*/
  }
}

These are all the base strategies that I currently use across my Inverse Templates. I also inherit InverseTemplate and make use of the DataContext, but you’ll have to wait for another time before I explain that in more detail.

Enhanced by Zemanta

Leave a Reply

Your email address will not be published. Required fields are marked *