123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271(*
* Copyright (c) 2014 Leo White <leo@lpw25.net>
*
* 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.
*)(* We are slightly more flexible here than OCaml usually is, and allow
'linking' of modules that have the same name. This is because we do
documentation at a package level - it's perfectly acceptable to have
libraries within a package that are never meant to be linked into the same
binary, however package-level documents such as module and type indexes
effectively have to link those libraries together. Hence we may find
ourselves in the unfortunate situation where there are multiple modules with the same
name in our include path. We therefore maintain a mapping of module/page
name to Root _list_. Where we've already made a judgement about which module
we're looking for we have a digest, and can pick the correct module. When we
don't (for example, when handling package-level mld files), we pick the
first right now. The ocamldoc syntax doesn't currently allow for specifying
more accurately than just the module name anyway.
Where we notice this ambiguity we warn the user to wrap their libraries,
which will generally fix this issue. *)openOr_errormoduleAccessible_paths:sigtypetvalcreate:directories:Fs.Directory.tlist->tvalfind:t->string->Fs.File.tlistend=structtypet={directories:Fs.Directory.tlist}letcreate~directories={directories}letfindtname=letuname=Astring.String.Ascii.capitalizename^".odoc"inletlname=Astring.String.Ascii.uncapitalizename^".odoc"inletrecloopacc=function|[]->acc|directory::dirs->(letlfile=Fs.File.create~directory~name:lnameinmatchUnix.stat(Fs.File.to_stringlfile)with|_->loop(lfile::acc)dirs|exceptionUnix.Unix_error_->(letufile=Fs.File.create~directory~name:unameinmatchUnix.stat(Fs.File.to_stringufile)with|_->loop(ufile::acc)dirs|exceptionUnix.Unix_error_->loopaccdirs))inloop[]t.directoriesendmoduleStringMap=Map.Make(String)letbuild_imports_mapm=letimports=m.Odoc_model.Lang.Compilation_unit.importsinList.fold_left(funmapimport->matchimportwith|Odoc_model.Lang.Compilation_unit.Import.Unresolved(name,_)->StringMap.addnameimportmap|Odoc_model.Lang.Compilation_unit.Import.Resolved(_,name)->StringMap.add(Odoc_model.Names.ModuleName.to_stringname)importmap)StringMap.emptyimportsletroot_nameroot=Odoc_model.Root.Odoc_file.nameroot.Odoc_model.Root.fileletunit_name(Odoc_file.Unit_content{root;_}|Page_content{root;_}|Source_tree_content{root;_})=root_nameroot(** TODO: Propagate warnings instead of printing. *)letload_units_from_filespaths=letsafe_readfileacc=matchOdoc_file.loadfilewith|Oku->u.content::acc|Error(`Msgmsg)->letwarning=Odoc_model.Error.filename_only"%s"msg(Fs.File.to_stringfile)inprerr_endline(Odoc_model.Error.to_stringwarning);accinList.fold_rightsafe_readpaths[]letunit_cache=Hashtbl.create42(** Load every units matching a given name. Cached. *)letload_units_from_name=letdo_loadaptarget_name=letpaths=Accessible_paths.findaptarget_nameinload_units_from_filespathsinfunaptarget_name->tryHashtbl.findunit_cachetarget_namewithNot_found->letunits=do_loadaptarget_nameinHashtbl.addunit_cachetarget_nameunits;unitsletrecfind_mapf=function|[]->None|hd::tl->(matchfhdwithSomex->Some(x,tl)|None->find_mapftl)letlookup_unit_with_digestaptarget_namedigest=letunit_that_match_digestu=matchuwith|Odoc_file.Unit_contentmwhenDigest.comparem.Odoc_model.Lang.Compilation_unit.digestdigest=0->Somem|_->Noneinletunits=load_units_from_nameaptarget_nameinmatchfind_mapunit_that_match_digestunitswith|Some(m,_)->Odoc_xref2.Env.Foundm|None->Not_found(** Lookup a compilation unit matching a name. If there is more than one
result, report on stderr and return the first one.
TODO: Correctly propagate warnings instead of printing. *)letlookup_unit_by_nameaptarget_name=letfirst_unitu=matchuwith|Odoc_file.Unit_contentm->Somem|Page_content_|Source_tree_content_->Noneinletrecfind_ambiguoustl=matchfind_mapfirst_unittlwith|Some(m,tl)->m::find_ambiguoustl|None->[]inletunits=load_units_from_nameaptarget_nameinmatchfind_mapfirst_unitunitswith|Some(m,tl)->(matchfind_ambiguoustlwith|[]->()|ambiguous->letambiguous=m::ambiguousinletambiguous=List.map(funm->root_namem.Odoc_model.Lang.Compilation_unit.root)ambiguousinletwarning=Odoc_model.Error.filename_only"Ambiguous lookup. Possible files: %a"Format.(pp_print_listpp_print_string)ambiguoustarget_nameinprerr_endline(Odoc_model.Error.to_stringwarning));Somem|None->None(** Lookup an unit. First looks into [imports_map] then searches into the
paths. *)letlookup_unit~important_digests~imports_mapaptarget_name=letof_optionf=matchfwithSomem->Odoc_xref2.Env.Foundm|None->Not_foundinmatchStringMap.findtarget_nameimports_mapwith|Odoc_model.Lang.Compilation_unit.Import.Unresolved(_,Somedigest)->lookup_unit_with_digestaptarget_namedigest|Unresolved(_,None)->ifimportant_digeststhenOdoc_xref2.Env.Forward_referenceelseof_option(lookup_unit_by_nameaptarget_name)|Resolved(root,_)->lookup_unit_with_digestaptarget_nameroot.digest|exceptionNot_found->ifimportant_digeststhenOdoc_xref2.Env.Not_foundelseof_option(lookup_unit_by_nameaptarget_name)(** Lookup a page.
TODO: Warning on ambiguous lookup. *)letlookup_pageaptarget_name=lettarget_name="page-"^target_nameinletis_pageu=matchuwith|Odoc_file.Page_contentp->Somep|Unit_content_|Source_tree_content_->Noneinletunits=load_units_from_nameaptarget_nameinmatchfind_mapis_pageunitswithSome(p,_)->Somep|None->None(** Add the current unit to the cache. No need to load other units with the same
name. *)letadd_unit_to_cacheu=lettarget_name=(matchuwith|Odoc_file.Page_content_->"page-"|Unit_content_->""|Source_tree_content_->"page-")^unit_nameuinHashtbl.addunit_cachetarget_name[u]typet={important_digests:bool;ap:Accessible_paths.t;open_modules:stringlist;}letcreate~important_digests~directories~open_modules=letap=Accessible_paths.create~directoriesin{important_digests;ap;open_modules}(** Helpers for creating xref2 env. *)openOdoc_xref2letbuild_compile_env_for_unit{important_digests;ap;open_modules=open_units}m=add_unit_to_cache(Odoc_file.Unit_contentm);letimports_map=build_imports_mapminletlookup_unit=lookup_unit~important_digests~imports_mapapandlookup_page=lookup_pageapinletresolver={Env.open_units;lookup_unit;lookup_page}inEnv.env_of_unitm~linking:falseresolver(** [important_digests] and [imports_map] only apply to modules. *)letbuild?(imports_map=StringMap.empty){important_digests;ap;open_modules=open_units}=letlookup_unit=lookup_unit~important_digests~imports_mapapandlookup_page=lookup_pageapin{Env.open_units;lookup_unit;lookup_page}letbuild_link_env_for_unittm=add_unit_to_cache(Odoc_file.Unit_contentm);letimports_map=build_imports_mapminletresolver=build~imports_maptinEnv.env_of_unitm~linking:trueresolverletbuild_env_for_pagetp=add_unit_to_cache(Odoc_file.Page_contentp);letresolver=build{twithimportant_digests=false}inEnv.env_of_pagepresolverletbuild_env_for_referencet=letresolver=build{twithimportant_digests=false}inEnv.env_for_referenceresolverletlookup_pagettarget_name=lookup_paget.aptarget_nameletresolve_importttarget_name=letrecloop=function|[]->None|path::tl->(matchOdoc_file.load_rootpathwith|Error_->looptl|Okroot->(matchroot.Odoc_model.Root.filewith|Compilation_unit_->Someroot|Page_->looptl))inloop(Accessible_paths.findt.aptarget_name)