Code generation based on .NET Core
Code generated by a computer can be good-looking, and can work for large scale engineering because it results in highly uniform applications.
The templating language GSL is originally created by Peter Hintjens of iMatix. gsl-net is powered by Javascript and its syntax reads like this:
. /* this is a comment */
. for (var i = 0; i < 10; i ++) {
. setOutput('SKU' + i.toString() + '.cs');
/**
* Generated by gsl-net
* do not modify by hand.
*/
namespace Demo
{
public class SKU${i.toString()}
{
}
}
.}
-
Grab these two files
-
Download the latest release (TBD)
-
Run
Gsl.exedemo.gsl demo.jsonand you should see a file called
demo.tsdrop into your current directory.
Here's some sample code to host your own templating engine.
dotnet add package Gsl.Template
dotnet add package System.IO.Abstractions
dotnet add package Microsoft.Extensions.Logging
var logger = loggerFactory.CreateLogger<Program>();
var fileSystem = new FileSystem();
var engine = new Engine(new Gsl.VM(fileSystem, logger), logger);
try
{
engine.Execute(new FileInfoWrapper(fileSystem, new FileInfo(pathToTemplate)),
new FileInfoWrapper(fileSystem, new FileInfo(pathToData)));
}
catch (Exception exception)
{
Console.Error.WriteLine(exception.Message);
Console.Error.WriteLine(exception.StackTrace);
}
If you decide that there are specific files that should not be subject to further code generation, you can apply the following command in your template:
. doNotOverwriteIf("DO NOT OVERWRITE", ".bak")
If gsl-net finds the phrase "DO NOT OVERWITE" in the output file, it will write
its output to a .bak file instead.
Then you can just diff the output and decide what part you may wish to copy
into your source manually. Hint: code.exe --diff FileA FileB
gsl-net supports protected sections where the code generation engine
will not overwrite specific named sections of generated code.
For instance,
. setOutput('Customer.cs');
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsValid()
{
. protect('customer-validation', '//')
}
}
This creates two markers with CUSTOM-CODE-BEGIN and CUSTOM-CODE-END. You can add your own code in these sections and they will be protected against being overwritten.
The protect command is sufficiently clever to indent the comment markers at the
same level as your own code. I'd like gsl-net to pay attention to how good-looking the
generated code can be.
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsValid()
{
// CUSTOM-CODE-BEGIN customer-validation
return ! string.IsNullOrEmpty(FirstName) && ! string.IsNullOrEmpty(LastName);
// CUSTOM-CODE-END customer-validation
}
}
(Warning - this will likely be revised)
. setOutput('Customer.cs')
. for (var i = 0; i < fields.length; i++) {
${fields[i].type ${fields[i].name}; /* ${fields[i].comment} */
. }
Results in a rather untidy-looking generated code:
string FirstName; /* the first name of the contact */
string SurName; /* the family name or the surname of the contact, it may include hyphens */
Date dob; /* date of birth */
gsl-net solves this problem with alignment markers
. setOutput('Customer.cs')
. for (var i = 0; i < fields.length; i++) {
. | | | |
${fields[i].type ${fields[i].name}; /* ${fields[i].comment} */
. }
And the results look so much better:
string FirstName; /* the first name of the contact */
string SurName; /* the family name or the surname of the contact, it may include hyphens */
Date dob; /* date of birth */
Let's say you need to generate some SQL statements that look like
CREATE TABLE foo (
bar INTEGER NOT NULL,
baz INTEGER NOT NULL,
qux INTEGER NOT NULL
)
Getting rid of the last comma is a common problem.
To solve this, we introduce an optional directive ?. If it occurs on a line, then the final optional character is left out.
CREATE TABLE foo (
.for (var i = 0; i < fields.length; i++) {
. ?
${fields[i]} INTEGER NOT NULL,
.}
)
GSL files can be refactored and included
. // we are in file 1
. // files are included relative to the current file
. include('../file2.gsl')
It is convenient to start with a working file and perform search and replace to arrive at a template. Suppose you have a source like this:
class Foo
{
findFoo(id): Foo;
deleteFoo(id): void;
createFoo(): Foo;
}
You can use a replaceText() directive on your template.
. var className = data.className;
. replaceText("Foo", className);
class Foo
{
findFoo(id): Foo;
deleteFoo(id): void;
createFoo(): Foo;
}
instead of the uglier option:
. var className = data.className;
class Foo
{
find${className}(id): Foo;
delete${className}(id): void;
create${className}(): Foo;
}
gsl-net provides these helper functions
kebabCase(properCase: string): string
camelCase(properCase: string): string
There are a few shortcuts for building this project
-
run
. _ok.ps1, and this should show you a list of options when you typeok -
You can build a self-contained .exe with
dotnet warp
-
Doneprotected sections -
import modules
-
.NET modules
-
load local files (eval JSON)