| 
					
				 | 
			
			
				@@ -6,12 +6,15 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; Features 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; - Import records from old .txt format 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;;;   - Removed as I'll never use it again 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; - Interactive prompt to manage expenses 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; - Generic expense handling 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; TODO 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; - Non interactive CLI Interface 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;; - Upload/download support like perl version 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-;;;   - Should support encryption/decryption of records 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;;;   - Should support encryption/decryption of records - DONE 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;;; - Interface is not good, and doesn't protect the user from 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;;;   data entry mistakes 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;;;; Reimplementation of my bills tracker in Lisp 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -148,38 +151,11 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			  :if-exists :overwrite 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			  :if-does-not-exist :create) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (yason:encode *records* stream)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (encrypt-records key filename)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (encrypt-records key filename) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (delete-file filename)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defun deserialize-records (key filename) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-   
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-;; Import records from old perl version (plaintext file) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-(defun import-records (filename) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (let ((old-file-lines 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	  (with-open-file (stream filename) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	    (loop for line = (read-line stream nil) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		  while line 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		  collect line))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	(mre (ppcre:create-scanner "^(.*)[0-9]{4}$")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	(ere (ppcre:create-scanner "^([A-Z].*)\ -\ \\\$([0-9]{1,4}) - PAID")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	(cur-mon) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	(cur-exp)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (loop for line in old-file-lines 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	  do (progn 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	       (if (ppcre:scan mre line) (setf cur-mon line)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	       (if (ppcre:scan ere line) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		   (progn 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		     (setf cur-exp (ppcre:register-groups-bind (first second) (ere line) :sharedp t (list first second))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		     (print cur-exp) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		     (if (gethash cur-mon *records*) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			 (let ((innerhash (gethash cur-mon *records*))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			   (setf (gethash (first cur-exp) innerhash) (second cur-exp)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		     (if (not (gethash cur-mon *records*)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			 (progn 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			   (add-month cur-mon) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			   (let ((innerhash (gethash cur-mon *records*))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			     (setf (gethash (first cur-exp) innerhash) (second cur-exp))))))))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (setf *records* (yason:parse (decrypt-records key filename)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defmacro generic-handler (form error-string) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   `(handler-case ,form 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -199,7 +175,6 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (format t "3. Write records~%") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (format t "4. Read records~%") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (format t "5. Quit~%") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (format t "6. Import Records~%") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (let 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ((answer (prompt-read "Select an option"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (if (string= answer "1") 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -217,14 +192,11 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	 "Serialization error or invalid filename")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (if (string= answer "4") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	(generic-handler 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	 (deserialize-records (prompt-read "Enter filename")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 (deserialize-records (prompt-read "Enter decryption key") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			      (prompt-read "Enter filename")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	 "Deserialization error or invalid filename")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (if (string= answer "5") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	(quit)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (if (string= answer "6") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	(generic-handler 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	 (import-records (prompt-read "Enter filename")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	 "Parsing error or invalid filename"))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	(quit))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (interactive-mode)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defun display-help () 
			 |