From b8d30053a925043c4c36b13c2d444c0dec168dfc Mon Sep 17 00:00:00 2001 From: s2quake Date: Sat, 21 Dec 2024 11:11:23 +0900 Subject: [PATCH 1/7] refactor: Remove completion order --- src/JSSoft.Terminals/SystemTerminalHost.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/JSSoft.Terminals/SystemTerminalHost.cs b/src/JSSoft.Terminals/SystemTerminalHost.cs index 4e13c4b..bc09e4c 100644 --- a/src/JSSoft.Terminals/SystemTerminalHost.cs +++ b/src/JSSoft.Terminals/SystemTerminalHost.cs @@ -74,7 +74,6 @@ static string GetPlatformName(PlatformID platformID) public static string NextCompletion(string[] completions, string text) { - completions = [.. completions.OrderBy(item => item)]; if (completions.Contains(text) is true) { for (var i = 0; i < completions.Length; i++) @@ -110,7 +109,6 @@ public static string NextCompletion(string[] completions, string text) public static string PrevCompletion(string[] completions, string text) { - completions = [.. completions.OrderBy(item => item)]; if (completions.Contains(text) is true) { for (var i = completions.Length - 1; i >= 0; i--) @@ -511,10 +509,16 @@ public string Prompt protected virtual string[] GetCompletion(string[] items, string find) { + if (find == string.Empty) + { + return items; + } + var query = from item in items where item.StartsWith(find) + orderby item select item; - return query.ToArray(); + return [.. query]; } protected void UpdateLayout(int bufferWidth, int bufferHeight) From e092a2edfd0f7aff95c8282b1766a28e4e2e0fa1 Mon Sep 17 00:00:00 2001 From: s2quake Date: Sat, 21 Dec 2024 11:13:02 +0900 Subject: [PATCH 2/7] fix: Fix an exception being thrown when a subcommand uses a property --- src/JSSoft.Commands/SubCommandAsync.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/JSSoft.Commands/SubCommandAsync.cs b/src/JSSoft.Commands/SubCommandAsync.cs index 10ccff5..a01502e 100644 --- a/src/JSSoft.Commands/SubCommandAsync.cs +++ b/src/JSSoft.Commands/SubCommandAsync.cs @@ -11,7 +11,7 @@ namespace JSSoft.Commands; internal sealed class SubCommandAsync( CommandMethodBase method, CommandMethodDescriptor methodDescriptor) - : CommandMethodInstance(methodDescriptor), ICommand, IAsyncExecutable + : CommandMethodInstance(methodDescriptor, method), ICommand, IAsyncExecutable { private readonly CommandMethodDescriptor _methodDescriptor = methodDescriptor; @@ -41,8 +41,6 @@ internal sealed class SubCommandAsync( CommandCollection ICommand.Commands { get; } = CommandCollection.Empty; - public object GetMemberOwner(CommandMemberDescriptor memberDescriptor) => method; - public Task ExecuteAsync(CancellationToken cancellationToken, IProgress progress) => _methodDescriptor.InvokeAsync( method, this, cancellationToken, progress); From 99924d9ddc7f3be847b3075520c1e36cc995ba39 Mon Sep 17 00:00:00 2001 From: s2quake Date: Tue, 24 Dec 2024 20:20:32 +0900 Subject: [PATCH 3/7] refactor: Improve parse and completion command --- .../JSSoft.Commands.Repl/SystemTerminal.cs | 4 +- src/JSSoft.Commands/AssemblyInfo.cs | 8 + src/JSSoft.Commands/CommandAsyncBase.cs | 10 +- src/JSSoft.Commands/CommandBase.cs | 7 +- .../CommandCompletionAttribute.cs | 29 -- .../CommandCompletionContext.cs | 60 +--- src/JSSoft.Commands/CommandContextBase.cs | 69 +++-- src/JSSoft.Commands/CommandDescriptor.cs | 2 +- src/JSSoft.Commands/CommandInvoker.cs | 12 +- .../CommandMemberDescriptor.cs | 27 +- .../CommandMemberDescriptorCollection.cs | 2 +- src/JSSoft.Commands/CommandMethodBase.cs | 6 +- .../CommandMethodDescriptor.cs | 123 ++++++-- .../CommandParameterDescriptor.cs | 6 +- src/JSSoft.Commands/CommandParser.cs | 4 +- .../CommandPropertyAttribute.cs | 7 + .../CommandPropertyDescriptor.cs | 6 +- .../CommandStaticTypeAttribute.cs | 10 + src/JSSoft.Commands/HelpCommandBase.cs | 7 +- src/JSSoft.Commands/ParseContext.cs | 268 +++++++++++------- src/JSSoft.Commands/ParseDescriptor.cs | 69 +++-- .../ParseDescriptorCollection.cs | 17 +- .../StandardCommandMethodDescriptor.cs | 110 ------- src/JSSoft.Commands/SubCommand.cs | 5 +- src/JSSoft.Commands/SubCommandAsync.cs | 3 +- src/JSSoft.Terminals/SystemTerminalBase.cs | 4 +- .../CommandContextTests/CompletionsTest.cs | 246 ++++++++++++++++ .../JSSoft.Commands.Tests/ParseContextTest.cs | 98 +++++++ 28 files changed, 800 insertions(+), 419 deletions(-) create mode 100644 src/JSSoft.Commands/AssemblyInfo.cs delete mode 100644 src/JSSoft.Commands/CommandCompletionAttribute.cs delete mode 100644 src/JSSoft.Commands/StandardCommandMethodDescriptor.cs create mode 100644 test/JSSoft.Commands.Tests/CommandContextTests/CompletionsTest.cs create mode 100644 test/JSSoft.Commands.Tests/ParseContextTest.cs diff --git a/example/JSSoft.Commands.Repl/SystemTerminal.cs b/example/JSSoft.Commands.Repl/SystemTerminal.cs index 8063f05..c580f0b 100644 --- a/example/JSSoft.Commands.Repl/SystemTerminal.cs +++ b/example/JSSoft.Commands.Repl/SystemTerminal.cs @@ -50,9 +50,9 @@ protected override string FormatPrompt(string prompt) return prompt; } - protected override string[] GetCompletion(string[] items, string find) + protected override string[] GetCompletions(string[] items, string find) { - return _commandContext.GetCompletion(items, find); + return _commandContext.GetCompletions(items, find); } protected override Task OnExecuteAsync(string command, CancellationToken cancellationToken) diff --git a/src/JSSoft.Commands/AssemblyInfo.cs b/src/JSSoft.Commands/AssemblyInfo.cs new file mode 100644 index 0000000..a67e923 --- /dev/null +++ b/src/JSSoft.Commands/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("JSSoft.Commands.Tests")] diff --git a/src/JSSoft.Commands/CommandAsyncBase.cs b/src/JSSoft.Commands/CommandAsyncBase.cs index 58a59c4..8d42043 100644 --- a/src/JSSoft.Commands/CommandAsyncBase.cs +++ b/src/JSSoft.Commands/CommandAsyncBase.cs @@ -110,7 +110,8 @@ internal string ExecutionName protected IProgress Progress => _progress ?? throw new InvalidOperationException("The progress is not available."); - public virtual string[] GetCompletions(CommandCompletionContext completionContext) => []; + string[] ICommand.GetCompletions(CommandCompletionContext completionContext) + => GetCompletions(completionContext); Task IAsyncExecutable.ExecuteAsync( CancellationToken cancellationToken, IProgress progress) @@ -128,6 +129,13 @@ Task IAsyncExecutable.ExecuteAsync( string ICommand.GetUsage(bool isDetail) => OnUsagePrint(isDetail); + protected virtual string[] GetCompletions(CommandCompletionContext completionContext) + { + var memberDescriptor = completionContext.MemberDescriptor; + var instance = this; + return memberDescriptor.GetCompletionsInternal(instance); + } + protected abstract Task OnExecuteAsync(CancellationToken cancellationToken); protected CommandMemberDescriptor GetDescriptor(string propertyName) diff --git a/src/JSSoft.Commands/CommandBase.cs b/src/JSSoft.Commands/CommandBase.cs index 92bc9d6..722c3d9 100644 --- a/src/JSSoft.Commands/CommandBase.cs +++ b/src/JSSoft.Commands/CommandBase.cs @@ -111,7 +111,12 @@ internal string ExecutionName string[] ICommand.GetCompletions(CommandCompletionContext completionContext) => GetCompletions(completionContext); - protected virtual string[] GetCompletions(CommandCompletionContext completionContext) => []; + protected virtual string[] GetCompletions(CommandCompletionContext completionContext) + { + var memberDescriptor = completionContext.MemberDescriptor; + var instance = this; + return memberDescriptor.GetCompletionsInternal(instance); + } protected abstract void OnExecute(); diff --git a/src/JSSoft.Commands/CommandCompletionAttribute.cs b/src/JSSoft.Commands/CommandCompletionAttribute.cs deleted file mode 100644 index c46872f..0000000 --- a/src/JSSoft.Commands/CommandCompletionAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. -// Licensed under the MIT License. See LICENSE.md in the project root for license information. -// - -namespace JSSoft.Commands; - -[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] -public class CommandCompletionAttribute : CommandStaticTypeAttribute -{ - public CommandCompletionAttribute(string methodName) - { - MethodName = methodName; - } - - public CommandCompletionAttribute(string staticTypeName, string methodName) - : base(staticTypeName) - { - MethodName = methodName; - } - - public CommandCompletionAttribute(Type staticType, string methodName) - : base(staticType) - { - MethodName = methodName; - } - - public string MethodName { get; } -} diff --git a/src/JSSoft.Commands/CommandCompletionContext.cs b/src/JSSoft.Commands/CommandCompletionContext.cs index f65a7cc..d1595e5 100644 --- a/src/JSSoft.Commands/CommandCompletionContext.cs +++ b/src/JSSoft.Commands/CommandCompletionContext.cs @@ -5,62 +5,16 @@ namespace JSSoft.Commands; -public sealed class CommandCompletionContext +public sealed class CommandCompletionContext( + ICommand command, + CommandMemberDescriptor memberDescriptor, + IReadOnlyDictionary properties) { - private CommandCompletionContext( - ICommand command, - CommandMemberDescriptor memberDescriptor, - string find, - IReadOnlyDictionary properties) - { - Command = command; - MemberDescriptor = memberDescriptor; - Find = find; - Properties = properties; - } + public ICommand Command { get; } = command; - public ICommand Command { get; } + public CommandMemberDescriptor MemberDescriptor { get; } = memberDescriptor; - public CommandMemberDescriptor MemberDescriptor { get; } - - public string Find { get; } - - public IReadOnlyDictionary Properties { get; } + public IReadOnlyDictionary Properties { get; } = properties; public string MemberName => MemberDescriptor.MemberName; - - internal static object? Create( - ICommand command, - ParseContext parseContext, - string find) - { - var properties = new Dictionary(); - var parseDescriptorByMemberDescriptor - = parseContext.Items.ToDictionary(item => item.MemberDescriptor); - - foreach (var item in parseDescriptorByMemberDescriptor.ToArray()) - { - var memberDescriptor = item.Key; - var parseDescriptor = item.Value; - if (parseDescriptor.HasValue is true) - { - properties.Add(memberDescriptor.MemberName, parseDescriptor.Value); - if (memberDescriptor.IsVariables is false) - { - parseDescriptorByMemberDescriptor.Remove(memberDescriptor); - } - } - } - - if (find.StartsWith(CommandUtility.Delimiter) is false - && find.StartsWith(CommandUtility.ShortDelimiter) is false - && parseDescriptorByMemberDescriptor.Count is not 0) - { - var memberDescriptor = parseDescriptorByMemberDescriptor.Keys.First(); - return new CommandCompletionContext( - command, memberDescriptor, find, properties); - } - - return null; - } } diff --git a/src/JSSoft.Commands/CommandContextBase.cs b/src/JSSoft.Commands/CommandContextBase.cs index f5c1736..2b8a7b2 100644 --- a/src/JSSoft.Commands/CommandContextBase.cs +++ b/src/JSSoft.Commands/CommandContextBase.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License. See LICENSE.md in the project root for license information. // +using System.Diagnostics; using System.IO; using System.Text; using System.Threading; @@ -142,8 +143,46 @@ public async Task ExecuteAsync( OnExecuted(EventArgs.Empty); } + [Obsolete("Use GetCompletions instead.")] public string[] GetCompletion(string[] items, string find) - => GetCompletion(_commandNode, new List(items), find); + { + var completions = GetCompletions(items, find); + if (find == string.Empty) + { + return completions; + } + + return [.. completions.Where(item => item.StartsWith(find))]; + } + + public string[] GetCompletions(string[] items, string find) + { + var completionList = new List(items.Length); + var itemList = new List(items); + var completions = GetCompletions(_commandNode, itemList); + for (var i = 0; i < completions.Length; i++) + { + var completion = completions[i]; + if (completion != string.Empty) + { + if (find == string.Empty || completion.StartsWith(find) is true) + { + completionList.Add(completions[i]); + } + } + else + { + Trace.TraceWarning($"The '{i}' completion string is empty. It will be ignored."); + } + } + + if (find != string.Empty) + { + completionList.Sort(); + } + + return [.. completionList]; + } internal static ICommand? GetCommand(ICommand parent, IList argList) { @@ -299,16 +338,14 @@ private static void AttachContext(ICommand command, ICommandContext commandConte } } - private string[] GetCompletion( - ICommand parent, IList itemList, string find) + private static string[] GetCompletions( + ICommand parent, IList itemList) { if (itemList.Count is 0) { var query = from child in parent.Commands where child.IsEnabled is true from name in new string[] { child.Name }.Concat(child.Aliases) - where name.StartsWith(find) - orderby name select name; return query.ToArray(); } @@ -317,15 +354,15 @@ orderby name var commandName = itemList[0]; if (parent.TryGetCommand(commandName, out var command) is true) { - if (command.IsEnabled is true && command.Commands.Any() is true) + if (command.IsEnabled is true && command.Commands.Count > 0) { itemList.RemoveAt(0); - return GetCompletion(command, itemList, find); + return GetCompletions(command, itemList); } else { var args = itemList.Skip(1).ToArray(); - if (GetCompletion(command, args, find) is string[] completions) + if (GetCompletions(command, args) is string[] completions) { return completions; } @@ -336,20 +373,18 @@ orderby name } } - private string[] GetCompletion(ICommand command, string[] args, string find) + private static string[] GetCompletions(ICommand command, string[] args) { var memberDescriptors = CommandDescriptor.GetMemberDescriptors(command); - var settings = Settings; - var parseContext = new ParseContext(memberDescriptors, settings, args); - var context = CommandCompletionContext.Create(command, parseContext, find); - if (context is CommandCompletionContext completionContext) + var parseContext = ParseContext.Create(memberDescriptors, args); + if (parseContext.Descriptor is { } descriptor) { + var properties = parseContext.GetProperties(); + var memberDescriptor = descriptor.MemberDescriptor; + var completionContext = new CommandCompletionContext( + command, memberDescriptor, properties); return command.GetCompletions(completionContext); } - else if (context is string[] completions) - { - return completions; - } return []; } diff --git a/src/JSSoft.Commands/CommandDescriptor.cs b/src/JSSoft.Commands/CommandDescriptor.cs index 4694ebd..f077b66 100644 --- a/src/JSSoft.Commands/CommandDescriptor.cs +++ b/src/JSSoft.Commands/CommandDescriptor.cs @@ -319,7 +319,7 @@ where methodInfo.IsBrowsable() is true methodDescriptorList.Capacity = items.Length + staticMethodDescriptors.Length; foreach (var item in items) { - methodDescriptorList.Add(new StandardCommandMethodDescriptor(item)); + methodDescriptorList.Add(new CommandMethodDescriptor(item)); } methodDescriptorList.AddRange(staticMethodDescriptors); diff --git a/src/JSSoft.Commands/CommandInvoker.cs b/src/JSSoft.Commands/CommandInvoker.cs index 2506c3c..a274590 100644 --- a/src/JSSoft.Commands/CommandInvoker.cs +++ b/src/JSSoft.Commands/CommandInvoker.cs @@ -175,9 +175,9 @@ private void Invoke(CommandMethodDescriptor methodDescriptor, string[] args) var instance = Instance; var memberDescriptors = methodDescriptor.Members; var settings = Settings; - var parseContext = new ParseContext(memberDescriptors, settings, args); + var parseContext = ParseContext.Create(memberDescriptors, args); var methodInstance = new CommandMethodInstance(methodDescriptor, instance); - parseContext.SetValue(methodInstance); + parseContext.SetValue(methodInstance, settings); methodDescriptor.Invoke(instance, methodInstance); } @@ -190,9 +190,9 @@ private Task InvokeAsync( var instance = Instance; var memberDescriptors = methodDescriptor.Members; var settings = Settings; - var parseContext = new ParseContext(memberDescriptors, settings, args); + var parseContext = ParseContext.Create(memberDescriptors, args); var methodInstance = new CommandMethodInstance(methodDescriptor, instance); - parseContext.SetValue(methodInstance); + parseContext.SetValue(methodInstance, settings); return methodDescriptor.InvokeAsync( instance, methodInstance, cancellationToken, progress); } @@ -202,7 +202,7 @@ private void Parse(string[] args) var instance = Instance; var memberDescriptors = CommandDescriptor.GetMemberDescriptors(instance); var settings = Settings; - var parserContext = new ParseContext(memberDescriptors, settings, args); - parserContext.SetValue(instance); + var parserContext = ParseContext.Create(memberDescriptors, args); + parserContext.SetValue(instance, settings); } } diff --git a/src/JSSoft.Commands/CommandMemberDescriptor.cs b/src/JSSoft.Commands/CommandMemberDescriptor.cs index 0cbe24f..4319112 100644 --- a/src/JSSoft.Commands/CommandMemberDescriptor.cs +++ b/src/JSSoft.Commands/CommandMemberDescriptor.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Threading.Tasks; +using JSSoft.Commands.Exceptions; namespace JSSoft.Commands; @@ -58,18 +59,7 @@ public abstract class CommandMemberDescriptor( internal void VerifyTrigger(ParseDescriptorCollection parseDescriptors) => OnVerifyTrigger(parseDescriptors); - internal string[]? GetCompletionInternal(object instance, string find) - { - if (GetCompletion(instance, find) is { } items) - { - var query = from item in items - where item.StartsWith(find) - select item; - return query.ToArray(); - } - - return null; - } + internal string[] GetCompletionsInternal(object instance) => GetCompletions(instance); protected abstract void SetValue(object instance, object? value); @@ -79,14 +69,17 @@ protected virtual void OnVerifyTrigger(ParseDescriptorCollection parseDescriptor { } - protected virtual string[]? GetCompletion(object instance, string find) => null; + protected virtual string[] GetCompletions(object instance) => []; - protected string[] GetCompletion( - object instance, string find, CommandMemberCompletionAttribute attribute) + protected string[] GetCompletions( + object instance, CommandMemberCompletionAttribute attribute) { + var declaringType = MemberInfo.DeclaringType; var methodName = attribute.MethodName; - var methodInfo = attribute.GetMethodInfo(instance.GetType(), methodName); - var obj = methodInfo.DeclaringType == instance.GetType() ? instance : null; + var parameterTypes = Array.Empty(); + var methodInfo = attribute.GetMethodInfo( + MemberInfo.DeclaringType, methodName, parameterTypes); + var obj = declaringType.IsInstanceOfType(instance) ? instance : null; try { diff --git a/src/JSSoft.Commands/CommandMemberDescriptorCollection.cs b/src/JSSoft.Commands/CommandMemberDescriptorCollection.cs index e28dc7b..b6fa78f 100644 --- a/src/JSSoft.Commands/CommandMemberDescriptorCollection.cs +++ b/src/JSSoft.Commands/CommandMemberDescriptorCollection.cs @@ -77,7 +77,7 @@ orderby memberDescriptor.IsRequired descending if (_itemByShortName.ContainsKey(item.ShortName) is true) { var message = $"{nameof(CommandMemberDescriptor)} '{item.ShortName}' cannot be " + - $"added because it already exists."; + $"added because it already exists in '{owner}'"; throw new CommandDefinitionException(message, owner); } diff --git a/src/JSSoft.Commands/CommandMethodBase.cs b/src/JSSoft.Commands/CommandMethodBase.cs index a3ea2b7..470d571 100644 --- a/src/JSSoft.Commands/CommandMethodBase.cs +++ b/src/JSSoft.Commands/CommandMethodBase.cs @@ -106,10 +106,8 @@ internal string ExecutionName } public virtual string[] GetCompletions( - CommandMethodDescriptor methodDescriptor, - CommandMemberDescriptor memberDescriptor, - string find) - => methodDescriptor.GetCompletionInternal(this, memberDescriptor, find); + CommandMethodDescriptor methodDescriptor, CommandMemberDescriptor memberDescriptor) + => methodDescriptor.GetCompletionsInternal(this, memberDescriptor); string[] ICommand.GetCompletions(CommandCompletionContext completionContext) => []; diff --git a/src/JSSoft.Commands/CommandMethodDescriptor.cs b/src/JSSoft.Commands/CommandMethodDescriptor.cs index 9ce032f..e419084 100644 --- a/src/JSSoft.Commands/CommandMethodDescriptor.cs +++ b/src/JSSoft.Commands/CommandMethodDescriptor.cs @@ -6,35 +6,67 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using JSSoft.Commands.Exceptions; +using JSSoft.Commands.Extensions; namespace JSSoft.Commands; -public abstract class CommandMethodDescriptor(MethodInfo methodInfo) +public sealed class CommandMethodDescriptor { - public abstract string Name { get; } + private readonly PropertyInfo? _validationPropertyInfo; + private readonly MethodInfo? _completionMethodInfo; - public abstract string[] Aliases { get; } + public CommandMethodDescriptor(MethodInfo methodInfo) + { + if (methodInfo.DeclaringType is null) + { + throw new CommandDeclaringTypeNullException(methodInfo); + } + + MethodInfo = methodInfo; + Usage = CommandDescriptor.GetUsage(methodInfo); + MethodName = methodInfo.Name; + IsAsync = methodInfo.IsAsync(); + Name = methodInfo.GetName(); + Aliases = methodInfo.GetAliases(); + DisplayName = methodInfo.GetDisplayName(); + Members = CommandDescriptor.GetMemberDescriptorsByMethodInfo(methodInfo); + Category = AttributeUtility.GetCategory(methodInfo); + _validationPropertyInfo = methodInfo.GetValidationPropertyInfo(); + _completionMethodInfo = methodInfo.GetCompletionMethodInfo(); + } - public abstract string DisplayName { get; } + public string Name { get; } - public abstract string MethodName { get; } + public string[] Aliases { get; } - public abstract bool IsAsync { get; } + public string DisplayName { get; } - public abstract CommandMemberDescriptorCollection Members { get; } + public string MethodName { get; } - public abstract string Category { get; } + public bool IsAsync { get; } - public MethodInfo MethodInfo { get; } = methodInfo; + public CommandMemberDescriptorCollection Members { get; } - public CommandUsage Usage { get; } = CommandDescriptor.GetUsage(methodInfo); + public string Category { get; } + + public MethodInfo MethodInfo { get; } + + public CommandUsage Usage { get; } internal bool CanExecute(object instance) => OnCanExecute(instance); internal object? Invoke(object instance, CommandMethodInstance methodInstance) { var parameters = methodInstance.GetParameters(); - return OnInvoke(instance, parameters); + if (MethodInfo.DeclaringType!.IsAbstract && MethodInfo.DeclaringType.IsSealed is true) + { + return MethodInfo.Invoke(null, parameters); + } + else + { + return MethodInfo.Invoke(instance, parameters); + } } internal Task InvokeAsync( @@ -44,7 +76,6 @@ internal Task InvokeAsync( IProgress progress) { var parameters = methodInstance.GetParameters(cancellationToken, progress); - if (OnInvoke(instance, parameters) is Task task) { return task; @@ -53,20 +84,74 @@ internal Task InvokeAsync( throw new UnreachableException(); } - internal string[] GetCompletionInternal( - object instance, CommandMemberDescriptor memberDescriptor, string find) + internal string[] GetCompletionsInternal( + object instance, CommandMemberDescriptor memberDescriptor) { - if (memberDescriptor.GetCompletionInternal(instance, find) is string[] items) + if (memberDescriptor.GetCompletionsInternal(instance) is string[] items) { return items; } - return GetCompletion(instance, [memberDescriptor, find]); + return GetCompletions(instance, [memberDescriptor]); } - protected abstract object? OnInvoke(object instance, object?[] parameters); + private object? OnInvoke(object instance, object?[] parameters) + { + if (MethodInfo.DeclaringType!.IsAbstract && MethodInfo.DeclaringType.IsSealed is true) + { + return MethodInfo.Invoke(null, parameters); + } + else + { + return MethodInfo.Invoke(instance, parameters); + } + } - protected virtual bool OnCanExecute(object instance) => true; + private bool OnCanExecute(object instance) + { + if (_validationPropertyInfo?.GetValue(instance) is bool @bool) + { + return @bool; + } + + return true; + } + + private string[] GetCompletions(object instance, object?[] parameters) + { + if (_completionMethodInfo is not null) + { + return InvokeCompletionMethod(instance, parameters); + } - protected virtual string[] GetCompletion(object instance, object?[] parameters) => []; + return []; + } + + private string[] InvokeCompletionMethod(object instance, object?[] parameters) + { + try + { + var value = _completionMethodInfo!.Invoke(instance, parameters); + if (value is string[] items) + { + return items; + } + else if (value is Task task) + { + if (task.Wait(CommandSettings.AsyncTimeout) is false) + { + return []; + } + + return task.Result; + } + + throw new UnreachableException(); + } + catch (Exception e) + { + Trace.TraceError($"{e}"); + return []; + } + } } diff --git a/src/JSSoft.Commands/CommandParameterDescriptor.cs b/src/JSSoft.Commands/CommandParameterDescriptor.cs index 4137ed3..64f8097 100644 --- a/src/JSSoft.Commands/CommandParameterDescriptor.cs +++ b/src/JSSoft.Commands/CommandParameterDescriptor.cs @@ -65,14 +65,14 @@ internal CommandParameterDescriptor( protected override object? GetValue(object instance) => _value; - protected override string[]? GetCompletion(object instance, string find) + protected override string[] GetCompletions(object instance) { if (_completionAttribute is not null) { - return GetCompletion(instance, find, _completionAttribute); + return GetCompletions(instance, _completionAttribute); } - return base.GetCompletion(instance, find); + return base.GetCompletions(instance); } private static object GetInitValue( diff --git a/src/JSSoft.Commands/CommandParser.cs b/src/JSSoft.Commands/CommandParser.cs index 39d18b1..d31eae3 100644 --- a/src/JSSoft.Commands/CommandParser.cs +++ b/src/JSSoft.Commands/CommandParser.cs @@ -44,8 +44,8 @@ public void Parse(string[] args) var instance = Instance; var memberDescriptors = CommandDescriptor.GetMemberDescriptors(instance); var settings = Settings; - var parserContext = new ParseContext(memberDescriptors, settings, args); - parserContext.SetValue(instance); + var parserContext = ParseContext.Create(memberDescriptors, args); + parserContext.SetValue(instance, settings); } protected virtual void OnVerify(string[] args) diff --git a/src/JSSoft.Commands/CommandPropertyAttribute.cs b/src/JSSoft.Commands/CommandPropertyAttribute.cs index 376d40c..1965923 100644 --- a/src/JSSoft.Commands/CommandPropertyAttribute.cs +++ b/src/JSSoft.Commands/CommandPropertyAttribute.cs @@ -31,7 +31,14 @@ public CommandPropertyAttribute(char shortName, bool useName) { } + /// + /// Gets or sets the initial value when the option is not specified on the command line. + /// public object InitValue { get; set; } = DBNull.Value; + /// + /// Gets or sets the default value when the option is specified on the command line + /// but the value is not set. + /// public object DefaultValue { get; set; } = DBNull.Value; } diff --git a/src/JSSoft.Commands/CommandPropertyDescriptor.cs b/src/JSSoft.Commands/CommandPropertyDescriptor.cs index f3769e2..9a36c70 100644 --- a/src/JSSoft.Commands/CommandPropertyDescriptor.cs +++ b/src/JSSoft.Commands/CommandPropertyDescriptor.cs @@ -131,14 +131,14 @@ protected override void OnVerifyTrigger(ParseDescriptorCollection parseDescripto } } - protected override string[]? GetCompletion(object instance, string find) + protected override string[] GetCompletions(object instance) { if (_completionAttribute is not null) { - return GetCompletion(instance, find, _completionAttribute); + return GetCompletions(instance, _completionAttribute); } - return base.GetCompletion(instance, find); + return base.GetCompletions(instance); } private static object GetDefaultValue(CommandPropertyBaseAttribute attribute) diff --git a/src/JSSoft.Commands/CommandStaticTypeAttribute.cs b/src/JSSoft.Commands/CommandStaticTypeAttribute.cs index 969701d..9f85fc5 100644 --- a/src/JSSoft.Commands/CommandStaticTypeAttribute.cs +++ b/src/JSSoft.Commands/CommandStaticTypeAttribute.cs @@ -60,4 +60,14 @@ internal MethodInfo GetMethodInfo(CommandMemberInfo memberInfo, string methodNam ?? throw new CommandDefinitionException( $"'{methodName}' method not found in '{type.FullName}'.", memberInfo); } + + internal MethodInfo GetMethodInfo(CommandMemberInfo memberInfo, string methodName, Type[] types) + { + var type = GetTypeWithFallback(memberInfo); + var bindingFlags = CommandSettings.GetBindingFlags(type); + + return type.GetMethod(methodName, bindingFlags, types) + ?? throw new CommandDefinitionException( + $"'{methodName}' method not found in '{type.FullName}'.", memberInfo); + } } diff --git a/src/JSSoft.Commands/HelpCommandBase.cs b/src/JSSoft.Commands/HelpCommandBase.cs index e2ba294..aa65d76 100644 --- a/src/JSSoft.Commands/HelpCommandBase.cs +++ b/src/JSSoft.Commands/HelpCommandBase.cs @@ -56,7 +56,7 @@ protected override string[] GetCompletions(CommandCompletionContext completionCo commandNames = items; } - return GetCommandNames(Context.Node, commandNames, completionContext.Find); + return GetCommandNames(Context.Node, commandNames); } return base.GetCompletions(completionContext); @@ -91,14 +91,13 @@ private void PrintCommandHelp(string[] commandNames) } } - private string[] GetCommandNames(ICommand node, string[] names, string find) + private string[] GetCommandNames(ICommand node, string[] names) { if (names.Length is 0) { var query = from child in node.Commands where child.IsEnabled is true from name in new string[] { child.Name }.Concat(child.Aliases) - where name.StartsWith(find) where name != Name orderby name select name; @@ -106,7 +105,7 @@ orderby name } else if (node.TryGetCommand(names[0], out var childNode) is true) { - return GetCommandNames(childNode, [.. names.Skip(1)], find); + return GetCommandNames(childNode, [.. names.Skip(1)]); } return []; diff --git a/src/JSSoft.Commands/ParseContext.cs b/src/JSSoft.Commands/ParseContext.cs index be006bb..7763b76 100644 --- a/src/JSSoft.Commands/ParseContext.cs +++ b/src/JSSoft.Commands/ParseContext.cs @@ -4,26 +4,64 @@ // using System.ComponentModel; -using System.Text; namespace JSSoft.Commands; -internal sealed class ParseContext( - CommandMemberDescriptorCollection memberDescriptors, CommandSettings settings, string[] args) +public sealed class ParseContext { - public ParseDescriptorCollection Items { get; } = Parse(memberDescriptors, args); + private readonly CommandMemberDescriptorCollection _memberDescriptors; + private readonly ParseDescriptorCollection _descriptors; + private readonly ParseDescriptor[] _implicitDescriptors; + private readonly ParseDescriptor? _variablesDescriptor; + private readonly List _variableList = []; + private int _countOfImplicit = 0; + private bool _isVariableMode; + + public ParseContext(CommandMemberDescriptorCollection memberDescriptors) + { + _memberDescriptors = memberDescriptors; + _descriptors = new ParseDescriptorCollection(memberDescriptors); + _implicitDescriptors = GetImplicitDescriptors(_descriptors); + _variablesDescriptor = GetVariableDescriptors(_descriptors); + Descriptor = _implicitDescriptors.FirstOrDefault() ?? _variablesDescriptor; + } + + public ParseDescriptor? Descriptor { get; private set; } - public void SetValue(object instance) + public static ParseContext Create( + CommandMemberDescriptorCollection memberDescriptors, string[] args) { - ThrowIfInvalidValue(Items); + try + { + var parseContext = new ParseContext(memberDescriptors); + parseContext.Next(args); + return parseContext; + } + catch (Exception e) + { + throw new CommandLineException("Failed to create a parse context.", e); + } + } - var items = Items; + public void Next(params string[] args) + { + foreach (var arg in args) + { + Next(arg); + } + } + + public void SetValue(object instance, CommandSettings settings) + { + ThrowIfInvalidValue(_descriptors); + + var items = _descriptors; var supportInitialize = instance as ISupportInitialize; var customCommandDescriptor = instance as ICustomCommandDescriptor; foreach (var item in items) { var memberDescriptor = item.MemberDescriptor; - if (item.HasValue is false && item.IsOptionSet is false) + if (item.IsValueSet is false && item.IsOptionSet is false) { continue; } @@ -61,119 +99,157 @@ public void SetValue(object instance) } } - private static ParseDescriptorCollection Parse( - CommandMemberDescriptorCollection memberDescriptors, string[] args) + public IReadOnlyDictionary GetProperties() { - var parseDescriptors = new ParseDescriptorCollection(memberDescriptors); - var implicitParseDescriptors = parseDescriptors.CreateQueue(); - var variablesDescriptor = memberDescriptors.VariablesDescriptor; - var argQueue = CreateQueue(args); - var variableList = new List(argQueue.Count); + var properties = new Dictionary(_descriptors.Count); + foreach (var descriptor in _descriptors) + { + var memberDescriptor = descriptor.MemberDescriptor; + if (descriptor.Value is not DBNull) + { + properties.Add(memberDescriptor.MemberName, descriptor.Value); + } + } - while (argQueue.Count is not 0) + return properties; + } + + private static ParseDescriptor[] GetImplicitDescriptors( + ParseDescriptorCollection parseDescriptors) + { + var query = from item in parseDescriptors + let memberDescriptor = item.MemberDescriptor + where item.IsRequired is true + where item.IsExplicit is false + select item; + return [.. query]; + } + + private static ParseDescriptor? GetVariableDescriptors( + ParseDescriptorCollection parseDescriptors) + => parseDescriptors.FirstOrDefault(item => item.MemberDescriptor.IsVariables); + + private static void ThrowIfInvalidValue(ParseDescriptorCollection parseDescriptors) + { + foreach (var parseDescriptor in parseDescriptors) + { + parseDescriptor.ThrowIfValueMissing(); + } + } + + private ParseDescriptor? FindNextDescriptor(ParseDescriptor descriptor) + { + if (Descriptor == descriptor) + { + _countOfImplicit++; + } + + if (_countOfImplicit < _implicitDescriptors.Length) { - var arg = argQueue.Dequeue(); - if (memberDescriptors.FindByOptionName(arg) is { } memberDescriptor) + return _implicitDescriptors[_countOfImplicit]; + } + + if (_descriptors.IsRequiredDone() is false) + { + return null; + } + + return _variablesDescriptor; + } + + private void Next(string arg) + { + if (_memberDescriptors.FindByOptionName(arg) is { } memberDescriptor) + { + var parseDescriptor = _descriptors[memberDescriptor]; + if (memberDescriptor.IsSwitch is true) { - if (memberDescriptor.IsSwitch is true) + parseDescriptor.SetSwitchValue(true); + parseDescriptor.IsOptionSet = true; + Descriptor = FindNextDescriptor(parseDescriptor); + } + else + { + if (memberDescriptor.IsRequired is false + && memberDescriptor.DefaultValue is not DBNull + && _descriptors.IsRequiredDone() is false) { - var parseDescriptor = parseDescriptors[memberDescriptor]; - parseDescriptor.SetSwitchValue(true); - parseDescriptor.IsOptionSet = true; + var optionName = memberDescriptor.DisplayName; + var message = $"'{optionName}'(with a default value) can be used only after " + + $"all required options are set."; + throw new ArgumentException(message, nameof(arg)); } - else + + if (memberDescriptor.IsRequired is true + && memberDescriptor.DefaultValue is not DBNull + && _countOfImplicit < _implicitDescriptors.Length) { - if (argQueue.TryPeek(out var nextArg) is true - && CommandUtility.IsOption(nextArg) is false - && nextArg != "--") - { - var textValue = argQueue.Dequeue(); - var parseDescriptor = parseDescriptors[memberDescriptor]; - parseDescriptor.SetValue(textValue); - } - - parseDescriptors[memberDescriptor].IsOptionSet = true; + var optionName = memberDescriptor.DisplayName; + var message = $"'{optionName}'(with a default value) can be used only after " + + $"all implicit options are set."; + throw new ArgumentException(message, nameof(arg)); } + + parseDescriptor.IsOptionSet = true; + Descriptor = parseDescriptor; } - else if (arg is "--") + } + else if (arg is "--") + { + if (_variablesDescriptor is null) { - variableList.AddRange([.. argQueue]); - argQueue.Clear(); + var message = "The '--' option cannot be used because the command does not have " + + "a variable option."; + throw new ArgumentException(message, nameof(arg)); } - else if (CommandUtility.IsMultipleSwitch(arg)) + + if (_isVariableMode is true) { - for (var i = 1; i < arg.Length; i++) - { - var s = arg[i]; - var item = $"-{s}"; - if (memberDescriptors.FindByOptionName(item) is not { } memberDescriptor1) - { - throw new InvalidOperationException($"Unknown switch: '{s}'."); - } - - if (memberDescriptor1.MemberType != typeof(bool)) - { - throw new InvalidOperationException($"Unknown switch: '{s}'."); - } - - parseDescriptors[memberDescriptor1].IsOptionSet = true; - } + var message = "The '--' option cannot be used because the mode is already set to " + + "'variable'."; + throw new ArgumentException(message, nameof(arg)); } - else if (CommandUtility.IsOption(arg) is true) + + if (Descriptor is { IsExplicit: true } descriptor && descriptor.Value is DBNull) { - variableList.Add(arg); + var optionName = descriptor.MemberDescriptor.DisplayName; + var message = $"Cannot use '--'. Value of the '{optionName}' must be specified."; + throw new ArgumentException(message, nameof(arg)); } - else + + if (_descriptors.IsRequiredDone() is false) { - if (implicitParseDescriptors.TryDequeue(out var parseDescriptor) is true) - { - parseDescriptor.SetValue(arg); - } - else - { - variableList.Add(arg); - } + var message = "Cannot use '--'. All required options must be set."; + throw new ArgumentException(message, nameof(arg)); } - } - if (variablesDescriptor is not null && variableList.Count is not 0) + _isVariableMode = true; + Descriptor = _variablesDescriptor; + } + else if (CommandUtility.IsMultipleSwitch(arg)) { - var variablesParseDescriptor = parseDescriptors[variablesDescriptor]; - variablesParseDescriptor.SetVariablesValue(variableList); - variableList.Clear(); + throw new NotSupportedException("Multiple switch is not supported."); } - - if (variableList.Count is not 0) + else if (CommandUtility.IsOption(arg) is true) { - var sb = new StringBuilder(); - sb.AppendLine("There are unhandled arguments."); - foreach (var item in variableList) - { - sb.AppendLine($" {item}"); - } - - throw new CommandLineException(sb.ToString()); + throw new ArgumentException($"Invalid option: '{arg}'", nameof(arg)); } - - return parseDescriptors; - } - - private static Queue CreateQueue(string[] args) - { - var queue = new Queue(args.Length); - foreach (var arg in args) + else if (Descriptor is { } descriptor) { - queue.Enqueue(arg); + if (descriptor == _variablesDescriptor) + { + _variableList.Add(arg); + descriptor.SetVariablesValue([.. _variableList]); + } + else + { + descriptor.SetValue(arg); + Descriptor = FindNextDescriptor(descriptor); + } } - - return queue; - } - - private static void ThrowIfInvalidValue(ParseDescriptorCollection parseDescriptors) - { - foreach (var parseDescriptor in parseDescriptors) + else { - parseDescriptor.ThrowIfValueMissing(); + throw new ArgumentException($"Invalid argument: '{arg}'", nameof(arg)); } } } diff --git a/src/JSSoft.Commands/ParseDescriptor.cs b/src/JSSoft.Commands/ParseDescriptor.cs index 4f81f6b..c2bf4a3 100644 --- a/src/JSSoft.Commands/ParseDescriptor.cs +++ b/src/JSSoft.Commands/ParseDescriptor.cs @@ -22,8 +22,6 @@ public sealed class ParseDescriptor(CommandMemberDescriptor memberDescriptor) public bool IsOptionSet { get; internal set; } - public bool HasValue => _value is not DBNull; - public object? Value { get @@ -57,40 +55,9 @@ public object? Value public string? TextValue { get; private set; } - public object? InitValue - { - get - { - if (MemberDescriptor.InitValue is not DBNull) - { - return MemberDescriptor.InitValue; - } - - if (MemberDescriptor.IsNullable is true) - { - return null; - } - - if (MemberDescriptor.MemberType.IsArray is true) - { - return Array.CreateInstance(MemberDescriptor.MemberType.GetElementType()!, 0); - } + public object? InitValue { get; } = GetInitValue(memberDescriptor); - if (MemberDescriptor.MemberType == typeof(string)) - { - return string.Empty; - } - - if (MemberDescriptor.MemberType.IsValueType is true) - { - return Activator.CreateInstance(MemberDescriptor.MemberType); - } - - return null; - } - } - - public object? ActualValue => IsValueSet is true ? Value : InitValue; + public override string ToString() => MemberDescriptor.DisplayName; internal void ValidateValue( ICommandValueValidator valueValidator, object instance, object? value) @@ -107,7 +74,7 @@ internal void ValidateValue( internal void ThrowIfValueMissing() { - if (HasValue is false && MemberDescriptor.DefaultValue is DBNull) + if (_value is DBNull && MemberDescriptor.DefaultValue is DBNull) { if (IsOptionSet is true) { @@ -144,6 +111,36 @@ internal void SetSwitchValue(bool value) IsValueSet = true; } + private static object? GetInitValue(CommandMemberDescriptor memberDescriptor) + { + if (memberDescriptor.InitValue is not DBNull) + { + return memberDescriptor.InitValue; + } + + if (memberDescriptor.IsNullable is true) + { + return null; + } + + if (memberDescriptor.MemberType.IsArray is true) + { + return Array.CreateInstance(memberDescriptor.MemberType.GetElementType()!, 0); + } + + if (memberDescriptor.MemberType == typeof(string)) + { + return string.Empty; + } + + if (memberDescriptor.MemberType.IsValueType is true) + { + return Activator.CreateInstance(memberDescriptor.MemberType); + } + + return null; + } + private static object Parse(CommandMemberDescriptor memberDescriptor, string arg) { var isListType = typeof(IList).IsAssignableFrom(memberDescriptor.MemberType); diff --git a/src/JSSoft.Commands/ParseDescriptorCollection.cs b/src/JSSoft.Commands/ParseDescriptorCollection.cs index 9e8b156..6a239f5 100644 --- a/src/JSSoft.Commands/ParseDescriptorCollection.cs +++ b/src/JSSoft.Commands/ParseDescriptorCollection.cs @@ -64,13 +64,16 @@ IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => _itemByMember.Values.GetEnumerator(); - internal Queue CreateQueue() + internal bool IsRequiredDone() { - var query = from ParseDescriptor item in _itemByMember.Values - where item.IsRequired is true - where item.IsExplicit is false - where item.HasValue is false - select item; - return new(query); + foreach (ParseDescriptor item in _itemByMember.Values) + { + if (item.IsRequired is true && item.Value is DBNull) + { + return false; + } + } + + return true; } } diff --git a/src/JSSoft.Commands/StandardCommandMethodDescriptor.cs b/src/JSSoft.Commands/StandardCommandMethodDescriptor.cs deleted file mode 100644 index 4ccdb5a..0000000 --- a/src/JSSoft.Commands/StandardCommandMethodDescriptor.cs +++ /dev/null @@ -1,110 +0,0 @@ -// -// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. -// Licensed under the MIT License. See LICENSE.md in the project root for license information. -// - -using System.Diagnostics; -using System.Threading.Tasks; -using JSSoft.Commands.Exceptions; -using JSSoft.Commands.Extensions; - -namespace JSSoft.Commands; - -internal sealed class StandardCommandMethodDescriptor : CommandMethodDescriptor -{ - private readonly PropertyInfo? _validationPropertyInfo; - private readonly MethodInfo? _completionMethodInfo; - - public StandardCommandMethodDescriptor(MethodInfo methodInfo) - : base(methodInfo) - { - if (methodInfo.DeclaringType is null) - { - throw new CommandDeclaringTypeNullException(methodInfo); - } - - MethodName = methodInfo.Name; - IsAsync = methodInfo.IsAsync(); - Name = methodInfo.GetName(); - Aliases = methodInfo.GetAliases(); - DisplayName = methodInfo.GetDisplayName(); - Members = CommandDescriptor.GetMemberDescriptorsByMethodInfo(methodInfo); - Category = AttributeUtility.GetCategory(methodInfo); - _validationPropertyInfo = methodInfo.GetValidationPropertyInfo(); - _completionMethodInfo = methodInfo.GetCompletionMethodInfo(); - } - - public override string MethodName { get; } - - public override string Name { get; } - - public override string[] Aliases { get; } - - public override string DisplayName { get; } - - public override string Category { get; } - - public override bool IsAsync { get; } - - public override CommandMemberDescriptorCollection Members { get; } - - protected override object? OnInvoke(object instance, object?[] parameters) - { - if (MethodInfo.DeclaringType!.IsAbstract && MethodInfo.DeclaringType.IsSealed is true) - { - return MethodInfo.Invoke(null, parameters); - } - else - { - return MethodInfo.Invoke(instance, parameters); - } - } - - protected override bool OnCanExecute(object instance) - { - if (_validationPropertyInfo?.GetValue(instance) is bool @bool) - { - return @bool; - } - - return base.OnCanExecute(instance); - } - - protected override string[] GetCompletion(object instance, object?[] parameters) - { - if (_completionMethodInfo is not null) - { - return InvokeCompletionMethod(instance, parameters); - } - - return base.GetCompletion(instance, parameters); - } - - private string[] InvokeCompletionMethod(object instance, object?[] parameters) - { - try - { - var value = _completionMethodInfo!.Invoke(instance, parameters); - if (value is string[] items) - { - return items; - } - else if (value is Task task) - { - if (task.Wait(CommandSettings.AsyncTimeout) is false) - { - return []; - } - - return task.Result; - } - - throw new UnreachableException(); - } - catch (Exception e) - { - Trace.TraceError($"{e}"); - return []; - } - } -} diff --git a/src/JSSoft.Commands/SubCommand.cs b/src/JSSoft.Commands/SubCommand.cs index 71f7858..5cb444c 100644 --- a/src/JSSoft.Commands/SubCommand.cs +++ b/src/JSSoft.Commands/SubCommand.cs @@ -8,7 +8,7 @@ namespace JSSoft.Commands; internal sealed class SubCommand(CommandMethodBase method, CommandMethodDescriptor methodDescriptor) - : CommandMethodInstance(methodDescriptor), + : CommandMethodInstance(methodDescriptor, method), ICommand, IExecutable { @@ -41,8 +41,7 @@ internal sealed class SubCommand(CommandMethodBase method, CommandMethodDescript public void Execute() => _methodDescriptor.Invoke(method, this); public string[] GetCompletions(CommandCompletionContext completionContext) - => method.GetCompletions( - _methodDescriptor, completionContext.MemberDescriptor, completionContext.Find); + => method.GetCompletions(_methodDescriptor, completionContext.MemberDescriptor); string ICommand.GetUsage(bool isDetail) { diff --git a/src/JSSoft.Commands/SubCommandAsync.cs b/src/JSSoft.Commands/SubCommandAsync.cs index a01502e..41bdba0 100644 --- a/src/JSSoft.Commands/SubCommandAsync.cs +++ b/src/JSSoft.Commands/SubCommandAsync.cs @@ -46,8 +46,7 @@ public Task ExecuteAsync(CancellationToken cancellationToken, IProgress method.GetCompletions( - _methodDescriptor, completionContext.MemberDescriptor, completionContext.Find); + => method.GetCompletions(_methodDescriptor, completionContext.MemberDescriptor); string ICommand.GetUsage(bool isDetail) { diff --git a/src/JSSoft.Terminals/SystemTerminalBase.cs b/src/JSSoft.Terminals/SystemTerminalBase.cs index 834bd01..49a6e1a 100644 --- a/src/JSSoft.Terminals/SystemTerminalBase.cs +++ b/src/JSSoft.Terminals/SystemTerminalBase.cs @@ -138,7 +138,7 @@ protected virtual void OnExecuted(Exception? exception) protected virtual string FormatCommand(string command) => command; - protected virtual string[] GetCompletion(string[] items, string find) => []; + protected virtual string[] GetCompletions(string[] items, string find) => []; protected virtual void OnDispose() { @@ -262,7 +262,7 @@ private sealed class InternalSystemTerminalHost(SystemTerminalBase terminalBase) protected override string[] GetCompletion(string[] items, string find) { - return terminalBase.GetCompletion(items, find); + return terminalBase.GetCompletions(items, find); } } diff --git a/test/JSSoft.Commands.Tests/CommandContextTests/CompletionsTest.cs b/test/JSSoft.Commands.Tests/CommandContextTests/CompletionsTest.cs new file mode 100644 index 0000000..37e975e --- /dev/null +++ b/test/JSSoft.Commands.Tests/CommandContextTests/CompletionsTest.cs @@ -0,0 +1,246 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System.Threading; +using System.Threading.Tasks; + +namespace JSSoft.Commands.Tests.CommandContextTests; + +public class CompletionsTest +{ + private static readonly ICommand[] _commands = + [ + new Test2Command(), + new Test1Command(), + new AddCommand(), + new NodeCommand(), + ]; + + private static readonly TestCommandContext _commandContext = new(_commands); + + [Fact] + public void CommandCompletion1_Test() + { + var completions = _commandContext.GetCompletions([], string.Empty); + var i = 0; + Assert.Equal("test2", completions[i++]); + Assert.Equal("test1", completions[i++]); + Assert.Equal("add", completions[i++]); + Assert.Equal("node", completions[i++]); + Assert.Equal("help", completions[i++]); + Assert.Equal("version", completions[i++]); + Assert.Equal(i, completions.Length); + } + + [Fact] + public void CommandCompletion2_Test() + { + var completions = _commandContext.GetCompletions([], "h"); + var i = 0; + Assert.Equal("help", completions[i++]); + Assert.Equal(i, completions.Length); + } + + [Fact] + public void CommandCompletion3_Test() + { + var completions = _commandContext.GetCompletions([], "t"); + var i = 0; + Assert.Equal("test1", completions[i++]); + Assert.Equal("test2", completions[i++]); + Assert.Equal(i, completions.Length); + } + + private sealed class Test1Command : CommandBase + { + [CommandPropertyRequired] + [CommandPropertyCompletion(nameof(GetTextCompletions))] + public string Text { get; set; } = string.Empty; + + [CommandPropertyExplicitRequired] + [CommandPropertyCompletion(nameof(GetMessageCompletions))] + public string Message { get; set; } = string.Empty; + + protected override void OnExecute() + { + // do nothing + } + + private static string[] GetTextCompletions() + => ["text2", "wow1", "text1", string.Empty, "wow2"]; + + private static string[] GetMessageCompletions() + => ["text2_e", "wow1_e", "text1_e", "wow2_e"]; + } + + [Fact] + public void Test1CommandCompletion1_Test() + { + var completions = _commandContext.GetCompletions(["test1"], string.Empty); + var i = 0; + Assert.Equal("text2", completions[i++]); + Assert.Equal("wow1", completions[i++]); + Assert.Equal("text1", completions[i++]); + Assert.Equal("wow2", completions[i++]); + Assert.Equal(i, completions.Length); + } + + [Fact] + public void Test1CommandCompletion2_Test() + { + var completions = _commandContext.GetCompletions(["test1", "wow"], string.Empty); + var i = 0; + Assert.Equal(i, completions.Length); + } + + [Fact] + public void Test1CommandCompletion3_Test() + { + var completions = _commandContext.GetCompletions( + ["test1", "wow", "--message"], string.Empty); + var i = 0; + Assert.Equal("text2_e", completions[i++]); + Assert.Equal("wow1_e", completions[i++]); + Assert.Equal("text1_e", completions[i++]); + Assert.Equal("wow2_e", completions[i++]); + Assert.Equal(i, completions.Length); + } + + private sealed class Test2Command : CommandAsyncBase + { + [CommandPropertyArray] + [CommandPropertyCompletion(nameof(GetItemCompletions))] + public string[] Items { get; set; } = []; + + protected override Task OnExecuteAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + private static string[] GetItemCompletions() + => ["none", "type_a", "unknown", "all"]; + } + + public static readonly IEnumerable Test2CommandCompletion1TestMembers = + [ + [Array.Empty()], + [new string[] { "--" }], + [new string[] { "none" }], + [new string[] { "all", "unknown" }], + ]; + + [Theory] + [MemberData(nameof(Test2CommandCompletion1TestMembers))] + public void Test2CommandCompletion1_Test(string[] items) + { + var completions = _commandContext.GetCompletions( + ["test2", .. items], string.Empty); + var i = 0; + Assert.Equal("none", completions[i++]); + Assert.Equal("type_a", completions[i++]); + Assert.Equal("unknown", completions[i++]); + Assert.Equal("all", completions[i++]); + Assert.Equal(i, completions.Length); + } + + public static readonly IEnumerable Test2CommandCompletion2TestMembers = + [ + [Array.Empty(), "n", new string[] { "none" }], + [new string[] { "--" }, "u", new string[] { "unknown" }], + [new string[] { "none" }, "a", new string[] { "all" }], + [new string[] { "all", "unknown" }, "a", new string[] { "all" }], + ]; + + [Theory] + [MemberData(nameof(Test2CommandCompletion2TestMembers))] + public void Test2CommandCompletion2_Test(string[] items, string find, string[] expectedItems) + { + var completions = _commandContext.GetCompletions( + ["test2", .. items], find); + Assert.Equal(expectedItems, completions); + } + + private abstract class AddCommandBase : CommandBase + { + [CommandProperty] + [CommandPropertyCompletion(nameof(GetBasePropertyCompletions))] + public string BaseProperty { get; set; } = string.Empty; + + private static string[] GetBasePropertyCompletions() => ["prop2", "prop1"]; + } + + private sealed class AddCommand : AddCommandBase + { + [CommandProperty] + public string Text { get; set; } = string.Empty; + + protected override void OnExecute() + { + // do nothing + } + + protected override string[] GetCompletions(CommandCompletionContext completionContext) + { + if (completionContext.MemberDescriptor.MemberName == nameof(Text)) + { + return ["text1", "text2"]; + } + + return base.GetCompletions(completionContext); + } + } + + [Fact] + public void AddCommandCompletion1_Test() + { + var completions = _commandContext.GetCompletions(["add", "--text"], string.Empty); + var i = 0; + Assert.Equal("text1", completions[i++]); + Assert.Equal("text2", completions[i++]); + Assert.Equal(i, completions.Length); + } + + [Fact] + public void AddCommandCompletion2_Test() + { + var completions = _commandContext.GetCompletions(["add", "--base-property"], string.Empty); + var i = 0; + Assert.Equal("prop2", completions[i++]); + Assert.Equal("prop1", completions[i++]); + Assert.Equal(i, completions.Length); + } + + [Fact] + public void AddCommandCompletion3_Test() + { + var completions = _commandContext.GetCompletions( + ["add", "--base-property"], "p"); + var i = 0; + Assert.Equal("prop1", completions[i++]); + Assert.Equal("prop2", completions[i++]); + Assert.Equal(i, completions.Length); + } + + private sealed class NodeCommand : CommandMethodBase + { + [CommandMethod] + public void Start( + [CommandParameterCompletion(nameof(GetPortCompletions))] int port) + { + // do nothing + } + + private string[] GetPortCompletions() => ["8080", "8081", "8082"]; + } + + [Fact] + public void NodeCommandCompletion1_Test() + { + var completions = _commandContext.GetCompletions(["node", "start"], string.Empty); + var i = 0; + Assert.Equal("8080", completions[i++]); + Assert.Equal("8081", completions[i++]); + Assert.Equal("8082", completions[i++]); + Assert.Equal(i, completions.Length); + } +} diff --git a/test/JSSoft.Commands.Tests/ParseContextTest.cs b/test/JSSoft.Commands.Tests/ParseContextTest.cs new file mode 100644 index 0000000..875d5b3 --- /dev/null +++ b/test/JSSoft.Commands.Tests/ParseContextTest.cs @@ -0,0 +1,98 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +namespace JSSoft.Commands.Tests; + +public class ParseContextTest +{ + [CommandPropertyExplicitRequired("es")] + public string ExplicitString { get; set; } = string.Empty; + + [CommandPropertyExplicitRequired("en", DefaultValue = 1)] + public int ExplicitNumber { get; set; } + + [CommandPropertyRequired] + public string RequiredString { get; set; } = string.Empty; + + [CommandPropertyRequired] + public int RequiredNumber { get; set; } + + [CommandProperty('t')] + public string String { get; set; } = string.Empty; + + [CommandProperty('n', DefaultValue = 1)] + public int Number { get; set; } + + [CommandPropertySwitch('s')] + public bool Switch { get; set; } + + [CommandPropertyArray] + public string[] Items { get; set; } = []; + + public static readonly IEnumerable TestMethod1TestData = + [ + [0, Array.Empty(), nameof(RequiredString)], + [1, new string[] { "a", }, nameof(RequiredNumber)], + [2, new string[] { "a", "1" }, string.Empty], + [3, new string[] { "a", "--es" }, nameof(ExplicitString)], + [4, new string[] { "--es" }, nameof(ExplicitString)], + [5, new string[] { "a", "1", "--es", "a" }, string.Empty], + [6, new string[] { "a", "1", "--es", "a", "--en" }, nameof(ExplicitNumber)], + [7, new string[] { "a", "1", "--en" }, nameof(ExplicitNumber)], + [8, new string[] { "a", "1", "--es", "a", "--en", "0" }, nameof(Items)], + [9, new string[] { "a", "1", "--es", "a", "--en", "--" }, nameof(Items)], + [10, new string[] { "a", "1", "--es", "a", "--en", "0", "--" }, nameof(Items)], + [11, new string[] { "-s" }, nameof(RequiredString)], + [12, new string[] { "-t" }, nameof(String)], + [13, new string[] { "a", "1", "--es", "a", "--en", "-n" }, nameof(Number)], + [14, new string[] { "a", "1", "--es", "a", "--en", "-n", "0" }, nameof(Items)], + [15, new string[] { "a", "1", "--es", "a", "--en", "-n", "0", "--" }, nameof(Items)], + [16, new string[] { "a", "1", "--es", "a", "--en", "-n", "0", "-s" }, nameof(Items)], + [17, new string[] { "a", "1", "--es", "a", "--en", "-n", "0", "-s", "--" }, nameof(Items)], + ]; + + [Theory] + [MemberData(nameof(TestMethod1TestData))] + public void TestMethod1(int index, string[] args, string expectedPropertyName) + { + var memberDescriptors = CommandDescriptor.GetMemberDescriptors(typeof(ParseContextTest)); + var parseContext = new ParseContext(memberDescriptors); + parseContext.Next(args); + var descriptor = parseContext.Descriptor; + var actualPropertyName = descriptor?.MemberDescriptor.MemberName ?? string.Empty; + Assert.Equal(expectedPropertyName, actualPropertyName); + Assert.True(index >= 0); + } + + public static readonly IEnumerable TestMethod2TestData = + [ + [ + 0, + new string[] { "--" }, + "Cannot use '--'. All required options must be set.", + ], + [ + 1, + new string[] { "a", "1", "--es", "a", "--en", "--", "--" }, + "The '--' option cannot be used because the mode is already set to ", + ], + [ + 2, + new string[] { "a", "1", "--es", "--" }, + "Cannot use '--'. Value of the '--es' must be specified", + ], + ]; + + [Theory] + [MemberData(nameof(TestMethod2TestData))] + public void TestMethod2_Throw(int index, string[] args, string message) + { + var memberDescriptors = CommandDescriptor.GetMemberDescriptors(typeof(ParseContextTest)); + var parseContext = new ParseContext(memberDescriptors); + var exception = Assert.Throws(() => parseContext.Next(args)); + Assert.StartsWith(message, exception.Message); + Assert.True(index >= 0); + } +} From 7d29a7e7906a90443b4440e2e60c216d8fed6e89 Mon Sep 17 00:00:00 2001 From: s2quake Date: Wed, 25 Dec 2024 09:58:09 +0900 Subject: [PATCH 4/7] style: Clean code --- src/JSSoft.Commands/CommandCollection.cs | 2 +- src/JSSoft.Commands/CommandMethodBase.cs | 2 +- .../CommandDeclaringTypeNullException.cs | 7 ++----- .../Exceptions/CommandInvalidNameException.cs | 13 +++++------- .../CommandInvalidShortNameException.cs | 11 ++++------ .../SystemTerminalHost.KeyBindings.cs | 14 +++++++------ src/JSSoft.Terminals/TerminalKeyBindings.cs | 21 +++++++++++-------- .../CommandAsICustomCommandDescriptor.cs | 2 +- test/JSSoft.Tests.Shared/RandomUtility.cs | 2 +- 9 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/JSSoft.Commands/CommandCollection.cs b/src/JSSoft.Commands/CommandCollection.cs index e049dc0..b74b03d 100644 --- a/src/JSSoft.Commands/CommandCollection.cs +++ b/src/JSSoft.Commands/CommandCollection.cs @@ -26,7 +26,7 @@ public CommandCollection(IEnumerable commands) Add(command); } - _commandList = new List(commands); + _commandList = [.. commands]; } public static CommandCollection Empty { get; } = new() { IsLocked = true }; diff --git a/src/JSSoft.Commands/CommandMethodBase.cs b/src/JSSoft.Commands/CommandMethodBase.cs index 470d571..ac72dbf 100644 --- a/src/JSSoft.Commands/CommandMethodBase.cs +++ b/src/JSSoft.Commands/CommandMethodBase.cs @@ -142,7 +142,7 @@ private static CommandCollection CreateCommands(CommandMethodBase obj) var query = from methodDescriptor in methodDescriptors select CreateCommand(obj, methodDescriptor); var commands = query.ToArray(); - return new CommandCollection(commands); + return [.. commands]; static ICommand CreateCommand( CommandMethodBase obj, CommandMethodDescriptor methodDescriptor) diff --git a/src/JSSoft.Commands/Exceptions/CommandDeclaringTypeNullException.cs b/src/JSSoft.Commands/Exceptions/CommandDeclaringTypeNullException.cs index 7902fab..005dfa5 100644 --- a/src/JSSoft.Commands/Exceptions/CommandDeclaringTypeNullException.cs +++ b/src/JSSoft.Commands/Exceptions/CommandDeclaringTypeNullException.cs @@ -5,10 +5,7 @@ namespace JSSoft.Commands.Exceptions; -internal sealed class CommandDeclaringTypeNullException : CommandDefinitionException +internal sealed class CommandDeclaringTypeNullException(CommandMemberInfo memberInfo) + : CommandDefinitionException($"{memberInfo}' does not have a declaring type.", memberInfo) { - public CommandDeclaringTypeNullException(CommandMemberInfo memberInfo) - : base($"{memberInfo}' does not have a declaring type.", memberInfo) - { - } } diff --git a/src/JSSoft.Commands/Exceptions/CommandInvalidNameException.cs b/src/JSSoft.Commands/Exceptions/CommandInvalidNameException.cs index 3789550..272b6b6 100644 --- a/src/JSSoft.Commands/Exceptions/CommandInvalidNameException.cs +++ b/src/JSSoft.Commands/Exceptions/CommandInvalidNameException.cs @@ -5,13 +5,10 @@ namespace JSSoft.Commands.Exceptions; -internal sealed class CommandInvalidNameException : CommandDefinitionException -{ - public CommandInvalidNameException(CommandMemberInfo memberInfo, string name) - : base( - message: $"'{name}' is not a name that matches the regular expression pattern " + +internal sealed class CommandInvalidNameException(CommandMemberInfo memberInfo, string name) + : CommandDefinitionException( + message: $"'{name}' is not a name that matches the regular expression pattern " + $"'{CommandUtility.NamePattern}'.", - memberInfo: memberInfo) - { - } + memberInfo: memberInfo) +{ } diff --git a/src/JSSoft.Commands/Exceptions/CommandInvalidShortNameException.cs b/src/JSSoft.Commands/Exceptions/CommandInvalidShortNameException.cs index 4a4d623..0c6c57b 100644 --- a/src/JSSoft.Commands/Exceptions/CommandInvalidShortNameException.cs +++ b/src/JSSoft.Commands/Exceptions/CommandInvalidShortNameException.cs @@ -5,12 +5,9 @@ namespace JSSoft.Commands.Exceptions; -internal sealed class CommandInvalidShortNameException : CommandDefinitionException +internal sealed class CommandInvalidShortNameException(CommandMemberInfo memberInfo, char value) + : CommandDefinitionException( + message: $"'{value}' is an invalid short name. Only alphabetic characters can be used.", + memberInfo: memberInfo) { - public CommandInvalidShortNameException(CommandMemberInfo memberInfo, char value) - : base( - message: $"'{value}' is an invalid short name. Only alphabetic characters can be used.", - memberInfo: memberInfo) - { - } } diff --git a/src/JSSoft.Terminals/SystemTerminalHost.KeyBindings.cs b/src/JSSoft.Terminals/SystemTerminalHost.KeyBindings.cs index bfc06e6..067782d 100644 --- a/src/JSSoft.Terminals/SystemTerminalHost.KeyBindings.cs +++ b/src/JSSoft.Terminals/SystemTerminalHost.KeyBindings.cs @@ -25,8 +25,9 @@ public partial class SystemTerminalHost new KeyBinding(TerminalKey.RightArrow, (t) => t.Right()), ]; - public static TerminalKeyBindingCollection WindowsKeyBindings { get; } = new(CommonKeyBindings) - { + public static TerminalKeyBindingCollection WindowsKeyBindings { get; } = + [ + .. CommonKeyBindings, new KeyBinding(TerminalKey.Enter, InputEnter), new KeyBinding(TerminalModifiers.Control, TerminalKey.C, (t) => t.CancelInput()), new KeyBinding(TerminalKey.Escape, (t) => t.Command = string.Empty, (t) => !t.IsPassword), @@ -38,10 +39,11 @@ public partial class SystemTerminalHost new KeyBinding(TerminalModifiers.Shift, TerminalKey.Tab, (t) => t.PrevCompletion(), (t) => !t.IsPassword), new KeyBinding(TerminalModifiers.Control, TerminalKey.LeftArrow, (t) => PrevWord(t), (t) => !t.IsPassword), new KeyBinding(TerminalModifiers.Control, TerminalKey.RightArrow, (t) => NextWord(t), (t) => !t.IsPassword), - }; + ]; - public static TerminalKeyBindingCollection LinuxKeyBindings { get; } = new(CommonKeyBindings) - { + public static TerminalKeyBindingCollection LinuxKeyBindings { get; } = + [ + .. CommonKeyBindings, new KeyBinding(TerminalKey.Enter, InputEnter), new KeyBinding(TerminalModifiers.Control, TerminalKey.C, (t) => t.CancelInput()), new KeyBinding(TerminalKey.Escape, (t) => {}), @@ -54,7 +56,7 @@ public partial class SystemTerminalHost new KeyBinding(TerminalModifiers.Control, TerminalKey.L, (t) => t.Clear(), (t) => !t.IsPassword), new KeyBinding(TerminalKey.Tab, (t) => t.NextCompletion(), (t) => !t.IsPassword), new KeyBinding(TerminalModifiers.Shift, TerminalKey.Tab, (t) => t.PrevCompletion(), (t) => !t.IsPassword), - }; + ]; private static TerminalKeyBindingCollection GetDefaultKeyBindings() { diff --git a/src/JSSoft.Terminals/TerminalKeyBindings.cs b/src/JSSoft.Terminals/TerminalKeyBindings.cs index 477e15b..cd6252f 100644 --- a/src/JSSoft.Terminals/TerminalKeyBindings.cs +++ b/src/JSSoft.Terminals/TerminalKeyBindings.cs @@ -47,17 +47,19 @@ public static TerminalKeyBindingCollection GetDefaultBindings() new TerminalKeyBinding(TerminalKey.Delete, (t) => t.Delete()), ]; - public static readonly TerminalKeyBindingCollection MacOS = new(Common) - { + public static readonly TerminalKeyBindingCollection MacOS = + [ + .. Common, new TerminalKeyBinding(TerminalModifiers.Control, TerminalKey.E, (t) => t.MoveToLast()), new TerminalKeyBinding(TerminalModifiers.Control, TerminalKey.A, (t) => t.MoveToFirst()), new TerminalKeyBinding(TerminalKey.PageUp, (g) => g.Scroll.PageUp()), new TerminalKeyBinding(TerminalKey.PageDown, (g) => g.Scroll.PageDown()), - }; + ]; - public static readonly TerminalKeyBindingCollection Windows = new(Common) - { + public static readonly TerminalKeyBindingCollection Windows = + [ + .. Common, new TerminalKeyBinding(TerminalKey.Home, (t) => t.MoveToFirst()), new TerminalKeyBinding(TerminalKey.End, (t) => t.MoveToLast()), @@ -68,10 +70,11 @@ public static TerminalKeyBindingCollection GetDefaultBindings() new TerminalKeyBinding(TerminalModifiers.Control, TerminalKey.Home, (g) => g.Scroll.ScrollToTop()), new TerminalKeyBinding(TerminalModifiers.Control, TerminalKey.End, (g) => g.Scroll.ScrollToBottom()), new TerminalKeyBinding(TerminalModifiers.Control, TerminalKey.A, (g) => g.Selections.SelectAll()), - }; + ]; - public static readonly TerminalKeyBindingCollection Linux = new(Common) - { + public static readonly TerminalKeyBindingCollection Linux = + [ + .. Common, new TerminalKeyBinding(TerminalModifiers.Control, TerminalKey.E, t => t.MoveToLast()), new TerminalKeyBinding(TerminalModifiers.Control, TerminalKey.A, t => t.MoveToFirst()), new TerminalKeyBinding(TerminalKey.Home, (t) => t.MoveToFirst()), @@ -83,5 +86,5 @@ public static TerminalKeyBindingCollection GetDefaultBindings() new TerminalKeyBinding(TerminalModifiers.Control | TerminalModifiers.Shift, TerminalKey.UpArrow, (g) => g.Scroll.LineUp()), new TerminalKeyBinding(TerminalModifiers.Shift, TerminalKey.Home, (g) => g.Scroll.ScrollToTop()), new TerminalKeyBinding(TerminalModifiers.Shift, TerminalKey.End, (g) => g.Scroll.ScrollToBottom()), - }; + ]; } diff --git a/test/JSSoft.Commands.Tests/CommandContextTests/CommandAsICustomCommandDescriptor.cs b/test/JSSoft.Commands.Tests/CommandContextTests/CommandAsICustomCommandDescriptor.cs index e215c1c..a6437c3 100644 --- a/test/JSSoft.Commands.Tests/CommandContextTests/CommandAsICustomCommandDescriptor.cs +++ b/test/JSSoft.Commands.Tests/CommandContextTests/CommandAsICustomCommandDescriptor.cs @@ -51,7 +51,7 @@ public RunCommand() itemList.AddRange(descriptors); } - _descriptorByInstance = new(itemList); + _descriptorByInstance = itemList.ToDictionary(item => item.Key, item => item.Value); _descriptors = new(GetType(), [.. _descriptorByInstance.Keys]); } diff --git a/test/JSSoft.Tests.Shared/RandomUtility.cs b/test/JSSoft.Tests.Shared/RandomUtility.cs index c1b74eb..6f3294f 100644 --- a/test/JSSoft.Tests.Shared/RandomUtility.cs +++ b/test/JSSoft.Tests.Shared/RandomUtility.cs @@ -216,7 +216,7 @@ public static List List(Func generator) items[i] = generator(); } - return new List(items); + return [.. items]; } public static ImmutableArray ImmutableArray(Func generator) From 933796b24f1ab44697f6f4423cf3dab71fa5f1eb Mon Sep 17 00:00:00 2001 From: s2quake Date: Thu, 26 Dec 2024 19:08:45 +0900 Subject: [PATCH 5/7] chore: Add public key to assembly info --- src/JSSoft.Commands/AssemblyInfo.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/JSSoft.Commands/AssemblyInfo.cs b/src/JSSoft.Commands/AssemblyInfo.cs index a67e923..9460b15 100644 --- a/src/JSSoft.Commands/AssemblyInfo.cs +++ b/src/JSSoft.Commands/AssemblyInfo.cs @@ -5,4 +5,9 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("JSSoft.Commands.Tests")] +[assembly: InternalsVisibleTo("JSSoft.Commands.Tests, PublicKey=002400000480000094000000060200000" + + "0240000525341310004000001000100b95aa034365ba94cb2597445ba4a19b4cbe" + + "54fe4402c3a7c99211bb321127c9c75d7a01523e1770a28886d857f5d9f1623d1d" + + "5ceb772bfabaa1b2037cc470b087d66dd5120758034f8eb84a352c577316a522dd" + + "95fd01bd868040176a3fd8d15ce3e3fbec56e5f833c5ccd1eddfa008f16714501b" + + "fba78ad788095faedcb7dba")] From c09c3e41446eb981ef21a785ef988a52b74cf7ca Mon Sep 17 00:00:00 2001 From: s2quake Date: Thu, 26 Dec 2024 19:16:46 +0900 Subject: [PATCH 6/7] fix: Fix netstandard 2.1 error --- src/JSSoft.Commands/CommandStaticTypeAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JSSoft.Commands/CommandStaticTypeAttribute.cs b/src/JSSoft.Commands/CommandStaticTypeAttribute.cs index 9f85fc5..30e710f 100644 --- a/src/JSSoft.Commands/CommandStaticTypeAttribute.cs +++ b/src/JSSoft.Commands/CommandStaticTypeAttribute.cs @@ -66,7 +66,7 @@ internal MethodInfo GetMethodInfo(CommandMemberInfo memberInfo, string methodNam var type = GetTypeWithFallback(memberInfo); var bindingFlags = CommandSettings.GetBindingFlags(type); - return type.GetMethod(methodName, bindingFlags, types) + return type.GetMethod(methodName, bindingFlags, binder: null, types, modifiers: null) ?? throw new CommandDefinitionException( $"'{methodName}' method not found in '{type.FullName}'.", memberInfo); } From ec0a5586e09c1bb68037aa41dd768e613c48fbcd Mon Sep 17 00:00:00 2001 From: s2quake Date: Thu, 26 Dec 2024 19:19:17 +0900 Subject: [PATCH 7/7] chore: Changes --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c4b08e2..9f8e3b3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ Commands changes To be Released. +* Renamed method `GetCompletion` to `GetCompletions`. [[#47]] + +[#47]: https://github.com/s2quake/commands/pull/47 + 7.0.0 -------------