| 
					
				 | 
			
			
				@@ -25,7 +25,7 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; Used for pretty printing output 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defconstant +CELL-FORMATS+ '(:left   "~vA" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                               :center "~v:@<~A~>" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                               :right  "~v@A")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                              :right  "~v@A")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defun format-table (stream data &key (column-label (loop for i from 1 to (length (car data)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                                                           collect (format nil "COL~D" i))) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -74,11 +74,29 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; the user provided a valid key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defun check-month (month-key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (if (stringp month-key) month-key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      (return-from check-month NIL)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	(return-from check-month NIL)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (if (ppcre:scan *old-month-line-regex* month-key) month-key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       (if (ppcre:scan *new-month-line-regex* month-key) month-key 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	  (return-from check-month NIL)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;; Called like: (add-month '202107) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defun add-month (month-key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (if (check-month month-key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (if (not (gethash month-key *records*)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	  (progn 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	    (setf (gethash month-key *records*) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		  (make-hash-table :test 'equalp)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	    month-key)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defun add-expense-to-month (expense value month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (if (gethash month *records*) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (setf (gethash expense (gethash month *records*)) value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (progn 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	(print (concatenate 'string "Adding" month)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	(if (add-month month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	    (setf (gethash expense (gethash month *records*)) value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	    (print (concatenate 'string "Failed to add" month)))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (opts:define-opts 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (:name :help 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    :description "Print help text" 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -89,6 +107,21 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    :short #\p 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    :long "print-month" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    :arg-parser #'check-month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (:name :add-expense 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :description "Non interactive interface for recording an expense. Expects expense name as an argument, and requires -v|--value and -m|month" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :short #\e 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :long "add-expense" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :arg-parser #'identity) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (:name :value 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :description "Used with -e|--add-expense. Must be an integer." 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :short #\v 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :long "value" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :arg-parser #'parse-integer) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (:name :month 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :description "Used with -e|--add-expense. Must be a valid month key." 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :short #\m 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :long "month" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   :arg-parser #'check-month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (:name :interactive-mode 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    :description "Run in interactive mode" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    :short #\i 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -161,12 +194,6 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   "Get records from remote server" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (setf *records* (yason:parse (decrypt-records key (gethash "content" (download-records)))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-;; Called like: (add-month '202107) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-(defun add-month (month-key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (if (check-month month-key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      (setf (gethash month-key *records*) (make-hash-table :test 'equalp)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      (return-from add-month NIL))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; Taken from practical common lisp 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defun prompt-read (prompt) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (format *query-io* "~a: " prompt) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -179,13 +206,6 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    (parse-integer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (prompt-read "Enter expense value")))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-(defun add-expense-to-month (month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (if (gethash month *records*) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      (let ((innerhash (gethash month *records*)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	    (exp-l (prompt-for-expense))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	(setf (gethash (first exp-l) innerhash) (second exp-l))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      (add-expense-to-month (add-month month)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; Given key for *records* hash, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; print expenses/values for month 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defun dump-month (month-key) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -237,7 +257,11 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ((answer (prompt-read "Select an option"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (if (string= answer "1") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	(generic-handler 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	 (add-expense-to-month (prompt-read "Enter month")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 (let ((month-input (prompt-read "Enter month")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	       (expense-input (prompt-for-expense))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	   (add-expense-to-month (first expense-input) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				 (second expense-input) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				 month-input)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	 "Invalid Input")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (if (string= answer "2") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	(generic-handler 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -256,7 +280,6 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (interactive-mode)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defun display-help () 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (format t "foo ~%") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (opts:describe 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    :prefix "fin-lisp.lisp - Basic expense tracker in lisp" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				    :usage-of "fin-lisp.lisp" 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -267,15 +290,29 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defun main () 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (if (= 1 (length sb-ext:*posix-argv*)) (interactive-mode)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (let ((matches (opts:get-opts))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (format t "~a ~%" matches) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ;;(format t "~a ~%" matches) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (when-option (matches :help) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       (display-help)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (when-option (matches :print-month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      (let ((key (prompt-read "Enter decryption key: ")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (let ((key (prompt-read "Enter decryption key")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	    (month-key (destructuring-bind (&key print-month) matches 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			 print-month))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	(get-records key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	(dump-month-table month-key))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	(dump-month-table month-key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	(quit))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (when-option (matches :add-expense) ;; This is probably the wrong way to resolve the arguments 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (when-option (matches :value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	(when-option (matches :month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	  (let ((key (prompt-read "Enter decryption key")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		(name-value-month (destructuring-bind (&key add-expense value month) matches 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				    (list add-expense value month)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 	    (get-records key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 	    (if (add-expense-to-month 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 		 (first name-value-month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 		 (second name-value-month) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 		 (third name-value-month)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 		(push-records key) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		(print "Invalid month input")))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (when-option (matches :interactive-mode) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       (progn 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	(interactive-mode) 
			 |