Code Templates and snippets

From OpenPetra Wiki
Revision as of 14:41, 28 April 2010 by Pokorra (talk | contribs) (→‎Placeholders)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Introduction

We use Code templates for the code generation. This allows us to write common code with placeholders. It should be easy to read and maintain, and avoids writing code several times.

We use code templates for the Object Relational Mapping (ORM), ie. for the typed data tables and typed datasets. This code was previously generated with CodeDOM, but that got too complicated. The alternative would have been to have the source in strings in the generator code, but that is hard to read and to maintain.

We also use code templates for the Winforms generation, for the same reasons.

It might help to have a look at the template files, they are in csharp/ICT/PetraTools/Templates (on Git).

Concepts

Placeholders

A placeholder is always with curly brackets, with a hash sign (#) and the name in capital letters.

eg. this is a place holder: {#NAMESPACE}

Now in the code generator, we call FTemplate.ReplacePlaceHolder("NAMESPACE", FCodeStorage.FNamespace);. You could also call FTemplate.SetCodelet("NAMESPACE", FCodeStorage.FNamespace);, it has the same effect, but ReplacePlaceHolder allows only one replacement, while SetCodelet overwrites previous assignments.

Alternatively, we can add several lines in a reserved place: eg.

   private void ShowData()
   {
       FPetraUtilsObject.DisableDataChangedEvent();
       {#SHOWDATA}
       [...]
   }

In the code generator, you will find the line:

FTemplate.AddToCodelet("SHOWDATA", "ShowDataManual();" + Environment.NewLine);

Note that all lines inserted into a place holder will have the same identation as the place holder had.

You can also clear a place holder, so that it will not appear in the result at all:

FTemplate.SetCodelet(String.Empty);

It is useful to clear the place holder before the code is processed, so that in the case there is no meaningful value for this place holder, then you will not get an error message. You can still call AddToCodelet or SetCodelet in the depths of your code generation. For a good place to initialise a codelet, see eg. Ict.Tools.CodeGeneration.Winforms, the function CreateCode, there is a comment "init some template variables that can be empty", with a number of initialisations of place holders.

Snippets

Some code is needed several times inside a file. It has to be inserted by the generator.

The snippets are written at the end of a template file.

A snippet starts with a tag with two hash values (eg. {##SHOWDATAFORCOLUMN}), and ends when a new snippet starts or at the bottom of the file.

example:

{##SHOWDATAFORCOLUMN}
{#IFDEF NOTDEFAULTTABLE}
{#IFDEF CANBENULL}
if ({#NOTDEFAULTTABLE} == null || {#NOTDEFAULTTABLE}[0].Is{#COLUMNNAME}Null())
{
   {#SETNULLVALUE}
}
else
[...]

In the generator, you have to create a new Template for the snippet:

ProcessTemplate snippetShowData = writer.Template.GetSnippet("SHOWDATAFORCOLUMN");

You will insert the placeholders in that snippet, eg.:

snippetShowData.SetCodelet("CANBENULL", !AField.bNotNull ? "yes" : "");
snippetShowData.SetCodelet("DETERMINECONTROLISNULL", this.GetControlValue(ctrl, null));

And then insert the snippet into the main template, into a placeholder:

writer.Template.InsertSnippet("SHOWDETAILS", snippetShowData);

INCLUDE

To avoid too much duplicate code, you can write template code once into one file, and include that template file into other template files.

The syntax is:

{#INCLUDE copyvalues.cs}

This is used for most of the Winforms templates to include commonly used snippets.

IFDEF/IFNDEF

To generate code differently depending for several situations, you can create sections that will be enabled or disabled depending whether the referenced placeholder is empty or has a value.

Please note that {#IFDEF}, {#IFNDEF} and the closing tags {#ENDIF} and {#ENDIFN} need to have no leading nor trailing spaces.

a few examples:

{#IFDEF SAVEDETAILS}
       // get the details from the previously selected row
       if (FPreviouslySelectedDetailRow != null)
       {
           GetDetailsFromControls(FPreviouslySelectedDetailRow);
       }
{#ENDIF SAVEDETAILS}
{#IFNDEF CANBENULL}
{#SETCONTROLVALUE}
{#ENDIFN CANBENULL}