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

content.lisp 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. (in-package :coleslaw)
  2. ;; Tagging
  3. (defclass tag ()
  4. ((name :initarg :name :reader tag-name)
  5. (slug :initarg :slug :reader tag-slug)
  6. (url :initarg :url)))
  7. (defmethod initialize-instance :after ((tag tag) &key)
  8. (with-slots (url slug) tag
  9. (setf url (compute-url nil slug 'tag-index))))
  10. (defun make-tag (str)
  11. "Takes a string and returns a TAG instance with a name and slug."
  12. (let ((trimmed (string-trim " " str)))
  13. (make-instance 'tag :name trimmed :slug (slugify trimmed))))
  14. (defun tag-slug= (a b)
  15. "Test if the slugs for tag A and B are equal."
  16. (string= (tag-slug a) (tag-slug b)))
  17. ;; Slugs
  18. (defun slug-char-p (char)
  19. "Determine if CHAR is a valid slug (i.e. URL) character."
  20. (or (char<= #\0 char #\9)
  21. (char<= #\a char #\z)
  22. (char<= #\A char #\Z)
  23. (member char '(#\_ #\-))))
  24. (defun slugify (string)
  25. "Return a version of STRING suitable for use as a URL."
  26. (remove-if-not #'slug-char-p (substitute #\- #\Space string)))
  27. ;; Content Types
  28. (defclass content ()
  29. ((url :initarg :url :reader page-url)
  30. (date :initarg :date :reader content-date)
  31. (file :initarg :file :reader content-file)
  32. (tags :initarg :tags :reader content-tags)
  33. (text :initarg :text :reader content-text))
  34. (:default-initargs :tags nil :date nil))
  35. (defmethod initialize-instance :after ((object content) &key)
  36. (with-slots (tags) object
  37. (when (stringp tags)
  38. (setf tags (mapcar #'make-tag (cl-ppcre:split "," tags))))))
  39. (defun parse-metadata (stream)
  40. "Given a STREAM, parse metadata from it or signal an appropriate condition."
  41. (flet ((parse-field (str)
  42. (nth-value 1 (cl-ppcre:scan-to-strings "[a-zA-Z]+:\\s+(.*)" str)))
  43. (field-name (line)
  44. (make-keyword (string-upcase (subseq line 0 (position #\: line))))))
  45. (unless (string= (read-line stream nil) (separator *config*))
  46. (error "The provided file lacks the expected header."))
  47. (loop for line = (read-line stream nil)
  48. until (string= line (separator *config*))
  49. appending (list (field-name line)
  50. (aref (parse-field line) 0)))))
  51. (defun read-content (file)
  52. "Returns a plist of metadata from FILE with :text holding the content."
  53. (flet ((slurp-remainder (stream)
  54. (let ((seq (make-string (- (file-length stream)
  55. (file-position stream)))))
  56. (read-sequence seq stream)
  57. (remove #\Nul seq))))
  58. (with-open-file (in file :external-format '(:utf-8))
  59. (let ((metadata (parse-metadata in))
  60. (content (slurp-remainder in))
  61. (filepath (enough-namestring file (repo *config*))))
  62. (append metadata (list :text content :file filepath))))))
  63. ;; Helper Functions
  64. (defun tag-p (tag obj)
  65. "Test if OBJ is tagged with TAG."
  66. (let ((tag (if (typep tag 'tag) tag (make-tag tag))))
  67. (member tag (content-tags obj) :test #'tag-slug=)))
  68. (defun month-p (month obj)
  69. "Test if OBJ was written in MONTH."
  70. (search month (content-date obj)))
  71. (defun by-date (content)
  72. "Sort CONTENT in reverse chronological order."
  73. (sort content #'string> :key #'content-date))
  74. (defun find-content-by-path (path)
  75. "Find the CONTENT corresponding to the file at PATH."
  76. (find path (find-all 'content) :key #'content-file :test #'string=))
  77. (defgeneric render-text (text format)
  78. (:documentation "Render TEXT of the given FORMAT to HTML for display.")
  79. (:method (text (format (eql :html)))
  80. text)
  81. (:method (text (format (eql :md)))
  82. (let ((3bmd-code-blocks:*code-blocks* t))
  83. (with-output-to-string (str)
  84. (3bmd:parse-string-and-print-to-stream text str)))))