diff --git a/src/Nox.Cli.Abstractions/Helpers/YamlHelper.cs b/src/Nox.Cli.Abstractions/Helpers/YamlHelper.cs index a9fb1ce..4f5c433 100644 --- a/src/Nox.Cli.Abstractions/Helpers/YamlHelper.cs +++ b/src/Nox.Cli.Abstractions/Helpers/YamlHelper.cs @@ -5,7 +5,7 @@ namespace Nox.Cli.Abstractions.Helpers; public static class YamlHelper { - private static readonly Regex _referenceRegex = new(@"\$ref\S*:\s*(?[\w:\.\/\\]+\b[\w\-\.\/]+)\s*", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(5)); + private static readonly Regex _referenceRegex = new(@"(?[\w:\.\/\\]+\b[\w\-\.\/]+)\s*)", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(5)); /// /// Resolve $ref <path> tags in a yaml source yaml file.
@@ -22,7 +22,7 @@ public static string ResolveYamlReferences(string path) var sourcePath = Path.GetDirectoryName(sourceFullPath); var sourceLines = File.ReadAllLines(path); - var outputLines = ResolveYamlReferences(sourceLines.ToList(), sourcePath!).Result; + var outputLines = ResolveYamlReferences(sourceLines.ToList(), sourcePath!, Path.GetFileName(path)).Result; return string.Join('\n', outputLines.ToArray()); } @@ -42,12 +42,12 @@ public static async Task ResolveYamlReferencesAsync(string path) var sourcePath = Path.GetDirectoryName(sourceFullPath); var sourceLines = await File.ReadAllLinesAsync(path); - var outputLines = await ResolveYamlReferences(sourceLines.ToList(), sourcePath!); + var outputLines = await ResolveYamlReferences(sourceLines.ToList(), sourcePath!, Path.GetFileName(path)); return string.Join('\n', outputLines.ToArray()); } - private static async Task> ResolveYamlReferences(List sourceLines, string path) + private static async Task> ResolveYamlReferences(List sourceLines, string path, string parentFile) { var outputLines = new List(); foreach (var sourceLine in sourceLines) @@ -58,9 +58,9 @@ private static async Task> ResolveYamlReferences(List sourc if (match.Success) { var padding = new string(' ', match.Index); - var childPath = match.Groups[1].Value; + var childPath = match.Groups[^1].Value; if (!Path.IsPathRooted(childPath)) childPath = Path.Combine(path!, childPath); - if (!File.Exists(childPath)) throw new NoxCliException($"Referenced yaml file does not exist for reference: {match.Groups[1].Value}"); + if (!File.Exists(childPath)) throw new NoxCliException($"Referenced yaml file does not exist for reference: {match.Groups[1].Value} in file: {parentFile}"); var childLines = await File.ReadAllLinesAsync(childPath); foreach (var childLine in childLines) { @@ -78,9 +78,9 @@ private static async Task> ResolveYamlReferences(List sourc } } - if (outputLines.Any(ol => ol.Contains("$ref:") && !ol.TrimStart().StartsWith('#'))) + if (outputLines.Any(ol => ol.Contains("$ref:") && !ol.Contains("\"$ref:") && !ol.TrimStart().StartsWith('#'))) { - outputLines = await ResolveYamlReferences(outputLines, path); + outputLines = await ResolveYamlReferences(outputLines, path, parentFile); } return outputLines; diff --git a/src/Nox.Cli.Caching/NoxCliCacheBuilder.cs b/src/Nox.Cli.Caching/NoxCliCacheBuilder.cs index 7592379..73ed86d 100755 --- a/src/Nox.Cli.Caching/NoxCliCacheBuilder.cs +++ b/src/Nox.Cli.Caching/NoxCliCacheBuilder.cs @@ -7,9 +7,9 @@ public class NoxCliCacheBuilder { private readonly NoxCliCacheManager _manager; - public NoxCliCacheBuilder(string remoteUrl, IPersistedTokenCache? tokenCache = null) + public NoxCliCacheBuilder(string remoteUrl, bool forceOffline, IPersistedTokenCache? tokenCache = null) { - _manager = new NoxCliCacheManager(remoteUrl, tokenCache); + _manager = new NoxCliCacheManager(remoteUrl, forceOffline, tokenCache); } public NoxCliCacheBuilder ForServer() diff --git a/src/Nox.Cli.Caching/NoxCliCacheManager.cs b/src/Nox.Cli.Caching/NoxCliCacheManager.cs index 259c41c..aebffca 100755 --- a/src/Nox.Cli.Caching/NoxCliCacheManager.cs +++ b/src/Nox.Cli.Caching/NoxCliCacheManager.cs @@ -27,6 +27,7 @@ public class NoxCliCacheManager: INoxCliCacheManager private string _workflowCachePath; private string _templateCachePath; private string _localWorkflowPath; + private bool _forceOffline; private bool _isServer; private readonly Uri _remoteUri; private Uri? _workflowUri; @@ -37,6 +38,29 @@ public class NoxCliCacheManager: INoxCliCacheManager private IDeserializer _deserializer; private string? _tenantId; + public NoxCliCacheManager(string? remoteUrl, bool forceOffline, IPersistedTokenCache? tokenCache = null) + { + _buildLog = new List(); + if (string.IsNullOrEmpty(remoteUrl)) + { + _remoteUri = new Uri("https://noxorg.dev"); + } + else + { + _remoteUri = new Uri(remoteUrl); + } + + _forceOffline = forceOffline; + _cachePath = WellKnownPaths.CachePath; + _workflowCachePath = WellKnownPaths.WorkflowsCachePath; + _templateCachePath = WellKnownPaths.TemplatesCachePath; + Directory.CreateDirectory(_cachePath); + _cacheFile = WellKnownPaths.CacheFile; + _localWorkflowPath = "."; + _tokenCache = tokenCache; + _deserializer = BuildDeserializer(); + } + internal void ForServer() { _isServer = true; @@ -73,6 +97,7 @@ internal void AddBuildEventHandler(EventHandler han public bool IsOnline { get { + if (_forceOffline) return false; if (_remoteUri.Host == "localhost") return true; return PingHelper.ServicePing(_remoteUri.Host); } @@ -138,32 +163,6 @@ public void RefreshTemplate(string name) } } } - else - { - throw new NoxCliException($"Unable to communicate with the online cache."); - } - } - - public NoxCliCacheManager(string? remoteUrl, IPersistedTokenCache? tokenCache = null) - { - _buildLog = new List(); - if (string.IsNullOrEmpty(remoteUrl)) - { - _remoteUri = new Uri("https://noxorg.dev"); - } - else - { - _remoteUri = new Uri(remoteUrl); - } - - _cachePath = WellKnownPaths.CachePath; - _workflowCachePath = WellKnownPaths.WorkflowsCachePath; - _templateCachePath = WellKnownPaths.TemplatesCachePath; - Directory.CreateDirectory(_cachePath); - _cacheFile = WellKnownPaths.CacheFile; - _localWorkflowPath = "."; - _tokenCache = tokenCache; - _deserializer = BuildDeserializer(); } internal INoxCliCacheManager Build() @@ -494,9 +493,12 @@ private string[] FindWorkflowsAndManifest(string searchPath = "") private FileInfo[] GetFilesWithSearchPatterns(DirectoryInfo path, string[] searchPatterns, SearchOption searchOption) { var files = new List(); - foreach (var pattern in searchPatterns) + if (path.Exists) { - files.AddRange( path.GetFiles(pattern, searchOption) ); + foreach (var pattern in searchPatterns) + { + files.AddRange( path.GetFiles(pattern, searchOption) ); + } } return files.ToArray(); } diff --git a/src/Nox.Cli.Helpers/YamlCleaner.cs b/src/Nox.Cli.Helpers/YamlCleaner.cs index edf58b6..ecffac3 100644 --- a/src/Nox.Cli.Helpers/YamlCleaner.cs +++ b/src/Nox.Cli.Helpers/YamlCleaner.cs @@ -1,3 +1,4 @@ +using System.CodeDom.Compiler; using System.Text; using YamlDotNet.Core; using YamlDotNet.RepresentationModel; @@ -13,11 +14,10 @@ public static StringBuilder RemoveEmptyNodes(StringBuilder sourceYaml) yaml.Load(input); var root = (YamlMappingNode)yaml.Documents[0].RootNode; RemoveEmptyChildren(root); - var yamlDoc = new YamlDocument(root); - var yamlStream = new YamlStream(yamlDoc); var sb = new StringBuilder(); var writer = new StringWriter(sb); - yamlStream.Save(writer, false); + var textWriter = new IndentedTextWriter(writer, " "); + yaml.Save(textWriter, false); writer.Flush(); writer.Close(); return sb; diff --git a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/ConsolePromptSchema_v1.cs b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/ConsolePromptSchema_v1.cs index 8d88618..bfabfce 100755 --- a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/ConsolePromptSchema_v1.cs +++ b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/ConsolePromptSchema_v1.cs @@ -114,6 +114,8 @@ public NoxActionMetaData Discover() private readonly RestClient _client = new(); private readonly Regex _resolveRefs = new("\"\\$ref\\\"\\s*:\\s*\\\"(?[\\w:/\\.]+)\\\"", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private readonly Regex _yamlVariableRegex = new(@"\$\{\{\s*(yaml)\.(?\b[\w\-_:]+)\s*\}\}", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); private readonly Dictionary _responses = new(); @@ -199,7 +201,7 @@ public async Task> ProcessAsync(INoxWorkflowContext if (_fileOptions != null && _fileOptions.ContainsKey("filename")) { - _yaml = YamlCleaner.RemoveEmptyNodes(_yaml); + //_yaml = YamlCleaner.RemoveEmptyNodes(_yaml); _yaml.Insert(0, Environment.NewLine); @@ -297,6 +299,7 @@ private async Task ProcessSchema(JsonSchema.JsonSchema schema, string rootKey = { var newKey = $"{rootKey}.{key}".TrimStart('.'); + var prefix = $"[grey]{newKey.PadRight(40, '.').EscapeMarkup()}[/] "; var yamlSpacing = new string(' ', newKey.Count(d => d == '.') * 2); @@ -547,7 +550,17 @@ private void AppendKey(string yamlSpacing, string key) { if (_defaults?.ContainsKey(key) ?? false) { - return (T)Convert.ChangeType(_defaults[key], typeof(T)); + var defaultValue = _defaults[key]; + var match = _yamlVariableRegex.Match(defaultValue.ToString()!); + if (match.Success) + { + var variableValue = _responses[match.Groups["variable"].ToString()].ToString(); + return (T)Convert.ChangeType(defaultValue.ToString()!.Replace(match.Groups[0].ToString(), variableValue), typeof(T)); + } + else + { + return (T)Convert.ChangeType(defaultValue, typeof(T)); + } } return default; @@ -555,34 +568,62 @@ private void AppendKey(string yamlSpacing, string key) private void ProcessDefaults(string key, string yamlSpacing, string yamlSpacingPostfix) { - Regex defaultArrayRegex = new(@"\[(.*?)\]\.(.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); + var processor = new DefaultsProcessor(); + foreach (var defaultEntry in _defaults!.Where(d => d.Key.StartsWith(key, StringComparison.CurrentCultureIgnoreCase))) + { + processor.Process(defaultEntry); + } - _yaml.AppendLine($"{yamlSpacing}{key}:"); - var arrayIndex = -1; - foreach (var defaultItem in _defaults!.Where(d => d.Key.StartsWith(key, StringComparison.CurrentCultureIgnoreCase))) + AppendYaml(processor.Result!, yamlSpacing); + } + + private void AppendYaml(DefaultNode node, string yamlSpacing, string spacingPostFix = "") + { + var value = ""; + if (!string.IsNullOrWhiteSpace(node.Value)) value = node.Value; + var key = node.Key; + if (key.StartsWith('[')) { - //check if this item is an array - var itemKey = defaultItem.Key; - var defaultSpacing = new string(' ', itemKey.Count(d => d == '.') * 2); - var match = defaultArrayRegex.Match(itemKey); - if (match.Success) //array + if (node.Children is { Count: > 0 } || node.Value!.StartsWith("$ref")) { - if (int.TryParse(match.Groups[1].ToString(), out var itemIndex)) - { - var defaultPrefix = " "; - if (itemIndex != arrayIndex) - { - defaultPrefix = "- "; - arrayIndex = itemIndex; - } - _yaml.AppendLine($"{defaultSpacing}{defaultPrefix}{match.Groups[2]}: {defaultItem.Value}"); - } + spacingPostFix = "- "; } else { - _yaml.AppendLine($"{yamlSpacing}{yamlSpacingPostfix}{key}: {_defaults![key]}"); + _yaml.AppendLine($"{yamlSpacing}{value}"); } } + else + { + key += ": "; + yamlSpacing = AddPostFix(yamlSpacing, spacingPostFix); + _yaml.AppendLine($"{yamlSpacing}{key}{value}"); + } + + if (node.Children.Count != 0) + { + yamlSpacing += " "; + foreach (var childNode in node.Children) + { + AppendYaml(childNode, yamlSpacing, spacingPostFix); + spacingPostFix = ""; + } + } + else if (node.Value!.StartsWith("$ref")) + { + yamlSpacing += " "; + yamlSpacing = AddPostFix(yamlSpacing, spacingPostFix); + _yaml.AppendLine($"{yamlSpacing}{value}"); + } + + } + + private string AddPostFix(string source, string postFix) + { + if (string.IsNullOrEmpty(postFix)) return source; + var result = source.Substring(0, source.Length - postFix.Length); + result += postFix; + return result; } } diff --git a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/DefaultNode.cs b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/DefaultNode.cs new file mode 100644 index 0000000..2f5a560 --- /dev/null +++ b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/DefaultNode.cs @@ -0,0 +1,8 @@ +namespace Nox.Cli.Plugin.Console; + +public class DefaultNode +{ + public string Key { get; set; } = string.Empty; + public string? Value { get; set; } + public List Children { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/DefaultsProcessor.cs b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/DefaultsProcessor.cs new file mode 100644 index 0000000..7e08a49 --- /dev/null +++ b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Console/DefaultsProcessor.cs @@ -0,0 +1,92 @@ +using System.Text.RegularExpressions; + +namespace Nox.Cli.Plugin.Console; + +public class DefaultsProcessor +{ + private readonly Regex _defaultItemRegex = new(@"(\w+)(\[\d*\])?", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); + private DefaultNode? _result; + + public DefaultNode? Result => _result; + + public void Process(KeyValuePair defaultEntry) + { + var matches = _defaultItemRegex.Matches(defaultEntry.Key);//.Where(m => !string.IsNullOrWhiteSpace(m.Value)).ToList(); + if (matches.Any()) + { + Process(matches, defaultEntry.Value.ToString()!); + } + } + + private void Process(MatchCollection matches, string value) + { + DefaultNode? node = null; + var lastMatch = matches.Last(); + + foreach (Match match in matches) + { + if (node == null) + { + node = FindNode(_result, match.Groups[1].Value); + } + else + { + var foundNode = FindNode(node, match.Groups[1].Value); + if (foundNode == null) + { + foundNode = AddChild(node, match.Groups[1].Value, null); + } + + node = foundNode; + } + + if (!string.IsNullOrEmpty(match.Groups[2].Value)) + { + var child = FindNode(node, match.Groups[2].Value); + if (child == null) + { + child = AddChild(node!, match.Groups[2].Value, null); + } + + node = child; + } + + if (match == lastMatch) + { + node!.Value = value; + } + } + } + + private DefaultNode? FindNode(DefaultNode? nodeToSearch, string key) + { + if (nodeToSearch == null) + { + _result = new DefaultNode + { + Key = key + }; + return _result; + } + if (nodeToSearch.Key == key) return nodeToSearch; + if (nodeToSearch.Children.Count != 0) + { + foreach (var child in nodeToSearch.Children!) + { + if (child.Key == key) return child; + } + } + return null; + } + + private DefaultNode AddChild(DefaultNode node, string key, string? value) + { + var child = new DefaultNode + { + Key = key, + Value = value + }; + node.Children.Add(child); + return child; + } +} \ No newline at end of file diff --git a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Core/CoreLoadNoxConfiguration_v1.cs b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Core/CoreLoadNoxConfiguration_v1.cs index 5685159..b671b01 100755 --- a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Core/CoreLoadNoxConfiguration_v1.cs +++ b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.Core/CoreLoadNoxConfiguration_v1.cs @@ -53,15 +53,6 @@ public Task> ProcessAsync(INoxWorkflowContext ctx) { var fullPath = Path.GetFullPath(_path); var solution = new NoxSolutionBuilder() - // .OnResolveSecrets((_, args) => - // { - // var secretsConfig = args.SecretsConfig; - // var secretKeys = args.Variables; - // var resolver = ctx.NoxSecretsResolver; - // if (resolver == null) throw new NoxCliException("Cannot load Nox solution definition, Secrets resolved has not been initialized."); - // resolver.Configure(secretsConfig!, Assembly.GetEntryAssembly()); - // args.Secrets = resolver.Resolve(secretKeys!); - // }) .Build(); ctx.SetProjectConfiguration(solution); diff --git a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.File/FileDeleteFile_v1.cs b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.File/FileDeleteFile_v1.cs index d965991..c872607 100755 --- a/src/Nox.Cli.Plugins/Nox.Cli.Plugin.File/FileDeleteFile_v1.cs +++ b/src/Nox.Cli.Plugins/Nox.Cli.Plugin.File/FileDeleteFile_v1.cs @@ -48,7 +48,7 @@ public Task> ProcessAsync(INoxWorkflowContext ctx) try { var fullPath = Path.GetFullPath(_path); - if (Directory.Exists(fullPath)) + if (Path.Exists(fullPath)) { System.IO.File.Delete(fullPath); } diff --git a/src/Nox.Cli.Server/Program.cs b/src/Nox.Cli.Server/Program.cs index e20edf7..1c993df 100755 --- a/src/Nox.Cli.Server/Program.cs +++ b/src/Nox.Cli.Server/Program.cs @@ -12,7 +12,7 @@ // Add services to the container. builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration, subscribeToJwtBearerMiddlewareDiagnosticsEvents: true); -var cacheManager = new NoxCliCacheBuilder(builder.Configuration["NoxScriptsUrl"]!) +var cacheManager = new NoxCliCacheBuilder(builder.Configuration["NoxScriptsUrl"]!, false) .WithTenantId(builder.Configuration["AzureAd:TenantId"]!) .ForServer() .Build(); diff --git a/src/Nox.Cli/Extensions/ConfiguratorExtensions.cs b/src/Nox.Cli/Extensions/ConfiguratorExtensions.cs index dd5dc08..75b720f 100755 --- a/src/Nox.Cli/Extensions/ConfiguratorExtensions.cs +++ b/src/Nox.Cli/Extensions/ConfiguratorExtensions.cs @@ -14,12 +14,11 @@ namespace Nox.Cli.Extensions; internal static class ConfiguratorExtensions { - public static IConfigurator AddNoxCommands(this IConfigurator cliConfig, IServiceCollection services, bool isOnline, string? remoteUrl = null) + public static IConfigurator AddNoxCommands(this IConfigurator cliConfig, IServiceCollection services, bool forceOffline) { var onlineCacheUrl = "https://noxorg.dev"; - if (!string.IsNullOrWhiteSpace(remoteUrl)) onlineCacheUrl = remoteUrl; var persistedTokenCache = services.BuildServiceProvider().GetRequiredService(); - var cacheBuilder = new NoxCliCacheBuilder(onlineCacheUrl, persistedTokenCache) + var cacheBuilder = new NoxCliCacheBuilder(onlineCacheUrl, forceOffline, persistedTokenCache) .WithBuildEventHandler((sender, args) => { AnsiConsole.MarkupLine(args.SpectreMessage); diff --git a/src/Nox.Cli/Extensions/ServiceCollectionExtensions.cs b/src/Nox.Cli/Extensions/ServiceCollectionExtensions.cs index e94caca..6c4061d 100755 --- a/src/Nox.Cli/Extensions/ServiceCollectionExtensions.cs +++ b/src/Nox.Cli/Extensions/ServiceCollectionExtensions.cs @@ -19,14 +19,6 @@ private static NoxSolution CreateSolution(IServiceProvider serviceProvider) { return new NoxSolutionBuilder() .AllowMissingSolutionYaml() - // .OnResolveSecrets((_, args) => - // { - // var secretsConfig = args.SecretsConfig; - // var secretKeys = args.Variables; - // var resolver = serviceProvider.GetRequiredService(); - // resolver.Configure(secretsConfig!, Assembly.GetEntryAssembly()); - // args.Secrets = resolver.Resolve(secretKeys!); - // }) .Build(); } } \ No newline at end of file diff --git a/src/Nox.Cli/Program.cs b/src/Nox.Cli/Program.cs index 24b4dde..9a32864 100755 --- a/src/Nox.Cli/Program.cs +++ b/src/Nox.Cli/Program.cs @@ -21,12 +21,11 @@ var isGettingVersion = (args.Length > 0 && args[0].ToLower().Equals("version")); -var remoteUrl = string.Empty; - -var remoteUrlArg = args.FirstOrDefault(arg => arg.StartsWith("--remoteUrl=")); -if (remoteUrlArg != null) +var forceOffline = false; +var offlineArg = args.FirstOrDefault(arg => arg.StartsWith("--offline")); +if (offlineArg != null) { - remoteUrl = remoteUrlArg.Replace("--remoteUrl=", ""); + forceOffline = true; } if (!isGettingVersion || args.Length == 0) @@ -39,7 +38,6 @@ AnsiConsole.MarkupLine(@$""); } -var isOnline = InternetChecker.CheckForInternet(); var services = new ServiceCollection(); services.AddSingleton(); @@ -67,7 +65,7 @@ if(!isGettingVersion && !isLoggingOut) { - config.AddNoxCommands(services, isOnline, remoteUrl); + config.AddNoxCommands(services, forceOffline); } config.AddCommand("logout") diff --git a/src/Nox.Cli/Properties/launchSettings.json b/src/Nox.Cli/Properties/launchSettings.json index e2d607c..e712be3 100755 --- a/src/Nox.Cli/Properties/launchSettings.json +++ b/src/Nox.Cli/Properties/launchSettings.json @@ -2,8 +2,8 @@ "profiles": { "Nox.Cli": { "commandName": "Project", - "commandLineArgs": "sync tf", - "workingDirectory": "/home/jan/Projects/IWG/Fno.MultiInstance", + "commandLineArgs": "new solution --offline", + "workingDirectory": "/home/jan/demo/CliDemo", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }