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
3 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

Hello @nnnnnnnn do you have an example of that change you mentioned in 0.4.0 ?

I am using 1.2.3 now and when I have a var attribute that is not an @Option, I receive this error at the struct level : Type 'GenerateToken' does not conform to protocol 'Decodable'

Here is the offending line in my AsyncParsableCommand struct:

        // initialize logger
        var log : Logger = Logger(label: "generate-token")

For the Codable conformance to work properly, your Logger type needs to be Codable — is that the case?

Thank you for the quick response. Nope it is not. It is the logger provided by swift-log project. Any workaround to use types that are not Codable ?

Right now, I ended up putting my log at top-level, outside of the struct, but this is not ideal as it is shared by all structs in the file.

fileprivate var log = Logger(label: "generate-token")