jpdillingham / utility.commandline.arguments Goto Github PK
View Code? Open in Web Editor NEWA C# .NET class library containing tools for parsing the command line arguments of console applications.
License: MIT License
A C# .NET class library containing tools for parsing the command line arguments of console applications.
License: MIT License
/
) as an argument delimiter is disabled by default due to cross-platform compatibility issues. Projects needing to retain the previous behavior should set Arguments.EnableForwardSlash = true
prior to invoking Populate() or Parse().The existing Parse()
has been replaced with three overloads.
Old:
public static Arguments Parse(string commandLineString = default(string), Type type = null, [CallerMemberName] string caller = default(string)) {}
New:
public static Arguments Parse(Action<ArgumentParseOptions> configure = null) {}
public static Arguments Parse(string commandLineString, Action<ArgumentParseOptions> configure = null) {}
public static Arguments Parse(string commandLineString, ArgumentParseOptions options) {}
The caller
argument wasn't doing anything in the first place, and the type
argument is now specified through the TargetType
property of ArgumentParseOptions
.
ArgumentParseOptions
contains the following properties:
TargetType
-- same functionality as the previous type
parameterCombineAllMultiples
-- combines repeated argument values into a listCombinableArguments
-- if CombineAllMultiples
is false, combines the specified arguments into a listIf TargetType is not supplied, or repeated arguments don't match a property in the type, the behavior depends on the other options for multiples, either everything or just those specified.
Presently, something like foo /bar.txt
is supported. This breaks when parsing /a /b /c
into three boolean parameters, so support for this needs to be removed from the regular expression.
Following this change, any values beginning with a forward slash will need to be quoted.
I believe #55 introduced an additional bug where arguments that begin with a non-alpha character .~
followed by a forward slash /
are not captured and are parsed as empty strings.
This does not change whether quoted or not.
Given an Argument like:
[Argument('f', "folder", "The folder location for some thing.")]
private static string FolderPath { get; set; }
These will fail to populate the FolderPath
property:
dotnet someapp.dll -f "../../path/in/parent"
dotnet someapp.dll -f ../../path/in/parent
dotnet someapp.dll -f "~/path/from/root"
dotnet someapp.dll -f ~/path/from/root
Environment Details:
.NET version: dotnet core 2.2.207
Repro OS: macOS 10.14.6
PackageVersion: 3.0.1
I'm writing a program that uses a lot of arguments and I can't map a short argument name to each of them.
If I try to define multiple arguments like this
[Argument(' ', "mykey", "Description")]
the parser seems to only store the first argument. Is there something I'm missing or is this not implemented?
Very crisp package. Thanks.
Wondering if there is a straight forward way to support subcommands as in:
git clone
git pull
git push
I tried to use the Operand feature but it appears that:
myprog begin start end -f 3.4
does not recognize "begin" as an operand.
However changing the usage to:
myprog -f 3.4 begin start end
does recognize begin as an operand.
Currently, Populate()
sets all argument properties to null
prior to populating values from arguments. This prevents default values from being specified like so:
/// <summary>
/// Gets or sets the value of the Int argument.
/// </summary>
[Argument('i', "integer", "Gets or sets the value of the Int argument.")]
private static int Int { get; set; } = 42
It does this by invoking ClearProperties()
as the first step, which sets the value of each argument property to null
via reflection (which, as a separate issue, likely doesn't work as intended for non-nullable types).
A simple fix which avoids a breaking change to the api is to add an optional parameter to Populate()
which controls whether ClearProperties()
is invoked prior to population. This new parameter would be added to the various overloads as optional with the name clearExistingValues
and defaulted to false
.
The proper way to execute a .NET Core console application with application arguments is as so:
dotnet run [dotnet args] -- [application args]
The code should begin parsing after the first explicit operand --
, but somehow the name of the executable file is being added as an application operand. Investigate and suppress this.
If an Argument
's value start with (e.g.) a period or backslash, like a path would, the value is not parsed correctly.
Example:
using System;
using System.Linq;
using Utility.CommandLine;
namespace Foo
{
class Program
{
[Argument('f', "folder")]
private static string Folder { get; set; }
[Operands]
private static string[] Operands { get; set; }
static void Main(string[] args)
{
Arguments.Populate();
Console.WriteLine($"Folder='{Folder}'");
Console.WriteLine($"Operands='{String.Join(';', Operands.Skip(1))}'");
}
}
}
PM> .\foo.exe
Folder=''
Operands=''
PM> .\foo.exe -f
Folder=''
Operands=''
PM> .\foo.exe -f a.txt
Folder='a.txt'
Operands=''
PM> .\foo.exe -f .\a.txt
Folder=''
Operands='.\a.txt'
PM> .\foo.exe -f ..\a.txt
Folder=''
Operands='..\a.txt'
PM> .\foo.exe -f \path\to\a.txt
Folder=''
Operands='\path\to\a.txt'
PM> .\foo.exe -f \\server\path\to\a.txt
Folder=''
Operands='\\server\path\to\a.txt'
I see it is possible to have arguments like
-a
with no value, but how does the code know the input is -a with no value?
[Argument('d', "duplicates")]
private static bool AllowDuplicates { get; set; }
-a text
Results in an ArgumentException
at runtime. The Parse()
method correctly treats -a text
as an argument pair because it is agnostic about the type of a
, however when propagating the argument dictionary to properties the code should check the type and split this pair into a boolean and an operand.
Per the documentation, this is supposed to work, but it doesn't.
Here's a failing test:
[Fact]
public void ParseMultiples()
{
Dictionary<string, object> test = Arguments.Parse("--test 1 --test 2").ArgumentDictionary;
Assert.NotEmpty(test);
Assert.Single(test);
Assert.Equal("1,2", test["test"]);
}
This is working as expected when you consider this line. It needs to be changed to append additional values if the key is already present.
I'll have to think about whether this will cause any breaking changes.
/
) must now be enclosed in quotes. Unquoted values are interpreted as additional arguments.As with #43, anywhere in the code that inspects the stack to infer the Type of the calling method to determine argument info is broken when called from async methods due to some things that get inserted under the hood.
This issue was partially fixed in #40; this fix should made generic and used in place of current stack inspection.
Extensions
is a common name, and will cause conflicts when added to source directly. Rename to ArgumentExtensions
or similar.
Consider adding a case insensitive enum parse for arguments backed by enum typed properties.
This issue will require a fairly significant rewrite of the Populate()
method, as well as the GetArgumentProperties()
method. The Parse()
method should remain unchanged; it is designed to parse arguments as presented and has no concept of short or long argument names.
Arguments requiring multiple values are an edge case, and arguments requiring multiple values while also allowing the user to freely switch between short and long names in the same argument string are an extreme edge case.
I have no plans to implement this functionality at this time, however if someone would like to take it on I've included failing tests for array and List backed properties.
/// <summary>
/// Tests the <see cref="Utility.CommandLine.Arguments.Populate(Type, string)"/> method with an explicit string
/// containing multiple instances of a list-backed argument, and containing a change from short to long names and back.
/// </summary>
[Fact]
public void PopulateWithNameChange()
{
Exception ex = Record.Exception(() => CommandLine.Arguments.Populate(GetType(), "-l one --list two -l three"));
Assert.Null(ex);
Assert.Equal(3, List.Count);
Assert.Equal("one", List[0]);
Assert.Equal("two", List[1]);
Assert.Equal("three", List[2]);
}
/// <summary>
/// Tests the <see cref="Utility.CommandLine.Arguments.Populate(Type, string)"/> method with an explicit string
/// containing multiple instances of an array-backed argument, and containing a change from short to long names and back.
/// </summary>
[Fact]
public void PopulateWithNameChange()
{
Exception ex = Record.Exception(() => CommandLine.Arguments.Populate(GetType(), "-a one --array two -a three"));
Assert.Null(ex);
Assert.Equal(3, Array.Length);
Assert.Equal("one", Array[0]);
Assert.Equal("two", Array[1]);
Assert.Equal("three", Array[2]);
}
Configuration might look like this:
[Argument('p', "password", enVar: "MYAPP_PASSWORD")]
private static string Password { get; set; }
Alternatively a breaking change can be introduced to insert the enVar
argument before helpText
, which would be more natural.
Environment variable values would be populated only if the argument is not present in the command line arguments.
If you have a command line like this --file=/mnt/data/test.xml
the file argument will be empty. The ArgumentRegEx
finds only the key but not the value. You can find an example here: https://regex101.com/r/SGsVEg/2
When reading an argument of which the value starts with /
, an empty string is returned. This is critical as I can't take in absolute file paths without this.
System.ArgumentException: 'Specified value '' for argument 'max' (expected type: System.Double). See inner exception for details.'
Inner exception:
FormatException: Input string was not in a correct format.
[Argument('1', "max", "max")]
private static double max { get; set; }
[Argument('2', "max_minus", "max minus")]
private static double max_minus { get; set; }
[Argument('3', "min", "min")]
private static double min { get; set; }
[Argument('4', "min_minus", "min minus")]
private static double min_minus { get; set; }
Gonna take a whack at this by upgrading the lib to .net standard and creating a core and non core app to prove interop.
In release 1.1.1 there is a null reference error. This was working in release 1.0.1.
The Method GetOperandsProperty fails if there are no Operands variables defined.
ClearProperties()
sets the value of each property to null
. Investigate the behavior of this for non nullable types and perhaps write some tests to verify. The desired behavior is to set nullable properties to null
and all others to default(Type)
GetArgumentHelp()
and ArgumentHelp
are deprecated in favor of GetArgumentInfo()
and ArgumentInfo
. The new functionality retains everything and adds Property
to return the backing PropertyInfo
.ArgumentDictionary
property of Arguments
now combines repeated short and long argument names into a single key of the name which appears first, but only if a Type is supplied to Parse()
.
Parse()
, all arguments are assumed to be single value (non-collection) Types, and combining multiple arguments of the same name will result in only the last value being retained.ArgumentList
property has been added to Arguments
to provide the full, individual list of parsed arguments with order preserved.The introduction of async Main() has changed the makeup of the stack, which the application relies on to find the target Type when one is not specified in Populate()
.
As a temporary workaround, use Populate(typeof(<class containing main()>))
See #41
Version: 6.0.0
When passing an argument that begins with a tilde, the argument is not populated to the attributed variable. This would be a common case in non-windows environments to signal relative-to-user paths.
Ex: dotnet run -file "~/data/path/to/file"
There is a way to create a help automatically?
Thank's
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.