Implement full site rendering, head+body injection and load-plugins. Misc. fixes.

This commit is contained in:
Brit Butler 2011-04-23 00:13:27 -04:00
parent e81f01440d
commit 5923b24e8f
9 changed files with 125 additions and 36 deletions

7
TODO
View file

@ -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?

View file

@ -18,4 +18,6 @@
(:file "posts"
:depends-on ("coleslaw"))
(:file "indices"
:depends-on ("posts"))))))
:depends-on ("posts"))
(:file "plugins"
:depends-on ("packages"))))))

View file

@ -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)
;; 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)))

View file

@ -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))

15
src/plugins.lisp Normal file
View file

@ -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)))

View file

@ -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))))

View file

@ -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))))

View file

@ -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"))

View file

@ -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")))