Skip to content

Remove tyxml from odoc_html_frontend #1072

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@
`count-occurrences` flag and command to count occurrences of every identifiers
(@panglesd, #976)

### Changed

- `Odoc_html_frontend` does not use tyxml, for smaller javascript sizes.
(#EmileTrotignon, #1072)

# Fixed

- Revert to outputing a file (without content) when rendering a hidden
compilation unit. This fixes cases where the dune rules would
fail. (@panglesd, #1069)


# 2.4.0

### Added
Expand Down Expand Up @@ -62,7 +68,7 @@
- Fix `--hidden` not always taken into account (@panglesd, #940)
- Syntax highlight labels in function arguments (@panglesd, #990)
- Ensure generated html ends with a newline (@3Rafal, #954)
- Warn against tags in pages (@Julow, #948)
- Warn against tags in pages (@Julow, #948)
- Remove unhelpful 'Unresolved_apply' errors (@gpetiot, #946)
- Allow links and references in headings (@EmileTrotignon, @panglesd, #942)
- Fix rendering of method types (@zoggy, #935)
Expand All @@ -89,7 +95,7 @@

### Fixed
- Shadowing issues (@jonludlam, #853)
- Layout fixes and improvements (@panglesd, #832, #839, #847)
- Layout fixes and improvements (@panglesd, #832, #839, #847)
- Handle comments on class constraints and inherit (@julow, #844)
- Disable the missing root warning (@jonludlam, #881)

Expand Down
7 changes: 6 additions & 1 deletion src/search/html.ml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,12 @@ let string_of_kind =

let value_rhs (t : Entry.value_entry) = " : " ^ Text.of_type t.type_

let of_strings = Odoc_html_frontend.of_strings
let of_strings ~kind ~prefix_name ~name ~rhs ~typedecl_params ~doc =
[
Tyxml.Html.Unsafe.data
(Odoc_html_frontend.of_strings ~kind ~prefix_name ~name ~rhs
~typedecl_params ~doc);
]
let rhs_of_kind (entry : Entry.kind) =
match entry with
| TypeDecl td -> typedecl_rhs td
Expand Down
14 changes: 7 additions & 7 deletions src/search/html.mli
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ val url :
(string, Odoc_document.Url.Error.t) Result.result

(** The below is intended for search engine that do not use the Json output but
Odoc as a library. Most search engine will use their own representation
instead of {!Entry.t}, and may not want to store the whole HTML in their
database. The following functions help give correct values to store in a
Odoc as a library. Most search engine will use their own representation
instead of {!Entry.t}, and may not want to store the whole HTML in their
database. The following functions help give correct values to store in a
search database. *)

val of_strings :
Expand All @@ -24,22 +24,22 @@ val of_strings :
html list

val names_of_id : Paths.Identifier.t -> string * string
(** [names_of_id id] is [("X.Y", "foo")] if [id] corresponds to [X.Y.foo].
The tuple is intended to be given respectively to the [prefix_name] and
(** [names_of_id id] is [("X.Y", "foo")] if [id] corresponds to [X.Y.foo].
The tuple is intended to be given respectively to the [prefix_name] and
[name] arguments of {!Odoc_html_frontend.of_strings}. *)

val of_doc : Comment.docs -> html
(** [of_doc d] returns the HTML associated of the documentation comment [d],
generated correctly for search (no links or anchors). *)

val html_string_of_doc : Comment.docs -> string
(** [html_string_of_doc d] is the same as {!of_doc} converted to a
(** [html_string_of_doc d] is the same as {!of_doc} converted to a
string. *)

(** Right-hand sides *)

val rhs_of_kind : Entry.kind -> string option
(** [rhs_of_kind k] is the right-hand-side string associated with the metadata
(** [rhs_of_kind k] is the right-hand-side string associated with the metadata
included in the kind [k]. If [k] is [Value _], it may be [": int"] *)

val typedecl_params_of_entry : Entry.t -> string option
Expand Down
103 changes: 89 additions & 14 deletions src/search/odoc_html_frontend.ml
Original file line number Diff line number Diff line change
@@ -1,5 +1,90 @@
module Html : sig
type t

val string_of_list : t list -> string

type attr

val a_class : string list -> attr
val code : a:attr list -> t list -> t
val span : a:attr list -> t list -> t
val div : a:attr list -> t list -> t
val txt : string -> t

module Unsafe : sig
val data : string -> t
end
end = struct
type t =
| Raw of string
| Txt of string
| Concat of t list

let add_escape_string buf s =
(* https://discuss.ocaml.org/t/html-encoding-of-string/4289/4 *)
let add = Buffer.add_string buf in
let len = String.length s in
let max_idx = len - 1 in
let flush start i =
if start < len then Buffer.add_substring buf s start (i - start)
in
let rec loop start i =
if i > max_idx
then flush start i
else begin
match String.get s i with
| '&' -> escape "&amp;" start i
| '<' -> escape "&lt;" start i
| '>' -> escape "&gt;" start i
| '\'' -> escape "&apos;" start i
| '"' -> escape "&quot;" start i
| '@' -> escape "&commat;" start i
| _ -> loop start (i + 1)
end
and escape amperstr start i =
flush start i ;
add amperstr ;
let next = i + 1 in
loop next next
in
loop 0 0

let to_string t =
let buf = Buffer.create 16 in
let rec go = function
| Raw s -> Buffer.add_string buf s
| Txt s -> add_escape_string buf s
| Concat xs -> List.iter go xs
in
go t ;
Buffer.contents buf

let string_of_list lst = to_string (Concat lst)

type attr = t

let a_class lst = Concat [ Raw "class=\""; Txt (String.concat " " lst); Raw "\"" ]

let attrs = function
| [] -> Concat []
| xs -> Concat (Raw " " :: xs)

let block name ~a body =
let name = Raw name in
Concat [ Raw "<"; name; attrs a; Raw ">"; Concat body; Raw "</"; name; Raw ">" ]

let code = block "code"
let span = block "span"
let div = block "div"
let txt s = Txt s

module Unsafe = struct
let data s = Raw s
end
end

let of_strings ~kind ~prefix_name ~name ~rhs ~typedecl_params ~doc =
let open Tyxml.Html in
let open Html in
let kind = code ~a:[ a_class [ "entry-kind" ] ] [ txt kind ]
and typedecl_params =
match typedecl_params with
Expand All @@ -19,9 +104,10 @@ let of_strings ~kind ~prefix_name ~name ~rhs ~typedecl_params ~doc =
]
and prefix_name =
match prefix_name with
| None -> []
| Some "" -> []
| Some prefix_name ->
[ span ~a:[ a_class [ "prefix-name" ] ] [ txt (prefix_name ^ ".") ] ]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is indeed a bug here. But wouldn't it be better to remove the addition of "." to the prefix name:

Suggested change
and prefix_name =
match prefix_name with
| None -> []
| Some "" -> []
| Some prefix_name ->
[ span ~a:[ a_class [ "prefix-name" ] ] [ txt (prefix_name ^ ".") ] ]
and prefix_name =
match prefix_name with
| None -> []
| Some prefix_name ->
[ span ~a:[ a_class [ "prefix-name" ] ] [ txt prefix_name ] ]

and modify names_of_id accordingly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is better i agree.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, no its not better. I believe there a few place where we String.split the prefix name. This would behave very poorly

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In sherlodoc or in odoc? Could you provide links to such places?

That would maybe mean that the prefix name is turned into a string too early, and we should keep it as a list?

Copy link
Collaborator Author

@EmileTrotignon EmileTrotignon Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its in sherlodoc.
We should probably keep it as a list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went and checked more closely. We do indeed use String.split on the name, but we do not use the name functions provided by odoc.search, we call fullname which is a list, so it does not change anything to switch that around. We could even say that the bug was sherlodoc's fault, we could have given None instead of Some "".

| None -> []
and name =
match name with
| Some name -> [ span ~a:[ a_class [ "entry-name" ] ] [ txt name ] ]
Expand All @@ -31,7 +117,7 @@ let of_strings ~kind ~prefix_name ~name ~rhs ~typedecl_params ~doc =
| None -> []
| Some rhs -> [ code ~a:[ a_class [ "entry-rhs" ] ] [ txt rhs ] ]
in
[
Html.string_of_list [
kind;
code
~a:[ a_class [ "entry-title" ] ]
Expand All @@ -40,26 +126,15 @@ let of_strings ~kind ~prefix_name ~name ~rhs ~typedecl_params ~doc =
]

let kind_doc = "doc"

let kind_typedecl = "type"

let kind_module = "mod"

let kind_exception = "exn"

let kind_class_type = "class"
let kind_class = "class"

let kind_method = "meth"

let kind_extension_constructor = "cons"

let kind_module_type = "sig"

let kind_constructor = "cons"

let kind_field = "field"

let kind_value = "val"

let kind_extension = "ext"
12 changes: 6 additions & 6 deletions src/search/odoc_html_frontend.mli
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
(** This library is intended for search engine that do not use the Json output
but Odoc as a library. Most search engine will use their own representation
instead of {!Entry.t}, and may not want to store the whole HTML in their
but Odoc as a library. Most search engine will use their own representation
instead of {!Entry.t}, and may not want to store the whole HTML in their
database.
This library contains functions that are useful for the frontend of such
search engines.
This library contains functions that are useful for the frontend of such
search engines.
These functions would have their place in Odoc_searc.html, but putting them
there means that you need to link to a lot of dependencies to use them, and
js-of-ocaml is unable to detect when these dependencies are unused. *)
Expand All @@ -15,8 +15,8 @@ val of_strings :
rhs:string option ->
typedecl_params:string option ->
doc:string ->
[> `Code | `Div ] Tyxml_html.elt list
(** [of_string] generates the html of an entry using strings associated to
string
(** [of_string] generates the html of an entry using strings associated to
the relevant parts of the entry. If the strings have the correct values,
it will return the same HTML as {!Odoc_search.Html.of_entry}. Correct values
are given by {!Odoc_search.Html}, and for kinds, bellow. *)
Expand Down
16 changes: 8 additions & 8 deletions test/search/html_search.t/run.t
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ The index file, one entry per line:
$ cat index.json | jq sort | jq '.[]' -c
{"id":[{"kind":"Root","name":"Main"},{"kind":"Type","name":"tdzdz"},{"kind":"Constructor","name":"A"}],"doc":"","kind":{"kind":"Constructor","args":{"kind":"Tuple","vals":["int","int"]},"res":"tdzdz"},"display":{"url":"page/Main/index.html#type-tdzdz.A","html":"<code class=\"entry-kind\">cons</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.tdzdz.</span><span class=\"entry-name\">A</span><code class=\"entry-rhs\"> : int * int -&gt; tdzdz</code></code><div class=\"entry-comment\"><div></div></div>"}}
{"id":[{"kind":"Root","name":"Main"},{"kind":"Type","name":"tdzdz"},{"kind":"Constructor","name":"B"}],"doc":"Bliiiiiiiiiii","kind":{"kind":"Constructor","args":{"kind":"Tuple","vals":["int list","int"]},"res":"tdzdz"},"display":{"url":"page/Main/index.html#type-tdzdz.B","html":"<code class=\"entry-kind\">cons</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.tdzdz.</span><span class=\"entry-name\">B</span><code class=\"entry-rhs\"> : int list * int -&gt; tdzdz</code></code><div class=\"entry-comment\"><div><p>Bliiiiiiiiiii</p></div></div>"}}
{"id":[{"kind":"Root","name":"J"}],"doc":"a paragraph two","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/J/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">.</span><span class=\"entry-name\">J</span></code><div class=\"entry-comment\"><div><p>a paragraph two</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"A paragraph","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">.</span><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><p>A paragraph</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"a list of things","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">.</span><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><p>a list <em>of</em> things</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"bliblib","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">.</span><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><p>bliblib</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"and code","kind":{"kind":"Doc","subkind":"CodeBlock"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">.</span><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><pre class=\"language-ocaml\"><code>and code</code></pre></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"some verbatim","kind":{"kind":"Doc","subkind":"Verbatim"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">.</span><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><pre>some verbatim</pre></div></div>"}}
{"id":[{"kind":"Root","name":"J"}],"doc":"a paragraph two","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/J/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">J</span></code><div class=\"entry-comment\"><div><p>a paragraph two</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"A paragraph","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><p>A paragraph</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"a list of things","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><p>a list <em>of</em> things</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"bliblib","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><p>bliblib</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"and code","kind":{"kind":"Doc","subkind":"CodeBlock"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><pre class=\"language-ocaml\"><code>and code</code></pre></div></div>"}}
{"id":[{"kind":"Page","name":"page"}],"doc":"some verbatim","kind":{"kind":"Doc","subkind":"Verbatim"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><pre>some verbatim</pre></div></div>"}}
{"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"I"}],"doc":"x + 1","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/Main/I/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">I</span></code><div class=\"entry-comment\"><div><p><code class=\"odoc-katex-math\">x + 1</code></p></div></div>"}}
{"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"I"}],"doc":"x + 1","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/Main/I/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">I</span></code><div class=\"entry-comment\"><div><p><code class=\"odoc-katex-math\">x + 1</code></p></div></div>"}}
{"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"I"}],"doc":"a paragraph two","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/Main/I/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">I</span></code><div class=\"entry-comment\"><div><p>a paragraph two</p></div></div>"}}
Expand All @@ -106,8 +106,8 @@ The index file, one entry per line:
{"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"X"}],"doc":"and this is a paragraph","kind":{"kind":"Doc","subkind":"Paragraph"},"display":{"url":"page/Main/X/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">X</span></code><div class=\"entry-comment\"><div><p>and this is a paragraph</p></div></div>"}}
{"id":[{"kind":"Root","name":"Main"},{"kind":"Label","name":"this-is-a-title"}],"doc":"this is a title","kind":{"kind":"Doc","subkind":"Heading"},"display":{"url":"page/Main/index.html#this-is-a-title","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">this-is-a-title</span></code><div class=\"entry-comment\"><div><p>this is a title</p></div></div>"}}
{"id":[{"kind":"Page","name":"page"},{"kind":"Label","name":"a-title"}],"doc":"A title","kind":{"kind":"Doc","subkind":"Heading"},"display":{"url":"page/index.html#a-title","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">page.</span><span class=\"entry-name\">a-title</span></code><div class=\"entry-comment\"><div><p>A title</p></div></div>"}}
{"id":[{"kind":"Root","name":"J"}],"doc":"a paragraph one","kind":{"kind":"Module"},"display":{"url":"page/J/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">.</span><span class=\"entry-name\">J</span></code><div class=\"entry-comment\"><div><p>a paragraph one</p></div></div>"}}
{"id":[{"kind":"Root","name":"Main"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">.</span><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div></div></div>"}}
{"id":[{"kind":"Root","name":"J"}],"doc":"a paragraph one","kind":{"kind":"Module"},"display":{"url":"page/J/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"entry-name\">J</span></code><div class=\"entry-comment\"><div><p>a paragraph one</p></div></div>"}}
{"id":[{"kind":"Root","name":"Main"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div></div></div>"}}
{"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"I"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/I/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">I</span></code><div class=\"entry-comment\"><div></div></div>"}}
{"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"M"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/M/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">M</span></code><div class=\"entry-comment\"><div></div></div>"}}
{"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"X"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/X/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">X</span></code><div class=\"entry-comment\"><div></div></div>"}}
Expand Down