2011-04-16 15:45:37 -04:00
|
|
|
(in-package :coleslaw)
|
|
|
|
|
2012-08-20 17:26:12 -04:00
|
|
|
(defparameter *posts* (make-hash-table :test #'equal)
|
|
|
|
"A hash table to store all the posts and their metadata.")
|
2011-04-16 15:45:37 -04:00
|
|
|
|
2012-08-20 10:44:46 -04:00
|
|
|
(defclass post ()
|
|
|
|
((slug :initform nil :initarg :slug :accessor post-slug)
|
|
|
|
(title :initform nil :initarg :title :accessor post-title)
|
|
|
|
(tags :initform nil :initarg :tags :accessor post-tags)
|
|
|
|
(date :initform nil :initarg :date :accessor post-date)
|
|
|
|
(format :initform nil :initarg :format :accessor post-format)
|
2012-08-20 20:36:57 -04:00
|
|
|
(content :initform nil :initarg :content :accessor post-content)))
|
2011-04-16 15:45:37 -04:00
|
|
|
|
2012-08-21 16:19:04 -04:00
|
|
|
(defun load-posts ()
|
|
|
|
"Read the stored .post files from the repo."
|
2012-08-20 23:57:57 -04:00
|
|
|
(clrhash *posts*)
|
2012-08-20 10:44:46 -04:00
|
|
|
(do-files (file (repo *config*) "post")
|
|
|
|
(with-open-file (in file)
|
|
|
|
(let ((post (read-post in)))
|
2012-08-20 23:57:57 -04:00
|
|
|
(if (gethash (post-slug post) *posts*)
|
|
|
|
(error "There is already an existing post with the slug ~a."
|
|
|
|
(post-slug post))
|
2012-08-21 16:19:04 -04:00
|
|
|
(setf (gethash (post-slug post) *posts*) post))))))
|
|
|
|
|
2012-08-21 21:20:01 -04:00
|
|
|
(defun post-url (post)
|
|
|
|
"Return the relative URL for a given post."
|
2012-08-21 22:15:25 -04:00
|
|
|
(format nil "~a.html" (post-slug post)))
|
2012-08-21 21:20:01 -04:00
|
|
|
|
2012-08-21 16:19:04 -04:00
|
|
|
(defun render-posts ()
|
|
|
|
"Iterate through the files in the repo to render+write the posts out to disk."
|
|
|
|
(load-posts)
|
2012-08-21 21:20:01 -04:00
|
|
|
(loop with posts = (sort (hash-table-values *posts*) #'string< :key #'post-date)
|
|
|
|
for i from 1 upto (length posts)
|
|
|
|
for prev = nil then post
|
|
|
|
for post = (nth (1- i) posts)
|
2012-08-21 23:12:40 -04:00
|
|
|
for next = (nth i posts)
|
2012-08-21 21:20:01 -04:00
|
|
|
do (write-post post :prev (and prev (post-url prev))
|
|
|
|
:next (and next (post-url next)))))
|
2011-04-16 15:45:37 -04:00
|
|
|
|
2012-08-20 19:43:03 -04:00
|
|
|
(defgeneric render-content (text format)
|
|
|
|
(:documentation "Compile TEXT from the given FORMAT to HTML for display.")
|
|
|
|
(:method (text (format (eql :html)))
|
|
|
|
text))
|
|
|
|
|
2012-08-20 20:36:57 -04:00
|
|
|
(defun read-post (in)
|
|
|
|
"Make a POST instance based on the data from the stream IN."
|
|
|
|
(flet ((check-header ()
|
|
|
|
(unless (string= (read-line in) ";;;;;")
|
|
|
|
(error "The provided file lacks the expected header.")))
|
|
|
|
(parse-field (str)
|
2012-08-22 13:43:17 -04:00
|
|
|
(nth-value 1 (cl-ppcre:scan-to-strings "[a-zA-Z]+: (.*)" str)))
|
|
|
|
(slurp-remainder ()
|
|
|
|
(read-sequence (make-string (- (file-length in) (file-position in)))
|
|
|
|
in :start (file-position in))))
|
2012-08-20 20:36:57 -04:00
|
|
|
(check-header)
|
|
|
|
(let ((args (loop for field in '("title" "tags" "date" "format")
|
|
|
|
for line = (read-line in nil)
|
2012-08-21 17:17:43 -04:00
|
|
|
appending (list (make-keyword (string-upcase field))
|
2012-08-21 16:37:18 -04:00
|
|
|
(aref (parse-field line) 0)))))
|
2012-08-22 13:43:17 -04:00
|
|
|
(check-header)
|
|
|
|
(setf (getf args :tags) (cl-ppcre:split ", " (getf args :tags))
|
|
|
|
(getf args :format) (make-keyword (string-upcase (getf args :format))))
|
|
|
|
(apply 'make-instance 'post
|
|
|
|
(append args (list :content (render-content (slurp-remainder)
|
|
|
|
(getf args :format))
|
|
|
|
:slug (slugify (getf args :title))))))))
|
2011-04-16 15:45:37 -04:00
|
|
|
|
2012-08-21 21:20:01 -04:00
|
|
|
(defun write-post (post &key prev next)
|
2012-08-20 10:44:46 -04:00
|
|
|
"Write out the HTML for POST in SLUG.html."
|
2012-08-21 22:16:20 -04:00
|
|
|
(render-page (format nil "posts/~a" (post-url post))
|
2012-08-20 11:06:35 -04:00
|
|
|
(funcall (theme-fn "POST")
|
|
|
|
(list :title (post-title post)
|
2012-08-21 22:06:14 -04:00
|
|
|
:tags (taglinks (post-tags post))
|
2012-08-20 11:06:35 -04:00
|
|
|
:date (post-date post)
|
2012-08-22 00:44:44 -04:00
|
|
|
:content (post-content post)
|
2012-08-21 21:20:01 -04:00
|
|
|
:prev prev
|
|
|
|
:next next))))
|
2012-08-20 16:21:12 -04:00
|
|
|
|
|
|
|
(defun slug-char-p (char)
|
2012-08-20 17:26:12 -04:00
|
|
|
"Determine if CHAR is a valid slug (i.e. URL) character."
|
2012-08-20 16:21:12 -04:00
|
|
|
(or (char<= #\0 char #\9)
|
|
|
|
(char<= #\a char #\z)
|
|
|
|
(char<= #\A char #\Z)
|
2012-08-21 14:57:15 -04:00
|
|
|
(member char '(#\_ #\- #\.))))
|
2012-08-20 16:21:12 -04:00
|
|
|
|
|
|
|
(defun slugify (string)
|
2012-08-20 17:26:12 -04:00
|
|
|
"Return a version of STRING suitable for use as a URL."
|
2012-08-20 16:21:12 -04:00
|
|
|
(remove-if-not #'slug-char-p (substitute #\- #\Space string)))
|