From 5923b24e8f7b3cf05df707e7b6349c8f3075e785 Mon Sep 17 00:00:00 2001 From: Brit Butler Date: Sat, 23 Apr 2011 00:13:27 -0400 Subject: [PATCH] Implement full site rendering, head+body injection and load-plugins. Misc. fixes. --- TODO | 7 ++-- coleslaw.asd | 4 ++- plugins/import.lisp | 20 +++++++----- src/packages.lisp | 19 +++++++---- src/plugins.lisp | 15 +++++++++ src/themes.lisp | 8 ++++- static/coleslaw.lisp | 77 ++++++++++++++++++++++++++++++++++++++------ static/indices.lisp | 6 ++-- static/posts.lisp | 5 +-- 9 files changed, 125 insertions(+), 36 deletions(-) create mode 100644 src/plugins.lisp diff --git a/TODO b/TODO index fafea12..48d63be 100644 --- a/TODO +++ b/TODO @@ -7,15 +7,14 @@ Ideas: TODO: ;;;; STATIC -;;;; implement head-inject/body-inject/navigation! ;;;; implement start-coleslaw, stop-coleslaw! -;;;; implement render-site!!! +;;;; implement cl-store use+init, once every 24 hours? ;;;; how many globals can we move into *storage* as keywords? ALL OF THEM! -;;;; -- What about generics? +;;;; --what about accessing them? ;;;; 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. +;;;; What do post update semantics look like? i.e. edit file to change tags. what about post-removal? ;;;; PLUGINS ;;;; add activate-plugin, deactivate-plugin, :active-plugins? diff --git a/coleslaw.asd b/coleslaw.asd index 9e5cc46..fc42e1e 100644 --- a/coleslaw.asd +++ b/coleslaw.asd @@ -18,4 +18,6 @@ (:file "posts" :depends-on ("coleslaw")) (:file "indices" - :depends-on ("posts")))))) + :depends-on ("posts")) + (:file "plugins" + :depends-on ("packages")))))) diff --git a/plugins/import.lisp b/plugins/import.lisp index 9832f53..087657e 100644 --- a/plugins/import.lisp +++ b/plugins/import.lisp @@ -26,6 +26,8 @@ object is determined by SERVICE.")) (when (plusp (length nodes)) (value (elt nodes 0)))))) (public-p () (string= "publish" (node-val "wp:status"))) + (post-p () + (string= "post" (node-val "wp:post_type"))) (make-timestamp (pubdate) (let* ((date (split-sequence #\Space (subseq pubdate 5))) (time (split-sequence #\: (fourth date)))) @@ -37,7 +39,8 @@ object is determined by SERVICE.")) (position (second date) +short-month-names+ :test #'string=) (parse-integer (third date)))))) - (when (public-p) + (when (and (public-p) + (post-p)) (let ((new-post (make-post (node-val "title") (node-val "category") (make-timestamp (node-val "pubDate")) @@ -48,20 +51,21 @@ object is determined by SERVICE.")) (comments (nodes "wp:comment"))) (add-post new-post (post-id new-post)) (when static-p - (write-post new-post)))))) + (ensure-directories-exist coleslaw::*input-dir*) + (write-post-file new-post)))))) -(defun write-post (post) - (let ((filepath (merge-pathnames (format nil "~5,'0d-~a.html" +(defun write-post-file (post) + (let ((filepath (merge-pathnames (format nil "~5,'0d-~a.post" (post-id post) (coleslaw::escape (post-title post))) - coleslaw::*input-directory*))) + coleslaw::*input-dir*))) (with-open-file (out filepath :direction :output - :if-exists :supersede :if-does-not-exist :create) + :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~%" (coleslaw::pretty-list (post-tags post))) - (format out "date: ~A~%" (coleslaw::pretty-date (post-date post))) + (format out "date: ~A~%" (coleslaw::year-month (post-date post))) (format out ";;;;;~%") (format out "~A~%" (post-content post))))) @@ -70,7 +74,7 @@ object is determined by SERVICE.")) from FILEPATH, converting them to appropriate coleslaw objects and inserting them into *storage*. The method to parse the file is determined by SERVICE. If STATIC-P is true, the posts will also be written into *.html files in -*input-directory*.")) +*input-dir*.")) (defmethod import-posts ((service (eql :wordpress)) filepath &key static-p) (let* ((xml (cxml:parse-file filepath (cxml-dom:make-dom-builder))) diff --git a/src/packages.lisp b/src/packages.lisp index a19258d..04fe71f 100644 --- a/src/packages.lisp +++ b/src/packages.lisp @@ -10,17 +10,15 @@ ;; themes #:*current-theme* #:*theme-dir* + #:add-injection + #:remove-injection - ;; WARNING: STILL IN FLUX ;; posts #:make-post #:add-post #:remove-post #:render-post #:find-post - #:find-by-tag - #:find-by-date - #:find-by-range #:post-url #:post-id @@ -49,8 +47,15 @@ #:comment-parent ;; indices - #:add-index - #:remove-index + #:make-index + #:add-to-index + #:remove-from-index #:render-index #:find-index - )) + #:index-url + + #:index-id + #:index-posts + + ;; plugins + #:load-plugins)) diff --git a/src/plugins.lisp b/src/plugins.lisp new file mode 100644 index 0000000..7a087c9 --- /dev/null +++ b/src/plugins.lisp @@ -0,0 +1,15 @@ +(in-package :coleslaw) + +(defun load-plugins (plugins) + "Resolve the path of each symbol in PLUGINS and call LOAD on the +resulting pathname. It is expected that the matching *.lisp files +are in the plugins folder in coleslaw's source directory." + (let ((files (mapcar (lambda (sym) + (merge-pathnames + (concatenate 'string "plugins/" + (string-downcase (symbol-name sym))) + (asdf:system-source-directory 'coleslaw))) + plugins))) + (map nil (lambda (file) + (compile-file file) + (load file)) files))) diff --git a/src/themes.lisp b/src/themes.lisp index 98968d9..d5cc0a7 100644 --- a/src/themes.lisp +++ b/src/themes.lisp @@ -4,10 +4,16 @@ "The name of a directory containing templates for HTML generation.") (defparameter *theme-dir* (merge-pathnames - (concatenate 'string "themes/" *current-theme*) + (concatenate 'string "themes/" *current-theme* "/") (asdf:system-source-directory 'coleslaw)) "The directory containing the current theme and other site templates.") +(defgeneric add-injection (str location) + (:documentation "Add STR to the list of elements injected in LOCATION.")) + +(defgeneric remove-injection (str location) + (:documentation "Remove STR from the list of elements injected in LOCATION.")) + (defun theme-package (&key (name *current-theme*)) (find-package (string-upcase (concatenate 'string "coleslaw.theme." name)))) diff --git a/static/coleslaw.lisp b/static/coleslaw.lisp index af35ff8..8ba8f64 100644 --- a/static/coleslaw.lisp +++ b/static/coleslaw.lisp @@ -16,20 +16,20 @@ e.g. \"Brit Butler (year)\".") "A string containing the (optional) license of the site, e.g. \"CC-BY-SA\". Otherwise, standard copyright is assumed.") -(defvar *output-directory* nil +(defvar *site-navigation* nil + "A string of HTML describing a navbar or similar structure.") + +(defvar *output-dir* nil "The path where the compiled coleslaw site will be output.") -(defvar *input-directory* nil +(defvar *input-dir* nil "The directory which will be watched for new posts.") (defun static-init () (setf *storage* (make-hash-table)) (loop for table in '(:authors :comments :posts :credentials) - do (unless (gethash table *storage*) - (setf (gethash table *storage*) (make-hash-table)))) - (unless (gethash :indices *storage*) - (setf (gethash :indices *storage*) - (make-hash-table :test #'equal)))) + do (setf (gethash table *storage*) (make-hash-table))) + (setf (gethash :indices *storage*) (make-hash-table :test #'equal))) (defmethod start-coleslaw (&rest options) ) @@ -43,14 +43,71 @@ e.g. \"CC-BY-SA\". Otherwise, standard copyright is assumed.") (defmethod set-credentials (name credentials) (setf (gethash name (gethash :credentials *storage*)) credentials)) +(defmethod add-injection ((str string) location) + (pushnew str (gethash location *storage*) :test #'string)) + +(defmethod remove-injection ((str string) location) + (setf (gethash location *storage*) + (remove str (gethash location *storage*) :test #'string=))) + (defmethod render-page (content) (let ((result (funcall (find-symbol "BASE" (theme-package)) (list :title *site-title* :siteroot *site-root* - :head-inject nil - :navigation nil + :head-inject (apply #'concatenate 'string + (gethash :head *storage*)) + :navigation *site-navigation* :content content - :body-inject nil + :body-inject (apply #'concatenate 'string + (gethash :body *storage*)) :license *site-license* :credits *site-credits*)))) result)) + +(defun write-post (post) + (let ((filepath (merge-pathnames + (concatenate 'string (year-month (post-date post)) + "/" (escape (post-title post)) ".html") + *output-dir*))) + (ensure-directories-exist filepath) + (with-open-file (out filepath :direction :output + :if-exists :supersede :if-does-not-exist :create) + (write-string (render-page (render-post (post-id post))) out)))) + +(defun write-index (index) + (ensure-directories-exist + (cl-fad:pathname-as-directory (merge-pathnames (index-id index) + *output-dir*))) + (let* ((count (length (index-posts index))) + (pages (ceiling (/ count 10)))) + (loop for page from 1 to pages do + (let ((filepath (merge-pathnames + (concatenate 'string (index-id index) + "/" (write-to-string page) ".html") + *output-dir*))) + (with-open-file (out filepath :direction :output + :if-exists :supersede :if-does-not-exist :create) + (write-string (render-page (render-index (index-id index) page)) out)))))) + +(defun render-site () + (flet ((copy-dir (from to) + (cl-fad:walk-directory from + (lambda (file) + (let ((name (concatenate 'string + (pathname-name file) "." + (pathname-type file)))) + (cl-fad:copy-file file (merge-pathnames name to))))))) + (when (cl-fad:directory-exists-p *output-dir*) + (cl-fad:delete-directory-and-files *output-dir*)) + (ensure-directories-exist *output-dir*) + (let ((css-dir (merge-pathnames "css/" *output-dir*)) + (static-dir (merge-pathnames "static/" *output-dir*))) + (ensure-directories-exist css-dir) + (ensure-directories-exist static-dir) + ;; TODO: Copy-dir dies if the directories aren't there... + (copy-dir (merge-pathnames "css/" *theme-dir*) css-dir) + (copy-dir (merge-pathnames "static/" *input-dir*) static-dir)) + (loop for post being the hash-values in (gethash :posts *storage*) + do (write-post post)) + (loop for index being the hash-values in (gethash :indices *storage*) + do (write-index index)))) diff --git a/static/indices.lisp b/static/indices.lisp index 6c90927..0426f65 100644 --- a/static/indices.lisp +++ b/static/indices.lisp @@ -45,9 +45,9 @@ (defmethod render-index (id page) (let* ((index-posts (index-posts (find-index id))) (start (* 10 (1- page))) - (end (if (< (+ start 9) (length index-posts)) + (end (if (> (length index-posts) (+ start 9)) (+ start 9) - (- (length index-posts) start))) + (length index-posts))) (posts (subseq index-posts start end)) (content (funcall (find-symbol "INDEX" (theme-package)) (list :taglinks (taglinks) @@ -65,4 +65,4 @@ content)) (defmethod index-url (id page) - (concatenate 'string *site-root* "/" id "/" (write-to-string page))) + (concatenate 'string *site-root* "/" id "/" (write-to-string page) ".html")) diff --git a/static/posts.lisp b/static/posts.lisp index d066a3f..2c67be2 100644 --- a/static/posts.lisp +++ b/static/posts.lisp @@ -32,7 +32,8 @@ (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)))) + (month-year (post-date post))) post) + (remove-from-index "recent" post) (setf (find-post id) nil))) (defmethod render-post (id) @@ -52,4 +53,4 @@ (let ((post (find-post id))) (concatenate 'string *site-root* "/" (year-month (post-date post)) "/" - (escape (post-title post))))) + (escape (post-title post)) ".html")))