Author: Kevin Schoon [kevinschoon@gmail.com]
Hash: c90ec24f990575d958d868ab6b2b88addad6e4e2
Timestamp: Tue, 29 Jun 2021 19:34:58 +0000 (3 years ago)

+306 -317 +/-7 browse
WIP convert to manifest based system
1diff --git a/bin/note.ml b/bin/note.ml
2index e73d549..c8232c3 100644
3--- a/bin/note.ml
4+++ b/bin/note.ml
5 @@ -3,29 +3,9 @@ open Note_lib
6
7 let cfg = Config.config_path |> Config.load
8
9- let context = cfg.context
10+ let manifest = cfg.state_dir |> Manifest.load_or_init
11
12- module Encoding = struct
13- let to_string ~style (note : Note.note) =
14- match style with
15- | `Raw -> note.content
16- | `Json -> Ezjsonm.to_string (Note.to_json note)
17- | `Yaml -> Yaml.to_string_exn (Note.to_json note)
18- | `Html -> note.content |> Omd.of_string |> Omd.to_html
19- end
20-
21- let note_of_title title =
22- sprintf {|
23- ---
24- title: "%s"
25- ---
26-
27- # %s
28- |} title title |> Note.of_string
29-
30- let get_notes =
31- let notes = cfg.state_dir |> Note.load ~context |> Note.flatten ~accm:[] in
32- notes
33+ let notes = manifest |> Note.resolve_manifest ~root:Note.root ~path:"/"
34
35 let get_title (note : Note.note) = note.frontmatter.title
36
37 @@ -36,14 +16,14 @@ let to_keys ~kind notes =
38 | `Title -> List.map ~f:get_title notes
39 | `Tags -> List.concat (List.map ~f:get_tags notes)
40
41- let search_arg kind =
42+ let name_arg =
43 Command.Arg_type.create
44- ~complete:(fun _ ~part ->
45- let notes = get_notes in
46- List.filter_map
47- ~f:(fun key ->
48- if String.is_substring ~substring:part key then Some key else None)
49- (to_keys ~kind notes))
50+ ~complete:(fun _ ~part -> [ part ])
51+ (fun filter -> filter)
52+
53+ let tag_arg =
54+ Command.Arg_type.create
55+ ~complete:(fun _ ~part -> [ part ])
56 (fun filter -> filter)
57
58 let key_arg =
59 @@ -58,75 +38,15 @@ let key_arg =
60 let flag_to_op state =
61 match state with true -> Note.Operator.And | false -> Note.Operator.Or
62
63- let column_list_arg =
64- Command.Arg_type.create (fun value ->
65- List.map ~f:Config.Column.of_string (String.split ~on:',' value))
66-
67- let encoding_arg =
68- Command.Arg_type.create
69- ~complete:(fun _ ~part ->
70- let string_keys =
71- List.map ~f:Config.Encoding.to_string Config.Encoding.all
72- in
73- List.filter
74- ~f:(fun key -> String.is_substring ~substring:part key)
75- string_keys)
76- Config.Encoding.of_string
77-
78- let list_style_arg =
79- Command.Arg_type.create
80- ~complete:(fun _ ~part ->
81- let string_keys =
82- List.map ~f:Config.ListStyle.to_string Config.ListStyle.all
83- in
84- List.filter
85- ~f:(fun key -> String.is_substring ~substring:part key)
86- string_keys)
87- Config.ListStyle.of_string
88-
89- let term_args =
90- let open Command.Let_syntax in
91- [%map_open
92- let title =
93- flag "title"
94- (listed (search_arg `Title))
95- ~doc:"regular expression matching the note title"
96- and tags =
97- flag "tag"
98- (listed (search_arg `Tags))
99- ~doc:"sequence of regular expressions matching note tags"
100- in
101- let term : Note.Term.t = { title; description = []; tags } in
102- term]
103+ let last_slug = manifest.items |> List.map ~f:(fun item -> item.slug) |> List.hd
104
105 (*
106 * commands
107 *)
108
109 let cat_note =
110- let open Command.Let_syntax in
111- Command.basic ~summary:"write notes to stdout"
112- ~readme:(fun () ->
113- {|
114- Write one or more notes to stdout. By default the cat command will write every
115- note to stdout as plain text however the encoding can be adjusted to yaml or
116- json for consumption by other tools.
117- |})
118- [%map_open
119- let term = term_args
120- and encoding =
121- flag "encoding"
122- (optional_with_default cfg.encoding encoding_arg)
123- ~doc:"format [json | yaml | raw] (default: raw)"
124- in
125- fun () ->
126- let notes =
127- cfg.state_dir |> Note.load ~context |> Note.find_many ~term ~notes:[]
128- in
129- List.iter
130- ~f:(fun note ->
131- print_endline (Encoding.to_string ~style:encoding note))
132- notes]
133+ Command.basic ~summary:"show the current configuration"
134+ (Command.Param.return (fun () -> ()))
135
136 let config_show =
137 Command.basic ~summary:"show the current configuration"
138 @@ -157,25 +77,20 @@ Create a new note and save it to disk in your configured state_dir. The
139 on_modification callback will be invoked if the file is committed to disk.
140 |})
141 [%map_open
142- let open_stdin =
143+ let _ =
144 flag "stdin" (optional bool)
145 ~doc:"read content from stdin and copy it into the note body"
146- and title = anon ("title" %: string) in
147+ and path = flag "path" (required name_arg) ~doc:"path"
148+ and tags = flag "tag" (listed tag_arg) ~doc:"tag"
149+ and description =
150+ flag "description" (optional_with_default "" string) ~doc:"description"
151+ in
152 fun () ->
153- let slug = Slug.next cfg.state_dir in
154- match open_stdin with
155- | Some _ ->
156- (* reading from stdin so write directly to note *)
157- let note =
158- In_channel.stdin |> In_channel.input_all |> Note.of_string
159- in
160- slug.path
161- |> Io.create ~callback:cfg.on_modification
162- ~content:(Note.to_string note)
163- | None ->
164- let content = title |> note_of_title |> Note.to_string in
165- Io.create_on_change ~callback:cfg.on_modification ~editor:cfg.editor
166- ~content slug.path]
167+ let manifest = manifest |> Manifest.create ~path ~description ~tags in
168+ let last = List.hd_exn manifest.items in
169+ print_endline (last.slug |> Slug.to_string);
170+ Io.create ~callback:None ~content:"test" (Slug.to_string last.slug);
171+ manifest |> Manifest.save]
172
173 let delete_note =
174 let open Command.Let_syntax in
175 @@ -185,15 +100,10 @@ let delete_note =
176 Delete the first note that matches the filter criteria.
177 |})
178 [%map_open
179- let term = term_args in
180+ let path = flag "path" (required name_arg) ~doc:"path" in
181 fun () ->
182- let note = cfg.state_dir |> Note.load ~context |> Note.find_one ~term in
183- match note with
184- | Some note ->
185- (Option.value_exn note.slug).path
186- |> Io.delete ~callback:cfg.on_modification
187- ~title:note.frontmatter.title
188- | None -> failwith "not found"]
189+ let manifest = manifest |> Manifest.remove ~path in
190+ manifest |> Manifest.save]
191
192 let edit_note =
193 let open Command.Let_syntax in
194 @@ -203,14 +113,14 @@ let edit_note =
195 Select a note that matches the filter criteria and open it in your text editor.
196 |})
197 [%map_open
198- let term = term_args in
199+ let path = flag "path" (required name_arg) ~doc:"path"
200+ and tags = flag "tag" (listed tag_arg) ~doc:"tag"
201+ and description =
202+ flag "description" (optional_with_default "" string) ~doc:"description"
203+ in
204 fun () ->
205- let note = cfg.state_dir |> Note.load ~context |> Note.find_one ~term in
206- match note with
207- | Some note ->
208- (Option.value_exn note.slug).path
209- |> Io.edit ~callback:cfg.on_modification ~editor:cfg.editor
210- | None -> failwith "not found"]
211+ let manifest = manifest |> Manifest.update ~path ~description ~tags in
212+ manifest |> Manifest.save]
213
214 let list_notes =
215 let open Command.Let_syntax in
216 @@ -221,25 +131,33 @@ List one or more notes that match the filter criteria, if no filter criteria
217 is provided then all notes will be listed.
218 |})
219 [%map_open
220- let _ = term_args
221- and style =
222- flag "style"
223- (optional_with_default cfg.list_style list_style_arg)
224- ~doc:"list style [fixed | wide | simple]"
225- and columns =
226- flag "columns"
227- (optional_with_default cfg.column_list column_list_arg)
228- ~doc:"columns to include in output"
229- in
230+ let _ = anon (sequence ("path" %: string)) in
231 fun () ->
232- let notes = cfg.state_dir |> Note.load ~context in
233- let styles = cfg.styles in
234- notes |> Display.to_string ~style ~columns ~styles |> print_endline]
235+ notes |> Display.convert_tree |> Display.Hierarchical.to_string
236+ |> print_endline
237+ (*
238+ let items =
239+ match paths |> List.length with
240+ | 0 -> [ manifest.items ]
241+ | _ ->
242+ paths |> List.map ~f:(fun path -> manifest |> Manifest.list ~path)
243+ in
244+ items
245+ |> List.iter ~f:(fun items ->
246+ items
247+ |> List.iter ~f:(fun item ->
248+ print_endline
249+ (item |> Manifest.Item.to_json |> Ezjsonm.to_string)))
250+ *)]
251
252 let sync =
253 Command.basic ~summary:"sync notes to a remote server"
254 (Command.Param.return (fun () -> Sync.sync cfg.on_sync))
255
256+ let serve =
257+ Command.basic ~summary:"serve notes from an http server"
258+ (Command.Param.return (fun () -> ()))
259+
260 let version =
261 match Build_info.V1.version () with
262 | None -> "n/a"
263 @@ -259,4 +177,5 @@ let run =
264 ("edit", edit_note);
265 ("ls", list_notes);
266 ("sync", sync);
267+ ("serve", serve);
268 ])
269 diff --git a/lib/manifest.ml b/lib/manifest.ml
270index 7fa4592..0fa70d8 100644
271--- a/lib/manifest.ml
272+++ b/lib/manifest.ml
273 @@ -10,19 +10,26 @@ end
274
275 module Item = struct
276 type t = {
277- parent : string option;
278- slug : string;
279- title : string;
280+ parent : Slug.t option;
281+ slug : Slug.t;
282+ path : string;
283 description : string;
284 tags : string list;
285 }
286
287- let make ~parent ~slug ~title ~description ~tags =
288- { parent; slug; title; description; tags }
289+ let compare t1 t2 = String.equal t1.path t2.path
290
291- let of_json json =
292- let slug = Ezjsonm.find json [ "slug" ] |> Ezjsonm.get_string in
293- let title = Ezjsonm.find json [ "title" ] |> Ezjsonm.get_string in
294+ let make ~parent ~slug ~path ~description ~tags =
295+ { parent; slug; path; description; tags }
296+
297+ let title item = item.path |> Filename.basename
298+
299+ let of_json ?(basepath = None) json =
300+ let slug =
301+ Ezjsonm.find json [ "slug" ]
302+ |> Ezjsonm.get_string |> Slug.of_string ~basepath
303+ in
304+ let path = Ezjsonm.find json [ "path" ] |> Ezjsonm.get_string in
305 let description =
306 Ezjsonm.find json [ "description" ] |> Ezjsonm.get_string
307 in
308 @@ -32,169 +39,162 @@ module Item = struct
309 | Some parent -> (
310 match parent with
311 | `Null -> None
312- | `String name -> Some name
313+ | `String name -> Some (name |> Slug.of_string)
314 | _ -> failwith "parent should be null or a string")
315 | None -> None
316 in
317- { slug; parent; title; description; tags }
318+ { slug; parent; path; description; tags }
319
320 let to_json item =
321 let parent =
322 match item.parent with
323- | Some parent -> parent |> Ezjsonm.string
324+ | Some parent -> parent |> Slug.shortname |> Ezjsonm.string
325 | None -> Ezjsonm.unit ()
326 in
327 Ezjsonm.dict
328 [
329 ("parent", parent);
330- ("slug", item.slug |> Ezjsonm.string);
331- ("title", item.title |> Ezjsonm.string);
332+ ("slug", item.slug |> Slug.shortname |> Ezjsonm.string);
333+ ("path", item.path |> Ezjsonm.string);
334 ("description", item.description |> Ezjsonm.string);
335 ("tags", item.tags |> Ezjsonm.strings);
336 ]
337 end
338
339- type t = { items : Item.t list }
340+ type t = { state_dir : string; items : Item.t list }
341+
342+ let make state_dir = { state_dir; items = [] }
343
344- let empty = { items = [] }
345+ let empty = { state_dir = ""; items = [] }
346
347- let of_json json =
348+ let of_json ?(state_dir = None) json =
349 let items =
350 Ezjsonm.find json [ "items" ]
351- |> Ezjsonm.get_list (fun item -> item |> Item.of_json)
352+ |> Ezjsonm.get_list (fun item -> item |> Item.of_json ~basepath:state_dir)
353 in
354- { items }
355+ let state_dir =
356+ match state_dir with Some state_dir -> state_dir | None -> "/"
357+ in
358+ { state_dir; items }
359
360 let to_json manifest =
361 let items = Ezjsonm.list Item.to_json manifest.items in
362 Ezjsonm.dict [ ("items", items) ]
363
364- let of_string manifest = manifest |> Ezjsonm.from_string |> of_json
365+ let of_string ?(state_dir = None) manifest =
366+ manifest |> Ezjsonm.from_string |> of_json ~state_dir
367
368 let to_string manifest = manifest |> to_json |> Ezjsonm.to_string
369
370- let lock path =
371- match path |> Sys.file_exists with
372+ let lockfile manifest = Filename.concat manifest.state_dir "note.lock"
373+
374+ let mpath manifest = Filename.concat manifest.state_dir "manifest.json"
375+
376+ let lock manifest =
377+ let lockfile = manifest |> lockfile in
378+ match lockfile |> Sys.file_exists with
379 | `Yes -> failwith "unable to aquire lock"
380- | `No | `Unknown -> Out_channel.write_all ~data:"<locked>" path
381+ | `No | `Unknown -> Out_channel.write_all ~data:"<locked>" lockfile
382
383- let unlock path =
384- match path |> Sys.file_exists with
385- | `Yes -> Sys.remove path
386+ let unlock manifest =
387+ let lockfile = manifest |> lockfile in
388+ match lockfile |> Sys.file_exists with
389+ | `Yes -> Sys.remove lockfile
390 | `No | `Unknown -> ()
391
392- let lockfile path = Filename.concat (path |> Filename.dirname) "note.lock"
393-
394- let load_or_init path =
395- match Sys.file_exists path with
396- | `Yes -> path |> In_channel.read_all |> of_string
397+ let load_or_init state_dir =
398+ let mpath = Filename.concat state_dir "manifest.json" in
399+ match Sys.file_exists mpath with
400+ | `Yes ->
401+ mpath |> In_channel.read_all |> of_string ~state_dir:(Some state_dir)
402 | `No | `Unknown ->
403- path |> Out_channel.write_all ~data:(to_string empty);
404- empty
405-
406- let save ~path manifest =
407- path |> lockfile |> lock;
408- Out_channel.write_all ~data:(to_string manifest) path;
409- path |> lockfile |> unlock
410-
411- let rec to_path ~manifest (item : Item.t) =
412- match item.parent with
413- | Some parent_slug ->
414- let parent =
415- manifest.items
416- |> List.find_exn ~f:(fun other -> String.equal other.slug parent_slug)
417- in
418- let base_path = parent |> to_path ~manifest in
419- let base_path = Filename.concat base_path item.title in
420- base_path
421- | None -> Filename.concat "/" item.title
422+ mpath |> Out_channel.write_all ~data:(to_string empty);
423+ make state_dir
424
425- let exists ~path manifest =
426- manifest.items
427- |> List.exists ~f:(fun item -> item |> to_path ~manifest |> String.equal path)
428+ let save manifest =
429+ manifest |> lock;
430+ Out_channel.write_all ~data:(to_string manifest) (manifest |> mpath);
431+ manifest |> unlock
432
433 let find ~path manifest =
434- (* find exactly one item, duplicates are unallowed *)
435- manifest.items
436- |> List.find ~f:(fun item ->
437- let file_path = item |> to_path ~manifest in
438- String.equal file_path path)
439+ manifest.items |> List.find ~f:(fun item -> Filename.equal item.path path)
440+
441+ (* TODO: no support for recursive operations yet *)
442+ let create ~path ~description ~tags manifest =
443+ if
444+ Option.is_some
445+ (manifest.items
446+ |> List.find ~f:(fun item -> Filename.equal item.path path))
447+ then failwith "duplicate entry"
448+ else
449+ let parent_dir = path |> Filename.dirname in
450+ let last_slug =
451+ match manifest.items |> List.hd with
452+ | Some item -> Some item.slug
453+ | None -> None
454+ in
455+ let next_slug = Slug.next ~last:last_slug manifest.state_dir in
456+ match parent_dir with
457+ | "." | "/" | "" ->
458+ (* root entry *)
459+ let item =
460+ Item.make ~parent:None ~slug:next_slug ~path ~description ~tags
461+ in
462+ { manifest with items = item :: manifest.items }
463+ | parent_dir -> (
464+ let parent = manifest |> find ~path:parent_dir in
465+ match parent with
466+ | Some parent ->
467+ let parent_slug = parent.slug in
468+ let item =
469+ Item.make ~parent:(Some parent_slug) ~slug:next_slug ~path
470+ ~description ~tags
471+ in
472+ { manifest with items = item :: manifest.items }
473+ | None -> failwith "no parent")
474
475 let list ~path manifest =
476- (* list items below path but not path itself *)
477 manifest.items
478 |> List.filter ~f:(fun item ->
479- let item_path = item |> to_path ~manifest in
480- Filename.equal path (Filename.dirname item_path))
481-
482- let insert ~path ~slug ~description ~tags manifest =
483- let title = path |> Filename.basename in
484- let dirname = path |> Util.dirname in
485- match dirname with
486- | "/" ->
487- let item = Item.make ~parent:None ~slug ~title ~description ~tags in
488- if manifest |> exists ~path:(item |> to_path ~manifest) then
489- failwith "duplicate item"
490- else
491- let items = item :: manifest.items in
492- { items }
493- | path ->
494- let parent =
495- match manifest |> find ~path with
496- | Some parent -> parent.slug
497- | None -> failwith "no parent"
498- in
499- let item =
500- Item.make ~parent:(Some parent) ~slug ~title ~description ~tags
501- in
502- if manifest |> exists ~path:(item |> to_path ~manifest) then
503- failwith "duplicate item"
504- else
505- let items = item :: manifest.items in
506- { items }
507+ String.equal (item.path |> Filename.dirname) path)
508
509 let remove ~path manifest =
510- match manifest |> find ~path with
511- | Some item ->
512- let others = manifest |> list ~path:(item |> to_path ~manifest) in
513- if Int.is_positive (List.length others) then
514- failwith "will not delete recursively"
515- else
516- let items =
517- manifest.items
518- |> List.filter ~f:(fun item ->
519- phys_equal
520- (Filename.equal path (item |> to_path ~manifest))
521- false)
522- in
523- { items }
524- | None -> failwith "not found"
525+ match manifest |> list ~path |> List.length with
526+ | 0 ->
527+ let items =
528+ manifest.items
529+ |> List.filter ~f:(fun item -> not (Filename.equal item.path path))
530+ in
531+ { manifest with items }
532+ | _ -> failwith "will not delete recursively"
533
534- let update ?(new_path = None) ~path ~description ~tags manifest =
535+ let update ~path ~description ~tags manifest =
536 let result =
537 manifest.items
538- |> List.findi ~f:(fun _ item ->
539- let file_path = item |> to_path ~manifest in
540- Filename.equal file_path path)
541+ |> List.findi ~f:(fun _ item -> Filename.equal item.path path)
542 in
543 match result with
544- | Some (index, item) -> (
545- match new_path with
546- | Some new_path ->
547- let manifest = manifest |> remove ~path in
548- manifest |> insert ~path:new_path ~slug:item.slug ~description ~tags
549- | None ->
550- let item = { item with description; tags } in
551- let items =
552- manifest.items
553- |> List.foldi ~init:[] ~f:(fun i accm other ->
554- if Int.equal i index then item :: accm else other :: accm)
555- in
556- { items })
557+ | Some (other, _) ->
558+ let items =
559+ manifest.items
560+ |> List.foldi ~init:[] ~f:(fun index accm item ->
561+ if Int.equal index other then
562+ let item = { item with description; tags } in
563+ item :: accm
564+ else item :: accm)
565+ in
566+ { manifest with items }
567 | None -> failwith "not found"
568
569- let slugs manifest = manifest.items |> List.map ~f:(fun item -> item.slug)
570-
571- let tags manifest =
572- manifest.items
573- |> List.fold ~init:[] ~f:(fun accm item -> List.concat [ accm; item.tags ])
574+ let move ~source ~dest manifest =
575+ let item = manifest |> find ~path:source in
576+ let others = manifest |> list ~path:source in
577+ match others |> List.length with
578+ | 0 -> (
579+ match item with
580+ | Some item ->
581+ let description, tags = (item.description, item.tags) in
582+ let manifest = manifest |> remove ~path:source in
583+ manifest |> create ~path:dest ~description ~tags
584+ | None -> failwith "not found")
585+ | _ -> failwith "cannot update recursively"
586 diff --git a/lib/note.ml b/lib/note.ml
587index cace7dd..3625456 100644
588--- a/lib/note.ml
589+++ b/lib/note.ml
590 @@ -162,6 +162,8 @@ let of_string ?(slug = None) content =
591 { frontmatter; content; slug }
592 else { frontmatter = Frontmatter.empty; content; slug }
593
594+ let root = Tree (of_string "", [])
595+
596 let rec flatten ~accm tree =
597 let (Tree (note, others)) = tree in
598 List.fold ~init:(note :: accm) ~f:(fun accm note -> flatten ~accm note) others
599 @@ -302,3 +304,51 @@ let load ~context path =
600 slug.path |> In_channel.read_all |> of_string ~slug:(Some slug))
601 in
602 of_list ~context notes
603+
604+ let rec resolve_manifest ~root ~path manifest : tree =
605+ let others =
606+ manifest |> Manifest.list ~path
607+ |> List.map ~f:(fun item ->
608+ let path = item.slug |> Slug.to_string in
609+ let note = In_channel.read_all path |> of_string in
610+ let root = Tree (note, []) in
611+ resolve_manifest ~root ~path manifest)
612+ in
613+ let (Tree (root, _)) = root in
614+ Tree (root, others)
615+ (*
616+ module Adapter (M : sig
617+ val db : Manifest.t
618+ end) =
619+ struct
620+ let read path =
621+ let result = M.db |> Manifest.find ~path in
622+ match result with
623+ | Some entry ->
624+ let note = entry.slug |> In_channel.read_all |> of_string in
625+ note
626+ | None -> failwith "not found"
627+
628+ let save ~path note =
629+ let description = note.frontmatter.description in
630+ let tags = note.frontmatter.tags in
631+ M.db |> Manifest.update ~path ~description ~tags
632+ end
633+
634+ let rec resolve_manifest ~tree ~path manifest =
635+ let items = manifest |> Manifest.list ~path in
636+ let items =
637+ items
638+ |> List.map ~f:(fun item ->
639+ let logical_path = item |> Manifest.to_path ~manifest in
640+ let slug = item.slug |> Slug.of_string in
641+ let note =
642+ slug |> Slug.to_string |> In_channel.read_all
643+ |> of_string ~slug:(Some slug)
644+ in
645+ manifest
646+ |> resolve_manifest ~tree:(Tree (note, [])) ~path:logical_path)
647+ in
648+ let (Tree (root, _)) = tree in
649+ Tree (root, items)
650+ *)
651 diff --git a/lib/slug.ml b/lib/slug.ml
652index 814f61d..200ef29 100644
653--- a/lib/slug.ml
654+++ b/lib/slug.ml
655 @@ -6,17 +6,31 @@ type t = { path : string; date : Date.t; index : int }
656
657 let to_string slug = slug.path
658
659- let of_string path =
660+ let of_string ?(basepath = None) path =
661 let result = Re.all pattern path |> List.hd_exn in
662 let items = Re.Group.all result |> Array.to_list in
663 let date = Date.parse ~fmt:"%Y%m%d" (List.nth_exn items 2) in
664 let index = int_of_string (List.nth_exn items 3) in
665+ let path =
666+ match basepath with
667+ | Some basepath -> Filename.concat basepath path
668+ | None -> path
669+ in
670+ let path =
671+ match Filename.check_suffix path "md" with
672+ | true -> path
673+ | false -> String.concat [ path; ".md" ]
674+ in
675 { path; date; index }
676
677 let shortname t =
678 let date_str = Date.format t.date "%Y%m%d" in
679 sprintf "note-%s-%d" date_str t.index
680
681+ let append ~path t =
682+ let path = Filename.concat path t.path in
683+ { t with path }
684+
685 let compare s1 s2 = String.compare s1.path s2.path
686
687 let is_note path =
688 diff --git a/test/manifest_test.ml b/test/manifest_test.ml
689index d60d0ff..980939e 100644
690--- a/test/manifest_test.ml
691+++ b/test/manifest_test.ml
692 @@ -3,49 +3,38 @@ open Note_lib
693
694 let test_recurse () =
695 let manifest =
696- Manifest.empty
697- |> Manifest.insert ~path:"/a" ~slug:"note-00000000-0" ~description:""
698- ~tags:[]
699- |> Manifest.insert ~path:"/a/b" ~slug:"note-00000000-1" ~description:""
700- ~tags:[]
701- |> Manifest.insert ~path:"/a/b/c" ~slug:"note-00000000-2" ~description:""
702- ~tags:[]
703- |> Manifest.insert ~path:"/a/b/c/d" ~slug:"note-00000000-3" ~description:""
704- ~tags:[]
705+ Manifest.make (Filename.temp_dir "note-test" "")
706+ |> Manifest.create ~path:"/a" ~description:"" ~tags:[]
707+ |> Manifest.create ~path:"/a/b" ~description:"" ~tags:[]
708+ |> Manifest.create ~path:"/a/b/c" ~description:"" ~tags:[]
709+ |> Manifest.create ~path:"/a/b/c/d" ~description:"" ~tags:[]
710 in
711 Alcotest.(check int) "n_results" 4 (List.length manifest.items)
712
713 let test_manifest () =
714- let temp_db = Filename.temp_file "note-test" "" in
715- Manifest.empty |> Manifest.save ~path:temp_db;
716+ let manifest = Manifest.make (Filename.temp_dir "note-test" "") in
717+ manifest |> Manifest.save;
718 let manifest =
719- Manifest.load_or_init temp_db
720- |> Manifest.insert ~path:"/fuu" ~slug:"note-00000000-0.md" ~description:""
721- ~tags:[]
722+ Manifest.load_or_init manifest.state_dir
723+ |> Manifest.create ~path:"/fuu" ~description:"" ~tags:[]
724 in
725 let result = manifest |> Manifest.find ~path:"/fuu" in
726 Alcotest.(check bool) "manifest loaded" (result |> Option.is_some) true;
727 let manifest =
728- manifest
729- |> Manifest.insert ~path:"/fuu/bar" ~slug:"note-00000000-1.md"
730- ~description:"" ~tags:[]
731+ manifest |> Manifest.create ~path:"/fuu/bar" ~description:"" ~tags:[]
732 in
733 let result = manifest |> Manifest.find ~path:"/fuu/bar" in
734 Alcotest.(check bool)
735 "manifest /fuu/bar inserted" (result |> Option.is_some) true;
736- let result_path = Option.value_exn result |> Manifest.to_path ~manifest in
737+ let result_path = (Option.value_exn result).path in
738 Alcotest.(check string) "result path" "/fuu/bar" result_path;
739 let manifest =
740- manifest
741- |> Manifest.insert ~path:"/fuu/baz" ~slug:"note-00000000-2.md"
742- ~description:"" ~tags:[]
743+ manifest |> Manifest.create ~path:"/fuu/baz" ~description:"" ~tags:[]
744 in
745 let results = manifest |> Manifest.list ~path:"/fuu" in
746 Alcotest.(check int) "n_results" 2 (List.length results);
747 let manifest =
748- manifest
749- |> Manifest.insert ~path:"/fuu/bar/qux" ~slug:"note-00000000-3.md"
750- ~description:"" ~tags:[]
751+ manifest |> Manifest.create ~path:"/fuu/bar/qux" ~description:"" ~tags:[]
752 in
753 let results = manifest |> Manifest.list ~path:"/fuu/bar" in
754 Alcotest.(check int) "n_results" 1 (List.length results);
755 @@ -57,11 +46,9 @@ let test_manifest () =
756
757 let test_update () =
758 let manifest =
759- Manifest.empty
760- |> Manifest.insert ~path:"/a" ~slug:"note-00000000-0" ~description:""
761- ~tags:[]
762- |> Manifest.insert ~path:"/a/b" ~slug:"note-00000000-1" ~description:""
763- ~tags:[]
764+ Manifest.make (Filename.temp_dir "note-test" "")
765+ |> Manifest.create ~path:"/a" ~description:"" ~tags:[]
766+ |> Manifest.create ~path:"/a/b" ~description:"" ~tags:[]
767 in
768 Alcotest.(check int) "two entries" 2 (List.length manifest.items);
769 let manifest =
770 @@ -73,29 +60,17 @@ let test_update () =
771
772 Alcotest.(check int) "two entries" 2 (List.length manifest.items)
773
774- let test_move () =
775- let manifest =
776- Manifest.empty
777- |> Manifest.insert ~path:"/a" ~slug:"note-00000000-0" ~description:""
778- ~tags:[]
779- |> Manifest.insert ~path:"/a/b" ~slug:"note-00000000-1" ~description:""
780- ~tags:[]
781- in
782- let manifest =
783- manifest
784- |> Manifest.update ~new_path:(Some "/b") ~path:"/a/b" ~description:""
785- ~tags:[]
786- in
787- Alcotest.(check bool)
788- "moved" true
789- (manifest |> Manifest.find ~path:"/b" |> Option.is_some) ;
790- Alcotest.(check int) "two entries" 2 (List.length manifest.items)
791-
792 let () =
793- Alcotest.run "Config"
794+ Alcotest.run "Manifest"
795 [
796- ("recurse", [ Alcotest.test_case "test recurse" `Quick test_recurse ]);
797- ("load", [ Alcotest.test_case "test manifest" `Quick test_manifest ]);
798- ("update", [ Alcotest.test_case "test update" `Quick test_update ]);
799- ("move", [ Alcotest.test_case "test move" `Quick test_move ]);
800+ ( "successive inserts",
801+ [
802+ Alcotest.test_case "insert several items into the manifest" `Quick
803+ test_recurse;
804+ ] );
805+ ( "manifest",
806+ [ Alcotest.test_case "test basic manifest" `Quick test_manifest ] );
807+ ( "update",
808+ [ Alcotest.test_case "test manifest update / move" `Quick test_update ]
809+ );
810 ]
811 diff --git a/test/note_test.ml b/test/note_test.ml
812index e2968da..0034b5e 100644
813--- a/test/note_test.ml
814+++ b/test/note_test.ml
815 @@ -10,6 +10,10 @@ let rec convert_tree tree =
816 let title = "[" ^ title ^ "]" in
817 Display.Hierarchical.Tree (title, List.map ~f:convert_tree others)
818
819+ let empty () =
820+ let _ = Note.of_string {||} in
821+ Alcotest.(check pass) "just an empty note" "" ""
822+
823 let make_a_note () =
824 let note =
825 Note.of_string
826 @@ -115,7 +119,7 @@ let insert_at () =
827 ~tree
828 (Note.Tree (n9, []))
829 in
830- Alcotest.(check bool) "inserted" true inserted ;
831+ Alcotest.(check bool) "inserted" true inserted;
832 let result =
833 Note.find_one
834 ~term:{ title = [ "note-9" ]; description = []; tags = [] }
835 @@ -213,8 +217,9 @@ let n3 =
836 # Note 3
837 |}
838
839- let n4 =
840- Note.of_string {|
841+ let n4 =
842+ Note.of_string
843+ {|
844 ---
845 title: note-4
846 parent: {"title": ["note-1"]}
847 @@ -242,14 +247,36 @@ let test_resolve () =
848 |}
849 in
850 let tree_as_string =
851- [ n3; n2; n1; n0 ; n4] |> Note.resolve ~root |> convert_tree
852+ [ n3; n2; n1; n0; n4 ] |> Note.resolve ~root |> convert_tree
853 |> Display.Hierarchical.to_string
854 in
855 Alcotest.(check string) "resolve" expected tree_as_string
856
857+ let test_resolve_manifest () =
858+
859+ (*
860+ let expected =
861+ {|
862+ [root]
863+ └──[n0]
864+ └──[n1]
865+ └──[n2]
866+ |}
867+ in
868+ let tree = Note.Tree (Note.of_string Note.root_template, []) in
869+ let tree_as_string =
870+ manifest
871+ |> Note.resolve_manifest ~path:"/" ~tree
872+ |> convert_tree |> Display.Hierarchical.to_string
873+ in *)
874+ Alcotest.(check pass) "resolve-manifest" "asfd" ",,,,"
875+
876 let () =
877 Alcotest.run "Note"
878 [
879+ ( "empty",
880+ [ Alcotest.test_case "parse an empty note" `Quick empty ]
881+ );
882 ( "create",
883 [ Alcotest.test_case "create a note from a string" `Quick make_a_note ]
884 );
885 @@ -265,4 +292,8 @@ let () =
886 ( "buf_insert",
887 [ Alcotest.test_case "buf insert flat list" `Quick test_buf_insert ] );
888 ("resolve", [ Alcotest.test_case "resolve flat list" `Quick test_resolve ]);
889+ ( "resolve-manifest",
890+ [
891+ Alcotest.test_case "resolve via manifest" `Quick test_resolve_manifest;
892+ ] );
893 ]
894 diff --git a/test/slug_test.ml b/test/slug_test.ml
895index f4af219..0c34fc7 100644
896--- a/test/slug_test.ml
897+++ b/test/slug_test.ml
898 @@ -5,7 +5,7 @@ let test_slug_of_string () =
899 let input = "/fuu/bar/note-19700101-0.md" in
900 let slug = input |> Slug.of_string in
901 Alcotest.(check string) "conversion" input (slug |> Slug.to_string);
902- let input = "note-19700101-0" in
903+ let input = "note-19700101-0.md" in
904 let slug = input |> Slug.of_string in
905 Alcotest.(check string) "bare" input (slug |> Slug.to_string)
906