Rez consists of two main parts:
- Specification for a simple templating language
- A library to implement the parsing and resolving of Rez template text in .NET applications.
Rez takes heavy inspiration from Mustache, which is a popular templating language used in many web applications.
One of the key reasons for creating Rez instead of using an existing solution was the need for terse, nestable tokens that are still easy to read.
The name Rez is a play on the word "res" (short for "resolution"), which is the process of resolving a template. The name was chosen as it is short and memorable and is not too similar to other names in the .NET ecosystem, or the domain of testing frameworks.
Rez is a powerful templating tool that allows you to create dynamic text with variables and functions.
To insert a variable, use curly braces:
Input:
Hello {variableName}!
Output:
Hello World!
Variables:
variableName = World
Variables can also be nested and will be resolve inside-out, left-to-right:
Input:
Hello {{nested}}!
Output:
Hello World!
Variables:
nested = variableName
variableName = World
Variables can also be recursive:
Note: There is a hard limit of 4096 recursions in a single template to prevent infinite loops
Input:
Hello {variableName1}!
Output:
Hello World!
Variables:
variableName1 = {variableName2}
variableName2 = World
To use a function, use curly braces with parentheses:
Input:
Hello {greeting()}!
Output:
Hello World!
Functions:
greeting() => World
Some functions can also support parameters, and can be nested:
Input:
{greeting({name})}!
Output:
Hello World!
Functions:
greeting(name) => {hi} name!
name => World
hi => Hello
To include braces without being resolved, use a backslash:
Input:
Hello \{world\}!
Output:
Hello {world}!
Variables:
world = World
If writing templates inside .json files, you will need to escape the backslashes as well
{
"myTemplate": "\\{escapedVariable\\}"
}
Template:
Hello, {name}! Welcome to our platform.
Variables:
{
"name": "John"
}
Resolved text:
Hello, John! Welcome to our platform.
Template:
The temperature is {temperature} degrees Fahrenheit ({fahrenheitToCelsius({temperature})} degrees Celsius).
Variables:
{
"temperature": "68"
}
Functions:
fahrenheitToCelsius(fahrenheit) => (fahrenheit - 32) * 5 / 9
Resolved text:
The temperature is 68 degrees Fahrenheit (20 degrees Celsius).
Template:
Dear {customer:name},
Thank you for your order of {product:name} ({product:sku}). Your order number is {order:number}.
The total cost of your order is {calculateTotal({order:price},{order:tax})}.
Best regards,
{company:name}
Variables:
{
"customer": {
"name": "Jane"
},
"product": {
"name": "Wireless Headphones",
"sku": "WH-123"
},
"order": {
"number": "ORD-456",
"price": "100",
"tax": "10"
},
"company": {
"name": "Electronics Store"
}
}
Functions:
calculateTotal(price,tax) => price + tax
Resolved text:
Dear Jane,
Thank you for your order of Wireless Headphones (WH-123). Your order number is ORD-456.
The total cost of your order is $110.
Best regards,
Electronics Store
Rez allows you to resolve variables and functions embedded within strings, making it easier to dynamically generate text based on the given variables and functions. This guide will help you understand how to use Rez and its template language to create powerful and flexible text templates.
In Rez's template language, variables and functions are enclosed within curly braces:
- Variables:
{variableName}
- Functions:
{functionName()}
or{functionName(parameter)}
To include the delimiters within the text without being resolved, you can use escape characters:
- Escaped variable:
\{escapedVariable\}
- Escaped function:
\{escapedFunction(parameter)\}
To use Rez to resolve the variables and functions in your JSON template, you'll need to:
- Instantiate an implementation of
IResolver
(e.g.Resolver
). - Add sources for variable and function resolution, in form of
IResolverSource
implementations. - Call the
Resolve()
method on theIResolver
instance, passing in the text to be resolved.
Here are some examples of how to set up and use Rez in C#
The easiest way is to directly call Resolver.Resolve() with some variables
var resolver = new Resolver();
resolver.AddSource(new ResolverSource(new Dictionary<string, string> { { "name", "World" } }));
var input = "Hello, {name}!";
var greeting = resolver.Resolve(input);
// greeting: "Hello, World!"
- Be mindful of the order in which sources are added to Rez. Rez will search the sources in the order they were added. If a variable or function is found in multiple sources, Rez will use the first one it encounters.
- Make use of custom
IResolverSource
implementations to provide additional variables and functions or to override existing ones. This can be useful when you want to extend the template language or provide domain-specific functionality. - When creating your text templates, ensure that variables and functions are enclosed within curly braces and use escape characters when necessary.
By following these guidelines, you'll be able to effectively use Rez to create dynamic text templates that can be easily maintained and updated.
Let's say we have a console application that reads a configuration from a JSON file and uses that configuration to populate variables.
The text to resolve will come from user input.
appsettings.json
{
"animal1": "fox",
"animal2": "dog",
"color": "brown",
"description": "lazy"
}
App code
// Read the configuration from the JSON file
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// Create a Resolver instance
var resolver = new Resolver();
// Add the config as a source for variable resolution
resolver.AddSource(new ConfigResolverSource(config));
// Get user input
Console.WriteLine("Enter a sentence:");
string input = Console.ReadLine();
// Resolve the text from the JSON template
string output = resolver.Resolve(input);
// Output the resolved text
Console.WriteLine(output);
If we run the application and enter the following text:
The quick {color} {animal1} jumped over the {description} {animal2}.
The output will be:
The quick brown fox jumped over the lazy dog.
Any plain text that doesn’t get resolved into variables or functions will always resolve as itself:
in > here are some words
out > here are some words
Insert a variable using curly braces:
in > {variable1}
out > variableValue1
Variables can be nested and will always be resolved from the innermost to the outermost, left to right:
in > {variable{{number2}}
--- > {variable2}
out > variableValue2
in > {variable1} {variable2} {variable3}
--- > variableValue1 {variable2} {variable3}
--- > variableValue1 variableValue2 {variable3}
out > variableValue1 variableValue2 variableValue3
When a variable is not found, it will resolve as the input, including the double curly braces:
in > {variable4}
out > {variable4}
Call a function with curly braces and parentheses:
in > {date()}
out > 2023-04-05
Functions can also accept parameters:
in > {fancyFunction(ooh!)}
out > ***ooh!***
Call a function with multiple parameters by separating them with commas:
in > wo{repeatFunction(lo,2)}
out > wololo
Note - Whitespace is NOT ignored in function parameters, and is treated as part of the parameter:
This usually doesn't matter for parameters that are numbers, but it's good to not get into the habit of adding whitespace to function parameters, as is the case with many programming languages.
in > {andFunction(apple,banana)}
out > apple&banana
in > {andFunction(apple, banana)}
out > apple& banana
When a function is not found, it will resolve as the input, including the double curly braces and parentheses:
in > {notAFunction(ooh!)}
out > {notAFunction(ooh!)}
Use a backslash to include braces without being resolved:
in > \{{variable1\}
out > {variable1}
This will persist through nested resolves:
in > {fancyFunction(\{variable1\})}
--- > ***\{variable1\}***
out > ***{variable1}***
Note—This doesn't persist if the output is used as input for another resolve:
This isn't usually something that a user writing a template would need to deal with, but if you experience unexpected behavior, contact the developer in charge of the project that you're writing a templates for.
(text is passed into 1st resolver)
in > \{variable1\}
out > {variable1}
(out is passed into 2nd resolver)
in > {variable1}
out > variableValue1
Variables used in examples:
variable1: variableValue1
variable2: variableValue2
variable3: variableValue3
number1: 1
number2: 2
number3: 3
Functions used in examples:
fancyFunction(input): ***input***
andFunction(input1,input2): input1&input2
repeatFunction(input,count): functionOutput2=[repeat input count times]