Author: Kevin Schoon [kevinschoon@gmail.com]
Hash: 5df11f01c43bc442f5afcfaa41ef364d702b6219
Timestamp: Wed, 17 Feb 2021 17:23:25 +0000 (3 years ago)

+97 -92 +/-11 browse
revamp search/filter with regular expressions
1diff --git a/doc/note-cat.1.scd b/doc/note-cat.1.scd
2index 7c16ed2..301e083 100644
3--- a/doc/note-cat.1.scd
4+++ b/doc/note-cat.1.scd
5 @@ -26,9 +26,6 @@ json for consumption by other tools.
6 *-encoding*
7 specify the output encoding
8
9- *-fulltext*
10- perform full text search instead of key comparision
11-
12 # EXAMPLES
13 ```
14 # print the parsed content of the fuubar note
15 diff --git a/doc/note-delete.1.scd b/doc/note-delete.1.scd
16index d526886..af09121 100644
17--- a/doc/note-delete.1.scd
18+++ b/doc/note-delete.1.scd
19 @@ -19,11 +19,6 @@ Delete one or more notes that match the filter criteria.
20 *FILTER*
21 tag or text based search terms
22
23- *optional arguments*
24-
25- *-fulltext*
26- perform full text search instead of key comparision
27-
28 # EXAMPLES
29 ```
30 # delete a note called fuubar
31 diff --git a/doc/note-edit.1.scd b/doc/note-edit.1.scd
32index f86b349..72792de 100644
33--- a/doc/note-edit.1.scd
34+++ b/doc/note-edit.1.scd
35 @@ -19,11 +19,6 @@ Select a note that matches the filter criteria and open it in your text editor.
36 *FILTER*
37 tag or text based search terms
38
39- *optional arguments*
40-
41- *-fulltext*
42- perform full text search instead of key comparision
43-
44 # EXAMPLES
45 ```
46 # edit a note called fuubar
47 diff --git a/doc/note-ls.1.scd b/doc/note-ls.1.scd
48index 208bea1..9682140 100644
49--- a/doc/note-ls.1.scd
50+++ b/doc/note-ls.1.scd
51 @@ -25,9 +25,6 @@ is provided then all notes will be listed.
52 *-columns*
53 columns to show in the note list
54
55- *-fulltext*
56- perform full text search instead of key comparision
57-
58 *-style*
59 list style [fixed | wide | simple]
60
61 diff --git a/lib/cmd.ml b/lib/cmd.ml
62index b950400..8984065 100644
63--- a/lib/cmd.ml
64+++ b/lib/cmd.ml
65 @@ -72,9 +72,6 @@ json for consumption by other tools.
66 |})
67 [%map_open
68 let filter_args = anon (sequence ("filter" %: filter_arg))
69- and fulltext =
70- flag "fulltext" no_arg
71- ~doc:"perform a fulltext search instead of just key comparison"
72 and encoding =
73 flag "encoding"
74 (optional_with_default cfg.encoding encoding_arg)
75 @@ -82,10 +79,7 @@ json for consumption by other tools.
76 in
77 fun () ->
78 let open Note.Search in
79- let filter_kind = if fulltext then Some Fulltext else None in
80- let notes =
81- find_many ?strategy:filter_kind ~args:filter_args get_notes
82- in
83+ let notes = find_many ~args:filter_args get_notes in
84 List.iter
85 ~f:(fun note ->
86 print_endline (Note.Encoding.to_string ~style:encoding note))
87 @@ -148,18 +142,11 @@ let delete_note =
88 Delete the first note that matches the filter criteria.
89 |})
90 [%map_open
91- let filter_args = anon (sequence ("filter" %: filter_arg))
92- and fulltext =
93- flag "fulltext" no_arg
94- ~doc:"perform a fulltext search instead of just key comparison"
95- in
96+ let filter_args = anon (sequence ("filter" %: filter_arg)) in
97 fun () ->
98 let open Note.Search in
99- let filter_kind = if fulltext then Fulltext else Keys in
100 let notes = get_notes in
101- let note =
102- Note.Search.find_one ~strategy:filter_kind ~args:filter_args notes
103- in
104+ let note = find_one ~args:filter_args notes in
105 match note with
106 | Some note ->
107 Io.delete ~callback:cfg.on_modification ~title:(Note.get_title note)
108 @@ -174,15 +161,10 @@ let edit_note =
109 Select a note that matches the filter criteria and open it in your text editor.
110 |})
111 [%map_open
112- let filter_args = anon (sequence ("filter" %: filter_arg))
113- and fulltext =
114- flag "fulltext" no_arg
115- ~doc:"perform a fulltext search instead of just key comparison"
116- in
117+ let filter_args = anon (sequence ("filter" %: filter_arg)) in
118 fun () ->
119 let open Note.Search in
120- let filter_kind = if fulltext then Fulltext else Keys in
121- let note = find_one ~strategy:filter_kind ~args:filter_args get_notes in
122+ let note = find_one ~args:filter_args get_notes in
123 match note with
124 | Some note ->
125 Io.edit ~callback:cfg.on_modification ~editor:cfg.editor
126 @@ -199,9 +181,6 @@ is provided then all notes will be listed.
127 |})
128 [%map_open
129 let filter_args = anon (sequence ("filter" %: filter_arg))
130- and fulltext =
131- flag "fulltext" no_arg
132- ~doc:"perform a fulltext search instead of just key comparison"
133 and style =
134 flag "style"
135 (optional_with_default cfg.list_style list_style_arg)
136 @@ -213,15 +192,10 @@ is provided then all notes will be listed.
137 in
138 fun () ->
139 let open Note.Search in
140- let filter_kind = if fulltext then Some Fulltext else None in
141- let notes =
142- Note.Search.find_many ?strategy:filter_kind ~args:filter_args
143- get_notes
144- in
145+ let notes = find_many ~args:filter_args get_notes in
146 let styles = cfg.styles in
147 let cells = Note.to_cells ~columns ~styles notes in
148- Display.to_stdout ~style cells;
149- ]
150+ Display.to_stdout ~style cells]
151
152 let sync =
153 Command.basic ~summary:"sync notes to a remote server"
154 diff --git a/lib/dune b/lib/dune
155index 32b55d4..05d525c 100644
156--- a/lib/dune
157+++ b/lib/dune
158 @@ -2,4 +2,4 @@
159 (name note_lib)
160 (preprocess
161 (pps ppx_jane))
162- (libraries ANSITerminal base core dune-build-info ezjsonm omd stdio yaml))
163+ (libraries ANSITerminal base core dune-build-info ezjsonm omd stdio re yaml))
164 diff --git a/lib/note.ml b/lib/note.ml
165index 229760d..c33f8bc 100644
166--- a/lib/note.ml
167+++ b/lib/note.ml
168 @@ -166,41 +166,59 @@ module Encoding = struct
169 end
170
171 module Search = struct
172+ open Re.Str
173
174- type strategy = Keys | Fulltext
175+ let dump_results results =
176+ List.iter
177+ ~f:(fun result ->
178+ print_endline (sprintf "%s - %d" (get_title (snd result)) (fst result)))
179+ results
180
181- let title key note = String.equal key (get_title note)
182+ let title expr note =
183+ let title_string = get_title note in
184+ string_match expr title_string 0
185
186- let tags key note =
187+ let tags expr note =
188 let tags = get_tags note in
189- List.count ~f:(fun tag -> String.equal key tag) tags > 0
190+ List.count ~f:(fun tag -> string_match expr tag 0) tags > 0
191
192- let of_strings strategy args =
193- match strategy with
194- | Keys ->
195- List.map
196- ~f:(fun arg ->
197- let has_title = title arg in
198- let has_tag = tags arg in
199- fun note -> has_title note || has_tag note)
200- args
201- | Fulltext -> failwith "not implemented"
202+ let content expr note =
203+ let words = Util.to_words note.markdown in
204+ List.count ~f:(fun word -> string_match expr word 0) words > 0
205
206- let find_one ?(strategy = Keys) ~args notes =
207- let filters = of_strings strategy args in
208- List.find
209- ~f:(fun note ->
210- List.count ~f:(fun filter -> filter note) filters > 0
211- || List.length filters = 0)
212- notes
213+ let match_and_rank ~args notes =
214+ let expressions = List.map ~f:regexp args in
215+ let matches =
216+ List.fold ~init:[]
217+ ~f:(fun accm note ->
218+ let has_title =
219+ List.count ~f:(fun expr -> title expr note) expressions > 0
220+ in
221+ let has_tag =
222+ List.count ~f:(fun expr -> tags expr note) expressions > 0
223+ in
224+ let has_content =
225+ List.count ~f:(fun expr -> content expr note) expressions > 0
226+ in
227+ match (has_title, has_tag, has_content) with
228+ | true, _, _ -> List.append accm [ (20, note) ]
229+ | _, true, _ -> List.append accm [ (10, note) ]
230+ | _, _, true -> List.append accm [ (5, note) ]
231+ | false, false, false -> accm)
232+ notes
233+ in
234+ List.rev (List.sort ~compare:(fun n1 n2 -> fst n1 - fst n2) matches)
235
236- let find_many ?(strategy = Keys) ~args notes =
237- let filters = of_strings strategy args in
238- List.filter
239- ~f:(fun note ->
240- List.count ~f:(fun filter -> filter note) filters > 0
241- || List.length filters = 0)
242- notes
243+ let find_one ~args notes =
244+ let results = match_and_rank ~args notes in
245+ let results = List.map ~f:snd results in
246+ if List.length results = 0 then None else Some (List.hd_exn results)
247+
248+ let find_many ~args notes =
249+ if List.length args = 0 then notes
250+ else
251+ let results = match_and_rank ~args notes in
252+ List.map ~f:snd results
253 end
254
255 open ANSITerminal
256 diff --git a/note.opam b/note.opam
257index a3056a0..38f9716 100644
258--- a/note.opam
259+++ b/note.opam
260 @@ -14,6 +14,7 @@ depends: [
261 "dune" {>= "2.7" & >= "2.7"}
262 "ezjsonm" {>= "1.2.0"}
263 "omd" {>= "1.3.1"}
264+ "re" {>= "v1.9.0"}
265 "stdio" {>= "v0.14.0"}
266 "yaml" {>= "2.1.0"}
267 "odoc" {with-doc}
268 diff --git a/test/dune b/test/dune
269index c8191e5..5ed362e 100644
270--- a/test/dune
271+++ b/test/dune
272 @@ -1,4 +1,4 @@
273 (tests
274- (names e2e filter)
275+ (names e2e search)
276 (libraries note_lib)
277 )
278 diff --git a/test/filter.ml b/test/filter.ml
279deleted file mode 100644
280index cd850ad..0000000
281--- a/test/filter.ml
282+++ /dev/null
283 @@ -1,13 +0,0 @@
284- open Note_lib
285-
286- let test_filter_by_keys =
287- let state_dir = Core.Filename.temp_dir "note-test" "" in
288- let slug = Slug.next state_dir in
289- let note =
290- Note.build ~tags:[ "fuu"; "bar" ] ~content:"" ~title:"A Very Important Note" slug
291- in
292- let result = Note.Filter.find_many ~strategy: Keys ~args: ["fuu" ; "bar"; "baz"] [note] in
293- assert (List.length result = 1)
294-
295- let () =
296- test_filter_by_keys;
297 diff --git a/test/search.ml b/test/search.ml
298new file mode 100644
299index 0000000..5a47765
300--- /dev/null
301+++ b/test/search.ml
302 @@ -0,0 +1,41 @@
303+ open Note_lib
304+
305+ let make_notes =
306+ let state_dir = Core.Filename.temp_dir "note-test" "" in
307+ let note_1 =
308+ Note.build ~tags:[ "fuu"; "bar" ] ~content:"" ~title:"A Very Important Note"
309+ (Slug.next state_dir)
310+ in
311+ let note_2 =
312+ Note.build ~tags:[ "fuu"; "bar"; "baz" ] ~content:""
313+ ~title:"Another Very Important Note" (Slug.next state_dir)
314+ in
315+ let note_3 =
316+ Note.build ~tags:[ "fuu"; "bar"; "baz" ] ~content:""
317+ ~title:"fuu" (Slug.next state_dir)
318+ in
319+ [note_1 ; note_2 ; note_3]
320+
321+
322+ let test_filter_by_keys =
323+ let notes = make_notes in
324+ let result =
325+ Note.Search.find_many ~args:[ "fuu"; "bar"; "baz" ]
326+ notes
327+ in
328+ assert (List.length result = 3)
329+
330+ let test_filter_by_title_find_one =
331+ let notes = make_notes in
332+ let result =
333+ Note.Search.find_one ~args:[ "fuu" ]
334+ notes
335+ in
336+ assert (Option.is_some result) ;
337+ let note = Option.get result in
338+ (* title should take priority *)
339+ assert ((Note.get_title note) = "fuu")
340+
341+ let () =
342+ test_filter_by_keys;
343+ test_filter_by_title_find_one;