|
| 1 | +# How to create new generated symbol type |
| 2 | + |
| 3 | +Available generated symbol types are described [here](../Available-Symbols-Generators.md). |
| 4 | +Generated symbols are processed by a corresponding macro component that can generate new variables before the template is instantiated. New variables can be created from other variables (usually parameters) or independently. |
| 5 | + |
| 6 | +We appreciate creating new types of generated symbols and macros by the community. |
| 7 | + |
| 8 | +Generated symbols follow the following syntax: |
| 9 | +```json |
| 10 | +{ |
| 11 | + "symbols": |
| 12 | + { |
| 13 | + "symbolName": |
| 14 | + { |
| 15 | + "type": "generated", |
| 16 | + "generator": "your-type", //type of generated symbol; should be same as type of the macro implementing it |
| 17 | + "dataType": "string", //data type of the value that symbol generates: "string", "choice", "bool", "float", "int", "hex", "text" |
| 18 | + "parameters": |
| 19 | + { |
| 20 | + "name": "value" //key-value parameters for the symbol. The value may be JSON array or object, if more complicated configuration is needed. |
| 21 | + }, |
| 22 | + "replaces": "to-be-replaced", // the text to replace with the value of this symbol in template content |
| 23 | + "fileRename": "to-be-replaced", // defines the portion of file name which will be replaced by symbol value |
| 24 | + } |
| 25 | + |
| 26 | + } |
| 27 | +} |
| 28 | +``` |
| 29 | + |
| 30 | +To create new generated symbol type, follow the following guideline |
| 31 | + |
| 32 | +1. Implement [`Microsoft.TemplateEngine.Orchestrator.RunnableProjects.Abstractions.IGeneratedSymbolMacro<T>`](../../src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/Abstractions/IGeneratedSymbolMacro.cs) interface. |
| 33 | + |
| 34 | +Any macro implementation is a component ([`IIdentifiedComponent`](../../src/Microsoft.TemplateEngine.Abstractions/IIdentifiedComponent.cs)) and will be loaded by template engine. |
| 35 | +The existing implementation macro components are located in [`Macros`](../../src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/Macros) folder of `Microsoft.TemplateEngine.Orchestrator.RunnableProjects` projects. |
| 36 | + |
| 37 | +The implementation should have: |
| 38 | +- `Id` property - unique GUID for component |
| 39 | +- `Type` property - unique `string` name, matching generated symbol type |
| 40 | +- `Evaluate(IEngineEnvironmentSettings environmentSettings, IVariableCollection variables, T config)` method - evaluates the variables based on `config` specified |
| 41 | +- `Evaluate(IEngineEnvironmentSettings environmentSettings, IVariableCollection variables, IGeneratedSymbolConfig generatedSymbolConfig)` method - evaluates the variables based on `generatedSymbolConfig` specified |
| 42 | +- `CreateConfig(IEngineEnvironmentSettings environmentSettings, IGeneratedSymbolConfig generatedSymbolConfig)` method - creates macro-specific configuration from `generatedSymbolConfig`, that later on can be passed to `Evaluate` method |
| 43 | + |
| 44 | +You may want to derive your own implementation from `BaseGeneratedSymbolMacro<T>` base class that offers some common logic. Configuration of the macro may derive from `BaseMacroConfig` class, that already have some utilities to parse the JSON configuration. |
| 45 | +When using base class, you need to implement the following members: |
| 46 | +- `Id` and `Type` properties assigned to constant unique values |
| 47 | +- `Evaluate` method: the method should create new variables to `IVariableCollection variableCollection` using parsed `config` and existing variables. It is recommended to log errors and debug information using logger available from `environmentSettings`. If you need to access the file system, do so via `environmentSettings.Host.FileSystem` abstraction. |
| 48 | +- `CreateConfig` method - creates macro specific config from `IGeneratedSymbolConfig` config. |
| 49 | + |
| 50 | +The very basic implementation may be: |
| 51 | +```CSharp |
| 52 | + internal class HelloMacro : BaseGeneratedSymbolMacro<HelloMacroConfig> |
| 53 | + { |
| 54 | + public override string Type => "hello"; |
| 55 | + |
| 56 | + public override Guid Id { get; } = new Guid("342BC62F-8FED-4E5A-AB59-F9AB98030155"); |
| 57 | + |
| 58 | + public override void Evaluate(IEngineEnvironmentSettings environmentSettings, IVariableCollection variableCollection, HelloMacroConfig config) |
| 59 | + { |
| 60 | + string greetings = $"Hello {config.NameToGreet}!"; |
| 61 | + //set configured variable to Hello Name! |
| 62 | + variableCollection[config.VariableName] = greetings; |
| 63 | + environmentSettings.Host.Logger.LogDebug("[{macro}]: Variable '{var}' was assigned to value '{value}'.", nameof(HelloMacro), config.VariableName, greetings); |
| 64 | + } |
| 65 | + |
| 66 | + protected override HelloMacroConfig CreateConfig(IGeneratedSymbolConfig generatedSymbolConfig) => new(this, generatedSymbolConfig); |
| 67 | + } |
| 68 | + |
| 69 | + internal class HelloMacroConfig : BaseMacroConfig<HelloMacro, HelloMacroConfig> |
| 70 | + { |
| 71 | + internal HelloMacroConfig(HelloMacro macro, string variableName, string? dataType = null, string nameToGreet) : base(macro, variableName, dataType) |
| 72 | + { |
| 73 | + if (string.IsNullOrEmpty(nameToGreet)) |
| 74 | + { |
| 75 | + throw new ArgumentException($"'{nameof(nameToGreet)}' cannot be null or empty.", nameof(nameToGreet)); |
| 76 | + } |
| 77 | + |
| 78 | + NameToGreet = nameToGreet; |
| 79 | + } |
| 80 | + |
| 81 | + internal HelloMacroConfig(HelloMacro macro, IGeneratedSymbolConfig generatedSymbolConfig) |
| 82 | + : base(macro, generatedSymbolConfig.VariableName, generatedSymbolConfig.DataType) |
| 83 | + { |
| 84 | + NameToGreet = GetMandatoryParameterValue(generatedSymbolConfig, nameof(NameToGreet)); |
| 85 | + } |
| 86 | + |
| 87 | + internal string NameToGreet { get; } |
| 88 | + } |
| 89 | +``` |
| 90 | + |
| 91 | +`IGeneratedSymbolConfig` config already contains the pre-parsed JSON from template.json. It has properties for: symbol name, data type (if specified) and parameters collection. |
| 92 | +Parameters collection contains parameter key-value pairs from JSON. Note that value is in JSON format, i.e. if the parameter value is string, the it contains `"\"string-value\""`. |
| 93 | +It is recommend to get `JToken` using `JToken.Parse` on this value when parsing the value or use helper methods available in `BaseMacroConfig` that can parse the data. |
| 94 | + |
| 95 | +2. Once the macro is implemented, add it to [components collection](../../src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/Components.cs). |
| 96 | + |
| 97 | +3. [optional] Update [JSON schema](../../src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/Schemas/JSON/template.json) with new generated symbol syntax. |
| 98 | +If you do so, also add [a new test case](../../test/Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests/SchemaTests/GeneratorTest.json) for testing the syntax. |
| 99 | + |
| 100 | +4. Add unit tests for new implementation. Macro related unit tests are located in [this folder](../../test/Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests/MacroTests/). |
| 101 | +For more complete scenario, consider adding the full template generation tests to [`RunnableProjectGeneratorTests.cs`](../../test/Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests/RunnableProjectGeneratorTests.cs). |
| 102 | + |
| 103 | +5. Update documentation in [docs folder](../Available-Symbols-Generators.md). |
| 104 | + |
0 commit comments