12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122open!Importopen!Std_internalincludeCommand_intf(* in order to define expect tests, we want to raise rather than exit if the code is
running in the test runner process *)letraise_instead_of_exit=matchPpx_inline_test_lib.Runtime.testingwith|`Testing`Am_test_runner->true|`Testing`Am_child_of_test_runner|`Not_testing->false;;exceptionExit_calledof{status:int}[@@derivingsexp_of](* [raise_instead_of_exit]-respecting wrappers for [exit] and functions that call it *)includestructletexitstatus=ifraise_instead_of_exitthenraise(Exit_called{status})elseexitstatus;;moduleExn=structletto_string=Exn.to_stringlethandle_uncaught_and_exitf=ifraise_instead_of_exitthen(tryf()with|exn->print_s[%sexp(exn:exn)])elseExn.handle_uncaught_and_exitf;;endendletunwordsxs=String.concat~sep:" "xsletunparagraphsxs=String.concat~sep:"\n\n"xsexceptionFailed_to_parse_command_lineofstringletdiefmt=Printf.ksprintf(funmsg()->raise(Failed_to_parse_command_linemsg))fmt;;lethelp_screen_compareab=matcha,bwith|_,"[-help]"->-1|"[-help]",_->1|_,"[-version]"->-1|"[-version]",_->1|_,"[-build-info]"->-1|"[-build-info]",_->1|_,"help"->-1|"help",_->1|_,"version"->-1|"version",_->1|_->0;;moduleFormat:sigmoduleV1:sigtypet={name:string;doc:string;aliases:stringlist}[@@derivingsexp]valsort:tlist->tlistvalto_string:tlist->stringvalword_wrap:string->int->stringlistendend=structmoduleV1=structtypet={name:string;doc:string;aliases:stringlist}[@@derivingsexp]letsortts=List.stable_sortts~compare:(funab->help_screen_comparea.nameb.name);;letword_wraptextwidth=letchunks=String.splittext~on:'\n'inList.concat_mapchunks~f:(funtext->letwords=String.splittext~on:' '|>List.filter~f:(funword->not(String.is_emptyword))inmatchList.foldwords~init:None~f:(funaccword->Some(matchaccwith|None->[],word|Some(lines,line)->(* efficiency is not a concern for the string lengths we expect *)letline_and_word=line^" "^wordinifString.lengthline_and_word<=widththenlines,line_and_wordelseline::lines,word))with|None->[]|Some(lines,line)->List.rev(line::lines));;letto_stringts=letn=List.foldts~init:0~f:(funacct->Int.maxacc(String.lengtht.name))inletnum_cols=80in(* anything more dynamic is likely too brittle *)letextendx=letslack=n-String.lengthxinx^String.makeslack' 'inletlhs_width=n+4inletlhs_pad=String.makelhs_width' 'inString.concat(List.mapts~f:(funt->letrowskv=letvs=word_wrapv(num_cols-lhs_width)inmatchvswith|[]->[" ";k;"\n"]|v::vs->letfirst_line=[" ";extendk;" ";v;"\n"]inletrest_lines=List.mapvs~f:(funv->[lhs_pad;v;"\n"])inList.concat(first_line::rest_lines)inString.concat(List.concat(rowst.namet.doc::(matcht.aliaseswith|[]->[]|[x]->[rows""(sprintf"(alias: %s)"x)]|xs->[rows""(sprintf"(aliases: %s)"(String.concat~sep:", "xs))])))));;endend(* universal maps are used to pass around values between different bits
of command line parsing code without having a huge impact on the
types involved
1. passing values from parsed args to command-line autocomplete functions
2. passing special values to a base commands that request them in their spec
* expanded subcommand path
* args passed to the base command
* help text for the base command
*)moduleEnv=structincludeUniv_mapletkey_createname=Univ_map.Key.create~namesexp_of_opaqueletmulti_add=Univ_map.Multi.addletset_with_default=Univ_map.With_default.setendmoduleCompleter=structtypet=(Env.t->part:string->stringlist)optionletrun_and_exittenv~part:never_returns=Option.itert~f:(funcompletions->List.iter~f:print_endline(completionsenv~part));exit0;;endmoduleArg_type=structtype'at={parse:string->('a,exn)Result.t;complete:Completer.t;key:'aUniv_map.Multi.Key.toption}letcreate?complete?keyof_string=letparsex=Result.try_with(fun()->of_stringx)in{parse;key;complete};;letmap?keyt~f=letparsestr=Result.map(t.parsestr)~finletcomplete=t.completein{parse;complete;key};;letstring=createFn.idletint=createInt.of_stringletchar=createChar.of_stringletfloat=createFloat.of_stringletdate=createDate.of_stringletpercent=createPercent.of_stringlethost_and_port=createHost_and_port.of_stringletsexp=createSexp.of_stringletsexp_convof_sexp=create(funs->of_sexp(Sexp.of_strings))letof_map?keymap=create?key~complete:(fun_~part:prefix->List.filter_map(Map.to_alistmap)~f:(fun(name,_)->ifString.is_prefixname~prefixthenSomenameelseNone))(funarg->matchMap.findmapargwith|Somev->v|None->failwithf"valid arguments: {%s}"(String.concat~sep:","(Map.keysmap))());;letof_alist_exn?keyalist=matchString.Map.of_alistalistwith|`Okmap->of_map?keymap|`Duplicate_keykey->failwithf"Command.Spec.Arg_type.of_alist_exn: duplicate key %s"key();;letbool=of_alist_exn["true",true;"false",false]letcomma_separated?(allow_empty=false)?key?(strip_whitespace=false)?(unique_values=false)t=letstrip=ifstrip_whitespacethenfunstr->String.stripstrelseFn.idinletcomplete=Option.mapt.complete~f:(funcomplete_eltenv~part->letprefixes,suffix=matchString.splitpart~on:','|>List.revwith|[]->[],part|hd::tl->List.revtl,hdinletis_allowed=ifnotunique_valuesthenfun(_:string)->trueelse(letseen_already=prefixes|>List.map~f:strip|>String.Set.of_listinfunchoice->not(Set.memseen_already(stripchoice)))inletchoices=matchList.filter(complete_eltenv~part:suffix)~f:(funchoice->(not(String.memchoice','))&&is_allowedchoice)with(* If there is exactly one choice to auto-complete, add a second choice with
a trailing comma so that auto-completion will go to the end but bash
won't add a space. If there are multiple choices, or a single choice
that must be final, there is no need to add a dummy option. *)|[choice]->[choice;choice^","]|choices->choicesinList.mapchoices~f:(funchoice->String.concat~sep:","(prefixes@[choice])))inletof_stringstring=letstring=stripstringinifString.is_emptystringthenifallow_emptythen[]elsefailwith"Command.Spec.Arg_type.comma_separated: empty list not allowed"elseList.map(String.splitstring~on:',')~f:(funstr->Result.ok_exn(t.parse(stripstr)))increate?key?completeof_string;;moduleExport=structletstring=stringletint=intletchar=charletfloat=floatletbool=boolletdate=dateletpercent=percentlethost_and_port=host_and_portletsexp=sexpletsexp_conv=sexp_convendendmoduleFlag=structmoduleNum_occurrences=structtypet={at_least_once:bool;at_most_once:bool}[@@derivingcompare,enumerate,fields,sexp_of]letto_help_stringtname=let{at_least_once;at_most_once}=tinletdescription=ifat_least_oncethennameelsesprintf"[%s]"nameinifat_most_oncethendescriptionelsesprintf"%s ..."description;;let%expect_test"to_help_string"=List.iter[%all:t]~f:(funt->print_s[%message""~_:(t:t)(to_help_stringt"name")]);[%expect{|
(((at_least_once false) (at_most_once false)) "[name] ...")
(((at_least_once true) (at_most_once false)) "name ...")
(((at_least_once false) (at_most_once true)) [name])
(((at_least_once true) (at_most_once true)) name) |}];;letto_help_string_deprecated{at_least_once;at_most_once=_}name=to_help_string{at_least_once;at_most_once=true}name;;letany={at_least_once=false;at_most_once=false}letat_least_once={at_least_once=true;at_most_once=false}letat_most_once={at_least_once=false;at_most_once=true}letexactly_once={at_least_once=true;at_most_once=true}endtypeaction=|No_argof(Env.t->Env.t)|Argof(Env.t->string->Env.t)*Completer.t|Restof(Env.t->stringlist->Env.t)moduleInternal=structtypet={name:string;aliases:stringlist;action:action;doc:string;num_occurrences:Num_occurrences.t;check_available:Env.t->unit;name_matching:[`Prefix|`Full_match_required]}letwrap_if_optionaltx=Num_occurrences.to_help_stringt.num_occurrencesxmoduleDoc=structtypet={arg_doc:stringoption;doc:string}letparse~action~doc=letarg_doc=match(action:action)with|No_arg_->None|Rest_|Arg_->(matchString.lsplit2doc~on:' 'with|None|Some("",_)->(matchactionwith|Arg_->Some("_",doc)|Rest_|No_arg_->None)|Some(arg,doc)->Some(arg,doc))inmatcharg_docwith|None->{doc=String.stripdoc;arg_doc=None}|Some(arg_doc,doc)->{doc=String.stripdoc;arg_doc=Somearg_doc};;letconcat~name~arg_doc=matcharg_docwith|None->name|Somearg_doc->name^" "^arg_doc;;endmoduleDeprecated=structletwrap_if_optionaltx=Num_occurrences.to_help_string_deprecatedt.num_occurrencesx;;(* flag help in the format of the old command. used for injection *)lethelp({name;doc;aliases;action;num_occurrences=_;check_available=_;name_matching=_}ast)=ifString.is_prefixdoc~prefix:" "then(name,String.lstripdoc)::List.mapaliases~f:(funx->x,sprintf"same as \"%s\""name)else(let{Doc.arg_doc;doc}=Doc.parse~action~docin(wrap_if_optionalt(Doc.concat~name~arg_doc),doc)::List.mapaliases~f:(funx->(wrap_if_optionalt(Doc.concat~name:x~arg_doc),sprintf"same as \"%s\""name)));;endletalign({name;doc;aliases;action;num_occurrences=_;check_available=_;name_matching=_}ast)=let{Doc.arg_doc;doc}=Doc.parse~action~docinletname=wrap_if_optionalt(Doc.concat~name~arg_doc)in{Format.V1.name;doc;aliases};;letcreateflags=matchString.Map.of_alist(List.mapflags~f:(funflag->flag.name,flag))with|`Duplicate_keyflag->failwithf"multiple flags named %s"flag()|`Okmap->List.concat_mapflags~f:(funflag->flag.name::flag.aliases)|>List.find_a_dup~compare:[%compare:string]|>Option.iter~f:(funx->failwithf"multiple flags or aliases named %s"x());map;;endtype'astate={action:action;read:Env.t->'a;num_occurrences:Num_occurrences.t}type'at=string->'astateletarg_flagnamearg_typereadwritenum_occurrences={read;num_occurrences;action=(letupdateenvarg=matcharg_type.Arg_type.parseargwith|Errorexn->die"failed to parse %s value %S.\n%s"namearg(Exn.to_stringexn)()|Okarg->letenv=writeenvargin(matcharg_type.Arg_type.keywith|None->env|Somekey->Env.multi_addenvkeyarg)inArg(update,arg_type.Arg_type.complete))};;letmap_flagt~finput=let{action;read;num_occurrences}=tinputin{action;read=(funenv->f(readenv));num_occurrences};;letwrite_optionnamekeyenvarg=Env.updateenvkey~f:(function|None->arg|Some_->die"flag %s passed more than once"name());;letrequired_value?defaultarg_typenamenum_occurrences=letkey=Env.Key.create~name[%sexp_of:_]inletreadenv=matchEnv.findenvkeywith|Somev->v|None->(matchdefaultwith|Somev->v|None->die"missing required flag: %s"name())inletwriteenvarg=write_optionnamekeyenvarginarg_flagnamearg_typereadwritenum_occurrences;;letrequiredarg_typename=required_valuearg_typenameNum_occurrences.exactly_onceletoptional_with_defaultdefaultarg_typename=required_value~defaultarg_typenameNum_occurrences.at_most_once;;letoptionalarg_typename=letkey=Env.Key.create~name[%sexp_of:_]inletreadenv=Env.findenvkeyinletwriteenvarg=write_optionnamekeyenvarginarg_flagnamearg_typereadwriteNum_occurrences.at_most_once;;letno_arg_general~key_value~deprecated_hookname=letkey=Env.Key.create~name[%sexp_of:unit]inletreadenv=Env.memenvkeyinletwriteenv=ifEnv.memenvkeythendie"flag %s passed more than once"name()elseEnv.setenvkey()inletactionenv=letenv=Option.foldkey_value~init:env~f:(funenv(key,value)->Env.set_with_defaultenvkeyvalue)inwriteenvinletaction=matchdeprecated_hookwith|None->action|Somef->funenv->letenv=actionenvinf();envin{read;action=No_argaction;num_occurrences=Num_occurrences.at_most_once};;letno_argname=no_arg_generalname~key_value:None~deprecated_hook:Noneletno_arg_register~key~valuename=no_arg_generalname~key_value:(Some(key,value))~deprecated_hook:None;;letno_arg_somevalue=map_flagno_arg~f:(function|true->Somevalue|false->None);;letlistedarg_typename=letkey=Env.With_default.Key.create~default:[]~name[%sexp_of:_list]inletreadenv=List.rev(Env.With_default.findenvkey)inletwriteenvarg=Env.With_default.changeenvkey~f:(funlist->arg::list)inarg_flagnamearg_typereadwriteNum_occurrences.any;;letone_or_morearg_typename=letkey=Env.With_default.Key.create~default:Fqueue.empty~name[%sexp_of:_Fqueue.t]inletreadenv=matchFqueue.to_list(Env.With_default.findenvkey)with|first::rest->first,rest|[]->die"missing required flag: %s"name()inletwriteenvarg=Env.With_default.changeenvkey~f:(funq->Fqueue.enqueueqarg)inarg_flagnamearg_typereadwriteNum_occurrences.at_least_once;;letescape_general~deprecated_hookname=letkey=Env.Key.create~name[%sexp_of:stringlist]inletactionenvcmd_line=Env.setenvkeycmd_lineinletreadenv=Env.findenvkeyinletaction=matchdeprecated_hookwith|None->action|Somef->funenvx->fx;actionenvxin{action=Restaction;read;num_occurrences=Num_occurrences.at_most_once};;letno_arg_abort~exit_name={action=No_arg(fun_->never_returns(exit()));num_occurrences=Num_occurrences.at_most_once;read=(fun_->())};;letescapename=escape_general~deprecated_hook:NonenamemoduleDeprecated=structletno_arg~hookname=no_arg_general~deprecated_hook:(Somehook)~key_value:Nonename;;letescape~hook=escape_general~deprecated_hook:(Somehook)endendmodulePath:sigtypetvalempty:tvalcreate:path_to_exe:string->tvalof_parts:stringlist->tvalappend:t->subcommand:string->tvalreplace_first:t->from:string->to_:string->tvalparts:t->stringlistvalparts_exe_basename:t->stringlistvalto_string:t->stringvalto_string_dots:t->stringvalpop_help:t->tvallength:t->intvalis_empty:t->boolend=structtypet=stringlistletempty=[]letcreate~path_to_exe=[path_to_exe]letof_partsparts=List.revpartsletappendt~subcommand=subcommand::tletparts=List.revletparts_exe_basenamet=matchList.revtwith|[]->[]|hd::tl->Filename.basenamehd::tl;;letto_stringt=unwords(parts_exe_basenamet)letlength=List.lengthletreplace_firstt~from~to_=letrecauxparts~acc~from~to_=matchpartswith|[]->acc|hd::tl->ifString.(=)hdfromthenList.rev_appendtl(to_::acc)elseauxtl~acc:(hd::acc)~from~to_inaux(partst)~acc:[]~from~to_;;letpop_help=function|"help"::t->t|_->assertfalse;;letto_string_dotst=(matchtwith|[]->[]|last::init->last::List.mapinit~f:(Fn.const"."))|>to_string;;letis_empty=List.is_emptyendmoduleAnons=structmoduleGrammar:sigtypetvalzero:tvalone:string->tvalmany:t->tvalmaybe:t->tvalconcat:tlist->tvalad_hoc:usage:string->tincludeInvariant.Swithtypet:=tmoduleSexpable:sigmoduleV1:sigtypet=|Zero|Oneofstring|Manyoft|Maybeoft|Concatoftlist|Ad_hocofstring[@@derivingbin_io,compare,sexp]valusage:t->stringendtypet=V1.t[@@derivingbin_io,compare,sexp]endvalto_sexpable:t->Sexpable.tvalnames:t->stringlistend=structmoduleSexpable=structmoduleV1=structtypet=|Zero|Oneofstring|Manyoft|Maybeoft|Concatoftlist|Ad_hocofstring[@@derivingbin_io,compare,sexp]letrecinvariantt=Invariant.invariant[%here]t[%sexp_of:t](fun()->matchtwith|Zero->()|One_->()|ManyZero->failwith"Many Zero should be just Zero"|Manyt->invariantt|MaybeZero->failwith"Maybe Zero should be just Zero"|Maybet->invariantt|Concat[]|Concat[_]->failwith"Flatten zero and one-element Concat"|Concatts->List.iterts~f:invariant|Ad_hoc_->());;lett_of_sexpsexp=lett=[%of_sexp:t]sexpininvariantt;t;;letrecusage=function|Zero->""|Oneusage->usage|ManyZero->failwith"bug in command.ml"|Many(One_ast)->sprintf"[%s ...]"(usaget)|Manyt->sprintf"[(%s) ...]"(usaget)|MaybeZero->failwith"bug in command.ml"|Maybet->sprintf"[%s]"(usaget)|Concatts->String.concat~sep:" "(List.mapts~f:usage)|Ad_hocusage->usage;;endincludeV1endtypet=Sexpable.V1.t=|Zero|Oneofstring|Manyoft|Maybeoft|Concatoftlist|Ad_hocofstringletto_sexpable=Fn.idletinvariant=Sexpable.V1.invariantletusage=Sexpable.V1.usageletrecis_fixed_arity=function|Zero->true|One_->true|Many_->false|Maybe_->false|Ad_hoc_->false|Concatts->(matchList.revtswith|[]->failwith"bug in command.ml"|last::others->assert(List.for_allothers~f:is_fixed_arity);is_fixed_aritylast);;letrecnames=function|Zero->[]|Ones->[s]|Manyt->namest|Maybet->namest|Ad_hocs->[s]|Concatts->List.concat_mapts~f:names;;letzero=Zeroletonename=Onenameletmany=function|Zero->Zero(* strange, but not non-sense *)|t->ifnot(is_fixed_arityt)thenfailwithf"iteration of variable-length grammars such as %s is disallowed"(usaget)();Manyt;;letmaybe=function|Zero->Zero(* strange, but not non-sense *)|t->Maybet;;letconcat=function|[]->Zero|car::cdr->letcar,cdr=List.foldcdr~init:(car,[])~f:(fun(t1,acc)t2->matcht1,t2with|Zero,t|t,Zero->t,acc|_,_->ifis_fixed_arityt1thent2,t1::accelsefailwithf"the grammar %s for anonymous arguments is not supported because \
there is the possibility for arguments (%s) following a variable \
number of arguments (%s). Supporting such grammars would \
complicate the implementation significantly."(usage(Concat(List.rev(t2::t1::acc))))(usaget2)(usaget1)())in(matchcdrwith|[]->car|_::_->Concat(List.rev(car::cdr)));;letad_hoc~usage=Ad_hocusageendmoduleParser:sigtype+'atvalfrom_env:(Env.t->'a)->'atvalone:name:string->'aArg_type.t->'atvalmaybe:'at->'aoptiontvalsequence:'at->'alisttvalfinal_value:'at->Env.t->'avalconsume:'at->string->for_completion:bool->(Env.t->Env.t)*'atvalcomplete:'at->Env.t->part:string->never_returnsmoduleFor_opening:sigvalreturn:'a->'atval(<*>):('a->'b)t->'at->'btval(>>|):'at->('a->'b)->'btendend=structtype'at=|Doneof(Env.t->'a)|Moreof'amore(* A [Test] will (generally) return a [Done _] value if there is no more input and
a [More] parser to use if there is any more input. *)|Testof(more:bool->'at)(* If we're only completing, we can't pull values out, but we can still step through
[t]s (which may have completion set up). *)|Only_for_completionofpackedlistand'amore={name:string;parse:string->for_completion:bool->(Env.t->Env.t)*'at;complete:Completer.t}andpacked=Packed:'at->packedletreturna=Done(fun_->a)letfrom_envf=Donefletpack_for_completion=function|Done_->[](* won't complete or consume anything *)|(More_|Test_)asx->[Packedx]|Only_for_completionps->ps;;letrec(<*>)tftx=matchtfwith|Donef->(matchtxwith|Donex->Done(funenv->fenv(xenv))|Testtest->Test(fun~more->tf<*>test~more)|More{name;parse;complete}->letparsearg~for_completion=letupd,tx'=parsearg~for_completioninupd,tf<*>tx'inMore{name;parse;complete}|Only_for_completionpacked->Only_for_completionpacked)|Testtest->Test(fun~more->test~more<*>tx)|More{name;parse;complete}->letparsearg~for_completion=letupd,tf'=parsearg~for_completioninupd,tf'<*>txinMore{name;parse;complete}|Only_for_completionpacked->Only_for_completion(packed@pack_for_completiontx);;let(>>|)tf=returnf<*>tletone_more~name{Arg_type.complete;parse=of_string;key}=letparseanon~for_completion=matchof_stringanonwith|Errorexn->iffor_completionthen(* we don't *really* care about this value, so just put in a dummy value so
completion can continue *)Fn.id,Only_for_completion[]elsedie"failed to parse %s value %S\n%s"nameanon(Exn.to_stringexn)()|Okv->letupdateenv=Option.foldkey~init:env~f:(funenvkey->Env.multi_addenvkeyv)inupdate,returnvinMore{name;parse;complete};;letone~namearg_type=Test(fun~more->ifmorethenone_more~namearg_typeelsedie"missing anonymous argument: %s"name());;letmaybet=Test(fun~more->ifmorethent>>|funa->SomeaelsereturnNone)letsequencet=letrecloop=Test(fun~more->ifmorethenreturn(funvacc->v::acc)<*>t<*>loopelsereturn[])inloop;;letrecfinal_valuetenv=matchtwith|Donea->aenv|Testf->final_value(f~more:false)env|More{name;_}->die"missing anonymous argument: %s"name()|Only_for_completion_->failwith"BUG: asked for final value when doing completion";;letrecconsume:typea.at->string->for_completion:bool->(Env.t->Env.t)*at=funtarg~for_completion->matchtwith|Done_->die"too many anonymous arguments"()|Testf->consume(f~more:true)arg~for_completion|More{parse;_}->parsearg~for_completion|Only_for_completionpacked->(matchpackedwith|[]->Fn.id,Only_for_completion[]|Packedt::rest->letupd,t=consumetarg~for_completioninupd,Only_for_completion(pack_for_completiont@rest));;letreccomplete:typea.at->Env.t->part:string->never_returns=funtenv~part->matchtwith|Done_->exit0|Testf->complete(f~more:true)env~part|More{complete;_}->Completer.run_and_exitcompleteenv~part|Only_for_completiont->(matchtwith|[]->exit0|Packedt::_->completetenv~part);;moduleFor_opening=structletreturn=returnlet(<*>)=(<*>)let(>>|)=(>>|)endendopenParser.For_openingtype'at={p:'aParser.t;grammar:Grammar.t}lett2t1t2={p=return(funa1a2->a1,a2)<*>t1.p<*>t2.p;grammar=Grammar.concat[t1.grammar;t2.grammar]};;lett3t1t2t3={p=return(funa1a2a3->a1,a2,a3)<*>t1.p<*>t2.p<*>t3.p;grammar=Grammar.concat[t1.grammar;t2.grammar;t3.grammar]};;lett4t1t2t3t4={p=return(funa1a2a3a4->a1,a2,a3,a4)<*>t1.p<*>t2.p<*>t3.p<*>t4.p;grammar=Grammar.concat[t1.grammar;t2.grammar;t3.grammar;t4.grammar]};;letnormalizestr=(* Verify the string is not empty or surrounded by whitespace *)letstrlen=String.lengthstrinifstrlen=0thenfailwith"Empty anonymous argument name provided";ifString.(<>)(String.stripstr)strthenfailwithf"argument name %S has surrounding whitespace"str();(* If the string contains special surrounding characters, don't do anything *)lethas_special_chars=letspecial_chars=Char.Set.of_list['<';'>';'[';']';'(';')';'{';'}']inString.existsstr~f:(Set.memspecial_chars)inifhas_special_charsthenstrelseString.uppercasestr;;let(%:)namearg_type=letname=normalizenamein{p=Parser.one~namearg_type;grammar=Grammar.onename};;letmap_anonst~f={p=t.p>>|f;grammar=t.grammar}letmaybet={p=Parser.maybet.p;grammar=Grammar.maybet.grammar}letmaybe_with_defaultdefaultt=lett=maybetin{twithp=(t.p>>|funv->Option.value~defaultv)};;letsequencet={p=Parser.sequencet.p;grammar=Grammar.manyt.grammar}letnon_empty_sequence_as_pairt=t2t(sequencet)letnon_empty_sequence_as_listt=lett=non_empty_sequence_as_pairtin{twithp=(t.p>>|fun(x,xs)->x::xs)};;moduleDeprecated=structletad_hoc~usage_arg={p=Parser.sequence(Parser.one~name:"WILL NEVER BE PRINTED"Arg_type.string);grammar=Grammar.ad_hoc~usage:usage_arg};;endendmoduleCmdline=structtypet=|Nil|Consofstring*t|Completeofstring[@@derivingcompare]letof_listargs=List.fold_rightargs~init:Nil~f:(funargargs->Cons(arg,args))letrecto_list=function|Nil->[]|Cons(x,xs)->x::to_listxs|Completex->[x];;letrecends_in_complete=function|Complete_->true|Nil->false|Cons(_,args)->ends_in_completeargs;;letextendt~extend~path=ifends_in_completetthentelse(letpath_list=Option.value~default:[](List.tl(Path.partspath))inof_list(to_listt@extendpath_list));;endmoduleKey_type=structtypet=|Subcommand|Flagletto_string=function|Subcommand->"subcommand"|Flag->"flag";;endletassert_no_underscoreskey_typeflag_or_subcommand=ifString.existsflag_or_subcommand~f:(func->Char.(=)c'_')thenfailwithf"%s %s contains an underscore. Use a dash instead."(Key_type.to_stringkey_type)flag_or_subcommand();;letnormalizekey_typekey=assert_no_underscoreskey_typekey;matchkey_typewith|Key_type.Flag->ifString.equalkey"-"thenfailwithf!"invalid %{Key_type} name: %S"key_typekey();ifString.existskey~f:Char.is_whitespacethenfailwithf!"invalid %{Key_type} name (contains whitespace): %S"key_typekey();ifString.is_prefix~prefix:"-"keythenkeyelse"-"^key|Key_type.Subcommand->String.lowercasekey;;letlookup_expandalistprefixkey_type=matchList.filteralist~f:(function|key,(_,`Full_match_required)->String.(=)keyprefix|key,(_,`Prefix)->String.is_prefixkey~prefix)with|[(key,(data,_name_matching))]->Ok(key,data)|[]->Error(sprintf!"unknown %{Key_type} %s"key_typeprefix)|matches->(matchList.findmatches~f:(fun(key,_)->String.(=)keyprefix)with|Some(key,(data,_name_matching))->Ok(key,data)|None->letmatching_keys=List.map~f:fstmatchesinError(sprintf!"%{Key_type} %s is an ambiguous prefix: %s"key_typeprefix(String.concat~sep:", "matching_keys)));;letlookup_expand_with_aliasesmapprefixkey_type=letalist=List.concat_map(String.Map.datamap)~f:(funflag->let{Flag.Internal.name;aliases;action=_;doc=_;num_occurrences=_;check_available=_;name_matching}=flaginletdata=flag,name_matchingin(name,data)::List.mapaliases~f:(funalias->alias,data))inmatchList.find_a_dupalist~compare:(fun(s1,_)(s2,_)->String.compares1s2)with|None->lookup_expandalistprefixkey_type|Some(flag,_)->failwithf"multiple flags named %s"flag();;moduleBase=structtypet={summary:string;readme:(unit->string)option;flags:Flag.Internal.tString.Map.t;anons:unit->([`Parse_args]->[`Run_main]->unit)Anons.Parser.t;usage:Anons.Grammar.t}moduleDeprecated=structletsubcommand_cmp_fst(a,_)(c,_)=help_screen_compareacletflags_help?(display_help_flags=true)t=letflags=String.Map.datat.flagsinletflags=ifdisplay_help_flagsthenflagselseList.filterflags~f:(funf->String.(<>)f.name"-help")inList.concat_map~f:Flag.Internal.Deprecated.helpflags;;endletformatted_flagst=String.Map.datat.flags|>List.map~f:Flag.Internal.align(* this sort puts optional flags after required ones *)|>List.sort~compare:(funab->String.comparea.Format.V1.nameb.name)|>Format.V1.sort;;moduleSexpable=structmoduleV2=structtypeanons=|Usageofstring|GrammarofAnons.Grammar.Sexpable.V1.t[@@derivingsexp]typet={summary:string;readme:stringoption[@sexp.option];anons:anons;flags:Format.V1.tlist}[@@derivingsexp]endmoduleV1=structtypet={summary:string;readme:stringoption[@sexp.option];usage:string;flags:Format.V1.tlist}[@@derivingsexp]letto_latest{summary;readme;usage;flags}={V2.summary;readme;anons=Usageusage;flags};;letof_latest{V2.summary;readme;anons;flags}={summary;readme;usage=(matchanonswith|Usageusage->usage|Grammargrammar->Anons.Grammar.Sexpable.V1.usagegrammar);flags};;endincludeV2endletto_sexpablet={Sexpable.summary=t.summary;readme=Option.mapt.readme~f:(funreadme->readme());anons=Grammar(Anons.Grammar.to_sexpablet.usage);flags=formatted_flagst};;letpath_key=Env.key_create"path"letargs_key=Env.key_create"args"lethelp_key=Env.key_create"help"letindent_by_2str=String.split~on:'\n'str|>List.map~f:(funline->" "^line)|>String.concat~sep:"\n";;letruntenv~path~args~verbose_on_parse_error~help_text=letenv=Env.setenvpath_keypathinletenv=Env.setenvargs_key(Cmdline.to_listargs)inletenv=Env.setenvhelp_keyhelp_textinletrecloopenvanons=function|Cmdline.Nil->List.iter(String.Map.datat.flags)~f:(funflag->flag.check_availableenv);Anons.Parser.final_valueanonsenv|Cons("-anon",Cons(arg,args))->(* the very special -anon flag is here as an escape hatch in case you have an
anonymous argument that starts with a hyphen. *)anonenvanonsargargs|Cons(arg,args)->ifString.is_prefixarg~prefix:"-"&¬(String.equalarg"-")(* support the convention where "-" means stdin *)then(letflag=arginlet(flag,{Flag.Internal.action;name=_;aliases=_;doc=_;num_occurrences=_;check_available=_;name_matching=_})=matchlookup_expand_with_aliasest.flagsflagKey_type.Flagwith|Errormsg->die"%s"msg()|Okx->xinmatchactionwith|No_argf->letenv=fenvinloopenvanonsargs|Arg(f,comp)->(matchargswith|Nil->die"missing argument for flag %s"flag()|Cons(arg,rest)->letenv=tryfenvargwith|Failed_to_parse_command_line_ase->ifCmdline.ends_in_completerestthenenvelseraiseeinloopenvanonsrest|Completepart->never_returns(Completer.run_and_exitcompenv~part))|Restf->ifCmdline.ends_in_completeargsthenexit0;letenv=fenv(Cmdline.to_listargs)inloopenvanonsNil)elseanonenvanonsargargs|Completepart->ifString.is_prefixpart~prefix:"-"then(List.iter(String.Map.keyst.flags)~f:(funname->ifString.is_prefixname~prefix:partthenprint_endlinename);exit0)elsenever_returns(Anons.Parser.completeanonsenv~part)andanonenvanonsargargs=letenv_upd,anons=Anons.Parser.consumeanonsarg~for_completion:(Cmdline.ends_in_completeargs)inletenv=env_updenvinloopenvanonsargsinmatchResult.try_with(fun()->loopenv(t.anons())args`Parse_args)with|Okthunk->thunk`Run_main|Errorexn->(matchexnwith|Failed_to_parse_command_line_whenCmdline.ends_in_completeargs->exit0|Exit_called{status}->exitstatus|_->letexn_str=matchexnwith|Failed_to_parse_command_linemsg->msg|_->Sexp.to_string_hum[%sexp(exn:exn)]inletverbose=Option.valueverbose_on_parse_error~default:trueinleterror_msg=ifverbosethenString.concat~sep:"\n\n"["Error parsing command line:";indent_by_2exn_str;"For usage information, run";" "^Path.to_stringpath^" -help\n"]elseexn_strinprerr_endlineerror_msg;exit1);;moduleSpec=structtype('a,'b)t={f:unit->('a->'b)Anons.Parser.t;usage:unit->Anons.Grammar.t;flags:unit->Flag.Internal.tlist}(* the (historical) reason that [param] is defined in terms of [t] rather than the
other way round is that the delayed evaluation mattered for sequencing of
read/write operations on ref cells in the old representation of flags *)type'aparam={param:'m.('a->'m,'m)t}openAnons.Parser.For_openingletappt1t2~f={f=(fun()->returnf<*>t1.f()<*>t2.f());flags=(fun()->t2.flags()@t1.flags());usage=(fun()->Anons.Grammar.concat[t1.usage();t2.usage()])};;(* So sad. We can't define [apply] in terms of [app] because of the value
restriction. *)letapplypfpx={param={f=(fun()->return(funmfmxk->mf(funf->mx(funx->k(fx))))<*>pf.param.f()<*>px.param.f());flags=(fun()->px.param.flags()@pf.param.flags());usage=(fun()->Anons.Grammar.concat[pf.param.usage();px.param.usage()])}};;let(++)t1t2=appt1t2~f:(funf1f2x->f2(f1x))let(+>)t1p2=appt1p2.param~f:(funf1f2x->f2(f1x))let(+<)t1p2=appp2.paramt1~f:(funf2f1x->f1(f2x))letstepf={f=(fun()->returnf);flags=(fun()->[]);usage=(fun()->Anons.Grammar.zero)};;letempty:'m.('m,'m)t={f=(fun()->returnFn.id);flags=(fun()->[]);usage=(fun()->Anons.Grammar.zero)};;letconstv={param={f=(fun()->return(funk->kv));flags=(fun()->[]);usage=(fun()->Anons.Grammar.zero)}};;letmapp~f={param={f=(fun()->p.param.f()>>|funck->c(funv->k(fv)));flags=p.param.flags;usage=p.param.usage}};;letwrapft={f=(fun()->t.f()>>|funrunmain->f~run~main);flags=t.flags;usage=t.usage};;letof_paramsparams=lett=params.paramin{f=(fun()->t.f()>>|funrunmain->runFn.idmain);flags=t.flags;usage=t.usage};;letto_params(t:('a,'b)t):('a->'b)param={param={f=(fun()->t.f()>>|funfk->kf);flags=t.flags;usage=t.usage}};;letof_paramp=p.paramletto_paramtmain=map(to_paramst)~f:(funk->kmain)letlookupkey={param={f=(fun()->Anons.Parser.from_env(funenvm->m(Env.find_exnenvkey)));flags=(fun()->[]);usage=(fun()->Anons.Grammar.zero)}};;letpath:Path.tparam=lookuppath_keyletargs:stringlistparam=lookupargs_keylethelp:stringLazy.tparam=lookuphelp_key(* This is only used internally, for the help command. *)letenv={param={f=(fun()->Anons.Parser.from_env(funenvm->menv));flags=(fun()->[]);usage=(fun()->Anons.Grammar.zero)}};;includestructmoduleArg_type=Arg_typeincludeArg_type.ExportendincludestructopenAnonstype'aanons='atlet(%:)=(%:)letmap_anons=map_anonsletmaybe=maybeletmaybe_with_default=maybe_with_defaultletsequence=sequenceletnon_empty_sequence_as_pair=non_empty_sequence_as_pairletnon_empty_sequence_as_list=non_empty_sequence_as_listlett2=t2lett3=t3lett4=t4letanonspec=Anons.Grammar.invariantspec.grammar;{param={f=(fun()->spec.p>>|funvk->kv);flags=(fun()->[]);usage=(fun()->spec.grammar)}};;endincludestructopenFlagtype'aflag='atletmap_flag=map_flagletescape=escapeletlisted=listedletone_or_more=one_or_moreletno_arg=no_argletno_arg_register=no_arg_registerletno_arg_abort=no_arg_abortletno_arg_some=no_arg_someletoptional=optionalletoptional_with_default=optional_with_defaultletrequired=requiredletflag?(aliases=[])?full_flag_requirednamemode~doc=letnormalizeflag=normalizeKey_type.Flagflaginletname=normalizenameinletaliases=List.map~f:normalizealiasesinlet{read;action;num_occurrences}=modenameinletcheck_available=matchnum_occurrences.at_least_oncewith|false->(ignore:Univ_map.t->unit)|true->funenv->ignore(readenv:_)inletname_matching=ifOption.is_somefull_flag_requiredthen`Full_match_requiredelse`Prefixin{param={f=(fun()->Anons.Parser.from_env(funenvm->m(readenv)));flags=(fun()->[{name;aliases;doc;action;num_occurrences;check_available;name_matching}]);usage=(fun()->Anons.Grammar.zero)}};;letflag_optional_with_default_doc?aliases?full_flag_requirednamearg_typesexp_of_default~default~doc=flag?aliases?full_flag_requiredname(optional_with_defaultdefaultarg_type)~doc:(sprintf!"%s (default: %{Sexp})"doc(sexp_of_defaultdefault));;includeApplicative.Make(structtypenonrec'at='aparamletreturn=constletapply=applyletmap=`Custommapend)letpair=bothendletflags_of_args_exnargs=List.foldargs~init:empty~f:(funacc(name,spec,doc)->letgenfflag_type=step(funmx->fx;m)+>flagnameflag_type~docinletcallfarg_type=gen(funx->Option.iterx~f)(optionalarg_type)inletsetrarg_type=call(funx->r:=x)arg_typeinletset_boolrb=gen(funpassed->ifpassedthenr:=b)no_arginacc++matchspecwith|Arg.Unitf->gen(funpassed->ifpassedthenf())no_arg|Arg.Setr->set_boolrtrue|Arg.Clearr->set_boolrfalse|Arg.Stringf->callfstring|Arg.Set_stringr->setrstring|Arg.Intf->callfint|Arg.Set_intr->setrint|Arg.Floatf->callffloat|Arg.Set_floatr->setrfloat|Arg.Boolf->callfbool|Arg.Symbol(syms,f)->letarg_type=Arg_type.of_alist_exn(List.mapsyms~f:(funsym->sym,sym))incallfarg_type|Arg.Restf->gen(funx->Option.iterx~f:(List.iter~f))escape|Arg.Tuple_->failwith"Arg.Tuple is not supported by Command.Spec.flags_of_args_exn"|((Arg.Expand_)[@ifocaml_version>=(4,05,0)])->failwith"Arg.Expand is not supported by Command.Spec.flags_of_args_exn");;moduleDeprecated=structincludeFlag.DeprecatedincludeAnons.Deprecatedendletarg_namesparam=lett=param.paraminletflag_names=Map.keys(Flag.Internal.create(t.flags()))inletanon_names=Anons.Grammar.names(t.usage())inList.concat[flag_names;anon_names];;letto_string_for_choose_oneparam=letnames=arg_namesparaminletnames_with_commas=List.filternames~f:(funs->String.containss',')inifnot(List.is_emptynames_with_commas)thenfailwiths"For simplicity, [Command.Spec.choose_one] does not support names with commas."names_with_commas[%sexp_of:stringlist];String.concat~sep:","names;;moduleIf_nothing_chosen=structtype(_,_)t=|Default_to:'a->('a,'a)t|Raise:('a,'a)t|Return_none:('a,'aoption)tendletchoose_one(typeab)(ts:aoptionparamlist)~(if_nothing_chosen:(a,b)If_nothing_chosen.t)=letts=List.mapts~f:(funt->to_string_for_choose_onet,t)inOption.iter(List.find_a_dup(List.map~f:fstts)~compare:String.compare)~f:(funname->failwiths"Command.Spec.choose_one called with duplicate name"name[%sexp_of:string]);List.foldts~init:(returnNone)~f:(funinit(name,t)->map2initt~f:(funinitvalue->matchvaluewith|None->init|Somevalue->(matchinitwith|None->Some(name,value)|Some(name',_)->die"Cannot pass both %s and %s"namename'())))|>map~f:(function|Some(_,value)->(matchif_nothing_chosenwith|Default_to(_:a)->(value:b)|Raise->value|Return_none->Somevalue)|None->(matchif_nothing_chosenwith|Default_tovalue->value|Return_none->None|Raise->die"Must pass one of these: %s"(String.concat~sep:"; "(List.map~f:fstts))()));;letand_arg_namest=mapt~f:(funvalue->value,arg_namest)letand_arg_namet=matcharg_namestwith|[name]->mapt~f:(funvalue->value,name)|names->raise_s[%message"[and_arg_name] expects exactly one name, got"~_:(names:stringlist)];;endendmoduleGroup=structtype'at={summary:string;readme:(unit->string)option;subcommands:(string*'a)listLazy.t;body:(path:stringlist->unit)option}moduleSexpable=structmoduleV2=structtype'at={summary:string;readme:stringoption[@sexp.option];subcommands:(string,'a)List.Assoc.tLazy.t}[@@derivingsexp]letmapt~f={twithsubcommands=Lazy.mapt.subcommands~f:(List.Assoc.map~f)};;endmoduleLatest=V2moduleV1=structtype'at={summary:string;readme:stringoption[@sexp.option];subcommands:(string,'a)List.Assoc.t}[@@derivingsexp]letmapt~f={twithsubcommands=List.Assoc.mapt.subcommands~f}letto_latest{summary;readme;subcommands}:'aLatest.t={summary;readme;subcommands=Lazy.from_valsubcommands};;letof_latest({summary;readme;subcommands}:'aLatest.t):'at={summary;readme;subcommands=Lazy.forcesubcommands};;endincludeLatestendletto_sexpable~subcommand_to_sexpablet={Sexpable.summary=t.summary;readme=Option.map~f:(funreadme->readme())t.readme;subcommands=Lazy.mapt.subcommands~f:(List.Assoc.map~f:subcommand_to_sexpable)};;endletabs_path~dirpath=ifFilename.is_absolutepaththenpathelseFilename.concatdirpath;;letcomp_cword="COMP_CWORD"moduleExec=structtypet={summary:string;readme:(unit->string)option;(* If [path_to_exe] is relative, interpret w.r.t. [working_dir] *)working_dir:string;path_to_exe:string;child_subcommand:stringlist}moduleSexpable=structmoduleV3=structtypet={summary:string;readme:stringoption[@sexp.option];working_dir:string;path_to_exe:string;child_subcommand:stringlist}[@@derivingsexp]letto_latest=Fn.idletof_latest=Fn.idendmoduleV2=structtypet={summary:string;readme:stringoption[@sexp.option];working_dir:string;path_to_exe:string}[@@derivingsexp]letto_v3t:V3.t={summary=t.summary;readme=t.readme;working_dir=t.working_dir;path_to_exe=t.path_to_exe;child_subcommand=[]};;letof_v3(t:V3.t)={summary=t.summary;readme=t.readme;working_dir=t.working_dir;path_to_exe=abs_path~dir:t.working_dirt.path_to_exe};;letto_latest=Fn.composeV3.to_latestto_v3letof_latest=Fn.composeof_v3V3.of_latestendmoduleV1=structtypet={summary:string;readme:stringoption[@sexp.option];(* [path_to_exe] must be absolute. *)path_to_exe:string}[@@derivingsexp]letto_v2t:V2.t={summary=t.summary;readme=t.readme;working_dir="/";path_to_exe=t.path_to_exe};;letof_v2(t:V2.t)={summary=t.summary;readme=t.readme;path_to_exe=abs_path~dir:t.working_dirt.path_to_exe};;letto_latest=Fn.composeV2.to_latestto_v2letof_latest=Fn.composeof_v2V2.of_latestendincludeV3endletto_sexpablet={Sexpable.summary=t.summary;readme=Option.map~f:(funreadme->readme())t.readme;working_dir=t.working_dir;path_to_exe=t.path_to_exe;child_subcommand=t.child_subcommand};;end(* A proxy command is the structure of an Exec command obtained by running it in a
special way *)moduleProxy=structmoduleKind=structtype'at=|BaseofBase.Sexpable.t|Groupof'aGroup.Sexpable.t|ExecofExec.Sexpable.t|Lazyof'atLazy.tendtypet={working_dir:string;path_to_exe:string;path_to_subcommand:stringlist;child_subcommand:stringlist;kind:tKind.t}letrecget_summary_from_kind(kind:tKind.t)=matchkindwith|Baseb->b.summary|Groupg->g.summary|Exece->e.summary|Lazyl->get_summary_from_kind(Lazy.forcel);;letget_summaryt=get_summary_from_kindt.kindletrecget_readme_from_kind(kind:tKind.t)=matchkindwith|Baseb->b.readme|Groupg->g.readme|Exece->e.readme|Lazyl->get_readme_from_kind(Lazy.forcel);;letget_readmet=get_readme_from_kindt.kindendtypet=|BaseofBase.t|GroupoftGroup.t|ExecofExec.t|ProxyofProxy.t|LazyoftLazy.tmoduleSexpable=structletsupported_versions:intQueue.t=Queue.create()letadd_versionn=Queue.enqueuesupported_versionsnmoduleV3=structlet()=add_version3typet=|BaseofBase.Sexpable.V2.t|GroupoftGroup.Sexpable.V2.t|ExecofExec.Sexpable.V3.t|LazyoftLazy.t[@@derivingsexp]letto_latest=Fn.idletof_latest=Fn.idendmoduleLatest=V3moduleV2=structlet()=add_version2typet=|BaseofBase.Sexpable.V2.t|GroupoftGroup.Sexpable.V1.t|ExecofExec.Sexpable.V2.t[@@derivingsexp]letrecto_latest:t->Latest.t=function|Baseb->Baseb|Exece->Exec(Exec.Sexpable.V2.to_lateste)|Groupg->Group(Group.Sexpable.V1.to_latest(Group.Sexpable.V1.mapg~f:to_latest));;letrecof_latest:Latest.t->t=function|Baseb->Baseb|Exece->Exec(Exec.Sexpable.V2.of_lateste)|Lazythunk->of_latest(Lazy.forcethunk)|Groupg->Group(Group.Sexpable.V1.map(Group.Sexpable.V1.of_latestg)~f:of_latest);;endmoduleV1=structlet()=add_version1typet=|BaseofBase.Sexpable.V1.t|GroupoftGroup.Sexpable.V1.t|ExecofExec.Sexpable.V1.t[@@derivingsexp]letrecto_latest:t->Latest.t=function|Baseb->Base(Base.Sexpable.V1.to_latestb)|Exece->Exec(Exec.Sexpable.V1.to_lateste)|Groupg->Group(Group.Sexpable.V1.to_latest(Group.Sexpable.V1.mapg~f:to_latest));;letrecof_latest:Latest.t->t=function|Baseb->Base(Base.Sexpable.V1.of_latestb)|Exece->Exec(Exec.Sexpable.V1.of_lateste)|Lazythunk->of_latest(Lazy.forcethunk)|Groupg->Group(Group.Sexpable.V1.map(Group.Sexpable.V1.of_latestg)~f:of_latest);;endmoduleInternal:sigtypet[@@derivingsexp]valof_latest:version_to_use:int->Latest.t->tvalto_latest:t->Latest.tend=structtypet=|V1ofV1.t|V2ofV2.t|V3ofV3.t[@@derivingsexp]letto_latest=function|V1t->V1.to_latestt|V2t->V2.to_latestt|V3t->V3.to_latestt;;letof_latest~version_to_uselatest=matchversion_to_usewith|1->V1(V1.of_latestlatest)|2->V2(V2.of_latestlatest)|3->V3(V3.of_latestlatest)|other->failwiths"unsupported version_to_use"other[%sexp_of:int];;endincludeLatestletsupported_versions=Int.Set.of_list(Queue.to_listsupported_versions)letextraction_var="COMMAND_OUTPUT_HELP_SEXP"endletrecsexpable_of_proxy_kind(kind:Proxy.tProxy.Kind.t)=matchkindwith|Basebase->Sexpable.Basebase|Execexec->Sexpable.Execexec|Lazythunk->Sexpable.Lazy(Lazy.map~f:sexpable_of_proxy_kindthunk)|Groupgroup->Sexpable.Group{groupwithsubcommands=Lazy.mapgroup.subcommands~f:(List.map~f:(fun(str,proxy)->str,sexpable_of_proxy_kindproxy.Proxy.kind))};;letsexpable_of_proxyproxy=sexpable_of_proxy_kindproxy.Proxy.kindletrecto_sexpable=function|Basebase->Sexpable.Base(Base.to_sexpablebase)|Execexec->Sexpable.Exec(Exec.to_sexpableexec)|Proxyproxy->sexpable_of_proxyproxy|Groupgroup->Sexpable.Group(Group.to_sexpable~subcommand_to_sexpable:to_sexpablegroup)|Lazythunk->Sexpable.Lazy(Lazy.map~f:to_sexpablethunk);;type('main,'result)basic_spec_command=summary:string->?readme:(unit->string)->('main,unit->'result)Base.Spec.t->'main->tletrecget_summary=function|Basebase->base.summary|Groupgroup->group.summary|Execexec->exec.summary|Proxyproxy->Proxy.get_summaryproxy|Lazythunk->get_summary(Lazy.forcethunk);;letextend_exn~mem~addmapkey_type~keydata=ifmemmapkeythenfailwithf"there is already a %s named %s"(Key_type.to_stringkey_type)key();addmap~key~data;;letextend_map_exnmapkey_type~keydata=extend_exnmapkey_type~keydata~mem:Map.mem~add:Map.set;;letextend_alist_exnalistkey_type~keydata=extend_exnalistkey_type~keydata~mem:(funalistkey->List.Assoc.memalistkey~equal:String.equal)~add:(funalist~key~data->List.Assoc.addalistkeydata~equal:String.equal);;moduleBailout_dump_flag=structletaddbase~name~aliases~text~text_summary=letflags=base.Base.flagsinletflags=extend_map_exnflagsKey_type.Flag~key:name{name;aliases;num_occurrences=Flag.Num_occurrences.at_most_once;check_available=ignore;action=No_arg(funenv->print_endline(textenv);exit0);doc=sprintf" print %s and exit"text_summary;name_matching=`Prefix}in{basewithBase.flags};;endletbasic_spec~summary?readme{Base.Spec.usage;flags;f}main=letflags=flags()inletusage=usage()inletanons()=letopenAnons.Parser.For_openinginf()>>|funk`Parse_args->letthunk=kmaininfun`Run_main->thunk()inletflags=Flag.Internal.createflagsinletbase={Base.summary;readme;usage;flags;anons}inletbase=Bailout_dump_flag.addbase~name:"-help"~aliases:["-?"]~text_summary:"this help text"~text:(funenv->Lazy.force(Env.find_exnenvBase.help_key))inBasebase;;letbasic=basic_specletsubs_key:(string*t)listEnv.Key.t=Env.key_create"subcommands"letlazy_group~summary?readme?preserve_subcommand_order?bodyalist=letsubcommands=Lazy.mapalist~f:(funalist->letalist=List.mapalist~f:(fun(name,t)->normalizeKey_type.Subcommandname,t)inmatchString.Map.of_alistalistwith|`Duplicate_keyname->failwithf"multiple subcommands named %s"name()|`Okmap->(matchpreserve_subcommand_orderwith|Some()->alist|None->Map.to_alistmap))inGroup{summary;readme;subcommands;body};;letgroup~summary?readme?preserve_subcommand_order?bodyalist=lazy_group~summary?readme?preserve_subcommand_order?body(Lazy.from_valalist);;letexec~summary?readme?(child_subcommand=[])~path_to_exe()=letworking_dir=Filename.dirname@@matchpath_to_exewith|`Absolute_|`Relative_to_me_->Sys.executable_name|`Relative_to_argv0_->Sys.argv.(0)inletpath_to_exe=matchpath_to_exewith|`Absolutep->ifnot(Filename.is_absolutep)thenfailwith"Path passed to `Absolute must be absolute"elsep|`Relative_to_mep|`Relative_to_argv0p->ifnot(Filename.is_relativep)thenfailwith"Path passed to `Relative_to_me must be relative"elsepinExec{summary;readme;working_dir;path_to_exe;child_subcommand};;letof_lazythunk=LazythunkmoduleShape=structmoduleFlag_info=structtypet=Format.V1.t={name:string;doc:string;aliases:stringlist}[@@derivingbin_io,compare,fields,sexp]endmoduleBase_info=structtypegrammar=Anons.Grammar.Sexpable.V1.t=|Zero|Oneofstring|Manyofgrammar|Maybeofgrammar|Concatofgrammarlist|Ad_hocofstring[@@derivingbin_io,compare,sexp]typeanons=Base.Sexpable.V2.anons=|Usageofstring|Grammarofgrammar[@@derivingbin_io,compare,sexp]typet=Base.Sexpable.V2.t={summary:string;readme:stringoption[@sexp.option];anons:anons;flags:Flag_info.tlist}[@@derivingbin_io,compare,fields,sexp]letget_usaget=matcht.anonswith|Usageusage->usage|Grammargrammar->Anons.Grammar.Sexpable.V1.usagegrammar;;endmoduleGroup_info=structtype'at='aGroup.Sexpable.V2.t={summary:string;readme:stringoption[@sexp.option];subcommands:(string*'a)List.tLazy.t}[@@derivingbin_io,compare,fields,sexp]letmap=Group.Sexpable.V2.mapendmoduleExec_info=structtypet=Exec.Sexpable.V3.t={summary:string;readme:stringoption[@sexp.option];working_dir:string;path_to_exe:string;child_subcommand:stringlist}[@@derivingbin_io,compare,fields,sexp]endmoduleT=structtypet=|BasicofBase_info.t|GroupoftGroup_info.t|ExecofExec_info.t*(unit->t)|LazyoftLazy.tendmoduleFully_forced=structtypet=|BasicofBase_info.t|GroupoftGroup_info.t|ExecofExec_info.t*t[@@derivingbin_io,compare,sexp]letreccreate:T.t->t=function|Basicb->Basicb|Groupg->Group(Group_info.mapg~f:create)|Exec(e,f)->Exec(e,create(f()))|Lazythunk->create(Lazy.forcethunk);;endincludeTletrecget_summary=function|Basicb->b.summary|Groupg->g.summary|Exec(e,_)->e.summary|Lazythunk->get_summary(Lazy.forcethunk);;endletrecproxy_of_sexpablesexpable~working_dir~path_to_exe~child_subcommand~path_to_subcommand:Proxy.t=letkind=kind_of_sexpablesexpable~working_dir~path_to_exe~child_subcommand~path_to_subcommandin{working_dir;path_to_exe;path_to_subcommand;child_subcommand;kind}andkind_of_sexpablesexpable~working_dir~path_to_exe~child_subcommand~path_to_subcommand=match(sexpable:Sexpable.t)with|Baseb->Proxy.Kind.Baseb|Exece->Proxy.Kind.Exece|Lazyl->Proxy.Kind.Lazy(Lazy.mapl~f:(funsexpable->kind_of_sexpablesexpable~working_dir~path_to_exe~child_subcommand~path_to_subcommand))|Groupg->Proxy.Kind.Group{gwithsubcommands=Lazy.mapg.subcommands~f:(List.map~f:(fun(str,sexpable)->letpath_to_subcommand=path_to_subcommand@[str]inletproxy=proxy_of_sexpablesexpable~working_dir~path_to_exe~child_subcommand~path_to_subcommandinstr,proxy))};;moduleVersion_info=structletsanitize_version~version=(* [version] was space delimited at some point and newline delimited
at another. We always print one (repo, revision) pair per line
and ensure sorted order *)String.splitversion~on:' '|>List.concat_map~f:(String.split~on:'\n')|>List.sort~compare:String.compare;;letprint_version~version=List.iter(sanitize_version~version)~f:print_endlineletprint_build_info~build_info=print_endline(forcebuild_info)letcommand~version~build_info=basic~summary:"print version information"Base.Spec.(empty+>flag"-version"no_arg~doc:" print the version of this build"+>flag"-build-info"no_arg~doc:" print build info for this build")(funversion_flagbuild_info_flag->ifbuild_info_flagthenprint_build_info~build_infoelseifversion_flagthenprint_version~versionelse(print_build_info~build_info;print_version~version);exit0);;letrecadd~version~build_infounversioned=matchunversionedwith|Basebase->letbase=Bailout_dump_flag.addbase~name:"-version"~aliases:[]~text_summary:"the version of this build"~text:(fun_->String.concat~sep:"\n"(sanitize_version~version))inletbase=Bailout_dump_flag.addbase~name:"-build-info"~aliases:[]~text_summary:"info about this build"~text:(fun_->forcebuild_info)inBasebase|Groupgroup->letsubcommands=Lazy.mapgroup.Group.subcommands~f:(funsubcommands->extend_alist_exnsubcommandsKey_type.Subcommand~key:"version"(command~version~build_info))inGroup{groupwithGroup.subcommands}|Proxyproxy->Proxyproxy|Execexec->Execexec|Lazythunk->Lazy(lazy(add~version~build_info(Lazy.forcethunk)));;endletrecsummary=function|Basex->x.summary|Groupx->x.summary|Execx->x.summary|Proxyx->Proxy.get_summaryx|Lazythunk->summary(Lazy.forcethunk);;moduleSpec=structincludeBase.Specletpath=map~f:Path.parts_exe_basenamepathendmoduleDeprecated=structmoduleSpec=Spec.Deprecatedletsummary=get_summaryletrecget_flag_names=function|Basebase->base.Base.flags|>String.Map.keys|Lazythunk->get_flag_names(Lazy.forcethunk)|Group_|Proxy_|Exec_->assertfalse;;lethelp_recursive~cmd~with_flags~expand_dotsts=letrechelp_recursive_rec~cmdts=letnew_s=s^(ifexpand_dotsthencmdelse".")^" "inmatchtwith|Lazythunk->lett=Lazy.forcethunkinhelp_recursive_rec~cmdts|Basebase->letbase_help=s^cmd,summary(Basebase)inifwith_flagsthenbase_help::List.map~f:(fun(flag,h)->new_s^flag,h)(List.sort~compare:Base.Deprecated.subcommand_cmp_fst(Base.Deprecated.flags_help~display_help_flags:falsebase))else[base_help]|Group{summary;subcommands;readme=_;body=_}->(s^cmd,summary)::(Lazy.forcesubcommands|>List.sort~compare:Base.Deprecated.subcommand_cmp_fst|>List.concat_map~f:(fun(cmd',t)->help_recursive_rec~cmd:cmd'tnew_s))|Proxy_|Exec_->(* Command.exec does not support deprecated commands *)[]inhelp_recursive_rec~cmdts;;endmoduleFor_unix(M:For_unix)=structopenM(* Clear the setting of environment variable associated with command-line
completion and recursive help so that subprocesses don't see them.
Use [unsafe_getenv] so setuid-root programs can still read environment variables.
There is no security risk here because the values are only used as triggers to dump
out command information. *)letgetenv_and_clearvar=letvalue=Sys.unsafe_getenvvarinifOption.is_somevaluethenUnix.unsetenvvar;value;;letmaybe_comp_cword()=getenv_and_clearcomp_cword|>Option.map~f:Int.of_stringletset_comp_cwordnew_value=letnew_value=Int.to_stringnew_valueinUnix.putenv~key:comp_cword~data:new_value;;moduleExec=structincludeExecletexec_with_argst~args~maybe_new_comp_cword=letprog=abs_path~dir:t.working_dirt.path_to_exeinletargs=t.child_subcommand@argsinOption.itermaybe_new_comp_cword~f:(funn->(* The logic for tracking [maybe_new_comp_cword] doesn't take into account whether
this exec specifies a child subcommand. If it does, COMP_CWORD needs to be set
higher to account for the arguments used to specify the child subcommand. *)set_comp_cword(n+List.lengtht.child_subcommand));never_returns(Unix.exec~prog~argv:(prog::args)());;endmoduleSexpable=structincludeSexpableletread_stdout_and_stderr(process_info:Unix.Process_info.t)=(* We need to read each of stdout and stderr in a separate thread to avoid deadlocks
if the child process decides to wait for a read on one before closing the other.
Buffering may hide this problem until output is "sufficiently large". *)letstart_readingdescrinfo=letoutput=Set_once.create()inletthread=Thread.create(fun()->Result.try_with(fun()->descr|>Unix.in_channel_of_descr|>In_channel.input_all)|>Set_once.set_exnoutput[%here])()instage(fun()->Thread.jointhread;Unix.closedescr;matchSet_once.getoutputwith|None->raise_s[%message"BUG failed to read"(info:Info.t)]|Some(Okoutput)->output|Some(Errorexn)->raiseexn)in(* We might hang forever trying to join the reading threads if the child process keeps
the file descriptor open. Not handling this because I think we've never seen it
in the wild despite running vulnerable code for years. *)(* We have to start both threads before joining any of them. *)letfinish_stdout=start_readingprocess_info.stdout(Info.of_string"stdout")inletfinish_stderr=start_readingprocess_info.stderr(Info.of_string"stderr")inunstagefinish_stdout(),unstagefinish_stderr();;letof_external~working_dir~path_to_exe~child_subcommand=letprocess_info=Unix.create_process_env()~prog:(abs_path~dir:working_dirpath_to_exe)~args:child_subcommand~env:(`Extend[extraction_var,supported_versions|>Int.Set.sexp_of_t|>Sexp.to_string])inUnix.closeprocess_info.stdin;letstdout,stderr=read_stdout_and_stderrprocess_infoinignore(Unix.wait(`Pidprocess_info.pid):Pid.t*Unix.Exit_or_signal.t);(* Now we've killed all the processes and threads we made. *)matchstdout|>Sexp.of_string|>Internal.t_of_sexp|>Internal.to_latestwith|exceptionexn->raise_s[%message"cannot parse command shape"~_:(exn:exn)(stdout:string)(stderr:string)]|t->t;;letrecfind(t:t)~path_to_subcommand=matchpath_to_subcommandwith|[]->t|sub::subs->(matchtwith|Base_->failwithf"unexpected subcommand %S"sub()|Lazythunk->find(Lazy.forcethunk)~path_to_subcommand|Exec{path_to_exe;working_dir;child_subcommand;_}->find(of_external~working_dir~path_to_exe~child_subcommand)~path_to_subcommand:(sub::(subs@child_subcommand))|Groupg->(matchList.Assoc.find(Lazy.forceg.subcommands)~equal:String.equalsubwith|None->failwithf"unknown subcommand %S"sub()|Somet->findt~path_to_subcommand:subs));;endletproxy_of_exe~working_dirpath_to_exechild_subcommand=Sexpable.of_external~working_dir~path_to_exe~child_subcommand|>proxy_of_sexpable~working_dir~path_to_exe~child_subcommand~path_to_subcommand:[];;letrecshape_of_proxyproxy:Shape.t=shape_of_proxy_kindproxy.Proxy.kindandshape_of_exe()~child_subcommand~path_to_exe~working_dir=shape_of_proxy(proxy_of_exe~working_dirpath_to_exechild_subcommand)andshape_of_proxy_kindkind=matchkindwith|Baseb->Basicb|Lazyl->Lazy(Lazy.map~f:shape_of_proxy_kindl)|Groupg->Group{gwithsubcommands=Lazy.mapg.subcommands~f:(List.Assoc.map~f:shape_of_proxy)}|Exec({Exec.Sexpable.child_subcommand;path_to_exe;working_dir;_}ase)->Exec(e,shape_of_exe~child_subcommand~path_to_exe~working_dir);;letrecshapet:Shape.t=matchtwith|Baseb->Basic(Base.to_sexpableb)|Groupg->Group(Group.to_sexpable~subcommand_to_sexpable:shapeg)|Proxyp->shape_of_proxyp|Exec({Exec.child_subcommand;path_to_exe;working_dir;_}ase)->Exec(Exec.to_sexpablee,shape_of_exe~child_subcommand~path_to_exe~working_dir)|Lazythunk->shape(Lazy.forcethunk);;letgather_help~recursive~flags~expand_dotsshape=letreclooppathaccshape=letstring_of_path=ifexpand_dotsthenPath.to_stringelsePath.to_string_dotsinletgather_grouppathaccsubcommands=letfiltered_subcommands=(* Only show the [help] subcommand at top-level. *)ifPath.is_emptypaththensubcommandselseList.Assoc.remove~equal:String.(=)subcommands"help"infiltered_subcommands|>List.stable_sort~compare:(funab->help_screen_compare(fsta)(fstb))|>List.fold~init:acc~f:(funacc(subcommand,shape)->letpath=Path.appendpath~subcommandinletname=string_of_pathpathinletdoc=Shape.get_summaryshapeinletacc=Fqueue.enqueueacc{Format.V1.name;doc;aliases=[]}inifrecursivethenlooppathaccshapeelseacc)inmatchshapewith|Exec(_,shape)->(* If the executable being called doesn't use [Core.Command], then sexp extraction
will fail. *)(trylooppathacc(shape())with|_->acc)|Groupg->gather_grouppathacc(Lazy.forceg.subcommands)|Basicb->ifflagsthenb.flags|>List.filter~f:(funfmt->String.(<>)fmt.Format.V1.name"[-help]")|>List.fold~init:acc~f:(funaccfmt->letpath=Path.appendpath~subcommand:fmt.Format.V1.nameinletfmt={fmtwithFormat.V1.name=string_of_pathpath}inFqueue.enqueueaccfmt)elseacc|Lazythunk->looppathacc(Lazy.forcethunk)inloopPath.emptyFqueue.emptyshape|>Fqueue.to_list;;letgroup_or_exec_help_text~flags~path~summary~readme~format_list=unparagraphs(List.filter_opt[Somesummary;Some(String.concat[" ";Path.to_stringpath;" SUBCOMMAND"]);readme;Some(ifflagsthen"=== subcommands and flags ==="else"=== subcommands ===");Some(Format.V1.to_stringformat_list)]);;letrechelp_for_shapeshapepath~expand_dots~flags~recursive=letformat_list=gather_help~expand_dots~flags~recursiveshapeinmatchshapewith|Basicb->letusage=Shape.Base_info.get_usagebinunparagraphs(List.filter_opt[Someb.summary;Some(" "^Path.to_stringpath^" "^usage);b.readme;Some"=== flags ===";Some(Format.V1.to_stringb.flags)])|Groupg->group_or_exec_help_text~flags~path~readme:g.readme~summary:g.summary~format_list|Exec(e,_)->group_or_exec_help_text~flags~path~readme:e.readme~summary:e.summary~format_list|Lazythunk->help_for_shape(Lazy.forcethunk)path~expand_dots~flags~recursive;;lethelp_subcommand~summary~readme=basic~summary:"explain a given subcommand (perhaps recursively)"Base.Spec.(empty+>flag"-recursive"no_arg~doc:" show subcommands of subcommands, etc."+>flag"-flags"no_arg~doc:" show flags as well in recursive help"+>flag"-expand-dots"no_arg~doc:" expand subcommands in recursive help"+>path+>env+>anon(maybe("SUBCOMMAND"%:string)))(funrecursiveflagsexpand_dotspath(env:Env.t)cmd_opt()->letsubs=matchEnv.findenvsubs_keywith|Somesubs->subs|None->assertfalse(* maintained by [dispatch] *)inletpath=letpath=Path.pop_helppathinOption.foldcmd_opt~init:path~f:(funpathsubcommand->Path.appendpath~subcommand)inletpath,shape=matchcmd_optwith|None->letsubcommands=List.Assoc.mapsubs~f:shape|>Lazy.from_valinletreadme=Option.mapreadme~f:(funreadme->readme())inpath,Shape.Group{readme;summary;subcommands}|Somecmd->(matchlookup_expand(List.Assoc.mapsubs~f:(funx->x,`Prefix))cmdSubcommandwith|Errore->die"unknown subcommand %s for command %s: %s"cmd(Path.to_stringpath)e()|Ok(possibly_expanded_name,t)->(* Fix the unexpanded value *)letpath=Path.replace_first~from:cmd~to_:possibly_expanded_namepathinpath,shapet)inprint_endline(help_for_shapeshapepath~recursive~flags~expand_dots));;(* This script works in both bash (via readarray) and zsh (via read -A). If you change
it, please test in both bash and zsh. It does not work tcsh (different function
syntax). *)letdump_autocomplete_function()=letfname=sprintf"_jsautocom_%s"(Pid.to_string(Unix.getpid()))inletargv_0=(Sys.get_argv()).(0)inprintf"function %s {\n\
\ export COMP_CWORD\n\
\ COMP_WORDS[0]=%s\n\
\ if type readarray > /dev/null\n\
\ then readarray -t COMPREPLY < <(\"${COMP_WORDS[@]}\")\n\
\ else IFS=\"\n\
\" read -d \"\" -A COMPREPLY < <(\"${COMP_WORDS[@]}\")\n\
\ fi\n\
}\n\
complete -F %s %s\n\
%!"fnameargv_0fnameargv_0;;letdump_help_sexp~supported_versionst~path_to_subcommand=Int.Set.interSexpable.supported_versionssupported_versions|>Int.Set.max_elt|>function|None->failwiths"Couldn't choose a supported help output version for Command.exec from the \
given supported versions."Sexpable.supported_versionsInt.Set.sexp_of_t|Someversion_to_use->to_sexpablet|>Sexpable.find~path_to_subcommand|>Sexpable.Internal.of_latest~version_to_use|>Sexpable.Internal.sexp_of_t|>Sexp.to_string|>print_string;;lethandle_environmentt~argv=matchargvwith|[]->failwith"missing executable name"|cmd::args->Option.iter(getenv_and_clearSexpable.extraction_var)~f:(funversion->letsupported_versions=Sexp.of_stringversion|>Int.Set.t_of_sexpindump_help_sexp~supported_versionst~path_to_subcommand:args;exit0);Option.iter(getenv_and_clear"COMMAND_OUTPUT_INSTALLATION_BASH")~f:(fun_->dump_autocomplete_function();exit0);cmd,args;;letprocess_args~cmd~args=letmaybe_comp_cword=maybe_comp_cword()inletargs=matchmaybe_comp_cwordwith|None->Cmdline.of_listargs|Somecomp_cword->letargs=List.take(args@[""])comp_cwordinList.fold_rightargs~init:Cmdline.Nil~f:(funargargs->matchargswith|Cmdline.Nil->Cmdline.Completearg|_->Cmdline.Cons(arg,args))inPath.create~path_to_exe:cmd,args,maybe_comp_cword;;letrecadd_help_subcommands=function|Base_ast->t|Exec_ast->t|Proxy_ast->t|Group{summary;readme;subcommands;body}->letsubcommands=Lazy.mapsubcommands~f:(funsubcommands->extend_alist_exn(List.Assoc.mapsubcommands~f:add_help_subcommands)Key_type.Subcommand~key:"help"(help_subcommand~summary~readme))inGroup{summary;readme;subcommands;body}|Lazythunk->Lazy(lazy(add_help_subcommands(Lazy.forcethunk)));;letmaybe_apply_extendargs~extend~path=Option.value_mapextend~default:args~f:(funf->Cmdline.extendargs~extend:f~path);;letrecdispatchtenv~extend~path~args~maybe_new_comp_cword~version~build_info~verbose_on_parse_error=matchtwith|Lazythunk->lett=Lazy.forcethunkindispatchtenv~extend~path~args~maybe_new_comp_cword~version~build_info~verbose_on_parse_error|Basebase->letargs=maybe_apply_extendargs~extend~pathinlethelp_text=lazy(help_for_shape(shapet)path~recursive:false~flags:true~expand_dots:false)inBase.runbaseenv~path~args~verbose_on_parse_error~help_text|Execexec->letargs=Cmdline.to_list(maybe_apply_extendargs~extend~path)inExec.exec_with_args~argsexec~maybe_new_comp_cword|Proxyproxy->letargs=proxy.path_to_subcommand@Cmdline.to_list(maybe_apply_extendargs~extend~path)inletexec={Exec.working_dir=proxy.working_dir;path_to_exe=proxy.path_to_exe;child_subcommand=proxy.child_subcommand;summary=Proxy.get_summaryproxy;readme=Proxy.get_readmeproxy|>Option.map~f:const}inExec.exec_with_args~argsexec~maybe_new_comp_cword|Group({summary;readme;subcommands=subs;body}asgroup)->letenv=Env.setenvsubs_key(Lazy.forcesubs)inletdie_showing_helpmsg=ifnot(Cmdline.ends_in_completeargs)then(eprintf"%s\n%!"(help_for_shape~recursive:false~flags:false~expand_dots:false(shape(Group{summary;readme;subcommands=subs;body}))path);die"%s"msg())in(matchargswith|Nil->(matchbodywith|None->die_showing_help(sprintf"missing subcommand for command %s"(Path.to_stringpath))|Somebody->body~path:(Path.parts_exe_basenamepath))|Cons(sub,rest)->letsub,rest=(* Match for flags recognized when subcommands are expected next *)matchsub,restwith(* Recognized at the top level command only *)|"-version",_whenPath.lengthpath=1->Version_info.print_version~version;exit0|"-build-info",_whenPath.lengthpath=1->Version_info.print_build_info~build_info;exit0(* Recognized everywhere *)|"-help",Nil->print_endline(help_for_shape~recursive:false~flags:false~expand_dots:false(shape(Group{groupwithsubcommands=subs}))path);exit0|"-help",Cmdline.Cons(sub,rest)->sub,Cmdline.Cons("-help",rest)|_->sub,restin(matchlookup_expand(List.Assoc.map(Lazy.forcesubs)~f:(funx->x,`Prefix))subSubcommandwith|Errormsg->die_showing_helpmsg|Ok(sub,t)->dispatchtenv~extend~path:(Path.appendpath~subcommand:sub)~args:rest~maybe_new_comp_cword:(Option.map~f:Int.predmaybe_new_comp_cword)~version~build_info~verbose_on_parse_error)|Completepart->letsubs=Lazy.forcesubs|>List.map~f:fst|>List.filter~f:(funname->String.is_prefixname~prefix:part)|>List.sort~compare:String.compareinList.itersubs~f:print_endline;exit0);;letdefault_version,default_build_info=(Version_util.version,(* lazy to avoid loading all the time zone stuff at toplevel *)lazy(Version_util.reprint_build_infoTime.sexp_of_t));;letrun?verbose_on_parse_error?(version=default_version)?build_info?(argv=Array.to_list(Sys.get_argv()))?extendt=letbuild_info=matchbuild_infowith|Somev->lazyv|None->default_build_infoinExn.handle_uncaught_and_exit(fun()->lett=Version_info.addt~version~build_infoinlett=add_help_subcommandstinletcmd,args=handle_environmentt~argvinletpath,args,maybe_new_comp_cword=process_args~cmd~argsintrydispatchtEnv.empty~extend~path~args~maybe_new_comp_cword~version~build_info~verbose_on_parse_errorwith|Failed_to_parse_command_linemsg->ifCmdline.ends_in_completeargsthenexit0else(prerr_endlinemsg;exit1));;letdeprecated_runt~cmd~args~is_help~is_help_rec~is_help_rec_flags~is_expand_dots=letpath_strings=String.splitcmd~on:' 'inletpath=Path.of_partspath_stringsinletargs=ifis_expand_dotsthen"-expand-dots"::argselseargsinletargs=ifis_help_rec_flagsthen"-flags"::argselseargsinletargs=ifis_help_recthen"-r"::argselseargsinletargs=ifis_helpthen"-help"::argselseargsinletargs=Cmdline.of_listargsinlett=add_help_subcommandstindispatchtEnv.empty~path~args~extend:None~maybe_new_comp_cword:None~version:default_version~build_info:default_build_info~verbose_on_parse_error:None;;end(* NOTE: all that follows is simply namespace management boilerplate. This will go away
once we re-work the internals of Command to use Applicative from the ground up. *)moduleParam=structmoduletypeS=sigtype+'atincludeApplicative.Swithtype'at:='atvalhelp:stringLazy.ttvalpath:stringlisttvalargs:stringlisttvalflag:?aliases:stringlist->?full_flag_required:unit->string->'aFlag.t->doc:string->'atvalflag_optional_with_default_doc:?aliases:stringlist->?full_flag_required:unit->string->'aArg_type.t->('a->Sexp.t)->default:'a->doc:string->'atvalanon:'aAnons.t->'atmoduleIf_nothing_chosen:sigtype(_,_)t=|Default_to:'a->('a,'a)t|Raise:('a,'a)t|Return_none:('a,'aoption)tendvalchoose_one:'aoptiontlist->if_nothing_chosen:('a,'b)If_nothing_chosen.t->'btvaland_arg_names:'at->('a*stringlist)tvaland_arg_name:'at->('a*string)tendmoduleA=structtype'at='aSpec.paramincludeApplicative.Make(structtypenonrec'at='atletreturn=Spec.constletapply=Spec.applyletmap=`CustomSpec.mapend)endincludeAlethelp=Spec.helpletpath=Spec.pathletargs=Spec.argsletflag=Spec.flagletanon=Spec.anonletchoose_one=Spec.choose_oneletand_arg_names=Spec.and_arg_namesletand_arg_name=Spec.and_arg_nameletflag_optional_with_default_doc=Spec.flag_optional_with_default_docmoduleArg_type=Arg_typemoduleIf_nothing_chosen=Spec.If_nothing_chosenincludeArg_type.ExportincludestructopenFlagletescape=escapeletlisted=listedletmap_flag=map_flagletno_arg=no_argletno_arg_abort=no_arg_abortletno_arg_register=no_arg_registerletno_arg_some=no_arg_someletone_or_more=one_or_moreletoptional=optionalletoptional_with_default=optional_with_defaultletrequired=requiredendincludestructopenAnonslet(%:)=(%:)letmap_anons=map_anonsletmaybe=maybeletmaybe_with_default=maybe_with_defaultletnon_empty_sequence_as_list=non_empty_sequence_as_listletnon_empty_sequence_as_pair=non_empty_sequence_as_pairletsequence=sequencelett2=t2lett3=t3lett4=t4endendmoduleLet_syntax=structincludeParammoduleLet_syntax=structincludeParammoduleOpen_on_rhs=Paramendendtype'resultbasic_command=summary:string->?readme:(unit->string)->(unit->'result)Param.t->tletbasic~summary?readmeparam=letspec=Spec.of_params@@Param.mapparam~f:(funrun()()->run())inletreadme=Option.mapreadme~f:(funf()->String.strip(f()))inbasic~summary?readmespec();;modulePrivate=structletabs_path=abs_pathmoduleAnons=AnonsmoduleCmdline=CmdlinemoduleFor_unix=For_unixmoduleFormat=FormatmodulePath=PathmoduleSpec=Specend