"Fossies" - the Fresh Open Source Software Archive

Member "ponyc-0.33.2/packages/cli/command_parser.pony" (3 Feb 2020, 10436 Bytes) of package /linux/misc/ponyc-0.33.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Pony source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "command_parser.pony": 0.33.1_vs_0.33.2.

    1 use "collections"
    2 
    3 class CommandParser
    4   let _spec: CommandSpec box
    5   let _parent: (CommandParser box | None)
    6 
    7   new box create(spec': CommandSpec box) =>
    8     """
    9     Creates a new parser for a given command spec.
   10     """
   11     _spec = spec'
   12     _parent = None
   13 
   14   new box _sub(spec': CommandSpec box, parent': CommandParser box) =>
   15     _spec = spec'
   16     _parent = parent'
   17 
   18   fun parse(
   19     argv: Array[String] box,
   20     envs: (Array[String] box | None) = None)
   21     : (Command | CommandHelp | SyntaxError)
   22   =>
   23     """
   24     Parses all of the command line tokens and env vars and returns a Command,
   25     or the first SyntaxError.
   26     """
   27     let tokens = argv.clone()
   28     try tokens.shift()? end  // argv[0] is the program name, so skip it
   29     let options: Map[String,Option] ref = options.create()
   30     let args: Map[String,Arg] ref = args.create()
   31     _parse_command(
   32       tokens, options, args,
   33       EnvVars(envs, _spec.name().upper() + "_", true),
   34       false)
   35 
   36   fun _fullname(): String =>
   37     match _parent
   38     | let p: CommandParser box => p._fullname() + "/" + _spec.name()
   39     else
   40       _spec.name()
   41     end
   42 
   43   fun _root_spec(): CommandSpec box =>
   44     match _parent
   45     | let p: CommandParser box => p._root_spec()
   46     else
   47       _spec
   48     end
   49 
   50   fun _parse_command(
   51     tokens: Array[String] ref,
   52     options: Map[String,Option] ref,
   53     args: Map[String,Arg] ref,
   54     envsmap: Map[String, String] box,
   55     ostop: Bool)
   56     : (Command | CommandHelp | SyntaxError)
   57   =>
   58     """
   59     Parses all of the command line tokens and env vars into the given options
   60     and args maps. Returns the first SyntaxError, or the Command when OK.
   61     """
   62     var opt_stop = ostop
   63     var arg_pos: USize = 0
   64 
   65     while tokens.size() > 0 do
   66       let token = try tokens.shift()? else "" end
   67       if token == "--" then
   68         opt_stop = true
   69 
   70       elseif not opt_stop and (token.compare_sub("--", 2, 0) == Equal) then
   71         match _parse_long_option(token.substring(2), tokens)
   72         | let o: Option =>
   73           if o.spec()._typ_p().is_seq() then
   74             options.upsert(o.spec().name(), o, {(x, n) => x._append(n) })
   75           else
   76             options.update(o.spec().name(), o)
   77           end
   78         | let se: SyntaxError => return se
   79         end
   80 
   81       elseif not opt_stop and
   82         ((token.compare_sub("-", 1, 0) == Equal) and (token.size() > 1)) then
   83         match _parse_short_options(token.substring(1), tokens)
   84         | let os: Array[Option] =>
   85           for o in os.values() do
   86             if o.spec()._typ_p().is_seq() then
   87               options.upsert(o.spec().name(), o, {(x, n) => x._append(n) })
   88             else
   89               options.update(o.spec().name(), o)
   90             end
   91           end
   92         | let se: SyntaxError =>
   93           return se
   94         end
   95 
   96       else // no dashes, must be a command or an arg
   97         if _spec.commands().size() > 0 then
   98           try
   99             match _spec.commands()(token)?
  100             | let cs: CommandSpec box =>
  101               return CommandParser._sub(cs, this).
  102                 _parse_command(tokens, options, args, envsmap, opt_stop)
  103             end
  104           else
  105             return SyntaxError(token, "unknown command")
  106           end
  107         else
  108           match _parse_arg(token, arg_pos)
  109           | let a: Arg =>
  110             if a.spec()._typ_p().is_seq() then
  111               args.upsert(a.spec().name(), a, {(x, n) => x._append(n) })
  112             else
  113               args.update(a.spec().name(), a)
  114               arg_pos = arg_pos + 1
  115             end
  116           | let se: SyntaxError => return se
  117           end
  118         end
  119       end
  120     end
  121 
  122     // If it's a help option, return a general or specific CommandHelp.
  123     try
  124       let help_option = options(_help_name())?
  125       if help_option.bool() then
  126         return
  127           if _spec is _root_spec() then
  128             Help.general(_root_spec())
  129           else
  130             Help.for_command(_root_spec(), [_spec.name()])
  131           end
  132       end
  133     end
  134 
  135     // If it's a help command, return a general or specific CommandHelp.
  136     if _spec.name() == _help_name() then
  137       try
  138         match args("command")?.string()
  139         | "" => return Help.general(_root_spec())
  140         | let c: String => return Help.for_command(_root_spec(), [c])
  141         end
  142       end
  143       return Help.general(_root_spec())
  144     end
  145 
  146     // Fill in option values from env or from coded defaults.
  147     for os in _spec.options().values() do
  148       if not options.contains(os.name()) then
  149         // Lookup and use env vars before code defaults
  150         if envsmap.contains(os.name()) then
  151           let vs =
  152             try
  153               envsmap(os.name())?
  154             else  // TODO(cq) why is else needed? we just checked
  155               ""
  156             end
  157           let v: _Value =
  158             match _ValueParser.parse(os._typ_p(), vs)
  159             | let v: _Value => v
  160             | let se: SyntaxError => return se
  161             end
  162           options.update(os.name(), Option(os, v))
  163         else
  164           if not os.required() then
  165             options.update(os.name(), Option(os, os._default_p()))
  166           end
  167         end
  168       end
  169     end
  170 
  171     // Check for missing options and error if any exist.
  172     for os in _spec.options().values() do
  173       if not options.contains(os.name()) then
  174         if os.required() then
  175           return SyntaxError(os.name(), "missing value for required option")
  176         end
  177       end
  178     end
  179 
  180     // Check for missing args and error if found.
  181     while arg_pos < _spec.args().size() do
  182       try
  183         let ars = _spec.args()(arg_pos)?
  184         if not args.contains(ars.name()) then // latest arg may be a seq
  185           if ars.required() then
  186             return SyntaxError(ars.name(), "missing value for required argument")
  187           end
  188           args.update(ars.name(), Arg(ars, ars._default_p()))
  189         end
  190       end
  191       arg_pos = arg_pos + 1
  192     end
  193 
  194     // Specifying only the parent and not a leaf command is an error.
  195     if _spec.is_parent() then
  196       return SyntaxError(_spec.name(), "missing subcommand")
  197     end
  198 
  199     // A successfully parsed and populated leaf Command.
  200     Command._create(_spec, _fullname(), consume options, args)
  201 
  202   fun _parse_long_option(
  203     token: String,
  204     args: Array[String] ref)
  205     : (Option | SyntaxError)
  206   =>
  207     """
  208     --opt=foo => --opt has argument foo
  209     --opt foo => --opt has argument foo, iff arg is required
  210     """
  211     let parts = token.split("=")
  212     let name = try parts(0)? else "???" end
  213     let targ = try parts(1)? else None end
  214     match _option_with_name(name)
  215     | let os: OptionSpec => _OptionParser.parse(os, targ, args)
  216     | None => SyntaxError(name, "unknown long option")
  217     end
  218 
  219   fun _parse_short_options(
  220     token: String,
  221     args: Array[String] ref)
  222     : (Array[Option] | SyntaxError)
  223   =>
  224     """
  225     if 'O' requires an argument
  226       -OFoo  => -O has argument Foo
  227       -O=Foo => -O has argument Foo
  228       -O Foo => -O has argument Foo
  229     else
  230       -O=Foo => -O has argument foo
  231     -abc => options a, b, c.
  232     -abcFoo => options a, b, c. c has argument Foo iff its arg is required.
  233     -abc=Foo => options a, b, c. c has argument Foo.
  234     -abc Foo => options a, b, c. c has argument Foo iff its arg is required.
  235     """
  236     let parts = token.split("=")
  237     let shorts = (try parts(0)? else "" end).clone()
  238     var targ = try parts(1)? else None end
  239 
  240     let options: Array[Option] ref = options.create()
  241     while shorts.size() > 0 do
  242       let c =
  243         try
  244           shorts.shift()?
  245         else
  246           0  // TODO(cq) Should never error since checked
  247         end
  248       match _option_with_short(c)
  249       | let os: OptionSpec =>
  250         if os._requires_arg() and (shorts.size() > 0) then
  251           // opt needs an arg, so consume the remainder of the shorts for targ
  252           if targ is None then
  253             targ = shorts.clone()
  254             shorts.truncate(0)
  255           else
  256             return SyntaxError(_short_string(c), "ambiguous args for short option")
  257           end
  258         end
  259         let arg = if shorts.size() == 0 then targ else None end
  260         match _OptionParser.parse(os, arg, args)
  261         | let o: Option => options.push(o)
  262         | let se: SyntaxError => return se
  263         end
  264       | None => return SyntaxError(_short_string(c), "unknown short option")
  265       end
  266     end
  267     options
  268 
  269   fun _parse_arg(token: String, arg_pos: USize): (Arg | SyntaxError) =>
  270     try
  271       let arg_spec = _spec.args()(arg_pos)?
  272       _ArgParser.parse(arg_spec, token)
  273     else
  274       return SyntaxError(token, "too many positional arguments")
  275     end
  276 
  277   fun _option_with_name(name: String): (OptionSpec | None) =>
  278     try
  279       return _spec.options()(name)?
  280     end
  281     match _parent
  282     | let p: CommandParser box => p._option_with_name(name)
  283     else
  284       None
  285     end
  286 
  287   fun _option_with_short(short: U8): (OptionSpec | None) =>
  288     for o in _spec.options().values() do
  289       if o._has_short(short) then
  290         return o
  291       end
  292     end
  293     match _parent
  294     | let p: CommandParser box => p._option_with_short(short)
  295     else
  296       None
  297     end
  298 
  299   fun tag _short_string(c: U8): String =>
  300     recover String.from_utf32(c.u32()) end
  301 
  302   fun _help_name(): String =>
  303     _root_spec().help_name()
  304 
  305 primitive _OptionParser
  306   fun parse(
  307     spec: OptionSpec,
  308     targ: (String|None),
  309     args: Array[String] ref)
  310     : (Option | SyntaxError)
  311   =>
  312     // Grab the token-arg if provided, else consume an arg if one is required.
  313     let arg = match targ
  314       | (let fn: None) if spec._requires_arg() =>
  315         try args.shift()? else None end
  316       else
  317         targ
  318       end
  319     // Now convert the arg to Type, detecting missing or mis-typed args
  320     match arg
  321     | let a: String =>
  322       match _ValueParser.parse(spec._typ_p(), a)
  323       | let v: _Value => Option(spec, v)
  324       | let se: SyntaxError => se
  325       end
  326     else
  327       if not spec._requires_arg() then
  328         Option(spec, spec._default_arg())
  329       else
  330         SyntaxError(spec.name(), "missing arg for option")
  331       end
  332     end
  333 
  334 primitive _ArgParser
  335   fun parse(spec: ArgSpec, arg: String): (Arg | SyntaxError) =>
  336     match _ValueParser.parse(spec._typ_p(), arg)
  337     | let v: _Value => Arg(spec, v)
  338     | let se: SyntaxError => se
  339     end
  340 
  341 primitive _ValueParser
  342   fun box parse(typ: _ValueType, arg: String): (_Value | SyntaxError) =>
  343     try
  344       typ.value_of(arg)?
  345     else
  346       SyntaxError(arg, "unable to convert '" + arg + "' to " + typ.string())
  347     end