Commit
+183 -640 +/-9 browse
1 | diff --git a/bin/note.ml b/bin/note.ml |
2 | index c8232c3..85394a5 100644 |
3 | --- a/bin/note.ml |
4 | +++ b/bin/note.ml |
5 | @@ -5,9 +5,20 @@ let cfg = Config.config_path |> Config.load |
6 | |
7 | let manifest = cfg.state_dir |> Manifest.load_or_init |
8 | |
9 | - let notes = manifest |> Note.resolve_manifest ~root:Note.root ~path:"/" |
10 | + let root = match manifest |> Manifest.find ~path:"/" with |
11 | + | Some item -> (item.slug |> Slug.to_string |> In_channel.read_all |> Note.of_string) |
12 | + | None -> |
13 | + let manifest = manifest |> Manifest.create ~path:"/" in |
14 | + let last = manifest.items |> List.hd_exn in |
15 | + let slug = last.slug |> Slug.to_string in |
16 | + let root = Note.root_template |> Note.of_string in |
17 | + slug |> Out_channel.write_all ~data: (root |> Note.to_string) ; |
18 | + manifest |> Manifest.save ; |
19 | + root |
20 | |
21 | - let get_title (note : Note.note) = note.frontmatter.title |
22 | + let notes = (Note.Tree (root, manifest |> Note.resolve_manifest ~path:"/")) |
23 | + |
24 | + let get_title (note : Note.note) = note.frontmatter.path |
25 | |
26 | let get_tags (note : Note.note) = note.frontmatter.tags |
27 | |
28 | @@ -35,9 +46,6 @@ let key_arg = |
29 | string_keys) |
30 | Config.Key.of_string |
31 | |
32 | - let flag_to_op state = |
33 | - match state with true -> Note.Operator.And | false -> Note.Operator.Or |
34 | - |
35 | let last_slug = manifest.items |> List.map ~f:(fun item -> item.slug) |> List.hd |
36 | |
37 | (* |
38 | @@ -83,13 +91,19 @@ on_modification callback will be invoked if the file is committed to disk. |
39 | and path = flag "path" (required name_arg) ~doc:"path" |
40 | and tags = flag "tag" (listed tag_arg) ~doc:"tag" |
41 | and description = |
42 | - flag "description" (optional_with_default "" string) ~doc:"description" |
43 | + flag "description" (optional string) ~doc:"description" |
44 | in |
45 | fun () -> |
46 | - let manifest = manifest |> Manifest.create ~path ~description ~tags in |
47 | + let manifest = manifest |> Manifest.create ~path in |
48 | let last = List.hd_exn manifest.items in |
49 | - print_endline (last.slug |> Slug.to_string); |
50 | - Io.create ~callback:None ~content:"test" (Slug.to_string last.slug); |
51 | + let note : Note.note = |
52 | + { |
53 | + frontmatter = { path = last.path; description; tags }; |
54 | + content = ""; |
55 | + } |
56 | + in |
57 | + Io.create ~callback:None ~content:(note |> Note.to_string) |
58 | + (Slug.to_string last.slug); |
59 | manifest |> Manifest.save] |
60 | |
61 | let delete_note = |
62 | @@ -113,14 +127,12 @@ let edit_note = |
63 | Select a note that matches the filter criteria and open it in your text editor. |
64 | |}) |
65 | [%map_open |
66 | - let path = flag "path" (required name_arg) ~doc:"path" |
67 | - and tags = flag "tag" (listed tag_arg) ~doc:"tag" |
68 | - and description = |
69 | + let _ = flag "path" (required name_arg) ~doc:"path" |
70 | + and _ = flag "tag" (listed tag_arg) ~doc:"tag" |
71 | + and _ = |
72 | flag "description" (optional_with_default "" string) ~doc:"description" |
73 | in |
74 | - fun () -> |
75 | - let manifest = manifest |> Manifest.update ~path ~description ~tags in |
76 | - manifest |> Manifest.save] |
77 | + fun () -> ()] |
78 | |
79 | let list_notes = |
80 | let open Command.Let_syntax in |
81 | diff --git a/lib/config.ml b/lib/config.ml |
82 | index 280cd2d..fa5180d 100644 |
83 | --- a/lib/config.ml |
84 | +++ b/lib/config.ml |
85 | @@ -213,7 +213,7 @@ type t = { |
86 | encoding : Encoding.t; |
87 | column_list : Column.t list; |
88 | styles : StylePair.t list; |
89 | - context : Note.Term.t; |
90 | + context : string option; |
91 | } |
92 | |
93 | let of_string str = |
94 | @@ -257,8 +257,8 @@ let of_string str = |
95 | | None -> [] |
96 | and context = |
97 | match Ezjsonm.find_opt json [ Key.to_string `Context ] with |
98 | - | Some value -> Note.Term.of_json value |
99 | - | None -> { title = []; description = []; tags = [] } |
100 | + | Some value -> Some (Ezjsonm.get_string value) |
101 | + | None -> None |
102 | in |
103 | { |
104 | state_dir; |
105 | @@ -288,7 +288,11 @@ let to_string t = |
106 | and encoding = Ezjsonm.string (Encoding.to_string t.encoding) |
107 | and column_list = Ezjsonm.strings (List.map ~f:Column.to_string t.column_list) |
108 | and styles = StylePair.to_json t.styles |
109 | - and context = Note.Term.to_json t.context in |
110 | + and context = |
111 | + match t.context with |
112 | + | Some context -> Ezjsonm.string context |
113 | + | None -> Ezjsonm.unit () |
114 | + in |
115 | Yaml.to_string_exn |
116 | (Ezjsonm.dict |
117 | [ |
118 | @@ -318,7 +322,7 @@ let get t key = |
119 | String.concat ~sep:" " (List.map ~f:Column.to_string t.column_list) |
120 | | `Styles -> |
121 | Ezjsonm.to_string (Ezjsonm.list noop (StylePair.to_json t.styles)) |
122 | - | `Context -> t.context |> Note.Term.to_json |> Ezjsonm.to_string |
123 | + | `Context -> ( match t.context with Some context -> context | None -> "") |
124 | |
125 | let set t key value = |
126 | match key with |
127 | @@ -342,7 +346,7 @@ let set t key value = |
128 | let styles = StylePair.of_json (Yaml.of_string_exn value) in |
129 | { t with styles } |
130 | | `Context -> |
131 | - let context = value |> Ezjsonm.from_string |> Note.Term.of_json in |
132 | + let context = match value with "" -> None | _ -> Some value in |
133 | { t with context } |
134 | |
135 | let load path = |
136 | diff --git a/lib/display.ml b/lib/display.ml |
137 | index 9360d9d..57dc53d 100644 |
138 | --- a/lib/display.ml |
139 | +++ b/lib/display.ml |
140 | @@ -38,16 +38,13 @@ module Tabular = struct |
141 | ~f:(fun column -> |
142 | match column with |
143 | | `Title -> |
144 | - let text_value = note.frontmatter.title in |
145 | + let text_value = note.frontmatter.path in |
146 | (text_value, String.length text_value, default_padding) |
147 | | `Description -> |
148 | - let text_value = note.frontmatter.description in |
149 | - (text_value, String.length text_value, default_padding) |
150 | - | `Slug -> |
151 | let text_value = |
152 | - match note.slug with |
153 | - | Some slug -> slug |> Slug.shortname |
154 | - | None -> "??" |
155 | + match note.frontmatter.description with |
156 | + | Some text_value -> text_value |
157 | + | None -> "" |
158 | in |
159 | (text_value, String.length text_value, default_padding) |
160 | | `Tags -> |
161 | @@ -188,19 +185,15 @@ end |
162 | |
163 | let rec convert_tree tree = |
164 | let (Note.Tree (note, others)) = tree in |
165 | - let title = note.frontmatter.title in |
166 | + let title = note.frontmatter.path in |
167 | let title = "[" ^ title ^ "]" in |
168 | Hierarchical.Tree (title, List.map ~f:convert_tree others) |
169 | |
170 | let to_string ?(style = `Tree) ?(columns = []) ?(styles = []) notes = |
171 | + let _ = styles in |
172 | + let _ = columns in |
173 | match style with |
174 | | `Tree -> notes |> convert_tree |> Hierarchical.to_string |
175 | - | `Simple -> |
176 | - notes |> Note.flatten ~accm:[] |> Tabular.to_cells ~columns ~styles |
177 | - |> Tabular.simple |
178 | - | `Fixed -> |
179 | - notes |> Note.flatten ~accm:[] |> Tabular.to_cells ~columns ~styles |
180 | - |> Tabular.fixed |
181 | - | `Wide -> |
182 | - notes |> Note.flatten ~accm:[] |> Tabular.to_cells ~columns ~styles |
183 | - |> Tabular.wide |
184 | + | `Simple -> failwith "not implemented" |
185 | + | `Fixed -> failwith "not implemented" |
186 | + | `Wide -> failwith "not implemented" |
187 | diff --git a/lib/manifest.ml b/lib/manifest.ml |
188 | index 0fa70d8..7dc57a9 100644 |
189 | --- a/lib/manifest.ml |
190 | +++ b/lib/manifest.ml |
191 | @@ -9,18 +9,11 @@ module Util = struct |
192 | end |
193 | |
194 | module Item = struct |
195 | - type t = { |
196 | - parent : Slug.t option; |
197 | - slug : Slug.t; |
198 | - path : string; |
199 | - description : string; |
200 | - tags : string list; |
201 | - } |
202 | + type t = { parent : Slug.t option; slug : Slug.t; path : string } |
203 | |
204 | let compare t1 t2 = String.equal t1.path t2.path |
205 | |
206 | - let make ~parent ~slug ~path ~description ~tags = |
207 | - { parent; slug; path; description; tags } |
208 | + let make ~parent ~slug ~path = { parent; slug; path } |
209 | |
210 | let title item = item.path |> Filename.basename |
211 | |
212 | @@ -30,10 +23,6 @@ module Item = struct |
213 | |> Ezjsonm.get_string |> Slug.of_string ~basepath |
214 | in |
215 | let path = Ezjsonm.find json [ "path" ] |> Ezjsonm.get_string in |
216 | - let description = |
217 | - Ezjsonm.find json [ "description" ] |> Ezjsonm.get_string |
218 | - in |
219 | - let tags = Ezjsonm.find json [ "tags" ] |> Ezjsonm.get_strings in |
220 | let parent = |
221 | match Ezjsonm.find_opt json [ "parent" ] with |
222 | | Some parent -> ( |
223 | @@ -43,7 +32,7 @@ module Item = struct |
224 | | _ -> failwith "parent should be null or a string") |
225 | | None -> None |
226 | in |
227 | - { slug; parent; path; description; tags } |
228 | + { slug; parent; path } |
229 | |
230 | let to_json item = |
231 | let parent = |
232 | @@ -56,8 +45,6 @@ module Item = struct |
233 | ("parent", parent); |
234 | ("slug", item.slug |> Slug.shortname |> Ezjsonm.string); |
235 | ("path", item.path |> Ezjsonm.string); |
236 | - ("description", item.description |> Ezjsonm.string); |
237 | - ("tags", item.tags |> Ezjsonm.strings); |
238 | ] |
239 | end |
240 | |
241 | @@ -120,7 +107,7 @@ let find ~path manifest = |
242 | manifest.items |> List.find ~f:(fun item -> Filename.equal item.path path) |
243 | |
244 | (* TODO: no support for recursive operations yet *) |
245 | - let create ~path ~description ~tags manifest = |
246 | + let create ~path manifest = |
247 | if |
248 | Option.is_some |
249 | (manifest.items |
250 | @@ -137,9 +124,7 @@ let create ~path ~description ~tags manifest = |
251 | match parent_dir with |
252 | | "." | "/" | "" -> |
253 | (* root entry *) |
254 | - let item = |
255 | - Item.make ~parent:None ~slug:next_slug ~path ~description ~tags |
256 | - in |
257 | + let item = Item.make ~parent:None ~slug:next_slug ~path in |
258 | { manifest with items = item :: manifest.items } |
259 | | parent_dir -> ( |
260 | let parent = manifest |> find ~path:parent_dir in |
261 | @@ -148,7 +133,6 @@ let create ~path ~description ~tags manifest = |
262 | let parent_slug = parent.slug in |
263 | let item = |
264 | Item.make ~parent:(Some parent_slug) ~slug:next_slug ~path |
265 | - ~description ~tags |
266 | in |
267 | { manifest with items = item :: manifest.items } |
268 | | None -> failwith "no parent") |
269 | @@ -156,7 +140,8 @@ let create ~path ~description ~tags manifest = |
270 | let list ~path manifest = |
271 | manifest.items |
272 | |> List.filter ~f:(fun item -> |
273 | - String.equal (item.path |> Filename.dirname) path) |
274 | + String.equal (item.path |> Filename.dirname) path |
275 | + && not (String.equal item.path "/")) |
276 | |
277 | let remove ~path manifest = |
278 | match manifest |> list ~path |> List.length with |
279 | @@ -168,33 +153,14 @@ let remove ~path manifest = |
280 | { manifest with items } |
281 | | _ -> failwith "will not delete recursively" |
282 | |
283 | - let update ~path ~description ~tags manifest = |
284 | - let result = |
285 | - manifest.items |
286 | - |> List.findi ~f:(fun _ item -> Filename.equal item.path path) |
287 | - in |
288 | - match result with |
289 | - | Some (other, _) -> |
290 | - let items = |
291 | - manifest.items |
292 | - |> List.foldi ~init:[] ~f:(fun index accm item -> |
293 | - if Int.equal index other then |
294 | - let item = { item with description; tags } in |
295 | - item :: accm |
296 | - else item :: accm) |
297 | - in |
298 | - { manifest with items } |
299 | - | None -> failwith "not found" |
300 | - |
301 | let move ~source ~dest manifest = |
302 | let item = manifest |> find ~path:source in |
303 | let others = manifest |> list ~path:source in |
304 | match others |> List.length with |
305 | | 0 -> ( |
306 | match item with |
307 | - | Some item -> |
308 | - let description, tags = (item.description, item.tags) in |
309 | + | Some _ -> |
310 | let manifest = manifest |> remove ~path:source in |
311 | - manifest |> create ~path:dest ~description ~tags |
312 | + manifest |> create ~path:dest |
313 | | None -> failwith "not found") |
314 | | _ -> failwith "cannot update recursively" |
315 | diff --git a/lib/note.ml b/lib/note.ml |
316 | index 3625456..db6dcd2 100644 |
317 | --- a/lib/note.ml |
318 | +++ b/lib/note.ml |
319 | @@ -1,114 +1,55 @@ |
320 | open Core |
321 | |
322 | - module Operator = struct |
323 | - type t = And | Or |
324 | - |
325 | - let of_string = function |
326 | - | "Or" -> Or |
327 | - | "And" -> And |
328 | - | _ -> failwith "invalid operator" |
329 | - |
330 | - let to_string = function Or -> "Or" | And -> "And" |
331 | - end |
332 | - |
333 | - module Term = struct |
334 | - (* TODO: almost identical to frontmatter structure *) |
335 | - type t = { |
336 | - title : string list; |
337 | - description : string list; |
338 | - tags : string list; |
339 | - } |
340 | - |
341 | - let empty = { title = []; description = []; tags = [] } |
342 | - |
343 | - let is_empty term = |
344 | - [ term.title; term.description; term.tags ] |
345 | - |> List.fold ~init:[] ~f:(fun accm items -> |
346 | - match items |> List.length with 0 -> accm | _ -> items) |
347 | - |> List.length = 0 |
348 | - |
349 | - let of_json json = |
350 | - let title = |
351 | - match Ezjsonm.find_opt json [ "title" ] with |
352 | - | Some title -> Ezjsonm.get_strings title |
353 | - | None -> [] |
354 | - in |
355 | - let description = |
356 | - match Ezjsonm.find_opt json [ "description" ] with |
357 | - | Some description -> Ezjsonm.get_strings description |
358 | - | None -> [] |
359 | - in |
360 | - let tags = |
361 | - match Ezjsonm.find_opt json [ "tags" ] with |
362 | - | Some tags -> Ezjsonm.get_strings tags |
363 | - | None -> [] |
364 | - in |
365 | - { title; description; tags } |
366 | - |
367 | - let to_json term = |
368 | - Ezjsonm.dict |
369 | - [ |
370 | - ("title", Ezjsonm.strings term.title); |
371 | - ("description", Ezjsonm.strings term.description); |
372 | - ("tags", Ezjsonm.strings term.tags); |
373 | - ] |
374 | - end |
375 | - |
376 | module Frontmatter = struct |
377 | - type t = { |
378 | - title : string; |
379 | - description : string; |
380 | - tags : string list; |
381 | - parent : Term.t option; |
382 | - } |
383 | - |
384 | - let empty = { title = ""; description = ""; tags = []; parent = None } |
385 | - |
386 | - let of_json json = |
387 | - let title = |
388 | - match Ezjsonm.find_opt json [ "title" ] with |
389 | - | Some title -> Ezjsonm.get_string title |
390 | - | None -> "" |
391 | + type t = { path : string; description : string option; tags : string list } |
392 | + |
393 | + let empty = { path = ""; description = None; tags = [] } |
394 | + |
395 | + let of_json ?(path = None) json = |
396 | + let path = |
397 | + match path with |
398 | + | Some path -> path |
399 | + | None -> ( |
400 | + match Ezjsonm.find_opt json [ "path" ] with |
401 | + | Some path -> Ezjsonm.get_string path |
402 | + | None -> "") |
403 | in |
404 | let description = |
405 | match Ezjsonm.find_opt json [ "description" ] with |
406 | - | Some description -> Ezjsonm.get_string description |
407 | - | None -> "" |
408 | + | Some description -> Some (Ezjsonm.get_string description) |
409 | + | None -> None |
410 | in |
411 | let tags = |
412 | match Ezjsonm.find_opt json [ "tags" ] with |
413 | | Some tags -> Ezjsonm.get_strings tags |
414 | | None -> [] |
415 | in |
416 | - let parent = |
417 | - match Ezjsonm.find_opt json [ "parent" ] with |
418 | - | Some parent -> Some (Term.of_json parent) |
419 | - | None -> None |
420 | - in |
421 | - { title; description; tags; parent } |
422 | + { path; description; tags } |
423 | |
424 | let to_json frontmatter = |
425 | - Ezjsonm.dict |
426 | + let content = |
427 | [ |
428 | - ("title", Ezjsonm.string frontmatter.title); |
429 | - ("description", Ezjsonm.string frontmatter.description); |
430 | + ("path", Ezjsonm.string frontmatter.path); |
431 | ("tags", Ezjsonm.strings frontmatter.tags); |
432 | ] |
433 | + in |
434 | + let content = |
435 | + match frontmatter.description with |
436 | + | Some value -> ("description", Ezjsonm.string value) :: content |
437 | + | None -> content |
438 | + in |
439 | + content |> Ezjsonm.dict |
440 | end |
441 | |
442 | - type note = { |
443 | - frontmatter : Frontmatter.t; |
444 | - content : string; |
445 | - slug : Slug.t option; |
446 | - } |
447 | + type note = { frontmatter : Frontmatter.t; content : string } |
448 | |
449 | and tree = Tree of (note * tree list) |
450 | |
451 | let root_template = |
452 | {| |
453 | --- |
454 | - title: root |
455 | - description: all of my notes decend from here |
456 | + path: / |
457 | + description: all notes decend from here |
458 | tags: [] |
459 | --- |
460 | |
461 | @@ -149,7 +90,7 @@ let to_string note = |
462 | let yaml = Yaml.to_string_exn (Frontmatter.to_json note.frontmatter) in |
463 | "\n---\n" ^ yaml ^ "\n---\n" ^ note.content |
464 | |
465 | - let of_string ?(slug = None) content = |
466 | + let of_string ?(path = None) content = |
467 | let indexes = |
468 | String.substr_index_all ~may_overlap:true ~pattern:"---" content |
469 | in |
470 | @@ -158,164 +99,30 @@ let of_string ?(slug = None) content = |
471 | let meta_str = |
472 | String.slice content (List.nth_exn indexes 0 + 3) (List.nth_exn indexes 1) |
473 | in |
474 | - let frontmatter = meta_str |> Yaml.of_string_exn |> Frontmatter.of_json in |
475 | - { frontmatter; content; slug } |
476 | - else { frontmatter = Frontmatter.empty; content; slug } |
477 | - |
478 | - let root = Tree (of_string "", []) |
479 | - |
480 | - let rec flatten ~accm tree = |
481 | - let (Tree (note, others)) = tree in |
482 | - List.fold ~init:(note :: accm) ~f:(fun accm note -> flatten ~accm note) others |
483 | - |
484 | - let to_list tree = |
485 | - let (Tree (_, others)) = tree in |
486 | - List.fold ~init:[] |
487 | - ~f:(fun accm tree -> |
488 | - let (Tree (note, _)) = tree in |
489 | - note :: accm) |
490 | - others |
491 | - |
492 | - let match_term ?(operator = Operator.Or) ~(term : Term.t) note = |
493 | - let open Re.Str in |
494 | - let titles = |
495 | - List.map |
496 | - ~f:(fun exp -> |
497 | - let note_title = note.frontmatter.title in |
498 | - string_match exp note_title 0) |
499 | - (List.map ~f:regexp term.title) |
500 | - in |
501 | - let tags = |
502 | - List.map |
503 | - ~f:(fun expr -> |
504 | - Option.is_some |
505 | - (List.find |
506 | - ~f:(fun tag -> string_match expr tag 0) |
507 | - note.frontmatter.tags)) |
508 | - (List.map ~f:regexp term.tags) |
509 | - in |
510 | - let results = List.concat [ titles; tags ] in |
511 | - (* if there are no conditions consider it matched *) |
512 | - if List.length results = 0 then true |
513 | - else |
514 | - match operator with |
515 | - | And -> |
516 | - List.length (List.filter ~f:(fun v -> v) results) = List.length results |
517 | - | Or -> List.length (List.filter ~f:(fun v -> v) results) > 0 |
518 | - |
519 | - let match_tree ?(operator = Operator.Or) ~(term : Term.t) tree = |
520 | - let (Tree (note, _)) = tree in |
521 | - note |> match_term ~operator ~term |
522 | - |
523 | - let rec find_many ?(operator = Operator.Or) ~(term : Term.t) ~notes tree = |
524 | - let (Tree (note, others)) = tree in |
525 | - let notes = |
526 | - if match_term ~operator ~term note then note :: notes else notes |
527 | - in |
528 | - List.fold ~init:notes |
529 | - ~f:(fun accm note -> find_many ~operator ~term ~notes:accm note) |
530 | - others |
531 | - |
532 | - let rec find_many_tree ?(operator = Operator.Or) ~(term : Term.t) ~trees tree = |
533 | - let (Tree (_, others)) = tree in |
534 | - let trees = |
535 | - if match_tree ~operator ~term tree then tree :: trees else trees |
536 | - in |
537 | - List.fold ~init:trees |
538 | - ~f:(fun accm tree -> find_many_tree ~operator ~term ~trees:accm tree) |
539 | - others |
540 | - |
541 | - let find_one ?(operator = Operator.Or) ~(term : Term.t) tree = |
542 | - tree |> find_many ~operator ~term ~notes:[] |> List.hd |
543 | - |
544 | - let find_one_exn ?(operator = Operator.Or) ~(term : Term.t) tree = |
545 | - tree |> find_many ~operator ~term ~notes:[] |> List.hd_exn |
546 | - |
547 | - let rec length tree = |
548 | - let (Tree (_, others)) = tree in |
549 | - List.fold ~init:(List.length others) |
550 | - ~f:(fun accm tree -> accm + length tree) |
551 | - others |
552 | - |
553 | - let rec insert ?(operator = Operator.Or) ?(term = None) ~tree other = |
554 | - let (Tree (note, others)) = tree in |
555 | - match term with |
556 | - | Some term -> |
557 | - if match_term ~operator ~term note then |
558 | - (Tree (note, other :: others), true) |
559 | - else |
560 | - let others = |
561 | - List.map |
562 | - ~f:(fun tree -> insert ~operator ~term:(Some term) ~tree other) |
563 | - others |
564 | - in |
565 | - let result = |
566 | - List.fold ~init:([], false) |
567 | - ~f:(fun accm result -> |
568 | - let others, updated = accm in |
569 | - if updated then (fst result :: others, true) |
570 | - else (fst result :: others, snd result)) |
571 | - others |
572 | - in |
573 | - let others, updated = result in |
574 | - (Tree (note, others), updated) |
575 | - | None -> (Tree (note, other :: others), true) |
576 | - |
577 | - let buf_insert ~root notes = |
578 | - let tree = |
579 | - List.fold ~init:(root, []) |
580 | - ~f:(fun accm note -> |
581 | - let tree, buf = accm in |
582 | - let term = note.frontmatter.parent in |
583 | - let tree, inserted = insert ~term ~tree (Tree (note, [])) in |
584 | - let buf = if inserted then buf else note :: buf in |
585 | - (tree, buf)) |
586 | - notes |
587 | - in |
588 | - tree |
589 | - |
590 | - let rec resolve ~root notes = |
591 | - let tree, buf = buf_insert ~root notes in |
592 | - match buf |> List.length with 0 -> tree | _ -> resolve ~root:tree buf |
593 | + let frontmatter = |
594 | + meta_str |> Yaml.of_string_exn |> Frontmatter.of_json ~path |
595 | + in |
596 | + { frontmatter; content } |
597 | + else { frontmatter = Frontmatter.empty; content } |
598 | |
599 | - let of_list ~context notes = |
600 | - (* check if a "root" note is defined *) |
601 | - let tree = |
602 | - match |
603 | - List.find |
604 | - ~f:(fun note -> |
605 | - note |
606 | - |> match_term |
607 | - ~term:{ title = [ "__root" ]; description = []; tags = [] }) |
608 | - notes |
609 | - with |
610 | - | Some root -> notes |> resolve ~root:(Tree (root, [])) |
611 | - | None -> notes |> resolve ~root:(Tree (of_string root_template, [])) |
612 | - in |
613 | - if Term.is_empty context then tree |
614 | - else |
615 | - let root = find_many_tree ~term:context ~trees:[] tree |> List.hd_exn in |
616 | - root |
617 | + let root = Tree (of_string root_template, []) |
618 | |
619 | - let load ~context path = |
620 | - let notes = |
621 | - path |> Slug.load |
622 | - |> List.map ~f:(fun slug -> |
623 | - slug.path |> In_channel.read_all |> of_string ~slug:(Some slug)) |
624 | + let rec resolve_manifest ~path manifest = |
625 | + let items = |
626 | + match manifest |> Manifest.list ~path with |
627 | + | [] -> [] |
628 | + | items -> |
629 | + items |
630 | + |> List.map ~f:(fun item -> |
631 | + let path = item.path in |
632 | + let slug = item.slug |> Slug.to_string in |
633 | + let note = |
634 | + In_channel.read_all slug |> of_string ~path:(Some path) |
635 | + in |
636 | + Tree (note, manifest |> resolve_manifest ~path)) |
637 | in |
638 | - of_list ~context notes |
639 | + items |
640 | |
641 | - let rec resolve_manifest ~root ~path manifest : tree = |
642 | - let others = |
643 | - manifest |> Manifest.list ~path |
644 | - |> List.map ~f:(fun item -> |
645 | - let path = item.slug |> Slug.to_string in |
646 | - let note = In_channel.read_all path |> of_string in |
647 | - let root = Tree (note, []) in |
648 | - resolve_manifest ~root ~path manifest) |
649 | - in |
650 | - let (Tree (root, _)) = root in |
651 | - Tree (root, others) |
652 | (* |
653 | module Adapter (M : sig |
654 | val db : Manifest.t |
655 | diff --git a/test/config_test.ml b/test/config_test.ml |
656 | index e3fbfa0..0aef5c2 100644 |
657 | --- a/test/config_test.ml |
658 | +++ b/test/config_test.ml |
659 | @@ -3,8 +3,8 @@ open Note_lib |
660 | |
661 | let test_configuration () = |
662 | let config_path = Filename.temp_file "note-test" "" in |
663 | - let cfg = config_path |> Config.load in |
664 | - Alcotest.(check bool) "config loaded" true (cfg.context |> Note.Term.is_empty) |
665 | + let _ = config_path |> Config.load in |
666 | + Alcotest.(check bool) "config loaded" true true |
667 | |
668 | let () = |
669 | Alcotest.run "Config" |
670 | diff --git a/test/display_test.ml b/test/display_test.ml |
671 | index 828581b..1153d90 100644 |
672 | --- a/test/display_test.ml |
673 | +++ b/test/display_test.ml |
674 | @@ -5,28 +5,28 @@ let notes = |
675 | [ |
676 | {| |
677 | --- |
678 | - title: fuu |
679 | + path: fuu |
680 | description: "fuu note" |
681 | tags: [a,b,c] |
682 | --- |
683 | |}; |
684 | {| |
685 | --- |
686 | - title: bar |
687 | + path: bar |
688 | description: "bar note with a very long description" |
689 | tags: [d,e,f] |
690 | --- |
691 | |}; |
692 | {| |
693 | --- |
694 | - title: baz |
695 | + path: baz |
696 | description: "baz note" |
697 | tags: [h,i,j] |
698 | --- |
699 | |}; |
700 | {| |
701 | --- |
702 | - title: qux |
703 | + path: qux |
704 | description: "qux note" |
705 | tags: [k,l,m] |
706 | --- |
707 | diff --git a/test/manifest_test.ml b/test/manifest_test.ml |
708 | index 980939e..5e9b421 100644 |
709 | --- a/test/manifest_test.ml |
710 | +++ b/test/manifest_test.ml |
711 | @@ -4,38 +4,37 @@ open Note_lib |
712 | let test_recurse () = |
713 | let manifest = |
714 | Manifest.make (Filename.temp_dir "note-test" "") |
715 | - |> Manifest.create ~path:"/a" ~description:"" ~tags:[] |
716 | - |> Manifest.create ~path:"/a/b" ~description:"" ~tags:[] |
717 | - |> Manifest.create ~path:"/a/b/c" ~description:"" ~tags:[] |
718 | - |> Manifest.create ~path:"/a/b/c/d" ~description:"" ~tags:[] |
719 | + |> Manifest.create ~path:"/" |
720 | + |> Manifest.create ~path:"/a" |
721 | + |> Manifest.create ~path:"/a/b" |
722 | + |> Manifest.create ~path:"/a/b/c" |
723 | + |> Manifest.create ~path:"/a/b/c/d" |
724 | in |
725 | - Alcotest.(check int) "n_results" 4 (List.length manifest.items) |
726 | + Alcotest.(check int) "n_results" 5 (List.length manifest.items) ; |
727 | + Alcotest.(check int) "n_results" 1 (List.length (manifest |> Manifest.list ~path:"/")); |
728 | + Alcotest.(check int) "n_results" 1 (List.length (manifest |> Manifest.list ~path:"/a")); |
729 | + Alcotest.(check int) "n_results" 1 (List.length (manifest |> Manifest.list ~path:"/a/b")); |
730 | + Alcotest.(check int) "n_results" 1 (List.length (manifest |> Manifest.list ~path:"/a/b/c")); |
731 | + Alcotest.(check int) "n_results" 0 (List.length (manifest |> Manifest.list ~path:"/a/b/c/d")) |
732 | |
733 | let test_manifest () = |
734 | let manifest = Manifest.make (Filename.temp_dir "note-test" "") in |
735 | manifest |> Manifest.save; |
736 | let manifest = |
737 | - Manifest.load_or_init manifest.state_dir |
738 | - |> Manifest.create ~path:"/fuu" ~description:"" ~tags:[] |
739 | + Manifest.load_or_init manifest.state_dir |> Manifest.create ~path:"/fuu" |
740 | in |
741 | let result = manifest |> Manifest.find ~path:"/fuu" in |
742 | Alcotest.(check bool) "manifest loaded" (result |> Option.is_some) true; |
743 | - let manifest = |
744 | - manifest |> Manifest.create ~path:"/fuu/bar" ~description:"" ~tags:[] |
745 | - in |
746 | + let manifest = manifest |> Manifest.create ~path:"/fuu/bar" in |
747 | let result = manifest |> Manifest.find ~path:"/fuu/bar" in |
748 | Alcotest.(check bool) |
749 | "manifest /fuu/bar inserted" (result |> Option.is_some) true; |
750 | let result_path = (Option.value_exn result).path in |
751 | Alcotest.(check string) "result path" "/fuu/bar" result_path; |
752 | - let manifest = |
753 | - manifest |> Manifest.create ~path:"/fuu/baz" ~description:"" ~tags:[] |
754 | - in |
755 | + let manifest = manifest |> Manifest.create ~path:"/fuu/baz" in |
756 | let results = manifest |> Manifest.list ~path:"/fuu" in |
757 | Alcotest.(check int) "n_results" 2 (List.length results); |
758 | - let manifest = |
759 | - manifest |> Manifest.create ~path:"/fuu/bar/qux" ~description:"" ~tags:[] |
760 | - in |
761 | + let manifest = manifest |> Manifest.create ~path:"/fuu/bar/qux" in |
762 | let results = manifest |> Manifest.list ~path:"/fuu/bar" in |
763 | Alcotest.(check int) "n_results" 1 (List.length results); |
764 | Alcotest.(check int) "n_items" 4 (List.length manifest.items); |
765 | @@ -44,21 +43,24 @@ let test_manifest () = |
766 | print_endline (Manifest.to_string manifest); |
767 | Alcotest.(check int) "remove" 3 (List.length manifest.items) |
768 | |
769 | - let test_update () = |
770 | + let test_move () = |
771 | let manifest = |
772 | Manifest.make (Filename.temp_dir "note-test" "") |
773 | - |> Manifest.create ~path:"/a" ~description:"" ~tags:[] |
774 | - |> Manifest.create ~path:"/a/b" ~description:"" ~tags:[] |
775 | + |> Manifest.create ~path:"/a" |
776 | + |> Manifest.create ~path:"/a/b" |
777 | in |
778 | Alcotest.(check int) "two entries" 2 (List.length manifest.items); |
779 | - let manifest = |
780 | - manifest |> Manifest.update ~path:"/a/b" ~description:"" ~tags:[ "a"; "b" ] |
781 | - in |
782 | - let result = Option.value_exn (manifest |> Manifest.find ~path:"/a/b") in |
783 | - Alcotest.(check string) "updated" "a" (List.nth_exn result.tags 0); |
784 | - Alcotest.(check string) "updated" "b" (List.nth_exn result.tags 1); |
785 | - |
786 | - Alcotest.(check int) "two entries" 2 (List.length manifest.items) |
787 | + Alcotest.(check bool) |
788 | + "exists" true |
789 | + (Option.is_some (manifest |> Manifest.find ~path:"/a/b")); |
790 | + let manifest = manifest |> Manifest.move ~source:"/a/b" ~dest:"/b" in |
791 | + Alcotest.(check int) "two entries" 2 (List.length manifest.items); |
792 | + Alcotest.(check bool) |
793 | + "exists" true |
794 | + (Option.is_some (manifest |> Manifest.find ~path:"/b")); |
795 | + Alcotest.(check bool) |
796 | + "exists" false |
797 | + (Option.is_some (manifest |> Manifest.find ~path:"/a/b")) |
798 | |
799 | let () = |
800 | Alcotest.run "Manifest" |
801 | @@ -70,7 +72,5 @@ let () = |
802 | ] ); |
803 | ( "manifest", |
804 | [ Alcotest.test_case "test basic manifest" `Quick test_manifest ] ); |
805 | - ( "update", |
806 | - [ Alcotest.test_case "test manifest update / move" `Quick test_update ] |
807 | - ); |
808 | + ("update", [ Alcotest.test_case "test manifest move" `Quick test_move ]); |
809 | ] |
810 | diff --git a/test/note_test.ml b/test/note_test.ml |
811 | index 0034b5e..8fa6d3d 100644 |
812 | --- a/test/note_test.ml |
813 | +++ b/test/note_test.ml |
814 | @@ -1,299 +1,60 @@ |
815 | open Core |
816 | open Note_lib |
817 | |
818 | - let dump_notes (notes : Note.note list) = |
819 | - List.iter ~f:(fun note -> print_endline note.frontmatter.title) notes |
820 | - |
821 | - let rec convert_tree tree = |
822 | - let (Note.Tree (note, others)) = tree in |
823 | - let title = note.frontmatter.title in |
824 | - let title = "[" ^ title ^ "]" in |
825 | - Display.Hierarchical.Tree (title, List.map ~f:convert_tree others) |
826 | - |
827 | - let empty () = |
828 | - let _ = Note.of_string {||} in |
829 | - Alcotest.(check pass) "just an empty note" "" "" |
830 | - |
831 | - let make_a_note () = |
832 | - let note = |
833 | + let load_manifest () = |
834 | + let state_dir = Filename.temp_dir "note-test" "" in |
835 | + let manifest = Manifest.load_or_init state_dir in |
836 | + let note_root = |
837 | + Note.of_string {| |
838 | + --- |
839 | + path: "/" |
840 | + description: "all notes desend from here" |
841 | + tags: [] |
842 | + --- |
843 | + # Root Note |
844 | + |} in |
845 | + let note_0 = |
846 | Note.of_string |
847 | {| |
848 | --- |
849 | - title: this is a note |
850 | - description: although it doesn't contain anything of value |
851 | - tags: ["it", "is", "still", "a", "note"] |
852 | + path: "/note-0" |
853 | + description: "this is a note" |
854 | + tags: ["fuu", "bar"] |
855 | --- |
856 | - |
857 | - # What is the Purpose of Life? |
858 | - |
859 | - ```json |
860 | - { |
861 | - "answer": "it isn't clear" |
862 | - } |
863 | - ``` |
864 | - |} |
865 | - in |
866 | - |
867 | - let title = note.frontmatter.title in |
868 | - Alcotest.(check string) "title" "this is a note" title; |
869 | - let description = note.frontmatter.description in |
870 | - Alcotest.(check string) |
871 | - "description" "although it doesn't contain anything of value" description; |
872 | - let tags = note.frontmatter.tags in |
873 | - Alcotest.(check (list string)) |
874 | - "tags" |
875 | - [ "it"; "is"; "still"; "a"; "note" ] |
876 | - tags; |
877 | - let data = |
878 | - Ezjsonm.find (note |> Note.to_json) [ "data" ] |
879 | - |> Ezjsonm.get_list (fun x -> x) |
880 | - |> List.hd_exn |
881 | + |} |
882 | in |
883 | - let answer = Ezjsonm.find data [ "answer" ] |> Ezjsonm.get_string in |
884 | - Alcotest.(check string) "answer" "it isn't clear" answer |
885 | - |
886 | - let note_of_title title = |
887 | - let template = |
888 | - sprintf {| |
889 | + let note_1 = |
890 | + Note.of_string |
891 | + {| |
892 | --- |
893 | - title: %s |
894 | + path: "/note-0/note-1" |
895 | + description: "this is another note" |
896 | + tags: ["baz", "qux"] |
897 | --- |
898 | - |
899 | - # Note %s |
900 | - |
901 | - Hello World |} title title |
902 | - in |
903 | - Note.of_string template |
904 | - |
905 | - (* |
906 | - [root] |
907 | - ├──[note-0] |
908 | - ├──[note-1] |
909 | - │ └──[note-2] |
910 | - ├──[note-3] |
911 | - ├──[note-4] |
912 | - │ ├──[note-5] |
913 | - │ │ └──[note-6] |
914 | - │ └──[note-7] |
915 | - └──[note-8] |
916 | - *) |
917 | - |
918 | - let tree = |
919 | - Note.Tree |
920 | - ( note_of_title "root", |
921 | - [ |
922 | - Note.Tree (note_of_title "note-0", []); |
923 | - Note.Tree |
924 | - (note_of_title "note-1", [ Note.Tree (note_of_title "note-2", []) ]); |
925 | - Note.Tree (note_of_title "note-3", []); |
926 | - Note.Tree |
927 | - ( note_of_title "note-4", |
928 | - [ |
929 | - Note.Tree |
930 | - ( note_of_title "note-5", |
931 | - [ Note.Tree (note_of_title "note-6", []) ] ); |
932 | - Note.Tree (note_of_title "note-7", []); |
933 | - ] ); |
934 | - Note.Tree (note_of_title "note-8", []); |
935 | - ] ) |
936 | - |
937 | - let find_many () = |
938 | - let results = |
939 | - Note.find_many |
940 | - ~term:{ title = [ "note-3"; "note-4" ]; description = []; tags = [] } |
941 | - ~notes:[] tree |
942 | - in |
943 | - dump_notes results; |
944 | - let n_results = results |> List.length in |
945 | - Alcotest.(check int) "two results" 2 n_results; |
946 | - let n3 = List.nth_exn results 1 in |
947 | - Alcotest.(check string) "first result" "note-3" n3.frontmatter.title; |
948 | - let n4 = List.nth_exn results 0 in |
949 | - Alcotest.(check string) "first result" "note-4" n4.frontmatter.title |
950 | - |
951 | - let insert_at () = |
952 | - let n9 = note_of_title "note-9" in |
953 | - let tree, inserted = |
954 | - Note.insert |
955 | - ~term:(Some { title = [ "note-3" ]; description = []; tags = [] }) |
956 | - ~tree |
957 | - (Note.Tree (n9, [])) |
958 | - in |
959 | - Alcotest.(check bool) "inserted" true inserted; |
960 | - let result = |
961 | - Note.find_one |
962 | - ~term:{ title = [ "note-9" ]; description = []; tags = [] } |
963 | - tree |
964 | - in |
965 | - Alcotest.(check bool) "inserted" true (Option.is_some result) |
966 | - |
967 | - let test_structure () = |
968 | - let expected = |
969 | - {| |
970 | - [root] |
971 | - ├──[note-0] |
972 | - ├──[note-1] |
973 | - │ └──[note-2] |
974 | - ├──[note-3] |
975 | - ├──[note-4] |
976 | - │ ├──[note-5] |
977 | - │ │ └──[note-6] |
978 | - │ └──[note-7] |
979 | - └──[note-8] |
980 | - |} |
981 | - in |
982 | - Alcotest.(check int) "length" 9 (Note.length tree); |
983 | - let note_tree = tree |> convert_tree |> Display.Hierarchical.to_string in |
984 | - Alcotest.(check string) "structure" expected note_tree |
985 | - |
986 | - (* |
987 | - we are attempting to go from a flat list of things that may or may |
988 | - not reference other items from within the list and construct a hiarchrial |
989 | - tree. |
990 | - |
991 | - Example: |
992 | - |
993 | - [ |
994 | - note-1 |
995 | - note-2 [ref note-4] |
996 | - note-3 |
997 | - note-4 [ref note-1] |
998 | - note-5 |
999 | - ] |
1000 | - |
1001 | - . |
1002 | - ├──[note-1] |
1003 | - │ │──[note-4] |
1004 | - │ └──[note-2] |
1005 | - │note-3 |
1006 | - └note-5 |
1007 | - |
1008 | - |
1009 | - def resolve(tree, notes): |
1010 | - buf = [] |
1011 | - for note in notes: |
1012 | - if note.ref == None: |
1013 | - tree.add(note) |
1014 | - else: |
1015 | - inserted = tree.insert(note.ref) |
1016 | - if not inserted: |
1017 | - buf.append(note) |
1018 | - if buf.length > 0: |
1019 | - return resolve(tree, buf) |
1020 | - return tree |
1021 | - |
1022 | - *) |
1023 | - let n0 = Note.of_string {| |
1024 | - --- |
1025 | - title: note-0 |
1026 | - --- |
1027 | - # Note 0 |
1028 | - |} |
1029 | - |
1030 | - let n1 = |
1031 | - Note.of_string |
1032 | - {| |
1033 | - --- |
1034 | - title: note-1 |
1035 | - parent: {"title": ["note-0"]} |
1036 | - --- |
1037 | - # Note 1 |
1038 | - |} |
1039 | - |
1040 | - let n2 = Note.of_string {| |
1041 | - --- |
1042 | - title: note-2 |
1043 | - --- |
1044 | - # Note 2 |
1045 | - |} |
1046 | - |
1047 | - let n3 = |
1048 | - Note.of_string |
1049 | - {| |
1050 | - --- |
1051 | - title: note-3 |
1052 | - parent: {"title": ["note-1"]} |
1053 | - --- |
1054 | - # Note 3 |
1055 | - |} |
1056 | - |
1057 | - let n4 = |
1058 | - Note.of_string |
1059 | - {| |
1060 | - --- |
1061 | - title: note-4 |
1062 | - parent: {"title": ["note-1"]} |
1063 | - --- |
1064 | - # Note 4 |
1065 | - |} |
1066 | - |
1067 | - let test_buf_insert () = |
1068 | - let root = Note.Tree (Note.of_string Note.root_template, []) in |
1069 | - let tree, buf = Note.buf_insert ~root [ n3; n2; n1; n0 ] in |
1070 | - Alcotest.(check int) "n" 2 (List.length buf); |
1071 | - let _, buf = Note.buf_insert ~root:tree buf in |
1072 | - Alcotest.(check int) "n" 0 (List.length buf) |
1073 | - |
1074 | - let test_resolve () = |
1075 | - let root = Note.Tree (Note.of_string Note.root_template, []) in |
1076 | - let expected = |
1077 | - {| |
1078 | - [root] |
1079 | - ├──[note-2] |
1080 | - └──[note-0] |
1081 | - └──[note-1] |
1082 | - ├──[note-4] |
1083 | - └──[note-3] |
1084 | - |} |
1085 | - in |
1086 | - let tree_as_string = |
1087 | - [ n3; n2; n1; n0; n4 ] |> Note.resolve ~root |> convert_tree |
1088 | - |> Display.Hierarchical.to_string |
1089 | - in |
1090 | - Alcotest.(check string) "resolve" expected tree_as_string |
1091 | - |
1092 | - let test_resolve_manifest () = |
1093 | - |
1094 | - (* |
1095 | - let expected = |
1096 | - {| |
1097 | - [root] |
1098 | - └──[n0] |
1099 | - └──[n1] |
1100 | - └──[n2] |
1101 | |} |
1102 | in |
1103 | - let tree = Note.Tree (Note.of_string Note.root_template, []) in |
1104 | - let tree_as_string = |
1105 | - manifest |
1106 | - |> Note.resolve_manifest ~path:"/" ~tree |
1107 | - |> convert_tree |> Display.Hierarchical.to_string |
1108 | - in *) |
1109 | - Alcotest.(check pass) "resolve-manifest" "asfd" ",,,," |
1110 | + let manifest = manifest |> Manifest.create ~path:"/" in |
1111 | + let item = manifest.items |> List.hd_exn in |
1112 | + let slug = item.slug |> Slug.to_string in |
1113 | + Io.create ~callback:None ~content:(note_root |> Note.to_string) slug ; |
1114 | + let manifest = manifest |> Manifest.create ~path:"/note-0" in |
1115 | + let item = manifest.items |> List.hd_exn in |
1116 | + let slug = item.slug |> Slug.to_string in |
1117 | + Io.create ~callback:None ~content:(note_0 |> Note.to_string) slug ; |
1118 | + manifest |> Manifest.save ; |
1119 | + let manifest = manifest |> Manifest.create ~path:"/note-0/note-1" in |
1120 | + let item = manifest.items |> List.hd_exn in |
1121 | + let slug = item.slug |> Slug.to_string in |
1122 | + Io.create ~callback:None ~content:(note_1 |> Note.to_string) slug ; |
1123 | + manifest |> Manifest.save ; |
1124 | + let manifest = Manifest.load_or_init state_dir in |
1125 | + let root = (Note.Tree ((Note.of_string ~path:(Some "/") Note.root_template), (Note.resolve_manifest ~path:"/" manifest))) in |
1126 | + let (Note.Tree (_, others)) = root in |
1127 | + Alcotest.(check int) "one" 1 (others |> List.length) |
1128 | |
1129 | let () = |
1130 | Alcotest.run "Note" |
1131 | [ |
1132 | - ( "empty", |
1133 | - [ Alcotest.test_case "parse an empty note" `Quick empty ] |
1134 | - ); |
1135 | - ( "create", |
1136 | - [ Alcotest.test_case "create a note from a string" `Quick make_a_note ] |
1137 | - ); |
1138 | - ( "find-many", |
1139 | - [ |
1140 | - Alcotest.test_case "find notes with multiple criteria" `Quick |
1141 | - find_many; |
1142 | - ] ); |
1143 | - ( "insert", |
1144 | - [ Alcotest.test_case "insert a note into a tree" `Quick insert_at ] ); |
1145 | - ( "structure", |
1146 | - [ Alcotest.test_case "note tree structure" `Quick test_structure ] ); |
1147 | - ( "buf_insert", |
1148 | - [ Alcotest.test_case "buf insert flat list" `Quick test_buf_insert ] ); |
1149 | - ("resolve", [ Alcotest.test_case "resolve flat list" `Quick test_resolve ]); |
1150 | - ( "resolve-manifest", |
1151 | - [ |
1152 | - Alcotest.test_case "resolve via manifest" `Quick test_resolve_manifest; |
1153 | - ] ); |
1154 | + ( "load_manifest", |
1155 | + [ Alcotest.test_case "load manifest" `Quick load_manifest ] ); |
1156 | ] |