···2323 run ~reserved:X.reserved_xmlnss @@ fun () -> k X.reserved_xmlnss
2424end
25252626-module In_backmatter = Algaeff.Reader.Make (struct
2727- type t = bool
2828-end)
2929-3026let local_path_components (config : Config.t) (uri : URI.t) =
3127 let host = Option.get @@ URI.host uri in
3228 let base_host = Option.get @@ URI.host config.url in
···4036let route (forest : State.t) uri : URI.t =
4137 match forest.={uri} with
4238 | None -> uri
4343- | Some tree -> (
3939+ | Some tree -> begin
4440 match Tree.to_evaluated tree with
4541 | Some evaluated when evaluated.route_locally ->
4642 let path = "" :: local_path_components forest.config uri in
4743 URI.make ~path ()
4848- | _ -> uri)
4949-5050-module Scope = struct
5151- open struct
5252- module E = Algaeff.Reader.Make (struct
5353- type t = URI.t option
5454- end)
4444+ | _ -> uri
5545 end
56465757- let read = E.read
4747+let mainmatter_cache = Hashtbl.create 1000
58485959- let run ~(forest : State.t) ~env kont =
6060- let@ () = E.run ~env in
6161- let loc_opt =
6262- let@ uri = Option.bind env in
6363- let@ path = Option.map @~ State.source_path_of_uri uri forest in
6464- let position =
6565- Range.{source = `File path; offset = 0; start_of_line = 0; line_num = 0}
6666- in
6767- Range.make (position, position)
6868- in
6969- let@ () = Reporter.with_loc loc_opt in
7070- kont ()
7171-end
7272-7373-module Loop_detection = Loop_detection_effect.Make ()
4949+type env = {
5050+ forest: State.t;
5151+ in_backmatter: bool;
5252+ uri: URI.t option;
5353+ loops: Loop_detection.t;
5454+}
74557575-let mainmatter_cache = Hashtbl.create 1000
5656+let range ~env =
5757+ let@ uri = Option.bind env.uri in
5858+ let@ path = Option.map @~ State.source_path_of_uri uri env.forest in
5959+ let position =
6060+ Range.{source = `File path; offset = 0; start_of_line = 0; line_num = 0}
6161+ in
6262+ Range.make (position, position)
76637764let render_xml_qname qname =
7865 let qname = Xmlns.normalise_qname qname in
···8067 | "" -> qname.uname
8168 | _ -> Format.sprintf "%s:%s" qname.prefix qname.uname
82698383-let render_xml_attr (forest : State.t) T.{key; value} =
7070+let render_xml_attr ~env T.{key; value} =
8471 let str_value =
8585- Plain_text_client.string_of_content ~forest ~router:(route forest) value
7272+ Plain_text_client.string_of_content ~forest:env.forest
7373+ ~router:(route env.forest) value
8674 in
8775 P.string_attr (render_xml_qname key) "%s" str_value
8876···10088 X.optional_ X.numbered dict.numbered;
10189 ]
10290103103-let rec render_section forest (section : T.content T.section) : P.node =
9191+let rec render_section ~env (section : T.content T.section) : P.node =
10492 let@ _ = Xmlns.run in
10593 X.tree
10694 (render_section_flags section.flags)
10795 [
108108- render_frontmatter forest section.frontmatter;
109109- (let@ () = Scope.run ~forest ~env:section.frontmatter.uri in
110110- X.mainmatter []
111111- @@
112112- if Loop_detection.have_seen_uri_opt section.frontmatter.uri then
113113- [X.info [] [P.txt "Transclusion loop detected, rendering stopped."]]
114114- else
115115- let@ () = Loop_detection.add_seen_uri_opt section.frontmatter.uri in
116116- render_mainmatter forest section);
9696+ render_frontmatter ~env section.frontmatter;
9797+ begin
9898+ let env = {env with uri = section.frontmatter.uri} in
9999+ X.mainmatter []
100100+ @@
101101+ if Loop_detection.have_seen_uri_opt section.frontmatter.uri env.loops
102102+ then
103103+ [X.info [] [P.txt "Transclusion loop detected, rendering stopped."]]
104104+ else
105105+ render_mainmatter
106106+ ~env:
107107+ {
108108+ env with
109109+ loops =
110110+ Loop_detection.add_seen_uri_opt section.frontmatter.uri
111111+ env.loops;
112112+ }
113113+ section
114114+ end;
117115 ]
118116119119-and render_mainmatter forest (section : T.content T.section) =
117117+and render_mainmatter ~env (section : T.content T.section) =
120118 match section.frontmatter.uri with
121121- | None -> render_content forest section.mainmatter
122122- | Some uri -> (
119119+ | None -> render_content ~env section.mainmatter
120120+ | Some uri -> begin
123121 match Hashtbl.find_opt mainmatter_cache uri with
124122 | None ->
125125- let nodes = render_content forest section.mainmatter in
123123+ let nodes = render_content ~env section.mainmatter in
126124 Hashtbl.add mainmatter_cache uri nodes;
127125 nodes
128128- | Some nodes -> nodes)
126126+ | Some nodes -> nodes
127127+ end
129128130130-and render_frontmatter (forest : State.t)
131131- (frontmatter : T.content T.frontmatter) : P.node =
129129+and render_frontmatter ~env (frontmatter : T.content T.frontmatter) : P.node =
132130 let result =
133131 X.frontmatter []
134132 [
135135- render_attributions forest frontmatter.uri frontmatter.attributions;
136136- render_dates forest frontmatter.dates;
137137- X.conditional forest.dev
138138- (X.optional (X.source_path [] "%s") frontmatter.source_path);
133133+ render_attributions ~env frontmatter.uri frontmatter.attributions;
134134+ render_dates ~env frontmatter.dates;
135135+ X.conditional env.forest.dev
136136+ @@ X.optional (X.source_path [] "%s") frontmatter.source_path;
139137 X.optional
140138 (fun uri -> X.uri [] "%s" @@ URI.to_string uri)
141139 frontmatter.uri;
142140 X.optional
143141 (fun uri ->
144142 X.display_uri [] "%s"
145145- @@ URI.display_path_string ~base:forest.config.url uri)
143143+ @@ URI.display_path_string ~base:env.forest.config.url uri)
146144 frontmatter.uri;
147145 X.optional (X.route [] "%s")
148148- @@ Option.map (Fun.compose URI.to_string (route forest)) frontmatter.uri;
146146+ @@ Option.map
147147+ (Fun.compose URI.to_string (route env.forest))
148148+ frontmatter.uri;
149149 begin match frontmatter.title with
150150 | None -> X.null []
151151 | Some _ ->
152152 let title =
153153- State.get_expanded_title ?scope:(Scope.read ()) frontmatter forest
153153+ State.get_expanded_title ?scope:env.uri frontmatter env.forest
154154 in
155155 X.title
156156 [
157157 X.text_ "%s"
158158- @@ Plain_text_client.string_of_content ~forest
159159- ~router:(route forest) title;
158158+ @@ Plain_text_client.string_of_content ~forest:env.forest
159159+ ~router:(route env.forest) title;
160160 ]
161161- @@ render_content forest title
161161+ @@ render_content ~env title
162162 end;
163163 begin match frontmatter.taxon with
164164 | None -> X.null []
165165- | Some taxon -> X.taxon [] @@ render_content forest taxon
165165+ | Some taxon -> X.taxon [] @@ render_content ~env taxon
166166 end;
167167- X.null @@ List.map (render_meta forest) frontmatter.metas;
167167+ X.null @@ List.map (render_meta ~env) frontmatter.metas;
168168 ]
169169 in
170170 result
171171172172-and render_meta forest (key, body) =
173173- X.meta [X.name "%s" key] @@ render_content forest body
172172+and render_meta ~env (key, body) =
173173+ X.meta [X.name "%s" key] @@ render_content ~env body
174174175175-and render_content (forest : State.t) (Content content : T.content) :
176176- P.node list =
175175+and render_content ~env (Content content : T.content) : P.node list =
177176 match content with
178177 | T.Text txt0 :: T.Text txt1 :: content ->
179179- render_content forest (Content (T.Text (txt0 ^ txt1) :: content))
178178+ render_content ~env (Content (T.Text (txt0 ^ txt1) :: content))
180179 | node :: content ->
181181- let xs = render_content_node forest node in
182182- let ys = render_content forest (Content content) in
180180+ let xs = render_content_node ~env node in
181181+ let ys = render_content ~env (Content content) in
183182 xs @ ys
184183 | [] -> []
185184186186-and render_content_node (forest : State.t) (node : 'a T.content_node) :
187187- P.node list =
185185+and render_content_node ~env (node : 'a T.content_node) : P.node list =
188186 match node with
189187 | Text str -> [P.txt "%s" str]
190188 | CDATA str -> [P.txt ~raw:true "<![CDATA[%s]]>" str]
191189 | Uri uri ->
192192- [P.txt "%s" (URI.display_path_string ~base:forest.config.url uri)]
193193- | Route_of_uri uri -> [P.txt "%s" (URI.to_string (route forest uri))]
190190+ [P.txt "%s" (URI.display_path_string ~base:env.forest.config.url uri)]
191191+ | Route_of_uri uri -> [P.txt "%s" (URI.to_string (route env.forest uri))]
194192 | Xml_elt elt ->
195193 let prefixes_to_add, (name, attrs, content) =
196194 let@ () = Xmlns.within_scope in
197195 ( render_xml_qname elt.name,
198198- List.map (render_xml_attr forest) elt.attrs,
199199- render_content forest elt.content )
196196+ List.map (render_xml_attr ~env) elt.attrs,
197197+ render_content ~env elt.content )
200198 in
201199 let attrs =
202200 let xmlns_attrs = List.map render_xmlns_prefix prefixes_to_add in
203201 attrs @ xmlns_attrs
204202 in
205203 [P.std_tag name attrs content]
206206- | Transclude transclusion -> render_transclusion forest transclusion
204204+ | Transclude transclusion -> render_transclusion ~env transclusion
207205 | Contextual_number uri ->
208206 let custom_number =
209209- let@ resource = Option.bind @@ forest.@{uri} in
207207+ let@ resource = Option.bind @@ env.forest.@{uri} in
210208 match resource with
211209 | T.Article article -> article.frontmatter.number
212210 | _ -> None
···218216 [
219217 X.uri_ "%s" @@ URI.to_string uri;
220218 X.display_uri_ "%s"
221221- @@ URI.display_path_string ~base:forest.config.url uri;
219219+ @@ URI.display_path_string ~base:env.forest.config.url uri;
222220 ];
223221 ]
224222 | Some num -> [P.txt "%s" num]
225223 end
226226- | Link link -> render_link forest link
224224+ | Link link -> render_link ~env link
227225 | Results_of_datalog_query q ->
228226 let article_to_section =
229227 T.article_to_section
···236234 metadata_shown = Some true;
237235 }
238236 in
239239- let results = Forest.run_datalog_query forest.graphs q in
240240- let@ article = List.map @~ Forest_util.get_sorted_articles forest results in
241241- render_section forest @@ article_to_section article
242242- | Section section -> [render_section forest section]
237237+ let results = Forest.run_datalog_query env.forest.graphs q in
238238+ let@ article =
239239+ List.map @~ Forest_util.get_sorted_articles env.forest results
240240+ in
241241+ render_section ~env @@ article_to_section article
242242+ | Section section -> [render_section ~env section]
243243 | KaTeX (mode, content) ->
244244 let display = match mode with Inline -> "inline" | Display -> "block" in
245245 let body = Format.asprintf "%a" TeX_like.pp_content content in
246246 [X.tex [X.display "%s" display] "<![CDATA[%s]]>" body]
247247- | Artefact resource -> [render_artefact forest resource]
247247+ | Artefact resource -> [render_artefact ~env resource]
248248 | Datalog_script _ -> []
249249250250-and render_artefact forest (resource : T.content T.artefact) =
250250+and render_artefact ~env (resource : T.content T.artefact) =
251251 X.resource
252252 [X.hash "%s" resource.hash]
253253 [
254254- X.resource_content [] @@ render_content forest resource.content;
254254+ X.resource_content [] @@ render_content ~env resource.content;
255255 render_resource_sources resource.sources;
256256 ]
257257···263263 [X.type_ "%s" source.type_; X.resource_part "%s" source.part]
264264 "<![CDATA[%s]]>" source.source
265265266266-and render_transclusion (forest : State.t) (transclusion : T.transclusion) :
267267- P.node list =
268268- match State.get_content_of_transclusion transclusion forest with
269269- | None -> Reporter.fatal (Resource_not_found transclusion.href)
270270- | Some content -> render_content forest content
266266+and render_transclusion ~env (transclusion : T.transclusion) : P.node list =
267267+ match State.get_content_of_transclusion transclusion env.forest with
268268+ | None ->
269269+ Reporter.fatal ?loc:(range ~env) (Resource_not_found transclusion.href)
270270+ | Some content -> render_content ~env content
271271272272-and render_link (forest : State.t) (link : T.content T.link) : P.node list =
273273- let article_opt = State.get_article link.href forest in
272272+and render_link ~env (link : T.content T.link) : P.node list =
273273+ let article_opt = State.get_article link.href env.forest in
274274 let attrs =
275275 match article_opt with
276276 | None ->
277277- begin if not @@ In_backmatter.read () then
278278- match State.suggestion_for_uri link.href forest with
277277+ begin if not env.in_backmatter then
278278+ match State.suggestion_for_uri link.href env.forest with
279279 | Ok -> ()
280280 | Not_found {suggestion} ->
281281- Reporter.emit @@ Broken_link {uri = link.href; suggestion}
281281+ Reporter.emit ?loc:(range ~env)
282282+ @@ Broken_link {uri = link.href; suggestion}
282283 end;
283284 [
284284- X.href "%s" @@ URI.to_string @@ route forest link.href;
285285+ X.href "%s" @@ URI.to_string @@ route env.forest link.href;
285286 X.type_ "external";
286287 ]
287288 | Some article ->
288289 [
289289- X.href "%s" @@ URI.to_string @@ route forest link.href;
290290+ X.href "%s" @@ URI.to_string @@ route env.forest link.href;
290291 X.title_ "%s"
291291- @@ Plain_text_client.string_of_content ~forest ~router:(route forest)
292292- @@ State.get_expanded_title ?scope:(Scope.read ()) article.frontmatter
293293- forest;
292292+ @@ Plain_text_client.string_of_content ~forest:env.forest
293293+ ~router:(route env.forest)
294294+ @@ State.get_expanded_title ?scope:env.uri article.frontmatter
295295+ env.forest;
294296 X.optional_ (X.uri_ "%s")
295297 @@ Option.map URI.to_string article.frontmatter.uri;
296298 X.optional_ (X.display_uri_ "%s")
297299 @@ Option.map
298298- (URI.display_path_string ~base:forest.config.url)
300300+ (URI.display_path_string ~base:env.forest.config.url)
299301 article.frontmatter.uri;
300302 X.type_ "local";
301303 ]
302304 in
303303- [X.link attrs @@ render_content forest link.content]
305305+ [X.link attrs @@ render_content ~env link.content]
304306305305-and render_attributions (forest : State.t) (scope : URI.t option)
307307+and render_attributions ~env (scope : URI.t option)
306308 (primary_attributions : _ T.attribution list) =
307309 X.authors []
308308- @@ List.map (render_attribution forest)
309309- @@ Forest_util.collect_attributions forest scope primary_attributions
310310+ @@ List.map (render_attribution ~env)
311311+ @@ Forest_util.collect_attributions env.forest scope primary_attributions
310312311311-and render_attribution forest (attrib : _ T.attribution) =
313313+and render_attribution ~env (attrib : _ T.attribution) =
312314 let tag =
313315 match attrib.role with Author -> X.author | Contributor -> X.contributor
314316 in
315315- tag [] @@ render_attribution_vertex forest attrib.vertex
317317+ tag [] @@ render_attribution_vertex ~env attrib.vertex
316318317317-and render_attribution_vertex (forest : State.t) vtx =
319319+and render_attribution_vertex ~env vtx =
318320 match vtx with
319321 | T.Uri_vertex href ->
320322 let content =
321323 T.Content
322324 [T.Transclude {href; target = Title {empty_when_untitled = false}}]
323325 in
324324- render_link forest T.{href; content}
325325- | T.Content_vertex content -> render_content forest content
326326+ render_link ~env T.{href; content}
327327+ | T.Content_vertex content -> render_content ~env content
326328327327-and render_dates forest dates = X.null @@ List.map (render_date forest) dates
329329+and render_dates ~env dates = X.null @@ List.map (render_date ~env) dates
328330329329-and render_date forest (date : Human_datetime.t) =
330330- let config = forest.config in
331331+and render_date ~env (date : Human_datetime.t) =
332332+ let config = env.forest.config in
331333 let href_attr =
332334 let str =
333335 Format.asprintf "%a" Human_datetime.pp (Human_datetime.drop_time date)
334336 in
335337 let uri = URI_scheme.named_uri ~base:config.url str in
336336- match State.get_article uri forest with
338338+ match State.get_article uri env.forest with
337339 | None -> X.null_
338338- | Some _ -> X.href "%s" @@ URI.to_string @@ route forest uri
340340+ | Some _ -> X.href "%s" @@ URI.to_string @@ route env.forest uri
339341 in
340342 X.date [href_attr]
341343 [
···359361 result
360362 in
361363 let config = forest.config in
362362- let@ () = Loop_detection.run in
363363- let@ () = Scope.run ~forest ~env:article.frontmatter.uri in
364364 let@ xmlnss = Xmlns.run in
365365- let@ () = In_backmatter.run ~env:false in
365365+ let env =
366366+ {
367367+ forest;
368368+ in_backmatter = false;
369369+ uri = article.frontmatter.uri;
370370+ loops = Loop_detection.empty;
371371+ }
372372+ in
366373 X.tree
367374 begin
368375 List.map render_xmlns_prefix xmlnss
···376383 ]
377384 end
378385 [
379379- render_frontmatter forest article.frontmatter;
386386+ render_frontmatter ~env article.frontmatter;
380387 X.mainmatter []
381388 @@ begin
382382- let@ () = Loop_detection.add_seen_uri_opt article.frontmatter.uri in
383383- render_mainmatter forest @@ T.article_to_section article
389389+ render_mainmatter
390390+ ~env:
391391+ {
392392+ env with
393393+ loops =
394394+ Loop_detection.add_seen_uri_opt article.frontmatter.uri
395395+ env.loops;
396396+ }
397397+ @@ T.article_to_section article
384398 end;
385385- (X.backmatter []
386386- @@
387387- let@ () = In_backmatter.run ~env:true in
388388- render_content forest article.backmatter);
399399+ X.backmatter []
400400+ @@ render_content ~env:{env with in_backmatter = true} article.backmatter;
389401 ]
390402391403let pp_xml ~(forest : State.t) ?stylesheet fmt (article : _ T.article) =
+16
lib/frontend/Loop_detection.ml
···11+(*
22+ * SPDX-FileCopyrightText: 2024 The Forester Project Contributors
33+ *
44+ * SPDX-License-Identifier: GPL-3.0-or-later
55+ *)
66+77+open Forester_core
88+99+type t = URI.Set.t
1010+let empty = URI.Set.empty
1111+let add_seen_uri = URI.Set.add
1212+let add_seen_uri_opt uri_opt =
1313+ match uri_opt with Some uri -> add_seen_uri uri | None -> Fun.id
1414+let have_seen_uri = URI.Set.mem
1515+let have_seen_uri_opt uri_opt =
1616+ match uri_opt with Some uri -> have_seen_uri uri | None -> fun _ -> false
+8
lib/frontend/Loop_detection.mli
···11+open Forester_core
22+33+type t
44+val empty : t
55+val add_seen_uri : URI.t -> t -> t
66+val add_seen_uri_opt : URI.t option -> t -> t
77+val have_seen_uri : URI.t -> t -> bool
88+val have_seen_uri_opt : URI.t option -> t -> bool
-25
lib/frontend/Loop_detection_effect.ml
···11-(*
22- * SPDX-FileCopyrightText: 2024 The Forester Project Contributors
33- *
44- * SPDX-License-Identifier: GPL-3.0-or-later
55- *)
66-77-open Forester_core
88-99-module Make () = struct
1010- open Algaeff.Reader.Make (struct
1111- type t = URI.Set.t
1212- end)
1313-1414- let add_seen_uri uri = scope @@ URI.Set.add uri
1515-1616- let add_seen_uri_opt uri_opt kont =
1717- match uri_opt with None -> kont () | Some uri -> add_seen_uri uri kont
1818-1919- let have_seen_uri uri = URI.Set.mem uri @@ read ()
2020-2121- let have_seen_uri_opt uri_opt =
2222- match uri_opt with None -> false | Some uri -> have_seen_uri uri
2323-2424- let run k = run ~env:URI.Set.empty k
2525-end