Flexible Lisp Blogware. Fork for personal use. Mirrored from https://github.com/kingcons/coleslaw originally.

posts.lisp 3.3KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. (in-package :coleslaw)
  2. (defparameter *posts* (make-hash-table :test #'equal)
  3. "A hash table to store all the posts and their metadata.")
  4. (defclass post ()
  5. ((slug :initform nil :initarg :slug :accessor post-slug)
  6. (title :initform nil :initarg :title :accessor post-title)
  7. (tags :initform nil :initarg :tags :accessor post-tags)
  8. (date :initform nil :initarg :date :accessor post-date)
  9. (format :initform nil :initarg :format :accessor post-format)
  10. (content :initform nil :initarg :content :accessor post-content)))
  11. (defmethod render ((object post) &key prev next)
  12. (funcall (theme-fn 'post) (list :config *config*
  13. :post object
  14. :prev prev
  15. :next next)))
  16. (defmethod page-path ((object post))
  17. (rel-path (staging *config*) "posts/~a.html" (post-slug object)))
  18. (defun read-post (in)
  19. "Make a POST instance based on the data from the stream IN."
  20. (flet ((check-header ()
  21. (unless (string= (read-line in) ";;;;;")
  22. (error "The provided file lacks the expected header.")))
  23. (parse-field (str)
  24. (nth-value 1 (cl-ppcre:scan-to-strings "[a-zA-Z]+: (.*)" str)))
  25. (field-name (line)
  26. (subseq line 0 (position #\: line)))
  27. (read-tags (str)
  28. (mapcar #'string-downcase (cl-ppcre:split ", " str)))
  29. (slurp-remainder ()
  30. (let ((seq (make-string (- (file-length in) (file-position in)))))
  31. (read-sequence seq in)
  32. (remove #\Nul seq))))
  33. (check-header)
  34. (let ((args (loop for line = (read-line in nil) until (string= line ";;;;;")
  35. appending (list (make-keyword (string-upcase (field-name line)))
  36. (aref (parse-field line) 0)))))
  37. (setf (getf args :tags) (read-tags (getf args :tags))
  38. (getf args :format) (make-keyword (string-upcase (getf args :format))))
  39. (apply 'make-instance 'post
  40. (append args (list :content (render-content (slurp-remainder)
  41. (getf args :format))
  42. :slug (slugify (getf args :title))))))))
  43. (defun load-posts ()
  44. "Read the stored .post files from the repo."
  45. (clrhash *posts*)
  46. (do-files (file (repo *config*) "post")
  47. (with-open-file (in file)
  48. (let ((post (read-post in)))
  49. (if (gethash (post-slug post) *posts*)
  50. (error "There is already an existing post with the slug ~a."
  51. (post-slug post))
  52. (setf (gethash (post-slug post) *posts*) post))))))
  53. (defun render-posts ()
  54. "Iterate through the files in the repo to render+write the posts out to disk."
  55. (loop for (prev post next) on (append '(nil) (sort (hash-table-values *posts*)
  56. #'string< :key #'post-date))
  57. while post do (write-page (page-path post)
  58. (render-page post nil :prev prev :next next))))
  59. (defun slug-char-p (char)
  60. "Determine if CHAR is a valid slug (i.e. URL) character."
  61. (or (char<= #\0 char #\9)
  62. (char<= #\a char #\z)
  63. (char<= #\A char #\Z)
  64. (member char '(#\_ #\- #\.))))
  65. (defun slugify (string)
  66. "Return a version of STRING suitable for use as a URL."
  67. (remove-if-not #'slug-char-p (substitute #\- #\Space string)))