···3939 let foreign_dirs = paths_of_dirs ~env config.foreign in
4040 Forester.plant_raw_forest_from_dirs ~env ~host: config.host ~dev: false ~tree_dirs ~asset_dirs ~foreign_dirs;
4141 let host = config.host in
4242- Forester.export ~env ~host ~asset_dirs
4242+ Forester.export ~env ~host
43434444let new_tree ~env config_filename dest_dir prefix template random =
4545 let@ () = Reporter.silence in
-22
lib/compiler/Asset_content_addresser.ml
···11-open Forester_core
22-33-let router : (string, string * iri) Hashtbl.t = Hashtbl.create 100
44-55-let install ~source_path ~content =
66- match Hashtbl.find_opt router source_path with
77- | Some (filename, iri) -> (filename, iri)
88- | None ->
99- let hash = Result.get_ok @@ Multihash_digestif.of_cstruct `Sha3_256 (Cstruct.of_string content) in
1010- let cid = Cid.v ~version: `Cidv1 ~codec: `Raw ~base: `Base32 ~hash in
1111- let cid_str = Cid.to_string cid in
1212- let ext = Filename.extension source_path in
1313- let filename = cid_str ^ ext in
1414- let iri = Iri.iri ~path: (Relative ["content"; filename]) () in
1515- Hashtbl.add router source_path (filename, iri);
1616- filename, iri
1717-1818-let iri_of_asset ~source_path =
1919- match Hashtbl.find_opt router source_path with
2020- | Some (_, iri) -> iri
2121- | None ->
2222- Reporter.fatalf Resource_not_found "Asset located at `%s' does not have a content address" source_path
+22
lib/compiler/Asset_router.ml
···11+open Forester_core
22+33+let router : (string, iri) Hashtbl.t = Hashtbl.create 100
44+55+let install ~host ~source_path ~content =
66+ match Hashtbl.find_opt router source_path with
77+ | Some iri -> iri
88+ | None ->
99+ let hash = Result.get_ok @@ Multihash_digestif.of_cstruct `Sha3_256 (Cstruct.of_string content) in
1010+ let cid = Cid.v ~version: `Cidv1 ~codec: `Raw ~base: `Base32 ~hash in
1111+ let cid_str = Cid.to_string cid in
1212+ let ext = Filename.extension source_path in
1313+ let filename = cid_str ^ ext in
1414+ let iri = Iri_scheme.hash_iri ~host filename in
1515+ Hashtbl.add router source_path iri;
1616+ iri
1717+1818+let iri_of_asset ~source_path =
1919+ match Hashtbl.find_opt router source_path with
2020+ | Some iri -> iri
2121+ | None ->
2222+ Reporter.fatalf Resource_not_found "Asset located at `%s' does not have a content address" source_path
+4-4
lib/compiler/Eval.ml
···262262 in
263263 emit_content_node ~loc @@ KaTeX (mode, content)
264264 | Xml_tag (name, attrs, body) ->
265265- let rec process : _ list -> T.xml_attr list = function
265265+ let rec process : _ list -> _ T.xml_attr list = function
266266 | [] -> []
267267 | (key, v) :: attrs ->
268268- T.{ key; value = V.extract_text { node with value = eval_tape v } } :: process attrs
268268+ T.{ key; value = V.extract_content { node with value = eval_tape v } } :: process attrs
269269 in
270270 let name = T.{ prefix = name.prefix; uname = name.uname; xmlns = name.xmlns } in
271271 let content = { node with value = eval_tape body } |> V.extract_content in
···404404 emit_content_node ~loc @@ T.Transclude transclusion
405405 | Route_asset ->
406406 let source_path = pop_text_arg ~loc in
407407- let iri = Asset_content_addresser.iri_of_asset ~source_path in
408408- emit_content_node ~loc @@ T.Text (Iri.to_string ~pctencode: false iri)
407407+ let iri = Asset_router.iri_of_asset ~source_path in
408408+ emit_content_node ~loc @@ T.Route_of_iri iri
409409 | Object { self; methods } ->
410410 let table =
411411 let env = Lex_env.read () in
···44module T = Xml_tree
55module Q = Query
6677-module type S = sig
88- type resource =
99- | Article of T.content T.article
1010- | Asset of { iri: iri; contents: string; filename: string }
77+type resource = T.content T.resource
11899+module type S = sig
1210 val plant_resource : resource -> unit
13111412 val get_resource : Iri.t -> resource option
···3836 let run_datalog_query =
3937 Datalog_eval.run_query Graphs.dl_db
40384141- type resource =
4242- | Article of article
4343- | Asset of { iri: iri; contents: string; filename: string }
4444-4539 let resources : (Iri.t, resource) Hashtbl.t =
4640 Hashtbl.create 1000
4741···56505751 let rec analyse_content_node (scope : Iri.t) (node : 'a T.content_node) : unit =
5852 match node with
5959- | Text _ | CDATA _ | Iri _ | Results_of_query _ | Results_of_datalog_query _ | TeX_cs _ | Img _ | Contextual_number _ -> ()
5353+ | Text _ | CDATA _ | Iri _ | Route_of_iri _ | Results_of_query _ | Results_of_datalog_query _ | TeX_cs _ | Img _ | Contextual_number _ -> ()
6054 | Transclude transclusion ->
6155 analyse_transclusion scope transclusion
6256 | Xml_elt elt ->
···7064 analyse_content scope content
7165 | KaTeX (_, content) ->
7266 analyse_content scope content
7373- | Artefact artefact ->
6767+ | Artefact artefact ->
7468 analyse_artefact scope artefact
7569 | Datalog_script script ->
7670 execute_datalog_script script
···145139 analyse_content scope article.backmatter
146140147141 let analyse_resource = function
148148- | Article article -> analyse_article article
149149- | Asset _ -> ()
142142+ | T.Article article -> analyse_article article
143143+ | T.Asset _ -> ()
150144151145 let iri_for_resource = function
152152- | Article article -> article.frontmatter.iri
153153- | Asset asset -> Some asset.iri
146146+ | T.Article article -> article.frontmatter.iri
147147+ | T.Asset asset -> Some asset.iri
154148155149 let plant_resource resource =
156150 analyse_resource resource;
157151 let@ iri = Option.iter @~ iri_for_resource resource in
152152+ let iri = Iri.normalize iri in
158153 match Hashtbl.mem resources iri with
159154 | false ->
160155 Hashtbl.add resources iri resource
···162157 Reporter.emitf Duplicate_tree "Already planted resource at address %a" pp_iri iri
163158164159 let get_resource iri =
160160+ let iri = Iri.normalize iri in
165161 Hashtbl.find_opt resources iri
166162167163 let get_all_resources () =
+2-4
lib/forest/Forest.mli
···2233module T = Xml_tree
4455-module type S = sig
66- type resource =
77- | Article of T.content T.article
88- | Asset of { iri: iri; contents: string; filename: string }
55+type resource = T.content T.resource
9677+module type S = sig
108 val plant_resource : resource -> unit
119 val get_resource : iri -> resource option
1210 val get_article : iri -> T.content T.article option
+1-1
lib/forest/Forest_util.ml
···11open Forester_core
2233module Make (F: Forest.S) = struct
44- module PT = Plain_text_client.Make(F)
44+ module PT = Plain_text_client.Make(F)(Plain_text_client.Default_params)
55 module C = Xml_tree.Comparators(PT)
6677 let get_sorted_articles addrs =
···5566module Make (R: sig val route : Iri.t -> string end) (F: Forest.S) = struct
7788- module PT = Plain_text_client.Make(F)
88+ module PT = Plain_text_client.Make(F)(Plain_text_client.Default_params)
991010 let render_tree ~dev ~host (doc : T.content T.article) =
1111 let@ iri = Option.bind doc.frontmatter.iri in
+29-19
lib/forest/Legacy_xml_client.ml
···20202121module Make (Params: Params) (F: Forest.S) () : S = struct
22222323- module PT = Plain_text_client.Make(F)
2423 module Util = Forest_util.Make(F)
25242625 module Xmlns = struct
···7978 Iri.equal ~normalize: true home_iri iri
8079 | None -> false
81808181+ let route_resource_iri ~suffix iri =
8282+ let host = Option.value ~default: "" @@ Iri.host iri in
8383+ let bare_route =
8484+ String.concat "-" @@
8585+ match Iri.path iri with
8686+ | Iri.Absolute xs -> xs
8787+ | Iri.Relative xs -> xs (* impossible? *)
8888+ in
8989+ begin
9090+ if host = Params.host then
9191+ if iri_is_home iri then "index.xml"
9292+ else
9393+ bare_route ^ suffix
9494+ else
9595+ "foreign-" ^ host ^ bare_route ^ suffix
9696+ end
9797+8298 let route iri =
8399 match F.get_resource iri with
8484- | Some (F.Article _) ->
8585- let host = Option.value ~default: "" @@ Iri.host iri in
8686- let bare_route =
8787- String.concat "-" @@
8888- match Iri.path iri with
8989- | Iri.Absolute xs -> xs
9090- | Iri.Relative xs -> xs (* impossible? *)
100100+ | Some resource ->
101101+ let suffix =
102102+ match resource with
103103+ | T.Article _ -> ".xml"
104104+ | T.Asset _ -> ""
91105 in
9292- begin
9393- if host = Params.host then
9494- if iri_is_home iri then "index.xml"
9595- else
9696- bare_route ^ ".xml"
9797- else
9898- "foreign-" ^ host ^ bare_route ^ ".xml"
9999- end
100100- | Some (F.Asset asset) ->
101101- "content/" ^ asset.filename
106106+ route_resource_iri ~suffix iri
102107 | None ->
103108 Iri.to_uri iri
109109+110110+ module PT = Plain_text_client.Make(F)(struct let route = route end)
104111105112 let get_expanded_title frontmatter =
106113 let scope = Scope.read () in
···114121 | _ -> Format.sprintf "%s:%s" qname.prefix qname.uname
115122116123 let render_xml_attr T.{ key; value } =
117117- P.string_attr (render_xml_qname key) "%s" value
124124+ let str_value = PT.string_of_content value in
125125+ P.string_attr (render_xml_qname key) "%s" str_value
118126119127 let render_prim_node p =
120128 X.prim p []
···195203 let relativised = Iri_scheme.relativise_iri ~host: Params.host iri in
196204 let str = Format.asprintf "%a" pp_iri relativised in
197205 [P.txt "%s" str]
206206+ | Route_of_iri iri ->
207207+ [P.txt "%s" (route iri)]
198208 | Xml_elt elt ->
199209 let prefixes_to_add, (name, attrs, content) =
200210 let@ () = Xmlns.within_scope in
+10-1
lib/forest/Plain_text_client.ml
···2233module T = Xml_tree
4455+module type Params = sig
66+ val route : iri -> string
77+end
88+99+module Default_params: Params = struct
1010+ let route = Iri.to_uri
1111+end
1212+513module type S = sig
614 val string_of_content : Xml_tree.content -> string
715 val pp_content : Format.formatter -> Xml_tree.content -> unit
816end
9171010-module Make (F: Forest.S) : S = struct
1818+module Make (F: Forest.S) (P: Params) : S = struct
11191220 let rec pp_content fmt = function
1321 | T.Content c -> c |> List.iter @@ pp_content_node fmt
···1523 and pp_content_node fmt : 'a T.content_node -> unit = function
1624 | Text txt | CDATA txt -> Format.pp_print_string fmt txt
1725 | Iri iri -> pp_iri fmt iri
2626+ | Route_of_iri iri -> Format.fprintf fmt "%s" (P.route iri)
1827 | KaTeX (_, content) -> pp_content fmt content
1928 | TeX_cs cs -> Format.fprintf fmt "\\%a" TeX_cs.pp cs
2029 | Xml_elt elt -> pp_content fmt elt.content
+7-1
lib/forest/Plain_text_client.mli
···2233module T := Xml_tree
4455+module type Params = sig
66+ val route : iri -> string
77+end
88+99+module Default_params: Params
1010+511module type S = sig
612 val string_of_content : Xml_tree.content -> string
713 val pp_content : Format.formatter -> Xml_tree.content -> unit
814end
9151010-module Make (_: Forest.S) : S
1616+module Make (_: Forest.S) (_: Params) : S
+21-20
lib/frontend/Forester.ml
···77module T = Xml_tree
88module F = Forest.Make(Forest_graphs.Make ())
99module FU = Forest_util.Make(F)
1010-module PT = Plain_text_client.Make(F)
1010+module PT = Plain_text_client.Make(F)(Plain_text_client.Default_params)
1111module C = T.Comparators(PT)
1212module EP = Eio.Path
1313···9191let complete ~host prefix =
9292 let@ article = Seq.filter_map @~ List.to_seq @@ get_all_articles () in
9393 let@ iri = Option.bind article.frontmatter.iri in
9494- let@ iri = Option.bind @@ Option_util.guard Iri_scheme.is_stable_iri iri in
9494+ let@ iri = Option.bind @@ Option_util.guard Iri_scheme.is_named_iri iri in
9595 let iri = Iri_scheme.relativise_iri ~host iri in
9696 let@ title = Option.bind article.frontmatter.title in
9797 let title = Format.asprintf "%a" PT.pp_content title in
···132132 let source_path = String.concat "/" source_path in
133133 let cwd = Eio.Stdenv.cwd env in
134134 let content = EP.load EP.(cwd / source_path) in
135135- let filename, iri = Forester_compiler.Asset_content_addresser.install ~source_path ~content in
136136- F.plant_resource @@ F.Asset { iri; contents = content; filename }
135135+ let iri = Forester_compiler.Asset_router.install ~host ~source_path ~content in
136136+ F.plant_resource @@ T.Asset { iri; host; content }
137137 end;
138138 begin
139139 let@ () = Reporter.profile "Expand, evaluate, and analyse forest" in
140140 begin
141141 let@ _, article = Seq.iter @~ Iri_map.to_seq @@ Forest_reader.read_trees ~host ~env parsed_trees in
142142- F.plant_resource @@ F.Article article
142142+ F.plant_resource @@ T.Article article
143143 end;
144144 begin
145145 let@ _foreign_dir = List.iter @~ foreign_dirs in
···163163 let cwd = Eio.Stdenv.cwd env in
164164 let all_resources = List.of_seq @@ F.get_all_resources () in
165165 begin
166166- let all_articles = List.filter_map (function F.Article article -> Some article | _ -> None) all_resources in
166166+ let all_articles = List.filter_map (function T.Article article -> Some article | _ -> None) all_resources in
167167 let json_string = Yojson.Basic.to_string @@ R.render_trees ~dev ~host all_articles in
168168 let json_path = EP.(cwd / output_dir_name / "forest.json") in
169169 Eio_util.ensure_context_of_path ~perm: 0o755 json_path;
···173173 let@ resource = Eio.Fiber.List.iter ~max_fibers: 20 @~ all_resources in
174174 let@ () = Reporter.easy_run in
175175 match resource with
176176- | F.Article article ->
176176+ | T.Article article ->
177177 let@ route = Option.iter @~ Option.map Client.route article.frontmatter.iri in
178178 let path = EP.(cwd / output_dir_name / route) in
179179 Eio_util.ensure_context_of_path ~perm: 0o755 path;
180180 let@ flow = EP.with_open_out ~create: (`Or_truncate 0o644) path in
181181 let@ writer = Eio.Buf_write.with_flow flow in
182182- Client.pp_xml ~stylesheet:"default.xsl" (Eio.Buf_write.make_formatter writer) article
183183- | F.Asset asset ->
182182+ Client.pp_xml ~stylesheet: "default.xsl" (Eio.Buf_write.make_formatter writer) article
183183+ | T.Asset asset ->
184184 let route = Client.route asset.iri in
185185 let dest = EP.(cwd / output_dir_name / route) in
186186 if Eio_util.file_exists dest then ()
187187 else
188188 begin
189189 Eio_util.ensure_context_of_path ~perm: 0o755 dest;
190190- EP.save ~create: (`Or_truncate 0o644) dest asset.contents
190190+ EP.save ~create: (`Or_truncate 0o644) dest asset.content
191191 end
192192 end
193193194194-let export ~env ~host ~asset_dirs : unit =
194194+let export ~env ~host : unit =
195195 let@ () = Reporter.profile "Export forest" in
196196- let all_resources = List.of_seq @@ F.get_all_resources () in
197197- let all_articles = List.filter_map (function F.Article article -> Some article | _ -> None) all_resources in
198198-196196+ let local_resources =
197197+ let@ resource = Seq.filter @~ F.get_all_resources () in
198198+ match resource with
199199+ | T.Article { frontmatter = { iri = Some iri; _ }; _ } ->
200200+ Iri.host iri = Some host
201201+ | T.Asset asset -> asset.host = host
202202+ | _ -> false
203203+ in
199204 let cwd = Eio.Stdenv.cwd env in
200200- let result = Json_client.render_trees ~host all_articles in
205205+ let result = Json_client.render ~host @@ List.of_seq local_resources in
201206 let dir = Eio.Path.(cwd / "export" / host) in
202207 Eio.Path.mkdirs ~exists_ok: true ~perm: 0o755 dir;
203203- Eio.Path.save ~create: (`Or_truncate 0o644) Eio.Path.(dir / "forest.json") result;
204204- let@ dir_to_copy = List.iter @~ asset_dirs in
205205- let source = EP.native_exn dir_to_copy in
206206- let dest_dir = EP.native_exn dir in
207207- Eio_util.copy_to_dir ~env ~cwd ~source ~dest_dir
208208+ Eio.Path.save ~create: (`Or_truncate 0o644) Eio.Path.(dir / "forest.json") result
-1
lib/frontend/Forester.mli
···2222val export :
2323 env: env ->
2424 host: string ->
2525- asset_dirs: dir list ->
2625 unit
27262827val copy_contents_of_dir :