Commit
+176 -1 +/-3 browse
1 | diff --git a/lib/manifest.ml b/lib/manifest.ml |
2 | new file mode 100644 |
3 | index 0000000..1fa6d1f |
4 | --- /dev/null |
5 | +++ b/lib/manifest.ml |
6 | @@ -0,0 +1,139 @@ |
7 | + open Core |
8 | + |
9 | + module Item = struct |
10 | + type t = { |
11 | + parent : string option; |
12 | + slug : string; |
13 | + title : string; |
14 | + description : string; |
15 | + tags : string list; |
16 | + } |
17 | + |
18 | + let make ~parent ~slug ~title ~description ~tags = |
19 | + { parent; slug; title; description; tags } |
20 | + |
21 | + let of_json json = |
22 | + let slug = Ezjsonm.find json [ "slug" ] |> Ezjsonm.get_string in |
23 | + let title = Ezjsonm.find json [ "title" ] |> Ezjsonm.get_string in |
24 | + let description = |
25 | + Ezjsonm.find json [ "description" ] |> Ezjsonm.get_string |
26 | + in |
27 | + let tags = Ezjsonm.find json [ "tags" ] |> Ezjsonm.get_strings in |
28 | + let parent = |
29 | + match Ezjsonm.find_opt json [ "parent" ] with |
30 | + | Some parent -> Some (parent |> Ezjsonm.get_string) |
31 | + | None -> None |
32 | + in |
33 | + { slug; parent; title; description; tags } |
34 | + |
35 | + let to_json item = |
36 | + let parent = |
37 | + match item.parent with |
38 | + | Some parent -> parent |> Ezjsonm.string |
39 | + | None -> Ezjsonm.unit () |
40 | + in |
41 | + Ezjsonm.dict |
42 | + [ |
43 | + ("parent", parent); |
44 | + ("slug", item.slug |> Ezjsonm.string); |
45 | + ("title", item.title |> Ezjsonm.string); |
46 | + ("description", item.description |> Ezjsonm.string); |
47 | + ("tags", item.tags |> Ezjsonm.strings); |
48 | + ] |
49 | + end |
50 | + |
51 | + type t = { items : Item.t list } |
52 | + |
53 | + let empty = { items = [] } |
54 | + |
55 | + let of_json json = |
56 | + let items = |
57 | + Ezjsonm.find json [ "items" ] |
58 | + |> Ezjsonm.get_list (fun item -> item |> Item.of_json) |
59 | + in |
60 | + { items } |
61 | + |
62 | + let to_json manifest = |
63 | + let items = Ezjsonm.list Item.to_json manifest.items in |
64 | + Ezjsonm.dict [ ("items", items) ] |
65 | + |
66 | + let of_string manifest = manifest |> Ezjsonm.from_string |> of_json |
67 | + |
68 | + let to_string manifest = manifest |> to_json |> Ezjsonm.to_string |
69 | + |
70 | + let lock path = |
71 | + match path |> Sys.file_exists with |
72 | + | `Yes -> failwith "unable to aquire lock" |
73 | + | `No | `Unknown -> Out_channel.write_all ~data:"<locked>" path |
74 | + |
75 | + let unlock path = |
76 | + match path |> Sys.file_exists with |
77 | + | `Yes -> Sys.remove path |
78 | + | `No | `Unknown -> () |
79 | + |
80 | + let lockfile path = Filename.concat (path |> Filename.dirname) "note.lock" |
81 | + |
82 | + let load_or_init path = |
83 | + match Sys.file_exists path with |
84 | + | `Yes -> path |> In_channel.read_all |> of_string |
85 | + | `No | `Unknown -> |
86 | + path |> Out_channel.write_all ~data:(to_string empty); |
87 | + empty |
88 | + |
89 | + let save ~path manifest = |
90 | + path |> lockfile |> lock; |
91 | + Out_channel.write_all ~data:(to_string manifest) path; |
92 | + path |> lockfile |> unlock |
93 | + |
94 | + let rec to_path ~manifest (item : Item.t) = |
95 | + match item.parent with |
96 | + | Some parent_slug -> |
97 | + let parent = |
98 | + manifest.items |
99 | + |> List.find_exn ~f:(fun other -> String.equal other.slug parent_slug) |
100 | + in |
101 | + let base_path = parent |> to_path ~manifest in |
102 | + let base_path = Filename.concat base_path item.title in |
103 | + base_path |
104 | + | None -> Filename.concat "/" item.title |
105 | + |
106 | + let exists ~path manifest = |
107 | + manifest.items |
108 | + |> List.exists ~f:(fun item -> item |> to_path ~manifest |> String.equal path) |
109 | + |
110 | + let find ~path manifest = |
111 | + manifest.items |
112 | + |> List.find ~f:(fun item -> |
113 | + let file_path = item |> to_path ~manifest in |
114 | + String.equal file_path path) |
115 | + |
116 | + let list ~path manifest = |
117 | + (* list items below path but not path itself *) |
118 | + manifest.items |
119 | + |> List.filter ~f:(fun item -> |
120 | + let item_path = item |> to_path ~manifest in |
121 | + String.equal path (Filename.dirname item_path)) |
122 | + |
123 | + let insert ~path ~slug ~title ~description ~tags manifest = |
124 | + match path with |
125 | + | "" | "/" -> |
126 | + let item = Item.make ~parent:None ~slug ~title ~description ~tags in |
127 | + if manifest |> exists ~path:(item |> to_path ~manifest) then |
128 | + failwith "duplicate item" |
129 | + else |
130 | + let items = item :: manifest.items in |
131 | + { items } |
132 | + | path -> |
133 | + let parent = |
134 | + match manifest |> find ~path with |
135 | + | Some parent -> parent.slug |
136 | + | None -> failwith "no parent" |
137 | + in |
138 | + let item = |
139 | + Item.make ~parent:(Some parent) ~slug ~title ~description ~tags |
140 | + in |
141 | + if manifest |> exists ~path:(item |> to_path ~manifest) then |
142 | + failwith "duplicate item" |
143 | + else |
144 | + let items = item :: manifest.items in |
145 | + { items } |
146 | diff --git a/test/dune b/test/dune |
147 | index d6c1f27..eaab266 100644 |
148 | --- a/test/dune |
149 | +++ b/test/dune |
150 | @@ -1,4 +1,4 @@ |
151 | (tests |
152 | - (names config_test display_test note_test slug_test) |
153 | + (names config_test display_test manifest_test note_test slug_test) |
154 | (libraries note_lib alcotest) |
155 | ) |
156 | diff --git a/test/manifest_test.ml b/test/manifest_test.ml |
157 | new file mode 100644 |
158 | index 0000000..45ead24 |
159 | --- /dev/null |
160 | +++ b/test/manifest_test.ml |
161 | @@ -0,0 +1,36 @@ |
162 | + open Core |
163 | + open Note_lib |
164 | + |
165 | + let test_manifest () = |
166 | + let temp_db = Filename.temp_file "note-test" "" in |
167 | + Manifest.empty |> Manifest.save ~path:temp_db; |
168 | + let manifest = |
169 | + Manifest.load_or_init temp_db |
170 | + |> Manifest.insert ~path:"/" ~slug:"note-00000000-0.md" ~title:"fuu" |
171 | + ~description:"" ~tags:[] |
172 | + in |
173 | + let result = manifest |> Manifest.find ~path:"/fuu" in |
174 | + Alcotest.(check bool) "manifest loaded" (result |> Option.is_some) true; |
175 | + let manifest = |
176 | + manifest |
177 | + |> Manifest.insert ~path:"/fuu" ~slug:"note-00000000-1.md" ~title:"bar" |
178 | + ~description:"" ~tags:[] |
179 | + in |
180 | + print_endline (Manifest.to_string manifest); |
181 | + let result = manifest |> Manifest.find ~path:"/fuu/bar" in |
182 | + Alcotest.(check bool) "manifest loaded" (result |> Option.is_some) true; |
183 | + let result_path = Option.value_exn result |> Manifest.to_path ~manifest in |
184 | + Alcotest.(check string) "result path" "/fuu/bar" result_path; |
185 | + let manifest = |
186 | + manifest |
187 | + |> Manifest.insert ~path:"/fuu" ~slug:"note-00000000-2.md" ~title:"baz" |
188 | + ~description:"" ~tags:[] |
189 | + in |
190 | + let results = manifest |> Manifest.list ~path:"/fuu" in |
191 | + Alcotest.(check int) "n_results" 2 (List.length results) |
192 | + |
193 | + let () = |
194 | + Alcotest.run "Config" |
195 | + [ |
196 | + ("load", [ Alcotest.test_case "test manifest" `Quick test_manifest ]); |
197 | + ] |