1 | type templates_t = { header: string option; footer: string option }
|
---|
2 | type t = { templates : templates_t }
|
---|
3 |
|
---|
4 | let ext = ".htm"
|
---|
5 | let empty_templates = { header = None; footer = None }
|
---|
6 | let default_opts = { templates = empty_templates }
|
---|
7 |
|
---|
8 | let init kv =
|
---|
9 | let open Logarion in
|
---|
10 | let header = match Store.KV.find "HTM-header" kv with
|
---|
11 | | fname -> Some (File_store.to_string fname)
|
---|
12 | | exception Not_found -> None in
|
---|
13 | let footer = match Store.KV.find "HTM-footer" kv with
|
---|
14 | | fname -> Some (File_store.to_string fname)
|
---|
15 | | exception Not_found -> None in
|
---|
16 | { templates = { header; footer} }
|
---|
17 |
|
---|
18 | let wrap c htm text_title body =
|
---|
19 | let site_title = try Logarion.Store.KV.find "Title" c.Conversion.kv
|
---|
20 | with Not_found -> "" in
|
---|
21 | let replace x = let open Str in
|
---|
22 | global_replace (regexp "{{archive-title}}") site_title x
|
---|
23 | |> global_replace (regexp "{{text-title}}") text_title
|
---|
24 | in
|
---|
25 | let header = match htm.templates.header with
|
---|
26 | | Some x -> replace x
|
---|
27 | | None -> "<header><a href='.'>" ^ site_title ^
|
---|
28 | "</a><nav><a href='feed.atom' id='feed'>feed</a></nav></header>"
|
---|
29 | in
|
---|
30 | let footer = match htm.templates.footer with None -> "" | Some x -> replace x in
|
---|
31 | "<!DOCTYPE HTML><html><head><title>" ^ text_title ^ " • " ^ site_title ^ "</title>\n\
|
---|
32 | <link rel='stylesheet' href='main.css'>\
|
---|
33 | <link rel='alternate' href='feed.atom' type='application/atom+xml'>\
|
---|
34 | <meta charset='utf-8'/><meta name='viewport' content='width=device-width, initial-scale=1.0'>\
|
---|
35 | </head><body>\n" ^ header ^ body ^ footer ^ "</body></html>"
|
---|
36 |
|
---|
37 | let topic_link root topic =
|
---|
38 | let replaced_space = String.map (function ' '->'+' | x->x) in
|
---|
39 | "<a href='index." ^ replaced_space root ^ ".htm#" ^ replaced_space topic ^ "'>"
|
---|
40 | ^ String.capitalize_ascii topic ^ "</a>"
|
---|
41 |
|
---|
42 | module HtmlConverter = struct
|
---|
43 | include Converter.Html
|
---|
44 | let angled_uri u a = if String.sub u 0 10 <> "urn:txtid:" then
|
---|
45 | angled_uri u a else angled_uri (String.(sub u 10 (length u - 10)) ^ ext) a
|
---|
46 | end
|
---|
47 |
|
---|
48 | let page htm conversion text =
|
---|
49 | let open Logarion in
|
---|
50 | let open Text in
|
---|
51 | let module T = Parsers.Plain_text.Make (HtmlConverter) in
|
---|
52 | let sep_append ?(sep=", ") a x = match a,x with "",_ -> x | _, "" -> a | _ -> a ^ sep ^ x in
|
---|
53 | let opt_kv key value = if String.length value > 0
|
---|
54 | then "<dt>" ^ key ^ "<dd>" ^ value else "" in
|
---|
55 | (* let author acc auth = sep_append acc Person.(auth.name ^ " ") in*)
|
---|
56 | let authors = (Person.Set.to_string text.authors ^ " ") in
|
---|
57 | let keywords = str_set "keywords" text in
|
---|
58 | let header =
|
---|
59 | let time x = {|<time datetime="|} ^ x ^ {|">|} ^ x ^ "</time>" in
|
---|
60 | let topic_links x =
|
---|
61 | let to_linked t a =
|
---|
62 | let ts = Topic_set.of_string t in
|
---|
63 | sep_append a (List.fold_left (fun a t -> sep_append ~sep:" > " a (topic_link (List.hd ts) t)) "" ts) in
|
---|
64 | String_set.fold to_linked x "" in
|
---|
65 | "<article><header><dl>"
|
---|
66 | ^ opt_kv "Title:" text.title
|
---|
67 | ^ opt_kv "Authors:" authors
|
---|
68 | ^ opt_kv "Date: " (time (Date.(pretty_date @@ listing text.date)))
|
---|
69 | ^ opt_kv "Series: " (str_set "series" text)
|
---|
70 | ^ opt_kv "Topics: " (topic_links (set "topics" text))
|
---|
71 | ^ opt_kv "Keywords: " keywords
|
---|
72 | ^ opt_kv "Id: " text.id
|
---|
73 | ^ {|</dl></header><pre style="white-space:pre-wrap">|} in
|
---|
74 | wrap conversion htm text.title ((T.of_string text.body header) ^ "</pre></article>")
|
---|
75 |
|
---|
76 | let to_dated_links ?(limit) meta_list =
|
---|
77 | let meta_list = match limit with
|
---|
78 | | None -> meta_list
|
---|
79 | | Some limit->
|
---|
80 | let rec reduced acc i = function
|
---|
81 | | [] -> acc
|
---|
82 | | h::t -> if i < limit then reduced (h::acc) (i+1) t else acc in
|
---|
83 | List.rev @@ reduced [] 0 meta_list
|
---|
84 | in
|
---|
85 | List.fold_left
|
---|
86 | (fun a m -> Printf.sprintf "%s<li> %s <a href=\"%s.htm\">%s</a>" a
|
---|
87 | Logarion.(Date.(pretty_date (listing m.Text.date)))
|
---|
88 | (Logarion.Text.short_id m) m.Logarion.Text.title)
|
---|
89 | "" meta_list
|
---|
90 |
|
---|
91 | let date_index ?(limit) conv htm meta_list =
|
---|
92 | match limit with
|
---|
93 | | Some limit -> wrap conv htm "Index" (to_dated_links ~limit meta_list)
|
---|
94 | | None -> wrap conv htm "Index" (to_dated_links meta_list)
|
---|
95 |
|
---|
96 | let fold_topic_roots topic_roots =
|
---|
97 | let list_item root t = "<li>" ^ topic_link root t in
|
---|
98 | "<nav><h2>Main topics</h2>"
|
---|
99 | ^ List.fold_left (fun a x -> a ^ list_item x x) "<ul>" (List.rev topic_roots)
|
---|
100 | ^ "</ul></nav>"
|
---|
101 |
|
---|
102 | let fold_topics topic_map topic_roots metas =
|
---|
103 | let open Logarion in
|
---|
104 | let rec unordered_list root topic =
|
---|
105 | List.fold_left (fun a x -> a ^ list_item root x) "<ul>" topic
|
---|
106 | ^ "</ul>"
|
---|
107 | and sub_items root topic = match Topic_set.Map.find_opt topic topic_map with
|
---|
108 | | None -> ""
|
---|
109 | | Some (_, subtopics) -> unordered_list root (String_set.elements subtopics)
|
---|
110 | and list_item root t =
|
---|
111 | let item =
|
---|
112 | if List.exists (fun x -> String_set.mem t (String_set.map Topic_set.topic (Text.set "topics" x))) metas
|
---|
113 | then topic_link root t else String.capitalize_ascii t
|
---|
114 | in
|
---|
115 | "<li>" ^ item ^ sub_items root t
|
---|
116 | in
|
---|
117 | "<nav><h2>Topics</h2>"
|
---|
118 | ^ List.fold_left (fun a x -> a ^ list_item x x) "<ul>" (List.rev topic_roots)
|
---|
119 | ^ "</ul></nav>"
|
---|
120 |
|
---|
121 | let text_item path meta =
|
---|
122 | let open Logarion in
|
---|
123 | "<time>" ^ Date.(pretty_date (listing meta.Text.date))
|
---|
124 | ^ {|</time> <a href="|} ^ path ^ Text.short_id meta ^ {|.htm">|} ^ meta.Text.title
|
---|
125 | ^ "</a><br>"
|
---|
126 |
|
---|
127 | let listing_index topic_map topic_roots path metas =
|
---|
128 | let rec item_group topics =
|
---|
129 | List.fold_left (fun acc topic -> acc ^ sub_groups topic ^ items topic) "" topics
|
---|
130 | and sub_groups topic = match Logarion.Topic_set.Map.find_opt topic topic_map with
|
---|
131 | | None -> ""
|
---|
132 | | Some (_, subtopics) -> item_group (Logarion.String_set.elements subtopics)
|
---|
133 | and items topic =
|
---|
134 | let items =
|
---|
135 | let open Logarion in
|
---|
136 | List.fold_left
|
---|
137 | (fun a e ->
|
---|
138 | if String_set.mem topic (String_set.map (Logarion.Topic_set.topic) (Text.set "Topics" e))
|
---|
139 | then text_item path e ^ a else a) "" metas in
|
---|
140 | match items with
|
---|
141 | | "" -> ""
|
---|
142 | | x -> {|<h2 id="|} ^ topic ^ {|">|} ^ String.capitalize_ascii topic ^ "</h2>" ^ x
|
---|
143 | in
|
---|
144 | "<nav><h1>Texts</h1>" ^ item_group topic_roots ^ "</nav>"
|
---|
145 |
|
---|
146 | let topic_main_index conv htm topic_roots metas =
|
---|
147 | wrap conv htm "Topics"
|
---|
148 | (fold_topic_roots topic_roots
|
---|
149 | ^ "<nav><h1>Latest</h1><ul>" ^ to_dated_links ~limit:8 metas
|
---|
150 | ^ {|</ul><a href="index.date.htm">More by date</a></nav>|} )
|
---|
151 |
|
---|
152 | let topic_sub_index conv htm topic_map topic_root metas =
|
---|
153 | wrap conv htm topic_root
|
---|
154 | (fold_topics topic_map [topic_root] metas
|
---|
155 | (* ^ {|<a href=".atom" id="feed">|}^ String.capitalize_ascii topic_root ^{| feed </a>|}*)
|
---|
156 | ^ listing_index topic_map [topic_root] "" metas)
|
---|
157 |
|
---|
158 | open Logarion
|
---|
159 | let indices htm c =
|
---|
160 | let file name = Logarion.File_store.file (Filename.concat c.Conversion.dir name) in
|
---|
161 | let index_name = try Store.KV.find "HTM-index" c.Conversion.kv with Not_found -> "index.html" in
|
---|
162 | let title = try Store.KV.find "Title" c.Conversion.kv with Not_found -> "" in
|
---|
163 |
|
---|
164 | if index_name <> "" then
|
---|
165 | file index_name (topic_main_index c htm c.topic_roots c.texts);
|
---|
166 |
|
---|
167 | file "index.date.htm" (date_index c htm c.texts);
|
---|
168 |
|
---|
169 | List.iter
|
---|
170 | (fun root -> file ("index." ^ root ^ ".htm") (topic_sub_index c htm c.topics root c.texts))
|
---|
171 | c.topic_roots;
|
---|
172 |
|
---|
173 | let base_url = try
|
---|
174 | let _i = Str.(search_forward (regexp "https?://[^;]*") (Store.KV.find "Locations" c.kv) 0) in
|
---|
175 | Str.(matched_string (Store.KV.find "Locations" c.kv))
|
---|
176 | with Not_found -> prerr_endline "Missing location for HTTP(S)"; "" in
|
---|
177 | file "feed.atom" (Atom.feed title c.id base_url "text/html" c.texts)
|
---|