├── .github
└── workflows
│ └── build_and_test.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── src
├── edit.rs
├── index.rs
├── lib.rs
├── main.rs
├── note.rs
└── storage.rs
└── tests
├── index_test.rs
├── note_test.rs
├── snapshots
├── note_test__editing_an_existing_daily_alters_the_same_file.snap
├── note_test__opening_two_notes_with_the_same_name_prevents_clobbering.snap
├── note_test__opening_two_notes_with_the_same_name_prevents_clobbering_even_if_collision_exists_on_disk.snap
├── note_test__writes_dailies_to_notes_directory.snap
├── note_test__writes_notes_to_notes_directory.snap
└── note_test__writes_notes_to_notes_directory_even_if_inode_changes.snap
└── testutil
└── mod.rs
/.github/workflows/build_and_test.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: Build and Test
4 | jobs:
5 | build_and_test:
6 | name: Build and Test
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions-rs/toolchain@v1
11 | with:
12 | toolchain: stable
13 |
14 | - name: Clippy
15 | uses: actions-rs/cargo@v1
16 | with:
17 | command: clippy
18 | args: --tests -- -Dwarnings
19 |
20 | - name: Check Formatting
21 | uses: actions-rs/cargo@v1
22 | with:
23 | command: fmt
24 | args: --check
25 |
26 | - name: Unit Tests
27 | uses: actions-rs/cargo@v1
28 | with:
29 | command: test
30 | args: --release
31 |
32 | - name: Check Build
33 | uses: actions-rs/cargo@v1
34 | with:
35 | command: build
36 | args: --bins --release
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [1.1.0] - 2025-02-09
9 |
10 | ### Changed
11 |
12 | - Make the offset in `quicknotes daily` a vararg, like `quicknotes new`'s title.
13 | You can now do `quicknotes daily 2 days ago`, rather than
14 | `quicknotes daily "2 days ago"`.
15 |
16 |
17 | ## [1.0.2] - 2025-01-15
18 |
19 | ### Changed
20 |
21 | - Bump MSRV to 1.83 (https://github.com/ollien/quicknotes/pull/2; thanks @paulpr0!)
22 |
23 | ## [1.0.1] - 2025-01-13
24 |
25 | ### Fixed
26 |
27 | - Updated `nucleo_picker` from alpha version.
28 | - Update patch versions of all other dependencies.
29 |
30 | ## [1.0.0] - 2025-01-11
31 |
32 | Initial project release
33 |
34 | [1.1.0]: https://github.com/ollien/quicknotes/compare/v1.0.2...v1.1.0
35 | [1.0.2]: https://github.com/ollien/quicknotes/compare/v1.0.1...v1.0.2
36 | [1.0.1]: https://github.com/ollien/quicknotes/compare/v1.0.0...v1.0.1
37 | [1.0.0]: https://github.com/ollien/quicknotes/releases/tag/v1.0.0
38 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 4
4 |
5 | [[package]]
6 | name = "ahash"
7 | version = "0.8.11"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
10 | dependencies = [
11 | "cfg-if",
12 | "once_cell",
13 | "version_check",
14 | "zerocopy",
15 | ]
16 |
17 | [[package]]
18 | name = "aho-corasick"
19 | version = "1.1.3"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
22 | dependencies = [
23 | "memchr",
24 | ]
25 |
26 | [[package]]
27 | name = "android-tzdata"
28 | version = "0.1.1"
29 | source = "registry+https://github.com/rust-lang/crates.io-index"
30 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
31 |
32 | [[package]]
33 | name = "android_system_properties"
34 | version = "0.1.5"
35 | source = "registry+https://github.com/rust-lang/crates.io-index"
36 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
37 | dependencies = [
38 | "libc",
39 | ]
40 |
41 | [[package]]
42 | name = "anstream"
43 | version = "0.6.18"
44 | source = "registry+https://github.com/rust-lang/crates.io-index"
45 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
46 | dependencies = [
47 | "anstyle",
48 | "anstyle-parse",
49 | "anstyle-query",
50 | "anstyle-wincon",
51 | "colorchoice",
52 | "is_terminal_polyfill",
53 | "utf8parse",
54 | ]
55 |
56 | [[package]]
57 | name = "anstyle"
58 | version = "1.0.10"
59 | source = "registry+https://github.com/rust-lang/crates.io-index"
60 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
61 |
62 | [[package]]
63 | name = "anstyle-parse"
64 | version = "0.2.6"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
67 | dependencies = [
68 | "utf8parse",
69 | ]
70 |
71 | [[package]]
72 | name = "anstyle-query"
73 | version = "1.1.2"
74 | source = "registry+https://github.com/rust-lang/crates.io-index"
75 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
76 | dependencies = [
77 | "windows-sys 0.59.0",
78 | ]
79 |
80 | [[package]]
81 | name = "anstyle-wincon"
82 | version = "3.0.7"
83 | source = "registry+https://github.com/rust-lang/crates.io-index"
84 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
85 | dependencies = [
86 | "anstyle",
87 | "once_cell",
88 | "windows-sys 0.59.0",
89 | ]
90 |
91 | [[package]]
92 | name = "anyhow"
93 | version = "1.0.95"
94 | source = "registry+https://github.com/rust-lang/crates.io-index"
95 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
96 |
97 | [[package]]
98 | name = "autocfg"
99 | version = "1.4.0"
100 | source = "registry+https://github.com/rust-lang/crates.io-index"
101 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
102 |
103 | [[package]]
104 | name = "bitflags"
105 | version = "2.7.0"
106 | source = "registry+https://github.com/rust-lang/crates.io-index"
107 | checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
108 |
109 | [[package]]
110 | name = "block-buffer"
111 | version = "0.10.4"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
114 | dependencies = [
115 | "generic-array",
116 | ]
117 |
118 | [[package]]
119 | name = "bumpalo"
120 | version = "3.16.0"
121 | source = "registry+https://github.com/rust-lang/crates.io-index"
122 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
123 |
124 | [[package]]
125 | name = "cc"
126 | version = "1.2.9"
127 | source = "registry+https://github.com/rust-lang/crates.io-index"
128 | checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
129 | dependencies = [
130 | "shlex",
131 | ]
132 |
133 | [[package]]
134 | name = "cfg-if"
135 | version = "1.0.0"
136 | source = "registry+https://github.com/rust-lang/crates.io-index"
137 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
138 |
139 | [[package]]
140 | name = "chrono"
141 | version = "0.4.39"
142 | source = "registry+https://github.com/rust-lang/crates.io-index"
143 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
144 | dependencies = [
145 | "android-tzdata",
146 | "iana-time-zone",
147 | "js-sys",
148 | "num-traits",
149 | "wasm-bindgen",
150 | "windows-targets 0.52.6",
151 | ]
152 |
153 | [[package]]
154 | name = "chrono-english"
155 | version = "0.1.7"
156 | source = "registry+https://github.com/rust-lang/crates.io-index"
157 | checksum = "f73d909da7eb4a7d88c679c3f5a1bc09d965754e0adb2e7627426cef96a00d6f"
158 | dependencies = [
159 | "chrono",
160 | "scanlex",
161 | ]
162 |
163 | [[package]]
164 | name = "clap"
165 | version = "4.5.26"
166 | source = "registry+https://github.com/rust-lang/crates.io-index"
167 | checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783"
168 | dependencies = [
169 | "clap_builder",
170 | "clap_derive",
171 | ]
172 |
173 | [[package]]
174 | name = "clap_builder"
175 | version = "4.5.26"
176 | source = "registry+https://github.com/rust-lang/crates.io-index"
177 | checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121"
178 | dependencies = [
179 | "anstream",
180 | "anstyle",
181 | "clap_lex",
182 | "strsim",
183 | ]
184 |
185 | [[package]]
186 | name = "clap_derive"
187 | version = "4.5.24"
188 | source = "registry+https://github.com/rust-lang/crates.io-index"
189 | checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
190 | dependencies = [
191 | "heck",
192 | "proc-macro2",
193 | "quote",
194 | "syn",
195 | ]
196 |
197 | [[package]]
198 | name = "clap_lex"
199 | version = "0.7.4"
200 | source = "registry+https://github.com/rust-lang/crates.io-index"
201 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
202 |
203 | [[package]]
204 | name = "colorchoice"
205 | version = "1.0.3"
206 | source = "registry+https://github.com/rust-lang/crates.io-index"
207 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
208 |
209 | [[package]]
210 | name = "colored"
211 | version = "2.2.0"
212 | source = "registry+https://github.com/rust-lang/crates.io-index"
213 | checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
214 | dependencies = [
215 | "lazy_static",
216 | "windows-sys 0.59.0",
217 | ]
218 |
219 | [[package]]
220 | name = "console"
221 | version = "0.15.10"
222 | source = "registry+https://github.com/rust-lang/crates.io-index"
223 | checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
224 | dependencies = [
225 | "encode_unicode",
226 | "libc",
227 | "once_cell",
228 | "windows-sys 0.59.0",
229 | ]
230 |
231 | [[package]]
232 | name = "core-foundation-sys"
233 | version = "0.8.7"
234 | source = "registry+https://github.com/rust-lang/crates.io-index"
235 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
236 |
237 | [[package]]
238 | name = "cpufeatures"
239 | version = "0.2.16"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
242 | dependencies = [
243 | "libc",
244 | ]
245 |
246 | [[package]]
247 | name = "crossbeam-deque"
248 | version = "0.8.6"
249 | source = "registry+https://github.com/rust-lang/crates.io-index"
250 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
251 | dependencies = [
252 | "crossbeam-epoch",
253 | "crossbeam-utils",
254 | ]
255 |
256 | [[package]]
257 | name = "crossbeam-epoch"
258 | version = "0.9.18"
259 | source = "registry+https://github.com/rust-lang/crates.io-index"
260 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
261 | dependencies = [
262 | "crossbeam-utils",
263 | ]
264 |
265 | [[package]]
266 | name = "crossbeam-utils"
267 | version = "0.8.21"
268 | source = "registry+https://github.com/rust-lang/crates.io-index"
269 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
270 |
271 | [[package]]
272 | name = "crossterm"
273 | version = "0.28.1"
274 | source = "registry+https://github.com/rust-lang/crates.io-index"
275 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
276 | dependencies = [
277 | "bitflags",
278 | "crossterm_winapi",
279 | "filedescriptor",
280 | "mio",
281 | "parking_lot",
282 | "rustix",
283 | "signal-hook",
284 | "signal-hook-mio",
285 | "winapi",
286 | ]
287 |
288 | [[package]]
289 | name = "crossterm_winapi"
290 | version = "0.9.1"
291 | source = "registry+https://github.com/rust-lang/crates.io-index"
292 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
293 | dependencies = [
294 | "winapi",
295 | ]
296 |
297 | [[package]]
298 | name = "crypto-common"
299 | version = "0.1.6"
300 | source = "registry+https://github.com/rust-lang/crates.io-index"
301 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
302 | dependencies = [
303 | "generic-array",
304 | "typenum",
305 | ]
306 |
307 | [[package]]
308 | name = "digest"
309 | version = "0.10.7"
310 | source = "registry+https://github.com/rust-lang/crates.io-index"
311 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
312 | dependencies = [
313 | "block-buffer",
314 | "crypto-common",
315 | ]
316 |
317 | [[package]]
318 | name = "directories"
319 | version = "5.0.1"
320 | source = "registry+https://github.com/rust-lang/crates.io-index"
321 | checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
322 | dependencies = [
323 | "dirs-sys",
324 | ]
325 |
326 | [[package]]
327 | name = "dirs-sys"
328 | version = "0.4.1"
329 | source = "registry+https://github.com/rust-lang/crates.io-index"
330 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
331 | dependencies = [
332 | "libc",
333 | "option-ext",
334 | "redox_users",
335 | "windows-sys 0.48.0",
336 | ]
337 |
338 | [[package]]
339 | name = "either"
340 | version = "1.13.0"
341 | source = "registry+https://github.com/rust-lang/crates.io-index"
342 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
343 |
344 | [[package]]
345 | name = "encode_unicode"
346 | version = "1.0.0"
347 | source = "registry+https://github.com/rust-lang/crates.io-index"
348 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
349 |
350 | [[package]]
351 | name = "equivalent"
352 | version = "1.0.1"
353 | source = "registry+https://github.com/rust-lang/crates.io-index"
354 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
355 |
356 | [[package]]
357 | name = "errno"
358 | version = "0.3.10"
359 | source = "registry+https://github.com/rust-lang/crates.io-index"
360 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
361 | dependencies = [
362 | "libc",
363 | "windows-sys 0.59.0",
364 | ]
365 |
366 | [[package]]
367 | name = "fallible-iterator"
368 | version = "0.3.0"
369 | source = "registry+https://github.com/rust-lang/crates.io-index"
370 | checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
371 |
372 | [[package]]
373 | name = "fallible-streaming-iterator"
374 | version = "0.1.9"
375 | source = "registry+https://github.com/rust-lang/crates.io-index"
376 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
377 |
378 | [[package]]
379 | name = "fastrand"
380 | version = "2.3.0"
381 | source = "registry+https://github.com/rust-lang/crates.io-index"
382 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
383 |
384 | [[package]]
385 | name = "filedescriptor"
386 | version = "0.8.2"
387 | source = "registry+https://github.com/rust-lang/crates.io-index"
388 | checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
389 | dependencies = [
390 | "libc",
391 | "thiserror 1.0.69",
392 | "winapi",
393 | ]
394 |
395 | [[package]]
396 | name = "generic-array"
397 | version = "0.14.7"
398 | source = "registry+https://github.com/rust-lang/crates.io-index"
399 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
400 | dependencies = [
401 | "typenum",
402 | "version_check",
403 | ]
404 |
405 | [[package]]
406 | name = "getrandom"
407 | version = "0.2.15"
408 | source = "registry+https://github.com/rust-lang/crates.io-index"
409 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
410 | dependencies = [
411 | "cfg-if",
412 | "libc",
413 | "wasi",
414 | ]
415 |
416 | [[package]]
417 | name = "hashbrown"
418 | version = "0.14.5"
419 | source = "registry+https://github.com/rust-lang/crates.io-index"
420 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
421 | dependencies = [
422 | "ahash",
423 | ]
424 |
425 | [[package]]
426 | name = "hashbrown"
427 | version = "0.15.2"
428 | source = "registry+https://github.com/rust-lang/crates.io-index"
429 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
430 |
431 | [[package]]
432 | name = "hashlink"
433 | version = "0.9.1"
434 | source = "registry+https://github.com/rust-lang/crates.io-index"
435 | checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
436 | dependencies = [
437 | "hashbrown 0.14.5",
438 | ]
439 |
440 | [[package]]
441 | name = "heck"
442 | version = "0.5.0"
443 | source = "registry+https://github.com/rust-lang/crates.io-index"
444 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
445 |
446 | [[package]]
447 | name = "iana-time-zone"
448 | version = "0.1.61"
449 | source = "registry+https://github.com/rust-lang/crates.io-index"
450 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
451 | dependencies = [
452 | "android_system_properties",
453 | "core-foundation-sys",
454 | "iana-time-zone-haiku",
455 | "js-sys",
456 | "wasm-bindgen",
457 | "windows-core",
458 | ]
459 |
460 | [[package]]
461 | name = "iana-time-zone-haiku"
462 | version = "0.1.2"
463 | source = "registry+https://github.com/rust-lang/crates.io-index"
464 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
465 | dependencies = [
466 | "cc",
467 | ]
468 |
469 | [[package]]
470 | name = "indexmap"
471 | version = "2.7.0"
472 | source = "registry+https://github.com/rust-lang/crates.io-index"
473 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
474 | dependencies = [
475 | "equivalent",
476 | "hashbrown 0.15.2",
477 | ]
478 |
479 | [[package]]
480 | name = "insta"
481 | version = "1.42.0"
482 | source = "registry+https://github.com/rust-lang/crates.io-index"
483 | checksum = "6513e4067e16e69ed1db5ab56048ed65db32d10ba5fc1217f5393f8f17d8b5a5"
484 | dependencies = [
485 | "console",
486 | "linked-hash-map",
487 | "once_cell",
488 | "similar",
489 | ]
490 |
491 | [[package]]
492 | name = "is_terminal_polyfill"
493 | version = "1.70.1"
494 | source = "registry+https://github.com/rust-lang/crates.io-index"
495 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
496 |
497 | [[package]]
498 | name = "itertools"
499 | version = "0.13.0"
500 | source = "registry+https://github.com/rust-lang/crates.io-index"
501 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
502 | dependencies = [
503 | "either",
504 | ]
505 |
506 | [[package]]
507 | name = "js-sys"
508 | version = "0.3.77"
509 | source = "registry+https://github.com/rust-lang/crates.io-index"
510 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
511 | dependencies = [
512 | "once_cell",
513 | "wasm-bindgen",
514 | ]
515 |
516 | [[package]]
517 | name = "lazy_static"
518 | version = "1.5.0"
519 | source = "registry+https://github.com/rust-lang/crates.io-index"
520 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
521 |
522 | [[package]]
523 | name = "libc"
524 | version = "0.2.169"
525 | source = "registry+https://github.com/rust-lang/crates.io-index"
526 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
527 |
528 | [[package]]
529 | name = "libredox"
530 | version = "0.1.3"
531 | source = "registry+https://github.com/rust-lang/crates.io-index"
532 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
533 | dependencies = [
534 | "bitflags",
535 | "libc",
536 | ]
537 |
538 | [[package]]
539 | name = "libsqlite3-sys"
540 | version = "0.30.1"
541 | source = "registry+https://github.com/rust-lang/crates.io-index"
542 | checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
543 | dependencies = [
544 | "cc",
545 | "pkg-config",
546 | "vcpkg",
547 | ]
548 |
549 | [[package]]
550 | name = "linked-hash-map"
551 | version = "0.5.6"
552 | source = "registry+https://github.com/rust-lang/crates.io-index"
553 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
554 |
555 | [[package]]
556 | name = "linux-raw-sys"
557 | version = "0.4.15"
558 | source = "registry+https://github.com/rust-lang/crates.io-index"
559 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
560 |
561 | [[package]]
562 | name = "lock_api"
563 | version = "0.4.12"
564 | source = "registry+https://github.com/rust-lang/crates.io-index"
565 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
566 | dependencies = [
567 | "autocfg",
568 | "scopeguard",
569 | ]
570 |
571 | [[package]]
572 | name = "log"
573 | version = "0.4.22"
574 | source = "registry+https://github.com/rust-lang/crates.io-index"
575 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
576 |
577 | [[package]]
578 | name = "memchr"
579 | version = "2.7.4"
580 | source = "registry+https://github.com/rust-lang/crates.io-index"
581 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
582 |
583 | [[package]]
584 | name = "mio"
585 | version = "1.0.3"
586 | source = "registry+https://github.com/rust-lang/crates.io-index"
587 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
588 | dependencies = [
589 | "libc",
590 | "log",
591 | "wasi",
592 | "windows-sys 0.52.0",
593 | ]
594 |
595 | [[package]]
596 | name = "nucleo"
597 | version = "0.5.0"
598 | source = "registry+https://github.com/rust-lang/crates.io-index"
599 | checksum = "5262af4c94921c2646c5ac6ff7900c2af9cbb08dc26a797e18130a7019c039d4"
600 | dependencies = [
601 | "nucleo-matcher",
602 | "parking_lot",
603 | "rayon",
604 | ]
605 |
606 | [[package]]
607 | name = "nucleo-matcher"
608 | version = "0.3.1"
609 | source = "registry+https://github.com/rust-lang/crates.io-index"
610 | checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85"
611 | dependencies = [
612 | "memchr",
613 | "unicode-segmentation",
614 | ]
615 |
616 | [[package]]
617 | name = "nucleo-picker"
618 | version = "0.7.0"
619 | source = "registry+https://github.com/rust-lang/crates.io-index"
620 | checksum = "24cf7ad2e101658755dce7dffbb15f40cf98587ef7be9a356995ab6f09597c85"
621 | dependencies = [
622 | "crossterm",
623 | "memchr",
624 | "nucleo",
625 | "parking_lot",
626 | "unicode-segmentation",
627 | "unicode-width 0.2.0",
628 | ]
629 |
630 | [[package]]
631 | name = "num-traits"
632 | version = "0.2.19"
633 | source = "registry+https://github.com/rust-lang/crates.io-index"
634 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
635 | dependencies = [
636 | "autocfg",
637 | ]
638 |
639 | [[package]]
640 | name = "once_cell"
641 | version = "1.20.2"
642 | source = "registry+https://github.com/rust-lang/crates.io-index"
643 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
644 |
645 | [[package]]
646 | name = "option-ext"
647 | version = "0.2.0"
648 | source = "registry+https://github.com/rust-lang/crates.io-index"
649 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
650 |
651 | [[package]]
652 | name = "parking_lot"
653 | version = "0.12.3"
654 | source = "registry+https://github.com/rust-lang/crates.io-index"
655 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
656 | dependencies = [
657 | "lock_api",
658 | "parking_lot_core",
659 | ]
660 |
661 | [[package]]
662 | name = "parking_lot_core"
663 | version = "0.9.10"
664 | source = "registry+https://github.com/rust-lang/crates.io-index"
665 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
666 | dependencies = [
667 | "cfg-if",
668 | "libc",
669 | "redox_syscall",
670 | "smallvec",
671 | "windows-targets 0.52.6",
672 | ]
673 |
674 | [[package]]
675 | name = "pkg-config"
676 | version = "0.3.31"
677 | source = "registry+https://github.com/rust-lang/crates.io-index"
678 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
679 |
680 | [[package]]
681 | name = "proc-macro2"
682 | version = "1.0.93"
683 | source = "registry+https://github.com/rust-lang/crates.io-index"
684 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
685 | dependencies = [
686 | "unicode-ident",
687 | ]
688 |
689 | [[package]]
690 | name = "quicknotes"
691 | version = "1.1.0"
692 | dependencies = [
693 | "anyhow",
694 | "chrono",
695 | "chrono-english",
696 | "clap",
697 | "colored",
698 | "directories",
699 | "insta",
700 | "itertools",
701 | "nucleo-picker",
702 | "regex",
703 | "rusqlite",
704 | "rusqlite_migration",
705 | "serde",
706 | "serde_derive",
707 | "sha2",
708 | "stringreader",
709 | "tempfile",
710 | "test-case",
711 | "textwrap",
712 | "thiserror 2.0.11",
713 | "toml",
714 | "walkdir",
715 | ]
716 |
717 | [[package]]
718 | name = "quote"
719 | version = "1.0.38"
720 | source = "registry+https://github.com/rust-lang/crates.io-index"
721 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
722 | dependencies = [
723 | "proc-macro2",
724 | ]
725 |
726 | [[package]]
727 | name = "rayon"
728 | version = "1.10.0"
729 | source = "registry+https://github.com/rust-lang/crates.io-index"
730 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
731 | dependencies = [
732 | "either",
733 | "rayon-core",
734 | ]
735 |
736 | [[package]]
737 | name = "rayon-core"
738 | version = "1.12.1"
739 | source = "registry+https://github.com/rust-lang/crates.io-index"
740 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
741 | dependencies = [
742 | "crossbeam-deque",
743 | "crossbeam-utils",
744 | ]
745 |
746 | [[package]]
747 | name = "redox_syscall"
748 | version = "0.5.8"
749 | source = "registry+https://github.com/rust-lang/crates.io-index"
750 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
751 | dependencies = [
752 | "bitflags",
753 | ]
754 |
755 | [[package]]
756 | name = "redox_users"
757 | version = "0.4.6"
758 | source = "registry+https://github.com/rust-lang/crates.io-index"
759 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
760 | dependencies = [
761 | "getrandom",
762 | "libredox",
763 | "thiserror 1.0.69",
764 | ]
765 |
766 | [[package]]
767 | name = "regex"
768 | version = "1.11.1"
769 | source = "registry+https://github.com/rust-lang/crates.io-index"
770 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
771 | dependencies = [
772 | "aho-corasick",
773 | "memchr",
774 | "regex-automata",
775 | "regex-syntax",
776 | ]
777 |
778 | [[package]]
779 | name = "regex-automata"
780 | version = "0.4.9"
781 | source = "registry+https://github.com/rust-lang/crates.io-index"
782 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
783 | dependencies = [
784 | "aho-corasick",
785 | "memchr",
786 | "regex-syntax",
787 | ]
788 |
789 | [[package]]
790 | name = "regex-syntax"
791 | version = "0.8.5"
792 | source = "registry+https://github.com/rust-lang/crates.io-index"
793 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
794 |
795 | [[package]]
796 | name = "rusqlite"
797 | version = "0.32.1"
798 | source = "registry+https://github.com/rust-lang/crates.io-index"
799 | checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
800 | dependencies = [
801 | "bitflags",
802 | "fallible-iterator",
803 | "fallible-streaming-iterator",
804 | "hashlink",
805 | "libsqlite3-sys",
806 | "smallvec",
807 | ]
808 |
809 | [[package]]
810 | name = "rusqlite_migration"
811 | version = "1.3.1"
812 | source = "registry+https://github.com/rust-lang/crates.io-index"
813 | checksum = "923b42e802f7dc20a0a6b5e097ba7c83fe4289da07e49156fecf6af08aa9cd1c"
814 | dependencies = [
815 | "log",
816 | "rusqlite",
817 | ]
818 |
819 | [[package]]
820 | name = "rustix"
821 | version = "0.38.43"
822 | source = "registry+https://github.com/rust-lang/crates.io-index"
823 | checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
824 | dependencies = [
825 | "bitflags",
826 | "errno",
827 | "libc",
828 | "linux-raw-sys",
829 | "windows-sys 0.59.0",
830 | ]
831 |
832 | [[package]]
833 | name = "rustversion"
834 | version = "1.0.19"
835 | source = "registry+https://github.com/rust-lang/crates.io-index"
836 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
837 |
838 | [[package]]
839 | name = "same-file"
840 | version = "1.0.6"
841 | source = "registry+https://github.com/rust-lang/crates.io-index"
842 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
843 | dependencies = [
844 | "winapi-util",
845 | ]
846 |
847 | [[package]]
848 | name = "scanlex"
849 | version = "0.1.4"
850 | source = "registry+https://github.com/rust-lang/crates.io-index"
851 | checksum = "088c5d71572124929ea7549a8ce98e1a6fd33d0a38367b09027b382e67c033db"
852 |
853 | [[package]]
854 | name = "scopeguard"
855 | version = "1.2.0"
856 | source = "registry+https://github.com/rust-lang/crates.io-index"
857 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
858 |
859 | [[package]]
860 | name = "serde"
861 | version = "1.0.217"
862 | source = "registry+https://github.com/rust-lang/crates.io-index"
863 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
864 | dependencies = [
865 | "serde_derive",
866 | ]
867 |
868 | [[package]]
869 | name = "serde_derive"
870 | version = "1.0.217"
871 | source = "registry+https://github.com/rust-lang/crates.io-index"
872 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
873 | dependencies = [
874 | "proc-macro2",
875 | "quote",
876 | "syn",
877 | ]
878 |
879 | [[package]]
880 | name = "serde_spanned"
881 | version = "0.6.8"
882 | source = "registry+https://github.com/rust-lang/crates.io-index"
883 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
884 | dependencies = [
885 | "serde",
886 | ]
887 |
888 | [[package]]
889 | name = "sha2"
890 | version = "0.10.8"
891 | source = "registry+https://github.com/rust-lang/crates.io-index"
892 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
893 | dependencies = [
894 | "cfg-if",
895 | "cpufeatures",
896 | "digest",
897 | ]
898 |
899 | [[package]]
900 | name = "shlex"
901 | version = "1.3.0"
902 | source = "registry+https://github.com/rust-lang/crates.io-index"
903 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
904 |
905 | [[package]]
906 | name = "signal-hook"
907 | version = "0.3.17"
908 | source = "registry+https://github.com/rust-lang/crates.io-index"
909 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
910 | dependencies = [
911 | "libc",
912 | "signal-hook-registry",
913 | ]
914 |
915 | [[package]]
916 | name = "signal-hook-mio"
917 | version = "0.2.4"
918 | source = "registry+https://github.com/rust-lang/crates.io-index"
919 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
920 | dependencies = [
921 | "libc",
922 | "mio",
923 | "signal-hook",
924 | ]
925 |
926 | [[package]]
927 | name = "signal-hook-registry"
928 | version = "1.4.2"
929 | source = "registry+https://github.com/rust-lang/crates.io-index"
930 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
931 | dependencies = [
932 | "libc",
933 | ]
934 |
935 | [[package]]
936 | name = "similar"
937 | version = "2.6.0"
938 | source = "registry+https://github.com/rust-lang/crates.io-index"
939 | checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
940 |
941 | [[package]]
942 | name = "smallvec"
943 | version = "1.13.2"
944 | source = "registry+https://github.com/rust-lang/crates.io-index"
945 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
946 |
947 | [[package]]
948 | name = "smawk"
949 | version = "0.3.2"
950 | source = "registry+https://github.com/rust-lang/crates.io-index"
951 | checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
952 |
953 | [[package]]
954 | name = "stringreader"
955 | version = "0.1.1"
956 | source = "registry+https://github.com/rust-lang/crates.io-index"
957 | checksum = "913e7b03d63752f6cdd2df77da36749d82669904798fe8944b9ec3d23f159905"
958 |
959 | [[package]]
960 | name = "strsim"
961 | version = "0.11.1"
962 | source = "registry+https://github.com/rust-lang/crates.io-index"
963 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
964 |
965 | [[package]]
966 | name = "syn"
967 | version = "2.0.96"
968 | source = "registry+https://github.com/rust-lang/crates.io-index"
969 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
970 | dependencies = [
971 | "proc-macro2",
972 | "quote",
973 | "unicode-ident",
974 | ]
975 |
976 | [[package]]
977 | name = "tempfile"
978 | version = "3.15.0"
979 | source = "registry+https://github.com/rust-lang/crates.io-index"
980 | checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
981 | dependencies = [
982 | "cfg-if",
983 | "fastrand",
984 | "getrandom",
985 | "once_cell",
986 | "rustix",
987 | "windows-sys 0.59.0",
988 | ]
989 |
990 | [[package]]
991 | name = "test-case"
992 | version = "3.3.1"
993 | source = "registry+https://github.com/rust-lang/crates.io-index"
994 | checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
995 | dependencies = [
996 | "test-case-macros",
997 | ]
998 |
999 | [[package]]
1000 | name = "test-case-core"
1001 | version = "3.3.1"
1002 | source = "registry+https://github.com/rust-lang/crates.io-index"
1003 | checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
1004 | dependencies = [
1005 | "cfg-if",
1006 | "proc-macro2",
1007 | "quote",
1008 | "syn",
1009 | ]
1010 |
1011 | [[package]]
1012 | name = "test-case-macros"
1013 | version = "3.3.1"
1014 | source = "registry+https://github.com/rust-lang/crates.io-index"
1015 | checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
1016 | dependencies = [
1017 | "proc-macro2",
1018 | "quote",
1019 | "syn",
1020 | "test-case-core",
1021 | ]
1022 |
1023 | [[package]]
1024 | name = "textwrap"
1025 | version = "0.16.1"
1026 | source = "registry+https://github.com/rust-lang/crates.io-index"
1027 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
1028 | dependencies = [
1029 | "smawk",
1030 | "unicode-linebreak",
1031 | "unicode-width 0.1.14",
1032 | ]
1033 |
1034 | [[package]]
1035 | name = "thiserror"
1036 | version = "1.0.69"
1037 | source = "registry+https://github.com/rust-lang/crates.io-index"
1038 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
1039 | dependencies = [
1040 | "thiserror-impl 1.0.69",
1041 | ]
1042 |
1043 | [[package]]
1044 | name = "thiserror"
1045 | version = "2.0.11"
1046 | source = "registry+https://github.com/rust-lang/crates.io-index"
1047 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
1048 | dependencies = [
1049 | "thiserror-impl 2.0.11",
1050 | ]
1051 |
1052 | [[package]]
1053 | name = "thiserror-impl"
1054 | version = "1.0.69"
1055 | source = "registry+https://github.com/rust-lang/crates.io-index"
1056 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
1057 | dependencies = [
1058 | "proc-macro2",
1059 | "quote",
1060 | "syn",
1061 | ]
1062 |
1063 | [[package]]
1064 | name = "thiserror-impl"
1065 | version = "2.0.11"
1066 | source = "registry+https://github.com/rust-lang/crates.io-index"
1067 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
1068 | dependencies = [
1069 | "proc-macro2",
1070 | "quote",
1071 | "syn",
1072 | ]
1073 |
1074 | [[package]]
1075 | name = "toml"
1076 | version = "0.8.19"
1077 | source = "registry+https://github.com/rust-lang/crates.io-index"
1078 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
1079 | dependencies = [
1080 | "serde",
1081 | "serde_spanned",
1082 | "toml_datetime",
1083 | "toml_edit",
1084 | ]
1085 |
1086 | [[package]]
1087 | name = "toml_datetime"
1088 | version = "0.6.8"
1089 | source = "registry+https://github.com/rust-lang/crates.io-index"
1090 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
1091 | dependencies = [
1092 | "serde",
1093 | ]
1094 |
1095 | [[package]]
1096 | name = "toml_edit"
1097 | version = "0.22.22"
1098 | source = "registry+https://github.com/rust-lang/crates.io-index"
1099 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
1100 | dependencies = [
1101 | "indexmap",
1102 | "serde",
1103 | "serde_spanned",
1104 | "toml_datetime",
1105 | "winnow",
1106 | ]
1107 |
1108 | [[package]]
1109 | name = "typenum"
1110 | version = "1.17.0"
1111 | source = "registry+https://github.com/rust-lang/crates.io-index"
1112 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
1113 |
1114 | [[package]]
1115 | name = "unicode-ident"
1116 | version = "1.0.14"
1117 | source = "registry+https://github.com/rust-lang/crates.io-index"
1118 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
1119 |
1120 | [[package]]
1121 | name = "unicode-linebreak"
1122 | version = "0.1.5"
1123 | source = "registry+https://github.com/rust-lang/crates.io-index"
1124 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
1125 |
1126 | [[package]]
1127 | name = "unicode-segmentation"
1128 | version = "1.12.0"
1129 | source = "registry+https://github.com/rust-lang/crates.io-index"
1130 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
1131 |
1132 | [[package]]
1133 | name = "unicode-width"
1134 | version = "0.1.14"
1135 | source = "registry+https://github.com/rust-lang/crates.io-index"
1136 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
1137 |
1138 | [[package]]
1139 | name = "unicode-width"
1140 | version = "0.2.0"
1141 | source = "registry+https://github.com/rust-lang/crates.io-index"
1142 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
1143 |
1144 | [[package]]
1145 | name = "utf8parse"
1146 | version = "0.2.2"
1147 | source = "registry+https://github.com/rust-lang/crates.io-index"
1148 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1149 |
1150 | [[package]]
1151 | name = "vcpkg"
1152 | version = "0.2.15"
1153 | source = "registry+https://github.com/rust-lang/crates.io-index"
1154 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1155 |
1156 | [[package]]
1157 | name = "version_check"
1158 | version = "0.9.5"
1159 | source = "registry+https://github.com/rust-lang/crates.io-index"
1160 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1161 |
1162 | [[package]]
1163 | name = "walkdir"
1164 | version = "2.5.0"
1165 | source = "registry+https://github.com/rust-lang/crates.io-index"
1166 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
1167 | dependencies = [
1168 | "same-file",
1169 | "winapi-util",
1170 | ]
1171 |
1172 | [[package]]
1173 | name = "wasi"
1174 | version = "0.11.0+wasi-snapshot-preview1"
1175 | source = "registry+https://github.com/rust-lang/crates.io-index"
1176 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1177 |
1178 | [[package]]
1179 | name = "wasm-bindgen"
1180 | version = "0.2.100"
1181 | source = "registry+https://github.com/rust-lang/crates.io-index"
1182 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
1183 | dependencies = [
1184 | "cfg-if",
1185 | "once_cell",
1186 | "rustversion",
1187 | "wasm-bindgen-macro",
1188 | ]
1189 |
1190 | [[package]]
1191 | name = "wasm-bindgen-backend"
1192 | version = "0.2.100"
1193 | source = "registry+https://github.com/rust-lang/crates.io-index"
1194 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
1195 | dependencies = [
1196 | "bumpalo",
1197 | "log",
1198 | "proc-macro2",
1199 | "quote",
1200 | "syn",
1201 | "wasm-bindgen-shared",
1202 | ]
1203 |
1204 | [[package]]
1205 | name = "wasm-bindgen-macro"
1206 | version = "0.2.100"
1207 | source = "registry+https://github.com/rust-lang/crates.io-index"
1208 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
1209 | dependencies = [
1210 | "quote",
1211 | "wasm-bindgen-macro-support",
1212 | ]
1213 |
1214 | [[package]]
1215 | name = "wasm-bindgen-macro-support"
1216 | version = "0.2.100"
1217 | source = "registry+https://github.com/rust-lang/crates.io-index"
1218 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
1219 | dependencies = [
1220 | "proc-macro2",
1221 | "quote",
1222 | "syn",
1223 | "wasm-bindgen-backend",
1224 | "wasm-bindgen-shared",
1225 | ]
1226 |
1227 | [[package]]
1228 | name = "wasm-bindgen-shared"
1229 | version = "0.2.100"
1230 | source = "registry+https://github.com/rust-lang/crates.io-index"
1231 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
1232 | dependencies = [
1233 | "unicode-ident",
1234 | ]
1235 |
1236 | [[package]]
1237 | name = "winapi"
1238 | version = "0.3.9"
1239 | source = "registry+https://github.com/rust-lang/crates.io-index"
1240 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1241 | dependencies = [
1242 | "winapi-i686-pc-windows-gnu",
1243 | "winapi-x86_64-pc-windows-gnu",
1244 | ]
1245 |
1246 | [[package]]
1247 | name = "winapi-i686-pc-windows-gnu"
1248 | version = "0.4.0"
1249 | source = "registry+https://github.com/rust-lang/crates.io-index"
1250 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1251 |
1252 | [[package]]
1253 | name = "winapi-util"
1254 | version = "0.1.9"
1255 | source = "registry+https://github.com/rust-lang/crates.io-index"
1256 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
1257 | dependencies = [
1258 | "windows-sys 0.59.0",
1259 | ]
1260 |
1261 | [[package]]
1262 | name = "winapi-x86_64-pc-windows-gnu"
1263 | version = "0.4.0"
1264 | source = "registry+https://github.com/rust-lang/crates.io-index"
1265 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1266 |
1267 | [[package]]
1268 | name = "windows-core"
1269 | version = "0.52.0"
1270 | source = "registry+https://github.com/rust-lang/crates.io-index"
1271 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
1272 | dependencies = [
1273 | "windows-targets 0.52.6",
1274 | ]
1275 |
1276 | [[package]]
1277 | name = "windows-sys"
1278 | version = "0.48.0"
1279 | source = "registry+https://github.com/rust-lang/crates.io-index"
1280 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
1281 | dependencies = [
1282 | "windows-targets 0.48.5",
1283 | ]
1284 |
1285 | [[package]]
1286 | name = "windows-sys"
1287 | version = "0.52.0"
1288 | source = "registry+https://github.com/rust-lang/crates.io-index"
1289 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1290 | dependencies = [
1291 | "windows-targets 0.52.6",
1292 | ]
1293 |
1294 | [[package]]
1295 | name = "windows-sys"
1296 | version = "0.59.0"
1297 | source = "registry+https://github.com/rust-lang/crates.io-index"
1298 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1299 | dependencies = [
1300 | "windows-targets 0.52.6",
1301 | ]
1302 |
1303 | [[package]]
1304 | name = "windows-targets"
1305 | version = "0.48.5"
1306 | source = "registry+https://github.com/rust-lang/crates.io-index"
1307 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
1308 | dependencies = [
1309 | "windows_aarch64_gnullvm 0.48.5",
1310 | "windows_aarch64_msvc 0.48.5",
1311 | "windows_i686_gnu 0.48.5",
1312 | "windows_i686_msvc 0.48.5",
1313 | "windows_x86_64_gnu 0.48.5",
1314 | "windows_x86_64_gnullvm 0.48.5",
1315 | "windows_x86_64_msvc 0.48.5",
1316 | ]
1317 |
1318 | [[package]]
1319 | name = "windows-targets"
1320 | version = "0.52.6"
1321 | source = "registry+https://github.com/rust-lang/crates.io-index"
1322 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
1323 | dependencies = [
1324 | "windows_aarch64_gnullvm 0.52.6",
1325 | "windows_aarch64_msvc 0.52.6",
1326 | "windows_i686_gnu 0.52.6",
1327 | "windows_i686_gnullvm",
1328 | "windows_i686_msvc 0.52.6",
1329 | "windows_x86_64_gnu 0.52.6",
1330 | "windows_x86_64_gnullvm 0.52.6",
1331 | "windows_x86_64_msvc 0.52.6",
1332 | ]
1333 |
1334 | [[package]]
1335 | name = "windows_aarch64_gnullvm"
1336 | version = "0.48.5"
1337 | source = "registry+https://github.com/rust-lang/crates.io-index"
1338 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
1339 |
1340 | [[package]]
1341 | name = "windows_aarch64_gnullvm"
1342 | version = "0.52.6"
1343 | source = "registry+https://github.com/rust-lang/crates.io-index"
1344 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
1345 |
1346 | [[package]]
1347 | name = "windows_aarch64_msvc"
1348 | version = "0.48.5"
1349 | source = "registry+https://github.com/rust-lang/crates.io-index"
1350 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
1351 |
1352 | [[package]]
1353 | name = "windows_aarch64_msvc"
1354 | version = "0.52.6"
1355 | source = "registry+https://github.com/rust-lang/crates.io-index"
1356 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
1357 |
1358 | [[package]]
1359 | name = "windows_i686_gnu"
1360 | version = "0.48.5"
1361 | source = "registry+https://github.com/rust-lang/crates.io-index"
1362 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
1363 |
1364 | [[package]]
1365 | name = "windows_i686_gnu"
1366 | version = "0.52.6"
1367 | source = "registry+https://github.com/rust-lang/crates.io-index"
1368 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
1369 |
1370 | [[package]]
1371 | name = "windows_i686_gnullvm"
1372 | version = "0.52.6"
1373 | source = "registry+https://github.com/rust-lang/crates.io-index"
1374 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
1375 |
1376 | [[package]]
1377 | name = "windows_i686_msvc"
1378 | version = "0.48.5"
1379 | source = "registry+https://github.com/rust-lang/crates.io-index"
1380 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
1381 |
1382 | [[package]]
1383 | name = "windows_i686_msvc"
1384 | version = "0.52.6"
1385 | source = "registry+https://github.com/rust-lang/crates.io-index"
1386 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
1387 |
1388 | [[package]]
1389 | name = "windows_x86_64_gnu"
1390 | version = "0.48.5"
1391 | source = "registry+https://github.com/rust-lang/crates.io-index"
1392 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
1393 |
1394 | [[package]]
1395 | name = "windows_x86_64_gnu"
1396 | version = "0.52.6"
1397 | source = "registry+https://github.com/rust-lang/crates.io-index"
1398 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
1399 |
1400 | [[package]]
1401 | name = "windows_x86_64_gnullvm"
1402 | version = "0.48.5"
1403 | source = "registry+https://github.com/rust-lang/crates.io-index"
1404 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
1405 |
1406 | [[package]]
1407 | name = "windows_x86_64_gnullvm"
1408 | version = "0.52.6"
1409 | source = "registry+https://github.com/rust-lang/crates.io-index"
1410 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
1411 |
1412 | [[package]]
1413 | name = "windows_x86_64_msvc"
1414 | version = "0.48.5"
1415 | source = "registry+https://github.com/rust-lang/crates.io-index"
1416 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
1417 |
1418 | [[package]]
1419 | name = "windows_x86_64_msvc"
1420 | version = "0.52.6"
1421 | source = "registry+https://github.com/rust-lang/crates.io-index"
1422 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
1423 |
1424 | [[package]]
1425 | name = "winnow"
1426 | version = "0.6.24"
1427 | source = "registry+https://github.com/rust-lang/crates.io-index"
1428 | checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
1429 | dependencies = [
1430 | "memchr",
1431 | ]
1432 |
1433 | [[package]]
1434 | name = "zerocopy"
1435 | version = "0.7.35"
1436 | source = "registry+https://github.com/rust-lang/crates.io-index"
1437 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
1438 | dependencies = [
1439 | "zerocopy-derive",
1440 | ]
1441 |
1442 | [[package]]
1443 | name = "zerocopy-derive"
1444 | version = "0.7.35"
1445 | source = "registry+https://github.com/rust-lang/crates.io-index"
1446 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
1447 | dependencies = [
1448 | "proc-macro2",
1449 | "quote",
1450 | "syn",
1451 | ]
1452 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quicknotes"
3 | description = "A notes application that makes taking notes... quick"
4 | version = "1.1.0"
5 | license = "BSD-3-Clause"
6 | homepage = "https://github.com/ollien/quicknotes"
7 | repository = "https://github.com/ollien/quicknotes"
8 | edition = "2021"
9 | rust-version = "1.83"
10 |
11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12 |
13 | [dependencies]
14 | anyhow = "1.0.94"
15 | chrono = "0.4.39"
16 | chrono-english = "0.1.7"
17 | clap = { version = "4.5.23", features = ["derive"] }
18 | colored = "2.1.0"
19 | directories = "5.0.1"
20 | itertools = "0.13.0"
21 | nucleo-picker = "0.7.0"
22 | regex = "1.11.1"
23 | rusqlite = { version = "0.32.1", features = ["bundled"] }
24 | rusqlite_migration = "1.3.1"
25 | serde = "1.0.215"
26 | serde_derive = "1.0.215"
27 | sha2 = "0.10.8"
28 | tempfile = "3.14.0"
29 | thiserror = "2.0.6"
30 | toml = "0.8.19"
31 | walkdir = "2.5.0"
32 |
33 | [dev-dependencies]
34 | insta = "1.41.1"
35 | stringreader = "0.1.1"
36 | test-case = "3.3.1"
37 | textwrap = "0.16.1"
38 |
39 | [profile.dev.package]
40 | insta.opt-level = 3
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2025, Nicholas Krichevsky
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `quicknotes`
2 |
3 | [](https://crates.io/crates/quicknotes)
4 | [](https://github.com/ollien/quicknotes/actions/workflows/build_and_test.yml)
5 |
6 | `quicknotes` is a notes application that makes taking notes... quick.
7 | You can edit your notes using your preferred text editor, and all notes are
8 | saved locally in plain text.
9 |
10 | ## Installation
11 |
12 | `quicknotes` can be installed from Cargo via
13 |
14 | ```
15 | cargo install quicknotes
16 | ```
17 |
18 | ## Usage
19 |
20 | To write a new note, all you have to do is run `quicknotes new
...`.
21 | For example, to create a new note about the time machine I am building, I would
22 | run `quicknotes new Flux Capacitor Design`.
23 |
24 | By default, this will launch the editor stored in `$EDITOR`, but this is
25 | configurable. All notes have a preamble, which must be preserved so that
26 | `quicknotes` can index your note, but after that, write what you want! There
27 | are no rules on formatting.
28 |
29 | ```
30 | ---
31 | title = "Flux Capacitor Design"
32 | created_at = 2025-01-11T10:58:00.587807852-05:00
33 | ---
34 |
35 | If my calculations are correct, when this baby hits 88 miles per hour...
36 | ```
37 |
38 | If you want to go back and revise your note, you can use `quicknotes open`,
39 | and search for your note. In general, the index will be automatically built
40 | when editing a note, but if for any reason you need to rebuild the index,
41 | you can run `quicknotes index`.
42 |
43 | `quicknotes` also supports "daily" notes, to aid your journaling. To open
44 | today's daily note, run `quicknotes daily`. This will create a new note with
45 | today's date, or open one if one already exists. You can also open a daily note
46 | from a previous day by doing `quicknotes daily `, where `offset` is a
47 | "fuzzy" date. You can either enter an absolute date (e.g. `2015-10-21`), or a
48 | relative date (e.g. `yesterday`, `2 days ago`).
49 |
50 | ## Configuration
51 |
52 | When you run `quicknotes` for the first time, a configuration file will be
53 | generated for you in your operating system's configuration directory.
54 |
55 |
56 | | Platform | Location |
57 | |------------|---------------------------------------------------------------------|
58 | | Linux | `$XDG_CONFIG_HOME/quicknotes/config.toml` |
59 | | macOS | `~/Library/Application Support/com.ollien.quicknotes/config.toml` |
60 | | Windows | `C:\Users\\AppData\Roaming\ollien\quicknotes\config.toml` |
61 |
62 | ```toml
63 | # required, directory where notes are stored
64 | notes_root = "/home/ferris/Documents/quicknotes/"
65 |
66 | # required, file extension for notes
67 | note_file_extension = ".md"
68 |
69 | # optional, uses $EDITOR if not specified, or `nano` if $EDITOR is unset
70 | editor_command = "/usr/bin/nvim"
71 | ```
72 |
73 | ## Philosophy
74 |
75 | I wrote `quicknotes` for my personal workflow, where I am constantly in a
76 | shell and often just want to write something down quickly. I've found that if
77 | I make my notes system too complicated, I'll end up doing silly things like
78 | pasting things in random `vim` buffers. To that end, I've designed `quicknotes`
79 | to be as frictionless as possible.
80 |
81 | Contributions are absolutely welcome, but I will note that I have designed
82 | `quicknotes` for my needs, first and foremost, and may not accept new features
83 | unless they seem like something I could use. It is not my goal to design a
84 | "swiss-army-knife" notes app, as so many others have; I wanted a note-taking
85 | tool that would give me an easy framework I could use day-to-day, and worked
86 | out of the box.
87 |
88 | ## Development
89 |
90 | `quicknotes` is a bog-standard Rust project, so development should be as simple
91 | as `cargo build` to build the project, and `cargo test` to run the tests.
92 |
93 | ## License
94 | BSD-3
95 |
--------------------------------------------------------------------------------
/src/edit.rs:
--------------------------------------------------------------------------------
1 | use std::io;
2 | use std::path::Path;
3 | use std::process::Command;
4 |
5 | /// A text editor that can edit a given note. There is no requirement about the editor itself,
6 | /// just that it can edit a file at a given path.
7 | pub trait Editor {
8 | fn name(&self) -> &str;
9 | /// Edit the given note
10 | ///
11 | /// # Errors
12 | ///
13 | /// Returns an error if the editor had a problem editing the note.
14 | fn edit(&self, path: &Path) -> io::Result<()>;
15 | }
16 |
17 | impl Editor for &E {
18 | fn name(&self) -> &str {
19 | (*self).name()
20 | }
21 |
22 | fn edit(&self, path: &Path) -> io::Result<()> {
23 | (*self).edit(path)
24 | }
25 | }
26 |
27 | /// An editor that runs a command to launch. This is useful for CLI tools such as `vim`.
28 | pub struct CommandEditor {
29 | command: String,
30 | }
31 |
32 | impl CommandEditor {
33 | #[must_use]
34 | pub fn new(command: String) -> Self {
35 | Self { command }
36 | }
37 | }
38 |
39 | impl Editor for CommandEditor {
40 | fn name(&self) -> &str {
41 | &self.command
42 | }
43 |
44 | fn edit(&self, path: &Path) -> io::Result<()> {
45 | Command::new(&self.command)
46 | .arg(path)
47 | .spawn()?
48 | .wait()
49 | .map(|_output| ())
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/index.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 | use std::fs::OpenOptions;
3 | use std::io;
4 | use std::path::{Path, PathBuf};
5 | use std::str::FromStr;
6 |
7 | use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone};
8 | use rusqlite::{Connection, Params, Row, Statement};
9 | use rusqlite_migration::{Migrations, M};
10 | use thiserror::Error;
11 |
12 | use crate::note::Preamble;
13 | use crate::warning;
14 |
15 | const DB_DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.f";
16 |
17 | #[derive(Clone, Debug, Eq, PartialEq)]
18 | pub struct IndexedNote {
19 | pub preamble: Preamble,
20 | pub kind: NoteKind,
21 | }
22 |
23 | #[derive(Clone, Copy, Debug, PartialEq, Eq)]
24 | pub enum NoteKind {
25 | Note,
26 | Daily,
27 | }
28 |
29 | impl NoteKind {
30 | fn to_sql_enum(self) -> String {
31 | match self {
32 | Self::Note => "note".to_string(),
33 | Self::Daily => "daily".to_string(),
34 | }
35 | }
36 |
37 | fn try_from_sql_enum(sql_enum: &str) -> Result {
38 | match sql_enum {
39 | "note" => Ok(Self::Note),
40 | "daily" => Ok(Self::Daily),
41 | _ => Err(InvalidNoteKindString(sql_enum.to_owned())),
42 | }
43 | }
44 | }
45 |
46 | #[derive(Error, Debug)]
47 | #[error("invalid note kind '{0}'")]
48 | struct InvalidNoteKindString(String);
49 |
50 | pub fn open(path: &Path) -> Result {
51 | let mut connection = Connection::open(path).map_err(OpenError::ConnectionOpenError)?;
52 |
53 | setup_database(&mut connection).map_err(OpenError::MigrationError)?;
54 |
55 | Ok(connection)
56 | }
57 |
58 | #[derive(Error, Debug)]
59 | pub enum OpenError {
60 | #[error("could not open index: {0}")]
61 | ConnectionOpenError(rusqlite::Error),
62 |
63 | #[error("could not setup index: {0}")]
64 | MigrationError(MigrationError),
65 | }
66 |
67 | #[derive(Error, Debug)]
68 | #[error(transparent)]
69 | pub struct MigrationError(#[from] rusqlite_migration::Error);
70 |
71 | pub fn reset(path: &Path) -> Result<(), ResetError> {
72 | OpenOptions::new()
73 | .write(true)
74 | .truncate(true)
75 | .open(path)
76 | .map(|_file| ())
77 | .or_else(|err| {
78 | if err.kind() == io::ErrorKind::NotFound {
79 | Ok(())
80 | } else {
81 | Err(ResetError(err))
82 | }
83 | })
84 | }
85 |
86 | #[derive(Error, Debug)]
87 | #[error("could not reset index database: {0}")]
88 | pub struct ResetError(io::Error);
89 |
90 | pub fn add_note(
91 | connection: &mut Connection,
92 | preamble: &Preamble,
93 | kind: NoteKind,
94 | path: &Path,
95 | ) -> Result<(), InsertError> {
96 | let path_string = path
97 | .to_str()
98 | .ok_or_else(|| InsertError::BadPath(path.to_owned()))?;
99 |
100 | connection
101 | .execute(
102 | "INSERT INTO notes VALUES (?1, ?2, ?3, ?4, ?5)
103 | ON CONFLICT(filepath) DO UPDATE SET
104 | title=?2,
105 | created_at=?3,
106 | utc_offset_seconds=?4,
107 | kind=?5
108 | ;",
109 | (
110 | &path_string,
111 | &preamble.title,
112 | preamble.created_at.format(DB_DATE_FORMAT).to_string(),
113 | preamble.created_at.offset().local_minus_utc(),
114 | kind.to_sql_enum(),
115 | ),
116 | )
117 | .map(|_rows| ())
118 | .map_err(InsertError::DatabaseError)
119 | }
120 |
121 | #[derive(Error, Debug)]
122 | pub enum InsertError {
123 | #[error("could not insert into index database: {0}")]
124 | DatabaseError(rusqlite::Error),
125 |
126 | #[error("cannot insert a non-utf-8 path to the database: {0}")]
127 | BadPath(PathBuf),
128 | }
129 |
130 | pub fn all_notes(
131 | connection: &mut Connection,
132 | ) -> Result, LookupError> {
133 | let mut query = connection
134 | .prepare("SELECT filepath, title, created_at, utc_offset_seconds, kind FROM notes;")?;
135 |
136 | lookup_notes(&mut query, [])
137 | }
138 |
139 | pub fn notes_with_kind(
140 | connection: &mut Connection,
141 | kind: NoteKind,
142 | ) -> Result, LookupError> {
143 | let mut query = connection.prepare(
144 | "SELECT filepath, title, created_at, utc_offset_seconds, kind FROM notes WHERE kind=?;",
145 | )?;
146 |
147 | lookup_notes(&mut query, [kind.to_sql_enum()])
148 | }
149 |
150 | fn lookup_notes(
151 | query: &mut Statement<'_>,
152 | params: P,
153 | ) -> Result, LookupError> {
154 | let notes = query
155 | .query_map(params, |row| match unpack_row(row) {
156 | Err(QueryFailure::DatabaseFailure(err)) => Err(err),
157 | Err(QueryFailure::InvalidRow(msg)) => {
158 | // TODO: perhaps we want some kind of read-repair here.
159 | warning!("{msg}; skipping entry");
160 |
161 | Ok(None)
162 | }
163 | Ok((path, preamble)) => Ok(Some((path, preamble))),
164 | })?
165 | .filter_map(Result::transpose)
166 | .collect::, _>>()?;
167 |
168 | Ok(notes)
169 | }
170 |
171 | #[derive(Error, Debug)]
172 | #[error(transparent)]
173 | pub struct LookupError(#[from] rusqlite::Error);
174 |
175 | pub fn delete_note(connection: &mut Connection, path: &Path) -> Result<(), DeleteError> {
176 | let path_string = path
177 | .to_str()
178 | .ok_or_else(|| DeleteError::BadPath(path.to_owned()))?;
179 |
180 | connection
181 | .execute("DELETE FROM notes WHERE filepath = ?;", (&path_string,))
182 | .map(|_affected| ())
183 | .map_err(DeleteError::DatabaseError)
184 | }
185 |
186 | #[derive(Error, Debug)]
187 | pub enum DeleteError {
188 | #[error("could not delete from index database: {0}")]
189 | DatabaseError(rusqlite::Error),
190 |
191 | #[error("cannot delete a non-utf-8 path from the database: {0}")]
192 | BadPath(PathBuf),
193 | }
194 |
195 | fn setup_database(connection: &mut Connection) -> Result<(), MigrationError> {
196 | migrations().to_latest(connection)?;
197 |
198 | Ok(())
199 | }
200 |
201 | fn unpack_row(row: &Row) -> Result<(PathBuf, IndexedNote), QueryFailure> {
202 | let raw_filepath: String = row.get(0)?;
203 | let title: String = row.get(1)?;
204 | let raw_created_at: String = row.get(2)?;
205 | let raw_utc_offset: i32 = row.get(3)?;
206 | let raw_kind: String = row.get(4)?;
207 |
208 | let filepath = PathBuf::from_str(&raw_filepath).unwrap(); // infallible error type
209 | let created_at = datetime_from_database(&raw_created_at, raw_utc_offset)?;
210 | let kind = NoteKind::try_from_sql_enum(&raw_kind)
211 | .map_err(|err| QueryFailure::InvalidRow(err.to_string()))?;
212 |
213 | Ok((
214 | filepath,
215 | IndexedNote {
216 | kind,
217 | preamble: Preamble { title, created_at },
218 | },
219 | ))
220 | }
221 |
222 | fn datetime_from_database(
223 | timestamp: &str,
224 | utc_offset_seconds: i32,
225 | ) -> Result, QueryFailure> {
226 | let offset = FixedOffset::east_opt(utc_offset_seconds).ok_or_else(|| {
227 | QueryFailure::InvalidRow(format!("Invalid UTC offset \"{utc_offset_seconds}\""))
228 | })?;
229 |
230 | NaiveDateTime::parse_from_str(timestamp, DB_DATE_FORMAT)
231 | .map_err(|err| QueryFailure::InvalidRow(format!("Invalid date \"{timestamp}\", {err}")))
232 | .and_then(|datetime| {
233 | offset
234 | .from_local_datetime(&datetime)
235 | .single()
236 | .ok_or_else(|| {
237 | QueryFailure::InvalidRow(format!(
238 | "Invalid date \"{timestamp} at offset {utc_offset_seconds}\""
239 | ))
240 | })
241 | })
242 | }
243 |
244 | enum QueryFailure {
245 | InvalidRow(String),
246 | DatabaseFailure(rusqlite::Error),
247 | }
248 |
249 | impl From for QueryFailure {
250 | fn from(error: rusqlite::Error) -> Self {
251 | Self::DatabaseFailure(error)
252 | }
253 | }
254 |
255 | fn migrations() -> Migrations<'static> {
256 | Migrations::new(vec![
257 | M::up(
258 | "CREATE TABLE notes (
259 | filepath TEXT PRIMARY KEY,
260 | title TEXT NOT NULL,
261 | created_at DATETIME NOT NULL,
262 | utc_offset_seconds INTEGER NOT NULL
263 | );",
264 | ),
265 | // Add the notes column. This migration is imperfect, because it classifies everything as 'note',
266 | // but given the index can be recreated, this is no big deal. Plus, I am the only one who used
267 | // the version without this :)
268 | M::up(
269 | r"
270 | CREATE TEMPORARY TABLE intermediate_notes AS
271 | SELECT
272 | filepath, title, created_at, utc_offset_seconds, 'note'
273 | FROM notes;
274 | DROP TABLE notes;
275 | CREATE TABLE notes (
276 | filepath TEXT PRIMARY KEY,
277 | title TEXT NOT NULL,
278 | created_at DATETIME NOT NULL,
279 | utc_offset_seconds INTEGER NOT NULL,
280 | kind CHECK (kind IN ('note', 'daily')) NOT NULL
281 | );
282 | INSERT INTO notes SELECT * FROM intermediate_notes;
283 | DROP TABLE intermediate_notes;
284 | ",
285 | ),
286 | ])
287 | }
288 |
289 | #[cfg(test)]
290 | mod tests {
291 | use std::path::PathBuf;
292 | use std::str::FromStr;
293 |
294 | use chrono::{FixedOffset, TimeZone};
295 |
296 | use super::*;
297 |
298 | #[test]
299 | pub fn migrations_valid() {
300 | migrations()
301 | .validate()
302 | .expect("failed to validate migrations");
303 | }
304 |
305 | #[test]
306 | pub fn can_insert_note() {
307 | let mut connection = Connection::open_in_memory().expect("could not open test database");
308 | setup_database(&mut connection).expect("could not setup test database");
309 |
310 | let preamble = Preamble {
311 | title: "Hello world".to_string(),
312 | created_at: FixedOffset::east_opt(-7 * 60 * 60)
313 | .unwrap()
314 | .with_ymd_and_hms(2015, 10, 21, 7, 28, 0)
315 | .single()
316 | .unwrap(),
317 | };
318 |
319 | add_note(
320 | &mut connection,
321 | &preamble,
322 | NoteKind::Note,
323 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/hello-world.txt").unwrap(),
324 | )
325 | .unwrap();
326 | }
327 |
328 | #[test]
329 | pub fn can_update_note_by_reinserting() {
330 | let mut connection = Connection::open_in_memory().expect("could not open test database");
331 | setup_database(&mut connection).expect("could not setup test database");
332 |
333 | let preamble1 = Preamble {
334 | title: "Hello world".to_string(),
335 | created_at: FixedOffset::east_opt(-7 * 60 * 60)
336 | .unwrap()
337 | .with_ymd_and_hms(2015, 10, 21, 7, 28, 0)
338 | .single()
339 | .unwrap(),
340 | };
341 |
342 | let preamble2 = Preamble {
343 | title: "Hello world!!".to_string(),
344 | ..preamble1
345 | };
346 |
347 | // insert the first note
348 | add_note(
349 | &mut connection,
350 | &preamble1,
351 | NoteKind::Note,
352 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/hello-world.txt").unwrap(),
353 | )
354 | .unwrap();
355 |
356 | // ... then update
357 | add_note(
358 | &mut connection,
359 | &preamble2,
360 | NoteKind::Note,
361 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/hello-world.txt").unwrap(),
362 | )
363 | .expect("Failed to update note");
364 |
365 | let notes = all_notes(&mut connection)
366 | .unwrap()
367 | .into_iter()
368 | .collect::>();
369 |
370 | // should only have the one note, which is the valid one
371 | assert_eq!(
372 | notes,
373 | [(
374 | PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/hello-world.txt")
375 | .unwrap(),
376 | IndexedNote {
377 | preamble: preamble2,
378 | kind: NoteKind::Note
379 | }
380 | )]
381 | );
382 | }
383 |
384 | #[test]
385 | pub fn cannot_insert_note_with_invalid_utf8_path() {
386 | let mut connection = Connection::open_in_memory().expect("could not open test database");
387 |
388 | let preamble = Preamble {
389 | title: "Hello world".to_string(),
390 | created_at: FixedOffset::east_opt(-7 * 60 * 60)
391 | .unwrap()
392 | .with_ymd_and_hms(2015, 10, 21, 7, 28, 0)
393 | .single()
394 | .unwrap(),
395 | };
396 |
397 | // construct an invalid path (this is platform dependent)
398 | #[cfg(unix)]
399 | let path = {
400 | use std::ffi::OsStr;
401 | use std::os::unix::ffi::OsStrExt;
402 | PathBuf::from(OsStr::from_bytes(&[0xFF, 0xFF]))
403 | };
404 |
405 | // construct an invalid path (this is platform dependent)
406 | #[cfg(windows)]
407 | let path = {
408 | use std::ffi::OsString;
409 | use std::os::windows::ffi::OsStringExt;
410 | PathBuf::from(OsString::from_wide(&[0xD800]))
411 | };
412 |
413 | #[cfg(not(any(unix, windows)))]
414 | panic!("Cannot run test on neither windows or unix");
415 |
416 | let insert_result = add_note(&mut connection, &preamble, NoteKind::Note, &path);
417 |
418 | assert!(insert_result.is_err());
419 | }
420 |
421 | #[test]
422 | pub fn can_select_inserted_notes() {
423 | let mut connection = Connection::open_in_memory().expect("could not open test database");
424 | setup_database(&mut connection).expect("could not setup test database");
425 |
426 | let preamble1 = Preamble {
427 | title: "Hello world".to_string(),
428 | created_at: FixedOffset::east_opt(-7 * 60 * 60)
429 | .unwrap()
430 | .with_ymd_and_hms(2015, 10, 21, 7, 28, 0)
431 | .single()
432 | .unwrap(),
433 | };
434 |
435 | add_note(
436 | &mut connection,
437 | &preamble1,
438 | NoteKind::Note,
439 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/hello-world.txt").unwrap(),
440 | )
441 | .unwrap();
442 |
443 | let preamble2 = Preamble {
444 | title: "notes notes notes".to_string(),
445 | created_at: FixedOffset::east_opt(-7 * 60 * 60)
446 | .unwrap()
447 | .with_ymd_and_hms(2015, 10, 21, 7, 28, 0)
448 | .single()
449 | .unwrap(),
450 | };
451 |
452 | add_note(
453 | &mut connection,
454 | &preamble2,
455 | NoteKind::Note,
456 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/notes-notes-notes.txt")
457 | .unwrap(),
458 | )
459 | .unwrap();
460 |
461 | let notes = all_notes(&mut connection).expect("Failed to query notes");
462 |
463 | assert_eq!(
464 | notes.get(
465 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/hello-world.txt")
466 | .unwrap(),
467 | ),
468 | Some(&IndexedNote {
469 | preamble: preamble1,
470 | kind: NoteKind::Note
471 | })
472 | );
473 |
474 | assert_eq!(
475 | notes.get(
476 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/notes-notes-notes.txt")
477 | .unwrap(),
478 | ),
479 | Some(&IndexedNote {
480 | preamble: preamble2,
481 | kind: NoteKind::Note
482 | })
483 | );
484 | }
485 |
486 | #[test]
487 | pub fn selecting_skips_notes_with_malformed_timestamps() {
488 | let mut connection = Connection::open_in_memory().expect("could not open test database");
489 | setup_database(&mut connection).expect("could not setup test database");
490 |
491 | let valid_note_preamble = Preamble {
492 | title: "This note is valid".to_string(),
493 | created_at: FixedOffset::east_opt(-7 * 60 * 60)
494 | .unwrap()
495 | .with_ymd_and_hms(2015, 10, 21, 7, 28, 0)
496 | .single()
497 | .unwrap(),
498 | };
499 |
500 | add_note(
501 | &mut connection,
502 | &valid_note_preamble,
503 | NoteKind::Note,
504 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/this-note-is-valid.txt")
505 | .unwrap(),
506 | )
507 | .unwrap();
508 |
509 | connection
510 | .execute(
511 | r#"INSERT INTO notes VALUES (
512 | "/home/ferris/Documents/quicknotes/notes/this-note-is-not-valid.txt",
513 | "This note is not valid",
514 | "malformed timestamp",
515 | 0,
516 | 'note'
517 | )"#,
518 | [],
519 | )
520 | .unwrap();
521 |
522 | let notes = all_notes(&mut connection)
523 | .expect("Failed to query notes")
524 | .into_iter()
525 | .collect::>();
526 |
527 | // should only have the one note, which is the valid one
528 | assert_eq!(
529 | notes,
530 | [(
531 | PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/this-note-is-valid.txt")
532 | .unwrap(),
533 | IndexedNote {
534 | preamble: valid_note_preamble,
535 | kind: NoteKind::Note
536 | }
537 | )]
538 | );
539 | }
540 |
541 | #[test]
542 | pub fn selecting_filters_notes_of_other_kinds() {
543 | let mut connection = Connection::open_in_memory().expect("could not open test database");
544 | setup_database(&mut connection).expect("could not setup test database");
545 |
546 | let preamble1 = Preamble {
547 | title: "Hello world".to_string(),
548 | created_at: FixedOffset::east_opt(-7 * 60 * 60)
549 | .unwrap()
550 | .with_ymd_and_hms(2015, 10, 21, 7, 28, 0)
551 | .single()
552 | .unwrap(),
553 | };
554 |
555 | add_note(
556 | &mut connection,
557 | &preamble1,
558 | NoteKind::Note,
559 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/hello-world.txt").unwrap(),
560 | )
561 | .unwrap();
562 |
563 | let preamble2 = Preamble {
564 | title: "2015-10-21".to_string(),
565 | created_at: FixedOffset::east_opt(-7 * 60 * 60)
566 | .unwrap()
567 | .with_ymd_and_hms(2015, 10, 21, 7, 28, 0)
568 | .single()
569 | .unwrap(),
570 | };
571 |
572 | add_note(
573 | &mut connection,
574 | &preamble2,
575 | NoteKind::Daily,
576 | &PathBuf::from_str("/home/ferris/Documents/quicknotes/daily/2015-10-21.txt").unwrap(),
577 | )
578 | .unwrap();
579 |
580 | let notes =
581 | notes_with_kind(&mut connection, NoteKind::Note).expect("Failed to query notes");
582 |
583 | let expected_entry = (
584 | PathBuf::from_str("/home/ferris/Documents/quicknotes/notes/hello-world.txt").unwrap(),
585 | IndexedNote {
586 | preamble: preamble1,
587 | kind: NoteKind::Note,
588 | },
589 | );
590 |
591 | assert_eq!(notes.into_iter().collect::>(), vec![expected_entry]);
592 | }
593 |
594 | #[test]
595 | pub fn delete_note_is_idempotent() {
596 | let mut connection = Connection::open_in_memory().expect("could not open test database");
597 | setup_database(&mut connection).expect("could not setup test database");
598 |
599 | // if this is ok, the test passes
600 | delete_note(&mut connection, Path::new("/does/not/exist")).expect("could not delete note");
601 | }
602 |
603 | #[test]
604 | pub fn delete_note_removes_notes_from_database() {
605 | let mut connection = Connection::open_in_memory().expect("could not open test database");
606 | setup_database(&mut connection).expect("could not setup test database");
607 |
608 | connection
609 | .execute(
610 | r#"INSERT INTO notes VALUES (
611 | "/home/ferris/Documents/quicknotes/notes/my-cool-note.txt",
612 | "Hello, world!",
613 | "2015-10-22T07:28:00.000",
614 | 0,
615 | 'note'
616 | )"#,
617 | [],
618 | )
619 | .unwrap();
620 |
621 | let notes = all_notes(&mut connection)
622 | .expect("Failed to query notes")
623 | .into_iter()
624 | .collect::>();
625 |
626 | // Prove the note is there
627 | assert!(!notes.is_empty());
628 |
629 | delete_note(
630 | &mut connection,
631 | Path::new("/home/ferris/Documents/quicknotes/notes/my-cool-note.txt"),
632 | )
633 | .expect("could not delete note");
634 |
635 | let notes = all_notes(&mut connection)
636 | .expect("Failed to query notes")
637 | .into_iter()
638 | .collect::>();
639 |
640 | // Prove the note is now gone
641 | assert!(notes.is_empty());
642 | }
643 | }
644 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![warn(clippy::all, clippy::pedantic)]
2 | #![allow(clippy::enum_variant_names)]
3 |
4 | use std::collections::HashMap;
5 | use std::fs::{self, File, OpenOptions};
6 | use std::io;
7 | use std::path::{Path, PathBuf};
8 |
9 | use chrono::{DateTime, NaiveDate, TimeZone};
10 | use index::{LookupError as IndexLookupError, OpenError as IndexOpenError};
11 | use io::Write;
12 | use note::{Preamble, SerializeError};
13 | use rusqlite::Connection;
14 | use storage::{
15 | store_if_different, StoreIfDifferentError, StoreNote, StoreNoteAt, StoreNoteIn, TempFileHandle,
16 | };
17 | use tempfile::{Builder as TempFileBuilder, NamedTempFile, TempPath};
18 | use thiserror::Error;
19 | use walkdir::{DirEntry, WalkDir};
20 |
21 | pub use edit::{CommandEditor, Editor};
22 | pub use index::{IndexedNote, NoteKind};
23 | pub use note::Preamble as NotePreamble;
24 |
25 | mod edit;
26 | mod index;
27 | mod note;
28 | mod storage;
29 |
30 | macro_rules! warning {
31 | ($($arg:tt)*) => {{
32 | use colored::Colorize;
33 |
34 | eprint!("{}: ", "warning".yellow());
35 | eprintln!($($arg)*)
36 | }};
37 | }
38 |
39 | pub(crate) use warning;
40 |
41 | pub struct NoteConfig {
42 | pub root_dir: PathBuf,
43 | pub file_extension: String,
44 | pub temp_root_override: Option,
45 | }
46 |
47 | impl NoteConfig {
48 | #[must_use]
49 | pub fn notes_directory_path(&self) -> PathBuf {
50 | self.root_dir.join(Path::new("notes"))
51 | }
52 |
53 | #[must_use]
54 | pub fn daily_directory_path(&self) -> PathBuf {
55 | self.root_dir.join(Path::new("daily"))
56 | }
57 |
58 | #[must_use]
59 | pub fn index_db_path(&self) -> PathBuf {
60 | self.root_dir.join(Path::new(".index.sqlite3"))
61 | }
62 | }
63 |
64 | /// Create a new note.
65 | ///
66 | /// The note will be created in the notes directory, with a name as close to the given title as
67 | /// possible, and then opened in the editor.
68 | ///
69 | /// Returns the path of the note, or None if nothing was written to the note.
70 | ///
71 | /// # Errors
72 | ///
73 | /// Returns an error if there is an I/O failure creating the note, the editor fails to launch, or
74 | /// if there is a problem adding the note to the index.
75 | pub fn make_note(
76 | config: &NoteConfig,
77 | editor: E,
78 | title: String,
79 | creation_time: &DateTime,
80 | ) -> Result