123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149(*
* 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)open AstringopenMisctypet={command :stringlist;output:Output.tlist;exit_code:int;vpad: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.pf ppf"{@[command: %a;@ output: %a;@ exit_code: %d@]}"Fmt.(Dump.listdump_string)t.commandFmt.(Dump.listOutput.dump)t.outputt.exit_codeletpp_vpadppft=letrecloop=function|0->()|n->Fmt.pfppf"\n";loop(Int.predn)inloopt.vpadletpp_command?(pad=0)ppf(t:t)=matcht.commandwith|[]->()|l->pp_vpadppft;letsepppf()=Fmt.pfppf"\\\n%a> "pp_padpadinFmt.pfppf"%a$ %a\n"pp_padpadFmt.(list~sepstring)lletpp_exit_code?(pad=0)ppfn=ifn<>0then Fmt.pfppf"%a[%d]\n"pp_padpadnletpp?padppf(t:t)=pp_command ?padppft;pp_lines(Output.pp?pad)ppft.output;pp_exit_code?padppft.exit_codelethpad_of_lines=function|[]->0|h::_->leti=ref0inwhile!i<String.lengthh&&h.[!i]=' 'doincridone;!iletof_lines~syntax~(loc:Location.t)t=letpos=loc.loc_startinlet hpad=matchsyntaxwithSyntax.Mli-> pos.pos_cnum+2|_->hpad_of_linestinletunpadline=matchsyntaxwith|Syntax.Mli->String.trimline|_->ifString.is_emptylinethenlineelseifString.lengthline<hpadthenFmt.failwith"invalid padding: %S"lineelseString.with_index_rangeline~first:hpadinletlines=List.mapunpadtinletlines=Lexer_cram.token(Lexing.from_string(String.concat~sep:"\n"lines))inletvpad=matchsyntaxwithSyntax.Mli-> 1|_->0inLog.debug(funl->l"Cram.of_lines (pad=%d) %a"hpadFmt.(Dump.listdump_line)lines);letmkcommandoutputexit_code={command;output=List.rev output;exit_code;vpad}inlet reccommand_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|`Command cmd::t->if command=[]thenaux[cmd][]acctelseaux[cmd][](mkcommand output0::acc)t|`Command_firstcmd::t->let cmd,t=command_cont[cmd]tinaux cmd[](mkcommandoutput 0::acc)t|(`Output_aso)::t->auxcommand(o::output)acct|(`Command_last s|`Command_conts)::t->auxcommandoutputacc(`Output s::t)inmatchlineswith|`Command_firstcmd::t->letcmd,t=command_cont[cmd]tin(hpad,auxcmd[][]t)|`Commandcmd::t->(hpad,aux [cmd][][]t)|[]->(0,[])|`Outputline::_->ifString.lengthline>0&&line.[0]='$'thenfailwith"Blocks must start with a command or similar, not with an output \
line. To indicate a line as a command, start it with a dollar \
followed by a space."elsefailwith"Blocks must start with a command or similar, not with an output \
line. Please, make sure that there's no spare empty line, \
particularly between the output and its input."|_->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_heredoc t)then String.concat~sep:" "t.commandelseString.concat~sep:"\n"t.command