Coder Social home page Coder Social logo

jpdillingham / utility.commandline.arguments Goto Github PK

View Code? Open in Web Editor NEW
118.0 13.0 21.0 260 KB

A C# .NET class library containing tools for parsing the command line arguments of console applications.

License: MIT License

C# 98.47% Shell 1.53%
csharp command-line command-line-parser commandline dotnet arguments-parser flags

utility.commandline.arguments's People

Contributors

branc116 avatar crahungit avatar dependabot[bot] avatar jpdillingham avatar juliogold avatar matthiasjentsch avatar montgomerybc avatar wburklund 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

utility.commandline.arguments's Issues

Breaking changes for v5.0.0

  • Forward slash (/) 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().

Breaking changes for v6.0.0

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 parameter
  • CombineAllMultiples -- combines repeated argument values into a list
  • CombinableArguments -- if CombineAllMultiples is false, combines the specified arguments into a list
    If TargetType is supplied and repeated arguments match a property in the type, the behavior remains the same; if the backing type of the property is a collection, values are combined, otherwise they are overwritten.

If 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.

Remove support for unquoted forward slashes as values

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.

Arguments starting with non-Alpha characters followed by Forward slashes are dropped completely

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

Arguments using only long name?

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?

Subcommad support

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.

Add support for default values

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.

With .NET Core the executable is identified as an operand

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.

Argument values starting with non-word-characters are treated as operands

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'

Documentation oversight

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?

Improper value propagation when a boolean flag is followed by an operand

        [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.

Parse() dos not capture multiple values for repeated arguments

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.

Breaking changes for v4.0.0

  • Argument values beginning with a forward slash (/) must now be enclosed in quotes. Unquoted values are interpreted as additional arguments.

Rename Extensions

Extensions is a common name, and will cause conflicts when added to source directly. Rename to ArgumentExtensions or similar.

Support enums

Consider adding a case insensitive enum parse for arguments backed by enum typed properties.

Properties backing arguments with multiple values not populated properly if short and long argument names are specified

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.

Failing Tests

TestClassWithListProperty

/// <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]);
}

TestClassWithArrayProperty

/// <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]);
}

Allow properties to be populated from environment variables

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.

Trying to use negative double value cause Exception

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; }

.net core support

Gonna take a whack at this by upgrading the lib to .net standard and creating a core and non core app to prove interop.

Null reference error

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.

Breaking changes for v3.0.0

  • GetArgumentHelp() and ArgumentHelp are deprecated in favor of GetArgumentInfo() and ArgumentInfo. The new functionality retains everything and adds Property to return the backing PropertyInfo.
  • The 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().
    • For example, if 'a' and "abc" were the short and long names for an argument, and if the backing Type for this argument was a collection, and "-a foo --abc bar" were supplied on the command line, the resulting dictionary entry would have key "a" and values "foo, bar". If the first argument was "abc" instead of "a", the dictionary key would be "abc".
    • For repeated arguments not backed by a collection Type, the value of the dictionary entry will be the last value appearing in the command line string.
    • If a Type is not supplied to 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.
  • The ArgumentList property has been added to Arguments to provide the full, individual list of parsed arguments with order preserved.

Populate() does not work with .NET Core 2.x

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()>))

Argument starting with tilde (~) are not captured and left blank

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"

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.