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 ( 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 ( 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 ( (\+ , \+ 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%}}}