···1818 | None -> "n/a"
1919 | Some v -> Build_info.V1.Version.to_string v
20202121-let build ~env config_filename dev render_only no_assets no_theme =
2121+let build ~env config_filename dev no_theme =
2222 let config = Forester_frontend.Config.parse_forest_config_file config_filename in
2323 let tree_dirs = paths_of_dirs ~env config.trees in
2424 let asset_dirs = paths_of_dirs ~env config.assets in
2525 let foreign_dirs = paths_of_dirs ~env config.foreign in
2626 Forester.plant_raw_forest_from_dirs ~env ~host: config.host ~dev ~tree_dirs ~asset_dirs ~foreign_dirs;
2727- Forester.render_forest ~env ~dev ~host: config.host ~home: config.home ~stylesheet: config.stylesheet;
2727+ Forester.render_forest ~env ~dev ~host: config.host ~home: config.home;
2828 let dirs_to_copy =
2929 (if not no_theme then [config.theme] else []) @
3030- (if not no_assets then config.assets else [])
3030+ []
3131 in
3232 let@ dir_to_copy = List.iter @~ dirs_to_copy in
3333 Forester.copy_contents_of_dir ~env @@ path_of_dir ~env dir_to_copy
···151151 let doc = "Run forester in development mode; this will attach source file locations to the generated json." in
152152 Arg.value @@ Arg.flag @@ Arg.info ["dev"] ~doc
153153 in
154154- let arg_no_assets =
155155- let doc = "Build without copying the asset directory" in
156156- Arg.value @@ Arg.flag @@ Arg.info ["no-assets"] ~doc
157157- in
158154 let arg_no_theme =
159155 let doc = "Build without copying the theme directory" in
160156 Arg.value @@ Arg.flag @@ Arg.info ["no-theme"] ~doc
161161- in
162162- let arg_render_only =
163163- let doc = "Builds the entire forest but renders only the specified comma-separated list of trees" in
164164- Arg.value @@ Arg.opt (Arg.some' (Arg.list Arg.string)) None @@ Arg.info ["render-only"] ~docv: "TREES" ~doc
165157 in
166158 let doc = "Build the forest" in
167159 let man =
···177169 const (build ~env)
178170 $ arg_config
179171 $ arg_dev
180180- $ arg_render_only
181181- $ arg_no_assets
182172 $ arg_no_theme
183173 )
184174
···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
+4
lib/compiler/Eval.ml
···402402 T.{ href; target; modifier = Identity }
403403 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)
405409 | Object { self; methods } ->
406410 let table =
407411 let env = Lex_env.read () in
+3-2
lib/compiler/Expand.ml
···152152 begin
153153 match import with
154154 | None ->
155155- Reporter.emitf ?loc: loc Tree_not_found "Could not find tree named `%s'" dep
155155+ Reporter.emitf ?loc: loc Resource_not_found "Could not import tree named `%s'" dep
156156 | Some tree ->
157157 begin
158158 match vis with
···338338 ["rel"; "links-to"], Syn.Text Builtin_relation.links_to;
339339 ["rel"; "is-reference"], Syn.Text Builtin_relation.is_reference;
340340 ["rel"; "is-person"], Syn.Text Builtin_relation.is_person;
341341- ["execute"], Syn.Dx_execute
341341+ ["execute"], Syn.Dx_execute;
342342+ ["route-asset"], Syn.Route_asset;
342343 ];
343344 Builtins.Transclude.alloc_expanded ();
344345 Builtins.Transclude.alloc_show_heading ();
···77module type S = sig
88 type resource =
99 | Article of T.content T.article
1010- | Asset of iri
1010+ | Asset of { iri: iri; contents: string; filename: string }
11111212 val plant_resource : resource -> unit
13131414 val get_resource : Iri.t -> resource option
1515 val get_article : Iri.t -> T.content T.article option
1616+ val get_all_resources : unit -> resource Seq.t
16171718 val get_expanded_title : ?scope: iri -> ?flags: T.title_flags -> T.content T.frontmatter -> T.content
1819 val get_content_of_transclusion : T.content T.transclusion -> T.content
···39404041 type resource =
4142 | Article of article
4242- | Asset of iri
4343+ | Asset of { iri: iri; contents: string; filename: string }
43444445 let resources : (Iri.t, resource) Hashtbl.t =
4546 Hashtbl.create 1000
···149150150151 let iri_for_resource = function
151152 | Article article -> article.frontmatter.iri
152152- | Asset iri -> Some iri
153153+ | Asset asset -> Some asset.iri
153154154155 let plant_resource resource =
155156 analyse_resource resource;
···163164 let get_resource iri =
164165 Hashtbl.find_opt resources iri
165166167167+ let get_all_resources () =
168168+ Hashtbl.to_seq_values resources
169169+166170 let get_article iri =
167171 let@ resource = Option.bind @@ get_resource iri in
168172 match resource with
···173177 match get_article addr with
174178 | Some article -> article
175179 | None ->
176176- Reporter.fatalf Tree_not_found "Could not find tree %a" pp_iri addr
180180+ Reporter.fatalf Resource_not_found "Could not find tree %a" pp_iri addr
177181178182 module Legacy_query_engine = Legacy_query_engine.Make(Graphs)
179183 include Legacy_query_engine
+3-1
lib/forest/Forest.mli
···55module type S = sig
66 type resource =
77 | Article of T.content T.article
88- | Asset of iri
88+ | Asset of { iri: iri; contents: string; filename: string }
991010 val plant_resource : resource -> unit
1111 val get_resource : iri -> resource option
1212 val get_article : iri -> T.content T.article option
1313+ val get_all_resources : unit -> resource Seq.t
1414+1315 val get_expanded_title : ?scope: iri -> ?flags: T.title_flags -> T.content T.frontmatter -> T.content
1416 val get_content_of_transclusion : T.content T.transclusion -> T.content
1517 val get_title_or_content_of_vertex : ?not_found: (iri -> T.content option) -> modifier: T.modifier -> T.content T.vertex -> T.content option
+21-33
lib/forest/Legacy_xml_client.ml
···6565 | Absolute [_] -> "user"
6666 | _ -> failwith "addr_type"
67676868- let route_bare_forester_iri ~path ~is_asset =
6969- let bare_route =
7070- String.concat (if is_asset then "/" else "-") @@
7171- match path with
7272- | Iri.Absolute xs -> xs
7373- | Iri.Relative xs -> xs (* impossible? *)
7474- in
7575- match is_asset with
7676- | true -> bare_route
7777- | false -> bare_route ^ ".xml"
7878-7979- let route_foreign_forester_iri ~host ~path ~is_asset =
8080- "foreign-" ^ host ^ "-" ^ route_bare_forester_iri ~path ~is_asset
8181-8282- let route_forester_iri ~host ~path ~is_asset =
8383- if host = Params.host then
8484- route_bare_forester_iri ~path ~is_asset
8585- else
8686- route_foreign_forester_iri ~host ~path ~is_asset
8787-8868 let home_iri =
8969 let@ root = Option.bind Params.home in
9070 let base = Iri_scheme.base_iri ~host: Params.host in
···9979 Iri.equal ~normalize: true home_iri iri
10080 | None -> false
10181102102- let iri_is_asset iri =
103103- match F.get_resource iri with
104104- | Some (F.Asset _) -> true
105105- | _ -> false
106106-10782 let route iri =
108108- match Iri.host iri with
109109- | Some host when Iri.scheme iri = Iri_scheme.scheme ->
110110- if iri_is_home iri then "index.xml"
111111- else
112112- let path = Iri.path iri in
113113- let is_asset = iri_is_asset iri in
114114- route_forester_iri ~host ~path ~is_asset
115115- | _ -> Iri.to_uri iri
8383+ 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? *)
9191+ 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
102102+ | None ->
103103+ Iri.to_uri iri
116104117105 let get_expanded_title frontmatter =
118106 let scope = Scope.read () in
+7-7
lib/frontend/Config.ml
···99 assets: string list;
1010 foreign: string list;
1111 theme: string;
1212- stylesheet: string
1312 }
1413 [@@deriving show, repr]
1514end
···2221 foreign = [];
2322 theme = "theme";
2423 home = None;
2525- stylesheet = "default.xsl"
2624 }
27252826let parse_forest_config_file filename =
···4947 | Some _ ->
5048 Reporter.emitf Configuration_error "In your configuration file, change `root' key to `home' in the [forest] group."
5149 in
5050+ let _ =
5151+ match get tbl (forest |-- key "stylesheet" |-- string) with
5252+ | None -> ()
5353+ | Some _ ->
5454+ Reporter.emitf Configuration_error "Custom XSL stylesheet injection is no longer supported; please remove the `stylesheet' key from the [forest] group."
5555+ in
5256 let trees =
5357 Option.value ~default: default_forest_config.trees @@
5458 get tbl (forest |-- key "trees" |-- array |-- strings)
···6569 Option.value ~default: default_forest_config.theme @@
6670 get tbl (forest |-- key "theme" |-- string)
6771 in
6868- let stylesheet =
6969- Option.value ~default: default_forest_config.stylesheet @@
7070- get tbl (forest |-- key "stylesheet" |-- string)
7171- in
7272- Forest_config.{ host; assets; trees; foreign; theme; home; stylesheet }
7272+ Forest_config.{ host; assets; trees; foreign; theme; home }