commit fbc6aae310278f303f5fb31e5d7aa36a3ee4c94d
Author: Decay <decaydjk@tilde.town>
Date: Mon, 3 Feb 2020 20:23:42 +0000
Initial checkin
Diffstat:
5 files changed, 173 insertions(+), 0 deletions(-)
diff --git a/miniblog.asd b/miniblog.asd
@@ -0,0 +1,17 @@
+(defsystem "miniblog"
+ :description "A minimal static blog in Common Lisp"
+ :version "0.0.1"
+ :author "DecayDJK <decaydjk@tilde.town>"
+ :depends-on ("uiop"
+ "cl-fad"
+ "cl-markdown"
+ "dbd-sqlite3"
+ "sxql"
+ "mito"
+ "local-time")
+ :pathname "src/"
+ :entry-point "miniblog:main"
+ :components ((:file "packages")
+ (:file "edit")
+ (:file "db")
+ (:file "miniblog")))
diff --git a/src/db.lisp b/src/db.lisp
@@ -0,0 +1,95 @@
+(in-package :miniblog-db)
+
+(defclass blog-entries ()
+ ((username :col-type (:varchar 64)
+ :accessor entry-username)
+ (last-updated-by :col-type (:varchar 64)
+ :accessor entry-last-updated-by)
+ (title :col-type (:varchar 200)
+ :accessor entry-title)
+ (content :col-type (:varchar 16384)
+ :accessor entry-content))
+ (:metaclass mito:dao-table-class))
+
+(defun init (&rest params)
+ "Initialize the DB DAO. Consumes parameters identical to
+ mito:connect-toplevel or sxql:connect"
+ (apply #'connect-toplevel params)
+ (ensure-table-exists 'blog-entries))
+
+(defun xform (entry)
+ "Transform a blog-entries object into a simple field list"
+ (list (mito.dao.mixin:object-id entry)
+ (mito.dao.mixin:object-created-at entry)
+ (mito.dao.mixin:object-updated-at entry)
+ (entry-title entry)
+ (entry-content entry)
+ (entry-username entry)
+ (entry-last-updated-by entry)))
+
+(defun add-entry (title content &key (username "nobody"))
+ "Add a new blog entry to the database"
+ (xform (create-dao 'blog-entries
+ :title title
+ :content content
+ :username username
+ :last-updated-by username)))
+
+(defun get-raw-entry (id)
+ (find-dao 'blog-entries :id id))
+
+(defun get-entry (id)
+ "Get entry by id, or nil if the requested id isn't found"
+ (let ((entry (get-raw-entry id)))
+ (if entry
+ (xform entry))))
+
+(defun get-entries (&key year month max-entries)
+ "Get entries from the database, optionally limited to a date
+ range or count"
+ (labels
+ ((curr-year ()
+ (nth 5 (multiple-value-list (get-decoded-time))))
+
+ (start-range (year month)
+ (format nil "~d-~2,'0d-~2,'0d"
+ (or year (curr-year))
+ (or month 1)
+ 1))
+
+ (end-range (year month)
+ (let* ((working-year (or year (curr-year)))
+ (working-month (or month 12))
+ (end-year
+ (if (eql working-month 12)
+ (+ working-year 1)
+ working-year))
+ (end-month
+ (if (eql working-month 12)
+ 1
+ (+ working-month 1))))
+ (format nil "~d-~2,'0d-~2,'0d" end-year end-month 1)))
+
+ (where-clause (year month)
+ (if (or year month)
+ (where (:and (:>= :created_at (start-range year month))
+ (:< :created_at (end-range year month)))))))
+
+ (mapcar #'xform
+ (select-dao 'blog-entries
+ (where-clause year month)
+ (order-by (:desc :created_at))
+ (if max-entries
+ (limit max-entries))))))
+
+(defun update-entry (id title content &key (username "nobody"))
+ "Update entry by id. Throws if the id isn't found"
+ (let ((entry (get-raw-entry id)))
+ (if entry
+ (xform (progn
+ (setf (entry-title entry) title)
+ (setf (entry-content entry) content)
+ (setf (entry-last-updated-by entry) username)
+ (save-dao entry)
+ entry))
+ (error "Post ID ~d not found!" id))))
diff --git a/src/edit.lisp b/src/edit.lisp
@@ -0,0 +1,45 @@
+(in-package :miniblog-edit)
+
+(defparameter *default-text-template*
+"Untitled post
+
+Text goes here"
+"Default template for a post")
+
+(defparameter *default-editor*
+ "/usr/bin/vi"
+ "Default editor, used as ultimate fallback if no other
+ choice is available")
+
+(defun get-default-template ()
+ *default-text-template*)
+
+(defun get-default-editor ()
+ (or (getenv "EDITOR") *default-editor*))
+
+(defun edit-file (filename editor)
+ "Run the specified editor against the given filename. Throws an error
+ if the editor can't be launched."
+ (run-program (list editor filename)
+ :input :interactive
+ :output :interactive))
+
+(defun edit-text (&key template editor)
+ "Hand over text to the user to be edited in an external text editor,
+ optionally taking a template and the path to the editor to be
+ executed, otherwise providing a default template and either the
+ user's EDITOR or some attempt at a reasonable default, respectively.
+ Returns the edited text or nil if the user made no changes."
+ (let* ((input-content (or template (get-default-template)))
+ (fname
+ (with-output-to-temporary-file (tempfile)
+ (write-string input-content tempfile))))
+ (edit-file (namestring fname) (or editor (get-default-editor)))
+ (let ((edited-content
+ (with-open-file (stream fname)
+ (let ((contents (make-string (file-length stream))))
+ (read-sequence contents stream)
+ (delete-file stream)
+ contents))))
+ (if (not (string= input-content edited-content))
+ edited-content))))
diff --git a/src/miniblog.lisp b/src/miniblog.lisp
@@ -0,0 +1,3 @@
+(in-package :miniblog)
+
+(defun main ())
diff --git a/src/packages.lisp b/src/packages.lisp
@@ -0,0 +1,13 @@
+(in-package :cl-user)
+
+(defpackage :miniblog-edit
+ (:use :cl :uiop/os :uiop/run-program :cl-fad)
+ (:export #:edit-text))
+
+(defpackage :miniblog-db
+ (:use :cl :mito :sxql)
+ (:export #:init #:add-entry #:get-entry #:get-entries #:update-entry))
+
+(defpackage :miniblog
+ (:use :cl)
+ (:export #:main))