git.m455.casa

fa

clone url: git://git.m455.casa/fa


src/fa.fnl

1 #!/usr/bin/env fennel
2
3 ;; For debugging ----------------------;;
4 ;; (local view (require :fennelview))
5 ;; (global pp (fn [x] (print (view x))))
6 ;; ------------------------------------;;
7
8 (local lume (require :lume))
9 (local home (os.getenv "HOME"))
10
11 (local program-name "fa")
12 (local program-version "0.5.0")
13 (local program-file (.. home "/." program-name))
14
15 ;; Not in use (yet?)
16 ;; This gets lowercased by (get-day)
17 ;; (local day-fmt "%a")
18
19 (local event-symbol "!")
20
21 (local keyword-today "today")
22
23 (local cmds-help
24 {:human "help"
25 :long "--help"
26 :short "-h"})
27 (local cmds-version
28 {:human "version"
29 :long "--version"
30 :short "-v"})
31 (local cmd-init "init")
32 (local cmd-add "add")
33 (local cmd-rm "rm")
34 (local cmd-ls "ls")
35 (local cmd-notify "notify")
36
37 (local newline "\n")
38 (local double-newline "\n\n")
39 (local space " ")
40 (local indent-2 " ")
41
42 (local messages
43 {:usage
44 (string.format "For usage, type '%s %s'." program-name cmds-help.human)
45
46 :program-version
47 (string.format "AGPLv3 -- %s" program-version)
48
49 :file-already-exists
50 (string.format "A file or directory named '%s' already exists." program-file)
51
52 :date-already-exists
53 (.. "The following event already exists in your agenda:" newline
54 "%s %s" newline
55 "Choose one of the options below:" newline
56 "1. Replace the old event" newline
57 "2. Add the event to the existing date" newline
58 "3. Cancell even creation" newline
59 "> ")
60
61 :file-doesnt-exist
62 (string.format "%s doesn't exist. Try running '%s %s' (without the quotation marks)."
63 program-file
64 program-name
65 cmd-init)
66
67 :cancelling-date-creation
68 "Cancelling date creation"
69
70 :not-an-option
71 "Error: '%s' is not an option."
72
73 :date-format
74 (.. "Error: Date format must be mmdd" newline
75 "Example: 1231 \"The last day of december\"")
76
77 :key-doesnt-exist
78 "The date '%s' wasn't found in your agenda."
79
80 :sub-key-doesnt-exist
81 "The item '%s' on %s wasn't found in your agenda."
82
83 :agenda-empty
84 "There is nothing in your agenda."
85
86 :initialized
87 (string.format "Successfully created %s" program-file)
88
89 :added
90 "Added '%s' to your agenda."
91
92 :removed
93 "Removed '%s' from your agenda."})
94
95 (fn key-exists? [tbl key]
96 (if (= (type tbl) :table)
97 (let [tbl-keys (lume.keys tbl)]
98 (if (lume.any tbl-keys #(= key $1))
99 true
100 false))
101 false))
102
103 (fn get-date []
104 (os.date "%m%d"))
105
106 ;; Not in use (yet?)
107 ;; (fn get-day []
108 ;; (string.lower (os.date day-fmt)))
109
110 (fn file-exists? [str]
111 (let [file-in (io.open str :r)]
112 (if file-in
113 (file-in:close)
114 false)))
115
116 (fn file->table [str]
117 (if (file-exists? str)
118 (with-open [file-in (io.open str :r)]
119 (lume.deserialize (file-in:read :*all)))
120 nil))
121
122 (fn table->file [str tbl]
123 (if (file-exists? str)
124 (with-open [file-out (io.open str :w)]
125 (file-out:write (lume.serialize tbl)))
126 nil))
127
128 (fn create-prefix [seq key]
129 (if (> (# seq) 1)
130 (string.format "%s. " key)
131 ""))
132
133 (fn print-format [str ...]
134 (print (string.format str ...)))
135
136 (fn init/create-file []
137 (with-open [file-out (io.open program-file :w)]
138 (file-out:write (lume.serialize []))
139 (print messages.initialized)))
140
141 (fn init []
142 (if (file-exists? program-file)
143 (print messages.file-already-exists)
144 (init/create-file)))
145
146 (fn add/add-event [date event-str]
147 (let [tbl (file->table program-file)]
148 (if (key-exists? tbl date)
149 (tset tbl date (+ 1 (length (. tbl date))) event-str)
150 (tset tbl date [event-str]))
151 (table->file program-file tbl)
152 (print-format messages.added event-str)))
153
154 (fn add [date-str event-str]
155 (if (file-exists? program-file)
156 (let [date (if (= date-str keyword-today) (get-date) date-str)]
157 (add/add-event date event-str))
158 (print messages.file-doesnt-exist)))
159
160 (fn rm [date-str event-str]
161 (if (file-exists? program-file)
162 (let [tbl (file->table program-file)
163 date (if (= date-str keyword-today) (get-date) date-str)
164 events-seq (. tbl date)
165 events-seq-index (tonumber event-str)
166 date-exists? (key-exists? tbl date)
167 event-exists? (key-exists? events-seq events-seq-index)]
168 (match [date-exists? event-exists? event-str]
169 ;; If only the date is given
170 [true false nil]
171 (do (tset tbl date nil)
172 (table->file program-file tbl)
173 (print-format messages.removed date))
174
175 ;; If date and event exist
176 [true true]
177 (let [event (. events-seq events-seq-index)]
178 (table.remove events-seq events-seq-index)
179 (if (= (# events-seq) 0)
180 (do (tset tbl date nil)
181 (table->file program-file tbl)
182 (print-format messages.removed event))
183 (do (tset tbl date events-seq)
184 (table->file program-file tbl)
185 (print-format messages.removed event))))
186
187 ;; If date exists, but event doesn't
188 [true false]
189 (print-format messages.sub-key-doesnt-exist event-str date)
190
191 ;; If date doesn't exist
192 [false]
193 (print-format messages.key-doesnt-exist date)))
194 (print messages.file-doesnt-exist)))
195
196 (fn notify []
197 (if (file-exists? program-file)
198 (when (key-exists? (file->table program-file) (get-date))
199 (io.write event-symbol))
200 (print messages.file-doesnt-exist)))
201
202 (fn ls/print-seq [events-seq]
203 (let [keys (lume.keys events-seq)]
204 ;; Keys are used here instead of the sequence values, so the keys
205 ;; print out in order
206 (each [_ key (pairs keys)]
207 (let [event (. events-seq key)
208 event-prefix (create-prefix events-seq key)]
209 (print-format "%s%s" event-prefix event)))))
210
211 (fn ls/ls-date [date-str]
212 (if (file-exists? program-file)
213 (let [tbl (file->table program-file)
214 date (if (= date-str keyword-today) (get-date) date-str)]
215 (when (key-exists? tbl date)
216 (let [events-seq (. tbl date)]
217 (ls/print-seq events-seq))))
218 (print messages.file-doesnt-exist)))
219
220 (fn ls/print-seq [events-seq date]
221 (let [[first-event] events-seq
222 [first-key & rest-keys] (lume.keys events-seq)
223 first-event-prefix (create-prefix events-seq first-key)]
224 (print-format "%s%s" first-event-prefix first-event)
225 ;; The rest-keys is used here instead of a rest-events, so the
226 ;; keys for the underlying table in the sequence don't restart at 1
227 (each [_ key (pairs rest-keys)]
228 (let [event (. events-seq key)
229 padding (string.rep " " (+ 1 (# date)))]
230 (print-format "%s %s. %s" padding key event)))))
231
232 (fn ls/sort-and-print-tbl [tbl tbl-keys]
233 (table.sort tbl-keys)
234 (each [_ date (pairs tbl-keys)]
235 ;; Print the date without trailing newline character
236 (io.write (string.format "%s: " date))
237 (let [events-seq (. tbl date)]
238 (ls/print-seq events-seq date))))
239
240 (fn ls [date-str]
241 (if (file-exists? program-file)
242 (if date-str
243 (ls/ls-date date-str)
244 (let [tbl (file->table program-file)
245 tbl-keys (lume.keys tbl)
246 tbl-length (# tbl-keys)]
247 (if (> tbl-length 0)
248 (ls/sort-and-print-tbl tbl tbl-keys)
249 (print messages.agenda-empty))))
250 (print messages.file-doesnt-exist)))
251
252 (fn help []
253 (print
254 (.. "Usage:\n"
255 " fa <command> [<arg>] [<arg>]\n"
256 "\n"
257 "Commands:\n"
258 " init - Creates your agenda.\n"
259 " add <mmdd|today> \"Quoted text\" - Adds an event to your agenda.\n"
260 " rm <mmdd|today> - Removes a given date from your agenda.\n"
261 " rm <mmdd|today> [<number>] - Removes an event from given date from your agenda.\n"
262 " ls [<mmdd|today>] - Lists the events on the given date.\n"
263 " ls - Lists all dates and their events.\n"
264 " notify - Displays a \"!\" if an event exists today.\n"
265 " version - Prints the current version of fa.\n"
266 "\n"
267 "Examples:\n"
268 " fa init\n"
269 " fa add 1231 \"Sherry's birthday\"\n"
270 " fa add today \"Sherry's birthday\"\n"
271 " fa rm 1231\n"
272 " fa rm 1231 3 (See note below)\n"
273 " fa rm today\n"
274 " fa rm today 3 (See note below)\n"
275 " fa ls today\n"
276 " fa ls 1231\n"
277 " fa ls\n"
278 " fa notify\n"
279 " fa version\n"
280 "\n"
281 "Note: You may need to run 'fa ls' to see which number correlates to which event.\n")))
282
283 (fn process-args [arg-tbl]
284 (match arg-tbl
285 [cmd-add date-str event-str nil]
286 (add date-str event-str)
287
288 [cmd-rm date-str event-str nil]
289 (rm date-str event-str)
290
291 [cmd-rm date-str nil]
292 (rm date-str)
293
294 [cmd-ls date-str nil]
295 (ls date-str)
296
297 [cmd-ls nil]
298 (ls)
299
300 ([cmd nil] ? (or (= cmd cmds-help.human)
301 (= cmd cmds-help.long)
302 (= cmd cmds-help.short)))
303 (help)
304
305 ([cmd nil] ? (or (= cmd cmds-version.human)
306 (= cmd cmds-version.long)
307 (= cmd cmds-version.short)))
308 (print messages.program-version)
309
310 [cmd-init nil]
311 (init)
312
313 [cmd-notify nil]
314 (notify)
315
316 _ (print messages.usage)))
317
318 (fn main [arg-tbl]
319 (process-args arg-tbl))
320
321 (main arg)