aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authororbifx <fox@orbitalfox.eu>2020-03-07 16:07:54 +0000
committerorbifx <fox@orbitalfox.eu>2020-03-07 16:10:08 +0000
commit48916c613ac6483e1ea77746a62c2d8100d81971 (patch)
tree7dc538b90ea4debcd1c15dfaf81ed7e659c43441
downloadlogarion-xml-48916c613ac6483e1ea77746a62c2d8100d81971.tar.gz
logarion-xml-48916c613ac6483e1ea77746a62c2d8100d81971.tar.bz2
logarion-xml-48916c613ac6483e1ea77746a62c2d8100d81971.zip
Static HTML and ATOM file generatorHEADmaster
-rw-r--r--.gitignore9
-rw-r--r--CONTRIBUTING.md83
-rw-r--r--Makefile12
-rw-r--r--README.md23
-rw-r--r--bin/dune4
-rw-r--r--bin/logarion_xml_cli.ml80
-rw-r--r--doc/logarion.odocl3
-rw-r--r--dune-project2
-rw-r--r--lib/atom.ml50
-rw-r--r--lib/config.ml18
-rw-r--r--lib/dune4
-rw-r--r--lib/html.ml155
-rw-r--r--lib/template.ml94
-rw-r--r--logarion-xml.opam23
14 files changed, 560 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93ee2a0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.merlin
+.logarion
+*.ymd
+\#*\#
+.\#*1
+*~
+*.o
+*.native
+_build \ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..46ef2c1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,83 @@
+# Contributing to Logarion
+
+Logarions primary aim is to create a note system, which doesn't waste resources.
+The secondary aim is to provide an exemplary OCaml project to demonstrate and promote the language (as it happens with many other "Blogging" systems written in other languages).
+
+As part of the secondary aim, the source code needs to written in a way that encourages the language's adoption and the participation to the OCaml developer community.
+
+## Starting with OCaml
+
+_"OCaml is an industrial strength programming language supporting functional, imperative and object-oriented styles"_ -- https://ocaml.org/
+
+OCaml simply rocks.
+
+If you are unfamiliar with OCaml, consider starting with these resources:
+
+- Install OCaml: https://ocaml.org/docs/install.html
+- Read about OCaml: https://ocaml.org/learn/books.html
+- Ask questions & join the community:
+ - Mailing lists: https://ocaml.org/community/
+ - IRC: irc://irc.freenode.net/#ocaml (Web client: https://riot.im/app/#/room/#freenode_#ocaml:matrix.org )
+ - Reddit: http://www.reddit.com/r/ocaml/
+ - Discourse: https://discuss.ocaml.org/
+ - .. other: https://ocaml.org/community/
+
+## Design principles
+
+[Unix philosophy](https://en.wikipedia.org/wiki/Unix_philosophy#Do_One_Thing_and_Do_It_Well)
+
+1. System simplicity & interoperability.
+2. Output quality.
+3. Distributed interactivity, like sharing with friends.
+
+## Developing & contributing
+
+### Clone
+
+```
+git clone https://cgit.orbitalfox.eu/logarion/
+```
+
+Install dependencies:
+
+```
+cd logarion
+pin add logarion . -n
+opam depext --install logarion
+```
+
+Build the project:
+
+```
+dune build src/logarion.exe
+```
+
+This will create `_build/default/src/logarion.exe` (the command line interface).
+
+### Project structure
+
+There are three layers:
+
+- notes
+- archive
+- interfaces & intermediate formats
+
+### Core
+
+- `logarion.ml`: repository related functions (listing, adding/removing, etc). ([src/logarion.ml](https://gitlab.com/orbifx/logarion/blob/master/src/logarion.ml))
+- `note.ml`: parsing from and to note files. ([src/note.ml](https://gitlab.com/orbifx/logarion/blob/master/src/note.ml))
+
+### Intermediate formats
+
+Converters:
+
+- `html.ml`: archive to HTML pages.
+- `atom.ml`: archive to Atom feeds.
+
+### Servers & utilities
+
+Logarion's archives can be served over various protocols using servers.
+Find related software here:
+
+- https://logarion.orbitalfox.eu/
+- https://cgit.orbitalfox.eu/ \ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d444f68
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+all: cli
+
+cli:
+ dune build bin/logarion_xml_cli.exe
+
+clean:
+ dune clean
+
+tgz:
+ cp _build/default/bin/logarion_xml_cli.exe logarion-xml
+ strip logarion-xml
+ tar czvf "logarion-xml-$(shell ./logarion-xml --version)-$(shell uname -s)-$(shell uname -m)-$(shell git rev-parse --short HEAD).tar.gz" logarion-xml
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6808146
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# Logarion XML
+
+[Logarion][] archive exporter to XHTML website with [Atom feed][].
+
+[Logarion]: https://logarion.orbitalfox.eu
+[Atom feed]: https://en.wikipedia.org/wiki/Atom_(Web_standard)
+
+## Install
+
+Either download an [executable] or build from [source]:
+
+[executable]: https://logarion.orbitalfox.eu/downloads
+[source]: https://cgit.orbitalfox.eu/logarion-xml
+
+## Archive conversion
+
+From within your archive directory run:
+
+ logarion_xml convert <export-path>
+
+Where `<export-path` is the directory to generate the HTML files and `feed.atom`.
+
+It will also copy the whole `.logarion/static` (CSS, fonts, etc) directory under `<export-path>/static`.
diff --git a/bin/dune b/bin/dune
new file mode 100644
index 0000000..fdfeab7
--- /dev/null
+++ b/bin/dune
@@ -0,0 +1,4 @@
+(executable
+ (name logarion_xml_cli)
+ (public_name logarion_xml)
+ (libraries logarion logarion.confix logarion.file logarion_xml cmdliner bos))
diff --git a/bin/logarion_xml_cli.ml b/bin/logarion_xml_cli.ml
new file mode 100644
index 0000000..3c2bbee
--- /dev/null
+++ b/bin/logarion_xml_cli.ml
@@ -0,0 +1,80 @@
+let version = "%%VERSION%%"
+
+open Cmdliner
+
+let init _force =
+ let configuration_dirs = [ ".logarion", "Logarion's internal directory"; ".logarion/static", "static files" ] in
+ match File.Directory.directories configuration_dirs with
+ | Error (d, descr) -> prerr_endline ("Error creating" ^ d ^ ", " ^ descr)
+ | Ok () -> Logarion_xml.Config.create_toml ".logarion"
+
+let convert target_dir =
+ let module Config = Confix.Config.Make (Confix.ConfixToml) in
+ let toml_config = match Confix.Config.Path.with_file "config.toml" with
+ | Ok cfg -> Config.from_path cfg
+ | Error str -> prerr_endline str; exit 1
+ in
+ let config = Config.to_record_or_exit Logarion.Archive.Configuration.of_config toml_config in
+
+ let module L = Logarion.Archive.Make (File) in
+ let store = File.store config.repository in
+ let archive = L.{ config; store } in
+ let all_notes = File.to_list ~order:(fun a b -> L.recency_order a.Logarion.Note.meta b.Logarion.Note.meta) L.note_lens archive.store in
+ let published_notes = List.filter Logarion.Meta.(fun n -> CategorySet.published n.Logarion.Note.meta.categories) all_notes in
+
+ if [] = published_notes then (prerr_endline "No published notes in archive"; exit 1);
+
+ let listed_metas =
+ List.filter Logarion.Meta.(fun note -> CategorySet.published note.Logarion.Note.meta.categories && CategorySet.listed note.meta.categories) published_notes
+ |> List.map L.meta_lens
+ in
+
+ let module T = Logarion_xml.Template in
+ let header = T.header_converter toml_config in
+ let body = T.body_converter toml_config in
+ let style = T.default_style in
+ let linker x = match Fpath.(relativize ~root:(v "/") (v x)) with Some l -> Fpath.to_string l | None -> "" in
+ let page_of_log metas = T.page_of_log linker header config metas in
+ let page_of_index metas = T.page_of_index linker header config metas in
+ let page_of_note note = T.page_of_note linker header body config note in
+ let path_of_note note extension = target_dir ^ "/" ^ Logarion.Meta.alias note.Logarion.Note.meta ^ extension in
+ let file_creation path content = let out = open_out path in output_string out content; close_out out in
+ match File.Directory.(directory target_dir |> print ~descr:"export" target_dir) with
+ | Error _ -> ()
+ | Ok _ ->
+ let url = match toml_config with
+ | Error _msg -> ""
+ | Ok cfg -> Confix.(Config.with_default "" ConfixToml.(string cfg ("web" / "url")))
+ in
+ match File.copy ~recursive:true ".logarion/static" (target_dir) with
+ | Ok _ ->
+ let note_write note =
+ file_creation (path_of_note note ".html") (page_of_note ~style note);
+ file_creation (path_of_note note ".md") (Logarion.Note.to_string note);
+ in
+ List.iter note_write published_notes;
+ file_creation (target_dir ^ "/log.html") (page_of_log ~style listed_metas);
+ file_creation (target_dir ^ "/index.html") (page_of_index ~style listed_metas);
+ file_creation (target_dir ^ "/feed.atom") (Logarion_xml.Atom.feed config url (L.note_with_id archive) listed_metas)
+ | Error (`Msg m) -> prerr_endline m
+
+let convert_term =
+ let directory =
+ Arg.(value & pos 0 string "webpage" & info [] ~docv:"target directory" ~doc:"Directory name to convert webpage into")
+ in
+ Term.(const convert $ directory),
+ Term.info
+ "convert" ~doc:"convert archive to HTML & ATOM feed"
+ ~man:[ `S "DESCRIPTION"; `P "Convert Logarion archive into HTML & ATOM feed" ]
+
+let default_cmd =
+ Term.(ret (const (`Help (`Pager, None)))),
+ Term.info "Logarion XML converter" ~version ~doc:"an article collection & publishing system"
+ ~man:[ `S "BUGS";
+ `P "Submit bugs <mailto:logarion@lists.orbitalfox.eu?subject=[Issue] summary-here>"; ]
+
+let cmds = [ convert_term ]
+
+let () =
+ Random.self_init();
+ match Term.eval_choice default_cmd cmds with `Error _ -> exit 1 | _ -> exit 0
diff --git a/doc/logarion.odocl b/doc/logarion.odocl
new file mode 100644
index 0000000..3f5faf0
--- /dev/null
+++ b/doc/logarion.odocl
@@ -0,0 +1,3 @@
+Logarion
+Ymd
+Web \ No newline at end of file
diff --git a/dune-project b/dune-project
new file mode 100644
index 0000000..8deeb35
--- /dev/null
+++ b/dune-project
@@ -0,0 +1,2 @@
+(lang dune 1.3)
+(name logarion-xml) \ No newline at end of file
diff --git a/lib/atom.ml b/lib/atom.ml
new file mode 100644
index 0000000..d820b56
--- /dev/null
+++ b/lib/atom.ml
@@ -0,0 +1,50 @@
+let esc = Xml_print.encode_unsafe_char
+
+let header config url =
+ let open Logarion.Meta in
+ let open Logarion.Archive.Configuration in
+ "<title>" ^ config.title ^ "</title>"
+ (* TODO: ^ "<subtitle>A subtitle.</subtitle>"*)
+ ^ "<link rel=\"alternate\" type=\"text/html\" href=\"" ^ url ^ "\"/>"
+ ^ "<link rel=\"self\" type=\"application/atom+xml\" href=\"" ^ url ^ "/feed.atom\" />"
+ ^ "<id>urn:uuid:" ^ Id.to_string config.id ^ "</id>"
+ ^ "<updated>" ^ Ptime.to_rfc3339 (Ptime_clock.now ()) ^ "</updated>"
+
+let opt_element tag_name content =
+ if content <> ""
+ then "<" ^ tag_name ^ ">" ^ content ^ "</" ^ tag_name ^ ">"
+ else ""
+
+let entry url note =
+ let open Logarion in
+ let meta = note.Note.meta in
+ let u = Meta.alias meta in
+ let open Meta in
+ let authors elt a =
+ a ^ "<author>"
+ ^ (opt_element "name" @@ esc elt.Author.name)
+ ^ (opt_element "uri" @@ esc (Uri.to_string elt.Author.address))
+ ^ "</author>"
+ in
+ ("<entry>"
+ ^ "<title>" ^ esc meta.title ^ "</title>"
+ ^ "<id>urn:uuid:" ^ Meta.Id.to_string meta.uuid ^ "</id>"
+ ^ "<link rel=\"alternate\" href=\"" ^ url ^ "/" ^ u ^ ".html\" />"
+ ^ "<updated>" ^ Date.(meta.date |> listing |> rfc_string) ^ "</updated>"
+ ^ Meta.AuthorSet.fold authors meta.authors ""
+ ^ opt_element "summary" @@ esc meta.abstract)
+ ^ Meta.StringSet.fold (fun elt a -> a ^ "<category term=\"" ^ elt ^ "\"/>") meta.topics ""
+ ^ "<content type=\"xhtml\"><div xmlns=\"http://www.w3.org/1999/xhtml\">"
+ ^ (Omd.to_html @@ Omd.of_string @@ esc note.Note.body)
+ ^ "</div></content>"
+ ^ "</entry>"
+
+let feed config url note_fn articles =
+ let fold_valid feed m = match note_fn m.Logarion.Meta.uuid with
+ | Some note -> feed ^ "\n" ^ entry url note
+ | None -> feed
+ in
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<feed xmlns=\"http://www.w3.org/2005/Atom\">\n"
+ ^ header config url
+ ^ List.fold_left fold_valid "" articles
+ ^ "</feed>"
diff --git a/lib/config.ml b/lib/config.ml
new file mode 100644
index 0000000..92c247a
--- /dev/null
+++ b/lib/config.ml
@@ -0,0 +1,18 @@
+let create_toml directory =
+ let exemplar_toml =
+ let open Toml in
+ let open TomlTypes in
+ of_key_values [
+ key "web",
+ TTable (
+ of_key_values [
+ key "url", TString "http://localhost:3666";
+ key "stylesheets", TArray (NodeString ["main.css"]);
+ key "static_dir", TString (directory ^ "static");
+ ]);
+ key "templates", TTable (of_key_values []);
+ ]
+ in
+ let config_file = open_out_gen [Open_append] 0700 (directory ^ "config.toml") in
+ output_bytes config_file (Toml.Printer.string_of_table exemplar_toml|> Bytes.of_string);
+ close_out config_file
diff --git a/lib/dune b/lib/dune
new file mode 100644
index 0000000..e4760eb
--- /dev/null
+++ b/lib/dune
@@ -0,0 +1,4 @@
+(library
+ (name logarion_xml)
+ (public_name logarion-xml)
+ (libraries logarion logarion.file tyxml mustache ptime ptime.clock.os))
diff --git a/lib/html.ml b/lib/html.ml
new file mode 100644
index 0000000..222e3be
--- /dev/null
+++ b/lib/html.ml
@@ -0,0 +1,155 @@
+open Tyxml.Html
+open Logarion
+
+let to_string tyxml = Format.asprintf "%a" (Tyxml.Html.pp ()) tyxml
+
+let head ~style linker t =
+ head (title (txt t)) [
+ link ~rel:[`Stylesheet] ~href:(linker style) ();
+ link ~rel:[`Alternate] ~href:(linker "/feed.atom") ~a:[a_mime_type "application/atom+xml"] ();
+ meta ~a:[a_charset "utf-8"] ();
+ ]
+
+let default_style = "/static/main.css"
+
+let page ?(style=default_style) linker head_title header main =
+ html (head ~style linker head_title) (body [ header; main ])
+
+let anchor url content = a ~a:[ a_href (uri_of_string url) ] content
+
+let div ?(style_class="") content =
+ let a = if style_class <> "" then [a_class [style_class]] else [] in
+ div ~a content
+
+let main = main
+
+let unescaped_data = Unsafe.data
+let data = txt
+let title = h1
+let heading = h2
+let header = header
+let aside = aside
+
+let pipe = span ~a:[a_class ["pipe"]] [txt " | "]
+
+let meta ~abstract ~authors ~date ~series ~topics ~keywords ~uuid =
+ let opt_span name value = if String.length value > 0 then (span [pipe; txt (name ^ value)]) else txt "" in
+ let authors = List.fold_left (fun acc x -> a ~a:[a_rel [`Author]] [txt x] :: acc) [] authors in
+ [ p ~a:[a_class ["abstract"]] [Unsafe.data abstract]; ]
+ @ authors
+ @ [
+ pipe;
+ time ~a:[a_datetime date] [txt date];
+ pipe;
+ opt_span "series: " series;
+ opt_span "topics: " topics;
+ opt_span "keywords: " keywords;
+ div [txt ("id: " ^ uuid)];
+ ]
+ |> div ~style_class:"meta"
+
+let note = article
+
+let text_item path meta =
+ let module Meta = Logarion.Meta in
+ tr [
+ td [ a ~a:[a_class ["title"]; a_href (path ^ Meta.alias meta ^ ".html")] [data meta.Meta.title] ];
+ td [ time @@ [unescaped_data Meta.Date.(pretty_date (listing meta.Meta.date))] ];
+ ]
+
+let listing_texts path metas =
+ let item meta = text_item path meta in
+ table @@ List.map item metas
+
+let listing_topics topic_roots =
+ let rec unordered_list topic_roots = ul @@ List.map list_item topic_roots
+ and list_item topic = li [ a ~a:[a_href ("#" ^ topic)] [txt topic] ] in
+ [ section ~a:[a_class ["topic_list"]] [ h3 [ txt "Topics" ]; unordered_list topic_roots ]]
+
+let listing_topics_recursive topic_map topic_roots metas =
+ let rec unordered_list topic_roots = ul @@ List.map list_item topic_roots
+ and sub_items topic = match Logarion.Meta.TopicSet.TopicMap.find_opt topic topic_map with
+ | None -> []
+ | Some (_, subtopics) -> [ unordered_list (Logarion.Meta.StringSet.elements subtopics) ]
+ and list_item topic =
+ let item =
+ let open Logarion.Meta in
+ if List.exists (fun x -> StringSet.mem topic (StringSet.map (Logarion.Meta.TopicSet.topic) x.topics)) metas
+ then a ~a:[a_href ("#" ^ topic)] [txt topic] else txt topic
+ in
+ li (item :: sub_items topic)
+ in
+ [ section ~a:[a_class ["topic_list"]] [ h1 [ txt "Topics" ]; unordered_list topic_roots ]]
+
+let listing_index topic_map topic_roots path metas =
+ let rec item_group topics = List.fold_left (fun acc topic -> acc @ sub_groups topic @ [tbody (items topic)]) [] topics
+ and sub_groups topic = match Logarion.Meta.TopicSet.TopicMap.find_opt topic topic_map with
+ | None -> []
+ | Some (_, subtopics) -> item_group (Logarion.Meta.StringSet.elements subtopics)
+ and items topic =
+ let items = List.fold_left Meta.(fun a e -> if StringSet.mem topic (StringSet.map (Logarion.Meta.TopicSet.topic) e.topics)
+ then text_item path e :: a else a) [] metas in
+ match items with
+ | [] -> []
+ | x -> [ tr [ th ~a:[a_colspan 3; a_id topic] [txt topic] ] ] @ x
+ in
+ [ section [ h1 [ txt "Texts" ]; tablex @@ item_group topic_roots ] ]
+
+module Renderer = struct
+ let meta meta e =
+ let e = List.hd e in
+ match e with
+ | "urn_name" -> [unescaped_data @@ "/note/" ^ Logarion.Meta.alias meta]
+ | "date" | "date_created" | "date_edited" | "date_published" | "date_human" ->
+ [time @@ [unescaped_data @@ Logarion.Meta.value_with_name meta e]]
+ | tag -> [unescaped_data @@ Logarion.Meta.value_with_name meta tag]
+
+ let note note e = match List.hd e with
+ | "body" -> [unescaped_data @@ Omd.to_html @@ Omd.of_string note.Logarion.Note.body]
+ | _ -> meta note.Logarion.Note.meta e
+
+ let archive archive e = match List.hd e with
+ | "title" -> [h1 [anchor ("index.html") [data archive.Logarion.Archive.Configuration.title]]]
+ | tag -> prerr_endline ("unknown tag: " ^ tag); [unescaped_data ""]
+end
+
+let form ymd =
+ let article_form =
+ let input_set title input = p [ label [ txt title; input ] ] in
+ let open Note in
+ let open Meta in
+ let authors = AuthorSet.to_string ymd.meta.authors in
+ [
+ input ~a:[a_name "uuid"; a_value (Id.to_string ymd.meta.uuid); a_input_type `Hidden] ();
+ input_set
+ "Title"
+ (input ~a:[a_name "title"; a_value ymd.meta.title; a_required ()] ());
+ input_set
+ "Authors"
+ (input ~a:[a_name "authors"; a_value authors] ());
+ input_set
+ "Topics"
+ (input ~a:[a_name "topics"; a_value (stringset_csv ymd.meta.topics)] ());
+ input_set
+ "Categories"
+ (input ~a:[a_name "categories"; a_value (CategorySet.to_csv ymd.meta.categories)] ());
+ input_set
+ "Keywords"
+ (input ~a:[a_name "keywords"; a_value (stringset_csv ymd.meta.keywords)] ());
+ input_set
+ "Series"
+ (input ~a:[a_name "series"; a_value (stringset_csv ymd.meta.series)] ());
+ input_set
+ "Abstract"
+ (input ~a:[a_name "abstract"; a_value ymd.meta.abstract] ());
+ input_set
+ "Text"
+ (textarea ~a:[a_name "body"] (txt ymd.body));
+ p [ button ~a:[a_button_type `Submit] [txt "Submit"] ];
+ ]
+ in
+ div
+ [ form
+ ~a:[a_method `Post; a_action (uri_of_string "/post.note"); a_accept_charset ["utf-8"]]
+ [ fieldset ~legend:(legend [txt "Article"]) article_form ]
+ ]
diff --git a/lib/template.ml b/lib/template.ml
new file mode 100644
index 0000000..f1206b1
--- /dev/null
+++ b/lib/template.ml
@@ -0,0 +1,94 @@
+type t = Mustache.t
+
+let of_string = Mustache.of_string
+let of_file f = File.to_string f |> of_string
+
+let string s = [Html.data s]
+let section ~inverted:_ _name _contents = prerr_endline "Mustache sections unsupported"; []
+let unescaped _elts = prerr_endline "Mustache unescaped not supported; used escaped instead"; []
+let partial ?indent:_ _name _ _ = prerr_endline "Mustache sections unsupported"; []
+let comment _ = [Html.data ""]
+let concat = List.concat
+
+let escaped_index ~from:_ ~n:_ _metas _e = [Html.data "temp"]
+ (* match List.hd e with *)
+ (* | "topics" -> *)
+ (* let topics = *)
+ (* ListLabels.fold_left *)
+ (* ~init:(Logarion.Meta.StringSet.empty) *)
+ (* ~f:(fun a e -> Logarion.Meta.unique_topics a e ) metas *)
+ (* in *)
+ (* Logarion.Meta.StringSet.fold (fun e a -> a ^ "<li><a href=\"/topic/" ^ e ^ "\">" ^ e ^ "</a></li>") topics "" *)
+
+let header_custom template _linker archive =
+ Mustache.fold ~string ~section ~escaped:(Html.Renderer.archive archive) ~unescaped ~partial ~comment ~concat template
+ |> Html.header
+
+let header_default linker archive =
+ Html.(header [title [anchor (linker "/") [data archive.Logarion.Archive.Configuration.title]]])
+
+let meta meta =
+ let open Logarion.Meta in
+ let abstract = meta.abstract in
+ let authors = List.map (fun elt -> elt.Author.name) @@ AuthorSet.elements meta.authors in
+ let date = Date.(pretty_date @@ listing meta.date) in
+ let series = stringset_csv meta.series in
+ let topics = stringset_csv meta.topics in
+ let keywords = stringset_csv meta.keywords in
+ let uuid = Id.to_string meta.uuid in
+ Html.meta ~abstract ~authors ~date ~series ~topics ~keywords ~uuid
+
+let body_custom template note =
+ Mustache.fold ~string ~section ~escaped:(Html.Renderer.note note) ~unescaped ~partial ~comment ~concat template
+ |> Html.note
+
+let body_default note =
+ let omd = Omd.of_string note.Logarion.Note.body in
+ let aside_content =
+ let toc = Omd.toc omd in
+ match toc with [] -> [] | _ -> [ Html.heading [Html.unescaped_data "Contents"]; Html.unescaped_data @@ Omd.to_html toc ]
+ in
+ Html.note
+ [ Html.title [Html.unescaped_data note.Logarion.Note.meta.Logarion.Meta.title]; (* TODO: Don't add title if body contains one *)
+ meta note.meta;
+ Html.aside aside_content;
+ Html.unescaped_data @@ Omd.to_html omd ]
+
+let page ~style linker title header body =
+ Html.to_string @@ Html.page ~style linker title header body
+
+let of_config config k = match config with
+ | Error msg -> prerr_endline ("Couldn't load [templates] section;" ^ msg); None
+ | Ok c ->
+ let open Confix.ConfixToml in
+ path c ("templates" / k)
+
+let converter default custom = function
+ | Some p ->
+ if Confix.Config.Path.path_exists p then custom @@ of_file p
+ else (prerr_endline @@ "Couldn't find: " ^ Fpath.to_string p; default)
+ | None -> default
+
+let header_converter config = converter header_default header_custom @@ of_config config "header"
+let body_converter config = converter body_default body_custom @@ of_config config "body"
+
+let default_style = Html.default_style
+
+let page_of_index ~style linker header archive metas =
+ let topic_map =
+ let topic_map = Logarion.Meta.TopicSet.TopicMap.empty in
+ List.fold_left (fun acc elt -> Logarion.Meta.TopicSet.to_map acc elt.Logarion.Meta.topics) topic_map metas
+ in
+ let topic_roots = Logarion.Meta.TopicSet.roots topic_map in
+ page ~style linker ("Index | " ^ archive.Logarion.Archive.Configuration.title) (header linker archive)
+ Html.(main (listing_topics_recursive topic_map topic_roots metas
+ @ listing_index topic_map topic_roots "" metas) )
+
+let page_of_log ~style linker header archive metas =
+ page ~style linker ("Log | " ^ archive.Logarion.Archive.Configuration.title) (header linker archive) (Html.main [Html.listing_texts "" metas])
+
+let page_of_note ~style linker header body archive note =
+ page ~style linker note.Logarion.Note.meta.Logarion.Meta.title (header linker archive) (body note)
+
+let page_of_msg ~style linker header archive title msg =
+ page ~style linker title (header linker archive) (Html.div [Html.data msg])
diff --git a/logarion-xml.opam b/logarion-xml.opam
new file mode 100644
index 0000000..6894ed3
--- /dev/null
+++ b/logarion-xml.opam
@@ -0,0 +1,23 @@
+opam-version: "2.0"
+name: "logarion-xml"
+synopsis: "Logarion XML (HTML, Atom) converters"
+homepage: "https://logarion.orbitalfox.eu"
+dev-repo: "git://orbitalfox.eu/logarion-xml"
+bug-reports: "mailto:logarion@lists.orbitalfox.eu?subject=[Issue]"
+maintainer: "Stavros Polymenis <sp@orbitalfox.eu>"
+authors: "Stavros Polymenis <sp@orbitalfox.eu>"
+license: "EUPL"
+build: [
+ ["dune" "subst"] {pinned}
+ ["dune" "build" "-p" name "-j" jobs]
+]
+depends: [
+ "dune" {build}
+ "tyxml"
+
+ "cmdliner"
+ "bos"
+ "toml"
+ "fpath"
+ "logarion" {build}
+]