(defpackage :lispostory (:use :cl :alexandria :serapeum) (:export :main :create-exe-and-die)) (in-package :lispostory) (defun http-get-json (url) (multiple-value-bind (body status) (drakma:http-request url) (if (/= status 200) nil (let ((body-str (flexi-streams:octets-to-string body))) (shasht:read-json body-str))))) (defun alist (&rest pairs) (loop for (key value) on pairs by #'cddr collect (cons key value))) (defun map-vector (func vec) (map 'vector func vec)) (defun string-to-hash-table-key (s) (string-replace " " s "")) (defun update-hash (hm key f) (setf (@ hm key) (funcall f (@ hm key)))) (defun string->bool (s) (if (string-equal (string-downcase s) "true") t nil)) (defun find-string (str seq) (sequence:find str seq :test #'string-equal)) (defclass data-source () ((cache :initform nil :accessor cache) (file-path :initarg :file-path :accessor file-path))) (defun maybe-pass (func arg) "Pass ARG to FUNC if arg is not nil." (if (fnil)arg (funcall func arg) (funcall func))) (defmacro define-data-source (singular-name) (let* ((class-name (symbolicate singular-name '-data-source)) (constructor-name (symbolicate 'make- singular-name '-data-source)) (plural-name (concat (symbol-name singular-name) "s")) (file-name (concat (string-downcase plural-name) ".json"))) `(progn (defclass ,class-name (data-source) ()) (defun ,constructor-name (&optional (file-path ,file-name)) (make-instance ',class-name :file-path file-path))))) (defgeneric data (data-source)) (defmethod data ((ds data-source)) (or (cache ds) (reload ds))) (defgeneric reload (data-source)) (defmethod reload ((ds data-source)) (if (uiop:file-exists-p (file-path ds)) (let ((data (shasht:read-json (uiop:read-file-string (file-path ds))))) (setf (cache ds) data) data))) (defgeneric refresh (data-source))