diff --git a/src/document/comment.ml b/src/document/comment.ml index 592cc26873..4f1238c7ec 100644 --- a/src/document/comment.ml +++ b/src/document/comment.ml @@ -84,6 +84,7 @@ module Reference = struct | `InstanceVariable (p, f) -> render_unresolved (p :> t) ^ "." ^ InstanceVariableName.to_string f | `Label (p, f) -> render_unresolved (p :> t) ^ "." ^ LabelName.to_string f + | `Asset (p, f) -> render_unresolved (p :> t) ^ "." ^ AssetName.to_string f (* This is the entry point. *) let to_ir : ?text:Inline.t -> Reference.t -> Inline.t = diff --git a/src/model/names.ml b/src/model/names.ml index d508d27abe..1083217b1d 100644 --- a/src/model/names.ml +++ b/src/model/names.ml @@ -143,3 +143,4 @@ module LabelName = SimpleName module PageName = SimpleName module DefName = SimpleName module LocalName = SimpleName +module AssetName = SimpleName diff --git a/src/model/names.mli b/src/model/names.mli index 9d6b3a68dd..1701e7adc8 100644 --- a/src/model/names.mli +++ b/src/model/names.mli @@ -99,3 +99,5 @@ module PageName : SimpleName module DefName : SimpleName module LocalName : SimpleName + +module AssetName : SimpleName diff --git a/src/model/paths.ml b/src/model/paths.ml index a7099e0321..81b51b9cc4 100644 --- a/src/model/paths.ml +++ b/src/model/paths.ml @@ -935,6 +935,10 @@ module Reference = struct module Page = struct type t = Paths_types.Resolved_reference.page end + + module Asset = struct + type t = Paths_types.Resolved_reference.asset + end end type t = Paths_types.Reference.any @@ -1016,4 +1020,8 @@ module Reference = struct module Page = struct type t = Paths_types.Reference.page end + + module Asset = struct + type t = Paths_types.Reference.asset + end end diff --git a/src/model/paths.mli b/src/model/paths.mli index 7e1a25b597..a8275d5fbd 100644 --- a/src/model/paths.mli +++ b/src/model/paths.mli @@ -507,6 +507,10 @@ module rec Reference : sig type t = Paths_types.Resolved_reference.page end + module Asset : sig + type t = Paths_types.Resolved_reference.asset + end + type t = Paths_types.Resolved_reference.any val identifier : t -> Identifier.t @@ -588,6 +592,10 @@ module rec Reference : sig type t = Paths_types.Reference.page end + module Asset : sig + type t = Paths_types.Reference.asset + end + type t = Paths_types.Reference.any type tag_any = Paths_types.Reference.tag_any diff --git a/src/model/paths_types.ml b/src/model/paths_types.ml index 70a838496c..4de49c051a 100644 --- a/src/model/paths_types.ml +++ b/src/model/paths_types.ml @@ -290,6 +290,8 @@ module Identifier = struct type reference_label = label type reference_page = page + + type reference_asset = asset_file end module rec Path : sig @@ -516,6 +518,7 @@ module rec Reference : sig | `TInstanceVariable | `TLabel | `TPage + | `TAsset | `TChildPage | `TChildModule | `TUnknown ] @@ -704,8 +707,16 @@ module rec Reference : sig | `ClassType of signature * ClassTypeName.t | `Method of class_signature * MethodName.t | `InstanceVariable of class_signature * InstanceVariableName.t - | `Label of label_parent * LabelName.t ] + | `Label of label_parent * LabelName.t + | `Asset of page * AssetName.t ] (** @canonical Odoc_model.Paths.Reference.t *) + + type asset = + [ `Resolved of Resolved_reference.asset + | `Root of string * [ `TAsset ] + | `Dot of label_parent * string + | `Asset of page * AssetName.t ] + (** @canonical Odoc_model.Paths.Reference.Asset.t *) end = Reference @@ -859,5 +870,8 @@ and Resolved_reference : sig | `InstanceVariable of class_signature * InstanceVariableName.t | `Label of label_parent * LabelName.t ] (** @canonical Odoc_model.Paths.Reference.Resolved.t *) + + type asset = [ `Identifier of Identifier.reference_asset ] + (** @canonical Odoc_model.Paths.Reference.Resolved.Asset.t *) end = Resolved_reference diff --git a/src/model/reference.ml b/src/model/reference.ml index 25c481507d..dbb0cbdfbd 100644 --- a/src/model/reference.ml +++ b/src/model/reference.ml @@ -75,6 +75,7 @@ let match_extra_odoc_reference_kind (_location as loc) s : Some `TLabel | Some "module-type" -> Some `TModuleType | Some "page" -> Some `TPage + | Some "asset" -> Some `TAsset | Some "value" -> d loc "value" "val"; Some `TValue @@ -323,6 +324,26 @@ let parse whole_reference_location s : |> Error.raise_exception) in + let page (kind, identifier, location) tokens : Page.t = + let kind = match_reference_kind location kind in + match tokens with + | [] -> ( + match kind with + | (`TUnknown | `TPage) as kind -> `Root (identifier, kind) + | _ -> expected [ "page" ] location |> Error.raise_exception) + | next_token :: tokens -> ( + match kind with + | `TUnknown -> `Dot (label_parent next_token tokens, identifier) + | _ -> + let suggestion = + Printf.sprintf "'page-%s' should be first." identifier + in + not_allowed ~what:"Page label" + ~in_what:"the last component of a reference path" ~suggestion + location + |> Error.raise_exception) + in + let start_from_last_component (kind, identifier, location) old_kind tokens = let new_kind = match_reference_kind location kind in let kind = @@ -385,6 +406,8 @@ let parse whole_reference_location s : | `TLabel -> `Label (label_parent next_token tokens, LabelName.make_std identifier) + | `TAsset -> + `Asset (page next_token tokens, AssetName.make_std identifier) | `TChildPage | `TChildModule -> let suggestion = Printf.sprintf "'child-%s' should be first." identifier diff --git a/src/model_desc/paths_desc.ml b/src/model_desc/paths_desc.ml index 0a42a9d4ed..aac96a6c58 100644 --- a/src/model_desc/paths_desc.ml +++ b/src/model_desc/paths_desc.ml @@ -34,6 +34,8 @@ module Names = struct let labelname = To_string LabelName.to_string + let assetname = To_string AssetName.to_string + let pagename = To_string PageName.to_string let parametername = To_string ModuleName.to_string @@ -194,6 +196,7 @@ module General_paths = struct | `TType -> C0 "`TType" | `TUnknown -> C0 "`TUnknown" | `TValue -> C0 "`TValue" + | `TAsset -> C0 "`TValue" | `TChildPage -> C0 "`TChildPage" | `TChildModule -> C0 "`TChildModule") @@ -316,7 +319,9 @@ module General_paths = struct ((x1 :> r), x2), Pair (reference, Names.instancevariablename) ) | `Label (x1, x2) -> - C ("`Label", ((x1 :> r), x2), Pair (reference, Names.labelname))) + C ("`Label", ((x1 :> r), x2), Pair (reference, Names.labelname)) + | `Asset (x1, x2) -> + C ("`Asset", ((x1 :> r), x2), Pair (reference, Names.assetname))) and resolved_reference : rr t = Variant diff --git a/src/xref2/component.ml b/src/xref2/component.ml index 0fb9a9723f..c5cc8c6b02 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -1450,6 +1450,11 @@ module Fmt = struct (parent :> t) (LabelName.to_string name) + and model_resolved_asset_reference ppf + (`Identifier id : Odoc_model.Paths.Reference.Resolved.Asset.t) = + Format.fprintf ppf "%a" model_identifier + (id :> Odoc_model.Paths.Identifier.t) + and model_reference ppf (r : Odoc_model.Paths.Reference.t) = let open Odoc_model.Paths.Reference in match r with @@ -1509,6 +1514,23 @@ module Fmt = struct Format.fprintf ppf "%a.%s" model_reference (parent :> t) (LabelName.to_string name) + | `Asset (parent, name) -> + Format.fprintf ppf "%a.%s" model_reference + (parent :> t) + (AssetName.to_string name) + + and model_asset_reference ppf (r : Odoc_model.Paths.Reference.Asset.t) = + let open Odoc_model.Paths.Reference in + match r with + | `Resolved r' -> + Format.fprintf ppf "r(%a)" model_resolved_asset_reference r' + | `Root (name, _) -> Format.fprintf ppf "unresolvedroot(%s)" name + | `Dot (parent, str) -> + Format.fprintf ppf "%a.%s" model_reference (parent :> t) str + | `Asset (parent, name) -> + Format.fprintf ppf "%a.%s" model_reference + (parent :> t) + (AssetName.to_string name) end module LocalIdents = struct diff --git a/src/xref2/component.mli b/src/xref2/component.mli index bc289ad73e..7a4032b58b 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -623,7 +623,13 @@ module Fmt : sig val model_resolved_reference : Format.formatter -> Odoc_model.Paths.Reference.Resolved.t -> unit + val model_resolved_asset_reference : + Format.formatter -> Odoc_model.Paths.Reference.Resolved.Asset.t -> unit + val model_reference : Format.formatter -> Odoc_model.Paths.Reference.t -> unit + + val model_asset_reference : + Format.formatter -> Odoc_model.Paths.Reference.Asset.t -> unit end module Of_Lang : sig diff --git a/src/xref2/env.ml b/src/xref2/env.ml index 703f97a71a..b028a55161 100644 --- a/src/xref2/env.ml +++ b/src/xref2/env.ml @@ -163,6 +163,7 @@ type t = { resolver : resolver option; recorder : recorder option; fragmentroot : (int * Component.Signature.t) option; + parent_page : Identifier.Page.t option; (** parent page *) } let is_linking env = env.linking @@ -199,6 +200,7 @@ let empty = recorder = None; ambiguous_labels = Identifier.Maps.Label.empty; fragmentroot = None; + parent_page = None; } let add_fragment_root sg env = @@ -802,12 +804,19 @@ let env_of_unit t ~linking resolver = let env = { empty with linking } in env |> add_module (t.id :> Identifier.Path.Module.t) dm m.doc in + let parent_page :> Identifier.Page.t option = + match t.id.iv with + | `Root (None, _) -> None + | `Root (Some parent, _) -> Some parent + in + let initial_env = { initial_env with parent_page } in set_resolver initial_env resolver |> open_units resolver let open_page page env = add_docs page.Lang.Page.content env let env_of_page page resolver = let initial_env = open_page page empty in + let initial_env = { initial_env with parent_page = Some page.name } in set_resolver initial_env resolver |> open_units resolver let env_for_reference resolver = @@ -870,3 +879,5 @@ let verify_lookups env lookups = | true, Some r -> r.lookups <- LookupTypeSet.union r.lookups lookups | _ -> ()); result + +let parent_page env = env.parent_page diff --git a/src/xref2/env.mli b/src/xref2/env.mli index 658c9f332f..0654a9a432 100644 --- a/src/xref2/env.mli +++ b/src/xref2/env.mli @@ -178,3 +178,5 @@ val len : int ref val n : int ref val verify_lookups : t -> LookupTypeSet.t -> bool + +val parent_page : t -> Identifier.Page.t option diff --git a/src/xref2/errors.ml b/src/xref2/errors.ml index 459d010386..b9a93a4b01 100644 --- a/src/xref2/errors.ml +++ b/src/xref2/errors.ml @@ -9,7 +9,8 @@ module Tools_error = struct [ `Module of Cpath.module_ ] (* Failed to resolve a module path when applying a fragment item *) ] - type reference_kind = [ `S | `T | `C | `CT | `Page | `Cons | `Field | `Label ] + type reference_kind = + [ `S | `T | `C | `CT | `Page | `Cons | `Field | `Label | `Asset | `Asset_or_label ] type expansion_of_module_error = [ `OpaqueModule (* The module does not have an expansion *) @@ -115,6 +116,8 @@ module Tools_error = struct | `Cons -> "constructor" | `Field -> "field" | `Label -> "label" + | `Asset -> "asset" + | `Asset_or_label -> "asset or label" in Format.pp_print_string fmt k @@ -291,7 +294,8 @@ type what = | `Module_type_u_expr of Component.ModuleType.U.expr | `Child_module of string | `Child_page of string - | `Reference of Reference.t ] + | `Reference of Reference.t + | `Asset_reference of Reference.Asset.t ] let report ~(what : what) ?tools_error action = let action = @@ -340,6 +344,7 @@ let report ~(what : what) ?tools_error action = | `Child_module rf -> r "child module" Astring.String.pp rf | `Child_page rf -> r "child page" Astring.String.pp rf | `Reference ref -> r "reference" model_reference ref + | `Asset_reference ref -> r "asset reference" model_asset_reference ref in match kind_of_error ~what tools_error with | Some (`Root name) -> Lookup_failures.report_root ~name diff --git a/src/xref2/ref_tools.ml b/src/xref2/ref_tools.ml index 6143fb41f1..e49aaa2522 100644 --- a/src/xref2/ref_tools.ml +++ b/src/xref2/ref_tools.ml @@ -499,9 +499,45 @@ module Page = struct | Some p -> Ok (`Identifier p.Odoc_model.Lang.Page.name, p) | None -> Error (`Lookup_by_name (`Page, name)) + let in_env_from_id env (id : Identifier.Page.t) = + match id with + | { iv = `Page (_, parent_name) | `LeafPage (_, parent_name); _ } -> + in_env env (PageName.to_string parent_name) + let of_element _env (`Page (id, page)) : t = (`Identifier id, page) end +module A = struct + (** Assets *) + + let rec in_page env (page : Odoc_model.Lang.Page.t) (asset_name : string) : + (Reference.Resolved.Asset.t, _) result = + let has_asset children asset = + List.exists + (function + | Odoc_model.Lang.Page.Asset_child a -> String.equal a asset + | _ -> false) + children + in + let parent_id + { + Odoc_model.Lang.Page.name = + { iv = `Page (parent, _) | `LeafPage (parent, _); _ }; + _; + } = + parent + in + if has_asset page.children asset_name then + Ok (`Identifier (Identifier.Mk.asset_file (page.name, asset_name))) + else + let parent = (parent_id page :> Identifier.Page.t option) in + match parent with + | Some parent -> + Page.in_env_from_id env parent >>= fun (_, p) -> + in_page env p asset_name + | None -> Error (`Lookup_by_name (`Asset, asset_name)) +end + module LP = struct (** Label parent *) @@ -707,7 +743,12 @@ let resolve_reference_dot_sg env ~parent_path ~parent_ref ~parent_sg name = Error (`Find_by_name (`Any, name)) let resolve_reference_dot_page env page name = - L.in_page env page name >>= resolved1 + match (L.in_page env page name, page) with + | Ok e, _ -> resolved1 e + | Error _, `P (_, p) -> ( + match A.in_page env p name with + | Ok e -> resolved1 e + | Error _ -> Error (`Find_by_name (`Asset_or_label, name))) let resolve_reference_dot_type env ~parent_ref t name = find Find.any_in_type t name >>= function @@ -729,26 +770,55 @@ let resolve_reference_dot env parent name = | (`C _ | `CT _) as p -> resolve_reference_dot_class env p name | `P _ as page -> resolve_reference_dot_page env page name +let resolve_page_reference env (r : Reference.Page.t) = + match r with + | `Resolved _ -> failwith "unimplemented" + | `Dot (_, name) | `Root (name, _) -> Page.in_env env name + +let resolve_asset_reference env (m : Reference.Asset.t) = + match m with + | `Resolved r -> Ok r + | `Root (name, _) -> ( + match Env.parent_page env with + | None -> Error (`Lookup_by_name (`Asset, name)) + | Some parent_page -> + Page.in_env_from_id env parent_page >>= fun (_, page) -> + A.in_page env page name) + | `Dot (parent, name) -> + let x = + resolve_label_parent_reference env parent >>= function + | (`S _ | `T _ | `C _ | `CT _) as c -> wrong_kind_error [ `Page ] c + | `P _ as page -> Ok page + in + x >>= fun (`P (_, p)) -> A.in_page env p name + | `Asset (parent_page, name) -> + resolve_page_reference env parent_page >>= fun (_, p) -> + A.in_page env p (AssetName.to_string name) + (** Warnings may be generated with [Error.implicit_warning] *) let resolve_reference = let resolved = resolved3 in - fun env r -> + fun env (r : t) -> match r with | `Root (name, `TUnknown) -> ( let identifier id = Ok (`Identifier (id :> Identifier.t)) in - env_lookup_by_name Env.s_any name env >>= function - | `Module (_, _) as e -> resolved (M.of_element env e) - | `ModuleType (_, _) as e -> resolved (MT.of_element env e) - | `Value (id, _) -> identifier id - | `Type (id, _) -> identifier id - | `Label (id, _) -> identifier id - | `Class (id, _) -> identifier id - | `ClassType (id, _) -> identifier id - | `Constructor (id, _) -> identifier id - | `Exception (id, _) -> identifier id - | `Extension (id, _) -> identifier id - | `Field (id, _) -> identifier id - | `Page (id, _) -> identifier id) + match env_lookup_by_name Env.s_any name env with + | Ok (`Module (_, _) as e) -> resolved (M.of_element env e) + | Ok (`ModuleType (_, _) as e) -> resolved (MT.of_element env e) + | Ok (`Value (id, _)) -> identifier id + | Ok (`Type (id, _)) -> identifier id + | Ok (`Label (id, _)) -> identifier id + | Ok (`Class (id, _)) -> identifier id + | Ok (`ClassType (id, _)) -> identifier id + | Ok (`Constructor (id, _)) -> identifier id + | Ok (`Exception (id, _)) -> identifier id + | Ok (`Extension (id, _)) -> identifier id + | Ok (`Field (id, _)) -> identifier id + | Ok (`Page (id, _)) -> identifier id + | Error _ as e -> ( + match resolve_asset_reference env (`Root (name, `TAsset)) with + | Ok res -> resolved1 res + | Error _ -> e)) | `Resolved r -> Ok r | `Root (name, (`TModule | `TChildModule)) -> M.in_env env name >>= resolved | `Module (parent, name) -> @@ -803,6 +873,8 @@ let resolve_reference = resolve_class_signature_reference env parent >>= fun p -> MM.in_class_signature env p name >>= resolved1 | `Root (name, `TInstanceVariable) -> MV.in_env env name >>= resolved1 + | (`Asset _ | `Root (_, `TAsset)) as t -> + resolve_asset_reference env t >>= resolved1 | `InstanceVariable (parent, name) -> resolve_class_signature_reference env parent >>= fun p -> MV.in_class_signature env p name >>= resolved1 @@ -810,5 +882,10 @@ let resolve_reference = let resolve_module_reference env m = Odoc_model.Error.catch_warnings (fun () -> resolve_module_reference env m) -let resolve_reference env m = +let resolve_reference : + Env.t -> t -> Resolved.t ref_result Odoc_model.Error.with_warnings = + fun env m -> Odoc_model.Error.catch_warnings (fun () -> resolve_reference env m) + +let resolve_asset_reference env m = + Odoc_model.Error.catch_warnings (fun () -> resolve_asset_reference env m) diff --git a/src/xref2/ref_tools.mli b/src/xref2/ref_tools.mli index c6608379e1..ba2a0eb275 100644 --- a/src/xref2/ref_tools.mli +++ b/src/xref2/ref_tools.mli @@ -13,3 +13,6 @@ val resolve_module_reference : val resolve_reference : Env.t -> t -> Resolved.t ref_result Odoc_model.Error.with_warnings + +val resolve_asset_reference : + Env.t -> Asset.t -> Resolved.Asset.t ref_result Odoc_model.Error.with_warnings diff --git a/test/xref2/references_to_assets.t/caml.gif_hidden b/test/xref2/references_to_assets.t/caml.gif_hidden new file mode 100644 index 0000000000..69ab3901db Binary files /dev/null and b/test/xref2/references_to_assets.t/caml.gif_hidden differ diff --git a/test/xref2/references_to_assets.t/index.mld b/test/xref2/references_to_assets.t/index.mld new file mode 100644 index 0000000000..d887683abd --- /dev/null +++ b/test/xref2/references_to_assets.t/index.mld @@ -0,0 +1,10 @@ +{0 Package page} + +A {{!asset-"caml.gif"}reference} to an asset. + +And a reference using a page parent, with various qualifications: +- {!page-other_page.asset-"caml_not.gif"} +- {!page-other_page."caml_not.gif"} +- {!other_page."caml_not.gif"} +- {!other_page.asset-"caml_not.gif"} +- {!"caml.gif"} \ No newline at end of file diff --git a/test/xref2/references_to_assets.t/other_page.mld b/test/xref2/references_to_assets.t/other_page.mld new file mode 100644 index 0000000000..0849b3015b --- /dev/null +++ b/test/xref2/references_to_assets.t/other_page.mld @@ -0,0 +1,3 @@ +{0 Another page with an asset} + +Hello darkness my old {!asset-"caml.gif"}. \ No newline at end of file diff --git a/test/xref2/references_to_assets.t/run.t b/test/xref2/references_to_assets.t/run.t new file mode 100644 index 0000000000..9981c2f52c --- /dev/null +++ b/test/xref2/references_to_assets.t/run.t @@ -0,0 +1,68 @@ +In this file, we test the resolving of asset references. + +More precisely we test resolving an an asset reference where the asset lives: +- in the current page (index.mld references ocaml.gif) +- in a parent page (test.mli references ocaml.gif) +- in a sibling page (test.mli references ocaml_not.gif, through page-other_page.ocaml_not.gif) + +Compile the module first + + $ ocamlc -c -bin-annot test.mli + +Then we need to odoc-compile the package mld file, listing its +children. If we omit the asset child, all assets reference resolving fail: + + $ odoc compile index.mld --child module-test --child page-other_page + $ odoc compile other_page.mld -I . --parent index + $ odoc compile test.cmti -I . --parent index + $ for i in *.odoc; do odoc link -I . $i; done + File "index.mld", line 10, characters 2-15: + Warning: Failed to resolve reference unresolvedroot(caml.gif) Couldn't find "caml.gif" + File "index.mld", line 9, characters 2-36: + Warning: Failed to resolve reference unresolvedroot(other_page).caml_not.gif Couldn't find asset "caml_not.gif" + File "index.mld", line 8, characters 2-30: + Warning: Failed to resolve reference unresolvedroot(other_page).caml_not.gif Couldn't find asset or label "caml_not.gif" + File "index.mld", line 7, characters 2-35: + Warning: Failed to resolve reference unresolvedroot(other_page).caml_not.gif Couldn't find asset or label "caml_not.gif" + File "index.mld", line 6, characters 2-41: + Warning: Failed to resolve reference unresolvedroot(other_page).caml_not.gif Couldn't find asset "caml_not.gif" + File "index.mld", line 3, characters 2-32: + Warning: Failed to resolve reference unresolvedroot(caml.gif) Couldn't find asset "caml.gif" + File "other_page.mld", line 3, characters 22-41: + Warning: Failed to resolve reference unresolvedroot(caml.gif) Couldn't find asset "caml.gif" + File "test.mli", line 4, characters 39-78: + Warning: Failed to resolve reference unresolvedroot(other_page).caml_not.gif Couldn't find asset "caml_not.gif" + File "test.mli", line 2, characters 4-34: + Warning: Failed to resolve reference unresolvedroot(caml.gif) Couldn't find asset "caml.gif" + +We should pass the asset as child of a page. + + $ odoc compile index.mld --child module-test --child asset-caml.gif --child page-other_page + $ odoc compile test.cmti -I . --parent index + $ odoc compile other_page.mld --child asset-caml_not.gif -I . --parent index + +Link and generate the HTML (forgetting the asset!): + + $ for i in *.odoc; do odoc link -I . $i; done + $ for i in *.odocl; do odoc html-generate --indent $i -o html; done + File "caml.gif": + Warning: asset is missing. + File "caml_not.gif": + Warning: asset is missing. + +Note that the html links are correct (there are dead links due to missing assets) + + $ grep caml.gif html/index/index.html +
A reference to an asset.
+caml.gif
caml_not.gif
caml_not.gif
caml_not.gif
caml_not.gif
A reference to an asset
+ $ grep caml_not.gif html/index/Test/index.html +caml_not.gif
+ $ grep caml.gif html/index/other_page/index.html
+ Hello darkness my old caml.gif
.
diff --git a/test/xref2/references_to_assets.t/test.mli b/test/xref2/references_to_assets.t/test.mli
new file mode 100644
index 0000000000..1f9f6e09f3
--- /dev/null
+++ b/test/xref2/references_to_assets.t/test.mli
@@ -0,0 +1,5 @@
+(**
+ A {{!asset-"caml.gif"}reference} to an asset
+
+ And a reference using a page parent: {!page-other_page.asset-"caml_not.gif"}
+ *)