miniblog

Miniblog: A command-line static blog system in Common Lisp
Log | Files | Refs | README | LICENSE

commit fbc6aae310278f303f5fb31e5d7aa36a3ee4c94d
Author: Decay <decaydjk@tilde.town>
Date:   Mon,  3 Feb 2020 20:23:42 +0000

Initial checkin

Diffstat:
Aminiblog.asd | 17+++++++++++++++++
Asrc/db.lisp | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/edit.lisp | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/miniblog.lisp | 3+++
Asrc/packages.lisp | 13+++++++++++++
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))