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