Skip to content

Latest commit

 

History

History
3152 lines (2435 loc) · 112 KB

en.md

File metadata and controls

3152 lines (2435 loc) · 112 KB

Inglês Português

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 the Command types that will be used throughout the process. If it is null then the system will try to automatically any class that extend from Command . Understand better in Specifying the types of commands.
  • enableMultiAction: Turns the MultiAction behavior. By default, this behavior is enabled. Understand better in Multi-action.
  • addDefaultAppHandler: If false so does not create the event handler that is responsible for the outputs standard engine and erros controls, and among others. The default is true . Understand better in Event control.
  • output: Redirects the output to the TextWriter specified. Otherwise will be used by default Console.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 value
  • App.OnComplete(ApplicationResult): Fires at the end of the implementation
  • App.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
  • boolorbool?
  • decimalordecimal?
  • doubleordouble?
  • intorint?
  • uintoruint?
  • DateTimeorDateTime?
  • byteorbyte?
  • shortorshort?
  • ushortorushort?
  • longorlong?
  • ulongorulong?
  • floatorfloat?
  • charorchar?
  • 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 : Use true , 1 , + (separated by space or attached with the name of the argument) or omit the value.
  • For the value FALSE : Use false , 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:

  1. Mapping: represented by the GetMaps method.
  2. Simple parser: Represented by the methodParseRaw
  3. Complex parser: Represented by the methodParse
  4. Execution: Represented by the methodExecute
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 argument
  • ShortNameAndNoValue: Argument in short form and without value (Boolean)
  • ShortNameAndHasValue: Argument in short form with value
  • ShortNameAndHasValueInName: 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 value
  • LongNameAndHasValueInName: 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.

  1. The first step is to find the methods according to the input. For this will be used as references, all ArgumentRaw in the format Unnamed , i.e. arguments without names. The search will be inside the map returned by this method GetMaps . When a method is found, an instance of the SysCommand.Parsing.ActionParsed type is created and each parameter of the method is represented by the class SysCommand.Parsing.ArgumentParsed .
  2. 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 first ArgumentRaw of the input is not a action . In this scenario all the standard methods will be chosen for the next step. From then on the process will be the same.
  3. 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.
  1. All levels that are not of action (arguments only) will be used to find the properties . When this happens, each property is represented by the class SysCommand.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.

  1. 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
  1. 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 model
        • ArgumentParsed1:AllRaw { "123" }
        • ArgumentParsed2:AllRaw { "456" }
      • Action3(string value = null): Was not chosen
        • ArgumentParsed1:AllRaw { "123" }
      • Action3(List<string> value): Was not chosen
        • ArgumentParsed1:AllRaw { "123", "456" }
    • Level 2:action3 123 456 678
      • Action3(List<string> value): Best method everyone must have this model
        • ArgumentParsed1:AllRaw { "123", "456", "678" }
      • Action3(string value = null):: Was not chosen
        • ArgumentParsed1:AllRaw { "123" }
      • Action3(int? value = null, string value2 = null): Was not chosen
        • ArgumentParsed1:AllRaw { "123" }
        • ArgumentParsed2:AllRaw { "456" }
    • Level 3:action3 999
      • Action3(string value = null): Best method
        • ArgumentParsed1:AllRaw { "999" }
      • Action3(int? value = null, string value2 = null): Expected sequence was combined
        • ArgumentParsed1:AllRaw { "999" }
      • Action3(List<string> value): Expected sequence was combined
        • ArgumentParsed1:AllRaw { "999" }

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:

  1. 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 or Enums 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.
  2. 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.
  3. 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 the Enum .
      • 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 inputs
      • AllRaw { "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 input A already has the reference Command5.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 inputs
      • AllRaw { "A", "B", "C" }
    • Command6.Prop1: Compatible with the reference
      • AllRaw { "A", "B", "C" }
    • Command4.Prop1: Property is valid, but the input A already has the reference Command5.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 a CommandParse list. The CommandParse 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 State NotMapped .
  • 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 all Command 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 method Main() 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 your int 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:

Commands/ExampleRazorCommand. cs
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; }
    }
}
Views/ExampleRazor/cshtml MyAction.
@model ExampleRazorCommand.MyModel
@if (Model == null)
{
    <text>#### HelloWorld {NONE} ####</text>
}
else {
    <text>#### HelloWorld (@Model.Name) ####</text>
}
Tests

Input1:

MyApp.exe my-action
MyApp.exe my-action2
MyApp.exe my-action3

Outputs:

#### HelloWorld {NONE} ####
#### HelloWorld {MyName} ####
My name: John
Note
  • The research of template via Arquivo físico or via Embedded 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
  • 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 tag type . If you don't use no template then ignore this step.
  • Implement your template

Example:

Commands/ExampleT4Command. cs
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; }
    }
}
Views/ExampleT4/MyActionView.tt
<#@ parameter type="Example.T4Command.MyModel" name="Model" #>
<# if(Model == null) { #>
#### HelloWorld {NONE} ####
<# } #>
<# else { #>
#### HelloWorld (<#= Model.Name #>) ####
<# } #>
Tests

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:

Commands/TableCommand. cs
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; }
    }
}
Tests

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 class DefaultDescriptor and will always be displayed.
  • B. the Command text always appears and your source comes from the Command.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 required
      • Strings.HelpArgDescOptionalWithDefaultValue: When the Member is optional and has a default value.
      • Strings.HelpArgDescOptionalWithoutDefaultValue: When the Member is optional and has no default value.
  • 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 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 verbs
  • Info: Is the default verb, will always be displayed, unless the user send the verb Quiet .
  • 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.

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 exist
    • ArgumentParsedState.ArgumentIsRequired: Indicates that an argument is required
    • ArgumentParsedState.ArgumentHasInvalidInput: Indicates that an argument that is not valid
    • ArgumentParsedState.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 the T type, if you can't find so returns null to complex types and Exception for struct .
  • 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 type T or creates a new instance via reflection where type T 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 using Show all files the option Solution Explorer .
  • string DefaultFilePrefix: Adds a prefix on all files. The default is null .
  • string DefaultFileExtension: Specifies the extension of the files. The default is .json .
  • bool UseTypeFullName: Determines if the formatting of the types T will include the full name of the type. The default is false .

Methods:

  • Save<T>(T obj): Saves an object in the default folder where file name is the T type name formatted, with exception to classes that have the attribute ObjectFile .
  • 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 the T type name formatted, with exception to classes that have the attribute ObjectFile .
  • 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, if null the name of the type T is used in the search, with exception to classes that have the attribute ObjectFile .
    • refresh: If false , 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 type T . To save physically it is necessary to use the method Save .
  • string GetObjectFileName(Type type): Returns the formatted type name or if you are using the ObjectFile attribute, returns the value of the FileName property.
  • string GetFilePath(string fileName): Returns the file path into the default folder.

ObjectFileAttribute:

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 type RedirectResult 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 format
  • void Add<T>(char shortName, string helpText, Action<T> action): Adds a configuration in short format
  • Add<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 configuration
  • void Parse(string[] args, bool enablePositionalArgs = false): Executes the parse

Properties:

  • ArgumentsValid: After the parse that information contains all the valid arguments
  • ArgumentsInvalid: After the parse that information contains all the invalid arguments
  • HasError: 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

https://github.com/juniorgasparotto/MarkdownGenerator