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

@Argument(parsing:) can't properly handle arguments outside the leaf subcommand when using @OptionGroup to access the parent #658

Open
2 tasks done
porglezomp opened this issue Aug 5, 2024 · 1 comment

Comments

@porglezomp
Copy link

porglezomp commented Aug 5, 2024

I have a CLI with two specific constraints:

  • The top-level command has an argument that is passed before the subcommand name (to provide a uniform context). Doing this with @OptionGroup on the parent CLI type.
  • Some subcommands want to collect free-form arguments so they can do more precise parsing (collecting related, order-sensitive flags) using @Argument(parsing: .allUnrecognized).

When set up this way, Swift argument parser starts producing bogus errors when the command line is used correctly.
And further, when used incorrectly, the error messages don't reflect what's actually wrong.

ArgumentParser version: 1.5.0 and main
Swift version: Swift 5.11, multiple Swift 6 betas and pre-release toolchains.

Checklist

  • If possible, I've reproduced the issue using the main branch of this package
  • I've searched for existing GitHub issues

Steps to Reproduce

It reproduces with this sample tool:

import ArgumentParser

@main
struct CLI: ParsableCommand {
    @Argument
    var context: String?

    static let configuration = CommandConfiguration(subcommands: [Command.self])

    struct Command: ParsableCommand {
        @OptionGroup
        var outer: CLI

        @Option
        var inner: Int?

        @Argument(parsing: .allUnrecognized)
        var query: [String]

        mutating func run() throws {
            if let context = outer.context {
                print("Context: \(context)")
            } else {
                print("No context set")
            }
            if let inner {
                print("Inner: \(inner)")
            }
            print("Query: \(query)")
        }
    }
}

Behavior

Running:

$ swift run -- cli context-here command --include hello --inner 123 --exclude hi

Should output:

Context: context-here
Inner: 123
Query: ["--include", "hello", "--exclude", "hi"]

But instead fails with:

Error: Unknown option '--include'
Usage: cli command [<context>] [--inner <inner>] <query> ...
  See 'cli command --help' for more information.

And running:

$ swift run -- cli context-here command --include hello --inner 123 --exclude hi

Should output:

Context: context-here
Inner: 123
Query: ["--exclude", "hi"]

But instead fails with:

Error: Missing expected argument '<query> ...'
Help:  <query>  
Usage: cli command [<context>] [--inner <inner>] <query> ...
  See 'cli command --help' for more information.

In both cases, the query fails to parse in different ways.

Variations

The "Missing expected argument ' ...'" is caused by query being required.
If you make it var query: [String] = [] then it doesn't produce that kind of error, it's always "Unknown option".

If you comment out the @OptionGroup var outer: CLI, then you can use the command line properly.
However, if you forget to pass the context argument on the outer CLI, like:

$ swift run -- cli command --inner 123 --exclude hi

Then you get the unhelpful error message:

Error: Unknown option '--inner'
Usage: cli [<context>] <subcommand>
  See 'cli --help' for more information.

indicating that it just treated command as the context argument. It should probably give an error about the missing subcommand? But also, context is optional here, so it should maybe be treating the command as the subcommand? That might be more context-sensitive than the parsing is intended to provide though.

If you use @Argument(parsing: .postTerminator), things work correctly, but you're forced to move all the flexible arguments to the end past the -- terminator, which is dramatically worse UX.

porglezomp added a commit to porglezomp-misc/swift-argument-parser that referenced this issue Aug 5, 2024
These tests are very poorly named and aren't necessarily in the right
file.
porglezomp added a commit to porglezomp-misc/swift-argument-parser that referenced this issue Aug 5, 2024
These tests are very poorly named and aren't necessarily in the right
file. They should be re-written with the actual fix.
@porglezomp
Copy link
Author

porglezomp commented Aug 5, 2024

I put up a commit here with tests for the behavior I expect—they're messy tests and should be re-written but should give an easier reproducer for what I'm running into here. porglezomp-misc@5e37672

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

1 participant