123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394(**************************************************************************)(* *)(* This file is part of Frama-C. *)(* *)(* Copyright (C) 2007-2023 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file licenses/LGPLv2.1). *)(* *)(**************************************************************************)(* -------------------------------------------------------------------------- *)(* --- Server Documentation --- *)(* -------------------------------------------------------------------------- *)openPackagetypejson=Yojson.Basic.tmoduleMd=MarkdownmoduleSenv=Server_parametersmodulePages=Map.Make(String)typechapter=[`Protocol|`Kernel|`Pluginofstring](* Section contents can be generated statically or dynamically.
Typically, general kernel dictionary entries can be extended by plugins.
The general case is to have a function that builds the (final) content
on demand. *)typesection=(unit->Markdown.elements)typepage={path:string;rootdir:string;(* path to document root *)chapter:chapter;title:string;order:int;descr:Markdown.elements;readme:Filepath.Normalized.toption;mutablesections:sectionlist;}letorder=ref0letpages:pagePages.tref=refPages.emptyletplugins:stringlistref=ref[]letentries:(string*Markdown.href)listref=ref[]letpathpage=page.pathlethrefpagename:Markdown.href=Section(page.path,name)(* -------------------------------------------------------------------------- *)(* --- Page Collection --- *)(* -------------------------------------------------------------------------- *)letchapterpg=pg.chapterletpath_forchapterfilename=matchchapterwith|`Protocol->".",filename|`Kernel->"..",Printf.sprintf"kernel/%s"filename|`Pluginname->"../..",Printf.sprintf"plugins/%s/%s"namefilenameletpagechapter~title?(descr=[])?readme~filename()=letrootdir,path=path_forchapterfilenameintryletother=Pages.findpath!pagesinSenv.failure"Duplicate page '%s' path@."path;otherwithNot_found->letorder=incrorder;!orderinletpage={order;rootdir;path;chapter;title;descr;readme;sections=[];}inbeginmatchchapterwith|`Kernel|`Protocol->()|`Pluginp->plugins:=p::!pluginsend;pages:=Pages.addpathpage!pages;pageletstatic()=[]letpublish~page?name?(index=[])~title?(contents=[])?(generated=static)()=letid=matchnamewithSomeid->id|None->titleinlethref=Md.Section(page.path,id)inletsection()=Markdown.section?name~title(contents@generated())inList.iter(funentry->entries:=(entry,href)::!entries)index;page.sections<-section::page.sections;hrefletprotocol~title~readme:filename=letreadme=Filepath.Normalized.concatsFc_config.datadir["server";"doc";filename]inignore(page`Protocol~title~readme~filename())let()=protocol~title:"Architecture"~readme:"server.md"(* -------------------------------------------------------------------------- *)(* --- Package Publication --- *)(* -------------------------------------------------------------------------- *)lethref_of_identnamesid=letchapter=matchid.pluginwith|Kernel->`Kernel|Pluginp->`Pluginpinletfilename=ifid.package=[]then"index.md"elseString.concat"_"id.package^".md"inletpage=snd@@path_forchapterfilenameinlettext=tryIdMap.findidnameswithNot_found->id.nameinMd.link~text:(Md.codetext)~page~name:id.name()letpage_of_packagepkg=letchapter=matchpkg.p_pluginwith|Kernel->`Kernel|Pluginp->`Pluginpinletfilename=ifpkg.p_package=[]then"index.md"elseString.concat"_"pkg.p_package^".md"intrylet_,path=path_forchapterfilenameinPages.findpath!pageswithNot_found->pagechapter~title:pkg.p_title~descr:(Markdown.parpkg.p_descr)?readme:pkg.p_readme~filename()letkind_of_decl=function|D_signal->"SIGNAL"|D_value_|D_state_->"STATE"|D_array_->"ARRAY"|D_type_|D_record_|D_enum_->"DATA"|D_request{rq_kind=`GET}->"GET"|D_request{rq_kind=`SET}->"SET"|D_request{rq_kind=`EXEC}->"EXEC"|D_decoder_|D_order_|D_default_->assertfalseletpp_for?declnames=letself=matchdeclwith|Somed->letname=d.d_ident.nameinMd.link~text:(Md.codename)~name()|None->Md.code"self"inPackage.{self;ident=href_of_identnames}letmd_param~kindppprm=Md.emphkind@Md.code"::="@matchprmwith|P_valuejt->Package.md_jtypeppjt|P_named_->Md.code"{"@Md.emph(kind^"…")@Md.code"}"letmd_named~kindpp=function|P_value_->[]|P_namedprms->lettitle=String.capitalize_asciikindinMd.table(Package.md_fields~titleppprms)letmd_signalssignals=ifsignals=[]then[]elseMd.quote(Md.emph"signals")@Md.blockMd.(list(List.map(funx->text(codex))signals))letdescr_of_declnamesdecl=matchdecl.d_kindwith|D_decoder_|D_order_|D_default_->assertfalse|D_signal->[]|D_state_->[](* TBC *)|D_value_->[](* TBC *)|D_array_->[](* TBC *)|D_typedata->letpp=pp_for~declnamesinMd.quote(pp.self@Md.code"::="@Package.md_jtypeppdata)|D_recordfields->letpp=pp_for~declnamesinMd.quote(pp.self@Md.code"::= {"@Md.emph"fields…"@Md.code"}")@Md.table(Package.md_fieldsppfields)|D_enumtags->letpp=pp_for~declnamesinMd.quote(pp.self@Md.code"::="@Md.emph"tags…")@Md.table(Package.md_tagstags)|D_requestrq->letpp=pp_fornamesinMd.quote(md_param~kind:"input"pprq.rq_input)@Md.quote(md_param~kind:"output"pprq.rq_output)@md_named~kind:"input"pprq.rq_input@md_named~kind:"output"pprq.rq_output@md_signalsrq.rq_signalsletdeclarationpagenamesdecl=matchdecl.d_kindwith|D_decoder_|D_order_|D_default_->()|_->letname=decl.d_ident.nameinletfullname=name_of_identdecl.d_identinletkind=kind_of_decldecl.d_kindinlettitle=Printf.sprintf"%s (`%s`)"fullnamekindinletindex=[title]inletcontents=Markdown.pardecl.d_descrinletgenerated()=descr_of_declnamesdeclinlethref=publish~page~name~title~index~contents~generated()inignorehrefletpackagepkg=beginletpage=page_of_packagepkginletnames=Package.resolvepkginList.iter(declarationpagenames)pkg.p_content;end(* -------------------------------------------------------------------------- *)(* --- Tables of Content --- *)(* -------------------------------------------------------------------------- *)lettitle_of_chapter=function|`Protocol->"Protocols"|`Kernel->"Kernel"|`Pluginname->"Plugin "^String.capitalize_asciinameletpages_of_chapterc=letw=ref[]inPages.iter(fun_p->ifp.chapter=cthenw:=p::!w)!pages;List.sort(funpq->p.order-q.order)!wlettable_of_pagep=Md.text(Md.link~text:(Md.plainp.title)~page:p.path())lettable_of_chapterc=[Md.H2(Markdown.plain(title_of_chapterc),None);Md.Block(Md.list(List.maptable_of_page(pages_of_chapterc)))]lettable_of_contents()=table_of_chapter`Protocol@table_of_chapter`Kernel@List.concat(List.map(funp->table_of_chapter(`Pluginp))(List.sort_uniqString.compare!plugins))moduleCmap=Map.Make(structtypet=stringlistletcompare=Stdlib.compareend)letindex_entry(title,href)=Md.text@@Markdown.href~text:(Md.plaintitle)hrefletindex()=letcategoryname=matchList.rev(String.split_on_char'.'name)with|[]->[]|_::rpath->List.revrpathinletcmap=List.fold_left(funcsentry->letc=category(fstentry)inletes=tryCmap.findccswithNot_found->[]inCmap.addc(entry::es)cs)Cmap.empty!entriesinletby_name(a,_)(b,_)=String.compareabinletcategories=Cmap.fold(funcesces->(c,List.sortby_namees)::ces)cmap[]inbeginList.fold_left(funelements(c,es)->letentries=Md.Block(Md.list@@List.mapindex_entryes)::elementsinifc=[]thenentrieselseletcname=String.concat"."cinlettitle=Printf.sprintf"Index of `%s`"cnameinMd.H3(Md.plaintitle,None)::entries)[]categoriesendletlink~toc~title~href:json=letlink=["title",`Stringtitle;"href",`Stringhref]in`Assoc(ifnottocthenlinkelse("toc",`Booltrue)::link)letlink_pagepage:jsonlist=List.fold_right(funplinks->ifp.chapter=page.chapterthenlettoc=(p.path=page.path)inlethref=Filename.basenamep.pathinlink~toc~title:p.title~href::linkselselinks)(pages_of_chapterpage.chapter)[]letmaindata:json=`Assoc["document",`String"Frama-C Server";"title",`String"Presentation";"root",`String".";]letmetadatapage:json=`Assoc["document",`String"Frama-C Server";"chapter",`String(title_of_chapterpage.chapter);"title",`Stringpage.title;"root",`Stringpage.rootdir;"link",`List(link_pagepage);](* -------------------------------------------------------------------------- *)(* --- Dump Documentation --- *)(* -------------------------------------------------------------------------- *)letpp_one_page~root~page~titlebody=letfull_path=Filepath.Normalized.concatrootpageinignore(Extlib.mkdir~parents:true(Filepath.dirnamefull_path)0o755);tryletchan=open_out(full_path:>string)inletfmt=Format.formatter_of_out_channelchaninlettitle=Md.plaintitleinMarkdown.(pp_pandoc~pagefmt(pandoc~titlebody))withSys_errore->Senv.fatal"Could not open file %a for writing: %s"Filepath.Normalized.prettyfull_pathe(* Build section contents in reverse order *)letbuildds=List.fold_left(funds->s()::d)dsletdump~root?(meta=true)()=beginPages.iter(funpathpage->Senv.feedback"[doc] Page: '%s'"path;lettitle=page.titleinletintro=matchpage.readmewith|None->Markdown.section~titlepage.descr|Somefile->ifFilepath.existsfilethenMarkdown.rawfile(file:>string)@page.descrelse(Senv.warning"Can not find %a file"Filepath.Normalized.prettyfile;Markdown.section~titlepage.descr)inletbody=Markdown.subsectionspage.descr(build[]page.sections)inpp_one_page~root~page:path~title(intro@body);ifmetathenletpath=Filepath.Normalized.concatroot(path^".json")inYojson.Basic.to_file(path:>string)(metadatapage);)!pages;Senv.feedback"[doc] Page: 'readme.md'";ifmetathenletpath=Filepath.Normalized.concatroot"readme.md.json"inYojson.Basic.to_file(path:>string)maindata;letbody=[Md.H1(Md.plain"Presentation",None);Md.Block(Md.text(Md.format"Version %s"Fc_config.version))]@table_of_contents()@[Md.H2(Md.plain"Index",None)]@index()inlettitle="Presentation"inpp_one_page~root~page:"readme.md"~titlebodyendlet()=Db.Main.extendbeginfun()->ifnot(Senv.Doc.is_empty())thenletroot=Senv.Doc.get()inifFilepath.is_dirrootthenbeginSenv.feedback"[doc] Root: '%a'"Filepath.Normalized.prettyroot;Package.iterpackage;dump~root();endelseSenv.error"[doc] File '%a' is not a directory"Filepath.Normalized.prettyrootend(* -------------------------------------------------------------------------- *)