Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Cli/Microsoft.TemplateEngine.Cli/Alias/AliasModel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Newtonsoft.Json;
using System.Text.Json.Serialization;

namespace Microsoft.TemplateEngine.Cli.Alias
{
Expand All @@ -17,7 +17,7 @@ internal AliasModel(IReadOnlyDictionary<string, IReadOnlyList<string>> commandAl
CommandAliases = new Dictionary<string, IReadOnlyList<string>>(commandAliases.ToDictionary(x => x.Key, x => x.Value), StringComparer.OrdinalIgnoreCase);
}

[JsonProperty]
[JsonInclude]
internal Dictionary<string, IReadOnlyList<string>> CommandAliases { get; set; }

internal void AddCommandAlias(string aliasName, IReadOnlyList<string> aliasTokens)
Expand Down
65 changes: 40 additions & 25 deletions src/Cli/Microsoft.TemplateEngine.Cli/Alias/AliasRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Utils;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace Microsoft.TemplateEngine.Cli.Alias
{
Expand Down Expand Up @@ -135,7 +136,7 @@ private void EnsureLoaded()
_aliases = new AliasModel();
return;
}
JObject parsed = _environmentSettings.Host.FileSystem.ReadObject(_aliasesFilePath);
JsonObject parsed = _environmentSettings.Host.FileSystem.ReadObject(_aliasesFilePath);
IReadOnlyDictionary<string, IReadOnlyList<string>> commandAliases = ToStringListDictionary(parsed, StringComparer.OrdinalIgnoreCase, "CommandAliases");

_aliases = new AliasModel(commandAliases);
Expand All @@ -145,7 +146,19 @@ private void Save()
{
if (_aliases is AliasModel { CommandAliases: { Count: > 0 } })
{
_environmentSettings.Host.FileSystem.WriteObject(_aliasesFilePath, _aliases);
JsonObject root = new();
JsonObject commandAliases = new();
foreach (var kvp in _aliases.CommandAliases)
{
JsonArray arr = new();
foreach (string item in kvp.Value)
{
arr.Add((JsonNode)JsonValue.Create(item)!);
}
commandAliases[kvp.Key] = arr;
}
root["CommandAliases"] = commandAliases;
_environmentSettings.Host.FileSystem.WriteObject(_aliasesFilePath, root);
}
else
{
Expand All @@ -154,50 +167,52 @@ private void Save()
}

// reads a dictionary whose values can either be string literals, or arrays of strings.
private IReadOnlyDictionary<string, IReadOnlyList<string>> ToStringListDictionary(JToken token, StringComparer? comparer = null, string? propertyName = null)
private IReadOnlyDictionary<string, IReadOnlyList<string>> ToStringListDictionary(JsonObject token, StringComparer? comparer = null, string? propertyName = null)
{
Dictionary<string, IReadOnlyList<string>> result = new(comparer ?? StringComparer.Ordinal);
JObject? jObj = token as JObject;
if (jObj == null || propertyName == null || !jObj.TryGetValue(propertyName, StringComparison.OrdinalIgnoreCase, out JToken? element))

if (propertyName == null)
{
return result;
}

jObj = element as JObject;
if (jObj == null)
// Case-insensitive property lookup for compatibility with Newtonsoft.Json behavior
JsonNode? element = null;
foreach (var prop in token)
{
if (string.Equals(prop.Key, propertyName, StringComparison.OrdinalIgnoreCase))
{
element = prop.Value;
break;
}
}

if (element is not JsonObject jObj)
{
return result;
}

foreach (JProperty property in jObj.Properties())
foreach (KeyValuePair<string, JsonNode?> property in jObj)
{
if (property.Value == null)
{
continue;
}
else if (property.Value.Type == JTokenType.String)
else if (property.Value.GetValueKind() == JsonValueKind.String)
{
result[property.Name] = new List<string>() { property.Value.ToString() };
result[property.Key] = new List<string>() { property.Value.GetValue<string>() };
}
else if (property.Value.Type == JTokenType.Array)
else if (property.Value is JsonArray arr)
{
JArray? arr = property.Value as JArray;
if (arr == null)
{
result[property.Name] = Array.Empty<string>();
}
else
List<string> values = new();
foreach (JsonNode? item in arr)
{
List<string> values = new();
foreach (JToken item in arr)
if (item != null && item.GetValueKind() == JsonValueKind.String)
{
if (item != null && item.Type == JTokenType.String)
{
values.Add(item.ToString());
}
values.Add(item.GetValue<string>());
}
result[property.Name] = values;
}
result[property.Key] = values;
}
}

Expand Down
18 changes: 11 additions & 7 deletions src/Cli/Microsoft.TemplateEngine.Cli/HostSpecificDataLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Logging;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.Mount;
using Microsoft.TemplateEngine.Edge.Settings;
using Microsoft.TemplateEngine.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.TemplateEngine.Cli
{
public class HostSpecificDataLoader : IHostSpecificDataLoader
{
private readonly IEngineEnvironmentSettings _engineEnvironment;

private static readonly JsonDocumentOptions s_jsonDocumentOptions = new()
{
CommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true
};

private readonly ConcurrentDictionary<ITemplateInfo, HostSpecificTemplateData> _cache =
new();

Expand All @@ -39,7 +45,7 @@ private HostSpecificTemplateData ReadHostSpecificTemplateDataUncached(ITemplateI
{
if (!string.IsNullOrWhiteSpace(hostData))
{
JObject jObject = JObject.Parse(hostData);
JsonObject? jObject = JsonNode.Parse(hostData, nodeOptions: null, s_jsonDocumentOptions)?.AsObject();
return new HostSpecificTemplateData(jObject);
Comment thread
NikolaMilosavljevic marked this conversation as resolved.
}
}
Expand All @@ -60,12 +66,10 @@ private HostSpecificTemplateData ReadHostSpecificTemplateDataUncached(ITemplateI
file = mountPoint.FileInfo(templateInfo.HostConfigPlace);
if (file != null && file.Exists)
{
JObject jsonData;
JsonObject? jsonData;
using (Stream stream = file.OpenRead())
using (TextReader textReader = new StreamReader(stream, true))
using (JsonReader jsonReader = new JsonTextReader(textReader))
{
jsonData = JObject.Load(jsonReader);
jsonData = JsonNode.Parse(stream, nodeOptions: null, s_jsonDocumentOptions)?.AsObject();
}
Comment thread
NikolaMilosavljevic marked this conversation as resolved.

return new HostSpecificTemplateData(jsonData);
Expand Down
100 changes: 77 additions & 23 deletions src/Cli/Microsoft.TemplateEngine.Cli/HostSpecificTemplateData.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace Microsoft.TemplateEngine.Cli
{
Expand All @@ -14,7 +15,7 @@ public class HostSpecificTemplateData
private const string ShortNameKey = "shortName";
private const string AlwaysShowKey = "alwaysShow";

internal HostSpecificTemplateData(JObject? jObject)
internal HostSpecificTemplateData(JsonObject? jObject)
{
var symbolsInfo = new Dictionary<string, IReadOnlyDictionary<string, string>>();

Expand All @@ -24,34 +25,63 @@ internal HostSpecificTemplateData(JObject? jObject)
return;
}

if (jObject.GetValue(nameof(UsageExamples), StringComparison.OrdinalIgnoreCase) is JArray usagesArray)
JsonNode? usagesNode = GetPropertyCaseInsensitive(jObject, nameof(UsageExamples));
if (usagesNode is JsonArray usagesArray)
{
UsageExamples = new List<string>(usagesArray.Values<string>().Where(v => v != null).OfType<string>());
UsageExamples = new List<string>(usagesArray
.Where(v => v != null && v.GetValueKind() == JsonValueKind.String)
.Select(v => v!.GetValue<string>()));
}

if (jObject.GetValue(nameof(SymbolInfo), StringComparison.OrdinalIgnoreCase) is JObject symbols)
JsonNode? symbolsNode = GetPropertyCaseInsensitive(jObject, nameof(SymbolInfo));
if (symbolsNode is JsonObject symbols)
{
foreach (var symbolInfo in symbols.Properties())
foreach (var symbolInfo in symbols)
{
if (!(symbolInfo.Value is JObject symbol))
if (symbolInfo.Value is not JsonObject symbol)
{
continue;
}

var symbolProperties = new Dictionary<string, string>();

foreach (var symbolProperty in symbol.Properties())
foreach (var symbolProperty in symbol)
{
symbolProperties[symbolProperty.Name] = symbolProperty.Value.Value<string>() ?? "";
if (symbolProperty.Value is null)
{
symbolProperties[symbolProperty.Key] = "";
}
else
{
var kind = symbolProperty.Value.GetValueKind();
symbolProperties[symbolProperty.Key] = kind switch
{
JsonValueKind.String => symbolProperty.Value.GetValue<string>(),
JsonValueKind.True => "true",
JsonValueKind.False => "false",
_ => symbolProperty.Value.ToJsonString()
};
}
}

symbolsInfo[symbolInfo.Name] = symbolProperties;
symbolsInfo[symbolInfo.Key] = symbolProperties;
}
}
SymbolInfo = symbolsInfo;

IsHidden = jObject.Value<bool>(nameof(IsHidden));

JsonNode? isHiddenNode = GetPropertyCaseInsensitive(jObject, nameof(IsHidden));
if (isHiddenNode != null)
{
var kind = isHiddenNode.GetValueKind();
if (kind == JsonValueKind.True)
{
IsHidden = true;
}
else if (kind == JsonValueKind.String && bool.TryParse(isHiddenNode.GetValue<string>(), out bool hidden))
{
IsHidden = hidden;
}
}
}

internal HostSpecificTemplateData(
Expand Down Expand Up @@ -144,7 +174,7 @@ public Dictionary<string, string> ShortNameOverrides
}
}

internal static HostSpecificTemplateData Default { get; } = new HostSpecificTemplateData((JObject?)null);
internal static HostSpecificTemplateData Default { get; } = new HostSpecificTemplateData((JsonObject?)null);

internal string DisplayNameForParameter(string parameterName)
{
Expand All @@ -157,26 +187,50 @@ internal string DisplayNameForParameter(string parameterName)
return parameterName;
}

private class HostSpecificTemplateDataJsonConverter : JsonConverter<HostSpecificTemplateData>
private static JsonNode? GetPropertyCaseInsensitive(JsonObject obj, string key)
{
public override HostSpecificTemplateData ReadJson(JsonReader reader, Type objectType, HostSpecificTemplateData? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException();
if (obj.TryGetPropertyValue(key, out JsonNode? result))
{
return result;
}

public override void WriteJson(JsonWriter writer, HostSpecificTemplateData? value, JsonSerializer serializer)
foreach (var kvp in obj)
{
if (value == null)
if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase))
{
return;
return kvp.Value;
}
}

return null;
}

private class HostSpecificTemplateDataJsonConverter : JsonConverter<HostSpecificTemplateData>
{
public override HostSpecificTemplateData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();

public override void Write(Utf8JsonWriter writer, HostSpecificTemplateData value, JsonSerializerOptions options)
{
writer.WriteStartObject();
if (value.IsHidden)
{
writer.WritePropertyName(nameof(IsHidden));
writer.WriteValue(value.IsHidden);
writer.WriteBoolean(nameof(IsHidden), value.IsHidden);
}
if (value.SymbolInfo.Any())
{
writer.WritePropertyName(nameof(SymbolInfo));
serializer.Serialize(writer, value.SymbolInfo);
writer.WriteStartObject();
foreach (var symbol in value.SymbolInfo)
{
writer.WritePropertyName(symbol.Key);
writer.WriteStartObject();
foreach (var prop in symbol.Value)
{
writer.WriteString(prop.Key, prop.Value);
}
writer.WriteEndObject();
}
writer.WriteEndObject();
}

if (value.UsageExamples != null && value.UsageExamples.Any(e => !string.IsNullOrWhiteSpace(e)))
Expand All @@ -187,7 +241,7 @@ public override void WriteJson(JsonWriter writer, HostSpecificTemplateData? valu
{
if (!string.IsNullOrWhiteSpace(example))
{
writer.WriteValue(example);
writer.WriteStringValue(example);
}
}
writer.WriteEndArray();
Expand Down
Loading
Loading