public typealias SendableWritableKeyPath = Sendable & WritableKeyPath typealias ArgParserFun = @Sendable (Input) -> ParsedCliArgs protocol ArgParserProtocol: Sendable { associatedtype Input associatedtype Value associatedtype Root associatedtype Context var context: Context { get } var keyPath: SendableWritableKeyPath { get } var parse: ArgParserFun { get } } struct ArgParser: ArgParserProtocol { let keyPath: SendableWritableKeyPath let parse: ArgParserFun let context: Context init( _ keyPath: SendableWritableKeyPath, _ parse: @escaping ArgParserFun, context: Context, ) { self.keyPath = keyPath self.parse = parse self.context = context } init( _ keyPath: SendableWritableKeyPath, _ parse: @escaping ArgParserFun, ) where Context == (), Input == SubArgParserInput { self.init(keyPath, parse, context: ()) } init( _ keyPath: SendableWritableKeyPath, _ parse: @escaping ArgParserFun, argPlaceholderIfMandatory: String? = nil, ) where Context == PosArgParserContext, Input == PosArgParserInput { self.init(keyPath, parse, context: PosArgParserContext(argPlaceholderIfMandatory: argPlaceholderIfMandatory)) } } typealias PosArgParser = ArgParser struct PosArgParserContext { let argPlaceholderIfMandatory: String? } func dashDashArg(mandatory: Bool) -> PosArgParser { return ArgParser( \.noopKeyPath, { input in switch (input.arg, mandatory) { case ("--", _): .succ((), advanceBy: 1) case (_, false): .succ((), advanceBy: 0) case (_, true): .fail("Expected: --. Got: \(input.arg.singleQuoted)", advanceBy: 0) } }, context: PosArgParserContext(argPlaceholderIfMandatory: mandatory ? "--" : nil), ) } func newMandatoryPosArgParser( _ keyPath: SendableWritableKeyPath>, _ parse: @escaping @Sendable (PosArgParserInput) -> ParsedCliArgs, placeholder: String, ) -> PosArgParser> { let parseWrapper: @Sendable (PosArgParserInput) -> ParsedCliArgs> = { parse($0).map { .initialized($0) } } return ArgParser( keyPath, parseWrapper, context: PosArgParserContext(argPlaceholderIfMandatory: placeholder), ) } // todo reuse in config public func parseEnum(_ raw: String, _ _: T.Type) -> ResOrStr where T.RawValue == String, T: CaseIterable { T(rawValue: raw).toResult("Can't parse '\(raw)'.\nPossible values: \(T.unionLiteral)") } public func parseUInt32(_ str: String) -> ResOrStr { UInt32(str).toResult("Can't convert '\(str)' to UInt32") } func parseCardinalDirectionArg(i: PosArgParserInput) -> ParsedCliArgs { .init(parseEnum(i.arg, CardinalDirection.self), advanceBy: 1) } func parseCardinalOrDfsDirection(i: PosArgParserInput) -> ParsedCliArgs { .init(parseEnum(i.arg, CardinalOrDfsDirection.self), advanceBy: 1) } func upcastArgParserFun(_ fun: @escaping ArgParserFun) -> ArgParserFun { { fun($0).map(Optional.init) } }