09be262007-09-16Martin Nilsson // // Argument parser // By Martin Nilsson
ab6f442011-04-25Martin Stjernholm // $Id$
09be262007-09-16Martin Nilsson // #pike __REAL_VERSION__
6244262008-05-03Martin Nilsson class OptLibrary
09be262007-09-16Martin Nilsson {
57238a2008-05-02Martin Nilsson  //! Base class for parsing an argument. Inherit this class to create
6244262008-05-03Martin Nilsson  //! custom made option types. class Opt
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  constant is_opt = 1;
9eaf1d2008-06-28Martin Nilsson  protected Opt next;
57238a2008-05-02Martin Nilsson  //! Should return 1 for set options or a string containing the //! value of the option. Returning 0 means the option was not set //! (or matched). To properly chain arguments parsers, return //! @expr{::get_value(argv, env)@} instead of @expr{0@}, unless //! you want to explicitly stop the chain and not set this option. int(0..1)|string get_value(array(string) argv, mapping(string:string) env) { if(next) return next->get_value(argv, env); return 0; }
09be262007-09-16Martin Nilsson 
6244262008-05-03Martin Nilsson  //! Should return a list of options that are parsed. To properly //! chain argument parsers, return @expr{your_opts + //! ::get_opts()@}. array(string) get_opts()
57238a2008-05-02Martin Nilsson  {
4c19032008-05-03Martin Nilsson  if(!next) return ({});
6244262008-05-03Martin Nilsson  return next->get_opts();
57238a2008-05-02Martin Nilsson  }
09be262007-09-16Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected this_program `|(mixed thing)
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  if( !objectp(thing) || !thing->is_opt )
57238a2008-05-02Martin Nilsson  error("Can only or %O with another %O.\n", this, this_program); if( next ) { next = next | thing; return this; } next = thing;
09be262007-09-16Martin Nilsson  return this; }
57238a2008-05-02Martin Nilsson  //! This function will be called by @expr{_sprintf@}, which //! handles formatting of chaining between objects.
9eaf1d2008-06-28Martin Nilsson  protected string __sprintf()
57238a2008-05-02Martin Nilsson  { return sprintf("%O()", this_program); }
09be262007-09-16Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected string _sprintf(int t)
57238a2008-05-02Martin Nilsson  { if( t!='O' ) return UNDEFINED; if( !next ) return __sprintf(); else return sprintf("%s|%O", __sprintf(), next); }
09be262007-09-16Martin Nilsson  }
6244262008-05-03Martin Nilsson  //! Parses an option without parameter, such as --help, -x or "x"
57238a2008-05-02Martin Nilsson  //! from -axb. //! //! @example
6244262008-05-03Martin Nilsson  //! Opt verbose = NoOpt("-v")|NoOpt("--verbose"); class NoOpt
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  inherit Opt;
9eaf1d2008-06-28Martin Nilsson  protected string opt; protected int double;
09be262007-09-16Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected void create(string _opt)
57238a2008-05-02Martin Nilsson  {
6244262008-05-03Martin Nilsson  if( sizeof(_opt)>2 && has_prefix(_opt, "--") )
57238a2008-05-02Martin Nilsson  double = 1;
6244262008-05-03Martin Nilsson  else if( sizeof(_opt)!=2 || _opt[0]!='-' || _opt=="--" ) error("%O not a valid option.\n", _opt); opt = _opt;
57238a2008-05-02Martin Nilsson  }
09be262007-09-16Martin Nilsson 
57238a2008-05-02Martin Nilsson  int(0..1)|string get_value(array(string) argv, mapping(string:string) env)
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  if( !sizeof(argv) ) return ::get_value(argv, env); if( double )
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  if( argv[0]==opt )
57238a2008-05-02Martin Nilsson  { argv[0] = 0; return 1; } return ::get_value(argv, env);
09be262007-09-16Martin Nilsson  }
57238a2008-05-02Martin Nilsson  if( sizeof(argv[0])>1 && argv[0][0]=='-' && argv[0][1]!='-' ) { array parts = argv[0]/"=";
6244262008-05-03Martin Nilsson  if( has_value(parts[0], opt[1..1]) )
57238a2008-05-02Martin Nilsson  {
6244262008-05-03Martin Nilsson  parts[0] -= opt[1..1];
57238a2008-05-02Martin Nilsson  argv[0] = parts*"="; if(argv[0]=="-") argv[0] = 0; return 1; } }
09be262007-09-16Martin Nilsson  return ::get_value(argv, env); }
6244262008-05-03Martin Nilsson  array(string) get_opts()
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  return ({ opt }) + ::get_opts();
09be262007-09-16Martin Nilsson  }
9eaf1d2008-06-28Martin Nilsson  protected string __sprintf()
57238a2008-05-02Martin Nilsson  {
6244262008-05-03Martin Nilsson  return sprintf("Arg.NoOpt(%O)", opt);
57238a2008-05-02Martin Nilsson  }
09be262007-09-16Martin Nilsson  }
6244262008-05-03Martin Nilsson  //! Environment fallback for an option. Can of course be used as //! only Opt source.
57238a2008-05-02Martin Nilsson  //! //! @example
6244262008-05-03Martin Nilsson  //! Opt debug = NoOpt("--debug")|Env("MY_DEBUG");
57238a2008-05-02Martin Nilsson  class Env
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  inherit Opt;
9eaf1d2008-06-28Martin Nilsson  protected string name;
09be262007-09-16Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected void create(string _name)
57238a2008-05-02Martin Nilsson  { name = _name; }
09be262007-09-16Martin Nilsson 
57238a2008-05-02Martin Nilsson  int(0..1)|string get_value(array(string) argv, mapping(string:string) env) { if( env[name] ) return env[name]; return ::get_value(argv, env); }
09be262007-09-16Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected string __sprintf()
57238a2008-05-02Martin Nilsson  {
6244262008-05-03Martin Nilsson  return sprintf("Arg.Env(%O)", name);
57238a2008-05-02Martin Nilsson  }
09be262007-09-16Martin Nilsson  }
57238a2008-05-02Martin Nilsson  //! Default value for a setting. //! //! @example
6244262008-05-03Martin Nilsson  //! Opt output = HasOpt("-o")|Default("a.out");
57238a2008-05-02Martin Nilsson  class Default
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  inherit Opt;
9eaf1d2008-06-28Martin Nilsson  protected string value;
09be262007-09-16Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected void create(string _value)
57238a2008-05-02Martin Nilsson  { value = _value; }
09be262007-09-16Martin Nilsson 
57238a2008-05-02Martin Nilsson  string get_value(array(string) argv, mapping(string:string) env) { return value; }
09be262007-09-16Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected string __sprintf()
57238a2008-05-02Martin Nilsson  {
6244262008-05-03Martin Nilsson  return sprintf("Arg.Default(%O)", value);
57238a2008-05-02Martin Nilsson  }
09be262007-09-16Martin Nilsson  }
6244262008-05-03Martin Nilsson  //! Parses an option that may have a parameter. @tt{--foo@},
57238a2008-05-02Martin Nilsson  //! @tt{-x@} and x in a sequence like @tt{-axb@} will set the //! variable to @expr{1@}. @tt{--foo=bar@}, @tt{-x bar@} and //! @tt{-x=bar@} will set the variable to @expr{bar@}. //! //! @example
6244262008-05-03Martin Nilsson  //! Opt debug = MaybeOpt("--debug"); class MaybeOpt
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  inherit NoOpt;
09be262007-09-16Martin Nilsson 
57238a2008-05-02Martin Nilsson  int(0..1)|string get_value(array(string) argv, mapping(string:string) env)
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  if( !sizeof(argv) ) return ::get_value(argv, env);
09be262007-09-16Martin Nilsson 
57238a2008-05-02Martin Nilsson  if( double )
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  // --foo
6244262008-05-03Martin Nilsson  if( argv[0]==opt )
57238a2008-05-02Martin Nilsson  { argv[0] = 0; return 1; }
09be262007-09-16Martin Nilsson 
57238a2008-05-02Martin Nilsson  // --foo=bar
6244262008-05-03Martin Nilsson  if( sscanf(argv[0], opt+"=%s", string ret)==1 )
57238a2008-05-02Martin Nilsson  { argv[0] = 0; return ret; }
09be262007-09-16Martin Nilsson 
57238a2008-05-02Martin Nilsson  return ::get_value(argv, env);
09be262007-09-16Martin Nilsson  }
57238a2008-05-02Martin Nilsson  // -x if( sizeof(argv[0])>1 && argv[0][0]=='-' && argv[0][1]!='-' )
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  array parts = argv[0]/"=";
6244262008-05-03Martin Nilsson  if( has_value(parts[0], opt[1..1]) &&
57238a2008-05-02Martin Nilsson  ( sizeof(parts)==1 ||
6244262008-05-03Martin Nilsson  parts[0][-1]!=opt[1] ) )
57238a2008-05-02Martin Nilsson  { // -xy, -xy=z
6244262008-05-03Martin Nilsson  parts[0] -= opt[1..1];
57238a2008-05-02Martin Nilsson  argv[0] = parts*"="; if(argv[0]=="-") argv[0] = 0; return 1; }
6244262008-05-03Martin Nilsson  else if( sizeof(parts)>1 && parts[0][-1]==opt[1] )
57238a2008-05-02Martin Nilsson  { // -yx=z
6244262008-05-03Martin Nilsson  parts[0] -= opt[1..1];
09be262007-09-16Martin Nilsson  if( parts[0]=="-" ) argv[0] = 0; else argv[0] = parts[0]; return parts[1..]*"=";
57238a2008-05-02Martin Nilsson  } return ::get_value(argv, env);
09be262007-09-16Martin Nilsson  } return ::get_value(argv, env); }
6244262008-05-03Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected string __sprintf()
6244262008-05-03Martin Nilsson  { return sprintf("Arg.MaybeOpt(%O)", opt); }
09be262007-09-16Martin Nilsson  }
6244262008-05-03Martin Nilsson  //! Parses an option that has a parameter. @tt{--foo=bar@}, @tt{-x
57238a2008-05-02Martin Nilsson  //! bar@} and @tt{-x=bar@} will set the variable to @expr{bar@}. //! //! @example
6244262008-05-03Martin Nilsson  //! Opt user = HasOpt("--user")|HasOpt("-u"); class HasOpt
09be262007-09-16Martin Nilsson  {
6244262008-05-03Martin Nilsson  inherit NoOpt;
57238a2008-05-02Martin Nilsson  int(0..1)|string get_value(array(string) argv, mapping(string:string) env)
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  if( !sizeof(argv) ) return ::get_value(argv, env); if( double )
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  // --foo bar
6244262008-05-03Martin Nilsson  if( argv[0]==opt )
57238a2008-05-02Martin Nilsson  { if( sizeof(argv)>1 ) { argv[0] = 0; string ret = argv[1]; argv[1] = 0; return ret; } return 0; // FIXME: Signal failure } // --foo=bar
6244262008-05-03Martin Nilsson  if( sscanf(argv[0], opt+"=%s", string ret)==1 )
09be262007-09-16Martin Nilsson  { argv[0] = 0; return ret; }
57238a2008-05-02Martin Nilsson  return ::get_value(argv, env);
09be262007-09-16Martin Nilsson  }
57238a2008-05-02Martin Nilsson  if( sizeof(argv[0])>1 && argv[0][0]=='-' && argv[0][1]!='-' )
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  array parts = argv[0]/"=";
6244262008-05-03Martin Nilsson  if( sizeof(parts[0]) && parts[0][-1]==opt[1] )
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  if( sizeof(parts)==1 ) { // "-xxxy z" if(sizeof(argv)>1) {
6244262008-05-03Martin Nilsson  parts[0] -= opt[1..1];
57238a2008-05-02Martin Nilsson  if( parts[0]=="-" ) argv[0] = 0; else argv[0] = parts[0]; string ret = argv[1]; argv[1] = 0; return ret; } // Fail. "-y" without any more elements in argv. return ::get_value(argv, env); } else
09be262007-09-16Martin Nilsson  {
57238a2008-05-02Martin Nilsson  // "-xxxy=z"
6244262008-05-03Martin Nilsson  parts[0] -= opt[1..1];
09be262007-09-16Martin Nilsson  if( parts[0]=="-" ) argv[0] = 0; else argv[0] = parts[0];
57238a2008-05-02Martin Nilsson  return parts[1..]*"=";
09be262007-09-16Martin Nilsson  } } }
57238a2008-05-02Martin Nilsson  return ::get_value(argv, env); }
6244262008-05-03Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected string __sprintf()
6244262008-05-03Martin Nilsson  { return sprintf("Arg.HasOpt(%O)", opt); }
09be262007-09-16Martin Nilsson  }
57238a2008-05-02Martin Nilsson 
6244262008-05-03Martin Nilsson } // -- OptLibrary
09be262007-09-16Martin Nilsson 
1f901a2008-05-01Martin Nilsson object REST = class {
9eaf1d2008-06-28Martin Nilsson  protected string _sprintf(int t)
1f901a2008-05-01Martin Nilsson  { return "Arg.REST"; } }();
6244262008-05-03Martin Nilsson // FIXME: Support for rc files? ( Opt x = Opt("--x")|INIFile(path, name); ) // FIXME: Support for type casts? ( Opt level = Integer(Opt("--level"));
09be262007-09-16Martin Nilsson  class LowOptions {
9eaf1d2008-06-28Martin Nilsson  protected inherit OptLibrary;
57238a2008-05-02Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected mapping(string:Opt) opts = ([]); protected mapping(string:int(1..1)|string) values = ([]); protected array(string) argv; protected string application;
09be262007-09-16Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected void create(array(string) _argv, void|mapping(string:string) env)
09be262007-09-16Martin Nilsson  { if(!env) env = getenv(); // Make a list of all the arguments we can parse. foreach(::_indices(2), string index) { mixed val = ::`[](index, 2);
6244262008-05-03Martin Nilsson  if(objectp(val) && val->is_opt) opts[index]=val;
09be262007-09-16Martin Nilsson  }
4c19032008-05-03Martin Nilsson  application = _argv[0];
09be262007-09-16Martin Nilsson  argv = _argv[1..];
6244262008-05-03Martin Nilsson  mapping(string:Opt) unset = opts+([]);
57238a2008-05-02Martin Nilsson 
09be262007-09-16Martin Nilsson  while(1) {
6244262008-05-03Martin Nilsson  if(!sizeof(argv)) break;
09be262007-09-16Martin Nilsson  int(0..1)|string value;
6244262008-05-03Martin Nilsson  foreach(unset; string index; Opt arg)
09be262007-09-16Martin Nilsson  { value = arg->get_value(argv, env); if(value) { m_delete(unset, index); values[index] = value; break; } } if(!value) value = unhandled_argument(argv, env); if(!value) break; else while( sizeof(argv) && argv[0] == 0 ) argv = argv[1..]; }
6244262008-05-03Martin Nilsson 
09be262007-09-16Martin Nilsson  if( sizeof(unset) ) {
6244262008-05-03Martin Nilsson  int(0..1)|string value; foreach(unset; string index; Opt arg) { value = arg->get_value(({}), env); if(value) { m_delete(unset, index); values[index] = value; } }
09be262007-09-16Martin Nilsson  } }
9eaf1d2008-06-28Martin Nilsson  protected int(0..1) unhandled_argument(array(string) argv,
09be262007-09-16Martin Nilsson  mapping(string:string) env) { return 0; }
1f901a2008-05-01Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson  protected mixed cast(string to)
1f901a2008-05-01Martin Nilsson  {
4c19032008-05-03Martin Nilsson  switch( to )
1f901a2008-05-01Martin Nilsson  {
4c19032008-05-03Martin Nilsson  case "mapping":
1f901a2008-05-01Martin Nilsson  return values + ([ REST : argv ]);
4c19032008-05-03Martin Nilsson  case "array": return argv;
1f901a2008-05-01Martin Nilsson  } return UNDEFINED; }
09be262007-09-16Martin Nilsson }
bed2712007-09-16Martin Nilsson //! The option parser class that contains all the argument objects. //!
09be262007-09-16Martin Nilsson class Options { inherit LowOptions;
9eaf1d2008-06-28Martin Nilsson  protected string|int `[](string id)
ef0c792007-09-16Martin Nilsson  { return values[id]; }
9eaf1d2008-06-28Martin Nilsson  protected string|int `->(string id)
ef0c792007-09-16Martin Nilsson  { return values[id]; }
9eaf1d2008-06-28Martin Nilsson  protected int(0..1)|string unhandled_argument(array(string) argv,
09be262007-09-16Martin Nilsson  mapping(string:string) env) {
57238a2008-05-02Martin Nilsson  if( !sizeof(argv) || argv[0]!="--help" ) return 0;
09be262007-09-16Martin Nilsson  string s = index("help_pre"); if( s ) write( s+"\n" );
6244262008-05-03Martin Nilsson  foreach(opts; string i; Opt opt)
09be262007-09-16Martin Nilsson  {
4c19032008-05-03Martin Nilsson  write( opt->get_opts()*", " + "\n");
09be262007-09-16Martin Nilsson  s = index(i+"_help"); if( s ) write( s ); // FIXME: Format } s = index("help_post"); if( s ) write( "\n"+s ); }
9eaf1d2008-06-28Martin Nilsson  protected string index(string i)
09be262007-09-16Martin Nilsson  { string s = ::`[](i, 2); if( !s ) return 0; if( !stringp(s) ) error("%O is not a string.\n", i); if( sizeof(s) ) { if( s[-1]!='\n' ) s += "\n"; return s; } return 0; } } // --- Simple interface class SimpleOptions { inherit LowOptions; int(0..1) unhandled_argument(array(string) argv, mapping(string:string) env) { string arg = argv[0]; if(!sizeof(arg) || arg[0]!='-') return 0; string name,value; if( has_prefix(arg, "--") ) {
1f901a2008-05-01Martin Nilsson  sscanf( arg, "--%s=%s", name, value ) || sscanf( arg, "--%s", name );
09be262007-09-16Martin Nilsson  if(!name) return 0; // arg == "--" values[name] = value||1;
1f901a2008-05-01Martin Nilsson  argv[0]=0;
09be262007-09-16Martin Nilsson  return 1; }
1f901a2008-05-01Martin Nilsson  sscanf( arg, "-%s=%s", name, value ) || sscanf( arg, "-%s", name );
9f67e72008-05-01Martin Nilsson  if( !name || !sizeof(name) ) return 0;
09be262007-09-16Martin Nilsson  foreach( name/1; int pos; string c ) if( pos == sizeof(name)-1 )
1f901a2008-05-01Martin Nilsson  values[c] = value||1;
09be262007-09-16Martin Nilsson  else
1f901a2008-05-01Martin Nilsson  values[c] = 1; argv[0]=0;
09be262007-09-16Martin Nilsson  return 1; } } // Handles // --foo -> "foo":1 // --foo=bar -> "foo":"bar" // -bar -> "b":1,"a":1,"r":1 // -bar=foo -> "b":1,"a":1,"r":"foo" (?) // --foo --bar -> "foo":1,"bar":1 // --foo - --bar -> "foo":1 // --foo x --bar -> "foo":1 (?) // // void main(int n, array argv) // {
6244262008-05-03Martin Nilsson // mapping opts = Arg.parse(argv); // argv = opts[Arg.REST];
09be262007-09-16Martin Nilsson // } mapping(string:string|int(1..1)) parse(array(string) argv) { return (mapping)SimpleOptions(argv); }