How to exclude members from parsing

So ParsableArguments requires that all members use one of their property wrappers.

I'm trying to add defaulted properties that won't participate in the parsing.
In so far, I've used a wrapper that ignores decoding

@propertyWrapper
struct Undecoded<Value>: Decodable {
    var storage: Value?

    init() { }
    init(from decoder: Decoder) throws { }

    var wrappedValue: Value {
        get { storage! }
        set { storage = newValue }
    }
}

struct Command: ParsableCommand {
  @Argument() var foo: Int
  @Undecoded let unparsed: Int

  mutating func validate() throws {
    unparsed = 4
  }
}

Is there any caveat in doing this? Any idea on improving this?
Ideally I'd want to do:

struct Command: ParsableCommand {
  @Argument() var foo: Int
  let unparsed = 4
}

I could also separate the Parsing from the Running logic, but it doesn't seem to be what ArgumentParser is promoting.

Huh. I need to learn more about how Decodable synthesis works. Your ideal code actually works as is:

struct Guess: ParsableCommand {
  @Argument() var guess: Int
  let magic = 4
  
  func run() {
    if guess > magic { print("too high") }
    else if guess < magic { print("too low") }
    else { print("you got it!") }
  }
}
% guess 5
too high
% guess 3
too low

...but if I change the let to var, it tries to decode a value for the magic property, and fails:

% guess 5
Error: Missing expected argument
Usage: guess <guess>
1 Like

That us both.

Turns out the excluding members also need to be Decodable, despite not being used.

It’s probably because you have a let property with an initial value so it cannot be overwritten during decoding, so it gets skipped.

4 Likes

Anyway to achieve this with var variables?

What happens if you use a var?

Pretty much this:


As said, I've been using @Unparsed, but it's less than ideal.

So you want to use a var but don’t want it to be decoded?

Yes, it has a default value. Ideally I'd have

var a = 3

I also try to get to something like this

@Undecoded(initialValue: 3) var a: Int

but it doesn't seem to avoid the decoding phase used by arg-parser.

Maybe you can exclude it by specifying the CodingKeys explicitly?

struct DecodableThing: Decodable {
	var foo: Int = 2
	let bar: String

	private enum CodingKeys: String, CodingKey {
		case bar
	}
}

let jsonString = """
{ "foo": 1, "bar": "hello" }
"""

let decodedThing = try! JSONDecoder().decode(DecodableThing.self, from: Data(jsonString.utf8))
print(decodedThing.foo) // 2
2 Likes

That‘s what I mentioned on Slack as being very annoying. I‘m not sure this was intended.

Encountered this myself. Pretty nasty. The error message is totally unhelpful, so I spent a couple of hours trying to figure out what was wrong with my @Options and @Flags. Only when I started putting breakpoints in the ArgumentParser library itself, on the error message line, did I discover the problem was a var that was not even marked as a argument.

Even when you discover that problem, it is a bit nasty: you can change to a "let" property, but then the compiler throws up warnings about them being ignored during coding. If you use a var, argument parser doesn't work.

It would at least be very useful if the name of the non-argument property that is causing the issue were named in the error message. For some reason it doesn't print, leaving the user in the dark.

Hello interested parties — the newest release (0.4.0) includes a change that allows var properties that aren't declared with a parsing property wrapper. Please try it out and open an issue if you have any problems!

2 Likes
Terms of Service

Privacy Policy

Cookie Policy