Commit
+70 -89 +/-6 browse
1 | diff --git a/lib/cmd.ml b/lib/cmd.ml |
2 | index 6893d5c..597d2a6 100644 |
3 | --- a/lib/cmd.ml |
4 | +++ b/lib/cmd.ml |
5 | @@ -8,21 +8,15 @@ let init_config path = |
6 | Config.initialize config_path config; |
7 | config |
8 | |
9 | - let get_slugs = |
10 | + let get_notes = |
11 | let open Config in |
12 | let cfg = init_config None in |
13 | - let slugs = Slug.load (get_exn cfg "state_dir") in |
14 | - List.map |
15 | - ~f:(fun s -> Filename.concat (get_exn cfg "state_dir") (Slug.to_string s)) |
16 | - slugs |
17 | - |
18 | - let get_notes_with_paths = |
19 | - List.map |
20 | - ~f:(fun path -> (Note.of_string (In_channel.read_all path), path)) |
21 | - get_slugs |
22 | - |
23 | - let get_notes = |
24 | - List.map ~f:(fun path -> Note.of_string (In_channel.read_all path)) get_slugs |
25 | + let state_dir = (get_exn cfg "state_dir") in |
26 | + List.map ~f: ( |
27 | + fun slug -> |
28 | + let data = In_channel.read_all (Slug.get_path slug) in |
29 | + Note.of_string ~data: data slug |
30 | + ) (Slug.load state_dir) |
31 | |
32 | type encoding = Json | Yaml | Text |
33 | |
34 | @@ -162,24 +156,21 @@ note ls "My Important Note" |
35 | fun () -> |
36 | let open Config in |
37 | let cfg = init_config None in |
38 | - let next_slug = Slug.next (Slug.load (get_exn cfg "state_dir")) in |
39 | - let target_file = |
40 | - Filename.concat (get_exn cfg "state_dir") (Slug.to_string next_slug) |
41 | - in |
42 | + let slug = Slug.next (get_exn cfg "state_dir") in |
43 | match open_stdin with |
44 | | Some _ -> |
45 | (* reading from stdin so write directly to note *) |
46 | let content = In_channel.input_all In_channel.stdin in |
47 | - let note = Note.build ~tags ~content title in |
48 | + let note = Note.build ~tags ~content ~title slug in |
49 | Io.create |
50 | ~callback:(get cfg "on_modification") |
51 | - ~content:(Note.to_string note) target_file |
52 | + ~content:(Note.to_string note) (Slug.get_path slug) |
53 | | None -> |
54 | - let note = Note.build ~tags ~content:"" title in |
55 | + let note = Note.build ~tags ~content:"" ~title slug in |
56 | let init_content = Note.to_string note in |
57 | Io.create_on_change |
58 | ~callback:(get cfg "on_modification") |
59 | - ~editor:(get_exn cfg "editor") init_content target_file] |
60 | + ~editor:(get_exn cfg "editor") init_content (Slug.get_path slug)] |
61 | |
62 | let delete_note = |
63 | let open Command.Let_syntax in |
64 | @@ -203,17 +194,14 @@ note delete fuubar |
65 | let open Config in |
66 | let cfg = init_config None in |
67 | let open Note.Filter in |
68 | - let filter_kind = if fulltext then Some Fulltext else None in |
69 | - let note = |
70 | - Note.Filter.find_one_with_paths ?strategy:filter_kind |
71 | - ~args:filter_args get_notes_with_paths |
72 | - in |
73 | - |
74 | + let filter_kind = if fulltext then Fulltext else Keys in |
75 | + let notes = get_notes in |
76 | + let note = Note.Filter.find_one ~strategy:filter_kind ~args:filter_args notes in |
77 | match note with |
78 | - | Some (note, path) -> |
79 | + | Some note -> |
80 | Io.delete |
81 | ~callback:(get cfg "on_modification") |
82 | - ~title:(Note.get_title note) path |
83 | + ~title:(Note.get_title note) (Note.get_path note) |
84 | | None -> failwith "not found"] |
85 | |
86 | let edit_note = |
87 | @@ -238,16 +226,13 @@ note edit fuubar |
88 | let open Config in |
89 | let cfg = init_config None in |
90 | let open Note.Filter in |
91 | - let filter_kind = if fulltext then Some Fulltext else None in |
92 | - let note = |
93 | - Note.Filter.find_one_with_paths ?strategy:filter_kind |
94 | - ~args:filter_args get_notes_with_paths |
95 | - in |
96 | + let filter_kind = if fulltext then Fulltext else Keys in |
97 | + let note = find_one ~strategy: filter_kind ~args: filter_args get_notes in |
98 | match note with |
99 | - | Some (_, path) -> |
100 | + | Some note -> |
101 | Io.edit |
102 | ~callback:(get cfg "on_modification") |
103 | - ~editor:(get_exn cfg "editor") path |
104 | + ~editor:(get_exn cfg "editor") (Note.get_path note) |
105 | | None -> failwith "not found"] |
106 | |
107 | let list_notes = |
108 | diff --git a/lib/note.ml b/lib/note.ml |
109 | index 9e1f242..eaab044 100644 |
110 | --- a/lib/note.ml |
111 | +++ b/lib/note.ml |
112 | @@ -1,15 +1,15 @@ |
113 | open Core |
114 | |
115 | - type t = { frontmatter : Ezjsonm.t option; markdown : Omd.t } |
116 | + type t = { frontmatter : Ezjsonm.t option; markdown : Omd.t ; slug : Slug.t} |
117 | |
118 | - let build ?(tags = []) ?(content = "") title = |
119 | + let build ?(tags = []) ?(content = "") ~title slug = |
120 | let frontmatter = |
121 | Some |
122 | (Ezjsonm.dict |
123 | [ ("title", Ezjsonm.string title); ("tags", Ezjsonm.strings tags) ]) |
124 | in |
125 | let markdown = Omd.of_string content in |
126 | - { frontmatter; markdown } |
127 | + { frontmatter; markdown ; slug} |
128 | |
129 | let get_title t = |
130 | let title = |
131 | @@ -43,6 +43,9 @@ let get_tags t = |
132 | | None -> [] ) |
133 | | None -> [] |
134 | |
135 | + let get_path t = |
136 | + Slug.get_path t.slug |
137 | + |
138 | let tokenize t = |
139 | let rec _tokenize markdown = |
140 | List.fold |
141 | @@ -131,7 +134,7 @@ let to_string t = |
142 | [ "---"; front_matter; "---"; Omd.to_text t.markdown ] |
143 | | None -> Omd.to_text t.markdown |
144 | |
145 | - let of_string data = |
146 | + let of_string ~data slug = |
147 | let indexes = String.substr_index_all ~may_overlap:true ~pattern:"---" data in |
148 | if List.length indexes >= 2 then |
149 | let meta_str = |
150 | @@ -151,11 +154,11 @@ let of_string data = |
151 | (String.slice data (List.nth_exn indexes 1 + 3) (String.length data)) |
152 | in |
153 | let frontmatter = Some frontmatter in |
154 | - { frontmatter; markdown } |
155 | + { frontmatter; markdown; slug } |
156 | else |
157 | let frontmatter = None in |
158 | let markdown = Omd.of_string data in |
159 | - { frontmatter; markdown } |
160 | + { frontmatter; markdown; slug} |
161 | |
162 | module Filter = struct |
163 | type strategy = Keys | Fulltext |
164 | @@ -185,14 +188,6 @@ module Filter = struct |
165 | || List.length filters = 0) |
166 | notes |
167 | |
168 | - let find_one_with_paths ?(strategy = Keys) ~args notes = |
169 | - let filters = of_strings strategy args in |
170 | - List.find |
171 | - ~f:(fun (note, _) -> |
172 | - List.count ~f:(fun filter -> filter note) filters > 0 |
173 | - || List.length filters = 0) |
174 | - notes |
175 | - |
176 | let find_many ?(strategy = Keys) ~args notes = |
177 | let filters = of_strings strategy args in |
178 | List.filter |
179 | diff --git a/lib/note.mli b/lib/note.mli |
180 | index 4700268..99781c7 100644 |
181 | --- a/lib/note.mli |
182 | +++ b/lib/note.mli |
183 | @@ -1,6 +1,6 @@ |
184 | type t |
185 | |
186 | - val build : ?tags:string list -> ?content:string -> string -> t |
187 | + val build : ?tags:string list -> ?content:string -> title:string -> Slug.t -> t |
188 | (** build a new note *) |
189 | |
190 | val get_title : t -> string |
191 | @@ -9,6 +9,9 @@ val get_title : t -> string |
192 | val get_tags : t -> string list |
193 | (** access tags in the frontmatter of a note *) |
194 | |
195 | + val get_path : t -> string |
196 | + (** access the absolute path of a note *) |
197 | + |
198 | val tokenize : t -> string list |
199 | (** split each word from the note into a list of string tokens *) |
200 | |
201 | @@ -40,7 +43,7 @@ val get_data : t -> Ezjsonm.t |
202 | val to_string : t -> string |
203 | (** convert a note into a string *) |
204 | |
205 | - val of_string : string -> t |
206 | + val of_string : data:string -> Slug.t -> t |
207 | (** decode a note from a string *) |
208 | |
209 | val to_json : t -> [> Ezjsonm.t ] |
210 | @@ -50,12 +53,6 @@ module Filter : sig |
211 | |
212 | val find_one : ?strategy:strategy -> args:string list -> t list -> t option |
213 | |
214 | - val find_one_with_paths : |
215 | - ?strategy:strategy -> |
216 | - args:string list -> |
217 | - (t * string) list -> |
218 | - (t * string) option |
219 | - |
220 | val find_many : ?strategy:strategy -> args:string list -> t list -> t list |
221 | end |
222 | |
223 | diff --git a/lib/slug.ml b/lib/slug.ml |
224 | index 2b1ce7c..71a0ec4 100644 |
225 | --- a/lib/slug.ml |
226 | +++ b/lib/slug.ml |
227 | @@ -1,44 +1,48 @@ |
228 | open Core |
229 | |
230 | - type t = Date.t * int |
231 | + type t = { path : string; date : Date.t; index : int } |
232 | |
233 | - let to_string slug = |
234 | - let time, index = slug in |
235 | - let date_str = Date.format time "%Y%m%d" in |
236 | - sprintf "note-%s-%d.md" date_str index |
237 | + let get_path t = t.path |
238 | |
239 | - let of_string slug_str = |
240 | + let to_string t = |
241 | + let date_str = Date.format t.date "%Y%m%d" in |
242 | + sprintf "note-%s-%d" date_str t.index |
243 | + |
244 | + let of_path path = |
245 | (* note-20010103-0.md *) |
246 | - let slug = Filename.chop_extension (Filename.basename slug_str) in |
247 | - let split = String.split ~on:'-' slug in |
248 | - (* TODO: add proper error handling *) |
249 | - ( Date.parse ~fmt:"%Y%m%d" (List.nth_exn split 1), |
250 | - int_of_string (List.nth_exn split 2) ) |
251 | + if is_some (String.substr_index ~pattern:"note-" path) then |
252 | + let slug = Filename.chop_extension (Filename.basename path) in |
253 | + let split = String.split ~on:'-' slug in |
254 | + (* TODO: add proper error handling *) |
255 | + let date = Date.parse ~fmt:"%Y%m%d" (List.nth_exn split 1) in |
256 | + let index = int_of_string (List.nth_exn split 2) in |
257 | + Some { path; date; index } |
258 | + else None |
259 | |
260 | - let load path = |
261 | - let file_names = Sys.ls_dir path in |
262 | + let load state_dir = |
263 | List.filter_map |
264 | - ~f:(fun name -> |
265 | - (* ignore any files that do not match the substring note- *) |
266 | - match String.substr_index ~pattern:"note-" name with |
267 | - | Some _ -> Some (of_string name) |
268 | - | None -> None) |
269 | - file_names |
270 | + ~f:(fun path -> of_path (Filename.concat state_dir path)) |
271 | + (Sys.ls_dir state_dir) |
272 | |
273 | - let next slugs = |
274 | + let next state_dir = |
275 | + let slugs = load state_dir in |
276 | (* find all slugs for today (00:00:00 -> 23:59:59) *) |
277 | let now = Time.now () in |
278 | let today = Time.to_date ~zone:Time.Zone.utc now in |
279 | let tomorrow = Date.add_days today 1 in |
280 | let filtered = |
281 | List.filter |
282 | - ~f:(fun (time, _) -> Date.between ~low:today ~high:tomorrow time) |
283 | + ~f:(fun slug -> Date.between ~low:today ~high:tomorrow slug.date) |
284 | slugs |
285 | in |
286 | let next_int = |
287 | List.fold ~init:(-1) |
288 | - ~f:(fun accm (_, i) -> if i > accm then i else accm) |
289 | + ~f:(fun accm slug -> if slug.index > accm then slug.index else accm) |
290 | filtered |
291 | + 1 |
292 | in |
293 | - (today, next_int) |
294 | + let date_str = Date.format today "%Y%m%d" in |
295 | + let path = |
296 | + Filename.concat state_dir (Core.sprintf "%s-%d.md" date_str next_int) |
297 | + in |
298 | + { path; date = today; index = next_int } |
299 | diff --git a/lib/slug.mli b/lib/slug.mli |
300 | index 8f74568..dc7c456 100644 |
301 | --- a/lib/slug.mli |
302 | +++ b/lib/slug.mli |
303 | @@ -16,14 +16,14 @@ indicating the number of notes that were taken on that date. |
304 | NOTE: Epoch time would be simpler and more accurate but less friendly for reading. |
305 | *) |
306 | |
307 | + val get_path : t -> string |
308 | + (** return the absolute path of the slug *) |
309 | + |
310 | val to_string : t -> string |
311 | (** convert a slug into a string *) |
312 | |
313 | - val of_string : string -> t |
314 | - (** convert a string into a slug *) |
315 | - |
316 | val load : string -> t list |
317 | (** read slugs from the given path *) |
318 | |
319 | - val next : t list -> t |
320 | + val next : string -> t |
321 | (** create a new slug *) |
322 | diff --git a/test/filter.ml b/test/filter.ml |
323 | index 97b8454..cd850ad 100644 |
324 | --- a/test/filter.ml |
325 | +++ b/test/filter.ml |
326 | @@ -1,13 +1,13 @@ |
327 | open Note_lib |
328 | |
329 | let test_filter_by_keys = |
330 | - let open Note.Filter in |
331 | + let state_dir = Core.Filename.temp_dir "note-test" "" in |
332 | + let slug = Slug.next state_dir in |
333 | let note = |
334 | - Note.build ~tags:[ "fuu"; "bar" ] ~content:"" "A Very Important Note" |
335 | + Note.build ~tags:[ "fuu"; "bar" ] ~content:"" ~title:"A Very Important Note" slug |
336 | in |
337 | - let result = find_many ~strategy: Keys ~args: ["fuu" ; "bar"; "baz"] [note] in |
338 | + let result = Note.Filter.find_many ~strategy: Keys ~args: ["fuu" ; "bar"; "baz"] [note] in |
339 | assert (List.length result = 1) |
340 | |
341 | let () = |
342 | test_filter_by_keys; |
343 | - |