src/model/settings.lisp
(in-package :murja.model.settings)
(named-readtables:in-readtable :murja.ps)
(eval-when (:compile-toplevel :load-toplevel :execute)
(defclass Setting ()
((setting-name :initarg :name :accessor setting-name-access :initform (error "Name needed"))
(default-value :initarg :default-value :initform (error "Value needed"))))
;; (defmethod print-object ((obj person) stream)
;; (print-unreadable-object (obj stream :type t)
;; (with-accessors ((name name)
;; (lisper lisper))
;; obj
;; (format stream "~a, lisper: ~a" name lisper))))
(defmethod print-object ((s Setting) output)
(print-unreadable-object (s output :type t)
(with-slots (setting-name default-value) s
(format output "~a - default: ~a" setting-name default-value))))
(defvar *setting-fields* nil
"A list that's used to populate Settings-class")
(defvar *setting->getter* (make-hash-table :test 'equal))
(defvar *inside-defsetting?* nil))
(defun update-setting (k v)
(postmodern:execute "INSERT INTO blog.Settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = excluded.value" k (stringify v)))
(defparameter setting-name-cleaner (alexandria:compose (alexandria:curry #'str:replace-all ":" "")
#'string-downcase
#'prin1-to-string
#'setting-name-access))
(defparameter setting-name-kwcleaner (alexandria:compose (alexandria:curry #'str:replace-all ":" "")
#'string-downcase
#'prin1-to-string))
(defun all-setting-names ()
"Returns all known setting names as \"strings\""
(map 'list setting-name-cleaner
*setting-fields*))
(defun setting-getter (setting-name-str)
(gethash setting-name-str *setting->getter*))
;; a stupid hack
(defun kw-to-sym (kw)
(read-from-string (str:replace-all ":" "" (prin1-to-string kw))))
(defmacro defsetting (name default-value)
"Sets up a setting and its default value. Please call this inside (define-settings)"
(unless (some (lambda (setting)
(with-slots (setting-name) setting
(equalp name setting-name)))
*setting-fields*)
(push (make-instance 'Setting :name (read-from-string (format nil ":~a" name)) :default-value default-value) *setting-fields*))
`(progn
(assert *inside-defsetting?* nil "Please wrap `(defsetting ~a ~a)` inside a define-settings macro" ,(prin1-to-string name) ,(prin1-to-string default-value))
(quote ,name)))
(defmacro create-settings ()
`(progn
(defclass Settings ()
(,@(map 'list
(lambda (setting)
(with-slots (setting-name default-value) setting
`(,(kw-to-sym setting-name) :initarg ,(read-from-string (format nil ":~a" setting-name))
:initform (or (caar (postmodern:query "SELECT value FROM blog.settings WHERE key = $1" ,(prin1-to-string setting-name)))
,default-value)
;; :accessor ,(kw-to-sym setting-name)
)))
*setting-fields*)))
(defparameter *settings*
(block settings-loader
(handler-bind ((cl-postgres:database-socket-error
(lambda (c)
(declare (ignore c))
(return-from settings-loader nil))))
(murja.middleware.db:with-db
(make-instance 'Settings)))))
,@(map 'list
(lambda (setting)
(with-slots (setting-name default-value) setting
`(progn
(defun ,(kw-to-sym setting-name) (&optional value)
,(format nil "If called without parameters, returns current value of ~a. If with parameters, update's setting's value both in-app and in db" setting-name)
(with-slots (,(kw-to-sym setting-name)) *settings*
(if value
(progn
(setf ,(kw-to-sym setting-name) value)
(murja.middleware.db:with-db
(update-setting ,(prin1-to-string (kw-to-sym setting-name)) value))
value)
,(kw-to-sym setting-name))))
(setf (gethash ,(funcall setting-name-kwcleaner setting-name)
*setting->getter*)
(function ,(kw-to-sym setting-name))))))
*setting-fields*)
'Settings))
(defmacro define-settings (&body body)
"Resets the settings (it's an error to call define-settings twice, but it doesn't throw as resetting the old settings in REPL is a feature) and after running through defsettings, creates the final setting getters and setters. Refer to package :documentation on example how to use this"
(setf *setting-fields* nil
*setting->getter* (make-hash-table :test 'equal))
(let ((*inside-defsetting?* t))
`(let ((*inside-defsetting?* t))
,@(map 'list
(lambda (config)
(destructuring-bind (name value . _) config
(declare (ignore _))
`(defsetting ,name ,value)))
body)
(create-settings))))
;; (defsetting random-size 45)
;; (page-size) => 45
;; (page-size 23456)
;; (page-size) => 23456
;; (setf *setting-fields* nil)