(ql:quickload "cl-store") (ql:quickload "cl-ppcre") ;;;; Reimplementation of my bills tracker in Lisp (defun reload () (load "~/Repos/fin-lisp/fin-lisp.lisp")) ;;; All records exist in this data structure ;;; nil on start and loaded in from file ;;; *records* represents as hash of months, ;;; where the key is the month stamp, eg 20210701 ;;; and the value is the monthly expenses hash (defvar *records* (make-hash-table :test 'equalp)) ;;; Lookup table for matching a "pretty" month name ;;; with a date eg. December2021 -> 202112 (defparameter *month-table* (list (cons 'January 01) (cons 'February 02) (cons 'March 03) (cons 'April 04) (cons 'May 05) (cons 'June 06) (cons 'July 07) (cons 'August 08) (cons 'September 09) (cons 'October 10) (cons 'November 11) (cons 'December 12))) (defun reset-records () (setf *records* (make-hash-table :test 'equalp))) ;; Called like: (add-month '202107) (defun add-month (month-key) (setf (gethash month-key *records*) (make-hash-table :test 'equalp)) month-key) ;;; Taken from practical common lisp (defun prompt-read (prompt) (format *query-io* "~a: " prompt) (force-output *query-io*) (read-line *query-io*)) (defun prompt-for-expense () (list (prompt-read "Enter expense name") (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))) ;;NIL)) (add-expense-to-month (add-month month)))) ;;; Given key for *records* hash, ;;; print expenses/values for month (defun dump-month (month-key) (format t "~a~C" month-key #\linefeed) (let ((month-hash) (exp-keys)) (setf month-hash (gethash month-key *records*)) (setf exp-keys (loop for key being the hash-keys of month-hash collect key)) (dolist (exp-key exp-keys) (format t "~a : ~a~C" exp-key (gethash exp-key month-hash) #\linefeed)))) ;;; Dump all records. ;;; This will also be used for data serialization at some point (defun dump-records () (let ((record-key-list (loop for key being the hash-keys of *records* collect key))) (dolist (month-key record-key-list) (dump-month month-key)))) (defun serialize-records (filename) (cl-store:store *records* filename)) (defun deserialize-records (filename) ;(setf *records* (cl-store:restore (pathname filename)))) (setf *records* (cl-store:restore 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-Za-z].*).*$([0-9]{1,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) (progn ;;; (let ((result) ;;; (month-num)) ;;; (setf result (ppcre:register-groups-bind (first second) (mre line) :sharedp t (list first second))) ;;; (setf month-num (cdr (assoc '(first result) *month-table*))) ;;; (setf cur-mon (concatenate (second result) month-num))))) (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))))))))))) ;; Entry point (defun main () (format t "Available options:~C" #\linefeed) (format t "1. Enter expense~C" #\linefeed) (format t "2. Display month~C" #\linefeed) (format t "3. Write records~C" #\linefeed) (format t "4. Read records~C" #\linefeed) (format t "5. Quit~C" #\linefeed) (format t "6. Import Records~C" #\linefeed) (let ((answer (prompt-read "Select an option"))) (if (string= answer "1") (add-expense-to-month (read-from-string (prompt-read "Enter month")))) (if (string= answer "2") (dump-month (prompt-read "Enter month"))) (if (string= answer "3") (serialize-records (prompt-read "Enter filename"))) (if (string= answer "4") (deserialize-records (prompt-read "Enter filename"))) (if (string= answer "5") (quit)) (if (string= answer "6") (import-records (prompt-read "Enter filename")))) (main))