use Misc (if-just-is, own, but-return) Cli a name Maybe StrView desc Maybe StrView fn mk(): Cli { name: None, desc: None } ShitStr = StrCase StrView (StrConcat StrView StrView) CommandDepth cmd ShitStr # XD outer Maybe (Ptr CommandDepth) fn nesting(x Ptr CommandDepth) nest = 0 cd = x while True case cd&.outer None return nest Just(outer) cd <= outer nest <= nest + 1 return unreachable() fn print-command-location(cd Ptr CommandDepth) case cd&.outer None Term.print(cd&.cmd) Just(outer) print-command-location(outer) Term.print(' \(cd&.cmd)') Error = StrConcat StrView StrView # geeg we still don't have proper unions. class CliThing parse-cli (self Ptr _, v Ptr s, args Ptr (Slice StrView), cd Ptr CommandDepth) -> Maybe Error # -> Should continue parsing. print-info (self _, nest I32) print-args-in-line (self _) -> I32 print-arg-descriptions (self _) -> I32 # or maybe instead of returning, also get a pointer to error and we can check if it was set. class CliDescribable name (self _, s StrView) -> _ describe (self _, s StrView) -> _ # either short is a class function (can create different datatypes) or it's not and we have to interpret the datatype # differently depending on the context when parsing. inst CliThing Cli parse-cli (_, _, _, _) return None print-args-in-line (self _): 0 print-arg-descriptions (self _): 0 print-info(self, nest) assert(nest == 0, 'da only real ting') self.name if-just(fn(name)) Term.print(name) if is-just(self.desc) Term.print(': ') self.desc if-just(fn(desc)) Term.print(desc) # OH MY heckin fizzbuzzerino if self.name is-just() or self.desc is-just() println('') inst CliDescribable Cli name (self, s StrView) return self { name: Just(s) } describe (self, s StrView) return self { desc: Just(s) } inst CliThing Unit parse-cli (_, _, _, _) return None print-args-in-line (self _): 0 print-arg-descriptions (self _): 0 print-info(_, _) pass fn def(): Unit fn print-nest (nest I32) for _ in 0 to (nest - 1) Term.print(' ') # help n shii fn requires-help (args Slice StrView): args head() if-just-is(fn s: s one-of(['-h', '--help'])) fn print-arg-line(cli CliThing) cli print-args-in-line() fn print-available-commands() pass fn print-options() pass fn help(cli CliThing, cd Ptr CommandDepth) Term.print('Usage: ') print-command-location(cd) print-arg-line(cli) println('') cli print-arg-descriptions() # cli print-info(0) # maybe make Short and Option actually require an arg() call (otherwise treated as flag?) # which would be created when specifying `option()` tho? Opt cli sub sub-s s name ShitStr desc Maybe StrView sub sub setter (s, sub-s) -> s outer cli inst CliThing Opt parse-cli (self Ptr _, s, args, cd) while True err = (&self&.outer) parse-cli(s, args, cd) if err is-just() return err if not args& head() if-just-is(fn s: s Str.eq(self&.name)) break args <&= args& Slice.from(1) cd =& CommandDepth { cmd: self&.name, outer: Just(cd) } sub =& Mem.zeroed() err = (&self&.sub) parse-cli(sub, args, cd) if requires-help(args&) help(self&.sub, cd) if err is-just() return err else s <&= self&.setter(s&, sub&) return None print-args-in-line (self): self.outer print-args-in-line() print-arg-descriptions (self): self.outer print-arg-descriptions() print-info (self, nest) print-info(self.outer, nest) print-nest(nest) Term.print(self.name) case self.desc Just(desc) println(desc) None println('') print-info(self.sub, nest + 1) # THINK ABOUT JOINING FLAGS TOGETHER (-ls for example) - only chars? # fn sflag(cli cli, name StrView) -> Opt Str cli <= CliThing cli # return Opt { name: '-\(name)', outer: cli } fn flag(cli cli, name StrView, setter s -> s) -> Opt cli () () s <= CliThing cli return Opt { name: '--\(name)' StrCase2() , outer: cli , setter: fn (s, Unit): setter(s) , sub: Unit , desc: None } Arg cli a s used Bool opty StrView -> Maybe a setter (s, a) -> s outer cli name Maybe StrView desc Maybe StrView inst CliThing Arg parse-cli (self Ptr _, s, args, cd) while True # (&self&.outer) parse-cli(args)? (and type like Either Error ()) err = (&self&.outer) parse-cli(s, args, cd) if err is-just() return err if self&.used return None case args head() None placeholder = '' return Just('expect arg\(placeholder)') Just(arg) case self&.opty(arg) None if requires-help(args&) # leave the arg be! return Just('gettin help: \(arg)') # temp return Just('could not parse arg: \(arg)') Just(x) self <&.used= True args <&= args& Slice.from(1) s <&= self&.setter(s&, x) print-args-in-line (self) argnum = self.outer print-args-in-line() case self.name Just(name) Term.print(' \(name)') None Term.print(' arg\(argnum)') return argnum + 1 print-arg-descriptions (self) argnum = self.outer print-args-in-line() case self.desc Just(desc) case self.name Just(name) Term.print(' \(name)') None Term.print(' arg\(argnum)') println(' - \(desc)') None pass return argnum + 1 print-info (self, nest) print-info(self.outer, nest) inst CliDescribable Arg name (self, s StrView) return self { name: Just(s) } describe (self, s StrView) return self { desc: Just(s) } fn arg(cli cli, parser StrView -> Maybe a, setter (s, a) -> s) -> Arg cli a s <= CliThing cli return Arg { used: False, outer: cli, opty: parser, setter, name: None, desc: None } fn option(cli cli, name StrView, parser StrView -> Maybe a, setter (s, a) -> s, more-opts) <= CliThing cli: Opt { name: '--\(name)' StrCase2() , outer: cli , setter: fn (s, a): setter(s, a) , sub: def() arg(parser, fn(_, x): x) more-opts() , desc: None } fn command(cli cli, name StrView, setter (s, sub) -> s, opts) <= CliThing cli: Opt { name: name StrCase1() , outer: cli , setter: setter , sub: def() opts() , desc: None } # fn help(cli cli) <= CliThing cli: # cli flag('help') # # we can make help() actually use predefined commands! fn int(s StrView) -> Maybe int <= FromIntegral int, Negation int: s parse-int() fn enum(convert Slice (StrView, a)) -> StrView -> Maybe a: fn (x): convert Slice.find(fn ((c, _)): c == x) fmap-maybe(snd) fn bool(s StrView) -> Maybe Bool: enum( [ ('True', True) , ('False', False) , ('true', True) , ('false', False) ])(s) Default cli s outer cli used Bool default s inst CliThing Default parse-cli (self Ptr _, s, args, cd) if not self&.used s <&= self&.default return parse-cli(&self&.outer, s, args, cd) print-args-in-line (self): self.outer print-args-in-line() print-arg-descriptions (self): self.outer print-arg-descriptions() print-info (self, nest) print-info(self.outer, nest) fn default(cli cli, def s) -> Default cli s <= CliThing cli: Default { outer: cli, used: False, default: def } fn parse(cli cli, args Slice StrView) -> Either Error s <= CliThing cli args =& args own() cli =& cli own() cd =& CommandDepth { cmd: 'base' StrCase1(), outer: None } s =& @undefined err = parse-cli(cli, s, args, cd) case err Just(err) help(cli&, cd) return Left(err) None case args head() Just(unrecognized-option) if requires-help(args&) arg = '' help(cli&, cd) return Left('gettin help: \(arg)') # temp return Left('unrectognized option: \(unrecognized-option)') None pass return Right(s&) S a I32 b Bool pp I32 # TODO: for now, anonymous structs seem to not work here. :((( Sub a I32 b Bool cli = mk() name('le compiler') describe('le compiler o algo') # short('a') command('build', fn (s, ss): s { a: (ss as Sub).a, b: ss.b }, fn(s): s arg(int, fn (s, x): s { a: x }) name('FILE') option('dupsko', bool, fn (s, x): s { b: x }, id) ) flag('debug', fn (s): println('DEBUG') but-return(s)) option('mypp', int, fn (s, x): s { pp: x }, id) default(S { a: 0, b: False, pp: 0 }) case cli parse(['-h', 'build', '1337', '--dupsko', 'true']) Right(s) # println(s.a) # println(s.b) # println(s.pp) pass Left(err) println(err) # --dupsko 69 -lsa # -dro dupsko (last option has a parameter) # -dor (if o has no default, it should throw an error) # kkc build [...]