123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527(*---------------------------------------------------------------------------
Copyright (c) 2011 The cmdliner programmers. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)(* Manpages *)typeblock=[`Sofstring|`Pofstring|`Preofstring|`Iofstring*string|`Noblank|`Blocksofblocklist]typetitle=string*int*string*string*stringtypet=title*blocklisttypexref=[`Main|`Cmdofstring|`Toolofstring|`Pageofstring*int](* Standard sections *)lets_name="NAME"lets_synopsis="SYNOPSIS"lets_description="DESCRIPTION"lets_commands="COMMANDS"lets_arguments="ARGUMENTS"lets_options="OPTIONS"lets_common_options="COMMON OPTIONS"lets_exit_status="EXIT STATUS"lets_exit_status_intro=`P"$(iname) exits with:"lets_environment="ENVIRONMENT"lets_environment_intro=`P"These environment variables affect the execution of $(iname):"lets_files="FILES"lets_examples="EXAMPLES"lets_bugs="BUGS"lets_authors="AUTHORS"lets_see_also="SEE ALSO"lets_none="cmdliner-none"(* Section order *)lets_created=""letorder=[|s_name;s_synopsis;s_description;s_created;s_commands;s_arguments;s_options;s_common_options;s_exit_status;s_environment;s_files;s_examples;s_bugs;s_authors;s_see_also;s_none;|]letorder_synopsis=1letorder_created=3letsection_of_orderi=order.(i)letsection_to_order~on_unknowns=letmax=Array.lengthorder-1inletrecloopi=matchi>maxwith|true->on_unknown|false->iforder.(i)=sthenielseloop(i+1)inloop0(* Section maps
Section maps, maps section names to their section order and reversed
content blocks (content is not reversed in `Block blocks). The sections
are listed in reversed order. Unknown sections get the order of the last
known section. *)typesmap=(string*(int*blocklist))listletsmap_of_blocksbs=(* N.B. this flattens `Blocks, not t.r. *)letrecloopss_orbssmap=function|[]->s,s_o,rbs,smap|`Snew_sec::bs->letnew_o=section_to_order~on_unknown:s_onew_secinloopnew_secnew_o[]((s,(s_o,rbs))::smap)bs|`Blocksblist::bs->lets,s_o,rbs,rmap=loopss_orbssmapblist(* not t.r. *)inloopss_orbsrmapbs|(`P_|`Pre_|`I_|`Noblankasc)::bs->loopss_o(c::rbs)smapbsinletfirst,(bs:blocklist)=matchbswith|`Ss::bs->s,bs|`Blocks(`Ss::blist)::bs->s,(`Blocksblist)::bs|_->"",bsinletfirst_o=section_to_order~on_unknown:order_synopsisfirstinlets,s_o,rc,smap=loopfirstfirst_o[][]bsin(s,(s_o,rc))::smapletsmap_to_blockssmap=(* N.B. this leaves `Blocks content untouched. *)letrecloopaccsmaps=function|b::rbs->loop(b::acc)smapsrbs|[]->letacc=ifs=""thenaccelse`Ss::accinmatchsmapwith|[]->acc|(_,(_,[]))::smap->loopaccsmap""[](* skip empty section *)|(s,(_,rbs))::smap->ifs=s_nonethenloopaccsmap""[](* skip *)elseloopaccsmapsrbsinloop[]smap""[]letsmap_has_sectionsmap~sec=List.exists(fun(s,_)->sec=s)smapletsmap_append_blocksmap~secb=leto=section_to_order~on_unknown:order_createdsecinlettry_insert=letrecloopmax_lt_oleft=function|(s',(o,rbs))::rightwhens'=sec->Ok(List.rev_append((sec,(o,b::rbs))::left)right)|(_,(o',_)ass)::right->letmax_lt_o=ifo'<othenmaxo'max_lt_oelsemax_lt_oinloopmax_lt_o(s::left)right|[]->ifmax_lt_o<>-1thenErrormax_lt_oelseOk(List.rev((sec,(o,[b]))::left))inloop(-1)[]smapinmatchtry_insertwith|Oksmap->smap|Errorinsert_before->letrecloopleft=function|(s',(o',_))::_asrightwheno'=insert_before->List.rev_append((sec,(o,[b]))::left)right|s::ss->loop(s::left)ss|[]->assertfalseinloop[]smap(* Formatting tools *)letstrf=Printf.sprintfletpf=Format.fprintfletpp_str=Format.pp_print_stringletpp_char=Format.pp_print_charletpp_indentppfc=fori=1tocdopp_charppf' 'doneletpp_lines=Cmdliner_base.pp_linesletpp_tokens=Cmdliner_base.pp_tokens(* Cmdliner markup handling *)leterrefmt=pfe("cmdliner error: "^^fmt^^"@.")leterr_unescaped~errscs=errerrs"unescaped %C in %S"csleterr_malformed~errss=errerrs"Malformed $(…) in %S"sleterr_unclosed~errss=errerrs"Unclosed $(…) in %S"sleterr_undef~errsids=errerrs"Undefined variable $(%s) in %S"idsleterr_illegal_esc~errscs=errerrs"Illegal escape char %C in %S"csleterr_markup~errsdirs=errerrs"Unknown cmdliner markup $(%c,…) in %S"dirsletis_markup_dir=function'i'|'b'->true|_->falseletis_markup_esc=function'$'|'\\'|'('|')'->true|_->falseletmarkup_need_esc=function'\\'|'$'->true|_->falseletmarkup_text_need_esc=function'\\'|'$'|')'->true|_->falseletescapes=(* escapes [s] from doc language. *)letmax_i=String.lengths-1inletrecescaped_lenil=ifi>max_ithenlelseifmarkup_text_need_escs.[i]thenescaped_len(i+1)(l+2)elseescaped_len(i+1)(l+1)inletescaped_len=escaped_len00inifescaped_len=String.lengthsthenselseletb=Bytes.createescaped_leninletrecloopik=ifi>max_ithenBytes.unsafe_to_stringbelseletc=String.unsafe_getsiinifnot(markup_text_need_escc)then(Bytes.unsafe_setbkc;loop(i+1)(k+1))else(Bytes.unsafe_setbk'\\';Bytes.unsafe_setb(k+1)c;loop(i+1)(k+2))inloop00letsubst_vars~errs~substbs=letmax_i=String.lengths-1inletflushstartstop=matchstart>max_iwith|true->()|false->Buffer.add_substringbsstart(stop-start+1)inletskip_escapekstarti=ifi>max_ithenerr_unescaped~errs'\\'selsekstart(i+1)inletrecskip_markupkstarti=ifi>max_ithen(err_unclosed~errss;kstarti)elsematchs.[i]with|'\\'->skip_escape(skip_markupk)start(i+1)|')'->kstart(i+1)|c->skip_markupkstart(i+1)inletrecadd_subststarti=ifi>max_ithen(err_unclosed~errss;loopstarti)elseifs.[i]<>')'thenadd_subststart(i+1)elseletid=String.subsstart(i-start)inletnext=i+1inbeginmatchsubstidwith|None->err_undef~errsids;Buffer.add_stringb"undefined";|Somev->Buffer.add_stringbvend;loopnextnextandloopstarti=ifi>max_ithenflushstartmax_ielseletnext=i+1inmatchs.[i]with|'\\'->skip_escapeloopstartnext|'$'->ifnext>max_ithenerr_unescaped~errs'$'selsebeginmatchs.[next]with|'('->letmin=next+2inifmin>max_ithen(err_unclosed~errss;loopstartnext)elsebeginmatchs.[min]with|','->skip_markuploopstart(min+1)|_->letstart_id=next+1influshstart(i-1);add_subststart_idstart_idend|_->err_unescaped~errs'$'s;loopstartnextend;|c->loopstartnextin(Buffer.clearb;loop00;Buffer.contentsb)letadd_markup_esc~errskbsstartnexttarget_need_escapetarget_escape=letmax_i=String.lengths-1inifnext>max_ithenerr_unescaped~errs'\\'selsematchs.[next]with|cwhennot(is_markup_escs.[next])->err_illegal_esc~errscs;k(next+1)(next+1)|c->(iftarget_need_escapecthentarget_escapebcelseBuffer.add_charbc);k(next+1)(next+1)letadd_markup_text~errskbsstarttarget_need_escapetarget_escape=letmax_i=String.lengths-1inletflushstartstop=matchstart>max_iwith|true->()|false->Buffer.add_substringbsstart(stop-start+1)inletrecloopstarti=ifi>max_ithen(err_unclosed~errss;flushstartmax_i)elseletnext=i+1inmatchs.[i]with|'\\'->(* unescape *)flushstart(i-1);add_markup_esc~errsloopbsstartnexttarget_need_escapetarget_escape|')'->flushstart(i-1);knextnext|cwhenmarkup_text_need_escc->err_unescaped~errscs;flushstart(i-1);loopnextnext|cwhentarget_need_escapec->flushstart(i-1);target_escapebc;loopnextnext|c->loopstartnextinloopstartstart(* Plain text output *)letmarkup_to_plain~errsbs=letmax_i=String.lengths-1inletflushstartstop=matchstart>max_iwith|true->()|false->Buffer.add_substringbsstart(stop-start+1)inletneed_escape_=falseinletescape__=assertfalseinletrecloopstarti=ifi>max_ithenflushstartmax_ielseletnext=i+1inmatchs.[i]with|'\\'->flushstart(i-1);add_markup_esc~errsloopbsstartnextneed_escapeescape|'$'->ifnext>max_ithenerr_unescaped~errs'$'selsebeginmatchs.[next]with|'('->letmin=next+2inifmin>max_ithen(err_unclosed~errss;loopstartnext)elsebeginmatchs.[min]with|','->letmarkup=s.[min-1]inifnot(is_markup_dirmarkup)then(err_markup~errsmarkups;loopstartnext)elseletstart_data=min+1in(flushstart(i-1);add_markup_text~errsloopbsstart_dataneed_escapeescape)|_->err_malformed~errss;loopstartnextend|_->err_unescaped~errs'$'s;loopstartnextend|cwhenmarkup_need_escc->err_unescaped~errscs;flushstart(i-1);loopnextnext|c->loopstartnextin(Buffer.clearb;loop00;Buffer.contentsb)letdoc_to_plain~errs~substbs=markup_to_plain~errsb(subst_vars~errs~substbs)letp_indent=7(* paragraph indentation. *)letl_indent=4(* label indentation. *)letpp_plain_blocks~errssubstppfts=letb=Buffer.create1024inletmarkupt=doc_to_plain~errsb~substtinletpp_tokensppft=pp_tokens~spaces:trueppftinletrecblank_line=function|`Noblank::ts->loopts|ts->Format.pp_print_cutppf();looptsandloop=function|[]->()|t::ts->matchtwith|`Noblank->loopts|`Blocksbs->loop(bs@ts)|`Ps->pfppf"%a@[%a@]@,"pp_indentp_indentpp_tokens(markups);blank_linets|`Ss->pfppf"@[%a@]@,"pp_tokens(markups);loopts|`Pres->pfppf"%a@[%a@]@,"pp_indentp_indentpp_lines(markups);blank_linets|`I(label,s)->letlabel=markuplabelands=markupsinpfppf"@[%a@[%a@]"pp_indentp_indentpp_tokenslabel;beginmatchswith|""->pfppf"@]@,"|s->letll=String.lengthlabelinifll<l_indentthen(pfppf"%a@[%a@]@]@,"pp_indent(l_indent-ll)pp_tokenss)else(pfppf"@\n%a@[%a@]@]@,"pp_indent(p_indent+l_indent)pp_tokenss)end;blank_linetsinlooptsletpp_plain_page~errssubstppf(_,text)=pfppf"@[<v>%a@]"(pp_plain_blocks~errssubst)text(* Groff output *)letmarkup_to_groff~errsbs=letmax_i=String.lengths-1inletflushstartstop=matchstart>max_iwith|true->()|false->Buffer.add_substringbsstart(stop-start+1)inletneed_escape=function'.'|'\''|'-'|'\\'->true|_->falseinletescapebc=Printf.bprintfb"\\N'%d'"(Char.codec)inletrecend_textstarti=Buffer.add_stringb"\\fR";loopstartiandloopstarti=ifi>max_ithenflushstartmax_ielseletnext=i+1inmatchs.[i]with|'\\'->flushstart(i-1);add_markup_esc~errsloopbsstartnextneed_escapeescape|'$'->ifnext>max_ithenerr_unescaped~errs'$'selsebeginmatchs.[next]with|'('->letmin=next+2inifmin>max_ithen(err_unclosed~errss;loopstartnext)elsebeginmatchs.[min]with|','->letstart_data=min+1influshstart(i-1);beginmatchs.[min-1]with|'i'->Buffer.add_stringb"\\fI"|'b'->Buffer.add_stringb"\\fB"|markup->err_markup~errsmarkupsend;add_markup_text~errsend_textbsstart_dataneed_escapeescape|_->err_malformed~errss;loopstartnextend|_->err_unescaped~errs'$'s;flushstart(i-1);loopnextnextend|cwhenmarkup_need_escc->err_unescaped~errscs;flushstart(i-1);loopnextnext|cwhenneed_escapec->flushstart(i-1);escapebc;loopnextnext|c->loopstartnextin(Buffer.clearb;loop00;Buffer.contentsb)letdoc_to_groff~errs~substbs=markup_to_groff~errsb(subst_vars~errs~substbs)letpp_groff_blocks~errssubstppftext=letbuf=Buffer.create1024inletmarkupt=doc_to_groff~errs~substbuftinletpp_tokensppft=pp_tokens~spaces:falseppftinletrecpp_block=function|`Blocksbs->List.iterpp_blockbs(* not T.R. *)|`Ps->pfppf"@\n.P@\n%a"pp_tokens(markups)|`Pres->pfppf"@\n.P@\n.nf@\n%a@\n.fi"pp_lines(markups)|`Ss->pfppf"@\n.SH %a"pp_tokens(markups)|`Noblank->pfppf"@\n.sp -1"|`I(l,s)->pfppf"@\n.TP 4@\n%a@\n%a"pp_tokens(markupl)pp_tokens(markups)inList.iterpp_blocktextletpp_groff_page~errssubstppf((n,s,a1,a2,a3),t)=pfppf".\\\" Pipe this output to groff -m man -K utf8 -T utf8 | less -R@\n\
.\\\"@\n\
.mso an.tmac@\n\
.TH \"%s\" %d \"%s\" \"%s\" \"%s\"@\n\
.\\\" Disable hyphenation and ragged-right@\n\
.nh@\n\
.ad l\
%a@?"nsa1a2a3(pp_groff_blocks~errssubst)t(* Printing to a pager *)letpp_to_temp_filepp_vv=tryletexec=Filename.basenameSys.argv.(0)inletfile,oc=Filename.open_temp_fileexec"out"inletppf=Format.formatter_of_out_channelocinpp_vppfv;Format.pp_print_flushppf();close_outoc;at_exit(fun()->trySys.removefilewithSys_errore->());SomefilewithSys_error_->Nonelettmp_file_for_pager()=tryletexec=Filename.basenameSys.argv.(0)inletfile=Filename.temp_fileexec"tty"inat_exit(fun()->trySys.removefilewithSys_errore->());SomefilewithSys_error_->Noneletfind_cmdcmds=letfind_win32(cmd,_args)=(* `where` does not support full path lookups *)ifString.equal(Filename.basenamecmd)cmdthen(Sys.command(strf"where %s 1> NUL 2> NUL"cmd)=0)elseSys.file_existscmdinletfind_posix(cmd,_args)=Sys.command(strf"command -v %s 1>/dev/null 2>/dev/null"cmd)=0inletfind=ifSys.win32thenfind_win32elsefind_posixintrySome(List.findfindcmds)withNot_found->Noneletpp_to_pagerprintppfv=letpager=letcmds=["less","";"more",""]inletcmds=try(Sys.getenv"PAGER","")::cmdswithNot_found->cmdsinletcmds=try(Sys.getenv"MANPAGER","")::cmdswithNot_found->cmdsinfind_cmdcmdsinmatchpagerwith|None->print`Plainppfv|Some(pager,opts)->letpager=matchSys.win32with|false->"LESS=FRX "^pager^opts|true->"set LESS=FRX && "^pager^optsinletgroffer=letcmds=["mandoc"," -m man -K utf-8 -T utf8";"groff"," -m man -K utf8 -T utf8";"nroff",""]infind_cmdcmdsinletcmd=matchgrofferwith|None->beginmatchpp_to_temp_file(print`Plain)vwith|None->None|Somef->Some(strf"%s < %s"pagerf)end|Some(groffer,opts)->letgroffer=groffer^optsinbeginmatchpp_to_temp_file(print`Groff)vwith|None->None|SomefwhenSys.win32->(* For some obscure reason the pipe below does not
work. We need to use a temporary file.
https://github.com/dbuenzli/cmdliner/issues/166 *)beginmatchtmp_file_for_pager()with|None->None|Sometmp->Some(strf"%s <%s >%s && %s <%s"grofferftmppagertmp)end|Somef->Some(strf"%s < %s | %s"grofferfpager)endinmatchcmdwith|None->print`Plainppfv|Somecmd->if(Sys.commandcmd)<>0thenprint`Plainppfv(* Output *)typeformat=[`Auto|`Pager|`Plain|`Groff]letrecprint?(errs=Format.err_formatter)?(subst=funx->None)fmtppfpage=matchfmtwith|`Pager->pp_to_pager(print~errs~subst)ppfpage|`Plain->pp_plain_page~errssubstppfpage|`Groff->pp_groff_page~errssubstppfpage|`Auto->letfmt=matchSys.getenv"TERM"with|exceptionNot_foundwhenSys.win32->`Pager|exceptionNot_found->`Plain|"dumb"->`Plain|_->`Pagerinprint~errs~substfmtppfpage