{ argp.a68 - argp-like command line parser

  Copyright (C) 2026 Jose E. Marchesi

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see
  <https://www.gnu.org/licenses/>. }

{ Each option can specify a name and a long name.  The name is a
  single character, and " " denotes the option has no short
  name.  The long name is a string, and the empty string ""
  denotes the option has no long name.

  Long options are recognized in the forms:

    --longarg               For long options with no argument.
    --longarg=VALUE         For long options with argument.

  Short options are recognized in the forms:

    -x                      For short option with no argument.
    -x VALUE                For short option with argument.
    -xyz                    For multiple short options with no
                            argument.
    -xyz VALUE              For multiple short options with
                            argument.

  Command line arguments are processed in argv order.  When an
  option is recognized via either its name or long name, the
  corresponding option handler is invoked.  The handler gets the
  option's argument as an argument, which may be empty if the
  option takes no arguments.

  The callback `handler' is invoked for every non-option
  argument found in the command line.

  Handlers for both option and no-option arguments shall yield a
  boolean.  If it is FALSE then parsing stops once the handler
  returns.  Otherwise parsing continues. }

module Argp =
def
    pub mode ArgOpt = struct (char name, string long_name,
                              bool arg_required, proc(string)bool handler);

    pub proc argp = (int p, [][]ArgOpt opts,
                     proc(int,string)bool no_opt_handler,
                     proc(string)void error_handler) void:
    begin
          proc getopt = (string prefix, string arg) ArgOpt:
          begin ArgOpt res, bool found := false;
                for i to UPB opts while NOT found
                do for j to UPB opts[i] while NOT found
                   do if arg = long_name of opts[i][j]
                         OR (arg /= " " AND arg = name of opts[i][j])
                      then res := opts[i][j]; found := true
                      fi
                   od
                od;
                (NOT found | error_handler ("unknown option " + prefix + arg));
                res
          end;

          bool found_dash_dash := false,
                                  skip_next_opt := false,
                                                   continue := true;

          for i from p to argc while continue
          do string arg = argv (i);
             if skip_next_opt
             then skip_next_opt := false
             elif arg = "--" AND NOT found_dash_dash
             then found_dash_dash := true
             elif found_dash_dash OR (UPB arg >= 1 andth arg[1] /= "-")
             then continue := no_opt_handler (i + 1, arg)
             elif UPB arg > 1 andth arg[2] = "-"
             then { Long option.  It may have an argument.  }
                  int eqidx = access Utils (char_in_string (arg, "="));
                  string optname = (eqidx > 0 | arg[3:eqidx - 1] | arg[3:]),
                  optarg = (eqidx > 0 AND UPB arg >= (eqidx + 1) | arg[eqidx + 1:]);
                  ArgOpt opt = getopt ("--", optname);

                  if (arg_required of opt) AND optarg = ""
                  then error_handler ("option --" + arg + " requires an argument") fi;
                  continue := (handler of opt) (optarg)
             else { This is one or more short options.  }
                  for j to UPB arg[2:]
                  do ArgOpt opt = getopt ("-", arg[j + 1]);
                     if arg_required of opt
                     then if i = argc orel (ELEMS argv (i + 1) > 1 andth argv (i + 1)[1] = "-")
                          then error_handler ("option -" + arg[2+j] + " requires an argument")
                          fi;
                          (handler of opt) (argv (i + 1));
                          skip_next_opt := true
                     else continue := (handler of opt) ("")
                     fi
                  od
             fi
          od
    end;

    skip
fed
