123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165(*
* 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.
*)openResultmodulePart=structtypet={name:string;sep_indent:string;(** Whitespaces before the [@@@part] separator *)body:string;}letv~name ~sep_indent~body={name;sep_indent;body}letname{name;_}=namelet sep_indent{sep_indent;_}=sep_indentletbody{body;_}=bodyend(** Remove empty strings at the beginning of a list *)letrecremove_empty_heads=function|""::tl->remove_empty_headstl|l->llettrim_empty_rev l=remove_empty_heads (List.rev(remove_empty_headsl))moduleParse_parts=structtypepart_decl=|Normalofstring|Compat_attrofstring*string(* ^^^^ This is for compat with the [[@@@part name]] delimiters *)|Part_beginofstring*string|Part_end|File_endletnext_part~name~sep_indent~is_begin_end_partlines_rev=let body=ifis_begin_end_partthenString.concat "\n"(List.revlines_rev)else"\n"^String.concat"\n"(trim_empty_rev lines_rev)inPart.v~name~sep_indent~bodyletanonymous_part =next_part~name:""~sep_indent:""(* let next_part_of_groups name sep_indent =
let sep_indent = Re.Group.get groups 1 in
let name = part_name_of_groups groups in
next_part ~name ~sep_indent *)letparse_lineline=matchlinewith|Error`End_of_file->File_end|Okline->(matchOcaml_delimiter.parselinewith|Okdelim->(matchdelimwith|Somedelim->(matchdelimwith|Part_begin(syntax,{indent;payload})->(matchsyntaxwith|Attr->Compat_attr(payload,indent)|Cmt->Part_begin(payload,indent))|Part_end->Part_end)|None->Normalline)|Error(`Msgmsg)->Fmt.epr"Warning: %s\n"msg;Normalline)letinput_line_erri=matchinput_line iwith|exception End_of_file->Error`End_of_file|line->Okline(* Once support for [@@@ parts] will be dropped `parse_part` should be much simpler *)letrecparse_partsinputmake_partcurrent_part part_lines nline=letopen Util.Result.Infixinletnline=nline+1inletline=input_line_errinputinmatch(parse_line line,current_part)with|Normal line,_->parse_parts inputmake_partcurrent_part(line::part_lines)nline|Part_end,Some _->parse_partsinputanonymous_partNone[]nline>>|List.cons(make_part~is_begin_end_part:truepart_lines)|Part_end,None->Error("There is no part to end.",nline)|Part_begin(next_part_name,sep_indent),None->letnext_part=next_part~name:next_part_name~sep_indentinletrcall =parse_partsinputnext_part(Somenext_part_name)[]nlineinifpart_lines=[]thenrcall(* Ignore empty anonymous parts: needed for legacy support *)elsercall>>|List.cons(make_part~is_begin_end_part:truepart_lines)|Compat_attr(name,sep_indent),None ->letnext_part=next_part~name~sep_indentinparse_parts inputnext_partNone[]nline>>|List.cons(make_part~is_begin_end_part:falsepart_lines)|Part_begin_,Somep|Compat_attr_,Somep->letmsg=Printf.sprintf"Part %s has no end."pinError(msg,nline)|File_end,Somep->letmsg=Printf.sprintf"File ended before part %s."pinError(msg,nline)|File_end,None->Ok[make_part~is_begin_end_part:truepart_lines]letof_filename=letinput =open_innameinmatchparse_partsinputanonymous_partNone[]0with|Okparts->parts|Error (msg,line)->Fmt.failwith"In file %s, line %d: %s"namelinemsgendtypefile=Part.tlistletreadfile=Parse_parts.of_file fileletfindfile~part=match part with|Somepart ->(matchList.find_opt(funp->String.equal(Part.namep)part)filewith|Somep->Some[Part.bodyp]|None->None)|None->List.fold_left(funaccp->Part.bodyp::acc)[]file|>List.rev|>funx->Somexletrecreplace_or_appendpart_namebody=function|p::tlwhenString.equal(Part.namep)part_name ->{pwithbody}::tl|p::tl->p::replace_or_appendpart_namebodytl|[]->[{name=part_name;sep_indent="";body}]letreplacefile~part~lines=letpart=match partwithNone->""|Somep->pinreplace_or_appendpart(String.concat"\n"lines)fileletcontentsfile =letlines=List.fold_left(funaccp->letbody=Part.bodypinmatchPart.namepwith|""->body::acc|n->letindent=Part.sep_indentpinbody::("\n"^indent^"[@@@part \""^n^"\"] ;;\n")::acc)[]fileinletlines=List.revlinesinletlines =String.concat"\n"linesinString.trimlines^"\n"