123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119(*---------------------------------------------------------------------------
Copyright (c) 2018 The b0 programmers. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)openB0_stdopenResult.Syntax(* Environment variables *)moduleEnv=structletpager="PAGER"letless="LESS"letterm="TERM"letinfos=letopenCmdlinerinCmd.Env.infopager~doc:"The pager used to display content. This is a command invocation \
given to execvp(3)."::Cmd.Env.infoterm~doc:"See option $(b,--no-pager)."::[]end(* Paging *)typet=Cmd.toptionletdoes_page=Option.is_someletfind?search?cmd~no_pager()=ifno_pagerthenOkNoneelsematchcmdwith|Somecmd->Ok(Os.Cmd.find?searchcmd)|None->matchOs.Env.var~empty_is_none:trueEnv.termwith|Some"dumb"|None->OkNone|Some_->letcmds=[Cmd.tool"less";Cmd.tool"more"]inlet*cmds=letempty_is_none=trueinmatchOs.Env.var'~empty_is_noneCmd.of_stringEnv.pagerwith|Error_ase->e|OkNone->Okcmds|Ok(Somecmd)->Ok(cmd::cmds)inOk(Os.Cmd.find_first?searchcmds)letpager_env()=matchOs.Env.var~empty_is_none:falseEnv.lesswith|Some_->OkNone|None->Result.bind(Os.Env.current_assignments())@@funenv->Ok(Some("LESS=FRX"::env))letpage_stdout=function|None->Ok()|Somepager->letuerr=Unix.error_messageinleterrfmt=Fmt.error("page stdout: "^^fmt)inletrecdup2fd0fd1=matchUnix.dup2fd0fd1with|()->Ok()|exceptionUnix.Unix_error(Unix.EINTR,_,_)->dup2fd0fd1|exceptionUnix.Unix_error(e,_,_)->err"dup2: %s"(uerre)inmatchpager_env()with|Errore->err"%s"e|Okenv->matchUnix.pipe()with|exceptionUnix.Unix_error(e,_,_)->err"pipe: %s"(uerre)|(pager_read,parent_write)->letstdin=Os.Cmd.in_fd~close:truepager_readinUnix.set_close_on_execparent_write;Os.Fd.apply~close:Unix.closeparent_write@@funparent_write->let*pid=Os.Cmd.spawn?env~stdinpagerinlet*()=dup2parent_writeUnix.stdoutinletparent_pid=Unix.getpid()inleton_parent_exit()=(* We need to be careful here, forked processes will also
get to execute this. *)ifparent_pid=Unix.getpid()thenbegin(* Before closing Unix.stdout it's better to flush
formatter and channels. Otherwise it's done later
by OCaml's standard shutdown procedure and it
raises Sys_error as the fd is no longer valid. *)(tryFmt.flushFmt.stdout()withSys_error_->());(tryflushstdoutwithSys_error_->());(tryUnix.closeUnix.stdoutwithUnix.Unix_error_->());Log.if_error~use:()@@Result.mapignore@@Os.Cmd.spawn_wait_statuspidendinat_exiton_parent_exit;Ok()letpage_filespagerfiles=matchpagerwith|Somepagerwhenfiles=[]->Ok()|Somepager->Os.Cmd.runCmd.(pager%%pathsfiles)|None->letrecloop=function|[]->Ok()|f::fs->matchOs.File.readfwith|Error_ase->e|Okcontents->Printf.printf"%s"contents;iffs<>[]thenPrintf.printf"\x1C"(* U+001C FS *);flushstdout;loopfsinloopfiles(* Cli interaction *)letno_pager?(docs=Cmdliner.Manpage.s_common_options)()=letopenCmdlinerinletdoc="Do not display the output in a pager. This automatically happens \
if the $(b,TERM) environment variable is $(b,dumb) or undefined."inArg.(value&flag&info["no-pager"]~docs~doc_envs:Env.infos~doc)