a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
a20af62000-09-26Fredrik Hübinette (Hubbe) 
833d382002-03-19Martin Nilsson #pragma strict_types
53d6c82001-10-28Martin Nilsson //! @[Getopt] is a group of functions which can be used to find command
883adb2000-12-13Henrik Grubbström (Grubba) //! line options. //! //! Command line options come in two flavors: long and short. The short ones //! consists of a dash followed by a character (@tt{-t@}), the long ones //! consist of two dashes followed by a string of text (@tt{--test@}). //! The short options can also be combined, which means that you can write //! @tt{-tda@} instead of @tt{-t -d -a@}. //! //! Options can also require arguments, in which case they cannot be //! combined. To write an option with an argument you write //! @tt{-t @i{argument@}@} or @tt{-t@i{argument@}@} or //! @tt{--test=@i{argument@}@}.
9eaf1d2008-06-28Martin Nilsson protected void my_error(string err, int throw_errors) {
dc22992002-11-17Martin Nilsson  if(throw_errors) error(err);
4e6c082007-05-02Henrik Grubbström (Grubba)  werror([string(0..255)]err);
dc22992002-11-17Martin Nilsson  exit(1); }
883adb2000-12-13Henrik Grubbström (Grubba) 
7f4f862001-05-10Henrik Grubbström (Grubba) //! This is a generic function to parse command line options of the //! type @tt{-f@}, @tt{--foo@} or @tt{--foo=bar@}.
883adb2000-12-13Henrik Grubbström (Grubba) //!
7f4f862001-05-10Henrik Grubbström (Grubba) //! @param argv //! The first argument should be the array of strings that was sent as
cbe8c92003-04-07Martin Nilsson //! the second argument to your @expr{main()@} function.
883adb2000-12-13Henrik Grubbström (Grubba) //!
7f4f862001-05-10Henrik Grubbström (Grubba) //! @param shortform
029deb2001-12-19Martin Stjernholm //! The second is a string with the short form of your option. The //! short form must be only one character long. It can also be an //! array of strings, in which case any of the options in the array //! will be accepted.
883adb2000-12-13Henrik Grubbström (Grubba) //!
7f4f862001-05-10Henrik Grubbström (Grubba) //! @param longform
029deb2001-12-19Martin Stjernholm //! This is an alternative and maybe more readable way to give the
cbe8c92003-04-07Martin Nilsson //! same option. If you give @expr{"foo"@} as @[longform] your program
029deb2001-12-19Martin Stjernholm //! will accept @tt{--foo@} as argument. This argument can also be //! an array of strings, in which case any of the options in the //! array will be accepted.
883adb2000-12-13Henrik Grubbström (Grubba) //!
7f4f862001-05-10Henrik Grubbström (Grubba) //! @param envvars
029deb2001-12-19Martin Stjernholm //! This argument specifies an environment variable that can be used //! to specify the same option, to make it easier to customize //! program usage. It can also be an array of strings, in which case //! any of the mentioned variables in the array may be used.
883adb2000-12-13Henrik Grubbström (Grubba) //!
7f4f862001-05-10Henrik Grubbström (Grubba) //! @param def //! This argument has two functions: It specifies if the option takes an //! argument or not, and it informs @[find_option()] what to return if the
0f74522010-08-19Henrik Grubbström (Grubba) //! option is not present. //! //! The value may be one of: //! @mixed
38ad452010-08-25Henrik Grubbström (Grubba) //! @type int(0..0)|zero
0f74522010-08-19Henrik Grubbström (Grubba) //! The option does not require a value.
38ad452010-08-25Henrik Grubbström (Grubba) //! @type int(1..1)|string
0f74522010-08-19Henrik Grubbström (Grubba) //! The option requires a value, and @[def] will be returned //! if the option is not present. If the option is present, //! but does not have an argument @[find_option()] will fail. //! //! Note that a set option will always return a @expr{string@}, //! so setting @[def] to @expr{1@} can be used to detect whether //! the option is present or not. //! @endmixed
883adb2000-12-13Henrik Grubbström (Grubba) //!
dc22992002-11-17Martin Nilsson //! @param throw_errors
cbe8c92003-04-07Martin Nilsson //! If @[throw_errors] has been specified @[find_option()] will //! throw errors on failure. If it has been left out, or is //! @expr{0@} (zero), it will instead print an error message on //! @[Stdio.stderr] and exit the program with result code 1 on //! failure.
dc22992002-11-17Martin Nilsson //!
7f4f862001-05-10Henrik Grubbström (Grubba) //! @returns //! Returns the value the option has been set to if any. //!
cbe8c92003-04-07Martin Nilsson //! If the option is present, but has not been set to anything //! @expr{1@} will be returned.
7f4f862001-05-10Henrik Grubbström (Grubba) //!
53d6c82001-10-28Martin Nilsson //! Otherwise if any of the environment variables specified in @[envvars] has
3f85852002-11-17Henrik Grubbström (Grubba) //! been set, that value will be returned.
7f4f862001-05-10Henrik Grubbström (Grubba) //! //! If all else fails, @[def] will be returned.
883adb2000-12-13Henrik Grubbström (Grubba) //!
e1b6932002-11-15Martin Nilsson //! @throws
3f85852002-11-17Henrik Grubbström (Grubba) //! If an option that requires an argument lacks an argument and
dc22992002-11-17Martin Nilsson //! @[throw_errors] is set an error will be thrown.
e1b6932002-11-15Martin Nilsson //!
883adb2000-12-13Henrik Grubbström (Grubba) //! @note
3f85852002-11-17Henrik Grubbström (Grubba) //! @[find_option()] modifies @[argv]. Parsed options will be removed //! from @[argv]. Elements of @[argv] that have been removed entirely will //! be replaced with zeroes.
7f4f862001-05-10Henrik Grubbström (Grubba) //! //! This function reads options even if they are written after the first //! non-option on the line.
883adb2000-12-13Henrik Grubbström (Grubba) //!
cbe8c92003-04-07Martin Nilsson //! Index @expr{0@} (zero) of @[argv] is not scanned for options, //! since it is reserved for the program name.
883adb2000-12-13Henrik Grubbström (Grubba) //!
3f85852002-11-17Henrik Grubbström (Grubba) //! Only the first ocurrance of an option will be parsed. To parse //! multiple ocurrances, call @[find_option()] multiple times. //!
883adb2000-12-13Henrik Grubbström (Grubba) //! @seealso
7f4f862001-05-10Henrik Grubbström (Grubba) //! @[Getopt.get_args()]
883adb2000-12-13Henrik Grubbström (Grubba) //!
e1b6932002-11-15Martin Nilsson string|int(0..1) find_option(array(string) argv, array(string)|string shortform, array(string)|string|void longform, array(string)|string|void envvars,
dc22992002-11-17Martin Nilsson  string|int(0..1)|void def, int|void throw_errors)
c2a4061997-02-06Fredrik Hübinette (Hubbe) {
e1b6932002-11-15Martin Nilsson  string|int(0..1) value;
c2a4061997-02-06Fredrik Hübinette (Hubbe) 
dc22992002-11-17Martin Nilsson  int(0..1) hasarg = !zero_type(def);
f965752002-03-14Martin Nilsson  if(!arrayp(longform)) longform = ({ [string]longform }); if(!arrayp(shortform)) shortform = ({ [string]shortform });
e1b6932002-11-15Martin Nilsson  if(stringp(envvars)) envvars = ({ [string]envvars }); foreach(argv; int i; string opt) { if(!i || !opt || sizeof(opt)<2 || opt[0]!='-') continue; if(opt[1] == '-') { if(opt=="--") break; string tmp=opt; sscanf(tmp, "%s=%s", tmp, value);
c2a4061997-02-06Fredrik Hübinette (Hubbe) 
e1b6932002-11-15Martin Nilsson  if(has_value([array(string)]longform, tmp[2..])) { argv[i]=0; if(hasarg && !value) { if(i == sizeof(argv)-1)
dc22992002-11-17Martin Nilsson  my_error( "No argument to option "+tmp+".\n",throw_errors );
e1b6932002-11-15Martin Nilsson  value=argv[i+1]; argv[i+1]=0; } return value || 1; } } else { foreach(opt/1; int j; string sopt) { if(has_value([array(string)]shortform, sopt)) { string arg = opt[j+1..]; if(hasarg) { if(arg == "") { if(i == sizeof(argv)-1)
dc22992002-11-17Martin Nilsson  my_error( "No argument to option -"+sopt+".\n",throw_errors );
e1b6932002-11-15Martin Nilsson  value=argv[i+1]; argv[i+1] = 0;
c2a4061997-02-06Fredrik Hübinette (Hubbe)  }
e1b6932002-11-15Martin Nilsson  else { value=arg; arg="";
c2a4061997-02-06Fredrik Hübinette (Hubbe)  } }
e1b6932002-11-15Martin Nilsson  else value=1; argv[i] = opt[..j-1]+arg; if(argv[i]=="-") argv[i]=0; return value;
c2a4061997-02-06Fredrik Hübinette (Hubbe)  } } } } if(arrayp(envvars))
0662a92000-07-12Henrik Grubbström (Grubba)  foreach([array(string)]envvars, value) if(value && (value=[string]getenv([string]value)))
c2a4061997-02-06Fredrik Hübinette (Hubbe)  return value; return def; }
a8c8b82003-07-24Henrik Grubbström (Grubba) //! Used with @[find_all_options()] to indicate that an option //! requires an argument. //! @seealso //! @[find_all_options()]
c2a4061997-02-06Fredrik Hübinette (Hubbe) constant HAS_ARG=1;
53d6c82001-10-28Martin Nilsson 
a8c8b82003-07-24Henrik Grubbström (Grubba) //! Used with @[find_all_options()] to indicate that an option //! does not take an argument. //! @seealso //! @[find_all_options()]
c2a4061997-02-06Fredrik Hübinette (Hubbe) constant NO_ARG=2;
53d6c82001-10-28Martin Nilsson 
a8c8b82003-07-24Henrik Grubbström (Grubba) //! Used with @[find_all_options()] to indicate that an option //! takes an optional argument. //! @seealso //! @[find_all_options()]
c2a4061997-02-06Fredrik Hübinette (Hubbe) constant MAY_HAVE_ARG=3;
f965752002-03-14Martin Nilsson  // ({ "name", type, "alias"|({"aliases"}), "env_var", default })
c2a4061997-02-06Fredrik Hübinette (Hubbe) #define NAME 0 #define TYPE 1 #define ALIASES 2 #define ENV 3 #define DEF 4
f965752002-03-14Martin Nilsson #define SIZE 5
e1b6932002-11-15Martin Nilsson 
7f4f862001-05-10Henrik Grubbström (Grubba) //! This function does the job of several calls to @[find_option()]. //! The main advantage of this is that it allows it to handle the
0320d12013-08-03Arne Goedeke //! @tt{@b{POSIX_ME_HARDER@}@} environment variable better. When either
7f4f862001-05-10Henrik Grubbström (Grubba) //! the argument @[posix_me_harder] or the environment variable //! @tt{@b{POSIX_ME_HARDER@}@} is true, no arguments will be parsed after //! the first non-option on the command line. //! //! @param argv
cbe8c92003-04-07Martin Nilsson //! The should be the array of strings that was sent as the second //! argument to your @expr{main()@} function.
7f4f862001-05-10Henrik Grubbström (Grubba) //! //! @param options //! Each element in the array @[options] should be an array on the //! following form: //! @array //! @elem string name //! Name is a tag used to identify the option in the output. //! @elem int type //! Type is one of @[Getopt.HAS_ARG], @[Getopt.NO_ARG] and //! @[Getopt.MAY_HAVE_ARG] and it affects how the error handling //! and parsing works. //! You should use @[HAS_ARG] for options that require a path, a number //! or similar. @[NO_ARG] should be used for options that do not need an //! argument, such as @tt{--version@}. @[MAY_HAVE_ARG] should be used //! for options that may or may not need an argument. //! @elem string|array(string) aliases //! This is a string or an array of string of options that will be //! looked for. Short and long options can be mixed, and short options //! can be combined into one string. Note that you must include the //! dashes so that @[find_all_options()] can distinguish between
cbe8c92003-04-07Martin Nilsson //! long and short options. Example: @expr{({"-tT","--test"})@}
7f4f862001-05-10Henrik Grubbström (Grubba) //! This would make @[find_all_options] look for @tt{-t@}, //! @tt{-T@} and @tt{--test@}. //! @elem void|string|array(string) env_var //! This is a string or an array of strings containing names of //! environment variables that can be used instead of the //! command line option. //! @elem void|mixed default
f965752002-03-14Martin Nilsson //! This is the default value a @[MAY_HAVE_ARG] option will have in the
e1b6932002-11-15Martin Nilsson //! output if it was set but not assigned any value.
7f4f862001-05-10Henrik Grubbström (Grubba) //! @endarray //! //! Only the first three elements need to be included. //! //! @param posix_me_harder //! Don't scan for arguments after the first non-option. //! //! @param throw_errors
cbe8c92003-04-07Martin Nilsson //! If @[throw_errors] has been specified @[find_all_options()] will //! throw errors on failure. If it has been left out, or is //! @expr{0@} (zero), it will instead print an error message on //! @[Stdio.stderr] and exit the program with result code 1 on //! failure.
883adb2000-12-13Henrik Grubbström (Grubba) //! //! @returns
7f4f862001-05-10Henrik Grubbström (Grubba) //! The good news is that the output from this function is a lot simpler. //! @[find_all_options()] returns an array where each element is an array on //! this form: //! @array //! @elem string name //! Option identifier name from the input. //! @elem mixed value //! Value given. If no value was specified, and no default has been //! specified, the value will be 1. //! @endarray
883adb2000-12-13Henrik Grubbström (Grubba) //! //! @note
7f4f862001-05-10Henrik Grubbström (Grubba) //! @[find_all_options()] modifies @[argv]. //!
cbe8c92003-04-07Martin Nilsson //! Index @expr{0@} (zero) of @[argv] is not scanned for options, //! since it is reserved for the program name.
883adb2000-12-13Henrik Grubbström (Grubba) //! //! @seealso
7f4f862001-05-10Henrik Grubbström (Grubba) //! @[Getopt.get_args()], @[Getopt.find_option()]
883adb2000-12-13Henrik Grubbström (Grubba) //!
f965752002-03-14Martin Nilsson array(array) find_all_options(array(string) argv,
6c7ff62003-01-02Martin Nilsson  array(array(array(string)|string|int)) options,
e1b6932002-11-15Martin Nilsson  void|int(-1..1) posix_me_harder, void|int throw_errors)
c2a4061997-02-06Fredrik Hübinette (Hubbe) {
e1b6932002-11-15Martin Nilsson  // --- Initialize variables
de4de32003-11-13Henrik Grubbström (Grubba)  mapping(string|int:array(string|int|array(string))) quick=([]);
c2a4061997-02-06Fredrik Hübinette (Hubbe) 
6c7ff62003-01-02Martin Nilsson  foreach(options; int i; array(array(string)|string|int) opt) {
e1b6932002-11-15Martin Nilsson  if(sizeof(opt)!=SIZE) { options[i] = opt + allocate(SIZE-sizeof(opt)); opt = options[i]; } array(string)|string aliases = [array(string)|string]opt[ALIASES]; if(!arrayp(aliases)) aliases = ({[string]aliases});
f965752002-03-14Martin Nilsson  foreach([array(string)]aliases, string optname)
de4de32003-11-13Henrik Grubbström (Grubba)  if(has_prefix(optname, "--"))
f965752002-03-14Martin Nilsson  quick[optname]=opt;
de4de32003-11-13Henrik Grubbström (Grubba)  else if (has_prefix(optname, "-")) { foreach(optname[1..]; ; int optletter) quick[optletter]=opt; } else { my_error(sprintf("Bad option alias for %O: %O; missing '-'.", opt[NAME], optname), throw_errors); }
f965752002-03-14Martin Nilsson  }
e1b6932002-11-15Martin Nilsson  posix_me_harder = posix_me_harder!=-1 && (posix_me_harder || !!getenv("POSIX_ME_HARDER")); // --- Do the actual parsing of arguments.
f965752002-03-14Martin Nilsson  array(array) ret=({});
de4de32003-11-13Henrik Grubbström (Grubba)  for (int e=1; e < sizeof(argv); e++) { string opt; if(!(opt = argv[e])) continue;
e1b6932002-11-15Martin Nilsson 
6c7ff62003-01-02Martin Nilsson  if(sizeof(opt)<2 || opt[0]!='-') {
e1b6932002-11-15Martin Nilsson  if(posix_me_harder) break; continue; } if(opt[1]=='-') { if(opt=="--") break; string arg; sscanf(opt, "%s=%s", opt, arg); if(array option=quick[opt]) { argv[e]=0; if(!arg && option[TYPE]==HAS_ARG) { if(e==sizeof(argv)-1) my_error( "No argument to option "+opt+".\n", throw_errors ); arg = argv[e+1]; argv[e+1] = 0;
c2a4061997-02-06Fredrik Hübinette (Hubbe)  }
e1b6932002-11-15Martin Nilsson  ret+=({ ({ option[NAME], arg || option[DEF] || 1 }) });
f965752002-03-14Martin Nilsson  }
e1b6932002-11-15Martin Nilsson  } else {
de4de32003-11-13Henrik Grubbström (Grubba)  Iterator iter = get_iterator(opt); iter->next(); foreach(iter; int j; int opt_letter) { if(array option=quick[opt_letter]) { opt[j]=0;
e1b6932002-11-15Martin Nilsson  string arg; if(option[TYPE]!=NO_ARG) { // HAS_ARG or MAY_HAVE_ARG arg = opt[j+1..];
c2a4061997-02-06Fredrik Hübinette (Hubbe) 
e1b6932002-11-15Martin Nilsson  if(option[TYPE]==HAS_ARG && arg=="") { if(e==sizeof(argv)-1)
de4de32003-11-13Henrik Grubbström (Grubba)  my_error( sprintf("No argument to option -%c.\n", opt_letter), throw_errors );
c2a4061997-02-06Fredrik Hübinette (Hubbe) 
e1b6932002-11-15Martin Nilsson  arg = argv[e+1]; argv[e+1] = 0; } else {
de4de32003-11-13Henrik Grubbström (Grubba)  arg = opt[j+1..]; opt = opt[..j];
e1b6932002-11-15Martin Nilsson  }
c2a4061997-02-06Fredrik Hübinette (Hubbe)  }
e1b6932002-11-15Martin Nilsson  if (arg == "") arg = 0; ret+=({ ({ option[NAME], arg || option[DEF] || 1 }) });
de4de32003-11-13Henrik Grubbström (Grubba)  if(sizeof(opt)==j+1) break; // if opts=opts[..j] we're done.
c2a4061997-02-06Fredrik Hübinette (Hubbe)  } }
e1b6932002-11-15Martin Nilsson 
de4de32003-11-13Henrik Grubbström (Grubba)  opt -= "\0"; if (opt != "-") argv[e] = opt; else argv[e] = 0;
c2a4061997-02-06Fredrik Hübinette (Hubbe)  } }
e1b6932002-11-15Martin Nilsson  // --- Fill out empty slots with environment values multiset(string) done = [multiset(string)]mkmultiset(column(ret, 0));
6c7ff62003-01-02Martin Nilsson  foreach(options, array(string|int|array(string)) option) {
e1b6932002-11-15Martin Nilsson  string name=[string]option[NAME]; if(done[name]) continue; if(option[ENV]) {
6c7ff62003-01-02Martin Nilsson  array(string)|string foo=[array(string)|string]option[ENV];
e1b6932002-11-15Martin Nilsson  if(!foo) continue; if(stringp(foo)) foo = ({ [string]foo });
c2a4061997-02-06Fredrik Hübinette (Hubbe) 
e1b6932002-11-15Martin Nilsson  foreach([array(string)]foo, foo) if(foo=[string]getenv([string]foo)) { ret += ({ ({name, foo}) });
f965752002-03-14Martin Nilsson  done[name] = 1;
e1b6932002-11-15Martin Nilsson  break;
c2a4061997-02-06Fredrik Hübinette (Hubbe)  } }
e1b6932002-11-15Martin Nilsson  }
f965752002-03-14Martin Nilsson 
c2a4061997-02-06Fredrik Hübinette (Hubbe)  return ret; }
883adb2000-12-13Henrik Grubbström (Grubba) //! This function returns the remaining command line arguments after
0a50712002-03-02Martin Nilsson //! you have run @[find_option()] or @[find_all_options()] to find
883adb2000-12-13Henrik Grubbström (Grubba) //! all the options in the argument list. If there are any options
0a50712002-03-02Martin Nilsson //! left not handled by @[find_option()] or @[find_all_options()]
883adb2000-12-13Henrik Grubbström (Grubba) //! this function will fail. //! //! If @[throw_errors] has been specified @[get_args()] will throw errors //! on failure. If it has been left out, or is @tt{0@} (zero), it will
029deb2001-12-19Martin Stjernholm //! instead print an error message on @[Stdio.stderr] and exit the //! program with result code 1 on failure.
883adb2000-12-13Henrik Grubbström (Grubba) //! //! @returns //! On success a new @[argv] array without the parsed options is //! returned. //! //! @seealso //! @[Getopt.find_option()], @[Getopt.find_all_options()] //!
e1b6932002-11-15Martin Nilsson array(string) get_args(array(string) argv, void|int(-1..1) posix_me_harder,
1790d12000-03-30Henrik Grubbström (Grubba)  void|int throw_errors)
c2a4061997-02-06Fredrik Hübinette (Hubbe) {
e1b6932002-11-15Martin Nilsson  posix_me_harder = posix_me_harder!=-1 && (posix_me_harder || !!getenv("POSIX_ME_HARDER")); foreach(argv; int i; string opt) {
6c7ff62003-01-02Martin Nilsson  if(!i || !stringp(opt)) continue; if(sizeof(opt)<2 || opt[0]!='-') {
e1b6932002-11-15Martin Nilsson  if(posix_me_harder) break; continue; } if(opt[1]=='-') { if(opt=="--") { argv[i]=0; break;
c2a4061997-02-06Fredrik Hübinette (Hubbe)  }
e1b6932002-11-15Martin Nilsson  my_error( "Unknown option "+opt+".\n", throw_errors ); } else {
9b2b9a2004-07-13Henrik Grubbström (Grubba)  if(sizeof(opt) == 2) my_error( "Unknown option "+opt+".\n", throw_errors ); my_error( "Unknown options "+opt+".\n", throw_errors );
c2a4061997-02-06Fredrik Hübinette (Hubbe)  } }
e1b6932002-11-15Martin Nilsson  argv -= ({0, 1});
c2a4061997-02-06Fredrik Hübinette (Hubbe)  return argv; }