1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
open Forester_prelude
open Forester_core
open struct
module T = Types
module Dx = Datalog_expr
end
include URI.Tbl
type article = T.content T.article
type env = (module Forest_graphs.S)
let execute_datalog_script graphs script =
let module Graphs = (val graphs : Forest_graphs.S) in
let@ sequent = List.iter @~ script in
Datalog_engine.db_add Graphs.dl_db (Datalog_eval.eval_sequent sequent)
let run_datalog_query (graphs : env) (q : (string, Vertex.t) Dx.query) : Vertex_set.t =
let@ () = Reporter.trace "when running query" in
let () = execute_datalog_script graphs Builtin_relation.axioms in
let module Graphs = (val graphs) in
Datalog_eval.run_query Graphs.dl_db q
let add_edge graphs rel ~source ~target =
let module Graphs = (val graphs : Forest_graphs.S) in
let premises = [] in
let conclusion =
let args = [Dx.Const source; Dx.Const target] in
Dx.{rel; args}
in
execute_datalog_script graphs [{conclusion; premises}]
let add_fact graphs rel node =
let module Graphs = (val graphs : Forest_graphs.S) in
let premises = [] in
let conclusion =
let args = [Dx.Const node] in
Dx.{rel; args}
in
execute_datalog_script graphs [{conclusion; premises}]
let rec analyse_content_node graphs (scope : URI.t) (node : 'a T.content_node) : unit =
match node with
| Text _ | CDATA _ | Route_of_uri _ | Uri _ | Results_of_datalog_query _ | Contextual_number _ -> ()
| Transclude transclusion ->
analyse_transclusion graphs scope transclusion
| Xml_elt elt ->
begin
let@ attr = List.iter @~ elt.attrs in
analyse_content graphs scope attr.value
end;
analyse_content graphs scope elt.content
| Section section ->
analyse_section graphs scope section
| Link link ->
add_edge graphs Builtin_relation.links_to ~source: (Uri_vertex scope) ~target: (Uri_vertex link.href);
analyse_content graphs scope link.content
| KaTeX (_, content) ->
analyse_content graphs scope content
| Artefact artefact ->
analyse_artefact graphs scope artefact
| Datalog_script script ->
execute_datalog_script graphs script
and analyse_artefact graphs scope artefact =
analyse_content graphs scope artefact.content
and analyse_transclusion graphs (scope : URI.t) (transclusion : T.transclusion) : unit =
match transclusion.target with
| Full _ | Mainmatter ->
add_edge graphs Builtin_relation.transcludes ~source: (Uri_vertex scope) ~target: (Uri_vertex transclusion.href)
| Title _ | Taxon -> ()
and analyse_content (graphs : env) (scope : URI.t) (content : T.content) : unit =
T.extract_content content |> List.iter @@ analyse_content_node graphs scope
and analyse_attribution graphs (scope : URI.t) (attr : _ T.attribution) =
let rel =
match attr.role with
| Author -> Builtin_relation.has_author
| Contributor -> Builtin_relation.has_direct_contributor
in
add_edge graphs rel ~source: (Uri_vertex scope) ~target: attr.vertex;
analyse_vertex graphs scope attr.vertex
and analyse_vertex graphs scope vtx =
match vtx with
| Uri_vertex _ -> ()
| Content_vertex content -> analyse_content graphs scope content
and analyse_tag graphs (scope : URI.t) (tag : _ T.vertex) =
analyse_vertex graphs scope tag;
add_edge graphs Builtin_relation.has_tag ~source: (Uri_vertex scope) ~target: tag
and analyse_taxon graphs (scope : URI.t) (taxon_opt : T.content option) =
let@ taxon = Option.iter @~ taxon_opt in
analyse_content graphs scope taxon;
add_edge graphs Builtin_relation.has_taxon ~source: (Uri_vertex scope) ~target: (Content_vertex taxon)
and analyse_attributions graphs (scope : URI.t) =
List.iter @@ analyse_attribution graphs scope
and analyse_tags graphs (scope : URI.t) =
List.iter @@ analyse_tag graphs scope
and analyse_frontmatter graphs (scope : URI.t) (fm : T.content T.frontmatter) : unit =
Option.iter (analyse_content graphs scope) fm.title;
analyse_taxon graphs scope fm.taxon;
analyse_attributions graphs scope fm.attributions;
analyse_tags graphs scope fm.tags;
analyse_metas graphs scope fm.metas
and analyse_metas graphs (scope : URI.t) =
List.iter @@ analyse_meta graphs scope
and analyse_meta graphs (scope : URI.t) (_, content) : unit =
analyse_content graphs scope content
and analyse_section graphs (scope : URI.t) (section : T.content T.section) : unit =
begin
let@ target = Option.iter @~ section.frontmatter.uri in
add_edge graphs Builtin_relation.transcludes ~source: (Uri_vertex scope) ~target: (Uri_vertex target)
end;
let scope = Option.value ~default: scope section.frontmatter.uri in
analyse_frontmatter graphs scope section.frontmatter;
analyse_content graphs scope section.mainmatter
let analyse_article graphs (article : article) : unit =
let@ scope = Option.iter @~ article.frontmatter.uri in
add_fact graphs Builtin_relation.is_article (T.Uri_vertex scope);
analyse_frontmatter graphs scope article.frontmatter;
analyse_content graphs scope article.mainmatter;
analyse_content graphs scope article.backmatter
let analyse_asset graphs (asset : T.asset) : unit =
add_fact graphs Builtin_relation.is_asset (T.Uri_vertex asset.uri)
let analyse_resource graphs = function
| T.Article article -> analyse_article graphs article
| T.Asset asset -> analyse_asset graphs asset
| _ -> ()
let get_article
: URI.t -> _ t -> T.content T.article option
= fun uri forest ->
match find_opt forest uri with
| None -> None
| Some (T.Asset _) ->
Logs.debug (fun m -> m "%a is an asset, not an article" URI.pp uri);
None
| Some (T.Syndication _) ->
Logs.debug (fun m -> m "%a is a syndication, not an article" URI.pp uri);
None
| Some (T.Article article) -> Some article
let rec get_expanded_title ?scope ?(flags = T.{empty_when_untitled = false}) (frontmatter : _ T.frontmatter) forest =
let short_title =
match frontmatter.title with
| Some content -> content
| None when not flags.empty_when_untitled ->
begin
match frontmatter.uri with
| Some uri -> T.Content [T.Uri uri]
| _ -> T.Content [T.Text "Untitled"]
end
| _ -> T.Content []
in
Option.value ~default: short_title @@
match frontmatter.designated_parent with
| Some parent_uri when not (Option.equal URI.equal scope frontmatter.designated_parent) ->
let@ parent = Option.map @~ get_article parent_uri forest in
let parent_title = get_expanded_title parent.frontmatter forest in
let parent_link = T.Link {href = parent_uri; content = parent_title} in
let chevron = T.Text " › " in
T.map_content (fun xs -> parent_link :: chevron :: xs) short_title
| _ -> None
let section_symbol = "§"
let get_content_of_transclusion (transclusion : T.transclusion) forest =
match transclusion.target with
| Full flags ->
let@ article = Option.map @~ get_article transclusion.href forest in
T.Content [T.Section (T.article_to_section article ~flags)]
| Mainmatter ->
let@ article = Option.map @~ get_article transclusion.href forest in
article.mainmatter
| Title flags ->
Option.some @@
begin
match get_article transclusion.href forest with
| None -> T.Content [T.Uri transclusion.href]
| Some article -> get_expanded_title ~flags article.frontmatter forest
end
| Taxon ->
let@ article = Option.map @~ get_article transclusion.href forest in
let default = T.Content [T.Text section_symbol] in
Option.value ~default article.frontmatter.taxon
let get_title_or_content_of_vertex ?(not_found = fun _ -> None) vertex forest =
match vertex with
| T.Content_vertex content -> Some content
| T.Uri_vertex uri ->
begin
match get_article uri forest with
| Some article -> article.frontmatter.title
| None -> not_found uri
end
let get_all_articles resources =
let extract_article = function
| T.Article a -> Some a
| _ -> None
in
resources
|> to_seq_values
|> Seq.filter_map extract_article
|> List.of_seq
let get_all_assets resources =
let = function
| T.Asset a -> Some a
| _ -> None
in
resources
|> to_seq_values
|> Seq.filter_map extract_asset
|> List.of_seq
let get_all_resources resources =
resources
|> to_seq_values
|> List.of_seq