From f66b27775476b2a7813568711e1ee8dc29439249 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Sun, 18 Feb 2024 11:54:27 +0100 Subject: [PATCH 01/46] Create CommandInputType --- addons/yat/src/types/CommandInputType.cs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 addons/yat/src/types/CommandInputType.cs diff --git a/addons/yat/src/types/CommandInputType.cs b/addons/yat/src/types/CommandInputType.cs new file mode 100644 index 00000000..412e642c --- /dev/null +++ b/addons/yat/src/types/CommandInputType.cs @@ -0,0 +1,3 @@ +namespace YAT.Types; + +public record CommandInputType(string Type, long Min, long Max); From 5ed9813c6709c3948d74f752a734527ff3cfafbf Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Sun, 18 Feb 2024 12:43:04 +0100 Subject: [PATCH 02/46] Add IsArray property to CommandInputType --- addons/yat/src/types/CommandInputType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/yat/src/types/CommandInputType.cs b/addons/yat/src/types/CommandInputType.cs index 412e642c..117a4a92 100644 --- a/addons/yat/src/types/CommandInputType.cs +++ b/addons/yat/src/types/CommandInputType.cs @@ -1,3 +1,3 @@ namespace YAT.Types; -public record CommandInputType(string Type, long Min, long Max); +public record CommandInputType(string Type, long Min, long Max, bool IsArray); From 4d999000708abdc1e7defca9797799ed889a4f56 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Sun, 18 Feb 2024 14:21:03 +0100 Subject: [PATCH 03/46] Update CommandInputType to use float for Min and Max values --- addons/yat/src/types/CommandInputType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/yat/src/types/CommandInputType.cs b/addons/yat/src/types/CommandInputType.cs index 117a4a92..154d6c1a 100644 --- a/addons/yat/src/types/CommandInputType.cs +++ b/addons/yat/src/types/CommandInputType.cs @@ -1,3 +1,3 @@ namespace YAT.Types; -public record CommandInputType(string Type, long Min, long Max, bool IsArray); +public record CommandInputType(string Type, float Min, float Max, bool IsArray); From a865c6b1c1baf932273bb209eb8d9c51d78479d8 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Sun, 18 Feb 2024 14:21:23 +0100 Subject: [PATCH 04/46] Add TryParseCommandInputType method to Text.cs --- CHANGELOG.md | 2 ++ addons/yat/src/helpers/Text.cs | 57 ++++++++++++++++++++++++++++++++++ test/helpers/TestText.cs | 29 ++++++++++++++++- 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcd6c846..1ab464ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ All notable changes to this project will be documented in this file. - Attributes: - Description - Usage +- CommandInputType record. +- TryParseCommandInputType method for Text helper class. ### Changed diff --git a/addons/yat/src/helpers/Text.cs b/addons/yat/src/helpers/Text.cs index c7256056..6a808c9b 100644 --- a/addons/yat/src/helpers/Text.cs +++ b/addons/yat/src/helpers/Text.cs @@ -1,7 +1,10 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Godot; +using YAT.Types; namespace YAT.Helpers; @@ -203,4 +206,58 @@ public static string ShortenPath(string path, ushort maxLength, string ellipsisC return result.ToString() + ellipsisChar + lastPart; } + + public static bool TryParseCommandInputType(string type, out CommandInputType parsed) + { + parsed = null; + type = type?.Trim(); + + // Used to later get min/max values + static bool allowedToHaveRange(string t) => t switch + { + "int" => true, + "float" => true, + "string" => true, + _ => false + }; + + if (string.IsNullOrEmpty(type)) return false; + + // Check if ends with ... to indicate an array + bool isArray = type.EndsWith("..."); + + if (isArray) type = type[..^3].Trim(); + + // Get the min and max values if present + var tokens = type.Trim(')').Split('(', StringSplitOptions.RemoveEmptyEntries); + + if (tokens.Length > 1) + { + if (tokens[1].EndsWith(':')) return false; + + var minMax = tokens[1].Split(':', StringSplitOptions.RemoveEmptyEntries); + + if (!allowedToHaveRange(tokens[0]) || minMax.Length > 2) return false; + + // If min value is not present, set it to 0 + if (minMax.Length == 1) + { + if (minMax[0].TryConvert(out float max)) + parsed = new(tokens[0], 0, max, isArray); + else return false; + } + else + { + if (minMax[0].TryConvert(out float min) && + minMax[1].TryConvert(out float max)) + parsed = new(tokens[0], min, max, isArray); + else return false; + } + } + else parsed = new(tokens[0], 0, 0, isArray); + + if (parsed.Min > parsed.Max) return false; + + return true; + } } diff --git a/test/helpers/TestText.cs b/test/helpers/TestText.cs index d5679eae..7d5a6e3b 100644 --- a/test/helpers/TestText.cs +++ b/test/helpers/TestText.cs @@ -87,9 +87,36 @@ public void TestSplitClean(string text, string separator, string[] expected) [TestCase("res://example/main_menu/MainMenu.tscn", (ushort)15, "...ainMenu.tscn")] [TestCase("res://example/main_menu/MainMenu.tscn", (ushort)0, "...")] [TestCase("", (ushort)16, "...")] - public void ShortenPath(string path, ushort maxLength, string expected) + public void TestShortenPath(string path, ushort maxLength, string expected) { AssertThat(Text.ShortenPath(path, maxLength)).IsEqual(expected); } + + [TestCase("int", "int", 0, 0, false, true)] + [TestCase("int(0:10)", "int", 0, 10, false, true)] + [TestCase("int(5:10)", "int", 5, 10, false, true)] + [TestCase("int(:10)", "int", 0, 10, false, true)] + [TestCase("int(sda:da)", "int", 0, 0, false, false)] + [TestCase("int(:dxdx)", "int", 0, 0, false, false)] + [TestCase("int(0:)", "int", 0, 0, false, false)] + [TestCase("int()", "int", 0, 0, false, true)] + [TestCase("x(5:10)", "x", 5, 10, false, false)] + [TestCase("choice", "choice", 0, 0, false, true)] + [TestCase("choice(0:10)", "choice", 0, 10, false, false)] + [TestCase("float(5:10)", "float", 5f, 10f, false, true)] + [TestCase("float(5.5:10.5)", "float", 5.5f, 10.5f, false, true)] + [TestCase("float(5:10)...", "float", 5f, 10f, true, true)] + [TestCase("int...", "int", 0, 0, true, true)] + public static void TestTryParseCommandInputType(string type, string eType, float min, float max, bool isArray, bool successful) + { + AssertThat(Text.TryParseCommandInputType(type, out var result)).IsEqual(successful); + + if (!successful) return; + + AssertThat(result.Type).IsEqual(eType); + AssertThat(result.Min).IsEqual(min); + AssertThat(result.Max).IsEqual(max); + AssertThat(result.IsArray).IsEqual(isArray); + } } } From eda8b8a809a14d86b140352f90cfb9d435bf702d Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Sun, 18 Feb 2024 14:51:34 +0100 Subject: [PATCH 05/46] Create CommandInput attribute --- CHANGELOG.md | 1 + .../src/attributes/CommandInputAttribute.cs | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 addons/yat/src/attributes/CommandInputAttribute.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ab464ec..ed953025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file. - Usage - CommandInputType record. - TryParseCommandInputType method for Text helper class. +- CommandInput attribute. ### Changed diff --git a/addons/yat/src/attributes/CommandInputAttribute.cs b/addons/yat/src/attributes/CommandInputAttribute.cs new file mode 100644 index 00000000..ebe7398b --- /dev/null +++ b/addons/yat/src/attributes/CommandInputAttribute.cs @@ -0,0 +1,25 @@ +using System; +using Godot; +using YAT.Helpers; +using YAT.Types; + +namespace YAT.Attributes; + +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +public class CommandInputAttribute : Attribute +{ + public string Name { get; private set; } + public CommandInputType Type { get; private set; } + public string Description { get; private set; } + + public CommandInputAttribute(string name, string type, string description = "") + { + Name = name; + Description = description; + + if (Text.TryParseCommandInputType(type, out var t)) + Type = t; + else + GD.PrintErr($"Invalid command input type: {type}"); + } +} From f0c65097e623267ca50655be9f87dd8cdfca22e0 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Sun, 18 Feb 2024 14:58:06 +0100 Subject: [PATCH 06/46] Argument and Option attributes inherit from CommandInput --- CHANGELOG.md | 1 + .../yat/src/attributes/ArgumentAttribute.cs | 33 ++----------------- .../src/attributes/CommandInputAttribute.cs | 2 +- addons/yat/src/attributes/OptionAttribute.cs | 32 ++---------------- 4 files changed, 8 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed953025..44bf5c8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ All notable changes to this project will be documented in this file. - Commands now return CommandResult record. - The SceneAboutToChange signal in the Cs command now also gives the old path, not just the new one. - Renamed ping command options. +- Argument & Option attributes inherit from CommandInput attribute. ### Removed diff --git a/addons/yat/src/attributes/ArgumentAttribute.cs b/addons/yat/src/attributes/ArgumentAttribute.cs index cf7f317f..9b4a173d 100644 --- a/addons/yat/src/attributes/ArgumentAttribute.cs +++ b/addons/yat/src/attributes/ArgumentAttribute.cs @@ -1,39 +1,12 @@ using System; -using System.Linq; namespace YAT.Attributes; [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] -public sealed class ArgumentAttribute : Attribute +public sealed class ArgumentAttribute : CommandInputAttribute { - public string Name { get; private set; } - public object Type { get; set; } - public string Description { get; private set; } - - public ArgumentAttribute(string name, string type, string Description = "") - { - Name = name; - Type = ParseDataType(type); - this.Description = Description; - } - - /// - /// Parses a string representation of a data type and returns the corresponding object or type. - /// - /// The string representation of the data type to parse. - /// The parsed object or type, or null if the data type could not be parsed. - private static object ParseDataType(string dataType) + public ArgumentAttribute(string name, string type, string description = "") + : base(name, type, description) { - var data = dataType?.Trim(); - - if (string.IsNullOrEmpty(data)) return null; - - if (data.StartsWith("[") && data.EndsWith("]")) - { - string[] values = data.Trim('[', ']').Split(',').Select(v => v.Trim()).ToArray(); - return values; - } - - return data; } } diff --git a/addons/yat/src/attributes/CommandInputAttribute.cs b/addons/yat/src/attributes/CommandInputAttribute.cs index ebe7398b..610c7127 100644 --- a/addons/yat/src/attributes/CommandInputAttribute.cs +++ b/addons/yat/src/attributes/CommandInputAttribute.cs @@ -20,6 +20,6 @@ public CommandInputAttribute(string name, string type, string description = "") if (Text.TryParseCommandInputType(type, out var t)) Type = t; else - GD.PrintErr($"Invalid command input type: {type}"); + GD.PushError($"Invalid command input type '{type}' for command '{name}'."); } } diff --git a/addons/yat/src/attributes/OptionAttribute.cs b/addons/yat/src/attributes/OptionAttribute.cs index 6142f11f..49074d6f 100644 --- a/addons/yat/src/attributes/OptionAttribute.cs +++ b/addons/yat/src/attributes/OptionAttribute.cs @@ -3,39 +3,13 @@ namespace YAT.Attributes; [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] -public sealed class OptionAttribute : Attribute +public sealed class OptionAttribute : CommandInputAttribute { - public string Name { get; private set; } - public string Description { get; private set; } - public object Type { get; set; } public object DefaultValue { get; private set; } - public OptionAttribute( - string name, - string type, - string description = "", - object defaultValue = null - ) + public OptionAttribute(string name, string type, string description = "", object defaultValue = null) + : base(name, type, description) { - Name = name; - Type = ParseDataType(type); - Description = description; DefaultValue = defaultValue; } - - /// - /// Parses the given data type string and returns an object representing the parsed data. - /// - /// The data type string to parse. - /// An object representing the parsed data. - private static object ParseDataType(string dataType) - { - var data = dataType?.Trim(); - - if (string.IsNullOrEmpty(data)) return null; - - var splitted = data.Split(','); - - return splitted.Length > 1 ? splitted : splitted[0]; - } } From 692b0a44c5a279eaffe13dbba4ca257163a70cbb Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Sun, 18 Feb 2024 16:07:54 +0100 Subject: [PATCH 07/46] Add support for multiple command input types --- addons/yat/src/attributes/CommandInputAttribute.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/addons/yat/src/attributes/CommandInputAttribute.cs b/addons/yat/src/attributes/CommandInputAttribute.cs index 610c7127..801f5942 100644 --- a/addons/yat/src/attributes/CommandInputAttribute.cs +++ b/addons/yat/src/attributes/CommandInputAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Godot; using YAT.Helpers; using YAT.Types; @@ -9,7 +10,7 @@ namespace YAT.Attributes; public class CommandInputAttribute : Attribute { public string Name { get; private set; } - public CommandInputType Type { get; private set; } + public LinkedList Types { get; private set; } public string Description { get; private set; } public CommandInputAttribute(string name, string type, string description = "") @@ -17,9 +18,12 @@ public CommandInputAttribute(string name, string type, string description = "") Name = name; Description = description; - if (Text.TryParseCommandInputType(type, out var t)) - Type = t; - else - GD.PushError($"Invalid command input type '{type}' for command '{name}'."); + foreach (var t in type.Split('|')) + { + if (Text.TryParseCommandInputType(t, out var commandInputType)) + Types.AddLast(commandInputType); + else + GD.PushError($"Invalid command input type '{t}' for command '{name}'."); + } } } From 0f2b92d22c790d0827a3b271361cb74133409381 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 11:25:27 +0100 Subject: [PATCH 08/46] Overloaded IsWithinRange method to support multiple types --- addons/yat/src/helpers/Numeric.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/addons/yat/src/helpers/Numeric.cs b/addons/yat/src/helpers/Numeric.cs index b1923d13..31598caa 100644 --- a/addons/yat/src/helpers/Numeric.cs +++ b/addons/yat/src/helpers/Numeric.cs @@ -5,19 +5,17 @@ namespace YAT.Helpers; public static class Numeric { - /// - /// Determines whether a value is within the specified range (inclusive). - /// - /// The type of the values to compare. - /// The value to check. - /// The minimum value of the range. - /// The maximum value of the range. - /// True if the value is within the range, false otherwise. public static bool IsWithinRange(T value, T min, T max) where T : IComparable { return value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0; } + public static bool IsWithinRange(T value, E min, E max) + where T : IComparable, IComparable + { + return value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0; + } + /// /// Tries to convert a string to the specified type. /// From e7870ee348480f3f365ac25933fca7b140c809d4 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 12:36:27 +0100 Subject: [PATCH 09/46] Add InvalidOption method to Messages class --- addons/yat/src/helpers/Messages.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/yat/src/helpers/Messages.cs b/addons/yat/src/helpers/Messages.cs index 1cac1021..31f09f1f 100644 --- a/addons/yat/src/helpers/Messages.cs +++ b/addons/yat/src/helpers/Messages.cs @@ -13,7 +13,9 @@ public static class Messages public static string InvalidNodePath(string path) => $"{path} is not a valid node path."; public static string InvalidArgument(string command, string argument, string expected) => $"{command} expected {argument} to be an {expected}."; public static string InvalidMethod(string method) => $"{method} is not a valid method."; + public static string InvalidOption(string commandName, string opt) => $"{commandName} does not have an option named {opt}."; public static string NodeHasNoChildren(string path) => $"{path} has no children."; + public static string DisposedNode => "The node has been disposed."; } From 2323e24530cd5ccf0ee197cc851db3ccecda92ce Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 12:39:43 +0100 Subject: [PATCH 10/46] Rewrite CommandValidator --- .../command_validator/CommandValidator.cs | 293 +++++------------- 1 file changed, 77 insertions(+), 216 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index 1287c481..6a9a2266 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -6,6 +6,7 @@ using YAT.Attributes; using YAT.Helpers; using YAT.Interfaces; +using YAT.Types; namespace YAT.Scenes; @@ -18,10 +19,10 @@ public partial class CommandValidator : Node /// /// The type of attribute to validate. /// The command to validate. - /// The arguments passed to the command. - /// The dictionary of arguments. + /// The arguments passed to the command. + /// The dictionary of arguments. /// True if the passed data is valid, false otherwise. - public bool ValidatePassedData(ICommand command, string[] passedArgs, out Dictionary args) where T : Attribute + public bool ValidatePassedData(ICommand command, string[] passedData, out Dictionary data) where T : CommandInputAttribute { Type type = typeof(T); Type argType = typeof(ArgumentAttribute); @@ -31,289 +32,149 @@ public bool ValidatePassedData(ICommand command, string[] passedArgs, out Dic CommandAttribute commandAttribute = command.GetAttribute(); + data = null; + if (commandAttribute is null) { Terminal.Output.Error(Messages.MissingAttribute("CommandAttribute", command.GetType().Name)); - args = null; return false; } - T[] argsArr = command.GetType().GetCustomAttributes(type, false) as T[] ?? Array.Empty(); + T[] dataAttrArr = command.GetType().GetCustomAttributes(type, false) as T[] ?? Array.Empty(); if (type == argType) { - args = argsArr.ToDictionary( - a => (a as ArgumentAttribute).Name, - a => (a as ArgumentAttribute).Type - ); - - if (passedArgs.Length < args.Count) + if (passedData.Length < dataAttrArr.Length) { - Terminal.Output.Error(Messages.MissingArguments(commandAttribute.Name, args.Keys.ToArray())); + Terminal.Output.Error(Messages.MissingArguments(commandAttribute.Name, data.Keys.ToArray())); return false; } - return ValidateCommandArguments(commandAttribute.Name, args, passedArgs); + return ValidateCommandArguments( + commandAttribute.Name, data, passedData, dataAttrArr as ArgumentAttribute[] + ); } else if (type == optType) - { - args = argsArr.ToDictionary( - a => (a as OptionAttribute)?.Name ?? string.Empty, - a => (object)new Tuple( - (a as OptionAttribute)?.Type, (a as OptionAttribute)?.DefaultValue - ) + return ValidateCommandOptions( + commandAttribute.Name, data, passedData, dataAttrArr as OptionAttribute[] ); - return ValidateCommandOptions(commandAttribute.Name, args, passedArgs); - } + return false; + } - args = null; + private bool ValidateCommandArguments( + string commandName, + Dictionary validatedArgs, + string[] passedArgs, + ArgumentAttribute[] arguments + ) + { + for (int i = 0; i < passedArgs.Length; i++) + if (!ValidateCommandArgument(commandName, arguments[i], validatedArgs, passedArgs[i])) return false; return true; } - private bool ValidateCommandArguments(string name, Dictionary args, string[] passedArgs) + private bool ValidateCommandOptions( + string commandName, + Dictionary validatedOpts, + string[] passedOpts, + OptionAttribute[] options + ) { - for (int i = 0; i < args.Count; i++) - { - string argName = args.Keys.ElementAt(i); - object argType = args.Values.ElementAt(i); - - if (!ValidateCommandArgument(argName, argType, args, passedArgs[i])) - return false; - } + foreach (var opt in options) + if (!ValidateCommandOption(commandName, opt, validatedOpts, passedOpts)) return false; return true; } public bool ValidateCommandArgument( - string name, - object type, - Dictionary args, + string commandName, + ArgumentAttribute argument, + Dictionary validatedArgs, string passedArg, bool log = true ) { - if (type is string[] options) + foreach (var type in argument.Types) { - var found = false; + object converted = ConvertStringToType(type, passedArg); - foreach (var opt in options) + if (converted is not null) { - if (opt == passedArg) - { - found = true; - args[name] = opt; - break; - } - - object convertedArg = ConvertStringToType(opt, passedArg); - - if (convertedArg is not null) - { - found = true; - args[name] = convertedArg; - break; - } - } - - if (!found) - { - if (log) Terminal.Output.Error( - Messages.InvalidArgument(name, name, string.Join(", ", options)) - ); - return false; - } - } - else - { - object convertedArg = ConvertStringToType( - type?.ToString() ?? name, passedArg - ); - - if (convertedArg is null) - { - if (log) Terminal.Output.Error( - Messages.InvalidArgument(name, name, (string)(type ?? name)) - ); - return false; + validatedArgs[argument.Name] = converted; + return true; } - - args[name] = convertedArg; + else if (log) + Terminal.Output.Error(Messages.InvalidArgument( + commandName, argument.Name, string.Join(", ", argument.Types) + )); } - return true; + return false; } - private bool ValidateCommandOptions(string name, Dictionary opts, string[] passedOpts) + private bool ValidateCommandOption( + string commandName, + OptionAttribute options, + Dictionary validatedOpts, + string[] passedOpts + ) { - foreach (var optEntry in opts) + foreach (var opt in passedOpts) { - string optName = optEntry.Key; - object optType = ((Tuple)optEntry.Value)?.Item1; - - var passedOpt = passedOpts.FirstOrDefault(o => o.StartsWith(optName), string.Empty) - .Split('=', 2, StringSplitOptions.TrimEntries); - string passedOptName = passedOpt?[0]; - string passedOptValue = passedOpt?.Length >= 2 ? passedOpt?[1] : null; - - // If option is not passed then set the option to its default value - if (string.IsNullOrEmpty(passedOptName)) - { - opts[optName] = ((Tuple)opts[optName])?.Item2; - continue; - } + if (!opt.StartsWith(options.Name)) continue; - // if option is a flag (there is no type specified for the option) - if (optType is null) - { - if (!string.IsNullOrEmpty(passedOptValue)) - { - Terminal.Output.Error(Messages.InvalidArgument(name, optName, optName)); - return false; - } - - opts[optName] = !string.IsNullOrEmpty(passedOptName); - continue; - } + string[] split = opt.Split('='); - // If option is passed but it doesn't have a value - if (string.IsNullOrEmpty(passedOptValue)) + if (split.Length != 2) { - Terminal.Output.Error(Messages.MissingValue(name, optName)); + Terminal.Output.Error(Messages.InvalidOption(commandName, opt)); return false; } - bool ProcessOptionValue(string valueType, Action set) - { - object converted = ConvertStringToType(valueType, passedOptValue); - - if (converted is null) - { - Terminal.Output.Error(Messages.InvalidArgument(name, optName, valueType ?? optName)); - return false; - } - - set(converted); - - return true; - } - - // If option expects a value (there is no ... at the end of the type) - if (optType is string valueType && !valueType.EndsWith("...") && - !valueType.Contains('|') - ) - { - if (ProcessOptionValue(valueType, - (converted) => opts[optName] = converted) - ) continue; - return false; - } + string value = split[1]; - // If option expects an array of values (type ends with ...) - if (optType is string valuesType && valuesType.EndsWith("...")) + foreach (var type in options.Types) { - string[] values = passedOptValue.Split(',', - StringSplitOptions.TrimEntries | - StringSplitOptions.RemoveEmptyEntries - ); - List validatedValues = new(); + object converted = ConvertStringToType(type, value); - foreach (var value in values) + if (converted is not null) { - if (!ProcessOptionValue(valuesType.Replace("...", string.Empty), - (converted) => validatedValues.Add(converted) - )) return false; - } - - opts[optName] = validatedValues.ToArray(); - continue; - } - - // If option expects one of the specified values (type contains |) - if (optType is string optionsType && optionsType.Contains('|')) - { - string[] options = optionsType.Split('|'); - var found = false; - - foreach (var opt in options) - { - if (opt == passedOptValue) - { - found = true; - opts[optName] = opt; - break; - } - - object converted = ConvertStringToType(opt, passedOptValue); - - if (converted is not null) - { - found = true; - opts[optName] = converted; - break; - } - } - - if (!found) - { - Terminal.Output.Error(Messages.InvalidArgument(name, optName, string.Join(", ", options))); - return false; + validatedOpts[options.Name] = converted; + return true; } + else + Terminal.Output.Error(Messages.InvalidOption(commandName, opt)); } } - return true; - } + validatedOpts[options.Name] = options.DefaultValue; - /// - /// Parses a string argument to extract a range of values. - /// - /// The type of the values in the range. - /// The string argument to parse. - /// Contains the minimum value of the range if the parse succeeded, - /// or the default value of if the parse failed. - /// Contains the maximum value of the range if the parse succeeded, - /// or the default value of if the parse failed. - /// true if the parse succeeded; otherwise, false. - private static bool GetRange(string arg, out T min, out T max) where T : notnull, IConvertible, IComparable - { - min = default!; - max = default!; - - if (!arg.Contains('(') || !arg.Contains(')')) return false; - - string[] parts = arg.Split('(', ')'); - string[] range = parts[1].Split(',', ':'); - - if (range.Length != 2) return false; - - if (!Numeric.TryConvert(range[0], out min)) return false; - if (!Numeric.TryConvert(range[1], out max)) return false; - - return true; + return false; } - private static object ConvertStringToType(string type, string value) + private static object ConvertStringToType(CommandInputType type, string value) { - var t = type.ToLower(); + var t = type.Type; if (t == "string" || t == value) return value; - if (t == "bool") return bool.Parse(value); + if (t == "bool") return bool.TryParse(value, out bool result) ? result : null; - if (t.StartsWith("int")) return TryConvertNumeric(type, value); + if (t.StartsWith("int")) return (int)TryConvertNumeric(type, value); if (t.StartsWith("float")) return TryConvertNumeric(type, value); - if (t.StartsWith("double")) return TryConvertNumeric(type, value); return null; } - private static object TryConvertNumeric(string type, string value) where T : notnull, IConvertible, IComparable + private static object TryConvertNumeric(CommandInputType type, string value) where T : notnull, IConvertible, IComparable, IComparable { - if (!Numeric.TryConvert(value, out T result)) return null; + if (!Numeric.TryConvert(value, out T result)) return null; + + if (type.Min != type.Max) return Numeric.IsWithinRange( + result, type.Min, type.Max + ) ? result : null; - if (GetRange(type, out T min, out T max)) - return Numeric.IsWithinRange(result, min, max) ? result : null; return result; } } From a31ddfa6d076370a0239e0193d9e8e2322e62ad5 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 12:39:57 +0100 Subject: [PATCH 11/46] Adjust manual generation --- addons/yat/src/interfaces/ICommand.cs | 28 ++++++++++----------------- 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/addons/yat/src/interfaces/ICommand.cs b/addons/yat/src/interfaces/ICommand.cs index ecf3b91b..b8c12058 100644 --- a/addons/yat/src/interfaces/ICommand.cs +++ b/addons/yat/src/interfaces/ICommand.cs @@ -72,13 +72,13 @@ public virtual string GenerateCommandManual() public virtual string GenerateArgumentsManual() { - var attributes = Reflection.GetAttributes(this); + var arguments = Reflection.GetAttributes(this); - if (attributes is null) return "\nThis command does not have any arguments."; + if (arguments is null) return "\nThis command does not have any arguments."; StringBuilder sb = new(); - if (attributes.Length == 0) + if (arguments.Length == 0) { sb.AppendLine("\nThis command does not have any arguments."); return sb.ToString(); @@ -86,25 +86,21 @@ public virtual string GenerateArgumentsManual() sb.AppendLine("[p align=center][font_size=18]Arguments[/font_size][/p]"); - foreach (var attr in attributes) - { - sb.AppendLine($"[b]{attr.Name}[/b]: {(attr.Type is string[] type - ? string.Join(" | ", type) - : attr.Type)} - {attr.Description}"); - } + foreach (var arg in arguments) + sb.AppendLine($"[b]{arg.Name}[/b]: {string.Join(" | ", arg.Types)}"); return sb.ToString(); } public virtual string GenerateOptionsManual() { - var attributes = Reflection.GetAttributes(this); + var options = Reflection.GetAttributes(this); - if (attributes is null) return "\nThis command does not have any options."; + if (options is null) return "\nThis command does not have any options."; StringBuilder sb = new(); - if (attributes.Length == 0) + if (options.Length == 0) { sb.AppendLine("\nThis command does not have any options."); return sb.ToString(); @@ -112,12 +108,8 @@ public virtual string GenerateOptionsManual() sb.AppendLine("[p align=center][font_size=18]Options[/font_size][/p]"); - foreach (var attr in attributes) - { - sb.AppendLine($"[b]{attr.Name}[/b]: {(attr.Type is string[] type - ? string.Join(" | ", type) - : attr.Type)} - {attr.Description}"); - } + foreach (var opt in options) + sb.AppendLine($"[b]{opt.Name}[/b]: {string.Join(" | ", opt.Types)}"); return sb.ToString(); } From 91be9dd1a4ba119dd2fc7ef5d178a930b235e1e2 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 12:50:04 +0100 Subject: [PATCH 12/46] Adjust tests --- test/helpers/TestNumeric.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/helpers/TestNumeric.cs b/test/helpers/TestNumeric.cs index b3301f1d..0c038e37 100644 --- a/test/helpers/TestNumeric.cs +++ b/test/helpers/TestNumeric.cs @@ -15,7 +15,7 @@ public partial class TestNumericHelper [TestCase(-1, 0, 10, false)] public void TestIsWithinRangeInt(int value, int min, int max, bool expected) { - AssertThat(Numeric.IsWithinRange(value, min, max)).IsEqual(expected); + AssertThat(Numeric.IsWithinRange(value, min, max)).IsEqual(expected); } [TestCase(0.3, 0.1, 0.5, true)] @@ -25,7 +25,7 @@ public void TestIsWithinRangeInt(int value, int min, int max, bool expected) [TestCase(0, 0.1, 0.5, false)] public void TestIsWithinRangeDouble(double value, double min, double max, bool expected) { - AssertThat(Numeric.IsWithinRange(value, min, max)).IsEqual(expected); + AssertThat(Numeric.IsWithinRange(value, min, max)).IsEqual(expected); } #endregion From 25940f1ad5be7110563a9e9c2c5b3dcb24ac4a96 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 13:22:20 +0100 Subject: [PATCH 13/46] Create LinkedList object --- addons/yat/src/attributes/CommandInputAttribute.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/yat/src/attributes/CommandInputAttribute.cs b/addons/yat/src/attributes/CommandInputAttribute.cs index 801f5942..d75645ef 100644 --- a/addons/yat/src/attributes/CommandInputAttribute.cs +++ b/addons/yat/src/attributes/CommandInputAttribute.cs @@ -6,11 +6,11 @@ namespace YAT.Attributes; -[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] public class CommandInputAttribute : Attribute { public string Name { get; private set; } - public LinkedList Types { get; private set; } + public LinkedList Types { get; private set; } = new(); public string Description { get; private set; } public CommandInputAttribute(string name, string type, string description = "") From 29ca1b31430c27c93822b90def9ac2ea4074e214 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 13:24:16 +0100 Subject: [PATCH 14/46] Adjust CommandInfo --- .../input_info/components/command_info/CommandInfo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/input_info/components/command_info/CommandInfo.cs b/addons/yat/src/scenes/base_terminal/components/input_info/components/command_info/CommandInfo.cs index a0627d73..7dc7298a 100644 --- a/addons/yat/src/scenes/base_terminal/components/input_info/components/command_info/CommandInfo.cs +++ b/addons/yat/src/scenes/base_terminal/components/input_info/components/command_info/CommandInfo.cs @@ -49,8 +49,8 @@ private string GenerateCommandInfo(string[] tokens) bool current = tokens.Length - 1 == i; bool valid = Terminal.CommandValidator.ValidateCommandArgument( commandAttribute.Name, - arg.Type, - new() { { arg.Name, arg.Type } }, + arg, + new() { { arg.Name, arg.Types } }, (tokens.Length - 1 >= i + 1) ? tokens[i + 1] : string.Empty, false ); @@ -60,7 +60,7 @@ private string GenerateCommandInfo(string[] tokens) valid ? string.Empty : $"[color={_yat.PreferencesManager.Preferences.ErrorColor.ToHtml()}]", current ? "[b]" : string.Empty, arg.Name, - (arg.Type is string[]) ? "options" : arg.Type, + string.Join(" | ", arg.Types.Select(t => t.Type)), current ? "[/b]" : string.Empty, valid ? string.Empty : "[/color]" ); From 902aef58e940fa38ff4ca98f5a6f0addd7e6d78a Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 13:28:47 +0100 Subject: [PATCH 15/46] Fix generating manual --- addons/yat/src/interfaces/ICommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/yat/src/interfaces/ICommand.cs b/addons/yat/src/interfaces/ICommand.cs index b8c12058..c6852241 100644 --- a/addons/yat/src/interfaces/ICommand.cs +++ b/addons/yat/src/interfaces/ICommand.cs @@ -87,7 +87,7 @@ public virtual string GenerateArgumentsManual() sb.AppendLine("[p align=center][font_size=18]Arguments[/font_size][/p]"); foreach (var arg in arguments) - sb.AppendLine($"[b]{arg.Name}[/b]: {string.Join(" | ", arg.Types)}"); + sb.AppendLine($"[b]{arg.Name}[/b]: {string.Join(" | ", arg.Types.Select(t => t.Type))}"); return sb.ToString(); } @@ -109,7 +109,7 @@ public virtual string GenerateOptionsManual() sb.AppendLine("[p align=center][font_size=18]Options[/font_size][/p]"); foreach (var opt in options) - sb.AppendLine($"[b]{opt.Name}[/b]: {string.Join(" | ", opt.Types)}"); + sb.AppendLine($"[b]{opt.Name}[/b]: {string.Join(" | ", opt.Types.Select(t => t.Type))}"); return sb.ToString(); } From dc86870a5abaaeb3c4700345c9c043bfd34a4995 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 18:02:47 +0100 Subject: [PATCH 16/46] Add more tests for TryParseCommandInputType method --- test/helpers/TestText.cs | 53 ++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/test/helpers/TestText.cs b/test/helpers/TestText.cs index 7d5a6e3b..b70025b1 100644 --- a/test/helpers/TestText.cs +++ b/test/helpers/TestText.cs @@ -92,21 +92,60 @@ public void TestShortenPath(string path, ushort maxLength, string expected) AssertThat(Text.ShortenPath(path, maxLength)).IsEqual(expected); } - [TestCase("int", "int", 0, 0, false, true)] + // Valid range and type, no array [TestCase("int(0:10)", "int", 0, 10, false, true)] [TestCase("int(5:10)", "int", 5, 10, false, true)] [TestCase("int(:10)", "int", 0, 10, false, true)] + [TestCase("int(-12:8)", "int", 5, 10, false, true)] + [TestCase("float(5:10)", "float", 5, 10, false, true)] + [TestCase("float(5.5:10.5)", "float", 5.5, 10.5, false, true)] + [TestCase("float(0.5:60)", "float", 0.5, 60, false, true)] + [TestCase("float(0.5 : 60)", "float", 0.5, 60, false, true)] + [TestCase("float(-0.5:60)", "float", 0.5, 60, false, true)] + [TestCase("string(5:10)", "string", 5, 10, false, true)] + // Invalid range and valid type, no array [TestCase("int(sda:da)", "int", 0, 0, false, false)] [TestCase("int(:dxdx)", "int", 0, 0, false, false)] - [TestCase("int(0:)", "int", 0, 0, false, false)] + [TestCase("float(:)", "float", 0, 0, false, false)] + [TestCase("float( : )", "float", 0, 0, false, false)] + [TestCase("string(5:)", "string", 0, 0, false, false)] + [TestCase("string(x:10)", "string", 0, 0, false, false)] + [TestCase("string(5:10:15)", "string", 0, 0, false, false)] + [TestCase("string(5:x)", "string", 0, 0, false, false)] + [TestCase("string(5::5)", "string", 0, 0, false, false)] + [TestCase("int(:-5)", "int", 0, 0, false, false)] + [TestCase("int(-12:-5)", "int", 0, 0, false, false)] + [TestCase("int(10:5)", "int", 0, 0, false, false)] [TestCase("int()", "int", 0, 0, false, true)] - [TestCase("x(5:10)", "x", 5, 10, false, false)] + // No range and valid type, no array + [TestCase("int", "int", 0, 0, false, true)] [TestCase("choice", "choice", 0, 0, false, true)] - [TestCase("choice(0:10)", "choice", 0, 10, false, false)] - [TestCase("float(5:10)", "float", 5f, 10f, false, true)] - [TestCase("float(5.5:10.5)", "float", 5.5f, 10.5f, false, true)] - [TestCase("float(5:10)...", "float", 5f, 10f, true, true)] + [TestCase("float", "float", 0, 0, false, true)] + [TestCase("string", "string", 0, 0, false, true)] + [TestCase("x", "x", 0, 0, false, true)] + // Valid range, type not allowed to have range, no array + [TestCase("x(5:10)", "x", 0, 0, false, false)] + [TestCase("choice(0:10)", "choice", 0, 0, false, false)] + [TestCase("choice(5:10)", "choice", 0, 0, false, false)] + [TestCase("y(:10)", "choice", 0, 0, false, false)] + [TestCase("y(-12:8)", "choice", 0, 0, false, false)] + // Invalid range and no type, no array + [TestCase("( : )", "", 0, 0, false, false)] + [TestCase("(:)", "", 0, 0, false, false)] + [TestCase("()", "", 0, 0, false, false)] + // No range and no type, no array + [TestCase("", "", 0, 0, false, true)] + // Valid type and array, no range [TestCase("int...", "int", 0, 0, true, true)] + [TestCase("float...", "float", 0, 0, true, true)] + [TestCase("string...", "string", 0, 0, true, true)] + [TestCase("choice...", "choice", 0, 0, true, true)] + // Valid range and type, array + [TestCase("float(5:10)...", "float", 5, 10, true, true)] + [TestCase("float(5.5:10.5)...", "float", 5.5, 10.5, true, true)] + [TestCase("float(:60)...", "float", 0.5, 60, true, true)] + // No range and no type, array + [TestCase("...", "", 0, 0, true, false)] public static void TestTryParseCommandInputType(string type, string eType, float min, float max, bool isArray, bool successful) { AssertThat(Text.TryParseCommandInputType(type, out var result)).IsEqual(successful); From 53a675c2a4a73ce26674450a26218c45902d3db6 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Mon, 19 Feb 2024 21:11:51 +0100 Subject: [PATCH 17/46] Update csproj --- godot-yat.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/godot-yat.csproj b/godot-yat.csproj index f1fcf8d0..2a7fa1da 100644 --- a/godot-yat.csproj +++ b/godot-yat.csproj @@ -10,6 +10,9 @@ true NU1605 + + $(DefaultItemExcludes);test/**/* + From 8dbc934e796c937ee47fe441cf434a851f280824 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 11:42:40 +0100 Subject: [PATCH 18/46] Update csproj --- godot-yat.csproj | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/godot-yat.csproj b/godot-yat.csproj index 2a7fa1da..9ae4bbc8 100644 --- a/godot-yat.csproj +++ b/godot-yat.csproj @@ -1,23 +1,23 @@ - - net7.0 - true - godotyat - 11.0 - true - - - true - - NU1605 - - $(DefaultItemExcludes);test/**/* - - - - - - - - + + net7.0 + true + godotyat + 11.0 + true + + + true + + NU1605 + + $(DefaultItemExcludes);test/**/* + + + + + + + + From c1f43d713770227ebee871572cff88f6fa43ef52 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 11:50:35 +0100 Subject: [PATCH 19/46] Options with null type are no longer supported --- CHANGELOG.md | 2 ++ addons/yat/src/commands/Cowsay.cs | 16 ++++++++-------- addons/yat/src/commands/Load.cs | 6 +++--- addons/yat/src/commands/Ls.cs | 4 ++-- addons/yat/src/commands/Ping.cs | 2 +- addons/yat/src/commands/Quit.cs | 2 +- addons/yat/src/commands/Stats.cs | 16 ++++++++-------- addons/yat/src/commands/TraceRoute.cs | 2 +- addons/yat/src/commands/Whereami.cs | 4 ++-- 9 files changed, 28 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44bf5c8f..272fbc21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file. - CommandInputType record. - TryParseCommandInputType method for Text helper class. - CommandInput attribute. +- InvalidOption method to Messages class. ### Changed @@ -31,6 +32,7 @@ All notable changes to this project will be documented in this file. - The SceneAboutToChange signal in the Cs command now also gives the old path, not just the new one. - Renamed ping command options. - Argument & Option attributes inherit from CommandInput attribute. +- Options with null type are no longer supported. ### Removed diff --git a/addons/yat/src/commands/Cowsay.cs b/addons/yat/src/commands/Cowsay.cs index cb0ce5dd..dd7be070 100644 --- a/addons/yat/src/commands/Cowsay.cs +++ b/addons/yat/src/commands/Cowsay.cs @@ -10,14 +10,14 @@ namespace YAT.Commands; [Command("cowsay", "Make a cow say something.", "[b]Usage[/b]: cowsay [i]message[/i]")] [Argument("message", "string", "The message to make the cow say.")] -[Option("-b", null, "Borg", false)] -[Option("-d", null, "Dead", false)] -[Option("-g", null, "Greedy", false)] -[Option("-p", null, "Paranoid", false)] -[Option("-s", null, "Stoned", false)] -[Option("-t", null, "Tired", false)] -[Option("-w", null, "Wired", false)] -[Option("-y", null, "Youthful", false)] +[Option("-b", "bool", "Borg", false)] +[Option("-d", "bool", "Dead", false)] +[Option("-g", "bool", "Greedy", false)] +[Option("-p", "bool", "Paranoid", false)] +[Option("-s", "bool", "Stoned", false)] +[Option("-t", "bool", "Tired", false)] +[Option("-w", "bool", "Wired", false)] +[Option("-y", "bool", "Youthful", false)] public sealed class Cowsay : ICommand { private BaseTerminal _terminal; diff --git a/addons/yat/src/commands/Load.cs b/addons/yat/src/commands/Load.cs index 6538cb7d..6fa07cdb 100644 --- a/addons/yat/src/commands/Load.cs +++ b/addons/yat/src/commands/Load.cs @@ -7,7 +7,7 @@ namespace YAT.Commands; [Command("load", "Loads specified object into the scene.", "[b]Usage[/b]: load [i]object_path[/i]")] [Argument("object_path", "string", "The object path to load.")] -[Option("-absolute", null, "If true, the object will be loaded at the origin, otherwise relative positioning will be used.", false)] +[Option("-absolute", "bool", "If true, the object will be loaded at the origin, otherwise relative positioning will be used.", false)] [Option("-x", "float", "The X position of the object.", 0f)] [Option("-y", "float", "The Y position of the object.", 0f)] [Option("-z", "float", "The Z position of the object.", -5f)] @@ -17,8 +17,8 @@ namespace YAT.Commands; [Option("-sx", "float", "The X scale of the object.", 1f)] [Option("-sy", "float", "The Y scale of the object.", 1f)] [Option("-sz", "float", "The Z scale of the object.", 1f)] -[Option("-hidden", null, "The object will be hidden.", false)] -[Option("-2d", null, "The object will be loaded as a 2D object.", false)] +[Option("-hidden", "bool", "The object will be hidden.", false)] +[Option("-2d", "bool", "The object will be loaded as a 2D object.", false)] public sealed class Load : ICommand { public CommandResult Execute(CommandData data) diff --git a/addons/yat/src/commands/Ls.cs b/addons/yat/src/commands/Ls.cs index 93cdd286..9075650c 100644 --- a/addons/yat/src/commands/Ls.cs +++ b/addons/yat/src/commands/Ls.cs @@ -13,8 +13,8 @@ namespace YAT.Commands; [Command("ls", "Lists the contents of the current directory.", "[b]Usage[/b]: ls")] [Argument("path", "string", "The path to list the contents of.")] -[Option("-n", null, "Displays the children of the current node.", false)] -[Option("-m", null, "Lists the methods of the current node.", false)] +[Option("-n", "bool", "Displays the children of the current node.", false)] +[Option("-m", "bool", "Lists the methods of the current node.", false)] public sealed class Ls : ICommand { private BaseTerminal _terminal; diff --git a/addons/yat/src/commands/Ping.cs b/addons/yat/src/commands/Ping.cs index b5b7cdba..3093c76a 100644 --- a/addons/yat/src/commands/Ping.cs +++ b/addons/yat/src/commands/Ping.cs @@ -17,7 +17,7 @@ namespace YAT.Commands; [Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000)] [Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30)] [Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32)] -[Option("-f", null, "Specifies that the packet can be fragmented.", false)] +[Option("-f", "bool", "Specifies that the packet can be fragmented.", false)] [Option("-delay", "int(1:10)", "The delay between pings in seconds.", 1)] [Option("-limit", "int(1:255)", "The maximum number of pings to send.", 0)] public sealed class Ping : ICommand diff --git a/addons/yat/src/commands/Quit.cs b/addons/yat/src/commands/Quit.cs index ecfc0f1e..87427625 100644 --- a/addons/yat/src/commands/Quit.cs +++ b/addons/yat/src/commands/Quit.cs @@ -6,7 +6,7 @@ namespace YAT.Commands; [Command("quit", "By default quits the game.", "[b]Usage[/b]: quit", "exit")] -[Option("-t", null, "Closes the terminal.", false)] +[Option("-t", "bool", "Closes the terminal.", false)] public sealed class Quit : ICommand { private YAT _yat; diff --git a/addons/yat/src/commands/Stats.cs b/addons/yat/src/commands/Stats.cs index fb46d2c6..13235199 100644 --- a/addons/yat/src/commands/Stats.cs +++ b/addons/yat/src/commands/Stats.cs @@ -9,14 +9,14 @@ namespace YAT.Commands; [Command("stats", "Manages the game monitor.", "[b]Usage:[/b] stats", "st")] -[Option("-all", null, "Shows all the information in the game monitor.", false)] -[Option("-fps", null, "Shows the FPS in the game monitor.", false)] -[Option("-os", null, "Shows the OS information in the game monitor.", false)] -[Option("-cpu", null, "Shows the CPU information in the game monitor.", false)] -[Option("-mem", null, "Shows memory information in the game monitor.", false)] -[Option("-engine", null, "Shows the engine information in the game monitor.", false)] -[Option("-objects", null, "Shows the objects information in the game monitor.", false)] -[Option("-lookingat", null, "Shows the info about node the player is looking at. Only in 3D.", false)] +[Option("-all", "bool", "Shows all the information in the game monitor.", false)] +[Option("-fps", "bool", "Shows the FPS in the game monitor.", false)] +[Option("-os", "bool", "Shows the OS information in the game monitor.", false)] +[Option("-cpu", "bool", "Shows the CPU information in the game monitor.", false)] +[Option("-mem", "bool", "Shows memory information in the game monitor.", false)] +[Option("-engine", "bool", "Shows the engine information in the game monitor.", false)] +[Option("-objects", "bool", "Shows the objects information in the game monitor.", false)] +[Option("-lookingat", "bool", "Shows the info about node the player is looking at. Only in 3D.", false)] public sealed class Stats : ICommand { private YAT _yat; diff --git a/addons/yat/src/commands/TraceRoute.cs b/addons/yat/src/commands/TraceRoute.cs index 5fab8181..11c70a22 100644 --- a/addons/yat/src/commands/TraceRoute.cs +++ b/addons/yat/src/commands/TraceRoute.cs @@ -15,7 +15,7 @@ namespace YAT.Commands; [Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000)] [Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30)] [Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32)] -[Option("-f", null, "Specifies that the packet can be fragmented.", false)] +[Option("-f", "bool", "Specifies that the packet can be fragmented.", false)] [Threaded] public sealed class TraceRoute : ICommand { diff --git a/addons/yat/src/commands/Whereami.cs b/addons/yat/src/commands/Whereami.cs index 60f0f9f6..d2245bda 100644 --- a/addons/yat/src/commands/Whereami.cs +++ b/addons/yat/src/commands/Whereami.cs @@ -5,8 +5,8 @@ namespace YAT.Commands; [Command("whereami", "Prints the current scene name and path.", "[b]Usage[/b]: whereami", "wai")] -[Option("-l", null, "Prints the full path to the scene file.", false)] -[Option("-s", null, "Prints info about currently selected node.", false)] +[Option("-l", "bool", "Prints the full path to the scene file.", false)] +[Option("-s", "bool", "Prints info about currently selected node.", false)] public sealed class Whereami : ICommand { public CommandResult Execute(CommandData data) From 4fc67dbbb431ecc4f5e4a715841086a3b38787cc Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 11:52:18 +0100 Subject: [PATCH 20/46] Add null check for type --- addons/yat/src/attributes/CommandInputAttribute.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/yat/src/attributes/CommandInputAttribute.cs b/addons/yat/src/attributes/CommandInputAttribute.cs index d75645ef..b48fcf94 100644 --- a/addons/yat/src/attributes/CommandInputAttribute.cs +++ b/addons/yat/src/attributes/CommandInputAttribute.cs @@ -18,6 +18,9 @@ public CommandInputAttribute(string name, string type, string description = "") Name = name; Description = description; + if (string.IsNullOrEmpty(type)) + GD.PushError($"Invalid command input type '{type}' for command '{name}'."); + foreach (var t in type.Split('|')) { if (Text.TryParseCommandInputType(t, out var commandInputType)) From fcb6b2559072605ff7c33c4e839e1aa00363a303 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 12:06:53 +0100 Subject: [PATCH 21/46] Fix null reference exception --- .../command_validator/CommandValidator.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index 6a9a2266..755342bf 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -32,7 +32,7 @@ public bool ValidatePassedData(ICommand command, string[] passedData, out Dic CommandAttribute commandAttribute = command.GetAttribute(); - data = null; + data = new(); if (commandAttribute is null) { @@ -46,7 +46,9 @@ public bool ValidatePassedData(ICommand command, string[] passedData, out Dic { if (passedData.Length < dataAttrArr.Length) { - Terminal.Output.Error(Messages.MissingArguments(commandAttribute.Name, data.Keys.ToArray())); + Terminal.Output.Error(Messages.MissingArguments( + commandAttribute.Name, dataAttrArr.Select(a => a.Name).ToArray()) + ); return false; } @@ -59,6 +61,8 @@ public bool ValidatePassedData(ICommand command, string[] passedData, out Dic commandAttribute.Name, data, passedData, dataAttrArr as OptionAttribute[] ); + data = null; + return false; } @@ -69,9 +73,9 @@ private bool ValidateCommandArguments( ArgumentAttribute[] arguments ) { - for (int i = 0; i < passedArgs.Length; i++) - if (!ValidateCommandArgument(commandName, arguments[i], validatedArgs, passedArgs[i])) return false; - + for (int i = 0; i < arguments.Length; i++) + if (!ValidateCommandArgument(commandName, arguments[i], validatedArgs, passedArgs[i])) + return false; return true; } @@ -82,8 +86,9 @@ private bool ValidateCommandOptions( OptionAttribute[] options ) { - foreach (var opt in options) - if (!ValidateCommandOption(commandName, opt, validatedOpts, passedOpts)) return false; + foreach (var opt in options) if ( + !ValidateCommandOption(commandName, opt, validatedOpts, passedOpts) + ) return false; return true; } @@ -161,7 +166,7 @@ private static object ConvertStringToType(CommandInputType type, string value) if (t == "string" || t == value) return value; if (t == "bool") return bool.TryParse(value, out bool result) ? result : null; - if (t.StartsWith("int")) return (int)TryConvertNumeric(type, value); + if (t.StartsWith("int")) return TryConvertNumeric(type, value); if (t.StartsWith("float")) return TryConvertNumeric(type, value); return null; From 45e1b8943c021ddaa5badef5d0b4a527ddb4fad5 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 13:26:15 +0100 Subject: [PATCH 22/46] Improve debug launch configurations (breakpoints now work) --- .vscode/launch.json | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1646c2f3..5ee07449 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,9 @@ "program": "${env:GODOT}", "args": [], "cwd": "${workspaceFolder}", - "stopAtEntry": false + "stopAtEntry": false, + "justMyCode": false, + "suppressJITOptimizations": true }, // Debug the scene that matches the name of the currently open *.cs file // (if there's a scene with the same name in the same directory). @@ -26,6 +28,28 @@ "program": "${env:GODOT}", "args": ["${fileDirname}/${fileBasenameNoExtension}.tscn"], "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "justMyCode": false, + "suppressJITOptimizations": true + }, + { + "name": "🕹 Run Game", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${env:GODOT}", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false + }, + { + "name": "🎭 Run Current Scene", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${env:GODOT}", + "args": ["${fileDirname}/${fileBasenameNoExtension}.tscn"], + "cwd": "${workspaceFolder}", "stopAtEntry": false } ] From 549998a1c58aa157cfa427ad93af75dbb01d2db3 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 14:58:46 +0100 Subject: [PATCH 23/46] Correctly handle boolean options --- .../command_validator/CommandValidator.cs | 62 +++++++++++++++---- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index 755342bf..22b46130 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -121,42 +121,78 @@ public bool ValidateCommandArgument( private bool ValidateCommandOption( string commandName, - OptionAttribute options, + OptionAttribute option, Dictionary validatedOpts, string[] passedOpts ) { - foreach (var opt in passedOpts) + var lookup = option.Types.ToLookup(t => t.Type); + + foreach (var passedOpt in passedOpts) { - if (!opt.StartsWith(options.Name)) continue; + if (!passedOpt.StartsWith(option.Name)) continue; + + string[] tokens = passedOpt.Split('='); - string[] split = opt.Split('='); + if (lookup.Contains("bool") && tokens.Length == 1) + { + validatedOpts[option.Name] = true; + return true; + } - if (split.Length != 2) + if ((!lookup.Contains("bool") && tokens.Length != 2) + || (lookup.Contains("bool") && tokens.Length != 1) + ) { - Terminal.Output.Error(Messages.InvalidOption(commandName, opt)); + Terminal.Output.Error(Messages.InvalidOption(commandName, passedOpt)); return false; } - string value = split[1]; + string value = tokens[1]; - foreach (var type in options.Types) + foreach (var type in option.Types) { + if (type.IsArray) + { + string[] values = value.Split(','); + + if (values.Length == 0) + { + Terminal.Output.Error(Messages.InvalidOption(commandName, passedOpt)); + return false; + } + + List convertedArr = new(); + + foreach (var v in values) + { + object convertedArrValue = ConvertStringToType(type, v); + + if (convertedArrValue is null) + { + Terminal.Output.Error(Messages.InvalidOption(commandName, passedOpt)); + return false; + } + + convertedArr.Add(convertedArrValue); + validatedOpts[option.Name] = convertedArr.ToArray(); + } + } + object converted = ConvertStringToType(type, value); if (converted is not null) { - validatedOpts[options.Name] = converted; + validatedOpts[option.Name] = converted; return true; } - else - Terminal.Output.Error(Messages.InvalidOption(commandName, opt)); } + Terminal.Output.Error(Messages.InvalidOption(commandName, passedOpt)); } - validatedOpts[options.Name] = options.DefaultValue; + validatedOpts[option.Name] = option.DefaultValue; - return false; + return true; } private static object ConvertStringToType(CommandInputType type, string value) From ae59b5201197b007734176acaef9c7253b1dd900 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 15:43:00 +0100 Subject: [PATCH 24/46] Add ValueOutOfRange & ArgumentValueOutOfRange messages --- CHANGELOG.md | 4 +++- addons/yat/src/helpers/Messages.cs | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 272fbc21..3af8f96a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,9 @@ All notable changes to this project will be documented in this file. - CommandInputType record. - TryParseCommandInputType method for Text helper class. - CommandInput attribute. -- InvalidOption method to Messages class. +- Messages: + - InvalidOption, + - ValueOutOfRange ### Changed diff --git a/addons/yat/src/helpers/Messages.cs b/addons/yat/src/helpers/Messages.cs index 31f09f1f..cefd04b5 100644 --- a/addons/yat/src/helpers/Messages.cs +++ b/addons/yat/src/helpers/Messages.cs @@ -16,6 +16,8 @@ public static class Messages public static string InvalidOption(string commandName, string opt) => $"{commandName} does not have an option named {opt}."; public static string NodeHasNoChildren(string path) => $"{path} has no children."; + public static string ValueOutOfRange(string value, float min, float max) => $"{value} is out of range. Expected a value between {min} and {max}."; + public static string ArgumentValueOutOfRange(string command, string argument, float min, float max) => $"{command} expected {argument} to be between {min} and {max}."; public static string DisposedNode => "The node has been disposed."; } From 6ddcc55228c0c6b22e58fed7c888deb1cff91d43 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 15:56:31 +0100 Subject: [PATCH 25/46] Better handle errors & value range --- .../command_validator/CommandValidator.cs | 98 ++++++++++++++----- 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index 22b46130..e2f267c8 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -103,17 +103,16 @@ public bool ValidateCommandArgument( { foreach (var type in argument.Types) { - object converted = ConvertStringToType(type, passedArg); - - if (converted is not null) + if (TryConvertStringToType(passedArg, type, out var converted)) { validatedArgs[argument.Name] = converted; return true; } else if (log) - Terminal.Output.Error(Messages.InvalidArgument( - commandName, argument.Name, string.Join(", ", argument.Types) - )); + PrintNumericError( + commandName, argument.Name, argument.Types, converted, + type.Min, type.Max + ); } return false; @@ -144,7 +143,9 @@ string[] passedOpts || (lookup.Contains("bool") && tokens.Length != 1) ) { - Terminal.Output.Error(Messages.InvalidOption(commandName, passedOpt)); + Terminal.Output.Error( + Messages.InvalidArgument(commandName, passedOpt, option.Name) + ); return false; } @@ -158,7 +159,9 @@ string[] passedOpts if (values.Length == 0) { - Terminal.Output.Error(Messages.InvalidOption(commandName, passedOpt)); + Terminal.Output.Error( + Messages.InvalidArgument(commandName, passedOpt, option.Name) + ); return false; } @@ -166,28 +169,41 @@ string[] passedOpts foreach (var v in values) { - object convertedArrValue = ConvertStringToType(type, v); - - if (convertedArrValue is null) + if (!TryConvertStringToType(v, type, out var convertedArrValue)) { - Terminal.Output.Error(Messages.InvalidOption(commandName, passedOpt)); + PrintNumericError( + commandName, option.Name, option.Types, convertedArrValue, + type.Min, type.Max + ); return false; } convertedArr.Add(convertedArrValue); validatedOpts[option.Name] = convertedArr.ToArray(); } + + return true; } - object converted = ConvertStringToType(type, value); + if (string.IsNullOrEmpty(value)) + { + Terminal.Output.Error(Messages.MissingValue(commandName, option.Name)); + return false; + } - if (converted is not null) + if (!TryConvertStringToType(value, type, out var converted)) { - validatedOpts[option.Name] = converted; - return true; + PrintNumericError( + commandName, option.Name, option.Types, converted, + type.Min, type.Max + ); + return false; } + + validatedOpts[option.Name] = converted; + return true; } - Terminal.Output.Error(Messages.InvalidOption(commandName, passedOpt)); + Terminal.Output.Error(Messages.InvalidArgument(commandName, passedOpt, option.Name)); } validatedOpts[option.Name] = option.DefaultValue; @@ -195,27 +211,55 @@ string[] passedOpts return true; } - private static object ConvertStringToType(CommandInputType type, string value) + private static bool TryConvertStringToType(string value, CommandInputType type, out object result) { var t = type.Type; + result = null; + + if (t == "string" || t == value) + { + result = value; + return true; + } - if (t == "string" || t == value) return value; - if (t == "bool") return bool.TryParse(value, out bool result) ? result : null; + if (t.StartsWith("int") || t.StartsWith("float")) + { + var status = TryConvertNumeric(value, type, out float r); + result = r; - if (t.StartsWith("int")) return TryConvertNumeric(type, value); - if (t.StartsWith("float")) return TryConvertNumeric(type, value); + return status; + } - return null; + return false; } - private static object TryConvertNumeric(CommandInputType type, string value) where T : notnull, IConvertible, IComparable, IComparable + private static bool TryConvertNumeric(string value, CommandInputType type, out T result) + where T : notnull, IConvertible, IComparable, IComparable { - if (!Numeric.TryConvert(value, out T result)) return null; + result = default; + + if (!Numeric.TryConvert(value, out result)) return false; if (type.Min != type.Max) return Numeric.IsWithinRange( result, type.Min, type.Max - ) ? result : null; + ); - return result; + return true; + } + + private void PrintNumericError( + string commandName, + string argumentName, + LinkedList types, + object value, float min, float max + ) + { + if (value is not null && !Numeric.IsWithinRange((float)value, min, max)) + Terminal.Output.Error(Messages.ArgumentValueOutOfRange( + commandName, argumentName, min, max + )); + else Terminal.Output.Error(Messages.InvalidArgument( + commandName, argumentName, string.Join(", ", types.Select(t => t.Type)) + )); } } From d4609fe0c7627e66e5d80456e75e161ef3b75089 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 15:57:29 +0100 Subject: [PATCH 26/46] Remove unnecessary comments --- addons/yat/src/helpers/Text.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/addons/yat/src/helpers/Text.cs b/addons/yat/src/helpers/Text.cs index 6a808c9b..028adbf6 100644 --- a/addons/yat/src/helpers/Text.cs +++ b/addons/yat/src/helpers/Text.cs @@ -3,31 +3,17 @@ using System.IO; using System.Linq; using System.Text; -using Godot; using YAT.Types; namespace YAT.Helpers; -/// -/// Provides helper methods for working with text. -/// public static class Text { - /// - /// Escapes BBCode tags in the given text by replacing '[' with '[lb]'. - /// - /// The text to escape. - /// The escaped text. public static string EscapeBBCode(string text) { return text.Replace("[", "[lb]"); } - /// - /// Makes the specified text bold by surrounding it with the appropriate tags. - /// - /// The text to make bold. - /// The specified text surrounded by bold tags. public static string MakeBold(string text) { return $"[b]{text}[/b]"; From 75352b88c86613d77aca9a50d48a5bcabcd30cfa Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 19:09:19 +0100 Subject: [PATCH 27/46] Use StringName where it makes sense --- .../yat/src/attributes/CommandInputAttribute.cs | 4 ++-- .../managers/command_manager/CommandManager.cs | 16 ++++++++++++---- addons/yat/src/types/CommandData.cs | 5 +++-- addons/yat/src/types/CommandInputType.cs | 4 +++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/addons/yat/src/attributes/CommandInputAttribute.cs b/addons/yat/src/attributes/CommandInputAttribute.cs index b48fcf94..f8ba0b58 100644 --- a/addons/yat/src/attributes/CommandInputAttribute.cs +++ b/addons/yat/src/attributes/CommandInputAttribute.cs @@ -9,11 +9,11 @@ namespace YAT.Attributes; [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] public class CommandInputAttribute : Attribute { - public string Name { get; private set; } + public StringName Name { get; private set; } public LinkedList Types { get; private set; } = new(); public string Description { get; private set; } - public CommandInputAttribute(string name, string type, string description = "") + public CommandInputAttribute(StringName name, string type, string description = "") { Name = name; Description = description; diff --git a/addons/yat/src/classes/managers/command_manager/CommandManager.cs b/addons/yat/src/classes/managers/command_manager/CommandManager.cs index e31d7fc7..f7d89ba0 100644 --- a/addons/yat/src/classes/managers/command_manager/CommandManager.cs +++ b/addons/yat/src/classes/managers/command_manager/CommandManager.cs @@ -53,20 +53,28 @@ public void Run(string[] args, BaseTerminal terminal) } ICommand command = Activator.CreateInstance(value) as ICommand; - Dictionary convertedArgs = null; - Dictionary convertedOpts = null; + Dictionary convertedArgs = null; + Dictionary convertedOpts = null; if (command.GetAttribute() is null) { if (!terminal.CommandValidator.ValidatePassedData( command, args[1..], out convertedArgs - )) return; + )) + { + GD.Print("Failed to validate passed arguments."); + return; + } if (command.GetAttributes() is not null) { if (!terminal.CommandValidator.ValidatePassedData( command, args[1..], out convertedOpts - )) return; + )) + { + GD.Print("Failed to validate passed options."); + return; + } } } diff --git a/addons/yat/src/types/CommandData.cs b/addons/yat/src/types/CommandData.cs index e873c780..68e14944 100644 --- a/addons/yat/src/types/CommandData.cs +++ b/addons/yat/src/types/CommandData.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading; +using Godot; using YAT.Interfaces; using YAT.Scenes; @@ -10,8 +11,8 @@ public sealed record CommandData( BaseTerminal Terminal, ICommand Command, string[] RawData, - Dictionary Arguments, - Dictionary Options, + Dictionary Arguments, + Dictionary Options, CancellationToken CancellationToken ); } diff --git a/addons/yat/src/types/CommandInputType.cs b/addons/yat/src/types/CommandInputType.cs index 154d6c1a..67da85b2 100644 --- a/addons/yat/src/types/CommandInputType.cs +++ b/addons/yat/src/types/CommandInputType.cs @@ -1,3 +1,5 @@ +using Godot; + namespace YAT.Types; -public record CommandInputType(string Type, float Min, float Max, bool IsArray); +public record CommandInputType(StringName Type, float Min, float Max, bool IsArray); From 657b9cc95f2e6049465f30e915b7c175f8646cea Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 19:16:16 +0100 Subject: [PATCH 28/46] Adjust code to use StringName --- addons/yat/src/commands/Stats.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/yat/src/commands/Stats.cs b/addons/yat/src/commands/Stats.cs index 13235199..67aac069 100644 --- a/addons/yat/src/commands/Stats.cs +++ b/addons/yat/src/commands/Stats.cs @@ -35,7 +35,7 @@ public CommandResult Execute(CommandData data) return Close(); } - private CommandResult Open(Dictionary opts) + private CommandResult Open(Dictionary opts) { bool all = (bool)opts["-all"]; bool fps = (bool)opts["-fps"]; From 9ed88f1da98ed156d16225b485bf3a5624bbd985 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 19:17:10 +0100 Subject: [PATCH 29/46] Use StringName's --- .../command_validator/CommandValidator.cs | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index e2f267c8..784f1f4e 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -22,7 +22,7 @@ public partial class CommandValidator : Node /// The arguments passed to the command. /// The dictionary of arguments. /// True if the passed data is valid, false otherwise. - public bool ValidatePassedData(ICommand command, string[] passedData, out Dictionary data) where T : CommandInputAttribute + public bool ValidatePassedData(ICommand command, string[] passedData, out Dictionary data) where T : CommandInputAttribute { Type type = typeof(T); Type argType = typeof(ArgumentAttribute); @@ -47,7 +47,7 @@ public bool ValidatePassedData(ICommand command, string[] passedData, out Dic if (passedData.Length < dataAttrArr.Length) { Terminal.Output.Error(Messages.MissingArguments( - commandAttribute.Name, dataAttrArr.Select(a => a.Name).ToArray()) + commandAttribute.Name, dataAttrArr.Select(a => a.Name).ToArray()) ); return false; } @@ -68,7 +68,7 @@ public bool ValidatePassedData(ICommand command, string[] passedData, out Dic private bool ValidateCommandArguments( string commandName, - Dictionary validatedArgs, + Dictionary validatedArgs, string[] passedArgs, ArgumentAttribute[] arguments ) @@ -81,7 +81,7 @@ ArgumentAttribute[] arguments private bool ValidateCommandOptions( string commandName, - Dictionary validatedOpts, + Dictionary validatedOpts, string[] passedOpts, OptionAttribute[] options ) @@ -96,7 +96,7 @@ OptionAttribute[] options public bool ValidateCommandArgument( string commandName, ArgumentAttribute argument, - Dictionary validatedArgs, + Dictionary validatedArgs, string passedArg, bool log = true ) @@ -108,11 +108,12 @@ public bool ValidateCommandArgument( validatedArgs[argument.Name] = converted; return true; } - else if (log) - PrintNumericError( - commandName, argument.Name, argument.Types, converted, - type.Min, type.Max - ); + + if (log) PrintNumericError( + commandName, argument.Name, + argument.Types, converted, + type.Min, type.Max + ); } return false; @@ -121,7 +122,7 @@ public bool ValidateCommandArgument( private bool ValidateCommandOption( string commandName, OptionAttribute option, - Dictionary validatedOpts, + Dictionary validatedOpts, string[] passedOpts ) { @@ -131,16 +132,17 @@ string[] passedOpts { if (!passedOpt.StartsWith(option.Name)) continue; + bool isBool = lookup.Contains("bool"); string[] tokens = passedOpt.Split('='); - if (lookup.Contains("bool") && tokens.Length == 1) + if (isBool && tokens.Length == 1) { validatedOpts[option.Name] = true; return true; } - if ((!lookup.Contains("bool") && tokens.Length != 2) - || (lookup.Contains("bool") && tokens.Length != 1) + if ((!isBool && tokens.Length != 2) + || (isBool && tokens.Length != 1) ) { Terminal.Output.Error( @@ -211,11 +213,13 @@ string[] passedOpts return true; } - private static bool TryConvertStringToType(string value, CommandInputType type, out object result) + private static bool TryConvertStringToType(StringName value, CommandInputType type, out object result) { - var t = type.Type; + var t = (string)type.Type; result = null; + GD.Print($"Trying to convert '{value}' to type '{t}'"); + if (t == "string" || t == value) { result = value; @@ -233,11 +237,9 @@ private static bool TryConvertStringToType(string value, CommandInputType type, return false; } - private static bool TryConvertNumeric(string value, CommandInputType type, out T result) + private static bool TryConvertNumeric(StringName value, CommandInputType type, out T result) where T : notnull, IConvertible, IComparable, IComparable { - result = default; - if (!Numeric.TryConvert(value, out result)) return false; if (type.Min != type.Max) return Numeric.IsWithinRange( @@ -248,8 +250,8 @@ private static bool TryConvertNumeric(string value, CommandInputType type, ou } private void PrintNumericError( - string commandName, - string argumentName, + StringName commandName, + StringName argumentName, LinkedList types, object value, float min, float max ) @@ -258,7 +260,7 @@ private void PrintNumericError( Terminal.Output.Error(Messages.ArgumentValueOutOfRange( commandName, argumentName, min, max )); - else Terminal.Output.Error(Messages.InvalidArgument( + else if (value is null) Terminal.Output.Error(Messages.InvalidArgument( commandName, argumentName, string.Join(", ", types.Select(t => t.Type)) )); } From 595ca08b03838bdbc5565ef4110ce044237ecb7d Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 20:50:16 +0100 Subject: [PATCH 30/46] Update InvalidArgument message --- addons/yat/src/helpers/Messages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/yat/src/helpers/Messages.cs b/addons/yat/src/helpers/Messages.cs index cefd04b5..6dd169b6 100644 --- a/addons/yat/src/helpers/Messages.cs +++ b/addons/yat/src/helpers/Messages.cs @@ -11,7 +11,7 @@ public static class Messages public static string MissingValue(string command, string option) => $"{command} expected {option} to be provided."; public static string InvalidNodePath(string path) => $"{path} is not a valid node path."; - public static string InvalidArgument(string command, string argument, string expected) => $"{command} expected {argument} to be an {expected}."; + public static string InvalidArgument(string command, string argument, string expected) => $"{command} expected {argument} to be an: {expected}."; public static string InvalidMethod(string method) => $"{method} is not a valid method."; public static string InvalidOption(string commandName, string opt) => $"{commandName} does not have an option named {opt}."; From 3ca06ebef25d927e03b2dfaee70cc4ca5765c073 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 21:04:01 +0100 Subject: [PATCH 31/46] Add support for limiting string length --- CHANGELOG.md | 5 ++++- .../command_validator/CommandValidator.cs | 13 +++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3af8f96a..7a9bf425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,12 +20,13 @@ All notable changes to this project will be documented in this file. - Attributes: - Description - Usage + - CommandInput - CommandInputType record. - TryParseCommandInputType method for Text helper class. -- CommandInput attribute. - Messages: - InvalidOption, - ValueOutOfRange +- String in arguments and options can have defined min and max length. ### Changed @@ -35,6 +36,8 @@ All notable changes to this project will be documented in this file. - Renamed ping command options. - Argument & Option attributes inherit from CommandInput attribute. - Options with null type are no longer supported. +- The way of defining the type for arguments and options has changed. +- CommandData now uses StringName instead of string for dictionaries. ### Removed diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index 784f1f4e..af68c6f5 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -218,9 +218,15 @@ private static bool TryConvertStringToType(StringName value, CommandInputType ty var t = (string)type.Type; result = null; - GD.Print($"Trying to convert '{value}' to type '{t}'"); + if (t == value) + { + result = value; + return true; + } - if (t == "string" || t == value) + if (t == "string" && (Numeric.IsWithinRange( + ((string)value).Length, type.Min, type.Max) || type.Min == type.Max + )) { result = value; return true; @@ -250,8 +256,7 @@ private static bool TryConvertNumeric(StringName value, CommandInputType type } private void PrintNumericError( - StringName commandName, - StringName argumentName, + StringName commandName, StringName argumentName, LinkedList types, object value, float min, float max ) From e269b7934ab3103f54486ba9a491eb7833845278 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 21:23:53 +0100 Subject: [PATCH 32/46] Add EStringConversionResult --- CHANGELOG.md | 10 ++++++---- addons/yat/src/enums/EStringConversionResult.cs | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 addons/yat/src/enums/EStringConversionResult.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a9bf425..b2e6e6c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,15 @@ All notable changes to this project will be documented in this file. - More results to ECommandResult. - CommandResult record. - Static methods to ICommand that corresponds to the ECommandResult results. -- ESceneChangeFailureReason enum. - Networking helper class. - NetworkingOptions record. - -limit option to ping command. +- CommandInputType record. +- TryParseCommandInputType method for Text helper class. +- String in arguments and options can have defined min and max length. +- Enum: + - ESceneChangeFailureReason + - EStringConversionResult - Commands: - TraceRoute - Load @@ -21,12 +26,9 @@ All notable changes to this project will be documented in this file. - Description - Usage - CommandInput -- CommandInputType record. -- TryParseCommandInputType method for Text helper class. - Messages: - InvalidOption, - ValueOutOfRange -- String in arguments and options can have defined min and max length. ### Changed diff --git a/addons/yat/src/enums/EStringConversionResult.cs b/addons/yat/src/enums/EStringConversionResult.cs new file mode 100644 index 00000000..de9387ab --- /dev/null +++ b/addons/yat/src/enums/EStringConversionResult.cs @@ -0,0 +1,8 @@ +namespace YAT.Enums; + +public enum EStringConversionResult +{ + Success, + Invalid, + OutOfRange +} From 19fa7bfa07a099d0c12eb15d8409041dc321a629 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Tue, 20 Feb 2024 22:07:10 +0100 Subject: [PATCH 33/46] Methods StartsWith & EndsWith from Text class are now extensions of string type --- CHANGELOG.md | 1 + addons/yat/src/helpers/Text.cs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2e6e6c4..78d63e5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ All notable changes to this project will be documented in this file. - Options with null type are no longer supported. - The way of defining the type for arguments and options has changed. - CommandData now uses StringName instead of string for dictionaries. +- Methods StartsWith & EndsWith from Text class are now extensions of string type. ### Removed diff --git a/addons/yat/src/helpers/Text.cs b/addons/yat/src/helpers/Text.cs index 028adbf6..09916027 100644 --- a/addons/yat/src/helpers/Text.cs +++ b/addons/yat/src/helpers/Text.cs @@ -97,22 +97,22 @@ public static string[] ConcatenateSentence(string[] strings) return modifiedStrings.ToArray(); } - public static bool StartsWith(string text, params char[] value) + public static bool StartsWith(this string text, params char[] value) { return value.Any(text.StartsWith); } - public static bool StartsWith(string text, params string[] value) + public static bool StartsWith(this string text, params string[] value) { return value.Any(text.StartsWith); } - public static bool EndsWith(string text, params char[] value) + public static bool EndsWith(this string text, params char[] value) { return value.Any(text.EndsWith); } - public static bool EndsWith(string text, params string[] value) + public static bool EndsWith(this string text, params string[] value) { return value.Any(text.EndsWith); } From 22f5bb13e269d47de67526c1cad8397aab47768e Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 11:56:13 +0100 Subject: [PATCH 34/46] It is no longer possible to call the history command via itself --- CHANGELOG.md | 4 ++++ addons/yat/src/commands/History.cs | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d63e5a..78c34c76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,10 @@ All notable changes to this project will be documented in this file. - FailureReason enum. +### Fixed + +- It is no longer possible to call the history command via itself, which could have caused an endless loop. + ## [1.23.0-beta 2024-02-13] ### Added diff --git a/addons/yat/src/commands/History.cs b/addons/yat/src/commands/History.cs index d9fb1376..7bd5e67e 100644 --- a/addons/yat/src/commands/History.cs +++ b/addons/yat/src/commands/History.cs @@ -18,7 +18,7 @@ namespace YAT.Commands; "[b]list[/b]: Lists the history.", "hist" )] -[Argument("action", "[clear, list, int]", "The action to perform.")] +[Argument("action", "clear|list|int", "The action to perform.")] public partial class History : ICommand { private YAT _yat; @@ -62,6 +62,12 @@ private void ExecuteFromHistory(int index) var command = _terminal.History.ElementAt(index); + if (command.StartsWith("history", "hist")) + { + _terminal.Print("Cannot execute history command from history."); + return; + } + _terminal.Print( $"Executing command at index {index}: {Text.EscapeBBCode(command)}" ); From c6e23987a085f0b57f02666721212f5343b39028 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 11:56:23 +0100 Subject: [PATCH 35/46] Add tag to the project --- project.godot | 1 + 1 file changed, 1 insertion(+) diff --git a/project.godot b/project.godot index 8476a261..0a834540 100644 --- a/project.godot +++ b/project.godot @@ -11,6 +11,7 @@ config_version=5 [application] config/name="godot-yat" +config/tags=PackedStringArray("addon") run/main_scene="res://example/scenes/main_menu/MainMenu.tscn" config/features=PackedStringArray("4.3", "C#") config/icon="res://yat_icon.png" From 7ee1e433a5b2151171abfba254369be1ccf47d33 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 12:42:28 +0100 Subject: [PATCH 36/46] Fix issues with casting & error logging --- .../command_validator/CommandValidator.cs | 115 ++++++++++-------- .../components/command_info/CommandInfo.cs | 1 - 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index af68c6f5..4906e533 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using YAT.Attributes; +using YAT.Enums; using YAT.Helpers; using YAT.Interfaces; using YAT.Types; @@ -14,6 +15,8 @@ public partial class CommandValidator : Node { [Export] public BaseTerminal Terminal { get; set; } + private StringName _commandName; + /// /// Validates the passed data for a given command and returns a dictionary of arguments. /// @@ -32,6 +35,7 @@ public bool ValidatePassedData(ICommand command, string[] passedData, out Dic CommandAttribute commandAttribute = command.GetAttribute(); + _commandName = commandAttribute.Name; data = new(); if (commandAttribute is null) @@ -53,12 +57,12 @@ public bool ValidatePassedData(ICommand command, string[] passedData, out Dic } return ValidateCommandArguments( - commandAttribute.Name, data, passedData, dataAttrArr as ArgumentAttribute[] + data, passedData, dataAttrArr as ArgumentAttribute[] ); } else if (type == optType) return ValidateCommandOptions( - commandAttribute.Name, data, passedData, dataAttrArr as OptionAttribute[] + data, passedData, dataAttrArr as OptionAttribute[] ); data = null; @@ -67,60 +71,58 @@ public bool ValidatePassedData(ICommand command, string[] passedData, out Dic } private bool ValidateCommandArguments( - string commandName, Dictionary validatedArgs, string[] passedArgs, ArgumentAttribute[] arguments ) { for (int i = 0; i < arguments.Length; i++) - if (!ValidateCommandArgument(commandName, arguments[i], validatedArgs, passedArgs[i])) + if (!ValidateCommandArgument(arguments[i], validatedArgs, passedArgs[i])) return false; return true; } private bool ValidateCommandOptions( - string commandName, Dictionary validatedOpts, string[] passedOpts, OptionAttribute[] options ) { foreach (var opt in options) if ( - !ValidateCommandOption(commandName, opt, validatedOpts, passedOpts) + !ValidateCommandOption(opt, validatedOpts, passedOpts) ) return false; return true; } public bool ValidateCommandArgument( - string commandName, ArgumentAttribute argument, Dictionary validatedArgs, string passedArg, bool log = true ) { + int index = 0; + foreach (var type in argument.Types) { - if (TryConvertStringToType(passedArg, type, out var converted)) + var status = TryConvertStringToType(passedArg, type, out var converted); + + if (status == EStringConversionResult.Success) { validatedArgs[argument.Name] = converted; return true; } - if (log) PrintNumericError( - commandName, argument.Name, - argument.Types, converted, - type.Min, type.Max - ); + if (log && index == argument.Types.Count - 1) + PrintErr(status, argument.Name, argument.Types, converted, type.Min, type.Max); + index++; } return false; } private bool ValidateCommandOption( - string commandName, OptionAttribute option, Dictionary validatedOpts, string[] passedOpts @@ -146,7 +148,7 @@ string[] passedOpts ) { Terminal.Output.Error( - Messages.InvalidArgument(commandName, passedOpt, option.Name) + Messages.InvalidArgument(_commandName, passedOpt, option.Name) ); return false; } @@ -162,7 +164,7 @@ string[] passedOpts if (values.Length == 0) { Terminal.Output.Error( - Messages.InvalidArgument(commandName, passedOpt, option.Name) + Messages.InvalidArgument(_commandName, passedOpt, option.Name) ); return false; } @@ -171,12 +173,11 @@ string[] passedOpts foreach (var v in values) { - if (!TryConvertStringToType(v, type, out var convertedArrValue)) + var st = TryConvertStringToType(v, type, out var convertedArrValue); + + if (st != EStringConversionResult.Success) { - PrintNumericError( - commandName, option.Name, option.Types, convertedArrValue, - type.Min, type.Max - ); + PrintErr(st, option.Name, option.Types, convertedArrValue, type.Min, type.Max); return false; } @@ -189,23 +190,23 @@ string[] passedOpts if (string.IsNullOrEmpty(value)) { - Terminal.Output.Error(Messages.MissingValue(commandName, option.Name)); + Terminal.Output.Error(Messages.MissingValue(_commandName, option.Name)); return false; } - if (!TryConvertStringToType(value, type, out var converted)) + var status = TryConvertStringToType(value, type, out var converted); + + if (status != EStringConversionResult.Success) { - PrintNumericError( - commandName, option.Name, option.Types, converted, - type.Min, type.Max - ); + PrintErr(status, option.Name, option.Types, converted, type.Min, type.Max); + return false; } validatedOpts[option.Name] = converted; return true; } - Terminal.Output.Error(Messages.InvalidArgument(commandName, passedOpt, option.Name)); + Terminal.Output.Error(Messages.InvalidArgument(_commandName, passedOpt, option.Name)); } validatedOpts[option.Name] = option.DefaultValue; @@ -213,34 +214,39 @@ string[] passedOpts return true; } - private static bool TryConvertStringToType(StringName value, CommandInputType type, out object result) + private static EStringConversionResult TryConvertStringToType( + string value, CommandInputType type, out object result + ) { var t = (string)type.Type; result = null; - if (t == value) + if (t.StartsWith("int", "float")) { - result = value; - return true; + var status = TryConvertNumeric(value, type, out float r); + result = r; + + return status + ? EStringConversionResult.Success + : EStringConversionResult.OutOfRange; } - if (t == "string" && (Numeric.IsWithinRange( - ((string)value).Length, type.Min, type.Max) || type.Min == type.Max - )) + if (t == "string") { + if (type.Min != type.Max && !Numeric.IsWithinRange(((string)value).Length, type.Min, type.Max) + ) return EStringConversionResult.OutOfRange; + result = value; - return true; + return EStringConversionResult.Success; } - if (t.StartsWith("int") || t.StartsWith("float")) + if (t == value) { - var status = TryConvertNumeric(value, type, out float r); - result = r; - - return status; + result = value; + return EStringConversionResult.Success; } - return false; + return EStringConversionResult.Invalid; } private static bool TryConvertNumeric(StringName value, CommandInputType type, out T result) @@ -255,18 +261,25 @@ private static bool TryConvertNumeric(StringName value, CommandInputType type return true; } - private void PrintNumericError( - StringName commandName, StringName argumentName, + private void PrintErr( + EStringConversionResult status, + StringName argumentName, LinkedList types, object value, float min, float max ) { - if (value is not null && !Numeric.IsWithinRange((float)value, min, max)) - Terminal.Output.Error(Messages.ArgumentValueOutOfRange( - commandName, argumentName, min, max - )); - else if (value is null) Terminal.Output.Error(Messages.InvalidArgument( - commandName, argumentName, string.Join(", ", types.Select(t => t.Type)) - )); + switch (status) + { + case EStringConversionResult.Invalid: + Terminal.Output.Error(Messages.InvalidArgument( + _commandName, value as string, string.Join(", ", types.Select(t => t.Type)) + )); + break; + case EStringConversionResult.OutOfRange: + Terminal.Output.Error(Messages.ArgumentValueOutOfRange( + _commandName, argumentName, min, max + )); + break; + } } } diff --git a/addons/yat/src/scenes/base_terminal/components/input_info/components/command_info/CommandInfo.cs b/addons/yat/src/scenes/base_terminal/components/input_info/components/command_info/CommandInfo.cs index 7dc7298a..147dfecb 100644 --- a/addons/yat/src/scenes/base_terminal/components/input_info/components/command_info/CommandInfo.cs +++ b/addons/yat/src/scenes/base_terminal/components/input_info/components/command_info/CommandInfo.cs @@ -48,7 +48,6 @@ private string GenerateCommandInfo(string[] tokens) { bool current = tokens.Length - 1 == i; bool valid = Terminal.CommandValidator.ValidateCommandArgument( - commandAttribute.Name, arg, new() { { arg.Name, arg.Types } }, (tokens.Length - 1 >= i + 1) ? tokens[i + 1] : string.Empty, From 1e48a33417ac6a78ad90ef362538a523ef87149e Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 13:00:25 +0100 Subject: [PATCH 37/46] Fix: The set command throws an exception when there are no registered extensions. --- addons/yat/src/commands/Set.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/yat/src/commands/Set.cs b/addons/yat/src/commands/Set.cs index e3787b04..13132caf 100644 --- a/addons/yat/src/commands/Set.cs +++ b/addons/yat/src/commands/Set.cs @@ -15,6 +15,8 @@ public CommandResult Execute(CommandData data) { var extensions = GetCommandExtensions("set"); + if (extensions is null) return ICommand.Failure("No extensions found."); + if (extensions.TryGetValue((string)data.Arguments["variable"], out Type extension)) return ExecuteExtension(extension, data with { RawData = data.RawData[1..] }); From 1bc36433eb3c57ef0944c5a2dfa3f70e7f00e3ef Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 13:07:52 +0100 Subject: [PATCH 38/46] Adjust commands to the new system --- addons/yat/src/commands/Cat.cs | 2 +- addons/yat/src/commands/Ip.cs | 2 +- addons/yat/src/commands/Ping.cs | 20 ++++++++++---------- addons/yat/src/commands/Reset.cs | 2 +- addons/yat/src/commands/ToggleAudio.cs | 4 ++-- addons/yat/src/commands/TraceRoute.cs | 12 ++++++------ addons/yat/src/commands/View.cs | 2 +- addons/yat/src/commands/Watch.cs | 2 +- addons/yat/src/commands/Wenv.cs | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/addons/yat/src/commands/Cat.cs b/addons/yat/src/commands/Cat.cs index eb9e1a3c..156202fb 100644 --- a/addons/yat/src/commands/Cat.cs +++ b/addons/yat/src/commands/Cat.cs @@ -14,7 +14,7 @@ public sealed class Cat : ICommand public CommandResult Execute(CommandData data) { string fileName = (string)data.Arguments["file"]; - int lineLimit = (int)data.Options["-l"]; + int lineLimit = (int)(float)data.Options["-l"]; if (!FileAccess.FileExists(fileName)) return ICommand.InvalidArguments($"File '{fileName}' does not exist."); diff --git a/addons/yat/src/commands/Ip.cs b/addons/yat/src/commands/Ip.cs index e6977357..6e8ceb5e 100644 --- a/addons/yat/src/commands/Ip.cs +++ b/addons/yat/src/commands/Ip.cs @@ -8,7 +8,7 @@ namespace YAT.Commands; [Command("ip", "Displays your private IP addresses.", "[b]Usage[/b]: ip")] -[Argument("action", "[addr]", "The action to perform.")] +[Argument("action", "addr", "The action to perform.")] public sealed class Ip : ICommand { private BaseTerminal _terminal; diff --git a/addons/yat/src/commands/Ping.cs b/addons/yat/src/commands/Ping.cs index 3093c76a..434c86ea 100644 --- a/addons/yat/src/commands/Ping.cs +++ b/addons/yat/src/commands/Ping.cs @@ -14,25 +14,25 @@ namespace YAT.Commands; )] [Threaded] [Argument("hostname", "string", "The host to trace the route to.")] -[Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000)] -[Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30)] -[Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32)] +[Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000f)] +[Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30f)] +[Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32f)] [Option("-f", "bool", "Specifies that the packet can be fragmented.", false)] -[Option("-delay", "int(1:10)", "The delay between pings in seconds.", 1)] -[Option("-limit", "int(1:255)", "The maximum number of pings to send.", 0)] +[Option("-delay", "int(1:10)", "The delay between pings in seconds.", 1f)] +[Option("-limit", "int(1:255)", "The maximum number of pings to send.", 0f)] public sealed class Ping : ICommand { public CommandResult Execute(CommandData data) { string hostname = (string)data.Arguments["hostname"]; - var maxPings = (int)data.Options["-limit"]; + var maxPings = (int)(float)data.Options["-limit"]; var options = new NetworkingOptions { - Timeout = (ushort)(int)data.Options["-t"], - TTL = (ushort)(int)data.Options["-ttl"], - BufferSize = (ushort)(int)data.Options["-b"], + Timeout = (ushort)(float)data.Options["-t"], + TTL = (ushort)(float)data.Options["-ttl"], + BufferSize = (ushort)(float)data.Options["-b"], DontFragment = !(bool)data.Options["-f"], - Delay = (ushort)((int)data.Options["-delay"] * 1000), + Delay = (ushort)((float)data.Options["-delay"] * 1000), }; uint pings = 0; diff --git a/addons/yat/src/commands/Reset.cs b/addons/yat/src/commands/Reset.cs index ef28712f..58ca71ca 100644 --- a/addons/yat/src/commands/Reset.cs +++ b/addons/yat/src/commands/Reset.cs @@ -9,7 +9,7 @@ namespace YAT.Commands; "Resets the terminal to its default position and/or size.", "[b]reset[/b] [i]action[/i]" )] -[Argument("action", "[all, position, size]", "The action to perform.")] +[Argument("action", "all|position|size", "The action to perform.")] public sealed class Reset : ICommand { public CommandResult Execute(CommandData data) diff --git a/addons/yat/src/commands/ToggleAudio.cs b/addons/yat/src/commands/ToggleAudio.cs index d3d56961..3558bb7b 100644 --- a/addons/yat/src/commands/ToggleAudio.cs +++ b/addons/yat/src/commands/ToggleAudio.cs @@ -6,13 +6,13 @@ namespace YAT.Commands; [Command("toggleaudio", "Toggles audio on or off.", "[b]Usage[/b]: toggleaudio")] -[Option("-id", "int(0:32767)", "The ID of the audio bus to toggle. If not provided, all buses will be toggled.", -1)] +[Option("-id", "int(0:32767)", "The ID of the audio bus to toggle. If not provided, all buses will be toggled.", -1f)] [Option("-name", "string", "The name of the audio bus to toggle. If not provided, all buses will be toggled.", null)] public sealed class ToggleAudio : ICommand { public CommandResult Execute(CommandData data) { - var id = (int)data.Options["-id"]; + var id = (int)(float)data.Options["-id"]; var name = (string)data.Options["-name"]; if (id == -1 && string.IsNullOrEmpty(name)) diff --git a/addons/yat/src/commands/TraceRoute.cs b/addons/yat/src/commands/TraceRoute.cs index 11c70a22..30ec1aac 100644 --- a/addons/yat/src/commands/TraceRoute.cs +++ b/addons/yat/src/commands/TraceRoute.cs @@ -12,9 +12,9 @@ namespace YAT.Commands; "[b]Usage[/b]: traceroute [i]hostname[/i]" )] [Argument("hostname", "string", "The host to trace the route to.")] -[Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000)] -[Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30)] -[Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32)] +[Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000f)] +[Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30f)] +[Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32f)] [Option("-f", "bool", "Specifies that the packet can be fragmented.", false)] [Threaded] public sealed class TraceRoute : ICommand @@ -24,9 +24,9 @@ public CommandResult Execute(CommandData data) var hostname = (string)data.Arguments["hostname"]; var options = new NetworkingOptions { - Timeout = (ushort)(int)data.Options["-t"], - TTL = (ushort)(int)data.Options["-ttl"], - BufferSize = (ushort)(int)data.Options["-b"], + Timeout = (ushort)(float)data.Options["-t"], + TTL = (ushort)(float)data.Options["-ttl"], + BufferSize = (ushort)(float)data.Options["-b"], DontFragment = !(bool)data.Options["-f"] }; diff --git a/addons/yat/src/commands/View.cs b/addons/yat/src/commands/View.cs index cc569fda..a1e257a6 100644 --- a/addons/yat/src/commands/View.cs +++ b/addons/yat/src/commands/View.cs @@ -20,7 +20,7 @@ namespace YAT.Commands; "You can also use the integer value of the type " + "[url=https://docs.godotengine.org/en/stable/classes/class_renderingserver.html#class-renderingserver-method-set-debug-generate-wireframes]ViewportDebugDraw[/url]." )] -[Argument("type", "[normal, unshaded, lightning, overdraw, wireframe, int]", "The type of debug draw to use.")] +[Argument("type", "normal|unshaded|lightning|overdraw|wireframe|int", "The type of debug draw to use.")] public sealed class View : ICommand { private static readonly int MAX_DRAW_MODE = Enum.GetValues(typeof(ViewportDebugDraw)).Length - 1; diff --git a/addons/yat/src/commands/Watch.cs b/addons/yat/src/commands/Watch.cs index 6197c3c5..be199670 100644 --- a/addons/yat/src/commands/Watch.cs +++ b/addons/yat/src/commands/Watch.cs @@ -14,7 +14,7 @@ namespace YAT.Commands; )] [Threaded] [Argument("command", "string", "The command to run.")] -[Argument("interval", "float(0.5, 60)", "The interval at which to run the command.")] +[Argument("interval", "float(0.5:60)", "The interval at which to run the command.")] public sealed class Watch : ICommand { private const uint SECONDS_MULTIPLIER = 1000; diff --git a/addons/yat/src/commands/Wenv.cs b/addons/yat/src/commands/Wenv.cs index 79944d63..732136fa 100644 --- a/addons/yat/src/commands/Wenv.cs +++ b/addons/yat/src/commands/Wenv.cs @@ -8,7 +8,7 @@ namespace YAT.Commands; [Command("wenv", "Manages the world environment.", "[b]Usage[/b]: wenv [i]action[/i]")] -[Argument("action", "[remove, restore]", "Removes or restores the world environment.")] +[Argument("action", "remove|restore", "Removes or restores the world environment.")] public sealed class Wenv : ICommand { private YAT _yat; From 95df641fc9cfc9b11d77221500b076a8fa53eb3c Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 14:36:09 +0100 Subject: [PATCH 39/46] Make it possible to convert to int type --- .../command_validator/CommandValidator.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index 4906e533..c4deedd7 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -223,7 +223,7 @@ private static EStringConversionResult TryConvertStringToType( if (t.StartsWith("int", "float")) { - var status = TryConvertNumeric(value, type, out float r); + var status = TryConvertNumeric(value, type, out var r); result = r; return status @@ -249,14 +249,31 @@ private static EStringConversionResult TryConvertStringToType( return EStringConversionResult.Invalid; } - private static bool TryConvertNumeric(StringName value, CommandInputType type, out T result) - where T : notnull, IConvertible, IComparable, IComparable + private static bool TryConvertNumeric(StringName value, CommandInputType type, out object result) { - if (!Numeric.TryConvert(value, out result)) return false; + result = null; + + if (type.Type == "int") + { + if (!Numeric.TryConvert(value, out int r)) return false; + + result = r; + + if (type.Min != type.Max) return Numeric.IsWithinRange( + r, type.Min, type.Max + ); + } + + if (type.Type == "float") + { + if (!Numeric.TryConvert(value, out float r)) return false; - if (type.Min != type.Max) return Numeric.IsWithinRange( - result, type.Min, type.Max - ); + result = r; + + if (type.Min != type.Max) return Numeric.IsWithinRange( + r, type.Min, type.Max + ); + } return true; } From 29ae5493c4408fd1c63bb2f412554313ba503351 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 14:56:03 +0100 Subject: [PATCH 40/46] Options with bool type no longer need to specify default value --- .../components/command_validator/CommandValidator.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index c4deedd7..215f26fc 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -129,14 +129,13 @@ string[] passedOpts ) { var lookup = option.Types.ToLookup(t => t.Type); + bool isBool = lookup.Contains("bool"); foreach (var passedOpt in passedOpts) { if (!passedOpt.StartsWith(option.Name)) continue; - bool isBool = lookup.Contains("bool"); string[] tokens = passedOpt.Split('='); - if (isBool && tokens.Length == 1) { validatedOpts[option.Name] = true; @@ -209,7 +208,9 @@ string[] passedOpts Terminal.Output.Error(Messages.InvalidArgument(_commandName, passedOpt, option.Name)); } - validatedOpts[option.Name] = option.DefaultValue; + validatedOpts[option.Name] = isBool && option.DefaultValue is null + ? false + : option.DefaultValue; return true; } From 2826c48d06f8aebeb871385a8075a7072986a567 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 14:56:17 +0100 Subject: [PATCH 41/46] Adjust commands --- addons/yat/src/commands/Cat.cs | 2 +- addons/yat/src/commands/Cowsay.cs | 16 ++++++++-------- addons/yat/src/commands/Load.cs | 6 +++--- addons/yat/src/commands/Ls.cs | 4 ++-- addons/yat/src/commands/Ping.cs | 24 ++++++++++++------------ addons/yat/src/commands/Preferences.cs | 4 +++- addons/yat/src/commands/QuickCommands.cs | 4 +++- addons/yat/src/commands/Quit.cs | 2 +- addons/yat/src/commands/Stats.cs | 16 ++++++++-------- addons/yat/src/commands/Timescale.cs | 4 +--- addons/yat/src/commands/ToggleAudio.cs | 2 +- addons/yat/src/commands/TraceRoute.cs | 8 ++++---- addons/yat/src/commands/Whereami.cs | 4 ++-- 13 files changed, 49 insertions(+), 47 deletions(-) diff --git a/addons/yat/src/commands/Cat.cs b/addons/yat/src/commands/Cat.cs index 156202fb..eb9e1a3c 100644 --- a/addons/yat/src/commands/Cat.cs +++ b/addons/yat/src/commands/Cat.cs @@ -14,7 +14,7 @@ public sealed class Cat : ICommand public CommandResult Execute(CommandData data) { string fileName = (string)data.Arguments["file"]; - int lineLimit = (int)(float)data.Options["-l"]; + int lineLimit = (int)data.Options["-l"]; if (!FileAccess.FileExists(fileName)) return ICommand.InvalidArguments($"File '{fileName}' does not exist."); diff --git a/addons/yat/src/commands/Cowsay.cs b/addons/yat/src/commands/Cowsay.cs index dd7be070..1e56cd3a 100644 --- a/addons/yat/src/commands/Cowsay.cs +++ b/addons/yat/src/commands/Cowsay.cs @@ -10,14 +10,14 @@ namespace YAT.Commands; [Command("cowsay", "Make a cow say something.", "[b]Usage[/b]: cowsay [i]message[/i]")] [Argument("message", "string", "The message to make the cow say.")] -[Option("-b", "bool", "Borg", false)] -[Option("-d", "bool", "Dead", false)] -[Option("-g", "bool", "Greedy", false)] -[Option("-p", "bool", "Paranoid", false)] -[Option("-s", "bool", "Stoned", false)] -[Option("-t", "bool", "Tired", false)] -[Option("-w", "bool", "Wired", false)] -[Option("-y", "bool", "Youthful", false)] +[Option("-b", "bool", "Borg")] +[Option("-d", "bool", "Dead")] +[Option("-g", "bool", "Greedy")] +[Option("-p", "bool", "Paranoid")] +[Option("-s", "bool", "Stoned")] +[Option("-t", "bool", "Tired")] +[Option("-w", "bool", "Wired")] +[Option("-y", "bool", "Youthful")] public sealed class Cowsay : ICommand { private BaseTerminal _terminal; diff --git a/addons/yat/src/commands/Load.cs b/addons/yat/src/commands/Load.cs index 6fa07cdb..2ffd1a04 100644 --- a/addons/yat/src/commands/Load.cs +++ b/addons/yat/src/commands/Load.cs @@ -7,7 +7,7 @@ namespace YAT.Commands; [Command("load", "Loads specified object into the scene.", "[b]Usage[/b]: load [i]object_path[/i]")] [Argument("object_path", "string", "The object path to load.")] -[Option("-absolute", "bool", "If true, the object will be loaded at the origin, otherwise relative positioning will be used.", false)] +[Option("-absolute", "bool", "If true, the object will be loaded at the origin, otherwise relative positioning will be used.")] [Option("-x", "float", "The X position of the object.", 0f)] [Option("-y", "float", "The Y position of the object.", 0f)] [Option("-z", "float", "The Z position of the object.", -5f)] @@ -17,8 +17,8 @@ namespace YAT.Commands; [Option("-sx", "float", "The X scale of the object.", 1f)] [Option("-sy", "float", "The Y scale of the object.", 1f)] [Option("-sz", "float", "The Z scale of the object.", 1f)] -[Option("-hidden", "bool", "The object will be hidden.", false)] -[Option("-2d", "bool", "The object will be loaded as a 2D object.", false)] +[Option("-hidden", "bool", "The object will be hidden.")] +[Option("-2d", "bool", "The object will be loaded as a 2D object.")] public sealed class Load : ICommand { public CommandResult Execute(CommandData data) diff --git a/addons/yat/src/commands/Ls.cs b/addons/yat/src/commands/Ls.cs index 9075650c..db8e268e 100644 --- a/addons/yat/src/commands/Ls.cs +++ b/addons/yat/src/commands/Ls.cs @@ -13,8 +13,8 @@ namespace YAT.Commands; [Command("ls", "Lists the contents of the current directory.", "[b]Usage[/b]: ls")] [Argument("path", "string", "The path to list the contents of.")] -[Option("-n", "bool", "Displays the children of the current node.", false)] -[Option("-m", "bool", "Lists the methods of the current node.", false)] +[Option("-n", "bool", "Displays the children of the current node.")] +[Option("-m", "bool", "Lists the methods of the current node.")] public sealed class Ls : ICommand { private BaseTerminal _terminal; diff --git a/addons/yat/src/commands/Ping.cs b/addons/yat/src/commands/Ping.cs index 434c86ea..7ecdcd9d 100644 --- a/addons/yat/src/commands/Ping.cs +++ b/addons/yat/src/commands/Ping.cs @@ -14,25 +14,25 @@ namespace YAT.Commands; )] [Threaded] [Argument("hostname", "string", "The host to trace the route to.")] -[Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000f)] -[Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30f)] -[Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32f)] -[Option("-f", "bool", "Specifies that the packet can be fragmented.", false)] -[Option("-delay", "int(1:10)", "The delay between pings in seconds.", 1f)] -[Option("-limit", "int(1:255)", "The maximum number of pings to send.", 0f)] +[Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000)] +[Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30)] +[Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32)] +[Option("-f", "bool", "Specifies that the packet can be fragmented.")] +[Option("-delay", "int(1:10)", "The delay between pings in seconds.", 1)] +[Option("-limit", "int(1:255)", "The maximum number of pings to send.", 0)] public sealed class Ping : ICommand { public CommandResult Execute(CommandData data) { - string hostname = (string)data.Arguments["hostname"]; - var maxPings = (int)(float)data.Options["-limit"]; + var hostname = (string)data.Arguments["hostname"]; + var maxPings = (int)data.Options["-limit"]; var options = new NetworkingOptions { - Timeout = (ushort)(float)data.Options["-t"], - TTL = (ushort)(float)data.Options["-ttl"], - BufferSize = (ushort)(float)data.Options["-b"], + Timeout = (ushort)(int)data.Options["-t"], + TTL = (ushort)(int)data.Options["-ttl"], + BufferSize = (ushort)(int)data.Options["-b"], DontFragment = !(bool)data.Options["-f"], - Delay = (ushort)((float)data.Options["-delay"] * 1000), + Delay = (ushort)((int)data.Options["-delay"] * 1000), }; uint pings = 0; diff --git a/addons/yat/src/commands/Preferences.cs b/addons/yat/src/commands/Preferences.cs index 63dbab08..adc3e264 100644 --- a/addons/yat/src/commands/Preferences.cs +++ b/addons/yat/src/commands/Preferences.cs @@ -5,7 +5,9 @@ namespace YAT.Commands; -[Command("preferences", "Creates a window with the available preferences.", "[b]Usage[/b]: options", "prefs")] +[Command("preferences", aliases: "prefs")] +[Usage("prefs")] +[Description("Creates a window with the available preferences.")] public sealed class Preferences : ICommand { private static Scenes.Preferences _windowInstance; diff --git a/addons/yat/src/commands/QuickCommands.cs b/addons/yat/src/commands/QuickCommands.cs index 4a94a7fb..0e0af714 100644 --- a/addons/yat/src/commands/QuickCommands.cs +++ b/addons/yat/src/commands/QuickCommands.cs @@ -6,7 +6,9 @@ namespace YAT.Commands; -[Command("quickcommands", "Manages Quick Commands.", "[b]Usage[/b]: quickcommands [i]action[/i] [i]name[/i] [i]command[/i]", "qc")] +[Command("quickcommands", aliases: "qc")] +[Description("Manages Quick Commands.")] +[Usage("quickcommands [i]action[/i] [i]name[/i] [i]command[/i]")] [Argument("action", "[add, remove, list]", "The action to perform.")] [Option("-name", "string", "The name of the quick command.")] [Option("-command", "string", "The command to execute when the quick command is called.")] diff --git a/addons/yat/src/commands/Quit.cs b/addons/yat/src/commands/Quit.cs index 87427625..505b8655 100644 --- a/addons/yat/src/commands/Quit.cs +++ b/addons/yat/src/commands/Quit.cs @@ -6,7 +6,7 @@ namespace YAT.Commands; [Command("quit", "By default quits the game.", "[b]Usage[/b]: quit", "exit")] -[Option("-t", "bool", "Closes the terminal.", false)] +[Option("-t", "bool", "Closes the terminal.")] public sealed class Quit : ICommand { private YAT _yat; diff --git a/addons/yat/src/commands/Stats.cs b/addons/yat/src/commands/Stats.cs index 67aac069..d867c4d7 100644 --- a/addons/yat/src/commands/Stats.cs +++ b/addons/yat/src/commands/Stats.cs @@ -9,14 +9,14 @@ namespace YAT.Commands; [Command("stats", "Manages the game monitor.", "[b]Usage:[/b] stats", "st")] -[Option("-all", "bool", "Shows all the information in the game monitor.", false)] -[Option("-fps", "bool", "Shows the FPS in the game monitor.", false)] -[Option("-os", "bool", "Shows the OS information in the game monitor.", false)] -[Option("-cpu", "bool", "Shows the CPU information in the game monitor.", false)] -[Option("-mem", "bool", "Shows memory information in the game monitor.", false)] -[Option("-engine", "bool", "Shows the engine information in the game monitor.", false)] -[Option("-objects", "bool", "Shows the objects information in the game monitor.", false)] -[Option("-lookingat", "bool", "Shows the info about node the player is looking at. Only in 3D.", false)] +[Option("-all", "bool", "Shows all the information in the game monitor.")] +[Option("-fps", "bool", "Shows the FPS in the game monitor.")] +[Option("-os", "bool", "Shows the OS information in the game monitor.")] +[Option("-cpu", "bool", "Shows the CPU information in the game monitor.")] +[Option("-mem", "bool", "Shows memory information in the game monitor.")] +[Option("-engine", "bool", "Shows the engine information in the game monitor.")] +[Option("-objects", "bool", "Shows the objects information in the game monitor.")] +[Option("-lookingat", "bool", "Shows the info about node the player is looking at. Only in 3D.")] public sealed class Stats : ICommand { private YAT _yat; diff --git a/addons/yat/src/commands/Timescale.cs b/addons/yat/src/commands/Timescale.cs index 60cfd402..da619d2c 100644 --- a/addons/yat/src/commands/Timescale.cs +++ b/addons/yat/src/commands/Timescale.cs @@ -11,9 +11,7 @@ public sealed class Timescale : ICommand { public CommandResult Execute(CommandData data) { - var scale = (float)data.Options["-set"]; - - Engine.TimeScale = scale; + Engine.TimeScale = (float)data.Options["-set"]; return ICommand.Success(); } diff --git a/addons/yat/src/commands/ToggleAudio.cs b/addons/yat/src/commands/ToggleAudio.cs index 3558bb7b..7897fd77 100644 --- a/addons/yat/src/commands/ToggleAudio.cs +++ b/addons/yat/src/commands/ToggleAudio.cs @@ -6,7 +6,7 @@ namespace YAT.Commands; [Command("toggleaudio", "Toggles audio on or off.", "[b]Usage[/b]: toggleaudio")] -[Option("-id", "int(0:32767)", "The ID of the audio bus to toggle. If not provided, all buses will be toggled.", -1f)] +[Option("-id", "int(0:32767)", "The ID of the audio bus to toggle. If not provided, all buses will be toggled.", -1)] [Option("-name", "string", "The name of the audio bus to toggle. If not provided, all buses will be toggled.", null)] public sealed class ToggleAudio : ICommand { diff --git a/addons/yat/src/commands/TraceRoute.cs b/addons/yat/src/commands/TraceRoute.cs index 30ec1aac..351b0a4b 100644 --- a/addons/yat/src/commands/TraceRoute.cs +++ b/addons/yat/src/commands/TraceRoute.cs @@ -12,10 +12,10 @@ namespace YAT.Commands; "[b]Usage[/b]: traceroute [i]hostname[/i]" )] [Argument("hostname", "string", "The host to trace the route to.")] -[Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000f)] -[Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30f)] -[Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32f)] -[Option("-f", "bool", "Specifies that the packet can be fragmented.", false)] +[Option("-t", "int(1:32767)", "The maximum time to wait for each reply, in milliseconds.", 10000)] +[Option("-ttl", "int(1:255)", "The maximum number of hops to search for the target.", 30)] +[Option("-b", "int(1:32767)", "The size of the buffer to send with the request.", 32)] +[Option("-f", "bool", "Specifies that the packet can be fragmented.")] [Threaded] public sealed class TraceRoute : ICommand { diff --git a/addons/yat/src/commands/Whereami.cs b/addons/yat/src/commands/Whereami.cs index d2245bda..6f5f1854 100644 --- a/addons/yat/src/commands/Whereami.cs +++ b/addons/yat/src/commands/Whereami.cs @@ -5,8 +5,8 @@ namespace YAT.Commands; [Command("whereami", "Prints the current scene name and path.", "[b]Usage[/b]: whereami", "wai")] -[Option("-l", "bool", "Prints the full path to the scene file.", false)] -[Option("-s", "bool", "Prints info about currently selected node.", false)] +[Option("-l", "bool", "Prints the full path to the scene file.")] +[Option("-s", "bool", "Prints info about currently selected node.")] public sealed class Whereami : ICommand { public CommandResult Execute(CommandData data) From 68409c6ed1d20ab5234f60397032c2a2e7fe5be6 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 15:13:54 +0100 Subject: [PATCH 42/46] Change how ranges with one value are handled --- addons/yat/src/helpers/Text.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/addons/yat/src/helpers/Text.cs b/addons/yat/src/helpers/Text.cs index 09916027..c3c1856a 100644 --- a/addons/yat/src/helpers/Text.cs +++ b/addons/yat/src/helpers/Text.cs @@ -219,18 +219,26 @@ public static bool TryParseCommandInputType(string type, out CommandInputType pa if (tokens.Length > 1) { - if (tokens[1].EndsWith(':')) return false; - + bool maxIsPresent = tokens[1].EndsWith(':'); var minMax = tokens[1].Split(':', StringSplitOptions.RemoveEmptyEntries); if (!allowedToHaveRange(tokens[0]) || minMax.Length > 2) return false; - // If min value is not present, set it to 0 if (minMax.Length == 1) { - if (minMax[0].TryConvert(out float max)) - parsed = new(tokens[0], 0, max, isArray); - else return false; + // If only min value is not present, set it to float.MinValue + if (!maxIsPresent) + { + if (minMax[0].TryConvert(out float max)) + parsed = new(tokens[0], float.MinValue, max, isArray); + else return false; + } // If only max value is not present, set it to float.MaxValue + else + { + if (minMax[0].TryConvert(out float min)) + parsed = new(tokens[0], min, float.MaxValue, isArray); + else return false; + } } else { From 317a52a16f010e1a4cc6ae1d9a76881d6535d39e Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 16:06:21 +0100 Subject: [PATCH 43/46] Improve converting passed value to a list --- .../components/command_validator/CommandValidator.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs index 215f26fc..5178a6b6 100644 --- a/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs +++ b/addons/yat/src/scenes/base_terminal/components/command_validator/CommandValidator.cs @@ -168,21 +168,21 @@ string[] passedOpts return false; } - List convertedArr = new(); + List convertedL = new(); foreach (var v in values) { - var st = TryConvertStringToType(v, type, out var convertedArrValue); + var st = TryConvertStringToType(v, type, out var convertedLValue); if (st != EStringConversionResult.Success) { - PrintErr(st, option.Name, option.Types, convertedArrValue, type.Min, type.Max); + PrintErr(st, option.Name, option.Types, convertedLValue, type.Min, type.Max); return false; } - convertedArr.Add(convertedArrValue); - validatedOpts[option.Name] = convertedArr.ToArray(); + convertedL.Add(convertedLValue); } + validatedOpts[option.Name] = convertedL.ToArray(); return true; } From e88c76149e3f3d69ab2fa7664da4c374c1fc04be Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 17:45:50 +0100 Subject: [PATCH 44/46] Simplify echo command --- addons/yat/src/commands/Echo.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/yat/src/commands/Echo.cs b/addons/yat/src/commands/Echo.cs index 546c1d7f..c1f61cf1 100644 --- a/addons/yat/src/commands/Echo.cs +++ b/addons/yat/src/commands/Echo.cs @@ -12,8 +12,7 @@ public sealed class Echo : ICommand { public CommandResult Execute(CommandData data) { - var text = string.Join(" ", data.RawData[1..^0]); - data.Terminal.Print(text); + data.Terminal.Print(data.Arguments["message"]); return ICommand.Success(); } From ef76e1a23e0c8923c1ba7a95abc6a6e60de69c1c Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 17:46:42 +0100 Subject: [PATCH 45/46] Update AUTOMATIC_INPUT_VALIDATION.md --- addons/yat/docs/AUTOMATIC_INPUT_VALIDATION.md | 117 ++++++++++++------ 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/addons/yat/docs/AUTOMATIC_INPUT_VALIDATION.md b/addons/yat/docs/AUTOMATIC_INPUT_VALIDATION.md index ce9f0da5..6985fcf5 100644 --- a/addons/yat/docs/AUTOMATIC_INPUT_VALIDATION.md +++ b/addons/yat/docs/AUTOMATIC_INPUT_VALIDATION.md @@ -3,79 +3,100 @@

Here you will find information on how YAT can perform validation of arguments, and options for your commands automatically.

-## Automatic input Validation +
> If you want documentation to be created based on attributes, > but do not want validation you can use the `NoValidate` attribute. -Custom input validation gives you more options, but can be cumbersome and unnecessarily lengthy commands. For this reason, YAT supports two ways of validating data: required (arguments), and optional (options). +Custom input validation is more flexible, but at the same time cumbersome, it can negatively affect the readability of the code as well as the time it takes to create a command. For this reason, YAT allows automatic input validation in two ways: required arguments and optional options. -After the validation is complete, YAT returns the data converted to the appropriate type, or cancels the command execution if a validation error occurs. +During validation, YAT checks whether the submitted data meets the relevant requirements and additionally performs conversion to the appropriate type. If the data does not meet the requirements, YAT displays an appropriate message. -For information on how to get data that has passed validation, see [CREATING_COMMANDS.md](./CREATING_COMMANDS.md). +> For information on how to get data that has passed validation, see [CREATING_COMMANDS.md](./CREATING_COMMANDS.md). + +### Defining data type requirements + +Both arguments and options allow flexibility in creating requirements for the type of data to be submitted. + +The created requirement can consist of a single type, e.g., `int`, `float`. It can also take several options like `int|float`, or `yes|no`. Multiple options are separated by a pipe `|`. Supported data types: - string - int - float -- double - bool +- constant string (e.q., `yes`, `normal`, `wireframe` etc.) -### Arguments +Both `string`, `int` and `float` support defining the ranges they can take. In the case of `string` it is the minimum and maximum length of the string, in the case of `int` and `float` it is the minimum and maximum value. -> The order of arguments above the class matters. +The limit for ranges is `-3.4028235E+38` and `3.4028235E+38`. -Arguments are required, if an argument is missing or data that does not meet the requirements is passed, -validation will fail and the command will not run. +Example of a requirement with one type and a range of values: + +```c +int(5:15) -> The value passed can only be an integer in the range 5 -15. +``` + +An example of a requirement with many possibilities: -You can specify what arguments, under what rules and in what order the command expects using the `Argument` attribute. +``` +normal|unshaded|wireframe|int(0:30) +``` + +In this case, the transferred data can take either a numeric value from 0 to 30 or one of the specified strings. -Arguments are defined as follows: +#### Creating a range -- `argument` - the user must use the name of this argument. -- `argument:data_type` - the user must use the given data type in place of this argument. -- `argument:[option1, option2]` - the user must use any of the given options in place of the argument. -- `argument:[option1, data_type, option3]` - the user must specify any of the given options or use the listed data type in place of the given argument +The range is created in parentheses given after the type that supports the range. The minimum and maximum values must be separated by a colon `:`. -Numeric types can accept ranges of values. For example: +One of the values may be omitted (the colon must still be present), in which case: +- the minimum value is omitted: then it takes the value `-3.4028235E+38`, +- maximum value is omitted: then it takes the value `3.4028235E+38`. -```csharp -[Argument("step", "double(0, 69.420)", "Number of steps.")] +Example of a range of values with both limits given: + +```cs +int(5:15) -> range is 5 - 15 ``` -Example of use: +Example of a range of values with only maximum limit given: -```csharp -[Argument("action", "[move, jump]", "Action to perform.")] -[Argument("direction", "[left, right, int]", "Direction to move/jump.")] +```cs +int(:15) -> range is -3.4028235E+38 - 15 ``` -In the above example, the command takes two arguments. -The first argument can take one of two values: `move` or `jump`. -The second argument has three possibilities, it can take `"left"`, `"right"` or a `number`. +Example of a range of values with only minimum limit given: -### Options +```cs +int(5:) -> range is 5 - 3.4028235E+38 +``` -> Options are not required, however, if an option is passed, -> but the data does not match, validation will fail and the command will not run. +### Arguments -Options are defined as follows: +> The order of arguments matters. -- `-opt` - passed option must match the name of the option. If option is present it returns `true` if not `null`. -- `-opt=data_type` - value of passed option must be of the specified data type. -- `-opt=option1|data_type|option3` - value of passed option must be of the specified data type or one of the specified options. -- `-opt=data_type...` - value of passed option must be an array of values of the specified data type. +Arguments are required, if an argument is missing or data that does not meet the requirements is passed, +validation will fail and the command will not run. -Numeric types can accept ranges of values. For example: +You can specify arguments using the `Argument` attribute: -```csharp -[Option("-step", "double(0:69.420)", "Number of steps to move.")] +```cs +[Argument("action", "move|jump", "Action to perform.")] +[Argument("direction", "left|right|int", "Direction to move/jump.")] ``` -Example of use: +In the above example, the command takes two arguments. +The first argument can take one of two values: `move` or `jump`. +The second argument has three possibilities, it can take `"left"`, `"right"` or a `number`. + +### Options + +Options are **not** required, however, if an option is passed, but the data does not match requirements, validation will fail and the command will not run. -```csharp +You can specify options using the `Option` attribute: + +```cs [Option("-action", "move|jump", "Action to perform.")] [Option("-direction", "left|right|int(-1:1)", "Direction to move.")] ``` @@ -84,11 +105,27 @@ In the above example, command can take two options. The first option can take one of two values: `move` or `jump`. The second option has three possibilities, it can take `"left"`, `"right"` or a `number` limited to a range from -1 to 1. +#### Arrays + +Options, unlike arguments, can also take an array of data of a given type, you just need to add `...` at the end of the type definition. + +Example: + +```cs +[Option("-p", "int(1:99)...", "Lorem ipsum.", new int[] { 1 })] +``` + +In this case, the transfer of data to the option is as follows: + +```sh +some_command -p=5,2,32,47 +``` + #### Default values -Options also support `default values`, which will be assigned to them when the user does not pass an option when running the command. If the default value is not set, and the user does not use the option, then its value is set to `null`: +Options also support `default values`, which will be assigned to them when the user does not pass an option when running the command. If the default value is not set, and the user does not use the option, then its value is set to `null` (`false` if type is bool): -```csharp +```cs [Option("-action", "move|jump", "Action to perform.", "move")] ``` From 389e8669781d1db0a553e1206d69d779300cb680 Mon Sep 17 00:00:00 2001 From: MASSHUU12 Date: Wed, 21 Feb 2024 17:54:27 +0100 Subject: [PATCH 46/46] Update script templates & changelog --- CHANGELOG.md | 10 +++++++--- script_templates/Node/Command.cs | 15 ++++++++------- script_templates/Node/ExtensibleCommand.cs | 8 +++++++- script_templates/Node/Extension.cs | 6 ++++-- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78c34c76..62730c25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,8 @@ All notable changes to this project will be documented in this file. - -limit option to ping command. - CommandInputType record. - TryParseCommandInputType method for Text helper class. -- String in arguments and options can have defined min and max length. -- Enum: +- String type in arguments and options can have defined min and max length. +- Enums: - ESceneChangeFailureReason - EStringConversionResult - Commands: @@ -41,6 +41,9 @@ All notable changes to this project will be documented in this file. - The way of defining the type for arguments and options has changed. - CommandData now uses StringName instead of string for dictionaries. - Methods StartsWith & EndsWith from Text class are now extensions of string type. +- Using default value for bool type options is no longer necessary. +- Double is no longer a valid type for options & arguments. +- Updated AUTOMATIC_INPUT_VALIDATION.md & script templates. ### Removed @@ -48,7 +51,8 @@ All notable changes to this project will be documented in this file. ### Fixed -- It is no longer possible to call the history command via itself, which could have caused an endless loop. +- It is possible to call the history command via itself, which can cause an endless loop. +- The set command throws an exception when there are no registered extensions. ## [1.23.0-beta 2024-02-13] diff --git a/script_templates/Node/Command.cs b/script_templates/Node/Command.cs index ea29c32c..0a0dff2a 100644 --- a/script_templates/Node/Command.cs +++ b/script_templates/Node/Command.cs @@ -5,14 +5,15 @@ using YAT.Interfaces; using YAT.Types; -namespace YAT.Commands +namespace YAT.Commands; + +[Command("_CLASS_")] +[Usage("_CLASS_")] +[Description("Lorem ipsum dolor sit amet.")] +public sealed class _CLASS_ : ICommand { - [Command("_CLASS_", "Lorem ipsum dolor sit amet.", "[b]Usage[/b]: _CLASS_")] - public sealed class _CLASS_ : ICommand + public CommandResult Execute(CommandData data) { - public CommandResult Execute(CommandData data) - { - return CommandResult.NotImplemented("_CLASS_ is not yet implemented!"); - } + return CommandResult.NotImplemented("_CLASS_ is not yet implemented!"); } } diff --git a/script_templates/Node/ExtensibleCommand.cs b/script_templates/Node/ExtensibleCommand.cs index c6dc873d..b87eabb2 100644 --- a/script_templates/Node/ExtensibleCommand.cs +++ b/script_templates/Node/ExtensibleCommand.cs @@ -7,7 +7,11 @@ using YAT.Interfaces; using YAT.Types; -[Command("_CLASS_", "Lorem ipsum dolor sit amet.", "[b]Usage[/b]: _CLASS_ [i]action[/i]")] +namespace YAT.Commands; + +[Command("_CLASS_")] +[Usage("_CLASS_ [i]action[/i]")] +[Description("Lorem ipsum dolor sit amet.")] [Argument("action", "string", "The name of the action to run.")] public partial class _CLASS_ : Extensible, ICommand { @@ -15,6 +19,8 @@ public CommandResult Execute(CommandData data) { var extensions = GetCommandExtensions("_CLASS_"); + if (extensions is null) return ICommand.Failure("No extensions found."); + if (extensions.TryGetValue((string)data.Arguments["action"], out Type extension)) return ExecuteExtension(extension, data with { RawData = data.RawData[1..] }); diff --git a/script_templates/Node/Extension.cs b/script_templates/Node/Extension.cs index a28fc013..5c6fd004 100644 --- a/script_templates/Node/Extension.cs +++ b/script_templates/Node/Extension.cs @@ -5,8 +5,10 @@ using YAT.Interfaces; using YAT.Types; -[Extension("_CLASS_", "Lorem ipsum dolor sit amet.", "[b]Usage[/b]: _CLASS_")] -public partial class _CLASS_ : IExtension +[Extension("_CLASS_")] +[Usage("_CLASS_")] +[Description("Lorem ipsum dolor sit amet.") +public sealed class _CLASS_ : IExtension { public CommandResult Execute(CommandData data) {