123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341(*---------------------------------------------------------------------------
Copyright (c) 2011 The cmdliner programmers. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)letstrf=Printf.sprintf(* Unique ids *)letuid=(* Thread-safe UIDs, Oo.id (object end) was used before.
Note this won't be thread-safe in multicore, we should use
Atomic but this is >= 4.12 and we have 4.08 for now. *)letc=ref0infun()->letid=!cinincrc;ifid>!cthenassertfalse(* too many ids *)elseid(* Edit distance *)letedit_distances0s1=letminimum(a:int)(b:int)(c:int):int=mina(minbc)inlets0,s1=ifString.lengths0<=String.lengths1thens0,s1elses1,s0inletm=String.lengths0andn=String.lengths1inletrecrowsrow0rowi=matchi>nwith|true->row0.(m)|false->row.(0)<-i;forj=1tomdoifs0.[j-1]=s1.[i-1]thenrow.(j)<-row0.(j-1)elserow.(j)<-minimum(row0.(j-1)+1)(row0.(j)+1)(row.(j-1)+1)done;rowsrowrow0(i+1)inrows(Array.init(m+1)(funx->x))(Array.make(m+1)0)1letsuggestscandidates=letadd(min,acc)name=letd=edit_distancesnameinifd=minthenmin,(name::acc)elseifd<minthend,[name]elsemin,accinletdist,suggs=List.fold_leftadd(max_int,[])candidatesinifdist<3(* suggest only if not too far *)thensuggselse[](* Invalid argument strings *)leterr_empty_list="empty list"leterr_incomplete_enumss=strf"Arg.enum: missing printable string for a value, other strings are: %s"(String.concat", "ss)(* Formatting tools *)letpp=Format.fprintfletpp_sp=Format.pp_print_spaceletpp_str=Format.pp_print_stringletpp_char=Format.pp_print_charletpp_text=Format.pp_print_textletpp_linesppfs=letrecstop_atsat~start~maxs=ifstart>maxthenstartelseifsats.[start]thenstartelsestop_atsat~start:(start+1)~maxsinletsubsstartstop~max=ifstart=stopthen""elseifstart=0&&stop>maxthenselseString.subsstart(stop-start)inletis_nlc=c='\n'inletmax=String.lengths-1inletrecloopstarts=matchstop_atis_nl~start~maxswith|stopwhenstop>max->Format.pp_print_stringppf(subsstartstop~max)|stop->Format.pp_print_stringppf(subsstartstop~max);Format.pp_force_newlineppf();loop(stop+1)sinloop0sletpp_tokens~spacesppfs=(* collapse white and hint spaces (maybe) *)letis_space=function' '|'\n'|'\r'|'\t'->true|_->falseinleti_max=String.lengths-1inletflushstartstop=pp_strppf(String.subsstart(stop-start+1))inletrecskip_whitei=ifi>i_maxthenielseifis_spaces.[i]thenskip_white(i+1)elseiinletrecloopstarti=ifi>i_maxthenflushstarti_maxelseifnot(is_spaces.[i])thenloopstart(i+1)elseletnext_start=skip_whiteiin(flushstart(i-1);ifspacesthenpp_spppf()elsepp_charppf' ';ifnext_start>i_maxthen()elseloopnext_startnext_start)inloop00(* Converter (end-user) error messages *)letquotes=strf"'%s'"sletalts_str?quotedalts=letquote=matchquotedwith|None->strf"$(b,%s)"|Somequoted->ifquotedthenquoteelse(funs->s)inmatchaltswith|[]->invalid_argerr_empty_list|[a]->(quotea)|[a;b]->strf"either %s or %s"(quotea)(quoteb)|alts->letrev_alts=List.revaltsinstrf"one of %s or %s"(String.concat", "(List.rev_mapquote(List.tlrev_alts)))(quote(List.hdrev_alts))leterr_multi_def~kindnamedocvv'=strf"%s %s defined twice (doc strings are '%s' and '%s')"kindname(docv)(docv')leterr_ambiguous~kinds~ambs=strf"%s %s ambiguous and could be %s"kind(quotes)(alts_str~quoted:trueambs)leterr_unknown?(dom=[])?(hints=[])~kindv=lethints=matchhints,domwith|[],[]->"."|[],dom->strf", must be %s."(alts_str~quoted:truedom)|hints,_->strf", did you mean %s?"(alts_str~quoted:truehints)instrf"unknown %s %s%s"kind(quotev)hintsleterr_nokinds=strf"no %s %s"(quotes)kindleterr_not_dirs=strf"%s is not a directory"(quotes)leterr_is_dirs=strf"%s is a directory"(quotes)leterr_elementkindsexp=strf"invalid element in %s ('%s'): %s"kindsexpleterr_invalidkindsexp=strf"invalid %s %s, %s"kind(quotes)expleterr_invalid_val=err_invalid"value"leterr_sep_missseps=err_invalid_vals(strf"missing a '%c' separator"sep)(* Converters *)type'aparser=string->[`Okof'a|`Errorofstring]type'aprinter=Format.formatter->'a->unittype'aconv='aparser*'aprinterletsome?(none="")(parse,print)=letparses=matchparseswith`Okv->`Ok(Somev)|`Error_ase->einletprintppfv=matchvwith|None->Format.pp_print_stringppfnone|Somev->printppfvinparse,printletsome'?none(parse,print)=letparses=matchparseswith`Okv->`Ok(Somev)|`Error_ase->einletprintppf=function|None->(matchnonewithNone->()|Somev->printppfv)|Somev->printppfvinparse,printletbool=letparses=try`Ok(bool_of_strings)with|Invalid_argument_->`Error(err_invalid_vals(alts_str~quoted:true["true";"false"]))inparse,Format.pp_print_boolletchar=letparses=matchString.lengths=1with|true->`Oks.[0]|false->`Error(err_invalid_vals"expected a character")inparse,pp_charletparse_witht_of_strexps=try`Ok(t_of_strs)withFailure_->`Error(err_invalid_valsexp)letint=parse_withint_of_string"expected an integer",Format.pp_print_intletint32=parse_withInt32.of_string"expected a 32-bit integer",(funppf->ppppf"%ld")letint64=parse_withInt64.of_string"expected a 64-bit integer",(funppf->ppppf"%Ld")letnativeint=parse_withNativeint.of_string"expected a processor-native integer",(funppf->ppppf"%nd")letfloat=parse_withfloat_of_string"expected a floating point number",Format.pp_print_floatletstring=(funs->`Oks),pp_strletenumsl=ifsl=[]theninvalid_argerr_empty_listelselett=Cmdliner_trie.of_listslinletparses=matchCmdliner_trie.findtswith|`Ok_asr->r|`Ambiguous->letambs=List.sortcompare(Cmdliner_trie.ambiguitiests)in`Error(err_ambiguous~kind:"enum value"s~ambs)|`Not_found->letalts=List.rev(List.rev_map(fun(s,_)->s)sl)in`Error(err_invalid_vals("expected "^(alts_str~quoted:truealts)))inletprintppfv=letsl_inv=List.rev_map(fun(s,v)->(v,s))slintrypp_strppf(List.assocvsl_inv)withNot_found->invalid_arg(err_incomplete_enum(List.mapfstsl))inparse,printletfile=letparses=matchSys.file_existsswith|true->`Oks|false->`Error(err_no"file or directory"s)inparse,pp_strletdir=letparses=matchSys.file_existsswith|true->ifSys.is_directorysthen`Okselse`Error(err_not_dirs)|false->`Error(err_no"directory"s)inparse,pp_strletnon_dir_file=letparses=matchSys.file_existsswith|true->ifnot(Sys.is_directorys)then`Okselse`Error(err_is_dirs)|false->`Error(err_no"file"s)inparse,pp_strletsplit_and_parsesepparses=(* raises [Failure] *)letparsesub=matchparsesubwith|`Errore->failwithe|`Okv->vinletrecsplitaccumj=leti=tryString.rindex_fromsjsepwithNot_found->-1inif(i=-1)thenletp=String.subs0(j+1)inifp<>""thenparsep::accumelseaccumelseletp=String.subs(i+1)(j-i)inletaccum'=ifp<>""thenparsep::accumelseaccuminsplitaccum'(i-1)insplit[](String.lengths-1)letlist?(sep=',')(parse,pp_e)=letparses=try`Ok(split_and_parsesepparses)with|Failuree->`Error(err_element"list"se)inletrecprintppf=function|v::l->pp_eppfv;if(l<>[])then(pp_charppfsep;printppfl)|[]->()inparse,printletarray?(sep=',')(parse,pp_e)=letparses=try`Ok(Array.of_list(split_and_parsesepparses))with|Failuree->`Error(err_element"array"se)inletprintppfv=letmax=Array.lengthv-1infori=0tomaxdopp_eppfv.(i);ifi<>maxthenpp_charppfsepdoneinparse,printletsplit_leftseps=tryleti=String.indexssepinletlen=String.lengthsinSome((String.subs0i),(String.subs(i+1)(len-i-1)))withNot_found->Noneletpair?(sep=',')(pa0,pr0)(pa1,pr1)=letparsers=matchsplit_leftsepswith|None->`Error(err_sep_missseps)|Some(v0,v1)->matchpa0v0,pa1v1with|`Okv0,`Okv1->`Ok(v0,v1)|`Errore,_|_,`Errore->`Error(err_element"pair"se)inletprinterppf(v0,v1)=ppppf"%a%c%a"pr0v0seppr1v1inparser,printerlett2=pairlett3?(sep=',')(pa0,pr0)(pa1,pr1)(pa2,pr2)=letparses=matchsplit_leftsepswith|None->`Error(err_sep_missseps)|Some(v0,s)->matchsplit_leftsepswith|None->`Error(err_sep_missseps)|Some(v1,v2)->matchpa0v0,pa1v1,pa2v2with|`Okv0,`Okv1,`Okv2->`Ok(v0,v1,v2)|`Errore,_,_|_,`Errore,_|_,_,`Errore->`Error(err_element"triple"se)inletprintppf(v0,v1,v2)=ppppf"%a%c%a%c%a"pr0v0seppr1v1seppr2v2inparse,printlett4?(sep=',')(pa0,pr0)(pa1,pr1)(pa2,pr2)(pa3,pr3)=letparses=matchsplit_leftsepswith|None->`Error(err_sep_missseps)|Some(v0,s)->matchsplit_leftsepswith|None->`Error(err_sep_missseps)|Some(v1,s)->matchsplit_leftsepswith|None->`Error(err_sep_missseps)|Some(v2,v3)->matchpa0v0,pa1v1,pa2v2,pa3v3with|`Okv1,`Okv2,`Okv3,`Okv4->`Ok(v1,v2,v3,v4)|`Errore,_,_,_|_,`Errore,_,_|_,_,`Errore,_|_,_,_,`Errore->`Error(err_element"quadruple"se)inletprintppf(v0,v1,v2,v3)=ppppf"%a%c%a%c%a%c%a"pr0v0seppr1v1seppr2v2seppr3v3inparse,printletenv_bool_parses=matchString.lowercase_asciiswith|""|"false"|"no"|"n"|"0"->`Okfalse|"true"|"yes"|"y"|"1"->`Oktrue|s->letalts=alts_str~quoted:true["true";"yes";"false";"no"]in`Error(err_invalid_valsalts)