123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118(*
* Copyright (c) 2018 Thomas Gazagnaire <thomas@gazagnaire.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)letsrc=Logs.Src.create"ocaml-mdx"moduleLog=(valLogs.src_logsrc:Logs.LOG)openAstringopenMisctypet={command:stringlist;output:Output.tlist;exit_code:int;}letdump_lineppf=function|#Output.taso->Output.dumpppfo|`Exiti->Fmt.pfppf"`Exit %d"i|`Commandc->Fmt.pfppf"`Command %S"c|`Command_firstc->Fmt.pfppf"`Command_first %S"c|`Command_contc->Fmt.pfppf"`Command_cont %S"c|`Command_lastc->Fmt.pfppf"`Command_last %S"cletdumpppf(t:t)=Fmt.pfppf"{@[command: %a;@ output: %a;@ exit_code: %d@]}"Fmt.(Dump.listdump_string)t.commandFmt.(Dump.listOutput.dump)t.outputt.exit_codeletpp_command?(pad=0)ppf(t:t)=matcht.commandwith|[]->()|l->letsepppf()=Fmt.pfppf"\\\n%a> "pp_padpadinFmt.pfppf"%a$ %a\n"pp_padpadFmt.(list~sepstring)lletpp_exit_code?(pad=0)ppfn=ifn<>0thenFmt.pfppf"%a[%d]\n"pp_padpadnletpp?padppf(t:t)=pp_command?padppft;pp_lines(Output.pp?pad)ppft.output;pp_exit_code?padppft.exit_codeletpad_of_lines=function|[]->0|h::_->leti=ref0inwhile(!i<String.lengthh&&h.[!i]=' ')doincri;done;!iletof_linest=letpad=pad_of_linestinletunpadline=ifString.is_emptylinethenlineelseifString.lengthline<padthenFmt.failwith"invalid padding: %S"lineelseString.with_index_rangeline~first:padinletlines=List.mapunpadtinletlines=Lexer_cram.token(Lexing.from_string(String.concat~sep:"\n"lines))inLog.debug(funl->l"Cram.of_lines (pad=%d) %a"padFmt.(Dump.listdump_line)lines);letmkcommandoutputexit_code={command;output=List.revoutput;exit_code}inletreccommand_contacc=function|`Command_contc::t->command_cont(c::acc)t|`Command_lastc::t->List.rev(c::acc),t|_->Fmt.failwith"invalid multi-line command"inletrecauxcommandoutputacc=function|[]whencommand=[]->List.revacc|[]->List.rev(mkcommandoutput0::acc)|`Exiti::t->aux[][](mkcommandoutputi::acc)t|`Ellipsisaso::t->auxcommand(o::output)acct|`Commandcmd::t->ifcommand=[]thenaux[cmd][]acctelseaux[cmd][](mkcommandoutput0::acc)t|`Command_firstcmd::t->letcmd,t=command_cont[cmd]tinauxcmd[](mkcommandoutput0::acc)t|`Output_aso::t->auxcommand(o::output)acct|(`Command_lasts|`Command_conts)::t->auxcommandoutputacc(`Outputs::t)inmatchlineswith|`Command_firstcmd::t->letcmd,t=command_cont[cmd]tinpad,auxcmd[][]t|`Commandcmd::t->pad,aux[cmd][][]t|[]->0,[]|_->Fmt.failwith"invalid cram block: %a"Fmt.(Dump.listdump_line)linesletexit_codet=t.exit_code(* http://tldp.org/LDP/abs/html/here-docs.html *)letuse_heredoc(t:t)=String.cut(List.hdt.command)~sep:"<<"<>Noneletcommand_linet=ifnot(use_heredoct)thenString.concat~sep:" "t.commandelseString.concat~sep:"\n"t.command