package forester

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file State.ml

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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
(*
 * SPDX-FileCopyrightText: 2024 The Forester Project Contributors
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *)

open Forester_prelude
open Forester_core

open Tree
open Forester_core
open struct module T = Types end
type resource = T.content T.resource

type t = {
  env: Eio_unix.Stdenv.base;
  dev: bool;
  config: Config.t;
  index: Tree.t URI.Tbl.t;
  diagnostics: Reporter.Message.t Asai.Diagnostic.t list URI.Tbl.t;
  graphs: (module Forest_graphs.S);
  import_graph: Forest_graph.t;
  dependency_cache: Cache.t;
  resolver: string URI.Tbl.t;
  search_index: Forester_search.Index.t;
  usages: (Tree.exports, URI.t Asai.Range.located) Hashtbl.t;
  history: Action.t list;
  hosts: (string, unit) Hashtbl.t;
  suggestions: URI.t URI.Tbl.t
}

let make
  ~(env : Eio_unix.Stdenv.base)
  ~(config : Config.t)
  ~(dev : bool)
  ?(graphs = (module Forest_graphs.Make (): Forest_graphs.S))
  ?(import_graph = Forest_graph.create ~size: 1000 ())
  ?(resolver = URI.Tbl.create 1000)
  ?(index = URI.Tbl.create 1000)
  ?(diagnostics = URI.Tbl.create 1000)
  ?(usages = Hashtbl.create 1000)
  ?(search_index = Forester_search.Index.create [])
  ?(dependency_cache = Cache.empty)
  ?(hosts = Hashtbl.create 10)
  ?(suggestions = URI.Tbl.create 1000)
  ()
= {env; dev; config; index; diagnostics; resolver; import_graph; graphs; search_index; dependency_cache; usages; hosts; suggestions; history = []}

module Syntax = struct
  let (.={}) state uri =
    URI.Tbl.find_opt state.index uri

  let (.={} <-) state uri tree =
    match state.={uri} with
    | None ->
      URI.Tbl.replace state.index uri tree
    | Some existing ->
      let o1 = Tree.origin tree in
      let o2 = Tree.origin existing in
      if o1 <> o2 then
        begin
          Reporter.emit (Duplicate_tree (o1, o2));
          URI.Tbl.replace state.index uri tree
        end
      else
        URI.Tbl.replace state.index uri tree
  (* URI.Tbl.replace state.index uri item *)

  (* / for units*)
  let (./{}) state uri =
    Option.bind
      (URI.Tbl.find_opt state.index uri)
      Tree.get_units

  (* updating units*)
  let (./{} <-) state uri units =
    let@ () = Reporter.tracef "when updating units for %a" URI.pp uri in
    match URI.Tbl.find_opt state.index uri with
    | None -> Reporter.fatal Internal_error ~extra_remarks: [Asai.Diagnostic.loctextf "Updating units: %a not found" URI.pp uri]
    | Some (Document _)
    | Some (Parsed _) ->
      Reporter.fatal Internal_error ~extra_remarks: [Asai.Diagnostic.loctextf "%a has not been expanded yet" URI.pp uri]
    | Some (Expanded expanded) ->
      URI.Tbl.replace
        state.index
        uri
        (Expanded {expanded with units})
    | Some (Resource _) -> ()

  (* ? for diagnostics*)
  let (.?{}) state uri =
    Option.value ~default: [] (URI.Tbl.find_opt state.diagnostics uri)

  let (.?{} <-) state uri diagnostics = URI.Tbl.add state.diagnostics uri diagnostics

  (* @ for article/resource *)
  let (.@{}) state uri =
    match URI.Tbl.find_opt state.index uri with
    | Some (Document _) -> None
    | Some (Parsed _)
    | Some (Expanded (_))
    | None ->
      None
    | Some (Resource res) -> Some res.resource
end

open Syntax

let update_history forest action = {forest with history = action :: forest.history}

let find_opt state uri = URI.Tbl.find_opt state.index uri
let to_seq state = URI.Tbl.to_seq state.index

let get_all_unparsed state =
  state.index
  |> URI.Tbl.to_seq_values
  |> Seq.filter is_unparsed

let get_all_code state =
  state.index
  |> URI.Tbl.to_seq_values
  |> Seq.filter_map to_code

let get_all_unexpanded state =
  state.index
  |> URI.Tbl.to_seq_values
  |> Seq.filter is_unexpanded

let get_all_expanded state =
  state.index
  |> URI.Tbl.to_seq_values
  |> Seq.filter_map to_syn

let get_all_unevaluated state =
  state.index
  |> URI.Tbl.to_seq_values
  |> Seq.filter is_unevaluated

let get_all_articles : t -> T.content T.article Seq.t = fun state ->
  state.index
  |> URI.Tbl.to_seq_values
  |> Seq.filter_map to_article

let get_all_evaluated : t -> evaluated Seq.t = fun state ->
  state.index
  |> URI.Tbl.to_seq_values
  |> Seq.filter_map to_evaluated

let get_all_resources : t -> T.content T.resource Seq.t = fun state ->
  state.index
  |> URI.Tbl.to_seq_values
  |> Seq.filter_map to_resource

let get_resource state uri =
  match state.={uri} with
  | None -> None
  | Some tree -> to_resource tree

let get_code state uri =
  match state.={uri} with
  | None -> None
  | Some tree -> to_code tree

let get_article : URI.t -> t -> T.content T.article option = fun uri forest ->
  match URI.Tbl.find_opt forest.index uri with
  | None
  | Some (Document _)
  | Some (Parsed _)
  | Some (Expanded _) ->
    None
  | Some (Resource {resource; _}) ->
    match resource with
    | T.Article a -> Some a
    | _ -> None

let section_symbol = "§"

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 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

(* A list of mistakes that a user might make when typing a given URI.
   For example, they might type "https://www.forester-notes.com/005P" instead of "https://www.forester-notes.com/005P/".
 *)
let wrong_variants_for_uri uri =
  let components = URI.path_components uri in
  match List.rev components with
  | "" :: rest ->
    [
      URI.with_path_components (List.rev rest) uri;
      URI.with_path_components (components @ ["index.html"]) uri;
      URI.with_path_components (components @ ["index.xml"]) uri
    ]
  | _ -> []

type uri_suggestion =
  | Ok
  | Not_found of {suggestion: URI.t option}

let suggestion_for_uri uri forest =
  match URI.host uri with
  | None -> Ok
  | Some host ->
    match Hashtbl.find_opt forest.hosts host with
    | None -> Ok
    | Some() ->
      match URI.Tbl.find_opt forest.index uri with
      | Some _ -> Ok
      | None -> Not_found {suggestion = URI.Tbl.find_opt forest.suggestions uri}

let plant_resource ?(route_locally = true) resource forest =
  let module Graphs = (val forest.graphs) in
  Forest.analyse_resource forest.graphs resource;
  let@ uri = Option.iter @~ T.uri_for_resource resource in
  let uri = URI.canonicalise uri in (* Seems dodgy if this isn't already canonical! *)
  Graphs.register_uri uri;
  begin
    let@ host = Option.iter @~ URI.host uri in
    Hashtbl.add forest.hosts host ()
  end;
  begin
    let@ wrong_variant = List.iter @~ wrong_variants_for_uri uri in
    URI.Tbl.add forest.suggestions wrong_variant uri
  end;
  match forest.={uri} with
  | None ->
    forest.={uri} <- Resource {resource; expanded = None; route_locally}
  | Some (Tree.Expanded syn) ->
    forest.={uri} <- Resource {resource; expanded = Some syn; route_locally}
  | _ ->
    forest.={uri} <- Resource {resource; expanded = None; route_locally}

let serialize_graphs
  : (module Forest_graphs.S) -> 'a
= fun s ->
  let module Graphs = (val s) in
  Graphs.dl_db

let batch_write : t -> _ = function
  | {import_graph; _} ->
    (* let dl_db = serialize_graphs graphs in *)
    let open Cache in
    let module Gmap = Forest_graph.Map(Cache.Dependecy_graph) in
    let tbl = Dependency_tbl.create 100 in
    let now = Unix.time () in
    let g =
      import_graph
      |> Gmap.map @@ function
          | T.Content_vertex _ ->
            (*Import graph has no content vertices*)
            assert false
          | T.Uri_vertex uri ->
            let item = Item.Tree uri in
            Dependency_tbl.add tbl item Item.{timestamp = Some now; color = Green};
            item
    in
    {Cache.empty with graph = g; tbl;}

let reconstruct = fun ~env: _ ~(_config : Config.t) paths cache ->
  match cache with
  | {search_index = _; _} ->
    (* let init = Phases.init ~env ~config ~dev: true in *)
    (* let graphs = Forest_graphs.init dl_db in *)
    paths
    |> Seq.iter (fun _path ->
        (* let uri = URI_scheme.path_to_uri ~base: config.url (Eio.Path.native_exn path) in *)
        (* match URI.Tbl.find_opt forest uri with *)
        (* | None -> () *)
        (* | Some tree -> *)
        (*   match check_timestamp path tree.timestamp with *)
        (*   | _ -> () *)
        ()
      )

let rec source_path_of_uri (uri : URI.t) (forest : t) : string option =
  let@ tree = Option.bind @@ find_opt forest uri in
  source_path_of_origin (Tree.origin tree) forest

and source_path_of_origin (origin : origin) (forest : t) : string option =
  match origin with
  | Physical document ->
    Option.some @@ Lsp.Uri.to_path @@ Lsp.Text_document.documentUri document
  | Subtree {parent} -> source_path_of_identity parent forest
  | Undefined -> None

and source_path_of_identity (identity : identity) (forest : t) : string option =
  let@ uri = Option.bind @@ identity_to_uri identity in
  source_path_of_uri uri forest
OCaml

Innovation. Community. Security.