Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid error when parsing a sub command with only one parameter fixed with the 'ExactlyOnce' attribute. #216

Open
bryjen opened this issue Dec 27, 2023 · 2 comments

Comments

@bryjen
Copy link

bryjen commented Dec 27, 2023

Description

Attempting to parse a sub command with only one parameter fixed with the ExactlyOnce attribute errors even though the correct format is provided.

Repro steps

open Argu

type Arguments =
    | [<First; CliPrefix(CliPrefix.None)>] Item_Group of ParseResults<SubArguments>
    
    interface IArgParserTemplate with
        member this.Usage =
            match this with
            | Item_Group _ -> "Specifies an item group."
            
and SubArguments =
    | [<ExactlyOnce>] Path of path:string list
    
    interface IArgParserTemplate with
        member this.Usage =
            match this with
            | Path _ -> "Some path."

Expected behavior

Suppose we have the following main function and input as the program args: item-group --path elem1 elem2

[<EntryPoint>]
let main argv =
    let parser = ArgumentParser.Create<Arguments>(errorHandler = ExceptionExiter())
    
    try
        let parseResults = parser.ParseCommandLine () 
        printfn $"%A{parseResults}"
    with
        | ex -> printfn $"%s{ex.Message}"
    0

Since only one of the path parameters was provided, it is expected to parse normally with the following output:

[Item_Group [Path ["elem1"; "elem2"]]]

Actual behavior

Instead, it errors, indicating that the '--path' parameter is missing despite being provided.

ERROR: missing parameter '--path'.
USAGE: ConsoleApplication.exe item-group [--help] --path [<path>...]

OPTIONS:

    --path [<path>...]    Some path.
    --help                display this list of options.

It has the following stack trace:

   at Argu.ExceptionExiter.Argu.IExiter.Exit[a](String msg, ErrorCode errorCode) in /_//src/Argu/Types.fs:line 63
   at Argu.ArgumentParser`1.ParseCommandLine(FSharpOption`1 inputs, FSharpOption`1 ignoreMissing, FSharpOption`1 ignoreUnrecognized, FSharpOption`1 raiseOnUsage) in /_//src/Argu/ArgumentParser.fs:line 140
   at Program.main(String[] argv) in C:\###\fsharp_proj\src\ConsoleApplication\Program.fs:line 24

Known workarounds

Maintaining the condition of having the parameter be provided exactly once, a work around is by having another hidden parameter like so:

// ...
and SubArguments =
    | [<Hidden>] Hidden
    | [<ExactlyOnce>] Path of path:string list
    
    interface IArgParserTemplate with
        member this.Usage =
            match this with
            | Hidden -> "" 
            | Path _ -> "Some path."

Parsing then works as expected, with the constraint (exactly once) being enforced as well.

Related information

  • Bug seems to be independent of the type of the parameter (errors for ints, bools, strings, etc. + lists).
  • Bug seems to be a problem with attributes.
    | Path of path:string list    // Does NOT error
    | [<Unique>] Path of path:string list    // Does NOT error
    | [<Mandatory>] Path of path:string list    // ERRORS
    | [<Mandatory; Unique>] Path of path:string list    // ERRORS
  • Win 11 (Version 22H2)
  • Argu Release 6.1.4
  • .NET 7.0.401
@bryjen bryjen changed the title Invalid 'Argu.ArguParseException' when parsing a sub command with only one parameter fixed with the 'ExactlyOnce' attribute. Invalid error when parsing a sub command with only one parameter fixed with the 'ExactlyOnce' attribute. Dec 27, 2023
@bartelink
Copy link
Member

An alternate approach is to specify that a Path is a string, and use GetResults to combine the instances into a list (I rarely use lists for things that I need 0/1 of - preferring to declare it in the singular, and then use Contains, TryGetResult, GetResult(x, defVal), GetResult(x, defThunk) to extract.

In that case, you'd be saying that Path is Mandatory. The fun bit is that this does not work... I've always used Unique as a workaround (to stop you supplying more than one). If you fancy having a go at fixing it, the simplest thing to do might be to figure out how to make Mandatory work correctly for subcommands.

I believe what will work is to declare it as a string, and then use PostProcessResults to error if you've the wrong number of items, i.e. parseResults.PostProcessResults(Path, fun xs -> xs |> List.exactlyOne)

@bartelink
Copy link
Member

bartelink commented Feb 18, 2024

@brygen were you able to get something that works your side?


I feel that expecting these semantics for list types is debatable - ultimately the main way that the syntax works is that you specify per item whether it is Mandatory or Unique or leave it. Then you can use Conttains, GetResult, TryGetResult, GetResults, to check/extract/get the group.
As such, I feel it might be best to close it as long as you have a way you can make your scenario work?
(Alternately, someone will need to spec out the exact behavior change and do a PR - having this languish unactionable and/or unactioned for ages helps nobody...)
Having thought more about it, my initial thought was incorrect - the tutorial uses string list types, so it's a first class feature that should work (i.e. having to do --path one --path two is not the same as being able to spec --paths one two, even if it winds up identical from the consumption point of view give or take a collect operation to flatten the list)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants