miniblog

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

commit 83660baa6068c8bae988a2615d47f43458508c45
parent 4325b31043605dbd23caf80dfc3f028d6ae8de73
Author: Decay <decaydjk@tilde.town>
Date:   Tue,  4 Feb 2020 23:13:49 +0000

Basic implementation of command-line interface and editing.

No file generation yet.

Diffstat:
Mminiblog.asd | 6++++--
Msrc/db.lisp | 9+++++++++
Msrc/edit.lisp | 13+++++++++++++
Msrc/miniblog.lisp | 98++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/packages.lisp | 11++++++-----
Msrc/template.eco | 1+
6 files changed, 130 insertions(+), 8 deletions(-)

diff --git a/miniblog.asd b/miniblog.asd @@ -9,10 +9,12 @@ "dbd-sqlite3" "sxql" "mito" - "local-time") + "local-time" + "str" + "command-line-arguments") :defsystem-depends-on (:eco) :pathname "src/" - :entry-point "miniblog:main" + :entry-point "miniblog:entry-point" :components ((:file "packages") (:file "format") (:file "edit") diff --git a/src/db.lisp b/src/db.lisp @@ -44,6 +44,15 @@ (if entry (xform entry)))) +(defmacro with-entry-id (entry id &rest body) + "Takes a varname to hold the entry list and a post id + and executes the forms in body with the entry bound + to the specified entry variable" + `(let ((,entry (miniblog.db:get-entry ,id))) + (if ,entry + (progn ,@body) + (format t "Post ID ~d not found~%" ,id)))) + (defun get-entries (&key year month max-entries) "Get entries from the database, optionally limited to a date range or count" diff --git a/src/edit.lisp b/src/edit.lisp @@ -43,3 +43,16 @@ Text goes here" contents)))) (if (not (string= input-content edited-content)) edited-content)))) + +(defun get-title-and-content (text) + "Extract the title and content from a post. Assumption is that if the + text has a first line, then a bare second line, then more content, + the first line is the title (git-ish semantics) otherwise the entire + content is the body and the title is empty" + (let* ((lines (lines text)) + (first-line (trim (nth 0 lines))) + (second-line (trim (nth 1 lines))) + (rest (nthcdr 2 lines))) + (if (and first-line rest (string= second-line "")) + (list (car lines) (unlines rest)) + (list () text)))) diff --git a/src/miniblog.lisp b/src/miniblog.lisp @@ -1,3 +1,99 @@ (in-package :miniblog) -(defun main ()) +(defparameter +command-line-spec+ + '((("add" #\a) :type boolean :optional t :documentation "Add new post") + (("edit" #\e) :type integer :optional t :documentation "Edit a post by ID") + (("get" #\g) :type integer :optional t :documentation "Get a post by ID") + (("list" #\l) :type boolean :optional t :documentation "List posts") + (("start" #\s) :type integer :optional t + :documentation "When listing posts, first post to list (default 0)") + ((#\n) :type integer :optional t + :documentation "When listing posts, max number of posts to list (default all)") + (("regen-all" #\r) :type boolean :optional t + :documentation "When adding or editing, regenerate all pages instead of just those miniblog thinks have changed") + (("help" #\h) :type boolean :optional t :documentation "This help information"))) + +(defun main (args) + (handle-command-line + +command-line-spec+ + 'miniblog + :command-line args + :name "miniblog")) + +(defun entry-point () + (main *command-line-arguments*)) + +(defun get-username () + (uiop:getenv "USER")) + +(defun add-new (regen) + (let ((text (miniblog.edit:edit-text))) + (if text + (let* ((post (miniblog.edit:get-title-and-content text)) + (title (nth 0 post)) + (content (nth 1 post))) + (miniblog.db:add-entry + (or title "Untitled") + content + :username (get-username))) + (format t "Abandoning post...~%")))) + +(defun date-format (datetime) + (funcall (miniblog.format:make-long-date-formatter *pst*) datetime)) + +(defun get-post (id) + (miniblog.db:with-entry-id entry id + (format t "ID: ~d~%" (nth 0 entry)) + (format t "Created: ~A by ~A~%" + (date-format (nth 1 entry)) (nth 5 entry)) + (format t "Last updated: ~A by ~A~%" + (date-format (nth 2 entry)) (nth 6 entry)) + (format t "Title: ~A~%" (nth 3 entry)) + (format t "Content:~%~A~%" (nth 4 entry)))) + +(defun make-template (title content) + (with-output-to-string (out) + (princ (or title "Untitled post") out) + (terpri out) + (terpri out) + (princ content out))) + +(defun edit-post (id regen) + (miniblog.db:with-entry-id entry id + (let ((text (miniblog.edit:edit-text + :template (make-template (nth 3 entry) (nth 4 entry))))) + (if text + (let* ((post (miniblog.edit:get-title-and-content text)) + (title (nth 0 post)) + (content (nth 1 post))) + (miniblog.db:update-entry + id title content + :username (get-username))) + (format t "No change, abandoning...~%"))))) + +(defun list-posts (start n) + (let* ((entries (miniblog.db:get-entries)) + (first (or start 0)) + (last (if n + (+ first n) + (length entries)))) + (dolist (entry (subseq entries first last)) + (format t "~d \"~A\" ~A~%" (first entry) (nth 3 entry) (nth 5 entry))))) + +(defun init-tz () + (reread-timezone-repository) + (defvar *pst* (find-timezone-by-location-name "US/Pacific"))) + +(defun get-db-filename () + (namestring (merge-pathnames + (user-homedir-pathname) + (make-pathname :name ".miniblog" :type "db")))) + +(defun miniblog (&key add get edit list start n regen-all help) + (init-tz) + (miniblog.db:init :sqlite3 :database-name (get-db-filename)) + (cond (add (add-new regen-all)) + (get (get-post get)) + (edit (edit-post edit regen-all)) + (list (list-posts start n)) + (t (show-option-help +command-line-spec+)))) diff --git a/src/packages.lisp b/src/packages.lisp @@ -7,13 +7,14 @@ #:make-long-date-formatter)) (defpackage :miniblog.edit - (:use :cl :uiop/os :uiop/run-program :cl-fad) - (:export #:edit-text)) + (:use :cl :uiop/os :uiop/run-program :cl-fad :str) + (:export #:edit-text #:get-title-and-content)) (defpackage :miniblog.db (:use :cl :mito :sxql) - (:export #:init #:add-entry #:get-entry #:get-entries #:update-entry)) + (:export #:init #:add-entry #:get-entry #:with-entry-id + #:get-entries #:update-entry)) (defpackage :miniblog - (:use :cl) - (:export #:main)) + (:use :cl :command-line-arguments :local-time) + (:export #:entry-point)) diff --git a/src/template.eco b/src/template.eco @@ -5,6 +5,7 @@ <title><%= (or title "Miniblog") %></title> </head> <body> + <!-- <%= year %><%= month %><%= start-entry %><%= max-entries %> --> <% if posts %> <% let ((short-date)) %> <% loop for (id created-at updated-at title content username last-updated-by) in posts do %>