- Starting
- Input
- Output
- Working with properties
- Working with methods
- Parameterless methods
- optionais parameters
- Overloads
- Using positional inputs
- Ignore public methods by a manual choice using attribute
- Customizing actions names and arguments
- Customizing the help information on the actions and its parameters
- Changing the position of positional parameters
- Properties of ArgumentAttribute attributes which are not used
- Standard methods
- Help
- Verbose
- Error handling
- Context variables
- Manager objects in the form of files
- Command redirection
- Cancellation of the continuity of the implementation
- Historical management arguments
- Extras-OptionSet
The application context initialization can be done in two ways, by an instance of the class App
with its possible customizations or through the static method App.RunApplication
that provides a feature called console Simulator that helps you test inputs within the Visual Studio itself, without the need to perform your ".exe" in an external console.
The class App
is in the top of the class hierarchy, each instance equates to an isolated context that will contain a tree of other objects that are unique to this context. No static resource is used here and it's important to have the freedom to create as many instances you want in any scope.
In your constructor are the first settings:
public App(
IEnumerable<Type> commandsTypes = null,
bool enableMultiAction = true,
bool addDefaultAppHandler = true,
TextWriter output = null
)
commandsTypes
: Specifies theCommand
types that will be used throughout the process. If it isnull
then the system will try to automatically any class that extend fromCommand
. Understand better in Specifying the types of commands.enableMultiAction
: Turns theMultiAction
behavior. By default, this behavior is enabled. Understand better in Multi-action.addDefaultAppHandler
: Iffalse
so does not create the event handler that is responsible for theoutputs
standard engine anderros
controls, and among others. The default istrue
. Understand better in Event control.output
: Redirects the output to theTextWriter
specified. Otherwise will be used by defaultConsole.Out
.
This feature helps you test your inputs inside the Visual Studio without having to run your ".exe" in an external console. It is important to note that this Simulator will only be displayed within Visual Studio.
The call is quite simple, just add a line for everything to work using the standard rules. If you want to customize your App
instance so use the App.RunApplication(Func<App> appFactory)
constructor.
public class Program
{
public static void Main(string[] args)
{
// Default
App.RunApplication();
// Or use custom App
/*
App.RunApplication(() =>
{
var app = new App(enableMultiAction: false);
return app;
});
*/
}
public class MyCommand : Command
{
public string MyProperty
{
set
{
App.Console.Write(value);
}
}
}
}
When you run this code in Visual Studio, a prompt with the label cmd>
will be displayed. This indicates that you can start your tests as many times as needed. To exit, you can use the default shortcut "CTRL + C" or press the "stop" button on the Visual Studio.
cmd> --my-property value
value
cmd> --my-property otherValue
otherValue
When specifying each Command
which will be used, you lose the automatic search feature, but gains the flexibility to control what Commands
should or should not be part of your system. For this you can work in two ways, the inclusive or exclusive. The inclusive form is basically the specification of each Command
and the exclusive is the opposite, first if everything loads and then remove what you don't want.
The class SysCommand.ConsoleApp.Loader.AppDomainCommandLoader
is responsible for pick up Commands
automatically and you can use it if you want to use. Internally the system makes use of it if the commandsTypes
parameter is null
.
Inclusive form example:
public class Program
{
public static void Main(string[] args)
{
var commandsTypes = new[]
{
typeof(FirstCommand)
};
// Specify what you want.
new App(commandsTypes).Run(args);
// Search for any class that extends from Command.
/*
new App().Run(args);
*/
}
public class FirstCommand : Command
{
public string FirstProperty
{
set
{
App.Console.Write("FirstProperty");
}
}
}
public class SecondCommand : Command
{
public string SecondProperty
{
set
{
App.Console.Write("SecondProperty");
}
}
}
}
MyApp.exe help
usage: [--first-property=<phrase>] <actions[args]>
FirstCommand
--first-property Is optional.
Displays help information
help
--action Is optional.
Use 'help --action=<name>' to view the details of
any action. Every action with the symbol "*" can
have his name omitted.
Note that when you enter help
the class SecondCommand
is not displayed.
Note also that there is a help to the help engine itself, that Command
should always exist, if it is not specified in the list of types your own system will create it using the help SysCommand.ConsoleApp.Commands.HelpCommand
standard. For more information about customizing help see Help.
Example of exclusively:
public class Program
{
public static void Main(string[] args)
{
// Create loader instance
var loader = new AppDomainCommandLoader();
// Remove unwanted command
loader.IgnoreCommand<FirstCommand>();
loader.IgnoreCommand<VerboseCommand>();
loader.IgnoreCommand<ArgsHistoryCommand>();
// Get all commands with 'ignored' filter
var commandsTypes = loader.GetFromAppDomain();
new App(commandsTypes).Run(args);
}
public class FirstCommand : Command
{
public string FirstProperty
{
set
{
App.Console.Write("FirstProperty");
}
}
}
public class SecondCommand : Command
{
public string SecondProperty
{
set
{
App.Console.Write("SecondProperty");
}
}
}
}
MyApp.exe help
usage: [--second-property=<phrase>] <actions[args]>
SecondCommand
--second-property Is optional.
Displays help information
help
--action Is optional.
Use 'help --action=<name>' to view the details of
any action. Every action with the symbol "*" can
have his name omitted.
Note that when you enter help
the class FirstCommand
is not displayed.
For now, don't pay attention now to the classes VerboseCommand
and ArgsHistoryCommand
they are internal commands and will be explained further in the documentation.
There are currently three types of commands:
User commands
Are the common commands and that inherit the class Command
.
Help commands
Are the controls that inherit from the Command
class and implement the interface IHelpCommand
. However, only one will be used.
Debug commands
The debug commands are commands that are loaded only when debugging in Visual Studio. An example of this type is the built-in command "ClearCommand", he makes the clear
call action to clear the prompt opened by Visual Studio during debug. To create a "debug" command, you must enable the flag Command.OnlyInDebug
.
public class ClearCommand : Command
{
public ClearCommand()
{
this.HelpText = "Clear window. Only in debug";
this.OnlyInDebug = true;
}
public void Clear()
{
Console.Clear();
}
}
An interesting way of using the SysCommand is making use of several commands in an action that orchestrate the executions. It is important to remember that commands must be designed to work independently, if this is not possible, don't make it a command, create a class that does not inherit from Command
and use in your action.
The example below shows a scenario where it would be interesting using several commands in an action. The idea is to create an application that can do the Assembly of a csproj
and also the ZIP to a folder. However, we have an action Publish
that will make the publication of the application using the two commands.
using SysCommand.ConsoleApp;
using SysCommand.Mapping;
using System;
using System.Diagnostics;
using System.IO;
namespace Publisher
{
public class OrchestratorCommand : Command
{
public void Publish(string csproj, string dirOutput)
{
var build = App.Commands.Get<MSBuildCommand>();
var zip = App.Commands.Get<ZipCommand>();
build.Build(csproj, dirOutput);
pack.Zip(dirOutput);
}
}
public class ZipCommand : Command
{
private void Zip(string dirToZip)
{
System.IO.Compression.ZipFile.CreateFromDirectory(dirToZip, $"{dirToZip}/package.zip"});
}
}
public class MSBuildCommand : Command
{
public BuildCommand()
{
this.UsePrefixInAllMethods = true;
}
public void Clear()
{
// Clear
}
[Action(UsePrefix = false)]
public void Build(string csproj, string dirOutput)
{
try
{
var startInfo = new ProcessStartInfo
{
CreateNoWindow = false,
UseShellExecute = false,
FileName = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe",
WindowStyle = ProcessWindowStyle.Normal,
Arguments = string.Format("{0} /t:Build /m /property:Configuration={1} /p:OutDir={2}", csproj, "Debug", dirOutput)
};
using (Process exeProcess = Process.Start(startInfo))
{
exeProcess.WaitForExit();
}
}
catch (Exception ex)
{
App.Console.Error(ex.Message);
}
}
}
}
The events are important to intercept every step of implementation and modify or extend the default behavior. Existing events are as follows:
App.OnBeforeMemberInvoke(ApplicationResult, IMemberResult)
: Fires before invoking each Member (property or method) that was parsed.App.OnAfterMemberInvoke(ApplicationResult, IMemberResult)
: Fires after invoking each Member (property or method) that was parsed.App.OnMethodReturn(ApplicationResult, IMemberResult)
:: Fires when a method returns valueApp.OnComplete(ApplicationResult)
: Fires at the end of the implementationApp.OnException(ApplicationResult, Exception)
: Fires in case of exception.
Example:
public class Program
{
public static void Main(string[] args)
{
var app = new App();
app.OnBeforeMemberInvoke += (appResult, memberResult) =>
{
app.Console.Write("Before: " + memberResult.Name);
};
app.OnAfterMemberInvoke += (appResult, memberResult) =>
{
app.Console.Write("After: " + memberResult.Name);
};
app.OnMethodReturn += (appResult, memberResult) =>
{
app.Console.Write("After MethodReturn: " + memberResult.Name);
};
app.OnComplete += (appResult) =>
{
app.Console.Write("Count: " + appResult.ExecutionResult.Results.Count());
throw new Exception("Some error!!!");
};
app.OnException += (appResult, exception) =>
{
app.Console.Write(exception.Message);
};
app.Run(args);
}
public class FirstCommand : Command
{
public string MyProperty { get; set; }
public string MyAction()
{
return "Return MyAction";
}
}
}
MyApp.exe --my-property value my-action
Before: MyProperty
After: MyProperty
Before: MyAction
After: MyAction
Return MyAction
After MethodReturn: MyAction
Count: 2
Some error!!!
In the above example, note that the control passed to who implemented the events.
By default, we have the handler SysCommand.ConsoleApp.Handlers.DefaultApplicationHandler
that is added automatically. This handler is responsible for the outputs
standard engine and erros
controls. This was the responsible print the line "Return MyAction" from the output above. To turn it off and have full control of the events, simply disable the flag addDefaultAppHandler = false
in the constructor.
new App(addDefaultAppHandler: false).Run(args);
Another way to add events is using the interface SysCommand.ConsoleApp.Handlers.IApplicationHandler
. That way your rule is isolated, but having the counterpoint to be required to implement all the methods of the interface. To add a new handler follow the example below:
new App(addDefaultAppHandler: false)
.AddApplicationHandler(new CustomApplicationHandler())
.Run(args);
We split the input of user on two entities: arguments
and actions
.
The arguments represent the most basic of a console application, are typically represented as follows:
C:\MyApp.exe --argument-name value // Long
C:\MyApp.exe -v value // Short
C:\MyApp.exe value // Positional
Programmatically, the arguments
can be derived from the parameters of the methods or properties.
Named arguments are characterized by two ways: the long and the short. In long form the argument must begin with --
followed by your name. In short order he must start with just a dash -
or a forward slash /
followed by only one character that represents the argument.
The values of the arguments must be in front of the argument name separated by a space
or by :
characters or =
.
Example:
public string MyProperty { get;set; }
public string v { get;set; }
Long input:
MyApp.exe --my-property value
MyApp.exe -v value
Input short:
MyApp.exe -v value
Or using the delimiter /
and the tabs =
and:
MyApp.exe --my-property=value
MyApp.exe /v:value
Positional arguments work without the need to use the names of the arguments. Simply insert their values directly. You just have to be careful with this feature, because it can confuse the user in case of many positional arguments.
Example:
public string PropA { get;set; }
public string PropB { get;set; }
public string PropC { get;set; }
Input named:
MyApp.exe --prop-a ValueA --prop-b ValueB --prop-c ValueC
Positional input:
MyApp.exe ValueA ValueB ValueC
Comments:
- For properties, the positional input is disabled by default, to enable this feature, use the
Command.EnablePositionalArgs
configuration. - For the methods, this kind of input is enabled by default, to disable it see in the Using positional inputstopic.
Are reserved words to perform a particular action on your application. They don't need any suffix as with the arguments
, just using them directly in your input. Its use is similar to the way we use git resources, see:
git add -A;
git commit -m "comments"
Where add
and commit
would be the name of the stock and -A
-m
their respective arguments and.
Programmatically, the actions are derived from the methods.
The multi-action feature allows you to be able to shoot more than one action
on the same input. By default, it comes enabled and if you find it unnecessary then just shut it off. It is important to note that the resource Historical management arguments will crash if it does.
Another important point is the need to "escape" your input if the value that you want to enter conflicts with a name of a action
. This rule holds true for values of arguments of any kind (properties or parameters).
Example:
public class Program
{
public static void Main(string[] args)
{
new App().Run(args);
// EnableMultiAction = false
/*
new App(null, false).Run(args);
*/
}
public class MyCommand : Command
{
public string Action1(string value = "default")
{
return $"Action1 (value = {value})";
}
public string Action2(string value = "default")
{
return $"Action2 (value = {value})";
}
}
}
MyApp.exe action1
Action1 (value = default)
MyApp.exe action2
Action2 (value = default)
MyApp.exe action1 action2
Action1 (value = default)
Action2 (value = default)
MyApp.exe action1 action2 action1 action1 action2
Action1 (value = default)
Action2 (value = default)
Action1 (value = default)
Action1 (value = default)
Action2 (value = default)
MyApp.exe action1 --value \\action2
Action1 (value = action2)
The last example shows how to use the scape in their values that conflict with names of actions. An important fact is that in the example was used two backslashes to do the scape, but this can vary from console to console, on bash
the use of only one backslash has no effect, probably he should use for other scapes before arriving in application.
All primitive types of .NET are supported, including nullable versions: Nullable<?>
.
string
bool
orbool?
decimal
ordecimal?
double
ordouble?
int
orint?
uint
oruint?
DateTime
orDateTime?
byte
orbyte?
short
orshort?
ushort
orushort?
long
orlong?
ulong
orulong?
float
orfloat?
char
orchar?
Enum
/Enum Flags
orEnum?
Generic collections
(IEnumerable
,IList
,ICollection
)Arrays
Generic syntax:
[action-name ][-|/|--][argument-name][=|:| ][value]
Syntax for string
:
The strings
can be used in two ways:
- Text with spaces: Use quotes
" "
for text with spaces. Otherwise you'll get a parse error. - Text without spaces: it is not mandatory the use of quotation marks, just insert your value directly.
MyApp.exe --my-string oneWord
MyApp.exe --my-string "oneWord"
MyApp.exe --my-string "two words"
Syntax for char
:
As well as .NET the chars can have values with a single character or a number that represents your value in the range of characters.
MyApp.exe --my-char 1
MyApp.exe --my-char A
Syntax for int
, long
, short
and its variations "u":
Are numerical entries where the only rule is the value entered does not exceed the limit of each type.
MyApp.exe --my-number 1
MyApp.exe --my-number 2
MyApp.exe --my-number 999999
Syntax for decimal
, double
and float
:
For those types you can use whole numbers or decimal numbers. Just stay tuned for the culture of your application configuration. If pt-br
you use the ,
/separator To the American format use.
EN-US:
MyApp.exe --my-number 10
MyApp.exe --my-number 0.99
EN:
MyApp.exe --my-number 10
MyApp.exe --my-number 0,99
Syntax for Boolean
:
- For the value
TRUE
: Usetrue
,1
,+
(separated by space or attached with the name of the argument) or omit the value. - For the value
FALSE
: Usefalse
,0
,-
(separated by space or attached with the name of the argument).
MyApp.exe -a // true
MyApp.exe -a- // false
MyApp.exe -a+ // true
MyApp.exe -a - // false
MyApp.exe -a + // true
MyApp.exe -a true // true
MyApp.exe -a false // false
MyApp.exe -a 0 // true
MyApp.exe -a 1 // false
Multiple assignments:
For arguments that are configured with the short form, you can set the same value in several arguments with just a trace -
, see:
public void Main(char a, char b, char c) {};
MyApp.exe -abc // true for a, b and c
MyApp.exe -abc- // false for a, b and c
MyApp.exe -abc+ // true for a, b and c
Syntax for DateTime
:
The input format for the types DateTime
depends on the culture that is configured in your application.
EN-US:
MyApp.exe --my-date "12/13/2000 00:00:00"
EN:
MyApp.exe --my-date "13/12/2000 00:00:00"
Universal:
MyApp.exe --my-date "2000-12-13 00:00:00"
Syntax for Enums
:
The input values may vary between the Enum
name, case-sensitive format, or your internal number. For Enum Flags
, use spaces to add to the value of the argument.
[Flags]
public enum Verbose
{
None = 0,
All = 1,
Info = 2,
Success = 4,
Critical = 8,
Warning = 16,
Error = 32,
Quiet = 64
}
public void Main(Verbose verbose, string otherParameter = null);
MyApp.exe --verbose Error Info Success
MyApp.exe --verbose 32 2 Success
MyApp.exe Success EnumNotContainsThisString // positional
In the last example, the value "EnumNotContainsThisString" does not belong to the enum Verbose
, so the next argument will receive this value if your type is compatible.
Syntax for generic collections and arrays
The lists/arrays have the same pattern of input, separate with a space to add a new list item. If your text has your content space, so add it in quotation marks.
public void Main(IEnumerable<decimal> myLst, string[] myArray = null);
MyApp.exe --my-lst 1.0 1.99
MyApp.exe 1.0 1.99 // positional
MyApp.exe --my-lst 1.0 1.99 --my-array str1 str2
MyApp.exe --my-lst 1.0 1.99 --my-array "string with spaces" "other string" uniqueWord
MyApp.exe 1.0 1.99 str1 str2 // positional
In the last example, the value "str1" breaks the sequence of numbers 1.0 1.99
, so the next argument will receive this value if your type is compatible.
Important!
All conversions take into consideration the culture set in the static property "CultureInfo.CurrentCulture.
The parser is divided into 4 stages and the namespace SysCommand.DefaultExecutor
is responsible for include the logic of each step. The interface SysCommand.DefaultExecutor.IExecutor
contains 4 methods that represent each of these steps and the class SysCommand.DefaultExecutor.Executor
implements this interface with the standard rules SysCommand
.
The steps are:
- Mapping: represented by the
GetMaps
method. - Simple parser: Represented by the method
ParseRaw
- Complex parser: Represented by the method
Parse
- Execution: Represented by the method
Execute
public interface IExecutor
{
IEnumerable<CommandMap> GetMaps(IEnumerable<CommandBase> commands);
IEnumerable<ArgumentRaw> ParseRaw(string[] args, IEnumerable<CommandMap> commandsMap);
ParseResult Parse(string[] args, IEnumerable<ArgumentRaw> argumentsRaw, IEnumerable<CommandMap> commandsMap, bool enableMultiAction);
ExecutionResult Execute(ParseResult parseResult, Action<IMemberResult, ExecutionScope> onInvoke);
}
In mapping the focus is popular a SysCommand.Mapping.CommandMap
model list where each CommandMap
item represents a Command
, i.e. the command map with all its Properties
and Methods
.
For each Property
we have the class SysCommand.Mapping.ArgumentMap
that contains all the information of a property so that it becomes a argument
on the command line. Basically, this information reflects the ArgumentAttribute
added attribute of other internal information.
For each Action
we have the class SysCommand.Mapping.ActionMap
that contains all the information an action so that it becomes a action
on the command line. Basically, this information reflects the ActionAttribute
added attribute of other internal information. This class contains a list with the signature IEnumerable<ArgumentMap> ArgumentsMaps
that represents its parameters.
Finally, a list of the IEnumerable<CommandMap>
type is returned containing the map of each Command
.
It's the moment where the transformation of an input
object in the simplest possible way, the only additional information that this step needs is a ActionMap
list, so you can know when one action
was inputada. Each item is represented by the class SysCommand.Parsing.ArgumentRaw
that contains all the information of the argument as for example Name
, Value
and ArgumentFormat
that determines the format of the input, see its possibilities:
Unnamed
: Positional argumentShortNameAndNoValue
: Argument in short form and without value (Boolean)ShortNameAndHasValue
: Argument in short form with valueShortNameAndHasValueInName
: Argument in short form and with unified value with the name of the argument using=
or:
.LongNameAndNoValue
: Argument in long form without value (Boolean)LongNameAndHasValue
: Argument in long form with valueLongNameAndHasValueInName
: Argument in long form and with unified value with the name of the argument using=
or:
.
This step need to know actions
, the only reason to escape values that conflict with actions
names.
Consider that action1
it is an action with 1 optional argument called --value
and that accepts positional values:
public void Action1(string value = null);
Shoots action1
twice:
MyApp.exe action1 action1
Runs only once the action "action1" with the value "action1" --value
argument. Without this escape "action1" would be called twice:
MyApp.exe action1 \action1
So the parser know that the input \action1
means action1
, i.e. without the escape bar \
.
Finally, a list of type IEnumerable<ArgumentRaw>
.
Is the longest step, where the result of the mapping combines with the result of the simple parser. The goal is to obtain the best routes for the same input.
- The first step is to find the methods according to the input. For this will be used as references, all
ArgumentRaw
in the formatUnnamed
, i.e. arguments without names. The search will be inside the map returned by this methodGetMaps
. When a method is found, an instance of theSysCommand.Parsing.ActionParsed
type is created and each parameter of the method is represented by the classSysCommand.Parsing.ArgumentParsed
. - The first
action
may have your name omitted, but for that it must be of type default. See Standard methods. If they exist, they will only be used when the firstArgumentRaw
of the input is not aaction
. In this scenario all the standard methods will be chosen for the next step. From then on the process will be the same. - After finding all methods of each
action
of the input, will be made in division levels. Each level will be created as follows:
- If the input start with arguments and there is no default method, so the first level will be created with these arguments.
- If there is more than one
action
in input, including the standard methods, each will represent a whole new level. - The arguments that are not part of the map of the action (leftovers) formed another level. This level will be created after this action.
- If you cannot find any action in input submitted, then there will be only one level with the arguments that may or may not exist.
- If there is no input, but there is default methods without parameters, so they will be chosen for the execution.
- All levels that are not of
action
(arguments only) will be used to find theproperties
. When this happens, each property is represented by the classSysCommand.Parsing.ArgumentParsed
as well as the parameters of the methods.
Important note: When the flag bool enableMultiAction
is off the parser will accept only one action
.
Example:
namespace Example.Input.Parser
{
using SysCommand.ConsoleApp;
using System;
public class Program
{
public static int Main(string[] args)
{
return App.RunApplication();
}
}
public class Command1 : Command
{
public string Property1 { get; set; }
public void Main(string a, string b, string c)
{
}
public void Action1(string value = null)
{
}
public void Action2(string value = null)
{
}
}
public class Command2 : Command
{
public string Property2 { get; set; }
public void Action1(string value = null)
{
}
public void Action2(string value = null)
{
}
}
}
A. 2 levels with the first belonging to the default method ' Main (...) ':
MyApp.exe --a 1 --b 2 --c 3 action2
| L1 | L2 |
B. 2 levels with two actions:
MyApp.exe action1 action2
| L1 | L2 |
C. 3 levels, starting with 1 arguments:
MyApp.exe --property1 value action1 action2
| L1 | L2 | L3 |
D. 3 levels, starting with 2 arguments:
MyApp.exe --property1 value --property2 value2 action1 action2
| L1 | L2 | L3 |
E. 4 levels with leftover arguments in ' action2 ':
MyApp.exe --property1 value action1 action2 --property2 value2
| L1 | L2 | L3 | L4 |
In the example E
the --property2
argument was derived from the extra arguments to the action action2
. Note that this action does not had your --value
argument specified in input and the --property2
argument isn't part of your map, so this argument goes as extra and input to the next level of arguments. These extras can be anywhere after the action
name, after your name, in the middle or at the end.
With the Division of levels for "actions" completed, is chosen the best methods within each level.
- That choice works as follows:
- Select the methods that have all valid parameters
- Among the valid methods, selects the first method, respectively:
- Most of the parameters combined with the input that was sent
- The least amount of parameters in your map
- The least amount of extra arguments
- With the best method at hand for each level, the next step is to remove all methods of the same level which does not combine with the best method. That doesn't mean you have to have the same signature, that is, you don't have to have the same name or the same amount of parameters and not the same kind, none of it matters, what counts is the relationship of the input with the method.
The desired combination is that all other methods have the same amounts of evaluated parameters ( ArgumentParsed
) and that the inputs of its parameters ( IEnumerable<ArgumentRaw> AllRaw
) combine with the inputs of the best method, even with the same sequence. This means that the strategy of parse the input was the same for methods that matched, thus ensures that there will not be using the same input for different purposes.
Examples:
namespace Example.Input.Parser
{
using SysCommand.ConsoleApp;
using System;
using System.Collections.Generic;
public class Program
{
public static int Main(string[] args)
{
return App.RunApplication();
}
}
public class Command1 : Command
{
public void Action3(string value = null)
{
App.Console.Write("Action3(string value = null)");
}
}
public class Command2 : Command
{
public void Action3(int? value = null, string value2 = null)
{
App.Console.Write("Action3(int? value = null, string value2 = null)");
}
}
public class Command3 : Command
{
public void Action3(List<string> value = null)
{
App.Console.Write("Action3(List<string> value)");
}
}
}
Note: the values of the arguments of all scenarios are in positional format
MyApp.exe action3 123 456 action3 123 456 678 action3 999
Output Level1:
Action3(int? value = null, string value2 = null)
Output Level2:
Action3(List<string> value)
Output Level3:
Action3(string value = null)
Action3(int? value = null, string value2 = null)
Action3(List<string> value)
Explanation:
- Inputs (
ArgumentRaw
) "action3", "123", "456", "action3", "123", "456", "678", "action3", "999" - This input has 3 level:
- Level 1:
action3 123 456
Action3(int? value = null, string value2 = null)
: Best method everyone must have this modelArgumentParsed
1:AllRaw { "123" }
ArgumentParsed
2:AllRaw { "456" }
Action3(string value = null)
: Was not chosenArgumentParsed
1:AllRaw { "123" }
Action3(List<string> value)
: Was not chosenArgumentParsed
1:AllRaw { "123", "456" }
- Level 2:
action3 123 456 678
Action3(List<string> value)
: Best method everyone must have this modelArgumentParsed
1:AllRaw { "123", "456", "678" }
Action3(string value = null)
:: Was not chosenArgumentParsed
1:AllRaw { "123" }
Action3(int? value = null, string value2 = null)
: Was not chosenArgumentParsed
1:AllRaw { "123" }
ArgumentParsed
2:AllRaw { "456" }
- Level 3:
action3 999
Action3(string value = null)
: Best methodArgumentParsed
1:AllRaw { "999" }
Action3(int? value = null, string value2 = null)
: Expected sequence was combinedArgumentParsed
1:AllRaw { "999" }
Action3(List<string> value)
: Expected sequence was combinedArgumentParsed
1:AllRaw { "999" }
- Level 1:
All methods "not chosen" were discarded in the process. This rule is paramount so that more than one action
with the same signature, is called at the same level.
That choice works as follows:
- Finds the "property" of reference for each input (
ArgumentRaw
) at the same level. To do this, select the first valid property that has the first input, then the second valid property that has the second input and so on until all the inputs are completely combined. It is possible that only a reference property has more than one input, is the case of lists orEnums Flags
. These types of classes will have preference to be references, because they combine more than one input. This rule does not exist for the methods because the parameters of the best methods are references to the other. - After locating the references, the second step is to delete the other valid properties that do not match the references. Here is the same rule of the parameters of the methods, that is, to combine the properties should have the same inputs (
ArgumentRaw
) and with the same sequences. So ensures that there will not be using the same input for different purposes. - If any
ArgumentRaw
is not combined, so all the valid arguments will be eliminated.
Examples:
namespace Example.Input.Parser
{
using SysCommand.ConsoleApp;
using System;
using System.Collections.Generic;
public class Program
{
public static int Main(string[] args)
{
return App.RunApplication();
}
}
public class Command4 : Command
{
public string Prop1 { set { App.Console.Write("Command4.Prop1"); } }
public string Prop2 { set { App.Console.Write("Command4.Prop2"); } }
public string Prop3 { set { App.Console.Write("Command4.Prop3"); } }
public string Prop4 { set { App.Console.Write("Command4.Prop4"); } }
public Command4()
{
this.EnablePositionalArgs = true;
}
}
public class Command5 : Command
{
[Flags]
public enum MyEnum
{
A = 1, B = 2, C = 4 , D = 8
}
public MyEnum Prop1
{
set
{
App.Console.Write("Command5.Prop1");
}
}
public Command5()
{
this.EnablePositionalArgs = true;
}
}
public class Command6 : Command
{
[Flags]
public enum MyEnum
{
A = 1, B = 2, C = 4
}
public MyEnum Prop1
{
set
{
App.Console.Write("Command6.Prop1");
}
}
public Command6()
{
this.EnablePositionalArgs = true;
}
}
}
Note: the values of the arguments of all scenarios are in positional format
Scenario 1: "Properties" with priority, but that are discarded because they are invalid.
MyApp.exe W Z Y T
Output Level1:
Command4.Prop1
Command4.Prop2
Command4.Prop3
Command4.Prop4
Explanation:
- Inputs (
ArgumentRaw
): "W", "Z", "Y", "T" - This input has 1 level:
Command4.Prop1
: Property for the input "W"AllRaw { "W" }
Command4.Prop2
: Property for the input "Z"AllRaw { "Z" }
Command4.Prop3
: Property for the input "Y"AllRaw { "Y" }
Command4.Prop4
: Property for the input "T"AllRaw { "T" }
Command5.Prop1
: Property that has priority, but is invalid, the input "W" is not part of theEnum
.AllRaw { "W" }
Command6.Prop1
: The same situation ofCommand5.Prop1
AllRaw { "W" }
Scenario 2: property with more combinations will be the reference
MyApp.exe A B C D
Output Level1:
Command5.Prop1
Explanation:
- Inputs (
ArgumentRaw
): "A", "B", "C", "D" - This input has 1 level:
Command5.Prop1
: Property of all reference inputsAllRaw { "A", "B", "C", "D" }
Command6.Prop1
: Property has the first 3 inputs, but it needs to be 100% compatible with the reference.AllRaw { "A", "B", "C" }
Command4.Prop1
: Property is valid, but the inputA
already has the referenceCommand5.Prop1
that has priority for most.AllRaw { "A" }
Command4.Prop2
: The same situation ofCommand4.Prop1
AllRaw { "B" }
Command4.Prop3
: The same situation ofCommand4.Prop1
AllRaw { "C" }
Command4.Prop4
: The same situation ofCommand4.Prop1
AllRaw { "D" }
Scenario 3: property with more combinations will be the reference
MyApp.exe A B C W
Output Level1:
Command4.Prop4
Command5.Prop1
Command6.Prop1
Explanation:
- Inputs (
ArgumentRaw
): "A", "B", "C", "W" - This input has 1 level:
Command5.Prop1
: 3 first reference property inputsAllRaw { "A", "B", "C" }
Command6.Prop1
: Compatible with the referenceAllRaw { "A", "B", "C" }
Command4.Prop1
: Property is valid, but the inputA
already has the referenceCommand5.Prop1
that has priority for most.AllRaw { "A" }
Command4.Prop2
: The same situation ofCommand4.Prop1
AllRaw { "B" }
Command4.Prop3
: The same situation ofCommand4.Prop1
AllRaw { "C" }
Command4.Prop4
: Input reference Property "W" which is the 4 position, position this property accepts.AllRaw { "W" }
All the properties "not chosen" were discarded in the process. This rule is paramount so that more than one property is called at the same level.
Finally, an instance of the SysCommand.Parsing.ParseResult
type is returned containing:
Levels
: Contains all levels, where each level has aCommandParse
list. TheCommandParse
contains a list of members (methods or properties) that are valid or invalid.Args
: The list of inputs that gave start to parse.Maps
: A list of maps began to parse.EnableMultiAction
: The same input parameter that began to parse.
The execution occurs only if all the levels have at least one Command
valid.
A Command
is considered valid when he has at least one valid member (method or property) and there is no invalid members.
If this rule fails, the Execute()
method will indicate in ExecutionResult.State
which the type property of the error and all errors will be displayed in the property ExecutionResult.Errors
:
ExecutionState.NotFound
: When there is no valid or invalid in any level. Or when there are only properties and all are with in the StateNotMapped
.ExecutionState.HasError
: Indicates that there is one or more invalid members in any of the levels.
If everything is okay, the order of execution is as follows:
- Sets the
ExecutionScope
in allCommand
that are valid. This is important for the command to access the current execution scope. - Performs all properties, regardless of what level it is.
- For each
Command
valid: If the command has properties, then executes the methodMain()
If it is implemented. - Executes all methods of each level in order from lowest to highest (or left to right of the input).
Finally, an instance of the SysCommand.Execution
type is returned containing:
Results
: The results of each Member (methods or properties)Errors
: List of errors, if any.State
: Success, HasError NotFound,
The engine output was expanded to increase productivity.
First, it was created a small wrapper System.Console
class called SysCommand.ConsoleApp.ConsoleWrapper
that is available within the context of the application App.Console
property. This wrapper can be inherited and have their resources modified or enhanced, but by default have the following functionality:
- Write methods for each type of verb
- Possibility of customization of the color of the text of each verb
App.Console.ColorInfo
App.Console.ColorCritical
App.Console.ColorError
App.Console.ColorSuccess
App.Console.ColorWarning
App.Console.ColorRead
- Variable output type control
App.Console.ExitCode
where you can use it as yourint Main(string[] args)
method's return:- "0": success
- "1": error
- Smart line break while using dós write and read methods. The variable
App.Console.BreakLineInNextWrite
controls the breaks and helps you not leave empty lines.
Another feature would be the use of returns
syntax in the actions and that will be, if any, used as output. This feature resembles "AspNet MVC".
Example:
public class TestOutput : Command
{
public decimal Test()
{
var result = this.App.Console.Read("My question: ");
if (result != "S")
{
// option1: use write method in wrapper class
this.App.Console.Write(1.1m);
// option2: use .NET class directly
Console.WriteLine(2.2m);
}
// option3: or use return, its the same the option1
return 3.3m;
}
}
Input:
MyApp.exe test
Outputs:
My question: N
1.1
2.2
3.3
Finally, it is worth remembering that none of this prevents you from using the common mechanisms of .NET, as the class "System.Console".
Another option to display outputs is to use templates Razor
. This mechanism is designed for simple things, it's very important to say that it lacks several features such as: debug and error analysis.
To use Razor
should follow some simple steps:
- By organization, create a folder named "Views".
- If you want more organization, create a subfolder within the folder "Views" with the
Command
name. - Create a template file with the extension ".cshtml" inside the folder "Views". This file must have the same name as the action (method)
- Implement your template and can use the variable "@Model"
- Display the properties of the file ".cshtml" and configures it with the Build Action = Embedded Resource or with the Copy to Output = Copy aways. This is required for the template manager find the file in the "bin/" just in case the use of the Copy to Output or within the Assembly from the default application domain by using the Build Action.
Example:
public class ExampleRazorCommand : Command
{
public string MyAction()
{
return View<MyModel>();
}
public string MyAction2()
{
var model = new MyModel
{
Name = "MyName"
};
return View(model, "MyAction.cshtml");
}
public string MyAction3()
{
return ViewContent("My name: @Model.Name", new { Name = "John" });
}
public class MyModel
{
public string Name { get; set; }
}
}
@model ExampleRazorCommand.MyModel
@if (Model == null)
{
<text>#### HelloWorld {NONE} ####</text>
}
else {
<text>#### HelloWorld (@Model.Name) ####</text>
}
Input1:
MyApp.exe my-action
MyApp.exe my-action2
MyApp.exe my-action3
Outputs:
#### HelloWorld {NONE} ####
#### HelloWorld {MyName} ####
My name: John
- The research of template via
Arquivo físico
or viaEmbedded Resource
follows the same logic. He seeks for way more specific by using the name of "command. action." and if he doesn't find him will try to find by generic name, without the name of the command.- Search first by:
ExampleRazorCommand.MyAction.cshtml
- If you cannot find on the first try, then search for:
MyAction.cshtml
- Search first by:
- You can pass the name of the view directly, without the need to use the automatic search. as in the example of the action
MyAction2()
.
Another option to display outputs is to use templates T4
. This mechanism, unlike templates Razor
is more complete, he didn't lose any of the benefits that Visual Studio provides us. Just follow a few steps to use it:
- By organization, create a folder "Views"
- Create a file T4 in the format "Runtime Text Template"
- If you use the template you need to configure a parameter, which is mandatory, must have the name
Model
and have the respective type configured on your tagtype
. If you don't use no template then ignore this step. - Implement your template
Example:
public class ExampleT4Command : Command
{
public string T4MyAction()
{
return ViewT4<MyActionView>();
}
public string T4MyAction2()
{
var model = new MyModel
{
Name = "MyName"
};
return ViewT4<MyActionView, MyModel>(model);
}
public class MyModel
{
public string Name { get; set; }
}
}
<#@ parameter type="Example.T4Command.MyModel" name="Model" #>
<# if(Model == null) { #>
#### HelloWorld {NONE} ####
<# } #>
<# else { #>
#### HelloWorld (<#= Model.Name #>) ####
<# } #>
Input1:
MyApp.exe t4-my-action
Input2:
MyApp.exe t4-my-action2
Outputs:
#### HelloWorld {NONE} ####
#### HelloWorld {MyName} ####
The class SysCommand.ConsoleApp.View.TableView
brings the output tabelado
feature that can be very useful to present information quickly and more visually organized. Of course everything depends on the amount of information you want to display, the higher, the worse the view.
Example:
public class TableCommand : Command
{
public string MyTable()
{
var list = new List<MyModel>
{
new MyModel() {Id = "1", Column2 = "Line 1 Line 1"},
new MyModel() {Id = "2 " , Column2 = "Line 2 Line 2"},
new MyModel() {Id = "3", Column2 = "Line 3 Line 3"}
};
return ViewTable(list);
}
public string MyTableCustomized()
{
var list = new List<MyModel>
{
new MyModel() {Id = "1", Column2 = "Line 1 Line 1"},
new MyModel() {Id = "2 " , Column2 = "Line 2 Line 2"},
new MyModel() {Id = "3", Column2 = "Line 3 Line 3"}
};
return TableView.ToTableView(list)
.Build()
.ToString();
}
public class MyModel
{
public string Id { get; set; }
public string Column2 { get; set; }
}
}
Input1:
MyApp.exe my-table
Outputs:
Id | Column2
--------------------
1 | Line 1 Line 1
--------------------
2 | Line 2 Line 2
--------------------
3 | Line 3 Line 3
--------------------
Working with properties is very simple and objective, simply create their properties as public and choose one of the two ways below to find out if a property was inputada by the user, you choose which to use:
First, you can use the method Main()
with no parameter, name Convention, will be in charge of be invoked if any of your property has been used in input from the user. The name "Main" was chosen to keep the naming pattern that the .NET uses in the console applications.
For safety, use all the primitive types such as Nullable
to ensure that the user did input. Or use the method GetArgument(string name)
to check whether a property has been analyzed. It is worth mentioning that a property with default value will always have a result after the parse and if necessary, use a check to see if the result came from user input.
Example:
public string Main()
{
if (this.MyProperty != null)
App.Console.Write("Has MyProperty");
if (this.MyPropertyInt != null)
App.Console.Write("Safe mode: MyPropertyInt");
if (this.MyPropertyUnsafeMode == 0)
App.Console.Write("Unsafe mode: Preferably, use nullable in MyPropertyUnsafeMode");
if (this.GetArgument("MyPropertyUnsafeMode") != null)
App.Console.Write("Safe mode, but use string: MyPropertyUnsafeMode");
if (this.GetArgument(nameof(MyPropertyUnsafeMode)) != null)
App.Console.Write("Safe mode, but only in c# 6: MyPropertyUnsafeMode");
if (this.GetArgument(nameof(MyPropertyDefaultValue)) != null)
App.Console.Write("MyPropertyDefaultValue aways has value");
// if necessary, add this verification to know if property had input.
if (this.GetArgument(nameof(MyPropertyDefaultValue)).IsMapped)
App.Console.Write("MyPropertyDefaultValue has input");
return "Main() methods can also return values ;)";
}
C:\MyApp.exe --my-property value
Has MyProperty
Unsafe mode: Preferably, use nullable in MyPropertyUnsafeMode
MyPropertyDefaultValue aways has value
Main() methods can also return values ;)
C:\MyApp.exe --my-property-int 0
Safe mode: MyPropertyInt
Unsafe mode: Preferably, use nullable in MyPropertyUnsafeMode
MyPropertyDefaultValue aways has value
Main() methods can also return values ;)
C:\MyApp.exe --my-property-unsafe-mode 0
Unsafe mode: Preferably, use nullable in MyPropertyUnsafeMode
Safe mode, but use string: MyPropertyUnsafeMode
Safe mode, but only in c# 6: MyPropertyUnsafeMode
MyPropertyDefaultValue aways has value
Main() methods can also return values ;)
C:\MyApp.exe --my-property-default-value 0
Unsafe mode: Preferably, use nullable in MyPropertyUnsafeMode
MyPropertyDefaultValue aways has value
MyPropertyDefaultValue has input
Main() methods can also return values ;)
Be very careful with properties that have default values, the fact that she have default value causes the method to Main()
always be called even when there is no input.
C:\MyApp.exe
Unsafe mode: Preferably, use nullable in MyPropertyUnsafeMode
MyPropertyDefaultValue aways has value
Main() methods can also return values ;)
Finally, you can still use the set { .. }
of your property to take some action. This feature is not recommended, because the method GetArgument(string name)
is not yet ready to be used right now, but if you want something quick, punctual and nothing prevents you from using this medium.
public class TestProperty2Command : Command
{
public bool MyCustomVerbose
{
set
{
if (value)
Console.WriteLine("MyCustomVerbose=true");
else
App.Console.Write("MyCustomVerbose=false");
}
}
}
C:\MyApp.exe --my-custom-verbose
MyCustomVerbose=true
C:\MyApp.exe --my-custom-verbose false
MyCustomVerbose=false
The following rule describes how the default behavior for a naming property become a argument
:
First converts the property name in lowercase, then add a dash "-" before each letter upper case that are in the middle or at the end of the name. In the case of properties with only one letter, the default is to leave the tiny font and input will be accepted only in short form.
This is the default rule of nomenclarutura and you can choose to use it or customized it for that use the ArgumentAttribute
attribute. The ArgumentAttribute
attribute usage is exclusive, to use it you are eliminating the naming pattern, that is, if you customize the short form you will be required to customize the long form also, and vice versa. Otherwise only the custom format is enabled.
Example:
public class CustomPropertiesNamesCommand : Command
{
// customized with long and short option
[Argument(LongName = "prop", ShortName = 'A')]
public int? MyCustomPropertyName { get; set; }
// only with long option
public string NormalLong { get; set; }
// customized only with short option
[Argument(ShortName = 'B')]
public string ForcedShort { get; set; }
// only with short option
public int? C { get; set; }
public CustomPropertiesNamesCommand()
{
}
public void Main()
{
if (MyCustomPropertyName != null)
App.Console.Write("MyCustomPropertyName=" + MyCustomPropertyName);
if (NormalLong != null)
App.Console.Write("NormalLong=" + NormalLong);
if (ForcedShort != null)
App.Console.Write("ForcedShort=" + ForcedShort);
if (C != null)
App.Console.Write("C=" + C);
}
}
C:\MyApp.exe --prop 111 --normal-long strvalue -B strvalue2 -c 9999
MyCustomPropertyName=111
NormalLong=strvalue
ForcedShort=strvalue2
C=9999
C:\MyApp.exe -A 111 --normal-long strvalue -B strvalue2 -c 9999
MyCustomPropertyName=111
NormalLong=strvalue
ForcedShort=strvalue2
C=9999
To configure the text of help use the ArgumentAttribute(Help="my help")
attribute. If you do not notify this attribute, your argument will still be displayed in the help, but no help information.
However, still appears a text of add-on for each argument, this text tells you whether the argument is mandatory or optional (with or without a default value). This text is displayed by default, but you can disable it with the ArgumentAttribute(ShowHelpComplement=false)
attribute.
public class CustomPropertiesHelpCommand : Command
{
// customized with long and short option
[Argument(Help = "This is my property")]
public int? MyPropertyHelp { get; set; }
[Argument(Help = "This is my property 2", ShowHelpComplement = false)]
public int? MyPropertyHelp2 { get; set; }
public CustomPropertiesHelpCommand()
{
this.HelpText = "Custom help for CustomPropertiesHelpCommand";
}
}
C:\MyApp.exe help
Custom help for CustomPropertiesHelpCommand
--my-property-help This is my property. Is optional.
--my-property-help2 This is my property 2
For more information about the help see in the topic Help.
For arguments that are required, you must use the ArgumentAtrribute
calling the flag IsRequired
.
Example:
public class TestProperty5Command : Command
{
[Argument(IsRequired = true)]
public string MyPropertyRequired { get; set; }
public void Main()
{
if (MyPropertyRequired != null)
App.Console.Write("MyPropertyRequired=" + MyPropertyRequired);
}
}
C:\MyApp.exe
There are errors in command: TestProperty5Command
The argument '--my-property-required' is required
C:\MyApp.exe --my-property-required 123
MyPropertyRequired=123
To enable the input just connect the positional flag EnablePositionalArgs
in your Command
, however it is important to validate how much it needed as many positional inputs can complicate too much the use of your application. Despite SysCommand
being well prepared for this type of input, we don't want you pollute the your input.
public class TestProperty3Command : Command
{
public int? MyPosicionalProperty1 { get; set; }
public int? MyPosicionalProperty2 { get; set; }
public TestProperty3Command()
{
this.EnablePositionalArgs = true;
}
public void Main()
{
if (MyPosicionalProperty1 != null)
App.Console.Write("MyPosicionalProperty1=" + MyPosicionalProperty1);
if (MyPosicionalProperty2 != null)
App.Console.Write("MyPosicionalProperty2=" + MyPosicionalProperty2);
}
}
C:\MyApp.exe --my-posicional-property1 1 --my-posicional-property2 2
MyPosicionalProperty1=1
MyPosicionalProperty2=2
C:\MyApp.exe 1 2
MyPosicionalProperty1=1
MyPosicionalProperty2=2
C:\MyApp.exe 1 --my-posicional-property2 2
MyPosicionalProperty1=1
MyPosicionalProperty2=2
You can also control the position of each property within the input using Position
configuration.
Example:
public class TestProperty3Command : Command
{
[Argument(Position = 2)]
public int? MyPosicionalProperty1 { get; set; }
[Argument(Position = 1)]
public int? MyPosicionalProperty2 { get; set; }
public TestProperty3Command()
{
this.EnablePositionalArgs = true;
}
public void Main()
{
if (MyPosicionalProperty1 != null)
App.Console.Write("MyPosicionalProperty1=" + MyPosicionalProperty1);
if (MyPosicionalProperty2 != null)
App.Console.Write("MyPosicionalProperty2=" + MyPosicionalProperty2);
}
}
C:\MyApp.exe --my-posicional-property1 1 --my-posicional-property2 2
MyPosicionalProperty1=1
MyPosicionalProperty2=2
C:\MyApp.exe 1 2
MyPosicionalProperty1=2
MyPosicionalProperty2=1
To change the default behavior of public properties, you need to just turn off the flag OnlyPropertiesWithAttribute
of Command
. With her off the parser will no longer look to the public properties and uses only the public properties and that have the ArgumentAtrribute
attribute.
Example:
public class TestProperty4Command : Command
{
public int? MyPropertyWithoutAttribute { get; set; }
[Argument]
public int? MyPropertyWithAttribute { get; set; }
public TestProperty4Command()
{
this.OnlyPropertiesWithAttribute = true;
}
public void Main()
{
if (MyPropertyWithAttribute != null)
App.Console.Write("MyPropertyWithAttribute=" + MyPropertyWithAttribute);
}
}
C:\MyApp.exe --my-property-with-attribute 1
MyPropertyWithAttribute=1
C:\MyApp.exe --my-property-without-attribute 1
There are errors in command: DoSomethingCommand
The argument '--my-property-without-attribute' does not exist
Working with methods is also very simple, all methods defined as public
, by default, are enabled to become actions
and be available for use. The interesting fact is that you can use the native resources making your .NET code cleaner, such as:
- Parameterless methods
- Methods with optional parameters with default value
- Methods with overloads
- Methods with the syntax
return
, by default, will be used as output in the console using
Example:
public class Method1Command : Command
{
public string MyAction()
{
return "MyAction";
}
}
C:\MyApp.exe my-action
MyAction
The optional parameters are useful to avoid creating overloads and in the case of a console application helps you actions
with several options, but not forcing the user to fill in all.
For safety, when using optional parameters, get to use all the primitive types such as Nullable
to ensure that the user has input. Or use the method GetAction()
to verify that the parameter has been mapped, i.e. If there was some sort of input.
Example:
public class Method1Command : Command
{
public string MyAction2(int? arg0 = null, int arg1 = 0)
{
// unsafe, because the user can enter with value "--arg1 0" and you never know.
if (arg1 != 0)
App.Console.Write("arg1 wrong way to do it!");
// safe, but bureaucratic
if (this.GetAction().Arguments.Any(f => f.Name == "arg1" && f.IsMapped))
App.Console.Write("arg1 has input");
// recommended. the best way!
if (arg0 != null)
App.Console.Write("arg0 has input");
return "MyAction2";
}
}
C:\MyApp.exe my-action2
MyAction2
C:\MyApp.exe my-action2 --arg0 99
arg0 has input
MyAction2
C:\MyApp.exe my-action2 --arg1 0
arg1 has input
MyAction2
Note: do not use the method GetAction()
in methods that are not actions
, you will get an exception.
The overload feature methods is supported in the same way as you would for any other purpose. Often this feature might be more interesting to use optional parameters, the code is cleaner. Other times it will not be possible, because with optional parameters the user has the option of selecting any parameter regardless of your position in the method, and you can't overload.
Example:
public class Method1Command : Command
{
public string MyAction3()
{
return "MyAction3";
}
public string MyAction3(int arg0)
{
return "arg0 has input";
}
public void MyAction3(int arg0, int arg1)
{
App.Console.Write("arg0 has input");
App.Console.Write("arg1 has input");
}
}
C:\MyApp.exe my-action3
MyAction3
C:\MyApp.exe my-action3 --arg0 9
arg0 has input
C:\MyApp.exe my-action3 --arg0 9 --arg1 99
arg0 has input
arg1 has input
C:\MyApp.exe my-action3 --arg1 99
There are errors in command: Method1Command
The argument '--arg1' does not exist
The last command showed the limitation of overload with regard to optional parameters. The parser understood that both methods with MyAction3
parameters are invalid, see:
MyAction3(int arg0)
: Does not have the input--arg1
that was requested, so this invalid.MyAction3(int arg0, int arg1)
: Has the input--arg1
, but does not have the input--arg0
, so this invalid.
In this case the parser had chosen the only valid method, i.e. the method MyAction3
without parameters and will use the extra argument --arg1
to try to find him as property on some Command
, however this property does not exist in any place, generating the error.
Another way to get your action in the console is using positional input. By default, all action
accept positional arguments, but it can be disabled using the ActionAttribute(EnablePositionalArgs = false)
attribute.
Example:
public string MyActionWithPosicional(int arg0, int arg1)
{
return "MyActionWithPosicional";
}
[Action(EnablePositionalArgs = false)]
public string MyActionWithoutPosicional(int arg0, int arg1)
{
return "MyActionWithoutPosicional";
}
C:\MyApp.exe my-action-with-posicional --arg0 1 --arg1 2
MyActionWithPosicional
C:\MyApp.exe my-action-with-posicional 1 2
MyActionWithPosicional
C:\MyApp.exe my-action-without-posicional --arg0 1 --arg1 2
MyActionWithoutPosicional
C:\MyApp.exe my-action-without-posicional 1 2
There are errors in command: Method1Command
Error in method: my-action-without-posicional(Int32, Int32)
The argument '--arg0' is required
The argument '--arg1' is required
To change the default behavior of public methods, you need to just turn off the flag OnlyMethodsWithAttribute
of Command
. With her off the parser will no longer look for the public methods and uses only the public methods and that have the ActionAtrribute
attribute.
Example:
public class Method2Command : Command
{
public Method2Command()
{
this.OnlyMethodsWithAttribute = true;
}
[Action]
public string MyActionWithAttribute()
{
return "MyActionWithAttribute";
}
public string MyActionWithoutAttribute()
{
return "MyActionWithAttribute";
}
}
C:\MyApp.exe my-action-with-attribute
MyActionWithAttribute
C:\MyApp.exe my-action-without-attribute
Could not find any action.
Another way to ignore public methods and without changing the default behavior of OnlyMethodsWithAttribute
the property Command
is by using the ActionAttribute(Ignore = true)
attribute.
Example:
public class Method3Command : Command
{
public string MyActionNotIgnored()
{
return "MyActionNotIgnored";
}
[Action(Ignore = true)]
public string MyActionIgnored()
{
return "MyActionIgnored";
}
}
C:\MyApp.exe my-action-not-ignored
MyActionNotIgnored
C:\MyApp.exe my-action-ignored
Could not find any action.
The following rule describes how the standard process of transformation of a name of a method in action and also the names of its parameters in arguments.
First converts the member name (methods or parameters) in small, then add a dash "-" before each letter upper case that are in the middle or at the end of the name. In the case of parameters with only one letter, the default is to leave the tiny font and input will be accepted only in short form.
This is the default rule of nomenclarutura and you can choose to use it or customized it so in whole or in part. For this use the attributes ActionAttribute
for methods and ArgumentAttribute
parameters. The ArgumentAttribute
attribute usage is exclusive, to use it you are eliminating the naming pattern, that is, if you customize the short form you will be required to customize the long form also, and vice versa. Otherwise only the custom format is enabled.
Example:
public class Method3Command : Command
{
[Action(Name = "ActionNewName")]
public string MyAction(
[Argument(LongName = "longName1", ShortName = 'a')]
string arg0, // customized with long and short option
string arg1, // only with long option
[Argument(ShortName = 'z')]
string arg2, // only with short option
string z // only with short option
)
{
return "ActionNewName";
}
}
C:\MyApp.exe ActionNewName --longName1 1 --arg1 2 -z 3 -w 4
ActionNewName
C:\MyApp.exe ActionNewName -a 1 --arg1 2 -z 3 -w 4
ActionNewName
Another customisation option is adding prefix before the name of each action
. This can be done in two ways, first you only need to call the Command.UsePrefixInAllMethods
command flag. With this flag turned on, all actions
will have the following name "CommandName-ActionName", i.e. they will contain the name of the Command
added a "-" followed by the name of action. If the command name has the suffix "Command" then this suffix will be removed.
You can still want this flag does not affect all his actions
, for that use the flag ActionAttribute(UsePrefix=false)
for a given action does not have your name changed with the prefix.
public class PrefixedCommand : Command
{
public PrefixedCommand()
{
this.UsePrefixInAllMethods = true;
}
public string MyAction()
{
return "prefixed-my-action";
}
[Action(Name = "my-action2-custom")]
public string MyAction2()
{
return "prefixed-my-action2-custom";
}
[Action(UsePrefix = false)]
public string MyActionWithoutPrefix()
{
return "my-action-without-prefix";
}
}
C:\MyApp.exe prefixed-my-action
prefixed-my-action
C:\MyApp.exe prefixed-my-action2-custom
prefixed-my-action2-custom
C:\MyApp.exe my-action-without-prefix
my-action-without-prefix
The second way is you specify which is the prefix of each action
using the command property Command.PrefixMethods
. So the prefix will not be processed using the command name and Yes you specify. It is worth mentioning that the flag Command.UsePrefixInAllMethods
still needs to be linked.
Example:
public class Prefixed2Command : Command
{
public Prefixed2Command()
{
this.PrefixMethods = "custom-prefix";
this.UsePrefixInAllMethods = true;
}
public string MyAction()
{
return "custom-prefix-my-action";
}
}
C:\MyApp.exe custom-prefix-my-action
custom-prefix-my-action
To configure the text of help use the ActionAttribute(Help="my help")
attribute. If you do not notify this attribute, your action will still be displayed in the help, but no help information.
For each parameter using the same attribute ArgumentAttribute(Help="")
Properties. The behavior is exactly the same. See Customizing the help information.
Example:
public class MethodHelpCommand : Command
{
public MethodHelpCommand()
{
this.HelpText = "Help for this command";
}
[Action(Help = "Action help")]
public string MyActionHelp(
[Argument(Help = "Argument help")]
string arg0, // With complement
[Argument(Help = "Argument help", ShowHelpComplement = false)]
string arg1 // Without complement
)
{
return "Action help";
}
}
C:\MyApp.exe help
Help for this command
my-action-help Action help
--arg0 Argument help. Is required.
--arg1 Argument help
For more information about the help see in the topic Help.
The property ArgumentAttribute(Position=X)
also works for parameters in the same way it works for properties. It's not a feature that makes a lot of sense, but it's important to document it.
Example:
public class Method5Command : Command
{
public string MyActionWithArgsInverted(
[Argument(Position = 2)]
string arg0,
[Argument(Position = 1)]
string arg1
)
{
return "MyActionWithArgsInverted";
}
}
C:\MyApp.exe my-action-with-args-inverted 1 2
arg0 = '2'; arg1 = '1'
The following properties do not make sense in the setting of parameters and methods exist only for that ArgumentAtrribute
attribute is shared in the use of properties.
- IsRequired: in C #, every parameter that has no default value is required, this setting is ignored if used.
- DefaultValue: How the own C # already gives us the option to default value on parameters, this setting is redundant, so it is ignored by the standard .NET is enough and cleaner.
The use of standard methods (or implicitos methods) make the feature is very similar to the use of properties, i.e., you are not required to specify the action
name and its parameters can be entered directly in the input as if they were arguments from properties.
By Convention, if you call your action
"Main" and she has parameters, it is considered as standard. To change this behavior you must turn off the flag Action(IsDefault = false)
, so the default behavior will be changed and your action "Main" (with parameters) will no longer be accessed so implied and will require the specification of your name in input. The reverse is also true, if your action has a different name and you would like to make it a standard method then just turn on the flag Action(IsDefault = true)
.
Example:
public class MethodDefaultCommand : Command
{
public string Main(string arg0)
{
return "Main(string arg0)";
}
public string Main(string arg0, string arg1)
{
return "Main(string arg0, string arg1)";
}
[Action(IsDefault = false)]
public string Main(int argument)
{
return "Main(int argument)";
}
[Action(IsDefault = true)]
public string AnyName(string argument)
{
return "AnyName(string argument)";
}
[Action(IsDefault = true)]
public string ActionWhenNotExistsInput()
{
return "ActionWhenNotExistsInput()";
}
}
C:\MyApp.exe --arg0 value
Main(string arg0)
C:\MyApp.exe --arg0 value --arg1 value1
Main(string arg0, string arg1)
C:\MyApp.exe main --argument 9999
Main(int argument)
C:\MyApp.exe --argument value
AnyName(string argument)
C:\MyApp.exe
ActionWhenNotExistsInput()
Comments:
- It is important to note that all standard methods can still be called explicit way, that is, with your name being specific.
- The use of default method without arguments only works if there is no argument required, otherwise this method will never be called because there will be an error requiring the use of the argument. '
The format of the help takes into account all the elements that compose the system, i.e. Commands
, Arguments
and Actions
. It is generated automatically using the help texts of each of these elements, so it's important to keep those information filled in and updated, this will help you and who is using your application.
In the standard format, there are two ways to display the help: the complete help and the help for action:
Displays the help for an action specifies:
MyApp.exe help my-action-name
Displays the help:
MyApp.exe help
To help complete the output format that is displayed will be the following:
usage: [--argument=<phrase>] [--argument-number=<number>]
[-v, --verbose=<None|All|Info|Success|Critical|Warning|Error|Quiet>]
--required-argument=<phase>
<actions[args]> (A)
Command help (B)
LongName (C1), ShortName (C2) Help text for arguments of command (properties) (C3). Complement (C4)
Action (D)
LongName (E1), ShortName (E2) Help text for arguments of actions (parameters) (E3). Complement (E4)
Use 'help --action=<name>' to view the details of
any action. Every action with the symbol "*" can
have his name omitted. (F)
The source of each text in each element Commands
, Arguments
and Actions
complementary texts and are on static class SysCommand.ConsoleApp.Strings
. Follows the mapping from each text as the format shown above:
- A. the text
usage
is generated internally by the classDefaultDescriptor
and will always be displayed. - B. the
Command
text always appears and your source comes from theCommand.HelpText
property that must be set in the constructor of your command. If you do not assign any value to this property, the default is to display the command name. - C. will be displayed all the arguments (properties) of the command, one under the other.
- C1. The source of this text comes from the
ArgumentAtrribute(LongName="")
attribute. - C2. The source of this text comes from the
ArgumentAtrribute(ShortName="")
attribute. - C3. The source of this text comes from the
ArgumentAtrribute(Help="")
attribute. - C4. This text will only appear if the flag
ArgumentAtrribute(ShowHelpComplement=true)
is turned on. The text that will be displayed will depend on the configuration of the Member:Strings.HelpArgDescRequired
: When the Member is requiredStrings.HelpArgDescOptionalWithDefaultValue
: When the Member is optional and has a default value.Strings.HelpArgDescOptionalWithoutDefaultValue
: When the Member is optional and has no default value.
- C1. The source of this text comes from the
- D. the source of this text comes from the
ActionAtrribute(Name="")
attribute. - E. Are the same sources of command arguments (properties), because both use the same attribute.
- F. supplementary Text to explain how the help works. The source of this text comes from the
Strings.HelpFooterDesc
class.
Example:
public class HelpCommand : Command
{
// With complement
[Argument(Help = "My property1 help")]
public string MyProperty1 { get; set; }
// Without complement
[Argument(Help = "My property2 help", ShowHelpComplement = false, IsRequired = true)]
public int MyProperty2 { get; set; }
public HelpCommand()
{
this.HelpText = "Help for this command";
}
[Action(Help = "Action help")]
public void MyActionHelp
(
[Argument(Help = "Argument help")]
string arg0, // With complement
[Argument(Help = "Argument help", ShowHelpComplement = false)]
string arg1 // Without complement
)
{
}
}
usage: --my-property2=<number> [--my-property1=<phrase>] [-v,
--verbose=<None|All|Info|Success|Critical|Warning|Error|Quiet>]
<actions[args]>
Help for this command
--my-property1 My property1 help. Is optional.
--my-property2 My property2 help
my-action-help Action help
--arg0 Argument help. Is required.
--arg1 Argument help
The help
feature is a built-in command SysCommand.ConsoleApp.Commands.HelpCommand.cs
that sets the two actions
of help that were presented in the previous topic. By definition, all help command needs to inherit from SysCommand.ConsoleApp.Commands.IHelpCommand
interface, so the system understands that this command will do that role. Compulsorily, there will always be a help command, if the user does not customize, the default command HelpCommand
is used.
Below, an example of a completely customized help:
using SysCommand.ConsoleApp;
public class Program
{
public static int Main()
{
return App.RunApplication();
}
public class CustomHelp : Command, SysCommand.ConsoleApp.Commands.IHelpCommand
{
public string MyCustomHelp(string action = null)
{
foreach(var map in this.App.Maps)
{
}
return "Custom help";
}
}
}
MyApp.exe my-custom-help
Custom help
MyApp.exe help
Could not find any action.
Another option is to create one Descriptor
that inherits from the SysCommand.ConsoleApp.Descriptor.IDescriptor
interface and set it on your App.Descriptor
property. This is possible because the default help uses the methods of help contained within this instance. This option is not recommended if you want to just customize help
.
A safer option would be to create an Descriptor
inheriting SysCommand.ConsoleApp.Descriptor.DefaultDescriptor
class and sobrescrer only methods of help.
using SysCommand.ConsoleApp;
public class Program
{
public static int Main()
{
return App.RunApplication(
() => {
var app = new App();
app.Descriptor = new CustomDescriptor();
// OR
app.Descriptor = new CustomDescriptor2();
return app;
}
);
}
public class CustomDescriptor : IDescriptor { ... }
public class CustomDescriptor2 : DefaultDescriptor
{
public override string GetHelpText(IEnumerable<CommandMap> commandMaps) { ... }
public override string GetHelpText(IEnumerable<CommandMap> commandMaps, string actionName) { ... }
}
}
Comments:
- The help command is the only one that cannot be ignored by initialization, if it does not exist in the list, the
SysCommand.ConsoleApp.Commands.HelpCommand.cs
command will be added internally. - For more information about customization of help on properties see the topic of Customizing the help information.
- For more information about customization of help on actions see the topic of Customizing the help information on the actions and its parameters.
The view control by verb this contained in a built-in command called SysCommand.ConsoleApp.Commands.VerboseCommand
. To your function is to change the value of the App.Console.Verbose
property if the user send a verbose input. Currently, the supported verbs are:
All
: Displays all verbsInfo
: Is the default verb, will always be displayed, unless the user send the verbQuiet
.Success
: Verb to success messages. Will only be displayed if the user requests.Critical
: Verb to critical messages. Will only be displayed if the user requests.Warning
: Verb for warning messages. Will only be displayed if the user requests.Error
: Verb to error messages. The system forces the sending of this form of in case of parse errors. Will only be displayed if the user requests.Quiet
: Verb to display no message, but if the message is being forced, this verb is ignored for this message.
For that functionality to work correctly is mandatory the use of output functions contained within the class SysCommand.ConsoleApp.ConsoleWrapper
and you have an instance available on App.Console
property.
Example:
public class TestVerbose : Command
{
public void Test()
{
this.App.Console.Write("output of info");
this.App.Console.Error("output of error");
this.App.Console.Error("output of error forced", forceWrite: true);
this.App.Console.Critical("output of critical");
}
}
Short form:
MyApp.exe test -v Critical
Long form:
MyApp.exe test --verbose Critical
Outputs:
output of info
output of error forced
output of critical
It is important to say that you can turn off this feature and implement your own mechanism for verbose. For that you need to disable the command VerboseCommand
and create your own set of functions for each verb.
- To disable the command
VerboseCommand
use the exclusive form of command specification. See the topic Specifying the types of commands.
Error handling is generated automatically by the system and are categorized as follows:
- Errors in parse process: Are errors that occur in the parse and process are sub categorised as follows:
ArgumentParsedState.ArgumentAlreadyBeenSet
: Indicates that an argument this duplicate on the same input.ArgumentParsedState.ArgumentNotExistsByName
: Indicates that a named argument does not exist.ArgumentParsedState.ArgumentNotExistsByValue
: Indicates that a positional argument does not existArgumentParsedState.ArgumentIsRequired
: Indicates that an argument is requiredArgumentParsedState.ArgumentHasInvalidInput
: Indicates that an argument that is not validArgumentParsedState.ArgumentHasUnsupportedType
: Indicates that it's all right with the input, but the type of the argument is not supported. See the list of supported types in Supported types.
- Not Found: no route found to the input requested.
- Exception génerica: there is no standard treatment, but it is possible to intercept any exception inside the event
App.OnException
.
Responsible for format and print errors is the default handler SysCommand.ConsoleApp.Handlers.DefaultApplicationHandler
. It intercepts the execution and if you have mistakes calls one of the ShowErrors(ApplicationResult appResult)
method, ShowNotFound(ApplicationResult appResult)
and that are in the class SysCommand.ConsoleApp.Descriptor.DefaultDescriptor
.
If you want to customize the error messages, you can change the handler DefaultApplicationHandler
completely (not recommended) or create a class that inherits from DefaultDescriptor
overriding only the methods of errors.
Example:
using SysCommand.ConsoleApp;
public class Program
{
public static int Main()
{
return App.RunApplication(
() => {
var app = new App();
app.Descriptor = new CustomDescriptor();
app.OnException += (appResult, exception) =>
{
app.Console.ExitCode = ExitCodeConstants.Error;
app.Console.Write(exception.Message);
};
return app;
}
);
}
public class CustomDescriptor : DefaultDescriptor
{
public override void ShowErrors(ApplicationResult appResult)
{
foreach (ExecutionError error in appResult.ExecutionResult.Errors)
{
foreach (ArgumentParsed prop in error.PropertiesInvalid)
{
if (prop.ParsingStates.HasFlag(ArgumentParsedState.ArgumentAlreadyBeenSet))
appResult.App.Console.Error(string.Format("The argument '{0}' has already been set", prop.GetArgumentNameInputted()));
if (prop.ParsingStates.HasFlag(ArgumentParsedState.ArgumentNotExistsByName))
appResult.App.Console.Error(string.Format("The argument '{0}' does not exist", prop.GetArgumentNameInputted()));
if (prop.ParsingStates.HasFlag(ArgumentParsedState.ArgumentNotExistsByValue))
appResult.App.Console.Error(string.Format("Could not find an argument to the specified value: {0}", prop.Raw));
if (prop.ParsingStates.HasFlag(ArgumentParsedState.ArgumentIsRequired))
appResult.App.Console.Error(string.Format("The argument '{0}' is required", prop.GetArgumentNameInputted()));
if (prop.ParsingStates.HasFlag(ArgumentParsedState.ArgumentHasInvalidInput))
appResult.App.Console.Error(string.Format("The argument '{0}' is invalid", prop.GetArgumentNameInputted()));
if (prop.ParsingStates.HasFlag(ArgumentParsedState.ArgumentHasUnsupportedType))
appResult.App.Console.Error(string.Format("The argument '{0}' is unsupported", prop.GetArgumentNameInputted()));
}
foreach (ActionParsed method in error.MethodsInvalid)
{
foreach (ArgumentParsed parameter in method.Arguments)
{
...
}
}
}
}
public override void ShowNotFound(ApplicationResult appResult)
{
appResult.App.Console.Error("Could not find any action.", forceWrite: true);
}
}
}
The property App.Items
is responsible for maintaining an isolated scope of variables for each instance of the class App
. In practice it is a collection of objects (key/value) that can assist in passing values between the commands.
This collection inherits from Dictionary<object, object>
and has been extended with the addition of some methods of help:
T Get<T>()
: Returns the first element of theT
type, if you can't find so returnsnull
to complex types andException
forstruct
.T Get<T>(object key)
: Returns the first element of informed key return behavior is the same as the above method.T GetOrCreate<T>()
: If exists, returns the first element of typeT
or creates a new instance via reflection where typeT
is the key.T GetOrCreate<T>(object key)
: If exists, returns the first element of informed key or creates a new instance via reflection.
Note: For the creation of new instances via reflection is required that the class has a constructor with no parameters.
Example:
namespace Example.ContextVariable
{
using SysCommand.ConsoleApp;
public class Program
{
public static int Main(string[] args)
{
return App.RunApplication(delegate()
{
var app = new App();
app.Items["variable1"] = 1;
return app;
});
}
}
public class Command1 : Command
{
public void Action()
{
this.App.Console.Write(App.Items["variable1"]);
App.Items["variable1"] = (int)App.Items["variable1"] + 1;
}
public void Action2()
{
this.App.Console.Write(App.Items["variable1"]);
}
}
}
MyApp.exe action action2
1
2
Note that the variable variable1
has been assigned in the creation of the App
context and was incremented when spent in action action2
.
This feature is very useful to persist information in file Json
format. He uses the dependence NewtonSoft.Json
framework to do all the work of serialization and deserialização.
The class SysCommand.ConsoleApp.Files.JsonFileManager
is responsible for the management of standard control objects in the form of files. It contains some features that will help you save time if you need to persist objects. The format will always be Json
.
Properties:
DefaultFolder
: Name of the default folder. The default is.app
.bool SaveInRootFolderWhenIsDebug
: Determines if the default folder is created at the root of the project when this in debug mode inside Visual Studio. This helps to show the files generated usingShow all files
the optionSolution Explorer
.string DefaultFilePrefix
: Adds a prefix on all files. The default isnull
.string DefaultFileExtension
: Specifies the extension of the files. The default is.json
.bool UseTypeFullName
: Determines if the formatting of the typesT
will include the full name of the type. The default isfalse
.
Methods:
Save<T>(T obj)
: Saves an object in the default folder where file name is theT
type name formatted, with exception to classes that have the attributeObjectFile
.Save(object obj, string fileName)
: Saves an object in the default folder with a specific name.Remove<T>()
: Removes an object in the default folder where file name is theT
type name formatted, with exception to classes that have the attributeObjectFile
.Remove(string fileName)
: Removes an object in the default folder with a specific name.T Get<T>(string fileName = null, bool refresh = false)
: Returns a default folder object.fileName
: Indicates the file name, ifnull
the name of the typeT
is used in the search, with exception to classes that have the attributeObjectFile
.refresh
: Iffalse
, will seek in the internal cache if you have already been loaded previously. Otherwise be forced file loading.
T GetOrCreate<T>(string fileName = null, bool refresh = false)
: Same behavior of the method above, but creates a new instance when you can't find the file in the default folder. It is important to say that the file will not be created, only the instance of the typeT
. To save physically it is necessary to use the methodSave
.string GetObjectFileName(Type type)
: Returns the formatted type name or if you are using theObjectFile
attribute, returns the value of theFileName
property.string GetFilePath(string fileName)
: Returns the file path into the default folder.
ObjectFile
Attribute:
This attribute is useful for attaching a file name in a given class. So, when using the methods Save<T>(T obj)
, Get<T>()
Remove<T>()
or, GetOrCreate<T>()
the name of the type of the object will no longer be used. The name set on the property ObjectFile(FileName="file.json")
will always be used for this type.
Example:
namespace Example.FileManager
{
using SysCommand.ConsoleApp;
using SysCommand.ConsoleApp.Files;
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static int Main(string[] args)
{
return App.RunApplication();
}
}
public class Command1 : Command
{
private JsonFileManager fileManager;
public Command1()
{
fileManager = App.Items.GetOrCreate<JsonFileManager>();
}
public void Save(string title, string description = null)
{
var tasks = fileManager.GetOrCreate<Tasks>();
tasks.LastUpdate = DateTime.Now;
var task = tasks.AllTasks.FirstOrDefault(t => t.Title == title);
if (task == null)
{
task = new Task
{
Id = tasks.AllTasks.Count + 1,
Title = title,
Description = description,
DateAndTime = DateTime.Now
};
tasks.AllTasks.Add(task);
}
fileManager.Save(tasks);
}
public void Get(string title)
{
var tasks = fileManager.GetOrCreate<Tasks>();
this.ShowTask(tasks.AllTasks.Where(t => t.Title.Contains(title)));
}
private void ShowTask(IEnumerable<Task> tasks)
{
foreach (var task in tasks)
this.ShowTask(task);
}
private void ShowTask(Task task)
{
if (task == null)
{
App.Console.Error("Task not found");
return;
}
App.Console.Write("Id: " + task.Id);
App.Console.Write("Title: " + task.Title ?? "-");
App.Console.Write("Description: " + task.Description ?? "-");
App.Console.Write("Date: " + task.DateAndTime);
}
[ObjectFile(FileName = "tasks")]
public class Tasks
{
public DateTime LastUpdate { get; set; }
public List<Task> AllTasks { get; set; } = new List<Task>();
}
public class Task
{
public int Id { get; set; }
public DateTime DateAndTime { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
}
}
MyApp.exe save "title1" "description1"
MyApp.exe save "title2" "description2"
MyApp.exe get "title"
Id: 1
Title: title1
Description: description1
Date: 20/02/2017 21:22:19
Id: 2
Title: title2
Description:
Date: 20/02/2017 21:24:20
Note that to create an instance of JsonFileManager
the scope of the App.Items
context, it is useful to keep only one instance of this Manager, saving memory and keeping the same settings anywhere that is uses it. Of course, if the settings are specified, then you will need to create a new instance with other settings in the scope.
To redirect your application with a new sequence of commands is very simple, just to your action return an instance of the class RedirectResult
by passing in your constructor a string containing the new string of commands. It is worth mentioning that the instances of the controls will be the same, that is, the State of each command will not return to the start, just the flow of execution. Another important point is that any action after action that returned the RedirectResult
will no longer be called.
Example:
public class RedirectCommand : Command
{
private int _count;
public RedirectResult RedirectNow(string arg)
{
_count++;
App.Console.Write($"Redirecting now!!. Count: {_count}");
return new RedirectResult("redirected", "--arg", arg);
}
public string Something()
{
return "Something";
}
public string Redirected(string arg)
{
_count++;
return $"Redirected: {arg}. Count: {_count}";
}
}
In the example below, the action Something
to be executed because she is set before the redirect.
C:\MyApp.exe something redirect-now my-value
Something
Redirecting now!!. Count: 1
Redirected: my-value. Count: 2
In the example below the action Something
will not be executed because she is set after the redirect.
C:\MyApp.exe redirect-now my-value something
Redirecting now!!. Count: 1
Redirected: my-value. Count: 2
When there are many actions with the same name and signature, all they will run together when requested by the user. However, you can prevent this by using the command ExecutionScope.StopPropagation()
within your action you want it to be the last on the execution stack.
Example:
public class StopPropagationCommand1 : Command
{
public string StopPropagationAction1(bool cancel = false)
{
return "StopPropagationCommand1.StopPropagationAction1";
}
public string StopPropagationAction2()
{
return "StopPropagationCommand1.StopPropagationAction2";
}
}
public class StopPropagationCommand2 : Command
{
public string StopPropagationAction1(bool cancel = false)
{
if (cancel)
{
ExecutionScope.StopPropagation();
}
return "StopPropagationCommand2.StopPropagationAction1";
}
public string StopPropagationAction2()
{
return "StopPropagationCommand2.StopPropagationAction2";
}
}
public class StopPropagationCommand3 : Command
{
public string StopPropagationAction1(bool cancel = false)
{
return "StopPropagationCommand3.StopPropagationAction1";
}
public string StopPropagationAction2()
{
return "StopPropagationCommand3.StopPropagationAction2";
}
}
C:\MyApp.exe stop-propagation-action1
StopPropagationCommand1.StopPropagationAction1
StopPropagationCommand2.StopPropagationAction1
StopPropagationCommand3.StopPropagationAction1
C:\MyApp.exe stop-propagation-action1 --cancel
StopPropagationCommand1.StopPropagationAction1
StopPropagationCommand2.StopPropagationAction1
Note that when using the script --cancel
action "StopPropagationCommand3. StopPropagationAction1" was not performed. That's why she was in last position of the execution stack and how the action "StopPropagationCommand2. StopPropagationAction1" cancelled the continuity of implementation, any other action of the string will be ignored.
Another possibility StopPropagation
is to use when there are multiple actions in the same input. The logic is the same, will be cancelled all actions of the stack that are after the action that triggered the stop.
C:\MyApp.exe stop-propagation-action1 stop-propagation-action2
StopPropagationCommand1.StopPropagationAction1
StopPropagationCommand2.StopPropagationAction1
StopPropagationCommand3.StopPropagationAction1
StopPropagationCommand1.StopPropagationAction2
StopPropagationCommand2.StopPropagationAction2
StopPropagationCommand3.StopPropagationAction2
C:\MyApp.exe stop-propagation-action1 --cancel stop-propagation-action2
StopPropagationCommand1.StopPropagationAction1
StopPropagationCommand2.StopPropagationAction1
Notice that execution has stopped at the same point.
This feature allows you to save those inputs which are used very often and can be persisted indeterminadamente. The your operation is quite simple, a built-in Command
named SysCommand.ConsoleApp.Commands.ArgsHistoryCommand
is responsible for saving the commands and loads them when prompted. The .app/history.json
file is where are saved the commands on the Json
format. The management actions
are as follows:
history-save [name]
: Use to save a command. It is mandatory to specify a name.history-load [name]
: Use to load a command using a previously saved name.history-delete [name]
: Use to delete a command.history-list
: Use to list all saved commands.
Example:
public class TestArgsHistories : Command
{
public void TestHistoryAction()
{
this.App.Console.Write("Testing");
}
}
C:\MyApp.exe test-history-action history-save "CommonCommand1"
Testing
C:\MyApp.exe history-load "CommonCommand1"
Testing
C:\MyApp.exe history-list
[CommonCommand1] test-history-action
C:\MyApp.exe history-remove "CommonCommand1"
C:\MyApp.exe history-list
The last two commands do not return outputs.
- To disable the command
ArgsHistoryCommand
see topic Specifying the types of commands. - The action
history-load
returns an object of typeRedirectResult
that forces redirection to a new command. Any input from this action will be despised. See the topic Command redirection. - This feature will only work if the flag
App.EnableMultiAction
is turned on.
This extra is designed for a specific occasion parse where the focus is to be simple. With the class SysCommand.Extras.OptionSet
it is possible to parse arguments in the traditional way.
Methods:
void Add<T>(string longName, string helpText, Action<T> action)
: Adds a setting in long formatvoid Add<T>(char shortName, string helpText, Action<T> action)
: Adds a configuration in short formatAdd<T>(string longName, char? shortName, string helpText, Action<T> action)
: Adds a configuration in the form of long and short.Add<T>(Argument<T> argument)
: Adds a complete configurationvoid Parse(string[] args, bool enablePositionalArgs = false)
: Executes the parse
Properties:
ArgumentsValid
: After the parse that information contains all the valid argumentsArgumentsInvalid
: After the parse that information contains all the invalid argumentsHasError
: Checks whether there is parse errors
Example:
using SysCommand.ConsoleApp;
using SysCommand.ConsoleApp.Helpers;
using SysCommand.Extras;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Example.Extras
{
public class Program
{
public static void Main(string[] args)
{
while (true)
{
Console.Write(Strings.CmdIndicator);
args = ConsoleAppHelper.StringToArgs(Console.ReadLine());
bool verbosity = false;
var shouldShowHelp = false;
var names = new List<string>();
var options = new OptionSet();
options.Add(new OptionSet.Argument<List<string>>("name", "the name of someone to greet.")
{
Action = (n) =>
{
if (n != null)
names.AddRange(n);
}
});
options.Add(new OptionSet.Argument<bool>('v', "show verbose")
{
Action = (v) =>
{
verbosity = v;
}
});
options.Add(new OptionSet.Argument<bool>("help", "show help")
{
Action = (h) =>
{
shouldShowHelp = h;
}
});
options.Parse(args);
if (!options.ArgumentsInvalid.Any())
{
Console.WriteLine("verbosity: " + verbosity);
Console.WriteLine("shouldShowHelp: " + shouldShowHelp);
Console.WriteLine("names.Count: " + names.Count);
}
else
{
Console.WriteLine("error");
}
}
}
}
}
cmd> --name a b c -v --help
verbosity: True
shouldShowHelp: True
names.Count: 3
This text was translated by a machine