Coder Social home page Coder Social logo

powerargs's Introduction

.NET

Binary

PowerArgs is available at the Official NuGet Gallery.

To build command line applications with text-based graphics try out klooie.

Overview

PowerArgs converts command line arguments into .NET objects that are easy to work with. It also provides a ton of additional, optional capabilities that you can try such as argument validation, auto generated usage, tab completion, and plenty of extensibility. You can even build a full-blown, interactive text UI application like this one.

It can also orchestrate the execution of your program. Giving you the following benefits:

  • Consistent and natural user error handling
  • Invoking the correct code based on an action (e.g. 'git push' vs. 'git pull')
  • Focus on writing your code

Here's a simple example that just uses the parsing capabilities of PowerArgs. The command line arguments are parsed, but you still have to handle exceptions and ultimately do something with the result.

// A class that describes the command line arguments for this program
public class MyArgs
{
    // This argument is required and if not specified the user will
    // be prompted.
    [ArgRequired(PromptIfMissing=true)]
    public string StringArg { get; set; }

    // This argument is not required, but if specified must be >= 0 and <= 60
    [ArgRange(0,60)]
    public int IntArg { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var parsed = Args.Parse<MyArgs>(args);
            Console.WriteLine("You entered string '{0}' and int '{1}'", parsed.StringArg, parsed.IntArg);
        }
        catch (ArgException ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ArgUsage.GenerateUsageFromTemplate<MyArgs>());
        }
    }
}

Here's the same example that lets PowerArgs do a little more for you. The application logic is factored out of the Program class and user exceptions are handled automatically. The way exceptions are handled is that any exception deriving from ArgException will be treated as user error. PowerArgs' built in validation system always throws these type of exceptions when a validation error occurs. PowerArgs will display the message as well as auto-generated usage documentation for your program. All other exceptions will still bubble up and need to be handled by your code.

// A class that describes the command line arguments for this program
[ArgExceptionBehavior(ArgExceptionPolicy.StandardExceptionHandling)]
public class MyArgs
{
    // This argument is required and if not specified the user will
    // be prompted.
    [ArgRequired(PromptIfMissing=true)]
    public string StringArg { get; set; }

    // This argument is not required, but if specified must be >= 0 and <= 60
    [ArgRange(0,60)]
    public int IntArg { get; set; }

    // This non-static Main method will be called and it will be able to access the parsed and populated instance level properties.
    public void Main()
    {
        Console.WriteLine("You entered string '{0}' and int '{1}'", this.StringArg, this.IntArg);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Args.InvokeMain<MyArgs>(args);
    }
}

Then there are more complicated programs that support multiple actions. For example, the 'git' program that we all use supports several actions such as 'push' and 'pull'. As a simpler example, let's say you wanted to build a calculator program that has 4 actions; add, subtract, multiply, and divide. Here's how PowerArgs makes that easy.

[ArgExceptionBehavior(ArgExceptionPolicy.StandardExceptionHandling)]
public class CalculatorProgram
{
    [HelpHook, ArgShortcut("-?"), ArgDescription("Shows this help")]
    public bool Help { get; set; }

    [ArgActionMethod, ArgDescription("Adds the two operands")]
    public void Add(TwoOperandArgs args)
    {
        Console.WriteLine(args.Value1 + args.Value2);
    }

    [ArgActionMethod, ArgDescription("Subtracts the two operands")]
    public void Subtract(TwoOperandArgs args)
    {
        Console.WriteLine(args.Value1 - args.Value2);
    }

    [ArgActionMethod, ArgDescription("Multiplies the two operands")]
    public void Multiply(TwoOperandArgs args)
    {
        Console.WriteLine(args.Value1 * args.Value2);
    }

    [ArgActionMethod, ArgDescription("Divides the two operands")]
    public void Divide(TwoOperandArgs args)
    {
        Console.WriteLine(args.Value1 / args.Value2);
    }
}

public class TwoOperandArgs
{
    [ArgRequired, ArgDescription("The first operand to process"), ArgPosition(1)]
    public double Value1 { get; set; }
    [ArgRequired, ArgDescription("The second operand to process"), ArgPosition(2)]
    public double Value2 { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Args.InvokeAction<CalculatorProgram>(args);
    }
}

Again, the Main method in your program class is just one line of code. PowerArgs will automatically call the right method in the CalculatorProgram class based on the first argument passed on the command line. If the user doesn't specify a valid action then they get a friendly error. If different actions take different arguments then PowerArgs will handle the validation on a per action basis, just as you would expect.

Here are some valid ways that an end user could call this program:

  • Calculator.exe add -Value1 1 -Value2 5 outputs '6'
  • Calculator.exe multiply /Value1:2 /Value2:5 outputs '10'
  • Calculator.exe add 1 4 outputs '5' - Since the [ArgPosition] attribute is specified on the Value1 and Value2 properties, PowerArgs knows how to map these arguments.

If you wanted to, your action method could accept loose parameters in each action method. I find this is useful for small, simple programs where the input parameters don't need to be reused across many actions.

[ArgActionMethod, ArgDescription("Adds the two operands")]
public void Add(
    [ArgRequired][ArgDescription("The first value to add"), ArgPosition(1)] double value1,
    [ArgRequired][ArgDescription("The second value to add"), ArgPosition(2)] double value2)
{
    Console.WriteLine(value1 + value2);
}

You can't mix and match though. An action method needs to be formatted in one of three ways:

  • No parameters - Meaning the action takes no additional arguments except for the action name (i.e. '> myprogram.exe myaction').
  • A single parameter of a complex type whose own properties describe the action's arguments, validation, and other metadata. The first calculator example used this pattern.
  • One or more 'loose' parameters that are individually revivable, meaning that one command line parameter maps to one property in your class. The second calculator example showed a variation of the Add method that uses this pattern.

Metadata Attributes

These attributes can be specified on argument properties. PowerArgs uses this metadata to influence how the parser behaves.

  • [ArgPosition(0)] This argument can be specified by position (no need for -propName)
  • [ArgShortcut("n")] Lets the user specify -n
  • [ArgDescription("Description of the argument")]
  • [ArgExample("example text", "Example description")]
  • [HelpHook] Put this on a boolean property and when the user specifies that boolean. PowerArgs will display the help info and stop processing any additional work. If the user is in the context of an action (e.g. myprogram myaction -help) then help is shown for the action in context only.
  • [ArgDefaultValue("SomeDefault")] Specify the default value
  • [ArgIgnore] Don't populate this property as an arg
  • [StickyArg] Use the last used value if not specified. This is preserved across sessions. Data is stored in /AppData/Roaming/PowerArgs by default.
  • [TabCompletion] Enable tab completion for parameter names (see documentation below)

Validator Attributes

These attributes can be specified on argument properties. You can create custom validators by implementing classes that derive from ArgValidator.

  • [ArgRequired(PromptIfMissing=bool)] This argument is required. There is also support for conditionally being required.
  • [ArgExistingFile] The value must match the path to an existing file
  • [ArgExistingDirectory] The value must match the path to an existing directory
  • [ArgRange(from, to)] The value must be a numeric value in the given range.
  • [ArgRegex("MyRegex")] Apply a regular expression validation rule
  • [UsPhoneNumber] A good example of how to create a reuable, custom validator.

Custom Revivers

Revivers are used to convert command line strings into their proper .NET types. By default, many of the simple types such as int, DateTime, Guid, string, char, and bool are supported.

If you need to support a different type or want to support custom syntax to populate a complex object then you can create a custom reviver.

This example converts strings in the format "x,y" into a Point object that has properties "X" and "Y".

[ArgReviverType]
public class CustomReviverExample
{
    // By default, PowerArgs does not know what a 'Point' is.  So it will
    // automatically search your assembly for arg revivers that meet the
    // following criteria:
    //    - Live in a class or struct with an [ArgReviverType] attribute
    //    - Are a public, static method with an [ArgReviver] attribute
    //    - Accepts exactly two string parameters
    //    - The return value matches the type that is needed

    public Point Point { get; set; }

    // This ArgReviver matches the criteria for a "Point" reviver
    // so it will be called when PowerArgs finds any Point argument.
    //
    // ArgRevivers should throw ArgException with a friendly message
    // if the string could not be revived due to user error.

    [ArgReviver]
    public static Point Revive(string key, string val)
    {
        var match = Regex.Match(val, @"(\d*),(\d*)");
        if (match.Success == false)
        {
            throw new ArgException("Not a valid point: " + val);
        }
        else
        {
            Point ret = new Point();
            ret.X = int.Parse(match.Groups[1].Value);
            ret.Y = int.Parse(match.Groups[2].Value);
            return ret;
        }
    }
}

Generate usage documentation from templates (built in or custom)

PowerArgs has always provided auto-generated usage documentation via the ArgUsage class. However, the format was hard coded, and gave very little flexibility in terms of the output format. With the latest release of PowerArgs usage documentation can be fully customized via templates. A template is just a piece of text that represents the documentation structure you want along with some placeholders that will be replaced with the actual information about your command line application. There are built in templates designed for the console and a web browser, and you can also create your own.

In its latest release, PowerArgs adds a new method called ArgUsage.GenerateUsageFromTemplate(). The method has several overloads, most of which are documented via XML intellisense comments. The part that needs a little more explanation is the template format. To start, let's talk about the built in templates.

The first one, the default, is designed to create general purpose command line usage documentation that is similar to the older usage documentation that PowerArgs generated. You can see what that template looks like here.

The second one is designed to create documentation that looks good in a browser. You can see what that template looks like here.

Here is an example of what the built in browser usage looks like.

You can create your own templates from scratch, or modify these default templates to suit your needs. The templating engine was built specifically for PowerArgs. It has quite a bit of functionality and extensibility, but for now I'm only going to document the basics.

Most of you probably use the class and attributes model when using PowerArgs, but under the hood there's a pretty extensive object model that gets generated from the classes you build. That model is what is bound to the template. If you're not familiar with the object model you can explore the code here.

You can see from the built in templates that there is placeholder syntax that lets you insert information from the model into template. For example, if your program is called 'myprogram' then the following text in the template would be replaced with 'myprogram'.

{{ExeName !}} is the best
// outputs - 'myprogram is the best'

Additionally, you can add a parameter to the replacement tag that indicates the color to use when printed on the command line as a ConsoleString. You can use any ConsoleColor as a parameter.

{{ExeName Cyan !}}

You can also choose to conditionally include portions of a template based on a property. Here's an example from the default template:

{{if HasActions}}Global options!{{if}}{{ifnot HasActions}}Options!{{ifnot}}:

In this case, if the HasActions property on the CommandLineArgumentsDefinition object is true then the usage will output 'Global options'. Otherwise it will output 'Options'. This flexibility is important since some command line programs have only simple options while others expose multiple commands within the same executable (e.g. git pull and git push).

Another thing you can do is to enumerate over a collection to include multiple template fragments in your output. Take this example.

{{each action in Actions}}
{{action.DefaultAlias!}} - {{action.Description!}}
!{{each}}

If your program has 3 actions defined then you'd get output like this.

action1 - action 1 description here
action2 - action 2 description here
action3 - action 3 description here

When referring to a part of your data model you can also navigate objects using dot '.' notation. Notice in the example above I was able to express {{ action.DefaultAlias !}}. You could go even deeper. For example {{ someObj.SomeProperty.DeeperProperty !}}. More advanced expressions like function calling with parameters are not supported.

PS

I'm pretty happy with the templating solution. In fact, hidden in PowerArgs is a general purpose template rendering engine that I've found useful in other projects for things like code generation. You can actually bind any string template to any plain old .NET object (dynamic objects not supported). Here's a basic sample:

var renderer = new DocumentRenderer();
var document = renderer.Render("Hi {{ Name !}}", new { Name = "Adam" });
// outputs 'Hi Adam'

Ambient Args

Access your parsed command line arguments from anywhere in your application.

MyArgs parsed = Args.GetAmbientArgs<MyArgs>();

This will get the most recent insance of type MyArgs that was parsed on the current thread. That way, you have access to things like global options without having to pass the result all throughout your code.

Secure String Arguments

Support for secure strings such as passwords where you don't want your users' input to be visible on the command line.

Just add a property of type SecureStringArgument.

public class TestArgs
{
    public SecureStringArgument Password { get; set; }
}

Then when you parse the args you can access the value in one of two ways. First there's the secure way.

TestArgs parsed = Args.Parse<TestArgs>();
SecureString secure = parsed.Password.SecureString; // This line causes the user to be prompted

Then there's the less secure way, but at least your users' input won't be visible on the command line.

TestArgs parsed = Args.Parse<TestArgs>();
string notSecure = parsed.Password.ConvertToNonsecureString(); // This line causes the user to be prompted

Tab Completion

Get tab completion for your command line arguments. Just add the TabCompletion attribute and when your users run the program from the command line with no arguments they will get an enhanced prompt (should turn blue) where they can have tab completion for command line argument names.

[TabCompletion]
public class TestArgs
{
    [ArgRequired]
    public string SomeParam { get; set; }
    public int AnotherParam { get; set; }
}

Sample usage:

someapp -some  <-- after typing "-some" you can press tab and have it fill in the rest of "-someparam"

You can even add your own tab completion logic in one of two ways. First there's the really easy way. Derive from SimpleTabCompletionSource and provide a list of words you want to be completable.

public class MyCompletionSource : SimpleTabCompletionSource
{
    public MyCompletionSource() : base(MyCompletionSource.GetWords()) {}
    private static IEnumerable<string> GetWords()
    {
        return new string[] { "SomeLongWordThatYouWantToEnableCompletionFor", "SomeOtherWordToEnableCompletionFor" };
    }
}

Then just tell the [TabCompletion] attribute where to find your class.

[TabCompletion(typeof(MyCompletionSource))]
public class TestArgs
{
    [ArgRequired]
    public string SomeParam { get; set; }
    public int AnotherParam { get; set; }
}

There's also the easy, but not really easy way if you want custom tab completion logic. Let's say you wanted to load your auto completions from a text file. You would implement ITabCompletionSource.

public class TextFileTabCompletionSource : ITabCompletionSource
{
    string[] words;
    public TextFileTabCompletionSource(string file)
    {
        words = File.ReadAllLines(file);
    }

    public bool TryComplete(bool shift, string soFar, out string completion)
    {
        var match = from w in words where w.StartsWith(soFar) select w;

        if (match.Count() == 1)
        {
            completion = match.Single();
            return true;
        }
        else
        {
            completion = null;
            return false;
        }
    }
}

If you expect your users to sometimes use the command line and sometimes run from a script then you can specify an indicator string. If you do this then only users who specify the indicator as the only argument will get the prompt.

[TabCompletion("$")]
public class TestArgs
{
    [ArgRequired]
    public string SomeParam { get; set; }
    public int AnotherParam { get; set; }
}

powerargs's People

Contributors

adamabdelhamed avatar adamabmsft avatar axlefublr avatar bitbonk avatar chaami avatar dpen2000 avatar floatas avatar ghowlett2020 avatar papamufflon avatar pgermishuys avatar remcoros avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

powerargs's Issues

Question - Custom HelpHooks

How would I go about creating a custom help hook, that opened up a URL instead of writing to console, for example?

I created UrlHelpHook : HelpHook and overrode the AfterCancel event, but whichever one I call, it always performs both the HelpHook AfterCancel and the UrlHelpHook AfterCancel.

[AttributeUsage(AttributeTargets.Property|AttributeTargets.Parameter)]
    public class UrlHelpHookAttribute : HelpHook
    {
        private string helpUrl;

        public UrlHelpHookAttribute(string url)
            :base()
        {
            this.helpUrl = Path.GetFullPath(url);

            if (!File.Exists(this.helpUrl))
            {
                throw new ValidationArgException("File not found - " + url, new FileNotFoundException());
            }
        }

        public override void AfterCancel(HookContext context)
        {
            if (WriteHelp == false) return;

            Console.WriteLine(@"Launching default browser to display HTML ...");
            Process.Start(this.helpUrl);
        }
    }

license

there is currently no license, could you add one? Because, you know, lack of license means no one can legally use it :-)

ArgIgnoreCase

Hi,

Is ArgIgnoreCase honoured when using an enum as a property. I know System.Enum.TryParseEnum allows you to pass whether to ignore case.

Regards,
Alex

How to suppress UnexpectedArgException

I have a BaseArgs class and a bunch of derived specialized argument classes. The BaseArgs class has an Enum for the action to be invoked. Actions are complex enough to warrant their own specific classes to hold their args.

I was hoping to use Args.Parse<BaseArgs>(args) to get the Action enum, then based on the result, use Args.Parse<DerivedArgs>(args), since DerivedArgs's type corresponds to the Action I got from the previous call. Is there a way to do this, or would I have to just try to parse as each derived class and catch the exceptions myself?

ArgActionMethods must declare 1 parameter...

In the ActionFrameworkV2 tests, I clearly see parameterless actions being invoked, but get the above-named exception when I attempt this myself. Has this feature not made it into the current Nuget version yet? (I'm using 1.8)

Non interactive mode

It would be great if there were some kind of option to put the argument parsing in "non interative mode" for applications running outside of a UI context, that will supress PromptIfMissing and TabCompletion.

C:\> MyApp.exe -noninteractive otheroptions

Allow applying multiple shortcuts to the same arg.

Currently the ArgShortcutAttribute is set to AllowMultiple=false. This prevents multiple shortcuts for the same arg.

I'd like to do something like this.

[ArgShortcut("h")]
[ArgShortcut("?")]
public bool Help { get; set; }

Allowing a user to specify -help, -h OR -?.

HelpHook - ArgCancelProcessingException not being thrown all the way up

How are you supposed to use Args.Parse together with HelpHook?

I've seen this example:

var parsed = Args.Parse<MyArgs>(args);
if (parsed.Help)
{
      ArgUsage.GetStyledUsage<MyArgs>().Write();
}

but it seems to make the HelpHook redundant.

For example:

From my Program.Main, I call this:

try
{
    var parsed = Args.Parse<MyArgs>(args);

    Console.WriteLine("Your name is {0}", parsed.Name);
}
catch(ArgException ex)
{
    ArgUsage.GetStyledUsage<MyArgs>().Write();
    Console.WriteLine(ex.Message);
}

I have a bool property Help, and it is decorated with the HelpHook attribute.

If I pass the argument -help (or -?) it runs the AfterCancel method of HelpHook, but the ArgCancelProcessingException eventually gets caught by:

private static T Execute<T>(Func<T> argsProcessingCode) where T : class
{
    executeRecursionCounter++;
    ArgHook.HookContext.Current = new ArgHook.HookContext();

    try
    {
        return argsProcessingCode();
    }
    catch (ArgCancelProcessingException ex)
    {
        if (executeRecursionCounter > 1)
        {
            throw;
        }

        return CreateEmptyResult<T>(ArgHook.HookContext.Current, cancelled: true);
    }

and CreateEmptyResult returns null, later giving "Object reference not set to an instance of an object" when it tries to access parsed.Name.

[Retitled] - Support aliases (or shortcuts) for action names

A working class with action methods is now throwing exceptions after upgrading from 1.8.? to 2.0.?. Neither the class containing the action methods nor the class invoking it has changed. Is the syntax new, or are new attributes necessary? (I didn't see any new arguments to the ArgActionMethod() attribute constructor...)

Will try rolling back to 1.8 for now.

Error when methods are in-lined

I just found that when I build PowerArgs in release, I get a TargetInvocationException (and NullReferenceException inner exception) from the AttrOverride.cs Set method, at this line here:

if (callingMethod.Name.StartsWith("set_") == false || callingMethod.IsSpecialName == false)

I have been told it's happening because:

  • In the two lines above, it's using the StackTrace to find the calling member.
  • It looks one frame ahead, and extracts the method name and tries to cast to MethodInfo
StackTrace stackTrace = new StackTrace();
var callingMethod = stackTrace.GetFrame(1).GetMethod() as MethodInfo;
  • What actually happens, is that the calling method has been optimised away by the compiler, and so what comes back from stackTrace.GetFrame(1).GetMethod() is in fact a RuntimeConstructorInfo, not method info.
  • Then, it calls callingMethod.Name in the offending line, which throws a NullReferenceException

There are 7 references to this method, in CommandLineArgument.cs and CommandLineAction.cs

StackTrace is also used in the Get method of AttrOverride.cs but at the moment, it isn't being inlined (though the compiler is within its rights to at some point in the future!)

One workaround is to always build debug and release without optimisation.
image

Another is to decorate all of the calling members' sets like this:

public string Description
{
    get
    {
        return overrides.Get<ArgDescription, string>(Metadata, d => d.Description, string.Empty);
    }
    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    set
    {
        overrides.Set(value);
    }
}

With either of these methods, it runs fine.

What might be a better long-term solution is getting the name using CallerMemberName instead of stackTrace.GetFrame():
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx

(This is .Net 4.5 though)

GetUsage<T>() fails when called from unit test

When called via a unit test, GetUsage()'s call to Assembly.GetEntryAssembly() returns null which causes a null reference exception when trying to access the assembly's location.

The following test code illustrates the problem:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using PowerArgs;

namespace ArgsTests
{
    [TestClass]
    public class ArgUsageTests
    {

        [TestMethod]
        public void TestArgUsage()
        {
            string[] args = new string[] { "test" };
            var commandArgs = Args.Parse<SomeArgs>(args);
            string usage = ArgUsage.GetUsage<SomeArgs>();
            Assert.IsTrue(usage.Contains("Usage"));
        }
    }

    public class SomeArgs
    {
        [ArgDescription("File name")]
        [ArgPosition(0)]
        public string FileName { get; set; }
    }

}

Parameters seem to behave differently in the latest version

Hi,

I have an app that uses PowerArgs. The usage looks like the following:

Usage: spec options

OPTION TYPE POSITION DESCRIPTION

-path (-p) String 0 The path to search all the Spec assemblies. The default is spec.

-example (-e) String NA The example to execute. This could be a spec, example group or an e
xample.
-pattern (-P) String NA Load files matching pattern. The default is Specs.

-output (-o) String NA The output path of the test results. The default is test-results.xm
l.
-format (-f) ConsoleFormatterType NA Specifies what format to use for output.

-dryrun (-d) Switch NA Invokes formatters without executing the examples.

-version (-v) Switch NA Display the version.

-colour (-c) Switch NA Enable colour in the output.

-help (-h) Switch NA You're looking at it.

EXAMPLE: spec.(exe|sh) spec
Execute all the specs in the folder spec.

I use to be able to run the program as spec.exe artifacts -P Unit.Specs. Now I get

Unexpected Argument: artifacts

Now if I run spec.exe -p artifacts -P Unit.Specs it tells me

Argument specified more than once: path

So this is treating it as -p and -P as the same option, which I am guessing is incorrect.

The only way for me to get it working is to specify the long form:

spec.exe -path artifacts -pattern Unit.Specs

Hopefully this highlights my issues:

Upgrading to 2.3.1.0 introduces a content file

After upgrading, Resource/DefaultBrowserUsageTemplate.html appears in my project.

The new ArgUsage.GenerateUsageFromTemplate<T>() works fine without it so this file is noise. Can it be removed from the package?

Usage string

GetUsage<T> doesn't allow to fully modify usage string. There is a need to get less detailed output in scriptcs/scriptcs - without type and position information.

Should we just add detailed parameter (true by default) to the GetUsage<T>, so if it's set - type and position information should be included, otherwise - not.

If this solution is suitable I can make a PR with it.

Displaying help documention

How do you set up the help for a parser that uses the Action Framework?

If I paraphrase the superencoder code snippets, what I'd want/expect is something does:

superencoder -help

-- displays the help docs

superencoder encode -help

-- displays EncodeArgs help docs

superencoder clip -help

-- displays ClipArgs help docs

At the moment, what I actually get is the following behaviour:

superencoder -help

-- displays the help docs and the error "No action was specified"

superencoder encode -help

-- displays EncodeArgs help docs and the error "The argument 'Source' is required"

superencoder clip -help

-- displays ClipArgs help docs and the error "The argument 'Source' is required"

So basically:

  1. Can you add a -help property to a parent class (which has actions) without having to specify an Action?
  2. Is it possible to make arguments Required when the user tries to use them, but not, for example, if another property, bool Help = true?

Cheers!

CustomReviver does not work if argument type and reviver are not defined in the same assembly

The cause is:

In ValidateArgScaffold method, ArgRevivers.CanRevive is called with the type of the property. But ArgRevivers.CanRevive loads custom revivers by searching the assembly of the type passed in. So if the type and custom reviver are not defined in the same assembly, the custom reviver cannot be found.

I encountered the problem when using System.Net.Mail.MailAddress as argument type. The code is pasted here to help you debug the issue: (in F#)

type MailAddressReviver()=
     [<ArgReviver>]
     static member Revive (key:string) (address:string) = 
           new System.Net.Mail.MailAddress(address)

 [<TabCompletion>]
 type MyArgs()=
     [<ArgDescription("Show this help information")>]
     [<ArgShortcut("h")>]
     member val Help = false with get, set

     [<ArgDescription("Mail to")>]
     [<ArgShortcut("mt")>]
     member val MailTo:System.Net.Mail.MailAddress = null with get, set

Arg Commands

Hi,

Would it be possible to set-up arguments as commands like in linux. For example "apt-get install" and then the command can have options (which you have already), though it would be nice if the long version of the options could be treated with a -- rather than -.

You can look at http://linux.die.net/man/8/apt-get to see what I mean.

Regards,
Alex

arrays of enum

Could you recognize arrays of enums?

e.g.
OptionsEnum[] arg1

Get list of "unmatched" arguments

It would be great to somehow get a list of "unmatched" arguments when calling Args.Parse<T>. For scriptcs, this would be useful for passing those parameters further down to the running script (see scriptcs #173).

This could be done in many ways, i.e.:

  • Add an overload to Args.Parse<T> that takes a callback for unmatched argument handling:
var args = Args.Parse<MyArguments>(args, unmatchedArgument => 
    {
        // Do something with the unmatchedArgument, i.e. pass it to script...
    }
  • Catching an UnmatchedArgumentException that has a list of arguments (could be part of #11):
try
{
    var args = Args.Parse<MyArguments>(args);
} 
catch (UnmatchedArgumentException ex)
{
    // Do something with ex.UnmatchedArguments...
}
  • Add a TryParse<T> method, that returns false when an argument couldn't be matched with a property and adds the arguments to the list.
var unmatchedArguments = new List<KeyValuePair<string, string>>();
if (!Args.TryParse<MyArguments>(out unmatchedArguments, args))
{
    // Do something with unmatchedArguments
}

I personally like the first solution. Is this something that can be achieved easily? I'd be happy to do the work ๐Ÿ˜„

Add ability to require arguments by group

A feature that I'd love to see in PowerArgs is the ability to require arguments by group.

For example, let's say my console application declares four switch arguments: A, B, C and D. I want to require users to choose exactly two switches: one from A/B and one from C/D. The following combinations would be valid:

myapp.exe -a -c
myapp.exe -a -d
myapp.exe -b -c
myapp.exe -b -d

But the following would NOT be valid:

myapp.exe -a -b
myapp.exe -c -d

This is a feature that CommmandLineParser supports via the MutuallyExclusiveSet property, and which prevents me from switching over to PA.

DefaultValueAttribute vs Constructor

In many command line parsers I see custom DefaultValueAttribute or similar but I can set default values for properties in constructor of my args class. And my question is: what is purpose of DefaultValueAttribute? Do we really need it? One reason in my head is API beauty because we set default values near others metadata like Required.

Help text not displaying available Enum string values, when property type is Enum[]

If one of my properties is of type Enum "Category", in the help text displayed, I get a very useful list of the categories available:
image

If I decorate this enum with the [Flags] attribute, and specify that multiple categories can be selected, I can change the type of my property to Category[] and PowerArgs is clever enough to parse a comma separated list of categories, and store them against my property.

But
In the help text, it no longer displays the list of Categories the user can choose from:
image

I don't think this is a Reviver issue, so is there a way to keep that functionality in?

Internal Command Driven App

Adam,

Nice work. I found your PowerArgs while looking for a library to parse internal command line strings. The various command line parsing libraries all work on the Main Args list. It would seem that PowerArgs could be enhanced to handle any string even before it is broken into the Args list. That way the library could be used inside an application to handle an internal command driven interface. Any thoughts?

dgp

Interactive mode raises an exception when started from PowerShell

I have an args class marked with [TabCompletion]. My console application seems to start fine in interactive mode from a command prompt but raises an exception when started from PowerShell (3.0 running from PowerShell ISE)

Unhandled Exception: 
System.IO.IOException: The handle is invalid.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.Console.GetBufferInfo(Boolean throwOnNoConsole, Boolean& succeeded)
   at System.Console.get_CursorLeft()
   at PowerArgs.StdConsoleProvider.get_CursorLeft()
   at PowerArgs.ConsoleHelper.ReadLine(String& rawInput, List`1 history, ITabCompletionSource[] tabCompletionHooks)
   at PowerArgs.TabCompletion.BeforeParse(HookContext context)
   at PowerArgs.ArgHook.HookContext.<RunBeforeParse>b__5(ArgHook h)
   at PowerArgs.ArgHook.HookContext.RunHook(Func`2 orderby, Action`1 hookAction)
   at PowerArgs.ArgHook.HookContext.RunBeforeParse()
   at PowerArgs.Args.ParseInternal(CommandLineArgumentsDefinition definition, String[] input)
   at PowerArgs.Args.ParseInternal[T](String[] input)
   at PowerArgs.Args.<>c__DisplayClass1`1.<ParseAction>b__0()
   at PowerArgs.Args.Execute[T](Func`1 argsProcessingCode)
   at PowerArgs.Args.ParseAction[T](String[] args)
   at PowerArgs.Args.<>c__DisplayClass11`1.<InvokeAction>b__10(String[] a)
   at PowerArgs.REPL.DriveREPL[T](TabCompletion t, Func`2 eval, String[] args)
   at PowerArgs.Args.<>c__DisplayClass11`1.<InvokeAction>b__f()
   at PowerArgs.Args.Execute[T](Func`1 argsProcessingCode)
   at PowerArgs.Args.InvokeAction[T](String[] args)
   at TurfSport.BackOffice.Services.Console.Program.Main(String[] args) in C:\svn\turfsport\TurfSportBackOffice\TurfSport4\trunk\TurfSport.BackOffice.Services.Console\Program.cs:line 26
    [TabCompletion]
    [ArgExceptionBehavior(ArgExceptionPolicy.StandardExceptionHandling)]
    [ArgExample("info", "About info")]
    public class ServiceActions
    {
        [ArgActionMethod, ArgDescription("Get info about the service console.")]
        public void Info(Actions.Info info)
        {
            info.DoAction();
        }
    }

And I am invoking using

private static void Main(string[] args)
        {
            log4net.Config.XmlConfigurator.Configure();

            Args.InvokeAction<ServiceActions>(args);
        }

In Usage Options output the whitespace is missing

When PowerArgs/ArgUsage.cs was rewritten in 0910357 the whitespace was removed inadvertently. In the latest code, the opening parentheses is missing the whitespace.

PowerArgs/ArgUsage.cs - June 4 (Lines 359-365)

                if (inlineAliasInfo != string.Empty) inlineAliasInfo = "(" + inlineAliasInfo + ")";

                rows.Add(new List<ConsoleString>()
                {
                    new ConsoleString("-")+(usageInfo.Name + inlineAliasInfo),
                    descriptionString,
                });

PowerArgs/ArgUsage.cs - May 24 (Lines 343-347)

                rows.Add(new List<ConsoleString>()
                {
                    new ConsoleString(indicator)+(usageInfo.Name + (usageInfo.Aliases.Count > 0 ? " ("+ usageInfo.Aliases[0] +")" : "")),
                    descriptionString,
                });

add ability to check if an arg was provided by a user

First let me say how awesome this lib is and thank you for it!

one feature idea i have is that I would really like to be able to say

parsed.AnArge.WasProvidedByUser()

naturally the method call can be anything you want it to be but the feature is something i would like to have.

this is partly because i currently have 3 date arguments, start and end date or an as of date. either start and end date must be provided or as of date so i am not able to mark them all as required. looking into solving this with a custom validator but it seems like i will still not be able to leverage the prompt if not provided attribute so if you can come up with another solution that would allow that, maybe an argument dependency option.

[ArgRequiredWith(ArgClass.OtherArg)]

[ArgRequireIfOtherArgumentNotProvided(ArgClass.OtherArg)]

thanks again for everything!

"double dash" option for command options

Would be fantastic to be able to use a "double dash" for options, or sub commands of a single dash.

I.e.

LogQuery -log --html -dst:C:\la\

The : would also be great :)

--Brendan

Descrition and default behavior.

First off - nice work, this is the best Args parser I could find for c#. Here my suggestion:

I end up replicating something like this:

            if (args.Length > 0) { 
                    try { 
                        var cliParameter = PowerArgs.Args.Parse<CliParameter>(args);
                        if (cliParameter.Help) {
                            Console.WriteLine( CliParameter.Description );
                            PowerArgs.ArgUsage.GetStyledUsage<CliParameter>().Write();
                            return;
                        }
                        # Do more work here ....  
                        return;
                    }
                    catch (PowerArgs.ArgException)
                    {
                        ;
                    }
            }
            PowerArgs.ArgUsage.GetStyledUsage<CliParameter>().Write();

How about adding:

  1. Support for having a [Description] tag in the Cli-Parameter class to provide some text that is prepended on the usage to explain what the problem does.
  2. Handling the above as a default behavior (eliminating the check for no-args and exceptions) with e.g. PowerArgs.Args.Parse(args, HandleIt = true );

Any thoughts?

Request: Enhanced enum & flags support

It would be really useful to have more powerful support for enum features, specifically (in order of importance):

  • Bitmasked enum (flag) values as arguments without custom type converters or ILists
  • ArgDescription attributes on enum values, which appear in the generated help text
  • ArgShortcut attributes on enum values, functioning the same as on parameter-level arguments

Exceptions hierarchy

In current version, ArgException is used for almost every exceptional situation - for args parsing errors, validating errors, and so on. Even such code, that's related to library using, uses ArgException:

var assembly = Assembly.GetEntryAssembly();
if (assembly == null)
{
    throw new ArgException("PowerArgs could not determine the name of your executable automatically.  This may happen if you run GetUsage<T>() from within unit tests.  Use GetUsageT>(string exeName) in unit tests to avoid this exception.");
}

The main issue that ArgException isn't useful, when it comes to proper error handling. For example:

     try
        {
            var parsed = Args.Parse<MyArgs>(args);
            Console.WriteLine("You entered string '{0}' and int '{1}'", parsed.StringArg, parsed.IntArg);
        }
        catch (ArgException ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ArgUsage.GetUsage<MyArgs>());
        }

Here we can't react properly - we just don't have information about exceptional situation - just a string message and that's all. We have 2 ways: to write message to the user (can be kinda cryptic for him) or to parse the string message (so we can know more about exceptional situation and react to it in other ways).

Solving this issue should involve exceptional situtions handling refactoring and creating of some exceptions hierarchy for the goods of library API.

Enum types abbreviations

It'd be nice if Enum parameters also got assigned single letters to abbreviate each enum member.

ArgUsage.GenerateUsageFromTemplate omits shortcuts

The obsolete GetStyledUsage displays shortcuts in addition to the long-form argument names, but the new GenerateUsageFromTemplate does not. I couldn't find any documentation on how to compose a template or find alternate options besides the default, so for now I have to go back to the obsolete GetStyledUsage and suppress the compiler warning. :-)

ArgUsage.GenerateUsageFromTemplate<T>() formatting regressions

In comparison to the old ArgUsage.GetStyledUsage<T>, the default rendering from the new ArgUsage.GenerateUsageFromTemplate<T>() has several shortcomings:

  • No TYPE column in arguments list
  • No required (*) indicator on parameters
  • No indentation for arguments list
  • Poor examples layout:-
    • No blank line after each example (hard to tell where one ends and the next begins), Moreover, there isn't even a line break between each example, which means the description of the next example blends into the parameters of the previous example
    • No colour distinction between the parameters of the example and its description

Multiple ArgActionType attributes or multiple Types in the attribute

I would like to see support for the deferred action methods to be placed in multiple, separate classes which should promote better separation of concerns and cleaner code.

In other words doing this:

[ArgActionType(AActions)]
[ArgActionType(BActions)]
public class MainActions { ... }

public class AActions { ... }
public class BActions { ... }

Or allow for multiple types in a single attribute such as:

[ArgActionType(AActions, BActions)]
public class MainActions { ... }

public class AActions { ... }
public class BActions { ... }

Action framework usage displaying incorrectly.

This seems to be an issue with 1.7.0.0 and 1.8.0.0.

I tested a usage message generated from the code given in the Advanced Example of the readme and this was the output.

Usage: ConsoleApplication22 options

   OPTION        TYPE         POSITION   DESCRIPTION                                     
   -action       string*      0          Either encode or clip                           
   -encodeargs   encodeargs   NA         Encode a new video file                         
   -clipargs     clipargs     NA         Save a portion of a video to a new video file   

   EXAMPLE: superencoder encode fromFile toFile -encoder Wmv
   Encode the file at 'fromFile' to an AVI at 'toFile'

The expected output (and how it functions in 1.6.0.0) is

Usage: ConsoleApplication22 <action> options

EXAMPLE: superencoder encode fromFile toFile -encoder Wmv
Encode the file at 'fromFile' to an AVI at 'toFile'

Actions:

encode - Encode a new video file

   OPTION          TYPE      POSITION   DESCRIPTION                                                    
   -source (-s)    string*   1          The source video file                                          
   -output (-o)    string    2          Output file.  If not specfied, defaults to current directory   
   -encoder (-e)   encoder   NA         The type of encoder to use                                     


clip - Save a portion of a video to a new video file

   OPTION           TYPE      POSITION   DESCRIPTION                                                    
   -source (-so)    string*   1          The source video file                                          
   -output (-ou)    string    2          Output file.  If not specfied, defaults to current directory   
   -from (-f)       double    NA         The starting point of the video, in seconds                    
   -to (-t)         double    NA         The ending point of the video, in seconds                      
   -encoder (-en)   encoder   NA         The type of encoder to use                                     

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.