|
@@ -3,10 +3,12 @@
|
|
|
(ql:quickload "cl-ppcre")
|
|
|
(ql:quickload "unix-opts")
|
|
|
(ql:quickload "ironclad")
|
|
|
+(ql:quickload "dexador")
|
|
|
|
|
|
;;; Features
|
|
|
;;; - Import records from old .txt format
|
|
|
;;; - Removed as I'll never use it again
|
|
|
+;;; - Added in again to retest some things
|
|
|
;;; - Interactive prompt to manage expenses
|
|
|
;;; - Generic expense handling
|
|
|
;;; TODO
|
|
@@ -24,6 +26,9 @@
|
|
|
;;; where the key is the month stamp, eg 20210701
|
|
|
;;; and the value is the monthly expenses hash
|
|
|
(defvar *records* (make-hash-table :test 'equalp))
|
|
|
+(defvar *api-config-path* "./auth.json")
|
|
|
+(defvar *api-url* NIL)
|
|
|
+(defvar *api-key* NIL)
|
|
|
|
|
|
(defun file-test (filename)
|
|
|
(if (probe-file filename) filename (print "Couldn't find filename")))
|
|
@@ -73,6 +78,9 @@
|
|
|
;;; See: https://www.cliki.net/Ironclad
|
|
|
|
|
|
;;; Return cipher when provided key
|
|
|
+;;; Currently, this is 'insecure' as we are using a string
|
|
|
+;;; coerced into a byte array as the key, aka a non-random secret.
|
|
|
+;;; Should use twofish
|
|
|
(defun get-cipher (key)
|
|
|
(ironclad:make-cipher
|
|
|
:blowfish
|
|
@@ -101,6 +109,34 @@
|
|
|
;;; End Encryption Stuff ;;;
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
+;;; Can only be called from the REPL,
|
|
|
+;;; used for importing according to the old schema
|
|
|
+(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)))))))))))
|
|
|
+
|
|
|
(defun reset-records ()
|
|
|
(setf *records* (make-hash-table :test 'equal)))
|
|
|
|
|
@@ -145,6 +181,7 @@
|
|
|
(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))))
|
|
|
|
|
|
+;;; Serialization and communicating with the web API
|
|
|
(defun serialize-records (key filename)
|
|
|
(with-open-file (stream filename
|
|
|
:direction :output
|
|
@@ -157,6 +194,21 @@
|
|
|
(defun deserialize-records (key filename)
|
|
|
(setf *records* (yason:parse (decrypt-records key filename))))
|
|
|
|
|
|
+(defun parse-api-config (path)
|
|
|
+ (let ((api-config-hash (yason:parse (uiop:read-file-string path)))
|
|
|
+ (ret-tuple '()))
|
|
|
+ (push (gethash "token" api-config-hash) ret-tuple)
|
|
|
+ (push (gethash "url" api-config-hash) ret-tuple)
|
|
|
+ ret-tuple))
|
|
|
+
|
|
|
+(defun download-records ()
|
|
|
+ (let* ((api-config (parse-api-config *api-config-path*))
|
|
|
+ (dl-records (yason:parse (dex:get (concatenate 'string (first api-config) "download")
|
|
|
+ :headers (list (cons "X-Token" (second api-config)))))))
|
|
|
+ dl-records))
|
|
|
+
|
|
|
+(defun upload-records (records-file))
|
|
|
+
|
|
|
(defmacro generic-handler (form error-string)
|
|
|
`(handler-case ,form
|
|
|
(error (e)
|
|
@@ -215,7 +267,6 @@
|
|
|
(when-option (matches :help)
|
|
|
(display-help))
|
|
|
(when-option (matches :print-month)
|
|
|
-
|
|
|
(when-option (matches :interactive-mode)
|
|
|
(progn
|
|
|
(interactive-mode)
|