Parses a string to set a value to a specified property or invoke a specified method.
Like Terminal or Powershell, it provides a REPL environment to provide a command-based development environment.
dotnet sdk 8.0.100 or later
c# 12
git clone https://github.com/s2quake/commands.git
dotnet build
# net7.0
dotnet build -p:TargetFrameworks=net7.0 --framework net7.0
# netcoreapp3.1
dotnet build -p:TargetFrameworks=netcoreapp3.1 --framework netcoreapp3.1
# netstandard2.1
dotnet build -p:TargetFrameworks=netstandard2.1 --framework netstandard2.1
# net481
dotnet build -p:TargetFrameworks=net481 --framework net481
# Run the property settings example project
dotnet run --project JSSoft.Commands.Parse --framework net8.0 -- --help
# Run the method call example project
dotnet run --project JSSoft.Commands.Invoke --framework net8.0 -- --help
# Run the CommandContext Execution example Project
dotnet run --project JSSoft.Commands.Sets --framework net8.0 -- --help
# Run the CommandContext Execution Example Project in the REPL environment
dotnet run --project JSSoft.Commands.Repl --framework net8.0
# Run the CommandContext Execution Example Project with Avalonia UI
dotnet run --project JSSoft.Commands.AppUI --framework net8.0
This is the most basic way to parse the command. Provides a function to set a value for a specified property.
var settings = new Settings();
var parser = new CommandParser(settings);
parser.Parse(args);
See the JSSoft.Commands.Parse project
As an extension of Parse, it provides the ability to call a specified method by parsing the command.
var commands = new Commands();
var invoker = new CommandInvoker(commands);
invoker.Invoke(args);
See the JSSoft.Commands.Invoke project
It provides various functions to manage and process more commands.
var commands = new ICommand[]
{
new LoginCommand(),
new LogoutCommand(),
new ExitCommand()
};
var commandContext = new CommandContext(commands);
commandContext.Execute(args);
or
await commandContext.ExecuteAsync(args);
See the JSSoft.Commands.Sets project
It can be combined with user input such as EditBox, TextBox, InputText to build a console or REPL-like environment.
var commands = new ICommand[]
{
new LoginCommand(),
new LogoutCommand(),
new ExitCommand()
};
var commandContext = new CommandContext(commands);
var terminal = new SystemTerminal(commandContext);
await terminal.StartAsync(CancellationToken.None);
See the JSSoft.Commands.Repl project
To define the value required for command syntax, define CommandPropertyRequired in the property.
[CommandPropertyRequired]
public string Value1 { get; set; } = string.Empty;
[CommandPropertyRequired]
public int Value2 { get; set; }
"value" // error! value for Value2 does not exists.
3 // format error!
"value" 3 // Value1 is "value", Value2 is 3
You can set default values like this: If there is no value in the command syntax, it is replaced with the default value.
[CommandPropertyRequired]
public string Value1 { get; set; } = string.Empty;
[CommandPropertyRequired(DefaultValue = 1)]
public int Value2 { get; set; }
"value" 2 // Value1 is "value", Value2 is 2
"value" // Value1 is "value", Value2 is 1
An explicit required argument indicates that the command syntax must have a value, but must include a switch statement, such as --value "2".
[CommandPropertyRequired]
public string Value1 { get; set; } = string.Empty;
[CommandPropertyExplicitRequired]
public int Value2 { get; set; }
"value" // error!
"value" 2 // error!
"value" --value2 // error!
"value" --value2 3 // Value1 is "value", Value2 is 3
--value2 3 "value" // Value1 is "value", Value2 is 3
In order to use the default values of explicit required arguments, the command syntax must include a switch statement such as --value.
[CommandPropertyRequired]
public string Value1 { get; set; } = string.Empty;
[CommandPropertyExplicitRequired(DefaultValue = 1)]
public int Value2 { get; set; }
"value" // error!
"value" 2 // error!
"value" --value2 // Value1 is "value", Value2 is 1
"value" --value2 3 // Value1 is "value", Value2 is 3
--value2 3 "value" // Value1 is "value", Value2 is 3
--value2 "value" // error! "value" is not int
The optional argument can be set whether or not to use a value using a switch statement.
[CommandProperty]
public string Value { get; set; } = string.Empty;
--value // error
--value text // value is "text"
To use the default, the command syntax must include a switch statement such as --value.
[CommandProperty(DefaultValue = "1")]
public string Value { get; set; } = string.Empty;
--value // value is "1"
--value text // value is "text"
A bool type switch statement that does not use a value should be defined as follows.
[CommandPropertySwitch]
public bool Switch { get; set; }
Variable arguments represent the values of the remaining arguments that were not parsed in the command syntax.
The property type of a variable arguments must be an array and must be defined for only one property.
[CommandPropertyArray]
public string[] Values { get; set; } = Array.Empty<string>();
-- value1 value2 value3 "value4"
To execute an attribute method through command syntax, you must define a CommandMethod in the method as follows.
Each parameter of the method is automatically defined as a required argument.
[CommandMethod]
public void Save(string message)
{
}
save "message"
If you want to additionally define optional arguments in the method, you can use CommandMethodProperty and add the name of the property defined as CommandProperty.
[CommandMethod]
[CommandMethodProperty("Value")]
public void Save(string message)
{
}
save "comment"
save "comment" --value text
You can use params as below as a variable arguments.
[CommandMethod]
public void Save(string message, params string[] args)
{
}
save "comment"
save "comment" -- "1" "text" "string"
Define the properties by prefixing "Can" to the method name as shown below.
public bool Can{MethodName} { get; }
example:
public bool CanSave => true;
Properties and methods defined as static can be included in the object and used.
static class GlobalSettings
{
[CommandProperty]
public static string ID { get; set; } = string.Empty;
[CommandProperty]
public static string Password { get; set; }
}
[CommandStaticProperty(typeof(GlobalSettings))]
class Settings
{
}
static class StaticCommand
{
[CommandMethod]
[CommandMethodProperty(nameof(Value))]
public static void List()
{
}
[CommandProperty]
public static int Value { get; set; }
}
[CommandStaticMethod(typeof(StaticCommand))]
class Commands
{
}
The property and method names defined as CommandProperty and CommandMethod are changed to kebab-case (spinal-case, Train-Case, Lisp-case).
Property name | Changed property name |
---|---|
Value | --value |
Message | --message |
IsLocked | --is-locked |
When using a name and a short name
[CommandProperty("custom-value", 'v')]
public string Value { get; set; } = string.Empty;
--custom-value or -v
When using only short names
[CommandProperty('v')]
public string Value { get; set; } = string.Empty;
-v
When using a short name and a default name
[CommandProperty('v', AllowName = true)]
public string Value { get; set; } = string.Empty;
-v or --value
Method name | Changed method name |
---|---|
Save | save |
LockTable | lock-table |
You can also set the method name yourself.
[CommandMethod("save")]
public void Save(string message)
{
}
You can define commands in the CommandContext.
class ExitCommand : CommandBase
{
[CommandPropertyRequired(DefaultValue = 0)]
public int ExitCode { get; set; }
protected override void OnExecute()
{
Environment.Exit(ExitCode);
}
}
exit
exit 0
You can define commands that have subcommands in CommandContext.
class UserCommand : CommandMethodBase
{
[CommandMethod]
[CommandMethodProperty(nameof(Message))]
public void Create(string userID)
{
}
[CommandMethod]
public void Delete(string userID)
{
}
[CommandMethod]
public void List()
{
}
[CommandProperty]
public string Message { get; set; }
}
user create "user1"
user create "user1" --message "new user"
user delete "user1"
user list
By implementing a partial class, you can add subcommand to the already implemented command.
[PartialCommand]
class UserPartialCommand : CommandMethodBase
{
public UserPartialCommand()
: base("user")
{
}
[CommandMethod]
public void SendMessage(string userID, string message)
{
}
}
You can use asynchronous methods, as shown in the example below. The parameters CancellationToken and IProgress are optional, but should always be the last declaration.
For more information, see the Choosing the overloads to provide
topic in the TAP.
The name of an asynchronous method is used without the suffix Async.
class UserCommand : CommandMethodBase
{
[CommandMethod]
public Task Invoke1Async()
{
return Task.CompletedTask;
}
[CommandMethod]
public Task Invoke2Async(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
[CommandMethod]
public Task Invoke3Async(IProgress<ProgressInfo> progress)
{
return Task.CompletedTask;
}
[CommandMethod]
public Task Invoke4Async(CancellationToken cancellationToken, IProgress<ProgressInfo> progress)
{
return Task.CompletedTask;
}
}
Released under the MIT License.
Copyright (c) 2024 Jeesu Choi
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.