Skip to content

Commit

Permalink
OnConfigurationLoad (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
chullybun authored Mar 15, 2024
1 parent 39e5feb commit f9e19ad
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Represents the **NuGet** versions.

## v2.2.0
- *Enhancement:* Added `ConeGenerator.OnConfigurationLoad` to enable validation and/or mutation of the configuration (`JsonNode`) before deserialization into the corresponding `ConfigBase` type.

## v2.1.0
- *Enhancement:* Added `CodeGenerator.LoadConfigAsync` to enable the loading of the configuration without having to execute the code generation. This is useful when needing to either further validate the configuration prior to execution, or be able to query the configuration without initiating the code generation.
- *Fixed:* All dependencies updated to the latest version.
Expand Down
40 changes: 36 additions & 4 deletions src/OnRamp/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

namespace OnRamp
Expand All @@ -19,6 +21,11 @@ namespace OnRamp
/// </summary>
public class CodeGenerator
{
/// <summary>
/// Represents the file name when loaded from a stream; i.e. actual file name is unknown.
/// </summary>
public const string StreamFileName = "<stream>";

/// <summary>
/// Create a new instance of the <see cref="CodeGenerator"/> class.
/// </summary>
Expand Down Expand Up @@ -152,22 +159,36 @@ public async Task<ConfigBase> LoadConfigAsync(string? configFileName = null)
/// <returns>The resultant <see cref="CodeGenStatistics"/>.</returns>
/// <exception cref="CodeGenException">Thrown when an error is encountered during the code-generation.</exception>
/// <exception cref="CodeGenChangesFoundException">Thrown where the code-generation would result in changes to an underlying artefact. This is managed by setting <see cref="ICodeGeneratorArgs.ExpectNoChanges"/> to <c>true</c>.</exception>
public async Task<ConfigBase> LoadConfigAsync(TextReader configReader, StreamContentType contentType, string configFileName = "<stream>")
public async Task<ConfigBase> LoadConfigAsync(TextReader configReader, StreamContentType contentType, string configFileName = StreamFileName)
{
ConfigBase? config;
IRootConfig rootConfig;

// Load, validate and prepare.
try
{
JsonNode? jsonNode;

// Read the YAML/JSON into a JsonNode.
try
{
config = contentType switch
jsonNode = contentType switch
{
StreamContentType.Yaml => (ConfigBase?)configReader.DeserializeYaml(Scripts.GetConfigType()),
StreamContentType.Json => (ConfigBase?)configReader.DeserializeJson(Scripts.GetConfigType()),
StreamContentType.Yaml => configReader.YamlToJsonNode(),
StreamContentType.Json => configReader.JsonToJsonNode(),
_ => throw new CodeGenException($"Stream content type is not supported.")
} ?? throw new CodeGenException($"Stream is empty.");

// Verify and mutate the configuration before deserialization.
OnConfigurationLoad(Scripts, configFileName, jsonNode);
}
catch (CodeGenException) { throw; }
catch (Exception ex) { throw new CodeGenException(ex.Message); }

// Deserialize the JsonNode in to the configured (script) type.
try
{
config = (ConfigBase?)jsonNode.Deserialize(Scripts.GetConfigType(), OnRamp.Utility.JsonSerializer.Options);
}
catch (CodeGenException) { throw; }
catch (Exception ex) { throw new CodeGenException(ex.Message); }
Expand Down Expand Up @@ -232,6 +253,9 @@ public Task<CodeGenStatistics> GenerateAsync(ConfigBase config)
if (config is not IRootConfig rootConfig)
throw new ArgumentException("Configuration must implement IRootConfig.", nameof(config));

CodeGenArgs.Logger?.LogInformation("{Content}", string.Empty);
CodeGenArgs.Logger?.LogInformation("{Content}", "Scripts:");

// Generate the scripted artefacts.
var overallStopwatch = Stopwatch.StartNew();
var overallStats = new CodeGenStatistics();
Expand Down Expand Up @@ -262,6 +286,14 @@ public Task<CodeGenStatistics> GenerateAsync(ConfigBase config)
return Task.FromResult(overallStats);
}

/// <summary>
/// Provides an opportunity to verify and manipulate the mutable <i>configuration</i> <see cref="JsonNode"/> as it is loaded; directly before it is deserialized into the configured <see cref="ConfigBase"/> as defined by the <see cref="CodeGenScript.ConfigType"/>.
/// </summary>
/// <param name="script">The <see cref="CodeGenScript"/>.</param>
/// <param name="fileName">The configuration file name.</param>
/// <param name="json">The <see cref="JsonNode"/>.</param>
protected virtual void OnConfigurationLoad(CodeGenScript script, string fileName, JsonNode json) { }

/// <summary>
/// Handles the processing before the <paramref name="script"/> is executed.
/// </summary>
Expand Down
3 changes: 0 additions & 3 deletions src/OnRamp/Console/CodeGenConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,6 @@ public static void WriteStandardizedArgs(ICodeGeneratorArgs args)
{
args.Logger.LogInformation("{Content}", $" {a.FullName}");
}

args.Logger.LogInformation("{Content}", string.Empty);
args.Logger.LogInformation("{Content}", "Scripts:");
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/OnRamp/OnRamp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>netstandard2.1</TargetFramework>
<RootNamespace>OnRamp</RootNamespace>
<Version>2.1.0</Version>
<Version>2.2.0</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
35 changes: 34 additions & 1 deletion src/OnRamp/Utility/StreamExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json.Nodes;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;

Expand Down Expand Up @@ -60,9 +61,41 @@ public static class TextReaderExtensions
return DeserializeJson(sr, type);
}
}
#pragma warning restore IDE0063
#pragma warning restore IDE0063
}

/// <summary>
/// Converts the <paramref name="yaml"/> <see cref="TextReader"/> content into a <see cref="JsonNode"/>.
/// </summary>
/// <param name="yaml">The YAML <see cref="TextReader"/>.</param>
/// <returns>The <see cref="JsonNode"/>.</returns>
public static JsonNode? YamlToJsonNode(this TextReader yaml)
{
var yml = new DeserializerBuilder().WithNodeTypeResolver(new YamlNodeTypeResolver()).Build().Deserialize(yaml);

#pragma warning disable IDE0063 // Use simple 'using' statement; cannot as need to be more explicit with managing the close and dispose.
using (var ms = new MemoryStream())
{
using (var sw = new StreamWriter(ms))
{
sw.Write(new SerializerBuilder().JsonCompatible().Build().Serialize(yml!));
sw.Flush();

ms.Position = 0;
using var sr = new StreamReader(ms);
return sr.JsonToJsonNode();
}
}
#pragma warning restore IDE0063
}

/// <summary>
/// Converts the <paramref name="json"/> <see cref="TextReader"/> content into a <see cref="JsonNode"/>.
/// </summary>
/// <param name="json">The YAML <see cref="TextReader"/>.</param>
/// <returns>The <see cref="JsonNode"/>.</returns>
public static JsonNode? JsonToJsonNode(this TextReader json) => JsonNode.Parse(json.ReadToEnd());

private class YamlNodeTypeResolver : INodeTypeResolver
{
private static readonly string[] boolValues = ["true", "false"];
Expand Down

0 comments on commit f9e19ad

Please sign in to comment.