nemec / clipr Goto Github PK
View Code? Open in Web Editor NEWCommand Line Interface ParseR for .Net
License: MIT License
Command Line Interface ParseR for .Net
License: MIT License
When building error messages during validation it may be helpful to refer to arguments by name in order to explain why parsing failed. Example:
Cannot use both -d/--delete and --move at the same time.
We already have the ability to refer to a strongly-typed name and value (e.g. nameof(opt.Delete)
and opt.Delete
), but the flags must be hardcoded. If we could pull them from the config, whenever it changes the validation message would automatically match.
Some notes:
config.GetArgumentFlags<Options>(opt => opt.Delete) == "-d/--delete"
Provide the ability to prompt for input when a named parameter that expects a value doesn't see one. Specifically, this can be used to allow a password to be input into the application without it appearing in the console history. Additionally, allow the input to be "masked" so it doesn't show on the screen either.
$ myapp.exe --user nemec --password SomePass12
Logged in!
$ myapp.exe --user nemec --password
Please type a password:
Logged in!
List values, used with the ParseAction.Append
, should support more delimiters. Right now, only whitespace works. I'd like to suggest that commas and semicolons are valid delimiters as well.
--listOfArguments a b c d
--listOfArguments a,b,c,d
--listOfArguments a;b;c;d
--listOfArguments a;b c,d
All four examples should add "a", "b", "c" and "d" to the backing list-property.
Note: If the argument is surrounded with quotation marks, it should not be treated as a delimited value.
--listOfArguments a;"b;c",d // Should be "a", "b;c" and "d".
--listOfArguments "a b c d" // Should be a single value.
--listOfArguments a,'b c';d // Should be "a", "b c" and "d".
It would be good if both single- and double quotes are supported, but I think it is a reasonable requirement to not allow mixed quotation marks, e.g. "a b'
.
I have a program that I have hardcoded some options into, including a couple of Boolean ones. I would like to be able to prompt the user for additional options, including the ability for them to undo options that were set by default. I had hoped to be able to just append user input to the string I hardcoded prior to calling CLIPR. To do this, I would need them to be able to specify options such as:
--whatIf false
or
--whatIf-
...in order to negate the "--whatIf" I had already included in the string. Is there a way to do this? Is this something that has come up before?
As the title says, allow help data to be configured by resx files. It doesn't make sense to configure option names themselves from the resx files though.
In multitenant systems (like a chatroom) it's possible you could build one command line system and want to restrict access to certain parameters/verbs based on users or roles. With the work in 2.x to make each parser resuable, it may be possible to add a permissions system around each call to parse.
Presently when a --arg value can not be converted, e.g. "2x" can not be converted to an int the message does not show the user which switch the error related to, e.g:
Value "2x" cannot be converted to the required type System.Int32.
It would be better for users to be shown which of the values they typed in is wrong, e.g:
Argument --size value "2x" cannot be converted to the required type System.Int32.
Unless I misunderstood the differences between PositionalArguments and NamedArguments, you can currently not have a NamedArgument and determine its position in the list of command line arguments.
This would be useful as you could allow different commands to be executed in a certain order without being restricted by the Verbs system (e.g. prog.exe --load-config default.cfg --open-file test.txt
).
Similar use case is shown in FFMPEG's argument system, where you refer to input media files by their order in the argument list, or certain flags only apply to the previous input.
Although untested, this may be worked around by implementing setters of the argument's properties which then increment a counter for determining the order. In that case, the parsing order will have to be clearly defined.
Thanks for your hard work,
RedMser
Right now the line width is configured to 80 characters, but users may grow or shrink their terminal as needed. clipr should automatically adapt to that setting, falling back to 80 characters only if a terminal is not available.
Additionally, allow devs to tell whether or not a verb was invoked if they wish to display the error message manually. Example:
var parser = new CliParser<Options>();
try
{
parser.Parse(args, opt)
.GetValueOrThrow();
}
catch (AggregateException e)
{
var help = new clipr.Usage.AutomaticHelpGenerator<Options>(); // Would like to use Options or Verb here
Console.Error.WriteLine(help.GetHelp(parser.BuildConfig()));
return;
}
Instead of requiring a default constructor, add the ability to initialize verbs from IoC containers and other factories.
You talk about help generation all over the place but you never mention any related code. Not in the readme, and not in the sample.
Please include an actual example of a Main method that contains all the features - parsing and help output and error output.
Thanks for building this library. It's very well done and has been really helpful for me in building some command line tools as part of our product. The one issue that I have is related to the license. While it appears to be a very simple, permissive license, management types tend to be more comfortable when a library or tool like this uses a standard, well-known license (MIT, Apache, etc.). :-)
Is this license copied from one of the more well-known OSS licenses, and if so can it be changed to reflect that?
class NoDescription
{
[NamedArgument("name")]
public string Name = null;
}
class Help: AutomaticHelpGenerator<NoDescription>
{
public Help()
{
ShortName = null;
LongName = "help";
}
}
[TestMethod]
[ExpectedException(typeof(ParserExit))]
public void Help_WithNoDescription_NoNullPointer()
{
var args = "--help".Split();
var opt = new NoDescription();
var parser = new CliParser<NoDescription>(
opt,
ParserOptions.None,
new Help());
parser.Parse(args);
}
pull request to follow...
Add support for auto-complete in the major terminal emulators (Powershell, Bash). May have to be a separate tool integrated into the build process since the completion scripts are often separate files.
https://blog.jcoglan.com/2013/02/12/tab-completion-for-your-command-line-apps/
http://stackoverflow.com/questions/25823910/pscmdlet-dynamic-auto-complete-a-parameter-like-get-process
http://stackoverflow.com/questions/33132028/powershell-binary-module-dynamic-tab-completion-for-cmdlet-parameter-values
The current first example:
var opt = CliParser.Parse<Options>("-vvv output.txt 1 2 -1 7".Split());
fails with the message: "Additional information: Extra positional arguments found: -1 7"
[PositionalArgument(1, MetaVar = "N",
NumArgs = 1,
Constraint = NumArgsConstraint.AtLeast,
Description = "Numbers to sum.")]
public List<int> Numbers { get; set; }
"-1" is treated as a new argument. I'm not inputting negative numbers as positionals, so I'll move on. But I thought I would point out this interesting problem.
David
I'd like to build a command line application where all the arguments aren't necessarily known by the main parser. The idea being to allow for extensions which may define their own arguments. Something akin to argparse's subparser functionality.
One approach would be a parser option to ignore unknown arguments. Parse everything per usual, but when the destination object doesn't have an argument defined just ignore it instead of throwing an error. Then I just pass args to the extension and it can do the same to pull it's own arguments.
Hello Dan,
I've just tried your library and I like it, simple and elegant.
I have a special situation:
I have some executable with various options. I save a shortcut on desktop pointing to the executable and add the desired named arguments at the end of the target field in the shortcut definition. Now if I double click the shortcut it will properly execute with the specified named arguments.
I want to provide the user's the ability to drag and drop files on the shortcut. It works with a positional argument, but the NumArgs must be at least 1, so it's not 'optional'. I tried setting NumArgs=0, but understandably it throws an exception. What would you suggest to work around this?
[PositionalArgument(1, Constraint = NumArgsConstraint.AtLeast, NumArgs = 1)]
public List<string> Files { get; set; }
Thanks!
TB
There may be some scenarios where you want to warn the end user if they enter a dangerous value that they may not have indended, the canonical example being rm -rf / bin/someprog
and putting an extra space in there.
Today the rm
program uses an extra parameter (--no-preserve-root
) to allow that value, but it also terminates the program immediately with an error. This feature will offer devs the option to choose "warn", "error", or custom behavior. To get the behavior used by rm
today, you would have to implement your own post-parse validation.
Allow -c true
or -c false
to set the flag as true
or false
, respectively.
It also integrates well with scripts, who can simply feed a boolean value to the argument rather than conditionally include the flag.
Imagine --names
consumes "at least one" argument, --other
consumes one argument, and there is one final positional argument.
Both ./prog.exe --names Fred Jake --other value positional
and ./prog.exe --names Fred Jake positional
consume the later arguments as part of the --names
argument instead of leaving them for the other argument parameters.
The way the mutually exclusive groups are made is rather unintuitive and, I must say, inconvenient.
If I have mutually exclusive groups of arguments, the amount of groups I would need to create would grow exponentially.
Let's say, I have my app.exe
, which either loads its config from a supplied file path or gets all its arguments from command line. As I understand, currently I would need to write it like this:
[ApplicationInfo(Name = "app")]
class CLIOptions
{
[MutuallyExclusiveGroup("file1")]
[NamedArgument('a', "ServerAddress", Required = true)]
public string ServerAddress { get; set; }
[MutuallyExclusiveGroup("file2")]
[NamedArgument('u', "Username", Required = true)]
public string Username { get; set; }
[MutuallyExclusiveGroup("file3")]
[PromptIfValueMissing(MaskInput = true)]
[NamedArgument('p', "Password", Required = true)]
public string Password { get; set; }
[MutuallyExclusiveGroup("file1")]
[MutuallyExclusiveGroup("file2")]
[MutuallyExclusiveGroup("file3")]
[NamedArgument('f', "File", Constraint = NumArgsConstraint.Optional, Const = "settings.json", Required = true)]
public string SettingsFile { get; set; }
}
Actually, even this wouldn't work, I still didn't figure out how to do it properly.
A much more sensible way would be to have mutually exclusive groups of arguments (and some way of letting the parser know which groups are mutually exclusive):
[ApplicationInfo(Name = "app")]
class CLIOptions
{
[MutuallyExclusiveGroup("cli")]
[NamedArgument('a', "ServerAddress", Required = true)]
public string ServerAddress { get; set; }
[MutuallyExclusiveGroup("cli")]
[NamedArgument('u', "Username", Required = true)]
public string Username { get; set; }
[MutuallyExclusiveGroup("cli")]
[PromptIfValueMissing(MaskInput = true)]
[NamedArgument('p', "Password", Required = true)]
public string Password { get; set; }
[MutuallyExclusiveGroup("file")]
[NamedArgument('f', "File", Constraint = NumArgsConstraint.Optional, Const = "settings.json", Required = true)]
public string SettingsFile { get; set; }
}
Parser would require that either options from group cli
or from group file
would be present.
StrictParse method does not use set custom help generator. They always instanciate a new AutomaticHelpGenerator.
Using Static Enumerations work fine for distinct values, but they don't work with list values.
Argument -c value "Red" cannot be converted to the required type System.Collections.Generic.IList
1[StaticEnumerationList.ColorEnum].`
I would like to be able to do something like this:
internal class Program
{
private static void Main(string[] args)
{
var options = CliParser.StrictParse<Options>(args);
foreach (var color in options.Colors)
Console.WriteLine("Color: {0}", color);
Console.ReadLine();
}
}
internal class Options
{
[NamedArgument('c', "colors", Action = ParseAction.Append, Constraint = NumArgsConstraint.AtLeast, Const = 1)]
public IList<ColorEnum> Colors { get; set; }
}
public enum Color
{
Red,
Green,
Blue
}
[StaticEnumeration]
internal abstract class ColorEnum
{
public static readonly ColorEnum Red = new EnumValue(Color.Red);
public class EnumValue : ColorEnum
{
public EnumValue(Color value)
{
Value = value;
}
public Color Value { get; private set; }
}
}
When there is a [PositionalArgument] with NumArgsConstraint.AtLeast -- is ignored when an argument that looks like a switch is encountered.
Test case:
internal class PositionalMultipleValuesDelimiter
{
[PositionalArgument(1, NumArgs = 1, Constraint = NumArgsConstraint.AtLeast)]
public List<string> Names { get; set; }
}
[TestMethod]
public void PositionalArguments_WithPositionalDelimiter_ParsesCorrectly()
{
var expectedNames = new List<string> {"Nancy", "--Rick", "Tim"};
var opt = CliParser.Parse<PositionalMultipleValuesDelimiter>(
"-- Nancy --Rick Tim".Split());
CollectionAssert.AreEqual(expectedNames, opt.Names);
}
pull to follow...
internal class NullUsageAndVersion
{
[PositionalArgument(0)]
public string Value { get; set; }
}
[TestMethod]
public void ParseArgument_Space()
{
var args = CliParser.Parse<NullUsageAndVersion>(new[] { " " });
Assert.AreEqual(" ", args.Value);
}
Solution: remove ParsingContext.cs, line 81: arg = arg.Trim();
Why trim arguments? Maybe user want argument to start or end with spaces.
When a property has a reference or nullable type, it should be possible for the user to specify that the value is optional (zero or one arguments) without turning that property into a list.
This way you could have an option that enables a server on a given port:
class Options
{
[NamedArgument('s', "server", Constraint=NumArgsConstraint.Optional)]
public int Server { get; set; }
}
--server // default port
--server 1234 // set port
With a constraint like that, we should verify that NumArgs wasn't explicitly set too.
The UI can be localized into additional languages, but I need help in translating! If you're fluent in another language, please consider adding a translation. Translations are provided by the resx
format and to add a new language, simply make a copy of Resources.resx and rename it according to the language's two-letter ISO 639-1 code.
For example, a Spanish translation would be named Resources.es.resx
. If your country or region has a special way of translating a piece of text, add the ISO two-letter country code (in uppercase) as well: Resources.es-MX.resx
Visual Studio provides a convenient UI for editing resource files, but the XML itself can be modified on any platform.
Thank you!
Note: currently translated into three non-English languages, however as this project evolves there may be more English UI items added, which makes the translations incomplete/out of date. If you see anything missing, even in a language that has already been added, please feel free to make some updates.
Consider the following:
using System;
using clipr;
using clipr.Core;
internal class Options
{
[NamedArgument('a', "optiona", Required = true, Description = "The A option.")]
public string Optiona { get; set; }
[NamedArgument('b', "optionb", Required = false, Description = "The B option.")]
public string Optionb { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
Options options = new Options();
CliParser<Options> cliParser = new CliParser<Options>(options, ParserOptions.CaseInsensitive);
try
{
cliParser.Parse(args);
}
catch (ParserExit)
{
return;
}
catch (ParseException e)
{
Console.WriteLine(e.Message);
return;
}
}
}
Produces the following help output:
Usage: CliprRepro [ -h|--help ] [ --version ] -a|--optionaA [ -b|--optionb B ]
Required Arguments:
-a, --optiona The A option.
Optional Arguments:
-b, --optionb The B option.
-h, --help Display this help document.
--version Displays the version of the current executable.
Notice -a|--optionaA
versus -b|--optionb B
.
Hi,
I have two named arguments with attributes defined as
[NamedArgument('a', "ArgumentA",
Constraint = NumArgsConstraint.Optional,
Action = ParseAction.Store,
Const = "MyConstValueA",
Description = "Argument A")]
public string A{ get; set; }
[NamedArgument('b', "ArgumentB",
Constraint = NumArgsConstraint.Optional,
Action = ParseAction.Store,
Const = "MyConstValueB
Description = "Argument B")]
public string B{ get; set; }
Command line is: MyApplication.exe -a -b
When leaving both arguments empty -a is reading -b as its value.
After Parse: A = -b and B = MyConstValueB
Expected: A = MyConstValueA and B = MyConstValueB
Is this functionality intended or how can it be defined to work as expected
Thanks,
Uwe
Please consider providing NuGet packages with strong named assemblies.
The 'quick but powerful example of the objects this library enables you to build' on the project start page quits with a ParseException stating that 'extra positional arguments are found'...
That's using NuGet package. Didn't try building from code.
It's expected that, at some point, a user will add invalid input. Instead of throwing exceptions we should force the dev to handle them as part of parsing.
Instead of configuring the parser via attributes, it would be useful to be able to programmatically build the parser with a fluent interface. This will allow options to be added and removed dynamically and provide a code (instead of configuration) based interface for parsing.
There is some work already started on this (see clipr.Fluent
namespace) but it's still in alpha stage.
I've done work on the backend to separate parsing into two discrete steps: building a configuration and the act of parsing. There are some situations (such as a chat bot) where it would make sense to parse multiple inputs with the same config, but currently the 'object' is passed in the CliParser
constructor, meaning each parser can only write to one object!
I am building a demo project on Windows and get a runtime exception that I am not sure how to fix. Could you please take a look and guide me out?
Source
using System;
using clipr;
namespace CliprTest
{
public class Program
{
public static void Main(string[] args)
{
var parsed = CliParser.StrictParse<Options>(args);
Console.WriteLine("Ran without params first");
}
}
[ApplicationInfo(Description = "Test")]
public class Options
{
}
}
Ouput
soloy@DESKTOP-MRND0KT MINGW64 /c/GitHub/CliprTest/src/CliprTest
$ dotnet build && dotnet run bin/Debug/netcoreapp1.0/CliprTest.dll
Project CliprTest (.NETCoreApp,Version=v1.0) will be compiled because inputs were modified
Compiling CliprTest for .NETCoreApp,Version=v1.0
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:02.2712506
Project CliprTest (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
Unhandled Exception: System.Resources.MissingManifestResourceException: Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "clipr.Properties.Resources.resources" was correctly embedded or linked into assembly "clipr.NetCore" at compile time, or that all the satellite assemblies required are loadable and fully signed.
at System.Resources.ManifestBasedResourceGroveler.HandleResourceStreamMissing(String fileName)
at System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet(CultureInfo culture, Dictionary`2 localResourceSets, Boolean tryParents, Boolean createIfNotExists, StackCrawlMark& stackMark)
at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo requestedCulture, Boolean createIfNotExists, Boolean tryParents, StackCrawlMark& stackMark)
at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo culture, Boolean createIfNotExists, Boolean tryParents)
at System.Resources.ResourceManager.GetString(String name, CultureInfo culture)
at clipr.Usage.AutomaticHelpGenerator`1.get_UsageTitle()
at clipr.Usage.AutomaticHelpGenerator`1.GetUsage(IParserConfig config)
at clipr.CliParser`1.StrictParse(String[] args)
at clipr.CliParser.StrictParse[TS](String[] args)
at CliprTest.Program.Main(String[] args)
Currently when a trigger (like --help
or --version
) is provided, a ParserExit
exception is thrown but no context is provided on which trigger was hit. Similar to #31 we should return some context when a trigger is hit.
There are cases where an argument may need to begin with a dash, such as passing arguments which will be passed on to another command. Ex:
MyProgram.exe --forwardArgs "--flagToForward --otherFlag --etc"
Since adding the check for "ParamIsArgument()" to clipr the string of arguments to forward on will be assumed to be another option flag and not be recognized as an argument to the --forwardArgs option.
Fields are faster to type than properties and may be initialised with default values in the same place.
class Options
{
[NamedArgument('n', "name")]
public string Name = "default name";
[NamedArgument('v', "verbose", Action = ParseAction.StoreTrue, Description = "Verbose output.")]
public bool Verbose = false;
}
static void Main(string[] args)
{
var opt = new Options();
var parser = new CliParser<Options>(opt);
parser.StrictParse(args);
}
At the moment, Verb data is not included in the default help generator nor can "help" be invoked for an individual verb.
Arguments that are required are listed under the "Optional Arguments" listing in the help text.
[ApplicationInfo(Description = "This is a set of options.")]
public class Options
{
[NamedArgument('c', "confirm", Action = ParseAction.StoreTrue, Required = true,
Description = "Confirms that the action is intended.")]
public bool Confirmed { get; set; }
}
Executing without arguments:
MyApplication.exe
Usage: MyApplication [ -h|--help ] [ --version ] [ -c|--confirm ]
Required named argument(s) "Confirmed" were not provided.
Executing with -h flag:
MyApplication.exe -h
Usage: MyApplication [ -h|--help ] [ --version ] [ -c|--confirm ]
This is a set of options.
Optional Arguments:
-c, --confirm Confirms that the action is intended.
-h, --help Display this help document.
--version Displays the version of the current executable.
Required arguments ("confirm" in this example) should be listed under a "Required Arguments" heading, and also preferably indicated in the "usage"-line.
We should be able to print the parser help message in our code.
Like print_usage in Python (https://docs.python.org/2/library/optparse.html#other-methods)
Currently, variable length args (e.g. List<string>
) will attempt to store the argument value as a string (instead of an enumerable) which fails to convert unless you also explicitly set the Constraint
attribute property.
When clipr detects a variable length type (say, an IEnumerable<>
) it should automatically default the Constraint
to AtLeast
, the expected default value.
I need to evaluate whether the new handlers can support async methods/async Main. It would be nice to also check whether it can support async methods for verb parsing, post-parse handlers, and other places where users/devs can hook in to clipr.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.