123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730(*
* optParse - Functions for parsing command line arguments.
* Copyright (C) 2004 Bardur Arantsson
*
* Heavily influenced by the optparse.py module from the Python
* standard library, but with lots of adaptation to the 'Ocaml Way'
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version,
* with the special exception on linking described in file LICENSE.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*)openPrintfletterminal_width=tryint_of_string(Sys.getenv"COLUMNS")(* Might as well use it if it's there... *)withFailure_->80|Not_found->80moduleGetOpt=structtypeaction=string->stringlist->unittypelong_opt=string*int*actiontypeshort_opt=char*int*actionexceptionErrorof(string*string)letsplit1haystackneedle=trylet(h,x)=BatString.splithaystack~by:needleinh,[x]withNot_found->haystack,[]letfind_optformat_nameoptionss=letrecloopl=matchlwith(x,y,z)::t->ifx=sthenx,y,zelseloopt|[]->raise(Error(format_names,"no such option"))inloopoptionsletfind_short_optoptions=find_opt(func->sprintf"-%c"c)optionsletfind_long_optoptions=find_opt(funs->"--"^s)optionsletparseonly_leading_optsotherfind_short_optfind_long_optargs=letrecloopargs=letgather_argsnamenargs=tryBatList.split_nthnargswithInvalid_argument_->raise(Error(name,"missing required arguments"))inletgather_long_optsargs=let(h,t)=split1s"="inlet(_,nargs,action)=find_long_opt(BatString.slice~first:2h)inlet(accum,args')=gather_argsh(nargs-List.lengtht)argsinactionh(t@accum);args'inletrecgather_short_opt_concatseen_argsskargs=ifk<String.lengthsthenletostr=sprintf"-%c"s.[k]and(_,nargs,action)=find_short_opts.[k]inifnargs=0thenbeginactionostr[];gather_short_opt_concatseen_argss(k+1)argsendelseifnotseen_argsthenlet(accum,args')=gather_argsostrnargsargsinactionostraccum;gather_short_opt_concattrues(k+1)args'elseraise(Error(sprintf"-%c"s.[k],sprintf"option list '%s' already contains an option requiring an argument"s))elseargsinletgather_short_optskargs=letostr=sprintf"-%c"s.[k]inlet(_,nargs,action)=find_short_opts.[k]inifnargs=0thengather_short_opt_concatfalseskargselselet(accum,args')=leth=BatString.slice~first:(k+1)sinifString.lengthh=0thengather_argsostrnargsargselselet(t,args'')=gather_argsostr(nargs-1)argsinh::t,args''inactionostraccum;args'inmatchargswith[]->[]|arg::args'->ifarg="--"thenargs'elseifBatString.starts_witharg"--"thenloop(gather_long_optargargs')elseifarg="-"thenbeginotherarg;loopargs'endelseifBatString.starts_witharg"-"thenloop(gather_short_optarg1args')elseifonly_leading_optsthenarg::args'elsebeginotherarg;loopargs'endinletargs'=loopargsinList.iterotherargs'endmoduleOpt=structexceptionNo_valueexceptionOption_errorofstring*stringexceptionOption_helptype'at={option_set:string->stringlist->unit;option_set_value:'a->unit;option_get:unit->'aoption;option_metavars:stringlist;option_defhelp:stringoption}letgetopt=matchopt.option_get()withSomex->x|None->raiseNo_valueletsetoptv=opt.option_set_valuevletis_setopt=BatOption.is_some(opt.option_get())letoptopt=opt.option_get()letvalue_optionmetavardefaultcoerceerrfmt=letdata=refdefaultin{option_metavars=[metavar];option_defhelp=None;option_get=(fun_->!data);option_set_value=(funx->data:=Somex);option_set=(funoptionargs->letarg=List.hdargsintrydata:=Some(coercearg)withexn->raise(Option_error(option,errfmtexnarg)))}letcallback_optionmetavarcoerceerrfmtf={option_metavars=[metavar];option_defhelp=None;option_get=(fun_->Some());option_set_value=(fun()->());option_set=(funoptionargs->letarg=List.hdargsinletdatum=refNoneinbegintrydatum:=Some(coercearg)withexn->raise(Option_error(option,errfmtexnarg))end;BatOption.mayf!datum)}endmoduleStdOpt=structopenOptletstore_const?defaultconst=letdata=refdefaultin{option_metavars=[];option_defhelp=None;option_get=(fun_->!data);option_set_value=(funx->data:=Somex);option_set=fun__->data:=Someconst}letstore_true()=store_const~default:falsetrueletstore_false()=store_const~default:truefalseletint_option?default?(metavar="INT")()=value_optionmetavardefaultint_of_string(fun_s->sprintf"invalid integer value '%s'"s)letint_callback?(metavar="INT")=callback_optionmetavarint_of_string(fun_s->sprintf"invalid integer value '%s'"s)letfloat_option?default?(metavar="FLOAT")()=value_optionmetavardefaultfloat_of_string(fun_s->sprintf"invalid floating point value '%s'"s)letfloat_callback?(metavar="FLOAT")=callback_optionmetavarfloat_of_string(fun_s->sprintf"invalid floating point value '%s'"s)letstr_option?default?(metavar="STR")()=value_optionmetavardefault(funs->s)(fun__->"cannot happen")letstr_callback?(metavar="STR")=callback_optionmetavar(funs->s)(fun__->"cannot happen")letany_option?(default=None)?(metavar="val")f=value_optionmetavardefaultf(fun__->"invalid option")letcount_option?(dest=ref0)?(increment=1)()={option_metavars=[];option_defhelp=None;option_get=(fun_->Some!dest);option_set_value=(funx->dest:=x);option_set=fun__->dest:=!dest+increment}letincr_option?(dest=ref0)=count_option~dest~increment:1letdecr_option?(dest=ref0)=count_option~dest~increment:(-1)lethelp_option()={option_metavars=[];option_defhelp=Some"show this help message and exit";option_get=(fun_->raiseNo_value);option_set_value=(fun_->());option_set=fun__->raiseOption_help}letversion_optionvfunc={option_metavars=[];option_defhelp=Some"show program's version and exit";option_get=(fun_->raiseNo_value);option_set_value=(fun_->());option_set=fun__->print_endline(vfunc());exit0}endmoduleFormatter=struct(* Note that the whitespace regexps must NOT treat the non-breaking
space character as whitespace. *)letwhitespace="\t\n\013\014\r "letsplit_into_chunkss=letbuf=Buffer.create(String.lengths)inletflush()=lets=Buffer.contentsbufinBuffer.clearbuf;sinletrecloopstateaccumi=if(i<String.lengths)thenif((state&¬(String.containswhitespaces.[i]))||((notstate)&&String.containswhitespaces.[i]))thenifBuffer.lengthbuf>0thenloop(notstate)(flush()::accum)ielseloop(notstate)accumielsebeginBuffer.add_charbufs.[i];loopstateaccum(i+1)endelseifBuffer.lengthbuf>0thenflush()::accumelseaccuminList.rev(loopfalse[]0)letis_whitespaces=letrecloopi=ifi<String.lengthsthenifString.containswhitespaces.[i]thenloop(i+1)elsefalseelsetrueinloop0letexpand_tabs?(tab_size=8)s=letlen=String.lengthsinletspacesn=String.maken' 'andb=Buffer.createleninletrecexpandicol=ifi<lenthenmatchs.[i]with'\t'->letn=tab_size-colmodtab_sizeinBuffer.add_stringb(spacesn);expand(i+1)(col+n)|'\n'->Buffer.add_stringb"\n";expand(i+1)0|c->Buffer.add_charbc;expand(i+1)(col+1)inexpand00;Buffer.contentsbletwrap?(initial_indent=0)?(subsequent_indent=0)text_width=letwrap_chunks_linewidthacc=letrecwrap(chunks,cur_line,cur_len)=matchchunkswith[]->[],cur_line,cur_len|hd::tl->letl=String.lengthhdinifcur_len+l<=widththenwrap(tl,hd::cur_line,cur_len+l)elsechunks,cur_line,cur_leninwrapaccinletwrap_long_last_wordwidth(chunks,cur_line,cur_len)=matchchunkswith[]->[],cur_line,cur_len|hd::tl->letl=String.lengthhdinifl>widththenmatchcur_linewith[]->tl,[hd],cur_len+l|_->chunks,cur_line,cur_lenelsechunks,cur_line,cur_leninletwrap_remove_last_ws(chunks,cur_line,cur_len)=matchcur_linewith[]->chunks,cur_line,cur_len|hd::tl->ifis_whitespacehdthenchunks,tl,cur_len-String.lengthhdelsechunks,cur_line,cur_leninletrecwrap_chunks_lineschunkslines=letindent=matchlineswith[]->initial_indent|_->subsequent_indentinletwidth=_width-indentinmatchchunkswithhd::tl->ifis_whitespacehd&&lines<>[]thenwrap_chunks_linestllineselse(* skip *)let(chunks',cur_line,_)=wrap_remove_last_ws(wrap_long_last_wordwidth(wrap_chunks_linewidth(chunks,[],0)))inwrap_chunks_lineschunks'((String.makeindent' '^String.concat""(List.revcur_line))::lines)|[]->List.revlinesinletchunks=split_into_chunks(expand_tabstext)inwrap_chunks_lineschunks[]letfill?(initial_indent=0)?(subsequent_indent=0)textwidth=String.concat"\n"(wrap~initial_indent~subsequent_indenttextwidth)typet={indent:unit->unit;dedent:unit->unit;format_usage:string->string;format_heading:string->string;format_description:string->string;format_option:charlist*stringlist->stringlist->stringoption->string}letformat_option_stringsshort_first(snames,lnames)metavars=letmetavar=String.concat" "metavarsinletlopts=List.map(matchmetavarwith""->(funz->sprintf"--%s"z)|_->funz->sprintf"--%s=%s"zmetavar)lnamesandsopts=List.map(funx->sprintf"-%c%s"xmetavar)snamesinmatchshort_firstwithtrue->String.concat", "(sopts@lopts)|false->String.concat", "(lopts@sopts)letindented_formatter?level:(extlevel=ref0)?indent:(extindent=ref0)?(indent_increment=2)?(max_help_position=24)?(width=terminal_width-1)?(short_first=true)()=letindent=ref0andlevel=ref0inlethelp_position=refmax_help_positionandhelp_width=ref(width-max_help_position)in{indent=(fun()->indent:=!indent+indent_increment;level:=!level+1;extindent:=!indent;extlevel:=!level);dedent=(fun()->indent:=!indent-indent_increment;level:=!level-1;assert(!level>=0);extindent:=!indent;extlevel:=!level);format_usage=(funusage->sprintf"usage: %s\n"usage);format_heading=(funheading->sprintf"%*s%s:\n\n"!indent""heading);format_description=(fundescription->letx=fill~initial_indent:(!indent)~subsequent_indent:(!indent)description(width-!indent)inifnot(BatString.ends_withx"\n")thenx^"\n\n"elsex^"\n");format_option=funnamesmetavarshelp->letopt_width=!help_position-!indent-2inletopt_strings=format_option_stringsshort_firstnamesmetavarsinletbuf=Buffer.create256inletindent_first=ifString.lengthopt_strings>opt_widththenbeginbprintfbuf"%*s%s\n"!indent""opt_strings;!help_positionendelsebeginbprintfbuf"%*s%-*s "!indent""opt_widthopt_strings;0endinBatOption.may(funoption_help->letlines=wrapoption_help!help_widthinmatchlineswithh::t->bprintfbuf"%*s%s\n"indent_first""h;List.iter(funx->bprintfbuf"%*s%s\n"!help_position""x)t|[]->())help;letcontents=Buffer.contentsbufinifString.lengthcontents>0&¬(BatString.ends_withcontents"\n")thencontents^"\n"elsecontents}lettitled_formatter?(level=ref0)?(indent=ref0)?(indent_increment=0)?(max_help_position=24)?(width=terminal_width-1)?(short_first=true)()=letformatter=indented_formatter~level~indent~indent_increment~max_help_position~width~short_first()inletformat_headingh=letc=match!levelwith0->'='|1->'-'|_->failwith"titled_formatter: Too much indentation"insprintf"%*s%s\n%*s%s\n\n"!indent""(##V<5##String.capitalize##V>=5##String.capitalize_asciih)!indent""(String.make(String.lengthh)c)inletformat_usageusage=sprintf"%s %s\n"(format_heading"Usage")usagein{formatterwithformat_usage=format_usage;format_heading=format_heading}endopenOptopenFormattermoduleOptParser=structexceptionOption_conflictofstringtypegroup={og_heading:string;og_description:stringoption;og_options:((charlist*stringlist)*stringlist*stringoption)BatRefList.t;og_children:groupBatRefList.t}typet={op_usage:string;op_suppress_usage:bool;op_only_leading:bool;op_prog:string;op_formatter:Formatter.t;op_long_options:GetOpt.long_optBatRefList.t;op_short_options:GetOpt.short_optBatRefList.t;op_groups:group}letunprogifyoptparsers=BatString.nreplace~str:s~sub:"%prog"~by:optparser.op_progletaddoptparser?(group=optparser.op_groups)?help?(hide=false)?short_name?(short_names=[])?long_name?(long_names=[])opt=letlnames=matchlong_namewithNone->long_names|Somex->x::long_namesandsnames=matchshort_namewithNone->short_names|Somex->x::short_namesiniflnames=[]&&snames=[]thenfailwith"Options must have at least one name"else(* Checking for duplicates: *)letsnames'=List.fold_left(funr(x,_,_)->x::r)[](BatRefList.to_listoptparser.op_short_options)andlnames'=List.fold_left(funr(x,_,_)->x::r)[](BatRefList.to_listoptparser.op_long_options)inletsconf=List.filter(fune->List.exists((=)e)snames')snamesandlconf=List.filter(fune->List.exists((=)e)lnames')lnamesinifList.lengthsconf>0thenraise(Option_conflict(sprintf"-%c"(List.hdsconf)))elseifList.lengthlconf>0thenraise(Option_conflict(sprintf"--%s"(List.hdlconf)));(* Add to display list. *)ifnothidethenBatRefList.addgroup.og_options((snames,lnames),opt.option_metavars,(matchhelpwithNone->opt.option_defhelp|Some_->help));(* Getopt: *)letnargs=List.lengthopt.option_metavarsinList.iter(funshort->BatRefList.addoptparser.op_short_options(short,nargs,opt.option_set))snames;List.iter(funlong->BatRefList.addoptparser.op_long_options(long,nargs,opt.option_set))lnamesletadd_groupoptparser?(parent=optparser.op_groups)?descriptionheading=letg={og_heading=heading;og_description=description;og_options=BatRefList.empty();og_children=BatRefList.empty()}inBatRefList.addparent.og_childreng;gletmake?(usage="%prog [options]")?description?version?(suppress_usage=false)?(suppress_help=false)?(only_leading_opts=false)?prog?(formatter=Formatter.indented_formatter())()=letoptparser={op_usage=usage;op_suppress_usage=suppress_usage;op_only_leading=only_leading_opts;op_prog=BatOption.default(Filename.basenameSys.argv.(0))prog;op_formatter=formatter;op_short_options=BatRefList.empty();op_long_options=BatRefList.empty();op_groups={og_heading="options";og_options=BatRefList.empty();og_children=BatRefList.empty();og_description=description}}inBatOption.may(* Add version option? *)(funversion->addoptparser~long_name:"version"(StdOpt.version_option(fun()->unprogifyoptparserversion)))version;ifnotsuppress_helpthen(* Add help option? *)addoptparser~short_name:'h'~long_name:"help"(StdOpt.help_option());optparserletformat_usageoptparsereol=matchoptparser.op_suppress_usagewithtrue->""|false->unprogifyoptparser(optparser.op_formatter.format_usageoptparser.op_usage)^eolleterroroptparser?(chn=stderr)?(status=1)message=fprintfchn"%s%s: %s\n"(format_usageoptparser"\n")optparser.op_progmessage;flushchn;exitstatusletusageoptparser?(chn=stdout)()=letrecloopg=(* Heading: *)output_stringchn(optparser.op_formatter.format_headingg.og_heading);optparser.op_formatter.indent();(* Description: *)BatOption.may(funx->output_stringchn(optparser.op_formatter.format_descriptionx))g.og_description;(* Options: *)BatRefList.iter(fun(names,metavars,help)->output_stringchn(optparser.op_formatter.format_optionnamesmetavarshelp))g.og_options;(* Child groups: *)output_stringchn"\n";BatRefList.iterloopg.og_children;optparser.op_formatter.dedent()inoutput_stringchn(format_usageoptparser"\n");loopoptparser.op_groups;flushchnletparseoptparser?(first=0)?lastargv=letargs=BatRefList.empty()andn=matchlastwithNone->Array.lengthargv-first|Somem->m-first+1inbegintryGetOpt.parseoptparser.op_only_leading(BatRefList.pushargs)(GetOpt.find_short_opt(BatRefList.to_listoptparser.op_short_options))(GetOpt.find_long_opt(BatRefList.to_listoptparser.op_long_options))(Array.to_list(Array.subargvfirstn))withGetOpt.Error(opt,errmsg)->erroroptparser(sprintf"option '%s': %s"opterrmsg)|Option_error(opt,errmsg)->erroroptparser(sprintf"option '%s': %s"opterrmsg)|Option_help->usageoptparser();exit0end;List.rev(BatRefList.to_listargs)letparse_argvoptparser=parseoptparser~first:1Sys.argvend