Author: Kevin Schoon [kevinschoon@gmail.com]
Hash: 8f102ac3f8959209bf245f99474c6c4c38361412
Timestamp: Wed, 16 Sep 2020 16:44:43 +0000 (4 years ago)

+70 -89 +/-6 browse
refactor note to include slug type
1diff --git a/lib/cmd.ml b/lib/cmd.ml
2index 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
109index 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
180index 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
224index 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
300index 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
323index 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-