Skip to content

Commit

Permalink
refactoring bits from next macro generation without API changes
Browse files Browse the repository at this point in the history
  • Loading branch information
vlada-shubina committed Nov 9, 2022
1 parent 7e0fb4b commit 6f12b25
Show file tree
Hide file tree
Showing 48 changed files with 801 additions and 203 deletions.
3 changes: 2 additions & 1 deletion docs/Available-Symbols-Generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ In this sample three symbols are defined:


## Coalesce
Behaves like the C# `??` operator.
Behaves like the C# `??` operator. Note: the empty string value and default value of value type is treated as `null`.
The typical use of this generator is to check if the parameter was provided by user, otherwise set fallback generated value.

#### Parameters

Expand Down
4 changes: 4 additions & 0 deletions docs/Contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@ We follow the same versioning as https://github.com/dotnet/sdk and release branc
| Development | *main* |
| Release | *release/** |

# Ways to contribute

- [create new value form](./contributing/how-to-create-new-value-form.md)

[Top](#top)
65 changes: 65 additions & 0 deletions docs/contributing/how-to-create-new-value-form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# How to create a new value form

Available value forms are described [here](../Runnable-Project-Templates---Value-Forms.md).
Value form represent the form of the parameter, usually related to specific casing.

We appreciate creating new value forms by the community.

Value forms follow the following syntax:
```json
{
"forms":
{
"nameOfTheForm":
{
"identifier": "name-unique", //type of value form; should be unique value
"param1": "value1", //key-value parameters for the form. The value may be JSON array or object, if more complicated configuration is needed.
"param2": "value2"
}
}
}
```

To create new value form, follow the following guideline:

1. Implement [`Microsoft.TemplateEngine.Orchestrator.RunnableProjects.ValueForms.IValueFormFactory`](../../src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/ValueForms/IValueFormFactory.cs) interface.

The existing implementation of value form are located in [`ValueForms`](../../src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/ValueForms) folder of `Microsoft.TemplateEngine.Orchestrator.RunnableProjects` projects.

The implementation should have:
- `Identifier` property - unique identifier of the form
- `Type` property - unique `string` name, matching generated symbol type
- `Create(string? name = null)` method - creates the form for default configuration
- `FromJObject(string name, JObject? configuration = null)` method - creates the form for json configuration

The `IValueForm` has `Identifier` property that matches factory `Identifier`, `Name` corresponding the name of the form in JSON and `Process` method that create the form for the passed `value`.

You may want to derive your own implementation from one of the base classes:
- `BaseValueFormFactory` - basic features
- `ActionableValueFormFactory`- the form that just does the action without configuration
- `ConfigurableValueFormFactory`- the form that performs the action and based on configuration
- `DependantValueFormFactory` - the form that needs other forms for processing

The very basic implementation may be:
```CSharp
internal class HelloValueForm : ActionableValueFormFactory
{
internal const string FormIdentifier = "hello";

public HelloValueForm() : base(FormIdentifier)
{
}

protected override string Process(string value) => $"Hello {value}!";
}
```

2. Once the value form is implemented, add it to [value form collection](../../src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/ValueFormRegistry.cs).

3. [optional] Update [JSON schema](../../src/Microsoft.TemplateEngine.Orchestrator.RunnableProjects/Schemas/JSON/template.json) with new value form syntax.
If you do so, also add [a new test case](../../test/Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests/SchemaTests/) for testing the syntax.

4. Add unit tests for new implementation. Macro related unit tests are located in [this folder](../../test/Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests/ValueFormTests/).
For more complete scenario, consider adding the full template generation tests to [`RunnableProjectGeneratorTests.cs`](../../test/Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests/RunnableProjectGeneratorTests.cs).

5. Update documentation in [docs folder](../Runnable-Project-Templates---Value-Forms.md).
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.TemplateEngine.Core.Expressions.Cpp2
{
public class Cpp2StyleEvaluatorDefinition : SharedEvaluatorDefinition<Cpp2StyleEvaluatorDefinition, Cpp2StyleEvaluatorDefinition.Tokens>
{
private static readonly Dictionary<Encoding, ITokenTrie> TokenCache = new Dictionary<Encoding, ITokenTrie>();
private static readonly Dictionary<Encoding, ITokenTrie> TokenCache = new();

public enum Tokens
{
Expand Down Expand Up @@ -80,7 +80,7 @@ protected override ITokenTrie GetSymbols(IProcessorState processor)
{
if (!TokenCache.TryGetValue(processor.Encoding, out ITokenTrie tokens))
{
TokenTrie trie = new TokenTrie();
TokenTrie trie = new();

//Logic
trie.AddToken(processor.Encoding.GetBytes("&&"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public abstract class SharedEvaluatorDefinition<TSelf, TTokens>
where TSelf : SharedEvaluatorDefinition<TSelf, TTokens>, new()
where TTokens : struct
{
private static readonly TSelf Instance = new TSelf();
private static readonly TSelf Instance = new();
private static readonly IOperatorMap<Operators, TTokens> Map = Instance.GenerateMap();
private static readonly bool DereferenceInLiteralsSetting = Instance.DereferenceInLiterals;
private static readonly string NullToken = Instance.NullTokenValue;
Expand Down Expand Up @@ -90,10 +90,10 @@ public static bool EvaluateFromString(ILogger logger, string text, IVariableColl
/// <returns></returns>
public static bool EvaluateFromString(ILogger logger, string text, IVariableCollection variables, out string faultedMessage, HashSet<string> referencedVariablesKeys = null)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(text)))
using (MemoryStream res = new MemoryStream())
using (MemoryStream ms = new(Encoding.UTF8.GetBytes(text)))
using (MemoryStream res = new())
{
EngineConfig cfg = new EngineConfig(logger, variables);
EngineConfig cfg = new(logger, variables);
IProcessorState state = new ProcessorState(ms, res, (int)ms.Length, (int)ms.Length, cfg, NoOperationProviders);
int len = (int)ms.Length;
int pos = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class DerivedSymbol : BaseValueSymbol
{
internal const string TypeName = "derived";

internal DerivedSymbol(string name, string valueTransform, string valueSource) : base(name, null)
internal DerivedSymbol(string name, string valueTransform, string valueSource, string? replaces = null) : base(name, replaces)
{
if (string.IsNullOrWhiteSpace(valueTransform))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ internal GeneratedSymbol(string name, string generator) : base(name, null)
{
throw new ArgumentException($"'{nameof(generator)}' cannot be null or whitespace.", nameof(generator));
}

Generator = generator;
}

internal GeneratedSymbol(string name, string generator, IReadOnlyDictionary<string, string> parameters, string? dataType = null) : this(name, generator)
{
DataType = dataType;
Parameters = parameters;
}

internal GeneratedSymbol(string name, JObject jObject) : base(jObject, name)
{
string? generator = jObject.ToString(nameof(Generator));
if (string.IsNullOrWhiteSpace(generator))
{
throw new TemplateAuthoringException(string.Format(LocalizableStrings.SymbolModel_Error_MandatoryPropertyMissing, name, GeneratedSymbol.TypeName, nameof(Generator).ToLowerInvariant()), name);
throw new TemplateAuthoringException(string.Format(LocalizableStrings.SymbolModel_Error_MandatoryPropertyMissing, name, TypeName, nameof(Generator).ToLowerInvariant()), name);
}

Generator = generator!;
DataType = jObject.ToString(nameof(DataType));
Parameters = jObject.ToJTokenStringDictionary(StringComparer.Ordinal, nameof(Parameters));
Parameters = jObject.ToJTokenStringDictionary(StringComparer.OrdinalIgnoreCase, nameof(Parameters));
}

/// <inheritdoc/>
Expand All @@ -56,6 +61,6 @@ internal GeneratedSymbol(string name, JObject jObject) : base(jObject, name)
/// - the key is a parameter name
/// - the value is a JSON value.
/// </summary>
public IReadOnlyDictionary<string, string> Parameters { get; internal init; } = new Dictionary<string, string>();
public IReadOnlyDictionary<string, string> Parameters { get; internal init; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.TemplateEngine.Orchestrator.RunnableProjects.ConfigModel
{
public class SymbolValueFormsModel
{
private SymbolValueFormsModel(IReadOnlyList<string> globalForms)
internal SymbolValueFormsModel(IReadOnlyList<string> globalForms)
{
GlobalForms = globalForms;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ public sealed class TemplateConfigModel
{
private const string NameSymbolName = "name";
private readonly ILogger? _logger;
private readonly Dictionary<string, IValueForm> _forms = SetupValueFormMapForTemplate();
private IReadOnlyDictionary<string, string> _tags = new Dictionary<string, string>();
private Dictionary<string, BaseSymbol> _symbols = new Dictionary<string, BaseSymbol>();
private Dictionary<string, BaseSymbol> _symbols = new();
private IReadOnlyList<PostActionModel> _postActions = new List<PostActionModel>();
private string? _author;
private string? _name;
Expand Down Expand Up @@ -141,7 +142,7 @@ private TemplateConfigModel(JObject source, ILogger? logger, string? baselineNam
}
}
}
foreach (var symbol in ImplicitBindSymbols)
foreach (BindSymbol symbol in ImplicitBindSymbols)
{
if (!symbols.ContainsKey(symbol.Name))
{
Expand All @@ -161,7 +162,7 @@ private TemplateConfigModel(JObject source, ILogger? logger, string? baselineNam

// Custom operations for specials
IReadOnlyDictionary<string, JToken> allSpecialOpsConfig = source.ToJTokenDictionary(StringComparer.OrdinalIgnoreCase, nameof(SpecialCustomOperations));
List<CustomFileGlobModel> specialCustomSetup = new List<CustomFileGlobModel>();
List<CustomFileGlobModel> specialCustomSetup = new();

foreach (KeyValuePair<string, JToken> globConfigKeyValue in allSpecialOpsConfig)
{
Expand All @@ -174,7 +175,7 @@ private TemplateConfigModel(JObject source, ILogger? logger, string? baselineNam

SpecialCustomOperations = specialCustomSetup;

List<TemplateConstraintInfo> constraints = new List<TemplateConstraintInfo>();
List<TemplateConstraintInfo> constraints = new();
foreach (JProperty prop in source.PropertiesOf(nameof(Constraints)))
{
if (prop.Value is not JObject obj)
Expand All @@ -190,7 +191,7 @@ private TemplateConfigModel(JObject source, ILogger? logger, string? baselineNam
continue;
}
obj.TryGetValue(nameof(TemplateConstraintInfo.Args), StringComparison.OrdinalIgnoreCase, out JToken? args);
constraints.Add(new TemplateConstraintInfo(type!, args.ToJSONString()));
constraints.Add(new TemplateConstraintInfo(type!, args?.ToString(Formatting.None)));
}
Constraints = constraints;
}
Expand Down Expand Up @@ -343,7 +344,7 @@ internal init
{
_symbols = value.ToDictionary(s => s.Name, s => s);
_symbols[NameSymbolName] = NameSymbol;
foreach (var symbol in ImplicitBindSymbols)
foreach (BindSymbol symbol in ImplicitBindSymbols)
{
if (!_symbols.ContainsKey(symbol.Name))
{
Expand All @@ -356,7 +357,17 @@ internal init
/// <summary>
/// Gets the list of forms defined in the template ("forms" JSON property).
/// </summary>
public IReadOnlyDictionary<string, IValueForm> Forms { get; internal init; } = new Dictionary<string, IValueForm>();
public IReadOnlyDictionary<string, IValueForm> Forms
{
get => _forms;
internal init
{
foreach (KeyValuePair<string, IValueForm> kvp in value)
{
_forms[kvp.Key] = kvp.Value;
}
}
}

/// <summary>
/// Gets the placeholder filename defined in the template ("placeholderFilename" JSON property).
Expand Down Expand Up @@ -494,9 +505,9 @@ private static BaseSymbol SetupDefaultNameSymbol(string? sourceName)
};
}

private static IReadOnlyDictionary<string, IValueForm> SetupValueFormMapForTemplate(JObject source)
private static Dictionary<string, IValueForm> SetupValueFormMapForTemplate(JObject? source = null)
{
Dictionary<string, IValueForm> formMap = new Dictionary<string, IValueForm>(StringComparer.Ordinal);
Dictionary<string, IValueForm> formMap = new(StringComparer.Ordinal);

// setup all the built-in default forms.
// name of the form is form identifier
Expand All @@ -506,6 +517,11 @@ private static IReadOnlyDictionary<string, IValueForm> SetupValueFormMapForTempl
formMap[builtInForm.Key] = builtInForm.Value.Create();
}

if (source == null)
{
return formMap;
}

// setup the forms defined by the template configuration.
// if any have the same name as a default, the default is overridden.
IReadOnlyDictionary<string, JToken> templateDefinedforms = source.ToJTokenDictionary(StringComparer.OrdinalIgnoreCase, nameof(Forms));
Expand All @@ -517,13 +533,12 @@ private static IReadOnlyDictionary<string, IValueForm> SetupValueFormMapForTempl
formMap[form.Key] = ValueFormRegistry.GetForm(form.Key, o);
}
}

return formMap;
}

private static IReadOnlyDictionary<string, IBaselineInfo> BaselineInfoFromJObject(IEnumerable<JProperty> baselineJProperties)
{
Dictionary<string, IBaselineInfo> allBaselines = new Dictionary<string, IBaselineInfo>();
Dictionary<string, IBaselineInfo> allBaselines = new();

foreach (JProperty property in baselineJProperties)
{
Expand All @@ -532,8 +547,8 @@ private static IReadOnlyDictionary<string, IBaselineInfo> BaselineInfoFromJObjec
continue;
}

var defaultOverrides = obj.Get<JObject>(nameof(Utils.BaselineInfo.DefaultOverrides))?.ToStringDictionary() ?? new Dictionary<string, string>();
BaselineInfo baseline = new BaselineInfo(defaultOverrides, obj.ToString(nameof(baseline.Description)));
IReadOnlyDictionary<string, string>? defaultOverrides = obj.Get<JObject>(nameof(Utils.BaselineInfo.DefaultOverrides))?.ToStringDictionary() ?? new Dictionary<string, string>();
BaselineInfo baseline = new(defaultOverrides, obj.ToString(nameof(baseline.Description)));
allBaselines[property.Name] = baseline;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ internal static EvaluatorType ParseEvaluatorName(string? name, EvaluatorType? @d
"C++" => EvaluatorType.CPP,
"MSBUILD" => EvaluatorType.MSBuild,
"VB" => EvaluatorType.VB,
_ => throw new TemplateAuthoringException($"Unrecognized evaluator: '{evaluatorName}'.", evaluatorName),
_ => throw new TemplateAuthoringException(string.Format(LocalizableStrings.EvaluatorSelector_Exception_UnknownEvaluator, evaluatorName)),
};
return evaluator;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

namespace Microsoft.TemplateEngine.Orchestrator.RunnableProjects
{
internal class GlobalRunConfig : IGlobalRunConfig
internal class GlobalRunConfig
{
public IReadOnlyList<IOperationProvider> Operations { get; init; } = Array.Empty<IOperationProvider>();

public IVariableConfig VariableSetup { get; init; } = VariableConfig.DefaultVariableSetup();

public IReadOnlyList<IMacroConfig> Macros { get; init; } = Array.Empty<IMacroConfig>();
public IReadOnlyList<IMacroConfig> GeneratedSymbolMacros { get; init; } = Array.Empty<IMacroConfig>();

public IReadOnlyList<IMacroConfig> ComputedMacros { get; init; } = Array.Empty<IMacroConfig>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ internal GlobalRunSpec(

if (configuration.SpecialOperationConfig != null)
{
foreach ((string glob, IGlobalRunConfig runConfig) in configuration.SpecialOperationConfig)
foreach ((string glob, GlobalRunConfig runConfig) in configuration.SpecialOperationConfig)
{
IReadOnlyList<IOperationProvider> specialOps = Array.Empty<IOperationProvider>();

Expand Down Expand Up @@ -94,7 +94,7 @@ internal void SetupFileSource(FileSourceMatchInfo source)
// If there are custom Conditional operations, don't include the default Conditionals.
//
// Note: we may need a more robust filtering mechanism in the future.
private IReadOnlyList<IOperationProvider> ResolveOperations(IGlobalRunConfig runConfig, IDirectory templateRoot, IVariableCollection variables)
private IReadOnlyList<IOperationProvider> ResolveOperations(GlobalRunConfig runConfig, IDirectory templateRoot, IVariableCollection variables)
{
IReadOnlyList<IOperationProvider> customOperations = SetupCustomOperations(runConfig.CustomOperations, templateRoot, variables);
IReadOnlyList<IOperationProvider> defaultOperations = SetupOperations(templateRoot.MountPoint.EnvironmentSettings, variables, runConfig);
Expand All @@ -113,7 +113,7 @@ private IReadOnlyList<IOperationProvider> ResolveOperations(IGlobalRunConfig run
return operations;
}

private IReadOnlyList<IOperationProvider> SetupOperations(IEngineEnvironmentSettings environmentSettings, IVariableCollection variables, IGlobalRunConfig runConfig)
private IReadOnlyList<IOperationProvider> SetupOperations(IEngineEnvironmentSettings environmentSettings, IVariableCollection variables, GlobalRunConfig runConfig)
{
// default operations
List<IOperationProvider> operations = new List<IOperationProvider>();
Expand Down

This file was deleted.

Loading

0 comments on commit 6f12b25

Please sign in to comment.