1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070moduleStable=structmoduleShape=Command_shape.Stableendopen!Importopen!Std_internalincludeCommand_intfmoduleShape=Command_shape(* 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|Exit_called{status=0}asexn->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))fmtlethelp_screen_compare=Shape.Private.help_screen_compare(* 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.setendmoduleAuto_complete=structtypet=Env.t->part:string->stringlistendmoduleCompleter=structtypet=Auto_complete.toptionletrun_and_exittenv~part:never_returns=Option.itert~f:(funcompletions->List.iter~f:print_endline(completionsenv~part));exit0;;endmoduleArg_type:sigtype'atvalextra_doc:'at->stringoptionlazy_tvalkey:'at->'aEnv.Multi.Key.toptionvalcomplete:'at->Completer.tvalparse:'at->string->('a,exn)resultvalcreate:?complete:Auto_complete.t->?key:'aEnv.Multi.Key.t->(string->'a)->'atvalmap:?key:'aEnv.Multi.Key.t->'bt->f:('b->'a)->'atvalof_lazy:?key:'aEnv.Multi.Key.t->'atlazy_t->'atvalof_map:?accept_unique_prefixes:bool->?case_sensitive:bool->?list_values_in_help:bool->?auto_complete:Auto_complete.t->?key:'aEnv.Multi.Key.t->'aString.Map.t->'atvalof_alist_exn:?accept_unique_prefixes:bool->?case_sensitive:bool->?list_values_in_help:bool->?auto_complete:Auto_complete.t->?key:'aEnv.Multi.Key.t->(string*'a)list->'atvalenumerated:?accept_unique_prefixes:bool->?case_sensitive:bool->?list_values_in_help:bool->?auto_complete:Auto_complete.t->?key:'aEnv.Multi.Key.t->(moduleEnumerable_stringablewithtypet='a)->'atvalenumerated_sexpable:?accept_unique_prefixes:bool->?case_sensitive:bool->?list_values_in_help:bool->?auto_complete:Auto_complete.t->?key:'aEnv.Multi.Key.t->(moduleEnumerable_sexpablewithtypet='a)->'atvalcomma_separated:?allow_empty:bool->?key:'alistEnv.Multi.Key.t->?strip_whitespace:bool->?unique_values:bool->'at->'alisttmoduleExport:sigvalstring:stringtvalint:inttvalchar:chartvalfloat:floattvalbool:booltvaldate:Date.ttvalpercent:Percent.ttvalhost_and_port:Host_and_port.ttvalsexp:Sexp.ttvalsexp_conv:?complete:Auto_complete.t->(Sexp.t->'a)->'atendmoduleFor_testing:sigvalcomplete:_t->Auto_complete.tvalparse:'at->string->'aOr_error.tendend=structtype'at={parse:string->'a;complete:Completer.t;key:'aUniv_map.Multi.Key.toption;extra_doc:stringoptionLazy.t}[@@derivingfields]letparsets=Result.try_with(fun()->t.parses)letcreate'?complete?keyparse~extra_doc={parse;key;complete;extra_doc}letcreate?complete?keyof_string=create'?complete?keyof_string~extra_doc:(Lazy.from_valNone);;letmap?keyt~f={twithkey;parse=(funs->f(t.parses))}letof_lazy?keyt=letparsestr=(forcet).parsestrinletcompleteenv~part=match(forcet).completewith|None->(* See [run_and_exit] - no completions is equivalent to not having a
[Complete]. *)[]|Somecomplete->completeenv~partinletextra_doc=Lazy.bindt~f:extra_docin{parse;complete=Somecomplete;key;extra_doc};;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_conv?completeof_sexp=create?complete(funs->of_sexp(Sexp.of_strings))letassociative?(accept_unique_prefixes=true)?(list_values_in_help=true)?auto_complete?key~case_sensitivealist=letopenstructmoduletypeS=sigincludeComparator.Swithtypet=stringvalis_prefix:string->prefix:string->boolendtype'at=|T:{cmp:(moduleSwithtypecomparator_witness='cmp);map:(string,'a,'cmp)Map.t}->'atendinlet(T{cmp=(moduleS);map})=letmake_map_raise_duplicate_key(typecmp)(moduleS:Swithtypecomparator_witness=cmp)alist=matchMap.of_alist(moduleS)alistwith|`Okmap->map|`Duplicate_key(_:S.t)->letduplicate_keys=List.mapalist~f:(fun(k,(_:'a))->k,k)|>Map.of_alist_multi(moduleS)|>Map.filter~f:(function|[]|[_]->false|_::_::_->true)|>Map.datainraise_s[%message"Command.Spec.Arg_type.of_alist_exn"(duplicate_keys:stringlistlist)]inletmakecmp=T{cmp;map=make_map_raise_duplicate_keycmpalist}inifcase_sensitivethenmake(moduleString)elsemake(moduleString.Caseless)inletcompleteuniv_map~part:prefix=matchauto_completewith|Somecomplete->completeuniv_map~part:prefix|None->List.filter_map(Map.to_alistmap)~f:(fun(name,_)->matchS.is_prefixname~prefixwith|false->None|true->(* Bash completion will not accept [Foo] as a completion for [f]. So we need
to match the capitalization given. *)letsuffix=String.suboname~pos:(String.lengthprefix)inletname=prefix^suffixinSomename)inletfindarg=matchMap.findmapargwith|Some_ass->s|None->(matchaccept_unique_prefixeswith|false->None|true->(matchMap.to_alistmap|>List.filter~f:(fun(name,_)->S.is_prefixname~prefix:arg)with|[(_singleton_key,v)]->Somev|[]|_::_::_->(* In the two-or-more case we could provide filtered help text, but it's
more generally useful to list all the options, which we do below. *)None))increate'~extra_doc:(lazy(iflist_values_in_helpthen(letvalues=String.concat~sep:", "(Map.keysmap)inSome[%string"(can be: %{values})"])elseNone))?key~complete(funarg->matchfindargwith|Somev->v|None->letvalid_arguments_extra=ifcase_sensitivethen""else" (case insensitive)"infailwithf"valid arguments%s: {%s}"valid_arguments_extra(String.concat~sep:","(Map.keysmap))());;letof_alist_exn?accept_unique_prefixes?(case_sensitive=true)?list_values_in_help?auto_complete?keyalist=associative?accept_unique_prefixes?list_values_in_help?auto_complete?key~case_sensitivealist;;letof_map?accept_unique_prefixes?case_sensitive?list_values_in_help?auto_complete?keymap=of_alist_exn?accept_unique_prefixes?case_sensitive?list_values_in_help?auto_complete?key(Map.to_alistmap);;letenumerated(typet)?accept_unique_prefixes?case_sensitive?list_values_in_help?auto_complete?key(moduleE:Enumerable_stringablewithtypet=t)=of_alist_exn?accept_unique_prefixes?case_sensitive?list_values_in_help?auto_complete?key(let%map.Listt=E.allinE.to_stringt,t);;letenumerated_sexpable(typet)?accept_unique_prefixes?case_sensitive?list_values_in_help?auto_complete?key(moduleE:Enumerable_sexpablewithtypet=t)=enumerated?accept_unique_prefixes?case_sensitive?list_values_in_help?auto_complete?key(modulestructincludeEletto_stringt=Sexp.to_string[%sexp(t:E.t)]end);;letbool=enumerated~list_values_in_help:false(moduleBool)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->t.parse(stripstr))increate?key?completeof_string;;moduleExport=structletstring=stringletint=intletchar=charletfloat=floatletbool=boolletdate=dateletpercent=percentlethost_and_port=host_and_portletsexp=sexpletsexp_conv=sexp_convendmoduleFor_testing=structletcompletet=matcht.completewith|Somef->f|None->fun_~part:_->[];;letparsetstr=parsetstr|>Or_error.of_exn_resultendendmoduleFlag=structmoduleNum_occurrences=structtypet=Shape.Num_occurrences.t={at_least_once:bool;at_most_once:bool}[@@derivingcompare,enumerate,fields,sexp_of]letto_help_string=Shape.Num_occurrences.to_help_stringletto_help_string_deprecated{at_least_once;at_most_once=_}flag_name=to_help_string{at_least_once;at_most_once=true}~flag_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)|Print_info_and_quitof(Env.t->string)|Argof(Env.t->string->Env.t)*Completer.t|Restof(Env.t->stringlist->Env.t)moduleInternal=structtypet={name:string;aliases:stringlist;aliases_excluded_from_help:stringlist(* [aliases_excluded_from_help] are aliases that don't show up in -help output.
Currently they're only used for double-dash built-in flags like --help and
--version. *);action:action;doc:string;num_occurrences:Num_occurrences.t;check_available:Env.t->unit;name_matching:[`Prefix|`Full_match_required]}letwrap_if_optionaltflag_name=Num_occurrences.to_help_stringt.num_occurrences~flag_name;;moduleDoc=structtypet={arg_doc:stringoption;doc:string}letparse~action~doc=letarg_doc,doc=match(action:action),String.lsplit2doc~on:' 'with|(No_arg_|Print_info_and_quit_),_->None,doc|Arg_,(None|Some("",_))->Some"_",doc|Rest_,(None|Some("",_))->None,doc|(Arg_|Rest_),Some(arg,doc)->Somearg,docin{doc=String.stripdoc;arg_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=_;aliases_excluded_from_help=_}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=_;aliases_excluded_from_help=_}ast):Shape.Flag_info.t=let{Doc.arg_doc;doc}=Doc.parse~action~docinletname=wrap_if_optionalt(Doc.concat~name~arg_doc)in{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;extra_doc:stringoptionLazy.t}type'at=string->'astateletarg_flagnamearg_typereadwritenum_occurrences={read;num_occurrences;action=(letupdateenvarg=matchArg_type.parsearg_typeargwith|Errorexn->die"failed to parse %s value %S.\n%s"namearg(Exn.to_stringexn)()|Okarg->letenv=writeenvargin(matchArg_type.keyarg_typewith|None->env|Somekey->Env.multi_addenv~key~data:arg)inArg(update,Arg_type.completearg_type));extra_doc=Arg_type.extra_docarg_type};;letmap_flagt~finput=let{action;read;num_occurrences;extra_doc}=tinputin{action;read=(funenv->f(readenv));num_occurrences;extra_doc};;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.setenv~key~data:()inletactionenv=letenv=Option.foldkey_value~init:env~f:(funenv(key,value)->Env.set_with_defaultenv~key~data:value)inwriteenvinletaction=matchdeprecated_hookwith|None->action|Somef->funenv->letenv=actionenvinf();envin{read;action=No_argaction;num_occurrences=Num_occurrences.at_most_once;extra_doc=Lazy.from_valNone};;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_more_as_pairarg_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;;let[@deprecated"[since 2021-07] Use [one_or_more_as_pair] or [one_or_more_as_list]"]one_or_more=one_or_more_as_pair;;letone_or_more_as_listarg_type=one_or_more_as_pairarg_type|>map_flag~f:(fun(x,xs)->x::xs);;letescape_general~deprecated_hookname=letkey=Env.Key.create~name[%sexp_of:stringlist]inletactionenvcmd_line=Env.setenv~key~data:cmd_lineinletreadenv=Env.findenvkeyinletaction=matchdeprecated_hookwith|None->action|Somef->funenvx->fx;actionenvxin{action=Restaction;read;num_occurrences=Num_occurrences.at_most_once;extra_doc=Lazy.from_valNone};;letno_arg_abort~exit_name={action=No_arg(fun_->never_returns(exit()));num_occurrences=Num_occurrences.at_most_once;read=(fun_->());extra_doc=Lazy.from_valNone};;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:sigtypet=Shape.Anons.Grammar.tvalzero:tvalone:string->tvalmany:t->tvalmaybe:t->tvalconcat:tlist->tvalad_hoc:usage:string->tincludeInvariant.Swithtypet:=tvalnames:t->stringlistend=structtypet=Shape.Anons.Grammar.t=|Zero|Oneofstring|Manyoft|Maybeoft|Concatoftlist|Ad_hocofstringletinvariant=Shape.Anons.Grammar.invariantletusage=Shape.Anons.Grammar.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->'alisttvalstop_parsing:'at->'atvalfinal_value:'at->Env.t->'amoduleConsume_result:sigtypenonrec'at={(* If emacs highlights [parser] as if it were a keyword, that's only because
[parser] was a keyword in camlp4. [parser] is a regular name in OCaml. *)parser:'at;parse_flags:bool;update_env:Env.t->Env.t}endvalconsume:'at->string->for_completion:bool->'aConsume_result.tvalcomplete:'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_completionofpackedlist|Stop_parsingof'atand'amore={name:string;parse:string->for_completion:bool->'aparse_result;complete:Completer.t}andpacked=Packed:'at->packedand'aparse_result={parser:'at;update_env:Env.t->Env.t}letreturna=Done(fun_->a)letfrom_envf=Donefletstop_parsingt=Stop_parsingtletpack_for_completion=function|Done_->[](* won't complete or consume anything *)|(More_|Test_|Stop_parsing_)asx->[Packedx]|Only_for_completionps->ps;;letparse_more{name;parse;complete}~f=letparsearg~for_completion=let{parser;update_env}=parsearg~for_completionin{parser=fparser;update_env}inMore{name;parse;complete};;letrec(<*>)t_leftt_right=matcht_left,t_rightwith(* [Done] *)|Donef,Donex->Done(funenv->fenv(xenv))(* next step [More] *)|Moremore,_->parse_moremore~f:(funtl->tl<*>t_right)|Done_,Moremore->parse_moremore~f:(funtr->t_left<*>tr)(* next step [Only_for_completion] *)|Only_for_completion_,_|Done_,Only_for_completion_->Only_for_completion(pack_for_completiont_left@pack_for_completiont_right)(* next step [Stop_parsing] *)|Stop_parsingtl,tr|(Done_astl),Stop_parsingtr->Stop_parsing(tl<*>tr)(* next step [Test] *)|Testtest,_->Test(fun~more->test~more<*>t_right)|Done_,Testtest->Test(fun~more->t_left<*>test~more);;let(>>|)tf=returnf<*>tletone_more~namearg_type=letparseanon~for_completion=matchArg_type.parsearg_typeanonwith|Errorexn->iffor_completionthen(* we don't *really* care about this value, so just put in a dummy value so
completion can continue *){parser=Only_for_completion[];update_env=Fn.id}elsedie"failed to parse %s value %S\n%s"nameanon(Exn.to_stringexn)()|Okv->{parser=returnv;update_env=(funenv->Option.fold(Arg_type.keyarg_type)~init:env~f:(funenvkey->Env.multi_addenv~key~data:v))}inMore{name;parse;complete=Arg_type.completearg_type};;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|Stop_parsingt->final_valuetenv|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";;moduleConsume_result=structtypenonrec'at={parser:'at;parse_flags:bool;update_env:Env.t->Env.t}endletrecconsume:typea.at->string->for_completion:bool->aConsume_result.t=funtarg~for_completion->matchtwith|Done_->die"too many anonymous arguments"()|Testf->consume(f~more:true)arg~for_completion|More{parse;_}->let{parser;update_env}=parsearg~for_completionin{parser;parse_flags=true;update_env}|Stop_parsingt->{(consumetarg~for_completion)withparse_flags=false}|Only_for_completionpacked->(matchpackedwith|[]->{parser=Only_for_completion[];parse_flags=true;update_env=Fn.id}|Packedt::rest->let({update_env;parse_flags;parser}:_Consume_result.t)=consumetarg~for_completionin{update_env;parse_flags;parser=Only_for_completion(pack_for_completionparser@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|Stop_parsingt->completetenv~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)};;letescapet={p=Parser.stop_parsingt.p;grammar=t.grammar}moduleDeprecated=structletad_hoc~usage_arg={p=Parser.sequence(Parser.one~name:"WILL NEVER BE PRINTED"Arg_type.Export.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=Shape.Private.Key_typeletassert_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_expand=Shape.Private.lookup_expandletlookup_expand_with_aliasesmapprefixkey_type=letalist=List.concat_map(String.Map.datamap)~f:(funflag->let{Flag.Internal.name;aliases;aliases_excluded_from_help;action=_;doc=_;num_occurrences=_;check_available=_;name_matching}=flaginletdata=flag,name_matchinginletaliases=aliases_excluded_from_help@aliasesin(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.nameb.name)|>Shape.Flag_help_display.sort;;letshapet:Shape.Base_info.t={summary=t.summary;readme=Option.mapt.readme~f:(funreadme->readme());anons=Grammart.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";;letget_flag_and_actiontarg=matchlookup_expand_with_aliasest.flagsargFlagwith|Errormsg->die"%s"msg()|Ok(flag_name,flag)->flag_name,flag.action;;letrun_flagtenvarg(args:Cmdline.t)=letflag,action=get_flag_and_actiontarginmatchactionwith|Print_info_and_quitinfo->letcompleting=Cmdline.ends_in_completeargsin(* If we're doing completion, version/help info aren't useful completion
responses. *)ifcompletingthenenv,argselse(print_endline(infoenv);exit0)|No_argf->fenv,args|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_completerestthenenvelseraiseeinenv,rest|Completepart->never_returns(Completer.run_and_exitcompenv~part))|Restf->ifCmdline.ends_in_completeargsthenexit0;fenv(Cmdline.to_listargs),Nil;;letrecrun_cmdlinetenvparser(cmdline:Cmdline.t)~for_completion~parse_flags=matchcmdlinewith|Nil->List.iter(String.Map.datat.flags)~f:(funflag->flag.check_availableenv);Anons.Parser.final_valueparserenv|Completepart->ifparse_flags&&String.is_prefixpart~prefix:"-"then(List.iter(String.Map.keyst.flags)~f:(funname->ifString.is_prefixname~prefix:partthenprint_endlinename);exit0)elsenever_returns(Anons.Parser.completeparserenv~part)|Cons(arg,args)->letarg,args,arg_is_flag=matchparse_flagswith|false->arg,args,false|true->(matcharg,argswith(* the '-anon' flag is here as an escape hatch in case you have an
anonymous argument that starts with a hyphen. *)|"-anon",Cons(arg,args)->arg,args,false(* support the common Unix convention where "-" means stdin *)|"-",_->arg,args,false|_,_->arg,args,String.is_prefixarg~prefix:"-")in(matcharg_is_flagwith|true->letenv,args=run_flagtenvargargsinrun_cmdlinetenvparserargs~parse_flags~for_completion|false->letparse_flags1=parse_flagsinlet({parser;parse_flags=parse_flags2;update_env}:_Anons.Parser.Consume_result.t)=Anons.Parser.consumeparserarg~for_completioninletenv=update_envenvinletparse_flags=parse_flags1&&parse_flags2inrun_cmdlinetenvparser~parse_flagsargs~for_completion);;letrun_exnexn~for_completion~path~verbose_on_parse_error=matchexnwith|Failed_to_parse_command_line_whenfor_completion->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;;letruntenv~when_parsing_succeeds~path~args~verbose_on_parse_error~help_text=letfor_completion=Cmdline.ends_in_completeargsinletenv=env|>Env.set~key:path_key~data:path|>Env.set~key:args_key~data:(Cmdline.to_listargs)|>Env.set~key:help_key~data:help_textinmatchResult.try_with(fun()->run_cmdlinetenv(t.anons())~for_completion~parse_flags:trueargs`Parse_args)with|Okthunk->when_parsing_succeeds();thunk`Run_main|Errorexn->run_exnexn~for_completion~path~verbose_on_parse_error;;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)}};;endletescape_anon~final_anon=Anons.escape(t2final_anon(sequence("ARG"%:string)))|>anon;;includestructopenFlagtype'aflag='atletmap_flag=map_flagletescape=escapeletlisted=listedletone_or_more=one_or_moreletone_or_more_as_pair=one_or_more_as_pairletone_or_more_as_list=one_or_more_as_listletno_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_internal?(aliases=[])?full_flag_requirednamemode~doc~aliases_excluded_from_help=letnormalizeflag=normalizeKey_type.Flagflaginletname=normalizenameinletaliases=List.map~f:normalizealiasesinlet{read;action;num_occurrences;extra_doc}=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;aliases_excluded_from_help;doc=(matchforceextra_docwith|Someextra_doc->[%string"%{doc} %{extra_doc}"]|None->doc);action;num_occurrences;check_available;name_matching}]);usage=(fun()->Anons.Grammar.zero)}};;letflag=flag_internal~aliases_excluded_from_help:[]letflag_optional_with_default_doc?aliases?full_flag_requirednamearg_typesexp_of_default~default~doc=letdoc=matchsexp_of_defaultdefaultwith|Sexp.Atom"_"->doc|default_sexp->sprintf!"%s (default: %{Sexp})"docdefault_sexpinflag?aliases?full_flag_requiredname(optional_with_defaultdefaultarg_type)~doc;;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_values_in_help:false(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"|((Arg.Rest_all_)[@ifocaml_version>=(4,12,0)])->failwith"Arg.Rest_all 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];;moduleChoose_one=structmoduleChoice_name:sigtypet[@@derivingcompare,sexp_of]includeComparator.Swithtypet:=tvalto_string:t->stringvallist_to_string:tlist->stringvalcreate_exn:'aparam->tend=structmoduleT=structtypet=stringlist[@@derivingcompare,sexp_of]endincludeTincludeComparator.Make(T)letcreate_exnparam=letnames=arg_namesparaminletnames_with_commas=List.filternames~f:(funs->String.containss',')inifnot(List.is_emptynames_with_commas)thenfailwiths~here:[%here]"For simplicity, [Command.Spec.choose_one] does not support names with \
commas."names_with_commas[%sexp_of:stringlist];matchnameswith|[]->raise_s[%message"[choose_one] expects choices to read command-line arguments."]|_::_->names;;letto_string=String.concat~sep:","letlist_to_stringts=List.mapts~f:to_string|>String.concat~sep:"\n "endmoduleIf_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)=matchList.mapts~f:(funt->Choice_name.create_exnt,t)|>Map.of_alist(moduleChoice_name)with|`Duplicate_keyname->failwiths~here:[%here]"[Command.Spec.choose_one] called with duplicate name"name[%sexp_of:Choice_name.t]|`Okts->Map.foldts~init:(return[])~f:(fun~key:name~data:tinit->map2initt~f:(funinitvalue->Option.foldvalue~init~f:(funinitvalue->(name,value)::init)))|>map~f:(function|_::_::_aspassed->die!"Cannot pass more than one of these: \n\
\ %{Choice_name.list_to_string}"(List.mappassed~f:fst)()|[(_,value)]->(matchif_nothing_chosenwith|Default_to(_:a)->(value:b)|Raise->(value:b)|Return_none->(Somevalue:b))|[]->(matchif_nothing_chosenwith|Default_tovalue->value|Return_none->None|Raise->die!"Must pass one of these:\n %{Choice_name.list_to_string}"(Map.keysts)()));;endmoduleIf_nothing_chosen=Choose_one.If_nothing_chosenletchoose_one=Choose_one.choose_oneletand_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}letshape~subcommand_to_shapet:_Shape.Group_info.t={summary=t.summary;readme=Option.map~f:(funreadme->readme())t.readme;subcommands=Lazy.mapt.subcommands~f:(List.Assoc.map~f:subcommand_to_shape)};;endletabs_path=Shape.Private.abs_pathletcomp_cword=Command_env_var.COMP_CWORDmoduleExec=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;env:envoption}letshapet:Shape.Exec_info.t={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=|BaseofShape.Base_info.t|Groupof'aShape.Group_info.t|ExecofShape.Exec_info.t|Lazyof'atLazy.tendtypet={working_dir:string;path_to_exe:string;path_to_subcommand:stringlist;child_subcommand:stringlist;kind:tKind.t}endtypet=|BaseofBase.t|GroupoftGroup.t|ExecofExec.t|LazyoftLazy.tletrecsexpable_shape:t->Shape.Sexpable.t=function|Basebase->Base(Base.shapebase)|Execexec->Exec(Exec.shapeexec)|Groupgroup->Group(Group.shape~subcommand_to_shape:sexpable_shapegroup)|Lazythunk->Lazy(Lazy.map~f:sexpable_shapethunk);;type('main,'result)basic_spec_command=summary:string->?readme:(unit->string)->('main,unit->'result)Base.Spec.t->'main->tletextend_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~aliases_excluded_from_help~text~text_summary=letflags=base.Base.flagsinletflags=extend_map_exnflagsKey_type.Flag~key:name{name;aliases_excluded_from_help;aliases;num_occurrences=Flag.Num_occurrences.at_most_once;check_available=ignore;action=Print_info_and_quit(funenv->textenv);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:["-?"]~aliases_excluded_from_help:["--help"]~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=letreadme=Option.mapreadme~f:(funf()->String.strip(f()))inlazy_group~summary?readme?preserve_subcommand_order?body(Lazy.from_valalist);;letexec~summary?readme?(child_subcommand=[])?env~path_to_exe()=letworking_dir=Filename.dirname@@matchpath_to_exewith|`Absolute_|`Relative_to_me_->Caml.Sys.executable_name|`Relative_to_argv0_->Caml.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;env};;letof_lazythunk=Lazythunkletrecproxy_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:Shape.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))};;moduletypeFor_version_info=sigmoduleVersion_util:Version_utilmoduleTime:sigtypet=Time_float.t[@@derivingsexp_of]endendmoduleVersion_info(M:For_version_info)=structopenMletprint_version~version=print_endline(forceversion)letprint_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:[]~aliases_excluded_from_help:["--version"]~text_summary:"the version of this build"~text:(fun_->forceversion)inletbase=Bailout_dump_flag.addbase~name:"-build-info"~aliases:[]~aliases_excluded_from_help:["--build-info"]~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}|Execexec->Execexec|Lazythunk->Lazy(lazy(add~version~build_info(Lazy.forcethunk)));;letnormalize_version_lineslines=String.concat~sep:"\n"(List.sortlines~compare:String.compare);;letdefault_version=lazy(normalize_version_linesVersion_util.version_list)letdefault_build_info=lazy(* lazy to avoid loading all the time zone stuff at toplevel *)(Version_util.reprint_build_infoTime.sexp_of_t);;endlet%test_module"Version_info"=(modulestructmoduleVersion_info=Version_info(structmoduleVersion_util=structletversion_list=["hg://some/path_0xdeadbeef";"ssh://a/path_8badf00d"]letreprint_build_infoto_sexp=Sexp.to_string(to_sexpTime_float.epoch)endmoduleTime=structtypet=Time_float.tletsexp_of_tt=Time_float.to_string_utct|>Sexp.of_stringendend)let%expect_test"print version where multiple repos are used"=Version_info.print_version~version:Version_info.default_version;[%expect{|
hg://some/path_0xdeadbeef
ssh://a/path_8badf00d |}];;let%expect_test"print build info"=Version_info.print_build_info~build_info:(lazy"some build info");[%expect{| some build info |}];;end);;letrecsummary=function|Basex->x.summary|Groupx->x.summary|Execx->x.summary|Lazythunk->summary(Lazy.forcethunk);;moduleSpec=structincludeBase.Specletpath=map~f:Path.parts_exe_basenamepathendmoduleDeprecated=structmoduleSpec=Spec.Deprecatedletsummary=summaryletrecget_flag_names=function|Basebase->base.Base.flags|>String.Map.keys|Lazythunk->get_flag_names(Lazy.forcethunk)|Group_|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))|Exec_->(* Command.exec does not support deprecated commands *)[]inhelp_recursive_rec~cmdts;;endmoduleFor_unix(For_unix_with_string_env_var:For_unixwithtypeenv_var:=string)=structmoduleVersion_info=Version_info(For_unix_with_string_env_var)moduleFor_unix_with_command_env_var:For_unixwithtypeenv_var:=Command_env_var.t=struct(* We force access to env vars to go through [Command_env_var] so that we can keep an
accurate enumeration of the variables we use. *)includeFor_unix_with_string_env_varmoduleUnix=structincludeUnixletputenv~key~data=putenv~key:(Command_env_var.to_stringkey)~dataletunsetenvkey=unsetenv(Command_env_var.to_stringkey)letunsafe_getenvkey=unsafe_getenv(Command_env_var.to_stringkey)typeenv=[`Replaceof(Command_env_var.t*string)list|`Extendof(Command_env_var.t*string)list|`Overrideof(Command_env_var.t*stringoption)list|`Replace_rawofstringlist]letconvert_envenv=letconvert_command_env_var_to_stringlist=List.maplist~f:(fun(env_var,str)->Command_env_var.to_stringenv_var,str)inmatchenvwith|`Replacelist->`Replace(convert_command_env_var_to_stringlist)|`Extendlist->`Extend(convert_command_env_var_to_stringlist)|`Overridelist->`Override(convert_command_env_var_to_stringlist)|`Replace_raw_asreplace->replace;;letexec~prog~argv?use_path?env()=exec~prog~argv?use_path?env:(Option.mapenv~f:convert_env)();;letcreate_process_env?working_dir?prog_search_path?argv0~prog~args~env()=create_process_env?working_dir?prog_search_path?argv0~prog~args~env:(convert_envenv)();;endendopenFor_unix_with_command_env_var(* 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=Unix.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@argsinletenv=t.envinOption.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(For_unix_with_string_env_var.Unix.exec?env~prog~argv:(prog::args)());;endmoduleSexpable=structincludeShape.Sexpableletread_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~on_uncaught_exn:`Print_to_stderr(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:(lethelp_sexp=supported_versions|>Int.Set.sexp_of_t|>Sexp.to_stringin`Extend[COMMAND_OUTPUT_HELP_SEXP,help_sexp])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|>Versioned.t_of_sexp|>of_versionedwith|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({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.shapeb)|Groupg->Group(Group.shape~subcommand_to_shape:shapeg)|Exec({Exec.child_subcommand;path_to_exe;working_dir;_}ase)->Exec(Exec.shapee,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:(fun(acc:Shape.Flag_info.tFqueue.t)(subcommand,shape)->letpath=Path.appendpath~subcommandinletname=string_of_pathpathinletdoc=Shape.get_summaryshapeinletacc=Fqueue.enqueueacc{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.name"[-help]")|>List.fold~init:acc~f:(funaccfmt->letpath=Path.appendpath~subcommand:fmt.nameinletfmt={fmtwithname=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(Shape.Flag_help_display.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(Shape.Flag_help_display.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=Caml.Sys.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~here:[%here]"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->sexpable_shapet|>Sexpable.find~path_to_subcommand|>Sexpable.to_versioned~version_to_use|>Sexpable.Versioned.sexp_of_t|>Sexp.to_string|>print_string;;lethandle_environmentt~argv=matchargvwith|[]->failwith"missing executable name"|cmd::args->Option.iter(getenv_and_clearCOMMAND_OUTPUT_HELP_SEXP)~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_clearCOMMAND_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|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~when_parsing_succeeds~complete_subcommands=matchtwith|Lazythunk->lett=Lazy.forcethunkindispatchtenv~extend~path~args~maybe_new_comp_cword~version~build_info~verbose_on_parse_error~when_parsing_succeeds~complete_subcommands|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~when_parsing_succeeds|Execexec->letargs=Cmdline.to_list(maybe_apply_extendargs~extend~path)inExec.exec_with_args~argsexec~maybe_new_comp_cword|Group({summary;readme;subcommands=subs;body}asgroup)->letcompleting=Cmdline.ends_in_completeargsinletenv=Env.setenv~key:subs_key~data:(Lazy.forcesubs)inletdie_showing_helpmsg=ifcompletingthenexit0else(eprintf"%s\n%!"(help_for_shape~recursive:false~flags:false~expand_dots:false(shape(Group{summary;readme;subcommands=subs;body}))path);die"%s"msg())inletrecparse_groupargs~maybe_new_comp_cword=letmaybe_new_comp_cword=Option.map~f:Int.predmaybe_new_comp_cwordinletskiprest=parse_grouprest~maybe_new_comp_cwordinletresolvesubrest=letsubs=List.Assoc.map(Lazy.forcesubs)~f:(funx->x,`Prefix)inmatchlookup_expandsubssubSubcommandwith|Errormsg->die_showing_helpmsg|Ok(sub,t)->dispatchtenv~when_parsing_succeeds~extend~path:(Path.appendpath~subcommand:sub)~args:rest~maybe_new_comp_cword~version~build_info~verbose_on_parse_error~complete_subcommandsinmatch(args:Cmdline.t)with|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)->(* Match for flags recognized when subcommands are expected next *)(matchsubwith(* Recognized at the top level command only *)|("-version"|"--version")whenPath.lengthpath=1->ifcompletingthenskiprestelse(Version_info.print_version~version;exit0)|("-build-info"|"--build-info")whenPath.lengthpath=1->ifcompletingthenskiprestelse(Version_info.print_build_info~build_info;exit0)(* Recognized everywhere *)|"-help"|"--help"->ifcompletingthenskiprestelse(matchrestwith|Nil|Complete(_:string)->print_endline(help_for_shape~recursive:false~flags:false~expand_dots:false(shape(Group{groupwithsubcommands=subs}))path);exit0|Cmdline.Cons(first_of_rest,rest_of_rest)->resolvefirst_of_rest(Cons(sub,rest_of_rest)))|(_:string)->resolvesubrest)|Completepart->letsubs=Lazy.forcesubs|>List.map~f:fst|>List.filter~f:(funname->String.is_prefixname~prefix:part)|>List.sort~compare:String.comparein(matchcomplete_subcommandswith|Somef->letsubcommands=shapet|>Shape.fully_forced|>Shape.Fully_forced.expanded_subcommandsin(matchf~path:(Path.partspath)~partsubcommandswith|None->exit1|Someto_output->print_endline(String.concat~sep:" "to_output);exit0)|None->List.itersubs~f:print_endline;exit0)inparse_groupargs~maybe_new_comp_cword;;letrun?verbose_on_parse_error?version?build_info?(argv=Array.to_listCaml.Sys.argv)?extend?(when_parsing_succeeds=Fn.id)?complete_subcommandst=letbuild_info=matchbuild_infowith|Somev->lazyv|None->Version_info.default_build_infoinletversion=matchversionwith|None->Version_info.default_version|Somev->(* [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 *)lazy(Version_info.normalize_version_lines(String.splitv~on:' '|>List.concat_map~f:(String.split~on:'\n')))inExn.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_error~when_parsing_succeeds~complete_subcommandswith|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:Version_info.default_version~build_info:Version_info.default_build_info~verbose_on_parse_error:None~when_parsing_succeeds:Fn.id~complete_subcommands: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->'atvalescape_anon:final_anon:'aAnons.t->('a*stringlist)tmoduleIf_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)tvalarg_names:'at->stringlistendmoduleA=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_oneletarg_names=Spec.arg_namesletand_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_moreletone_or_more_as_pair=one_or_more_as_pairletone_or_more_as_list=one_or_more_as_listletoptional=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=t4endletescape_anon=Spec.escape_anonendmoduleLet_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_pathletword_wrap=Shape.Private.word_wrapmoduleAnons=AnonsmoduleCmdline=CmdlinemoduleFor_unix=For_unixmodulePath=PathmoduleSpec=structincludeSpecletto_string_for_choose_oneparam=Choose_one.Choice_name.(create_exnparam|>to_string);;endendletrun=`Use_Command_unixletshape=`Use_Command_unix