diff --git a/TODO b/TODO index e5f69eb..fafea12 100644 --- a/TODO +++ b/TODO @@ -12,15 +12,10 @@ TODO: ;;;; implement render-site!!! ;;;; how many globals can we move into *storage* as keywords? ALL OF THEM! ;;;; -- What about generics? +;;;; write a proper version of escape considering wild pathnames and valid URL issues ;;;; implement atom feed. RSS too? ;;;; implement non-disqus comment support? ;;;; What do post update semantics look like? i.e. edit file to change tags. -;;; indices -;; what should it really look like, keeping in mind the API should be mostly -;; identical between the static and dynamic backend? -;; indexes should be id, type, title + posts. rewrite indices to use them. -;;; posts -;; post-url, improve escaping. ;;;; PLUGINS ;;;; add activate-plugin, deactivate-plugin, :active-plugins? diff --git a/coleslaw-static.asd b/coleslaw-static.asd index 197588b..d1fef4e 100644 --- a/coleslaw-static.asd +++ b/coleslaw-static.asd @@ -5,7 +5,7 @@ :maintainer "Brit Butler " :author "Brit Butler " :licence "LLGPL" - :depends-on (:coleslaw :trivial-timers :cl-store) + :depends-on (:coleslaw :trivial-timers :cl-store :split-sequence) :components ((:module static :components ((:file "coleslaw") (:file "comments" diff --git a/plugins/hunchentoot.lisp b/plugins/hunchentoot.lisp new file mode 100644 index 0000000..47b9a2a --- /dev/null +++ b/plugins/hunchentoot.lisp @@ -0,0 +1,4 @@ +(ql:quickload '(hunchentoot)) + +(in-package :coleslaw) + diff --git a/plugins/import.lisp b/plugins/import.lisp index 077647a..9832f53 100644 --- a/plugins/import.lisp +++ b/plugins/import.lisp @@ -30,13 +30,13 @@ object is determined by SERVICE.")) (let* ((date (split-sequence #\Space (subseq pubdate 5))) (time (split-sequence #\: (fourth date)))) (encode-timestamp 0 - (parse-integer (third time)) ; sec - (parse-integer (second time)) ; min - (parse-integer (first time)) ; hr - (parse-integer (first date)) ; day + (parse-integer (third time)) + (parse-integer (second time)) + (parse-integer (first time)) + (parse-integer (first date)) (position (second date) +short-month-names+ - :test #'string=) ; month - (parse-integer (third date)))))) ; year + :test #'string=) + (parse-integer (third date)))))) (when (public-p) (let ((new-post (make-post (node-val "title") (node-val "category") @@ -48,20 +48,20 @@ object is determined by SERVICE.")) (comments (nodes "wp:comment"))) (add-post new-post (post-id new-post)) (when static-p - (write-post post)))))) + (write-post new-post)))))) (defun write-post (post) - (let ((filepath (merge-pathnames *input-dir* - (format nil "~5d,'0/~a.html" + (let ((filepath (merge-pathnames (format nil "~5,'0d-~a.html" (post-id post) - ;; TODO: Write + use escaping fn. - (post-title post))))) - (with-open-file (out filepath :direction :output :if-exists :supersede) + (coleslaw::escape (post-title post))) + coleslaw::*input-directory*))) + (with-open-file (out filepath :direction :output + :if-exists :supersede :if-does-not-exist :create) ;; TODO: What other data/metadata should we write out? (format out ";;;;;~%") (format out "title: ~A~%" (post-title post)) - (format out "tags: ~A~%" (pretty-tags (post-tags post))) - (format out "date: ~A~%" (pretty-date (post-date post))) + (format out "tags: ~A~%" (coleslaw::pretty-list (post-tags post))) + (format out "date: ~A~%" (coleslaw::pretty-date (post-date post))) (format out ";;;;;~%") (format out "~A~%" (post-content post))))) diff --git a/src/indices.lisp b/src/indices.lisp index 2d61911..3936b10 100644 --- a/src/indices.lisp +++ b/src/indices.lisp @@ -1,13 +1,24 @@ (in-package :coleslaw) -(defgeneric add-index (index id) - (:documentation "Insert INDEX into *storage* with the given ID.")) +(defclass index () + ((id :initform nil :initarg :id + :accessor index-id) + (posts :initform nil :initarg :posts + :accessor index-posts))) -(defgeneric remove-index (id) - (:documentation "Remove the index matching ID from *storage*.")) +(defgeneric make-index (id posts) + (:documentation "Create an INDEX with the given data.")) + +(defgeneric add-to-index (id post) + (:documentation "Add POST to the index matching ID, creating INDEX if +it does not exist.")) + +(defgeneric remove-from-index (id post) + (:documentation "Remove POST from the index matching ID, removing INDEX if +it holds no more posts.")) (defgeneric render-index (id page) - (:documentation "Generate the final HTML for the index with given ID.")) + (:documentation "Generate the final HTML for a given PAGE of the index ID.")) (defgeneric find-index (id) (:documentation "Retrieve the index matching ID from *storage*.")) diff --git a/src/posts.lisp b/src/posts.lisp index 5e34629..b0e311e 100644 --- a/src/posts.lisp +++ b/src/posts.lisp @@ -30,15 +30,5 @@ (defgeneric find-post (id) (:documentation "Retrieve a post from *storage* matching ID.")) -(defgeneric find-by-tag (tag) - (:documentation "Retrieve all posts from *storage* tagged with TAG.")) - -(defgeneric find-by-date (date) - (:documentation "Retrieve all posts from *storage* matching DATE.")) - -(defgeneric find-by-range (start end) - (:documentation "Retrieve all posts from *storage* with ids between -START and END.")) - (defgeneric post-url (id) (:documentation "Return the URL for the post with the given ID.")) diff --git a/static/coleslaw.lisp b/static/coleslaw.lisp index f3a6e69..af35ff8 100644 --- a/static/coleslaw.lisp +++ b/static/coleslaw.lisp @@ -24,9 +24,12 @@ e.g. \"CC-BY-SA\". Otherwise, standard copyright is assumed.") (defun static-init () (setf *storage* (make-hash-table)) - (loop for table in '(:authors :comments :posts :indices :credentials) + (loop for table in '(:authors :comments :posts :credentials) do (unless (gethash table *storage*) - (setf (gethash table *storage*) (make-hash-table))))) + (setf (gethash table *storage*) (make-hash-table)))) + (unless (gethash :indices *storage*) + (setf (gethash :indices *storage*) + (make-hash-table :test #'equal)))) (defmethod start-coleslaw (&rest options) ) diff --git a/static/indices.lisp b/static/indices.lisp index 24e1cb2..6c90927 100644 --- a/static/indices.lisp +++ b/static/indices.lisp @@ -1,51 +1,68 @@ (in-package :coleslaw) +(defmethod make-index (id posts) + (make-instance 'index :id id :posts posts)) + +(defmethod find-index (id) + (gethash id (gethash :indices *storage*))) + +(defun (setf find-index) (new-val id) + (setf (gethash id (gethash :indices *storage*)) new-val) + new-val) + +(defmethod add-to-index (id (post post)) + (let ((index (find-index id))) + (if index + (push post (index-posts index)) + (setf index (make-index id (list post)))) + (setf (find-index id) index))) + +(defmethod remove-from-index (id (post post)) + (let ((index (find-index id))) + (setf (index-posts index) (remove post (index-posts index))) + (if (index-posts index) + (setf (find-index id) index) + (remhash id (gethash :indices *storage*))))) + (defun monthlinks () - (loop for month in (gethash :months-index *storage*) - collecting (list :url (index-url :date month) :name month))) + (loop for month in (gethash :months-list *storage*) + collecting (list :url (index-url (concatenate 'string "date/" month) 1) + :name month))) (defun taglinks () - (loop for tag in (gethash :tags-index *storage*) - collecting (list :url (index-url :tag tag) :name tag))) + (loop for tag in (gethash :tags-list *storage*) + collecting (list :url (index-url (concatenate 'string "tag/" tag) 1) + :name tag))) -(defun index-title (id &optional page) - (case id - (:range "Recent Posts") - (:date (format nil "Posts from ~A" page)) - (:tag (format nil "Posts tagged ~A" page)))) - -(defun index-posts (id page) - (case id - (:range (let* ((count (hash-table-count (gethash :posts *storage*))) - (start (- count (* 10 (1- page)))) - (end (- start 9))) - (remove nil (find-by-range start end)))) - (:date (find-by-date page)) - (:tag (find-by-tag page)))) +(defun index-title (id) + (let ((split-id (split-sequence:split-sequence #\/ id))) + (cond ((string= "date" (first split-id)) + (format nil "Posts from ~A" (second split-id))) + ((string= "tag" (first split-id)) + (format nil "Posts tagged ~A" (second split-id))) + (t (format nil "Recent Posts"))))) (defmethod render-index (id page) - (let* ((posts (index-posts id page)) + (let* ((index-posts (index-posts (find-index id))) + (start (* 10 (1- page))) + (end (if (< (+ start 9) (length index-posts)) + (+ start 9) + (- (length index-posts) start))) + (posts (subseq index-posts start end)) (content (funcall (find-symbol "INDEX" (theme-package)) (list :taglinks (taglinks) :monthlinks (monthlinks) - :title (index-title id page) + :title (index-title id) :posts (loop for post in posts collect (list :url (post-url (post-id post)) :title (post-title post) :date (pretty-date (post-date post)) :contents (post-content post))) - :prev (when (and (numberp id) - (index-posts id (1- page))) + :prev (when (> page 1) (index-url id (1- page))) - :next (when (and (numberp id) - (index-posts id (1+ page))) + :next (when (< (* 10 page) (length index-posts)) (index-url id (1+ page))))))) content)) (defmethod index-url (id page) - (flet ((keyword-name (keyword) - (format nil "~A" keyword))) - (if (member id '(:date :tag)) - (concatenate 'string *site-root* "/" - (string-downcase (keyword-name id)) "/" page) - (concatenate 'string *site-root* "/page/" (write-to-string page))))) + (concatenate 'string *site-root* "/" id "/" (write-to-string page))) diff --git a/static/posts.lisp b/static/posts.lisp index f5cd016..d066a3f 100644 --- a/static/posts.lisp +++ b/static/posts.lisp @@ -8,23 +8,38 @@ :content content :aliases aliases)) +(defmethod find-post (id) + (gethash id (gethash :posts *storage*))) + +(defun (setf find-post) (new-val id) + (setf (gethash id (gethash :posts *storage*)) new-val) + new-val) + (defmethod add-post ((post post) id) - (setf (gethash id (gethash :posts *storage*)) post) - (loop for tag in (post-tags post) - do (pushnew tag (gethash :tags-index *storage*) :test #'string=)) - (let ((date (post-date post)) - (month (format nil "~4d-~2,'0d" (local-time:timestamp-year date) - (local-time:timestamp-month date)))) - (pushnew month (gethash :months-index *storage*) :test #'string=))) + (setf (find-post id) post) + (add-to-index "recent" post) + (loop for tag in (post-tags post) do + (pushnew tag (gethash :tags-list *storage*) :test #'string=) + (add-to-index (concatenate 'string "tag/" tag) post)) + (let ((year-month (year-month (post-date post)))) + (pushnew (year-month (post-date post)) + (gethash :months-list *storage*) :test #'string=) + (add-to-index (concatenate 'string "date/" year-month) post))) (defmethod remove-post (id) - (setf (gethash id (gethash :posts *storage*)) nil)) + ;; Removes post from storage and indexes but not disk! Should we support more? + (let ((post (find-post id))) + (loop for tag in (post-tags post) do + (remove-from-index (concatenate 'string "tag/" tag) post)) + (remove-from-index (concatenate 'string "date/" + (month-year (post-date post)))) + (setf (find-post id) nil))) (defmethod render-post (id) (let* ((post (find-post id)) (result (funcall (theme-fn "POST") (list :title (post-title post) - :tags (pretty-tags (post-tags post)) + :tags (pretty-list (post-tags post)) :date (pretty-date (post-date post)) :content (post-content post) :prev (when (find-post (1- id)) @@ -33,36 +48,8 @@ (post-url (1+ id))))))) result)) -(defmethod find-post (id) - (gethash id (gethash :posts *storage*))) - -(defmethod find-by-tag (tag) - (let ((results nil)) - (loop for post being the hash-values in (gethash :posts *storage*) - do (when (member tag (post-tags post) :test #'string=) - (push post results))) - results)) - -(defmethod find-by-date (year-month) - (let ((results nil) - (year (parse-integer (subseq year-month 0 4))) - (month (parse-integer (subseq year-month 5)))) - (loop for post being the hash-values in (gethash :posts *storage*) - do (let ((date (post-date post))) - (when (and (= year (local-time:timestamp-year date)) - (= month (local-time:timestamp-month date))) - (push post results)))) - (sort results #'local-time:timestamp> :key #'post-date))) - -(defmethod find-by-range (start end) - (if (> start end) - (loop for id from start downto end collecting (find-post id)) - (loop for id from start upto end collecting (find-post id)))) - (defmethod post-url (id) - (flet ((escape (str) - (substitute #\- #\Space str))) - (let ((post (find-post id))) - (concatenate 'string *site-root* "/" - (year-month (post-date post)) "/" - (escape (post-title post)))))) + (let ((post (find-post id))) + (concatenate 'string *site-root* "/" + (year-month (post-date post)) "/" + (escape (post-title post))))) diff --git a/static/util.lisp b/static/util.lisp index f2cb7aa..6b4a409 100644 --- a/static/util.lisp +++ b/static/util.lisp @@ -12,3 +12,8 @@ (defun theme-fn (name) (find-symbol name (theme-package))) + +(defun escape (str) + (substitute #\. #\/ + (substitute #\_ #\? + (substitute #\- #\Space str))))