View source with raw comments or as raw
   1/*  Part of SWI-Prolog
   2
   3    Author:        Marcus Uneson
   4    E-mail:        marcus.uneson@ling.lu.se
   5    WWW:           http://person.sol.lu.se/MarcusUneson/
   6    Copyright (c)  2011-2015, Marcus Uneson
   7    All rights reserved.
   8
   9    Redistribution and use in source and binary forms, with or without
  10    modification, are permitted provided that the following conditions
  11    are met:
  12
  13    1. Redistributions of source code must retain the above copyright
  14       notice, this list of conditions and the following disclaimer.
  15
  16    2. Redistributions in binary form must reproduce the above copyright
  17       notice, this list of conditions and the following disclaimer in
  18       the documentation and/or other materials provided with the
  19       distribution.
  20
  21    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  24    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  25    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  26    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  27    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  28    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  29    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  30    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  31    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32    POSSIBILITY OF SUCH DAMAGE.
  33*/
  34
  35:- module(optparse,
  36    [  opt_parse/5,     %+OptsSpec, +CLArgs, -Opts, -PositionalArgs,-ParseOptions
  37       opt_parse/4,     %+OptsSpec, +CLArgs, -Opts, -PositionalArgs,
  38       opt_arguments/3, %+OptsSpec, -Opts, -PositionalArgs
  39       opt_help/2       %+OptsSpec, -Help
  40    ]).
  41
  42:- use_module(library(apply)).
  43:- use_module(library(lists)).
  44:- use_module(library(option)).
  45:- use_module(library(error)).
  46:- set_prolog_flag(double_quotes, codes).
  47%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% EXPORTS
  48
  49/** <module> command line parsing
  50
  51This  module  helps  in  building  a    command-line   interface  to  an
  52application. In particular, it provides functions   that  take an option
  53specification and a list of atoms, probably  given to the program on the
  54command line, and  return  a  parsed   representation  (a  list  of  the
  55customary Key(Val) by default; or optionally,   a list of Func(Key, Val)
  56terms in the style of current_prolog_flag/2).   It can also synthesize a
  57simple help text from the options specification.
  58
  59The terminology in the following  is   partly  borrowed from python, see
  60http://docs.python.org/library/optparse.html#terminology . Very briefly,
  61_arguments_ is what you provide on the command line and for many prologs
  62show up as a  list  of   atoms  =|Args|=  in =|current_prolog_flag(argv,
  63Args)|=. For a typical prolog incantation, they can be divided into
  64
  65    * _|runtime arguments|_, which controls the prolog runtime;
  66    conventionally, they are ended by '--';
  67    * _options_, which are key-value pairs (with a boolean value
  68    possibly implicit) intended to control your program in one way
  69    or another; and
  70    * _|positional arguments|_, which is what remains after
  71    all runtime arguments and options have been removed (with
  72    implicit arguments -- true/false for booleans -- filled in).
  73
  74Positional arguments are in  particular   used  for  mandatory arguments
  75without which your program  won't  work  and   for  which  there  are no
  76sensible defaults (e.g,, input file names).  Options, by contrast, offer
  77flexibility by letting  you  change  a   default  setting.  Options  are
  78optional not only by etymology: this library  has no notion of mandatory
  79or required options (see  the  python   docs  for  other rationales than
  80laziness).
  81
  82The command-line arguments enter your program as   a  list of atoms, but
  83the programs perhaps expects booleans, integers,   floats or even prolog
  84terms. You tell the parser so by providing an _|options specification|_.
  85This is just a list of individual   option specifications. One of those,
  86in turn, is a list of ground   prolog terms in the customary Name(Value)
  87format. The following terms are recognized (any others raise error).
  88
  89        * opt(Key)
  90        Key is what the option later will be accessed by, just like for
  91        current_prolog_flag(Key, Value). This term is mandatory (an error is
  92        thrown if missing).
  93
  94        * shortflags(ListOfFlags)
  95        ListOfFlags denotes any single-dashed, single letter args specifying the
  96        current option (=|-s , -K|=, etc). Uppercase letters must be quoted.
  97        Usually ListOfFlags will be a singleton list, but sometimes aliased flags
  98        may be convenient.
  99
 100        * longflags(ListOfFlags)
 101        ListOfFlags denotes any double-dashed arguments specifying
 102        the current option (=|--verbose, --no-debug|=, etc). They are
 103        basically a more readable alternative to short flags, except
 104
 105        1. long flags can be specified as =|--flag value|= or
 106        =|--flag=value|= (but not as =|--flagvalue|=); short flags as
 107        =|-f val|= or =|-fval|= (but not =|-f=val|=)
 108        2. boolean long flags can be specified as =|--bool-flag|=
 109        or =|--bool-flag=true|= or =|--bool-flag true|=; and they can be
 110        negated as =|--no-bool-flag|= or =|--bool-flag=false|= or
 111        =|--bool-flag false|=.
 112
 113        Except that shortflags must be single characters, the
 114        distinction between long and short is in calling convention, not
 115        in namespaces. Thus, if you have shortflags([v]), you can use it
 116        as =|-v2|= or =|-v 2|= or =|--v=2|= or =|--v 2|= (but not
 117        =|-v=2|= or =|--v2|=).
 118
 119        Shortflags and longflags both default to =|[]|=. It can be useful to
 120        have flagless options -- see example below.
 121
 122        * meta(Meta)
 123        Meta is optional and only relevant for the synthesized usage message
 124        and is the name (an atom) of the metasyntactic variable (possibly)
 125        appearing in it together with type and default value (e.g,
 126        =|x:integer=3|=, =|interest:float=0.11|=). It may be useful to
 127        have named variables (=|x|=, =|interest|=) in case you wish to
 128        mention them again in the help text. If not given the =|Meta:|=
 129        part is suppressed -- see example below.
 130
 131        * type(Type)
 132        Type is one of =|boolean, atom, integer, float, term|=.
 133        The corresponding argument will be parsed appropriately. This
 134        term is optional; if not given, defaults to =|term|=.
 135
 136        * default(Default)
 137        Default value. This term is optional; if not given, or if given the
 138        special value '_', an uninstantiated variable is created (and any
 139        type declaration is ignored).
 140
 141        * help(Help)
 142        Help is (usually) an atom of text describing the option in the
 143        help text. This term is optional (but obviously strongly recommended
 144        for all options which have flags).
 145
 146        Long lines are subject to basic word wrapping -- split on white
 147        space, reindent, rejoin. However, you can get more control by
 148        supplying the line breaking yourself: rather than a single line of
 149        text, you can provide a list of lines (as atoms). If you do, they
 150        will be joined with the appropriate indent but otherwise left
 151        untouched (see the option =mode= in the example below).
 152
 153Absence of mandatory option specs or the presence of more than one for a
 154particular option throws an error, as do unknown or incompatible types.
 155
 156As a concrete example from a fictive   application,  suppose we want the
 157following options to be read from the  command line (long flag(s), short
 158flag(s), meta:type=default, help)
 159
 160==
 161--mode                  -m     atom=SCAN       data gathering mode,
 162                                               one of
 163                                                SCAN: do this
 164                                                READ: do that
 165                                                MAKE: make numbers
 166                                                WAIT: do nothing
 167--rebuild-cache         -r     boolean=true    rebuild cache in
 168                                               each iteration
 169--heisenberg-threshold  -t,-h  float=0.1       heisenberg threshold
 170--depths, --iters       -i,-d  K:integer=3     stop after K
 171                                               iterations
 172--distances                    term=[1,2,3,5]  initial prolog term
 173--output-file           -o     FILE:atom=_     write output to FILE
 174--label                 -l     atom=REPORT     report label
 175--verbosity             -v     V:integer=2     verbosity level,
 176                                               1 <= V <= 3
 177==
 178
 179We may also have some configuration  parameters which we currently think
 180not   needs   to   be   controlled   from    the   command   line,   say
 181path('/some/file/path').
 182
 183This interface is  described  by   the  following  options specification
 184(order between the specifications of a particular option is irrelevant).
 185
 186==
 187ExampleOptsSpec =
 188    [ [opt(mode    ), type(atom), default('SCAN'),
 189        shortflags([m]),   longflags(['mode'] ),
 190        help([ 'data gathering mode, one of'
 191             , '  SCAN: do this'
 192             , '  READ: do that'
 193             , '  MAKE: fabricate some numbers'
 194             , '  WAIT: don''t do anything'])]
 195
 196    , [opt(cache), type(boolean), default(true),
 197        shortflags([r]),   longflags(['rebuild-cache']),
 198        help('rebuild cache in each iteration')]
 199
 200    , [opt(threshold), type(float), default(0.1),
 201        shortflags([t,h]),  longflags(['heisenberg-threshold']),
 202        help('heisenberg threshold')]
 203
 204    , [opt(depth), meta('K'), type(integer), default(3),
 205        shortflags([i,d]),longflags([depths,iters]),
 206        help('stop after K iterations')]
 207
 208    , [opt(distances), default([1,2,3,5]),
 209        longflags([distances]),
 210        help('initial prolog term')]
 211
 212    , [opt(outfile), meta('FILE'), type(atom),
 213        shortflags([o]),  longflags(['output-file']),
 214        help('write output to FILE')]
 215
 216    , [opt(label), type(atom), default('REPORT'),
 217        shortflags([l]), longflags([label]),
 218        help('report label')]
 219
 220    , [opt(verbose),  meta('V'), type(integer), default(2),
 221        shortflags([v]),  longflags([verbosity]),
 222        help('verbosity level, 1 <= V <= 3')]
 223
 224    , [opt(path), default('/some/file/path/')]
 225    ].
 226==
 227
 228The  help  text  above  was   accessed  by  =|opt_help(ExamplesOptsSpec,
 229HelpText)|=. The options appear in the same order as in the OptsSpec.
 230
 231Given  =|ExampleOptsSpec|=,  a  command   line  (somewhat  syntactically
 232inconsistent, in order to demonstrate different calling conventions) may
 233look as follows
 234
 235==
 236ExampleArgs = [ '-d5'
 237              , '--heisenberg-threshold', '0.14'
 238              , '--distances=[1,1,2,3,5,8]'
 239              , '--iters', '7'
 240              , '-ooutput.txt'
 241              , '--rebuild-cache', 'true'
 242              , 'input.txt'
 243              , '--verbosity=2'
 244              ].
 245==
 246
 247opt_parse(ExampleOptsSpec, ExampleArgs, Opts, PositionalArgs) would then
 248succeed with
 249
 250==
 251Opts =    [ mode('SCAN')
 252          , label('REPORT')
 253          , path('/some/file/path')
 254          , threshold(0.14)
 255          , distances([1,1,2,3,5,8])
 256          , depth(7)
 257          , outfile('output.txt')
 258          , cache(true)
 259          , verbose(2)
 260          ],
 261PositionalArgs = ['input.txt'].
 262==
 263
 264Note that path('/some/file/path') showing up in Opts has a default value
 265(of the implicit type 'term'), but   no corresponding flags in OptsSpec.
 266Thus it can't be set from the  command   line.  The rest of your program
 267doesn't need to know that, of course.   This  provides an alternative to
 268the common practice of asserting  such   hard-coded  parameters  under a
 269single predicate (for instance   setting(path, '/some/file/path')), with
 270the advantage that you  may  seamlessly   upgrade  them  to command-line
 271options, should you  one  day  find  this   a  good  idea.  Just  add an
 272appropriate flag or two and a line  of help text. Similarly, suppressing
 273an option in a cluttered interface amounts to commenting out the flags.
 274
 275opt_parse/5 allows more control through an   additional argument list as
 276shown in the example below.
 277
 278==
 279?- opt_parse(ExampleOptsSpec, ExampleArgs,  Opts, PositionalArgs,
 280             [ output_functor(appl_config)
 281             ]).
 282
 283Opts =    [ appl_config(verbose, 2),
 284          , appl_config(label, 'REPORT')
 285          ...
 286          ]
 287==
 288
 289This representation may be preferable  with the empty-flag configuration
 290parameter style above (perhaps with asserting appl_config/2).
 291
 292## Notes and tips {#optparse-notes}
 293
 294    * In the example we were mostly explicit about the types. Since the
 295    default is =|term|=, which subsumes =|integer, float, atom|=, it
 296    may be possible to get away cheaper (e.g., by only giving booleans).
 297    However, it is recommended practice to always specify types:
 298    parsing becomes more reliable and error messages will be easier to interpret.
 299
 300
 301    * Note that =|-sbar|= is taken to mean =|-s bar|=, not =|-s -b -a -r|=,
 302    that is, there is no clustering of flags.
 303
 304    * =|-s=foo|= is disallowed. The rationale is that although some
 305    command-line parsers will silently interpret this as =|-s =foo|=, this is very
 306    seldom what you want. To have an option argument start with '=' (very
 307    un-recommended), say so explicitly.
 308
 309    * The example specifies the option =|depth|= twice: once as
 310    =|-d5|= and once as =|--iters 7|=. The default when encountering duplicated
 311    flags is to =|keeplast|= (this behaviour can be controlled, by ParseOption
 312    duplicated_flags).
 313
 314    * The order of the options returned by the parsing functions is the same as
 315    given on the command
 316    line, with non-overridden defaults prepended and duplicates removed
 317    as in previous item. You should not rely on this, however.
 318
 319    * Unknown flags (not appearing in OptsSpec) will throw errors. This
 320    is usually a Good Thing. Sometimes, however, you may wish to pass
 321    along flags to an external program (say, one called by shell/2), and
 322    it means duplicated effort and a maintenance headache to have to
 323    specify all possible flags for the external program explicitly (if
 324    it even can be done). On the other hand, simply taking all unknown
 325    flags as valid makes error checking much less efficient and
 326    identification of positional arguments uncertain. A better solution
 327    is to collect all arguments intended for passing along to an
 328    indirectly called program as a single argument, probably as an atom
 329    (if you don't need to inspect them first) or as a prolog term (if
 330    you do).
 331
 332@author Marcus Uneson
 333@version 0.20 (2011-04-27)
 334@tbd: validation? e.g, numbers; file path existence; one-out-of-a-set-of-atoms
 335*/
 336
 337:- predicate_options(opt_parse/5, 5,
 338                     [ allow_empty_flag_spec(boolean),
 339                       duplicated_flags(oneof([keepfirst,keeplast,keepall])),
 340                       output_functor(atom),
 341                       suppress_empty_meta(boolean)
 342                     ]).
 343
 344:- multifile
 345    error:has_type/2,
 346    parse_type/3.
 347
 348%%   opt_arguments(+OptsSpec, -Opts, -PositionalArgs) is det
 349%
 350%    Extract  commandline  options   according    to   a  specification.
 351%    Convenience predicate, assuming that command-line  arguments can be
 352%    accessed by current_prolog_flag/2 (as  in   swi-prolog).  For other
 353%    access mechanisms and/or more control, get   the args and pass them
 354%    as a list of atoms to opt_parse/4 or opt_parse/5 instead.
 355%
 356%    Opts is a list of parsed  options   in  the form Key(Value). Dashed
 357%    args not in OptsSpec are not permitted   and  will raise error (see
 358%    tip on how to  pass  unknown   flags  in  the  module description).
 359%    PositionalArgs are the remaining non-dashed   args  after each flag
 360%    has taken its argument (filling in =true= or =false= for booleans).
 361%    There are no restrictions on non-dashed   arguments and they may go
 362%    anywhere (although it is  good  practice   to  put  them last). Any
 363%    leading arguments for the runtime (up   to  and including '--') are
 364%    discarded.
 365
 366opt_arguments(OptsSpec, Opts, PositionalArgs) :-
 367    current_prolog_flag(argv, Argv),
 368    opt_parse(OptsSpec, Argv, Opts, PositionalArgs).
 369
 370%%   opt_parse(+OptsSpec, +ApplArgs, -Opts, -PositionalArgs) is det
 371%
 372%    Equivalent to opt_parse(OptsSpec, ApplArgs, Opts, PositionalArgs, []).
 373
 374
 375opt_parse(OptsSpec, ApplArgs, Opts, PositionalArgs) :-
 376    opt_parse(OptsSpec, ApplArgs, Opts, PositionalArgs, []).
 377
 378%%   opt_parse(+OptsSpec, +ApplArgs, -Opts, -PositionalArgs, +ParseOptions) is det
 379%
 380%    Parse the arguments Args (as list  of atoms) according to OptsSpec.
 381%    Any runtime arguments (typically terminated by '--') are assumed to
 382%    be removed already.
 383%
 384%    Opts is a list of parsed options   in the form Key(Value), or (with
 385%    the option functor(Func)  given)  in   the  form  Func(Key, Value).
 386%    Dashed args not in OptsSpec are not  permitted and will raise error
 387%    (see tip on how to pass unknown   flags in the module description).
 388%    PositionalArgs are the remaining non-dashed   args  after each flag
 389%    has taken its argument (filling in =true= or =false= for booleans).
 390%    There are no restrictions on non-dashed   arguments and they may go
 391%    anywhere  (although  it  is  good  practice   to  put  them  last).
 392%    ParseOptions are
 393%
 394%    * output_functor(Func)
 395%      Set the functor Func of the returned options Func(Key,Value).
 396%      Default is the special value 'OPTION' (upper-case), which makes
 397%      the returned options have form Key(Value).
 398%
 399%    * duplicated_flags(Keep)
 400%      Controls how to handle options given more than once on the commad line.
 401%      Keep is one of  =|keepfirst, keeplast, keepall|= with the obvious meaning.
 402%      Default is =|keeplast|=.
 403%
 404%    * allow_empty_flag_spec(Bool)
 405%      If true (default), a flag specification is not required (it is allowed
 406%      that both shortflags and longflags be either [] or absent).
 407%      Flagless options cannot be manipulated from the command line
 408%      and will not show up in the generated help. This is useful when you
 409%      have (also) general configuration parameters in
 410%      your OptsSpec, especially if you think they one day might need to be
 411%      controlled externally. See example in the module overview.
 412%      allow_empty_flag_spec(false) gives the more customary behaviour of
 413%      raising error on empty flags.
 414
 415
 416opt_parse(OptsSpec, ApplArgs, Opts, PositionalArgs, ParseOptions) :-
 417    opt_parse_(OptsSpec, ApplArgs, Opts, PositionalArgs, ParseOptions).
 418
 419
 420%%   opt_help(+OptsSpec, -Help:atom) is det
 421%
 422%    True when Help is a help string synthesized from OptsSpec.
 423
 424opt_help(OptsSpec, Help) :-
 425    opt_help(OptsSpec, Help, []).
 426
 427% semi-arbitrary default format settings go here;
 428% if someone needs more control one day, opt_help/3 could be exported
 429opt_help(OptsSpec, Help, HelpOptions0) :-
 430    Defaults = [ line_width(80)
 431               , min_help_width(40)
 432               , break_long_flags(false)
 433               , suppress_empty_meta(true)
 434               ],
 435    merge_options(HelpOptions0, Defaults, HelpOptions),
 436    opt_help_(OptsSpec, Help, HelpOptions).
 437
 438
 439%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% OPT_PARSE
 440
 441opt_parse_(OptsSpec0, Args0, Opts, PositionalArgs, ParseOptions) :-
 442    assertion(ground(Args0)),
 443    assertion(is_list_of_atoms(Args0)),
 444
 445    check_opts_spec(OptsSpec0, ParseOptions, OptsSpec),
 446
 447    maplist(atom_codes, Args0, Args1),
 448    parse_options(OptsSpec, Args1, Args2, PositionalArgs),
 449    add_default_opts(OptsSpec, Args2, Args3),
 450
 451    option(duplicated_flags(Keep), ParseOptions, keeplast),
 452    remove_duplicates(Keep, Args3, Args4),
 453
 454    option(output_functor(Func), ParseOptions, 'OPTION'),
 455    refunctor_opts(Func, Args4, Opts). %}}}
 456
 457
 458
 459%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% MAKE HELP
 460opt_help_(OptsSpec0, Help, HelpOptions) :-
 461    check_opts_spec(OptsSpec0, HelpOptions, OptsSpec1),
 462    include_in_help(OptsSpec1, OptsSpec2),
 463    format_help_fields(OptsSpec2, OptsSpec3),
 464    col_widths(OptsSpec3, [shortflags, metatypedef], CWs),
 465    long_flag_col_width(OptsSpec3, LongestFlagWidth),
 466    maplist(format_opt(LongestFlagWidth, CWs, HelpOptions), OptsSpec3, Lines),
 467    atomic_list_concat(Lines, Help).
 468
 469include_in_help([], []).
 470include_in_help([OptSpec|OptsSpec], Result) :-
 471    (  flags(OptSpec, [_|_])
 472    -> Result = [OptSpec|Rest]
 473    ;  Result = Rest
 474    ),
 475    include_in_help(OptsSpec, Rest).
 476
 477format_help_fields(OptsSpec0, OptsSpec) :-
 478    maplist(embellish_flag(short), OptsSpec0, OptsSpec1),
 479    maplist(embellish_flag(long), OptsSpec1, OptsSpec2),
 480    maplist(merge_meta_type_def, OptsSpec2, OptsSpec).
 481
 482merge_meta_type_def(OptSpecIn, [metatypedef(MTD)|OptSpecIn]) :-
 483    memberchk(meta(Meta), OptSpecIn),
 484    memberchk(type(Type), OptSpecIn),
 485    memberchk(default(Def), OptSpecIn),
 486    atom_length(Meta, N),
 487    (  N > 0
 488    -> format(atom(MTD), '~w:~w=~w', [Meta, Type, Def])
 489    ;  format(atom(MTD), '~w=~w', [Type, Def])
 490    ).
 491embellish_flag(short, OptSpecIn, OptSpecOut) :-
 492    memberchk(shortflags(FlagsIn), OptSpecIn),
 493    maplist(atom_concat('-'), FlagsIn, FlagsOut0),
 494    atomic_list_concat(FlagsOut0, ',',  FlagsOut),
 495    merge_options([shortflags(FlagsOut)], OptSpecIn, OptSpecOut).
 496embellish_flag(long, OptSpecIn, OptSpecOut) :-
 497    memberchk(longflags(FlagsIn), OptSpecIn),
 498    maplist(atom_concat('--'), FlagsIn, FlagsOut),
 499    merge_options([longflags(FlagsOut)], OptSpecIn, OptSpecOut).
 500
 501col_widths(OptsSpec, Functors, ColWidths) :-
 502    maplist(col_width(OptsSpec), Functors, ColWidths).
 503col_width(OptsSpec, Functor, ColWidth) :-
 504    findall(N,
 505            ( member(OptSpec, OptsSpec),
 506              M =.. [Functor, Arg],
 507              member(M, OptSpec),
 508              format(atom(Atom), '~w', [Arg]),
 509              atom_length(Atom, N0),
 510              N is N0 + 2     %separate cols with two spaces
 511            ),
 512            Ns),
 513    max_list([0|Ns], ColWidth).
 514
 515long_flag_col_width(OptsSpec, ColWidth) :-
 516    findall(FlagLength,
 517           ( member(OptSpec, OptsSpec),
 518             memberchk(longflags(LFlags), OptSpec),
 519             member(LFlag, LFlags),
 520             atom_length(LFlag, FlagLength)
 521             ),
 522            FlagLengths),
 523    max_list([0|FlagLengths], ColWidth).
 524
 525
 526format_opt(LongestFlagWidth, [SFlagsCW, MTDCW], HelpOptions, Opt, Line) :-
 527    memberchk(shortflags(SFlags), Opt),
 528
 529    memberchk(longflags(LFlags0), Opt),
 530    group_length(LongestFlagWidth, LFlags0, LFlags1),
 531    LFlagsCW is LongestFlagWidth + 2, %separate with comma and space
 532    option(break_long_flags(BLF), HelpOptions, true),
 533    (  BLF
 534    -> maplist(atomic_list_concat_(',\n'), LFlags1, LFlags2)
 535    ;  maplist(atomic_list_concat_(', '), LFlags1, LFlags2)
 536    ),
 537    atomic_list_concat(LFlags2, ',\n', LFlags),
 538
 539    memberchk(metatypedef(MetaTypeDef), Opt),
 540
 541    memberchk(help(Help), Opt),
 542    HelpIndent is LFlagsCW + SFlagsCW + MTDCW + 2,
 543    option(line_width(LW), HelpOptions, 80),
 544    option(min_help_width(MHW), HelpOptions, 40),
 545    HelpWidth is max(MHW, LW - HelpIndent),
 546    (  atom(Help)
 547    -> line_breaks(Help, HelpWidth, HelpIndent, BrokenHelp)
 548    ;  assertion(is_list_of_atoms(Help))
 549    -> indent_lines(Help, HelpIndent, BrokenHelp)
 550    ),
 551    format(atom(Line), '~w~t~*+~w~t~*+~w~t~*+~w~n',
 552      [LFlags, LFlagsCW, SFlags, SFlagsCW, MetaTypeDef, MTDCW, BrokenHelp]).
 553
 554
 555line_breaks(TextLine, LineLength, Indent, TextLines) :-
 556    atomic_list_concat(Words, ' ', TextLine),
 557    group_length(LineLength, Words, Groups0),
 558    maplist(atomic_list_concat_(' '), Groups0, Groups),
 559    indent_lines(Groups, Indent, TextLines).
 560
 561indent_lines(Lines, Indent, TextLines) :-
 562    format(atom(Separator), '~n~*|', [Indent]),
 563    atomic_list_concat(Lines, Separator, TextLines).
 564
 565atomic_list_concat_(Separator, List, Atom) :-
 566    atomic_list_concat(List, Separator, Atom).
 567
 568%group_length(10,
 569%             [here, are, some, words, you, see],
 570%             [[here are], [some words], [you see]]) %each group >= 10F
 571group_length(LineLength, Words, Groups) :-
 572    group_length_(Words, LineLength, LineLength, [], [], Groups).
 573
 574group_length_([], _, _, ThisLine, GroupsAcc, Groups) :-
 575    maplist(reverse, [ThisLine|GroupsAcc], GroupsAcc1),
 576    reverse(GroupsAcc1, Groups).
 577group_length_([Word|Words], LineLength, Remains, ThisLine, Groups, GroupsAcc) :-
 578    atom_length(Word, K),
 579    (  (Remains >= K; ThisLine = [])  %Word fits on ThisLine, or too long too fit
 580    -> Remains1 is Remains - K - 1,  %even on a new line
 581     group_length_(Words, LineLength, Remains1, [Word|ThisLine], Groups, GroupsAcc)
 582
 583                                     %Word doesn't fit on ThisLine (non-empty)
 584    ;  group_length_([Word|Words], LineLength, LineLength, [], [ThisLine|Groups], GroupsAcc)
 585    ).
 586
 587
 588%}}}
 589
 590
 591%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% OPTSSPEC DEFAULTS
 592
 593
 594add_default_defaults(OptsSpec0, OptsSpec, Options) :-
 595    option(suppress_empty_meta(SEM), Options, true),
 596    maplist(default_defaults(SEM), OptsSpec0, OptsSpec).
 597
 598default_defaults(SuppressEmptyMeta, OptSpec0, OptSpec) :-
 599    (  SuppressEmptyMeta
 600    -> Meta = ''
 601    ;  memberchk(type(Type), OptSpec0)
 602    -> meta_placeholder(Type, Meta)
 603    ;  Meta = 'T'
 604    ),
 605
 606    Defaults = [ help('')
 607             , type(term)
 608             , shortflags([])
 609             , longflags([])
 610             , default('_')
 611             , meta(Meta)
 612             ],
 613    merge_options(OptSpec0, Defaults, OptSpec).
 614    %merge_options(+New, +Old, -Merged)
 615
 616
 617meta_placeholder(boolean, 'B').
 618meta_placeholder(atom, 'A').
 619meta_placeholder(float, 'F').
 620meta_placeholder(integer, 'I').
 621meta_placeholder(term, 'T').
 622
 623
 624
 625%}}}
 626
 627
 628%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% OPTSSPEC VALIDATION
 629
 630%this is a bit paranoid, but OTOH efficiency is no issue
 631check_opts_spec(OptsSpec0, Options, OptsSpec) :-
 632    validate_opts_spec(OptsSpec0, Options),
 633    add_default_defaults(OptsSpec0, OptsSpec, Options),
 634    validate_opts_spec(OptsSpec, Options).
 635
 636validate_opts_spec(OptsSpec, ParseOptions) :-
 637    \+ invalidate_opts_spec(OptsSpec, ParseOptions).
 638
 639invalidate_opts_spec(OptsSpec, _ParseOptions) :-
 640    %invalid if not ground -- must go first for \+ to be sound
 641    ( \+ ground(OptsSpec)
 642    -> throw(error(instantiation_error,
 643                   context(validate_opts_spec/1, 'option spec must be ground')))
 644
 645    %invalid if conflicting flags
 646    ; ( member(O1, OptsSpec), flags(O1, Flags1), member(F, Flags1),
 647        member(O2, OptsSpec), flags(O2, Flags2), member(F, Flags2),
 648        O1 \= O2)
 649    -> throw(error(domain_error(unique_atom, F),
 650                   context(validate_opts_spec/1, 'ambiguous flag')))
 651
 652    %invalid if unknown opt spec
 653    ; ( member(OptSpec, OptsSpec),
 654        member(Spec, OptSpec),
 655        functor(Spec, F, _),
 656        \+ member(F, [opt, shortflags, longflags, type, help, default, meta]) )
 657    ->  throw(error(domain_error(opt_spec, F),
 658                   context(validate_opts_spec/1, 'unknown opt spec')))
 659
 660    %invalid if mandatory option spec opt(ID) is not unique in the entire Spec
 661    ; ( member(O1, OptsSpec), member(opt(Name), O1),
 662        member(O2, OptsSpec), member(opt(Name), O2),
 663        O1 \= O2)
 664    -> throw(error(domain_error(unique_atom, Name),
 665                   context(validate_opts_spec/1, 'ambiguous id')))
 666    ).
 667
 668invalidate_opts_spec(OptsSpec, _ParseOptions) :-
 669    member(OptSpec, OptsSpec),
 670    \+ member(opt(_Name), OptSpec),
 671    %invalid if mandatory option spec opt(ID) is absent
 672    throw(error(domain_error(unique_atom, OptSpec),
 673                context(validate_opts_spec/1, 'opt(id) missing'))).
 674
 675invalidate_opts_spec(OptsSpec, ParseOptions) :-
 676    member(OptSpec, OptsSpec), %if we got here, OptSpec has a single unique Name
 677    member(opt(Name), OptSpec),
 678
 679    option(allow_empty_flag_spec(AllowEmpty), ParseOptions, true),
 680
 681    %invalid if allow_empty_flag_spec(false) and no flag is given
 682    ( (\+ AllowEmpty, \+ flags(OptSpec, [_|_]))
 683    -> format(atom(Msg), 'no flag specified for option ''~w''', [Name]),
 684       throw(error(domain_error(unique_atom, _),
 685                context(validate_opts_spec/1, Msg)))
 686
 687    %invalid if any short flag is not actually single-letter
 688    ; ( memberchk(shortflags(Flags), OptSpec),
 689        member(F, Flags),
 690        atom_length(F, L),
 691        L > 1)
 692    ->  format(atom(Msg), 'option ''~w'': flag too long to be short', [Name]),
 693        throw(error(domain_error(short_flag, F),
 694                context(validate_opts_spec/1, Msg)))
 695
 696    %invalid if any option spec is given more than once
 697    ; duplicate_optspec(OptSpec,
 698      [type,opt,default,help,shortflags,longflags,meta])
 699    ->  format(atom(Msg), 'duplicate spec in option ''~w''', [Name]),
 700        throw(error(domain_error(unique_functor, _),
 701                context(validate_opts_spec/1, Msg)))
 702
 703    %invalid if unknown type
 704    ;   (   memberchk(type(Type), OptSpec),
 705            Type \== term,
 706            \+ clause(error:has_type(Type,_), _)
 707        )
 708    ->  format(atom(Msg), 'unknown type ''~w'' in option ''~w''', [Type, Name]),
 709        throw(error(type_error(flag_value, Type),
 710              context(validate_opts_spec/1, Msg)))
 711
 712    %invalid if type does not match default
 713    %note1: reverse logic: we are trying to _in_validate OptSpec
 714
 715    %note2: 'term' approves of any syntactically valid prolog term, since
 716    %if syntactically invalid, OptsSpec wouldn't have parsed
 717
 718    %note3: the special placeholder '_' creates a new variable, so no typecheck
 719    ;    (memberchk(type(Type), OptSpec),
 720          Type \= term,
 721          memberchk(default(Default), OptSpec),
 722          Default \= '_'
 723    ->   \+ must_be(Type, Default))
 724
 725    %invalidation failed, i.e., optspec is OK
 726    ; fail
 727    ).
 728
 729duplicate_optspec(_, []) :- !, fail.
 730duplicate_optspec(OptSpec, [Func|Funcs]) :-
 731    functor(F, Func, 1),
 732    findall(F, member(F, OptSpec), Xs),
 733    (Xs = [_,_|_]
 734    -> true
 735    ; duplicate_optspec(OptSpec, Funcs)
 736    ).
 737
 738
 739%}}}
 740
 741
 742%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PARSE OPTIONS
 743% NOTE:
 744% -sbar could be interpreted in two ways: as short for -s bar, and
 745% as short ('clustered') for -s -b -a -r. Here, the former interpretation
 746% is chosen.
 747% Cf http://perldoc.perl.org/Getopt/Long.html (no clustering by default)
 748
 749
 750parse_options(OptsSpec, Args0, Options, PosArgs) :-
 751    append(Args0, [""], Args1),
 752    parse_args_(Args1, OptsSpec, Args2),
 753    partition_args_(Args2, Options, PosArgs).
 754
 755%{{{ PARSE ARGS
 756
 757
 758%if arg is boolean flag given as --no-my-arg, expand to my-arg, false, re-call
 759parse_args_([Arg,Arg2|Args], OptsSpec, [opt(KID, false)|Result]) :-
 760    flag_name_long_neg(Dashed, NonDashed, Arg, []),
 761    flag_id_type(OptsSpec, NonDashed, KID, boolean),
 762    !,
 763    parse_args_([Dashed, "false", Arg2|Args], OptsSpec, Result).
 764
 765%if arg is ordinary boolean flag, fill in implicit true if arg absent; re-call
 766parse_args_([Arg,Arg2|Args], OptsSpec, Result) :-
 767    flag_name(K, Arg, []),
 768    flag_id_type(OptsSpec, K, _KID, boolean),
 769    \+ member(Arg2, ["true", "false"]),
 770    !,
 771    parse_args_([Arg, "true", Arg2 | Args], OptsSpec, Result).
 772
 773% separate short or long flag run together with its value and parse
 774parse_args_([Arg|Args], OptsSpec, [opt(KID, Val)|Result]) :-
 775    flag_name_value(Arg1, Arg2, Arg, []),
 776    \+ short_flag_w_equals(Arg1, Arg2),
 777    flag_name(K, Arg1, []),
 778    !,
 779    parse_option(OptsSpec, K, Arg2, opt(KID, Val)),
 780    parse_args_(Args, OptsSpec, Result).
 781
 782%from here, unparsed args have form
 783%  PosArg1,Flag1,Val1,PosArg2,PosArg3,Flag2,Val2, PosArg4...
 784%i.e., positional args may go anywhere except between FlagN and ValueN
 785%(of course, good programming style says they should go last, but it is poor
 786%programming style to assume that)
 787
 788parse_args_([Arg1,Arg2|Args], OptsSpec, [opt(KID, Val)|Result]) :-
 789    flag_name(K, Arg1, []),
 790    !,
 791    parse_option(OptsSpec, K, Arg2, opt(KID, Val)),
 792    parse_args_(Args, OptsSpec, Result).
 793
 794parse_args_([Arg1,Arg2|Args], OptsSpec, [pos(At)|Result]) :-
 795    \+ flag_name(_, Arg1, []),
 796    !,
 797    atom_codes(At, Arg1),
 798    parse_args_([Arg2|Args], OptsSpec, Result).
 799
 800parse_args_([""], _, []) :- !.   %placeholder, but useful for error messages
 801parse_args_([], _, []) :- !.
 802
 803short_flag_w_equals([0'-,_C], [0'=|_]) :-
 804    throw(error(syntax_error('disallowed: <shortflag>=<value>'),_)).
 805
 806
 807
 808flag_id_type(OptsSpec, FlagCodes, ID, Type) :-
 809    atom_codes(Flag, FlagCodes),
 810    member(OptSpec, OptsSpec),
 811    flags(OptSpec, Flags),
 812    member(Flag, Flags),
 813    member(type(Type), OptSpec),
 814    member(opt(ID), OptSpec).
 815
 816%{{{ FLAG DCG
 817
 818%DCG non-terminals:
 819%  flag_name(NonDashed)                  %c, flag-name, x
 820%  flag_name_short(Dashed, NonDashed)    %c, x
 821%  flag_name_long(Dashed, NonDashed)     %flag-name
 822%  flag_name_long_neg(Dashed, NonDashed) %no-flag-name
 823%  flag_value(Val)                       %non-empty string
 824%  flag_value0(Val)                      %any string, also empty
 825%  flag_name_value(Dashed, Val)          %pair of flag_name, flag_value
 826
 827
 828flag_name(NonDashed) --> flag_name_long(_, NonDashed).
 829flag_name(NonDashed) --> flag_name_short(_, NonDashed).
 830flag_name(NonDashed) --> flag_name_long_neg(_, NonDashed).
 831
 832flag_name_long_neg([0'-,0'-|Cs], Cs) --> "--no-", name_long(Cs).
 833flag_name_long([0'-,0'-|Cs], Cs) --> "--", name_long(Cs).
 834flag_name_short([0'-|C], C) --> "-", name_1st(C).
 835
 836flag_value([C|Cs]) --> [C], flag_value0(Cs).
 837flag_value0([]) --> [].
 838flag_value0([C|Cs]) --> [C], flag_value0(Cs).
 839flag_name_value(Dashed, Val) --> flag_name_long(Dashed, _), "=", flag_value0(Val).
 840flag_name_value(Dashed, Val) --> flag_name_short(Dashed, _), flag_value(Val).
 841
 842name_long([C|Cs]) --> name_1st([C]), name_rest(Cs).
 843name_1st([C]) --> [C], {name_1st(C)}.
 844name_rest([]) --> [].
 845name_rest([C|Cs]) --> [C], {name_char(C)}, name_rest(Cs).
 846name_1st(C) :- char_type(C, alpha).
 847name_char(C) :- char_type(C, alpha).
 848name_char( 0'- ). %}}}
 849
 850
 851%{{{ PARSE OPTION
 852parse_option(OptsSpec, Arg1, Arg2, opt(KID, Val)) :-
 853    (  flag_id_type(OptsSpec, Arg1, KID, Type)
 854    -> parse_val(Arg1, Type, Arg2, Val)
 855    ;  format(atom(Msg), '~s', [Arg1]),
 856     opt_help(OptsSpec, Help),        %unknown flag: dump usage on stderr
 857     nl(user_error),
 858     write(user_error, Help),
 859     throw(error(domain_error(flag_value, Msg),context(_, 'unknown flag')))
 860    ).
 861
 862
 863parse_val(Opt, Type, Cs, Val) :-
 864    catch(
 865    parse_loc(Type, Cs, Val),
 866    E,
 867    ( format('~nERROR: flag ''~s'': expected atom parsable as ~w, found ''~s'' ~n',
 868                             [Opt,                           Type,        Cs]),
 869      throw(E))
 870    ).
 871
 872%parse_loc(+Type, +ListOfCodes, -Result).
 873parse_loc(Type, _LOC, _) :-
 874    var(Type), !, throw(error(instantiation_error, _)).
 875parse_loc(_Type, LOC, _) :-
 876    var(LOC), !, throw(error(instantiation_error, _)).
 877parse_loc(boolean, Cs, true) :- atom_codes(true, Cs), !.
 878parse_loc(boolean, Cs, false) :- atom_codes(false, Cs), !.
 879parse_loc(atom, Cs, Result) :- atom_codes(Result, Cs), !.
 880parse_loc(integer, Cs, Result) :-
 881    number_codes(Result, Cs),
 882    integer(Result),
 883
 884    !.
 885parse_loc(float, Cs, Result)   :-
 886    number_codes(Result, Cs),
 887    float(Result),
 888
 889    !.
 890parse_loc(term, Cs, Result) :-
 891    atom_codes(A, Cs),
 892    term_to_atom(Result, A),
 893
 894    !.
 895parse_loc(Type, Cs, Result) :-
 896    parse_type(Type, Cs, Result),
 897    !.
 898parse_loc(Type, _Cs, _) :- %could not parse Cs as Type
 899    throw(error(type_error(flag_value, Type), _)),
 900    !. %}}}
 901%}}}
 902
 903%%  parse_type(+Type, +Codes:list(code), -Result) is semidet.
 904%
 905%   Hook to parse option text Codes to an object of type Type.
 906
 907partition_args_([], [], []).
 908partition_args_([opt(K,V)|Rest], [opt(K,V)|RestOpts], RestPos) :-
 909    !,
 910    partition_args_(Rest, RestOpts, RestPos).
 911partition_args_([pos(Arg)|Rest], RestOpts, [Arg|RestPos]) :-
 912    !,
 913    partition_args_(Rest, RestOpts, RestPos).
 914
 915
 916
 917
 918%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ADD DEFAULTS
 919
 920add_default_opts([], Opts, Opts).
 921add_default_opts([OptSpec|OptsSpec], OptsIn, Result) :-
 922    memberchk(opt(OptName), OptSpec),
 923    (  memberchk(opt(OptName, _Val), OptsIn)
 924    -> Result = OptsOut                      %value given on cl, ignore default
 925
 926    ;                                        %value not given on cl:
 927       memberchk(default('_'), OptSpec)      % no default in OptsSpec (or 'VAR'):
 928    -> Result = [opt(OptName, _) | OptsOut]  % create uninstantiated variable
 929    ;
 930       memberchk(default(Def), OptSpec),     % default given in OptsSpec
 931%       memberchk(type(Type), OptSpec),      % already typechecked
 932%       assertion(must_be(Type, Def)),
 933       Result = [opt(OptName, Def) | OptsOut]
 934    ),
 935    add_default_opts(OptsSpec, OptsIn, OptsOut).
 936
 937
 938
 939%}}}
 940
 941
 942%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% REMOVE DUPLICATES
 943remove_duplicates(_, [], []) :- !.
 944remove_duplicates(keeplast, [opt(OptName, Val) | Opts], Result) :-
 945    !,
 946    (  memberchk(opt(OptName, _), Opts)
 947    -> Result = RestOpts
 948    ;  Result = [opt(OptName, Val) | RestOpts]
 949    ),
 950    remove_duplicates(keeplast, Opts, RestOpts).
 951
 952remove_duplicates(keepfirst, OptsIn, OptsOut) :-
 953    !,
 954    reverse(OptsIn, OptsInRev),
 955    remove_duplicates(keeplast, OptsInRev, OptsOutRev),
 956    reverse(OptsOutRev, OptsOut).
 957
 958remove_duplicates(keepall, OptsIn, OptsIn) :- !.
 959remove_duplicates(K, [_|_], _) :-
 960    !,
 961    throw(error(domain_error(keep_flag, K), _)). %}}}
 962
 963
 964%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% REFUNCTOR
 965refunctor_opts(Fnct, OptsIn, OptsOut) :-
 966    maplist(refunctor_opt(Fnct), OptsIn, OptsOut).
 967
 968refunctor_opt('OPTION', opt(OptName, OptVal), Result) :-
 969    !,
 970    Result =.. [OptName, OptVal].
 971
 972refunctor_opt(F, opt(OptName, OptVal), Result) :-
 973    Result =.. [F, OptName, OptVal]. %}}}
 974
 975
 976%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ACCESSORS
 977
 978flags(OptSpec, Flags) :- memberchk(shortflags(Flags), OptSpec).
 979flags(OptSpec, Flags) :- memberchk(longflags(Flags), OptSpec). %}}}
 980
 981%{{{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% UTILS
 982is_list_of_atoms([]).
 983is_list_of_atoms([X|Xs]) :- atom(X), is_list_of_atoms(Xs).
 984%}}}