Implement full site rendering, head+body injection and load-plugins. Misc. fixes.
This commit is contained in:
parent
e81f01440d
commit
5923b24e8f
9 changed files with 125 additions and 36 deletions
7
TODO
7
TODO
|
@ -7,15 +7,14 @@ Ideas:
|
||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
;;;; STATIC
|
;;;; STATIC
|
||||||
;;;; implement head-inject/body-inject/navigation!
|
|
||||||
;;;; implement start-coleslaw, stop-coleslaw!
|
;;;; 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!
|
;;;; 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
|
;;;; write a proper version of escape considering wild pathnames and valid URL issues
|
||||||
;;;; implement atom feed. RSS too?
|
;;;; implement atom feed. RSS too?
|
||||||
;;;; implement non-disqus comment support?
|
;;;; 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
|
;;;; PLUGINS
|
||||||
;;;; add activate-plugin, deactivate-plugin, :active-plugins?
|
;;;; add activate-plugin, deactivate-plugin, :active-plugins?
|
||||||
|
|
|
@ -18,4 +18,6 @@
|
||||||
(:file "posts"
|
(:file "posts"
|
||||||
:depends-on ("coleslaw"))
|
:depends-on ("coleslaw"))
|
||||||
(:file "indices"
|
(:file "indices"
|
||||||
:depends-on ("posts"))))))
|
:depends-on ("posts"))
|
||||||
|
(:file "plugins"
|
||||||
|
:depends-on ("packages"))))))
|
||||||
|
|
|
@ -26,6 +26,8 @@ object is determined by SERVICE."))
|
||||||
(when (plusp (length nodes)) (value (elt nodes 0))))))
|
(when (plusp (length nodes)) (value (elt nodes 0))))))
|
||||||
(public-p ()
|
(public-p ()
|
||||||
(string= "publish" (node-val "wp:status")))
|
(string= "publish" (node-val "wp:status")))
|
||||||
|
(post-p ()
|
||||||
|
(string= "post" (node-val "wp:post_type")))
|
||||||
(make-timestamp (pubdate)
|
(make-timestamp (pubdate)
|
||||||
(let* ((date (split-sequence #\Space (subseq pubdate 5)))
|
(let* ((date (split-sequence #\Space (subseq pubdate 5)))
|
||||||
(time (split-sequence #\: (fourth date))))
|
(time (split-sequence #\: (fourth date))))
|
||||||
|
@ -37,7 +39,8 @@ object is determined by SERVICE."))
|
||||||
(position (second date) +short-month-names+
|
(position (second date) +short-month-names+
|
||||||
:test #'string=)
|
:test #'string=)
|
||||||
(parse-integer (third date))))))
|
(parse-integer (third date))))))
|
||||||
(when (public-p)
|
(when (and (public-p)
|
||||||
|
(post-p))
|
||||||
(let ((new-post (make-post (node-val "title")
|
(let ((new-post (make-post (node-val "title")
|
||||||
(node-val "category")
|
(node-val "category")
|
||||||
(make-timestamp (node-val "pubDate"))
|
(make-timestamp (node-val "pubDate"))
|
||||||
|
@ -48,20 +51,21 @@ object is determined by SERVICE."))
|
||||||
(comments (nodes "wp:comment")))
|
(comments (nodes "wp:comment")))
|
||||||
(add-post new-post (post-id new-post))
|
(add-post new-post (post-id new-post))
|
||||||
(when static-p
|
(when static-p
|
||||||
(write-post new-post))))))
|
(ensure-directories-exist coleslaw::*input-dir*)
|
||||||
|
(write-post-file new-post))))))
|
||||||
|
|
||||||
(defun write-post (post)
|
(defun write-post-file (post)
|
||||||
(let ((filepath (merge-pathnames (format nil "~5,'0d-~a.html"
|
(let ((filepath (merge-pathnames (format nil "~5,'0d-~a.post"
|
||||||
(post-id post)
|
(post-id post)
|
||||||
(coleslaw::escape (post-title post)))
|
(coleslaw::escape (post-title post)))
|
||||||
coleslaw::*input-directory*)))
|
coleslaw::*input-dir*)))
|
||||||
(with-open-file (out filepath :direction :output
|
(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?
|
;; TODO: What other data/metadata should we write out?
|
||||||
(format out ";;;;;~%")
|
(format out ";;;;;~%")
|
||||||
(format out "title: ~A~%" (post-title post))
|
(format out "title: ~A~%" (post-title post))
|
||||||
(format out "tags: ~A~%" (coleslaw::pretty-list (post-tags 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 ";;;;;~%")
|
||||||
(format out "~A~%" (post-content post)))))
|
(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
|
from FILEPATH, converting them to appropriate coleslaw objects and inserting
|
||||||
them into *storage*. The method to parse the file is determined by SERVICE.
|
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
|
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)
|
(defmethod import-posts ((service (eql :wordpress)) filepath &key static-p)
|
||||||
(let* ((xml (cxml:parse-file filepath (cxml-dom:make-dom-builder)))
|
(let* ((xml (cxml:parse-file filepath (cxml-dom:make-dom-builder)))
|
||||||
|
|
|
@ -10,17 +10,15 @@
|
||||||
;; themes
|
;; themes
|
||||||
#:*current-theme*
|
#:*current-theme*
|
||||||
#:*theme-dir*
|
#:*theme-dir*
|
||||||
|
#:add-injection
|
||||||
|
#:remove-injection
|
||||||
|
|
||||||
;; WARNING: STILL IN FLUX
|
|
||||||
;; posts
|
;; posts
|
||||||
#:make-post
|
#:make-post
|
||||||
#:add-post
|
#:add-post
|
||||||
#:remove-post
|
#:remove-post
|
||||||
#:render-post
|
#:render-post
|
||||||
#:find-post
|
#:find-post
|
||||||
#:find-by-tag
|
|
||||||
#:find-by-date
|
|
||||||
#:find-by-range
|
|
||||||
#:post-url
|
#:post-url
|
||||||
|
|
||||||
#:post-id
|
#:post-id
|
||||||
|
@ -49,8 +47,15 @@
|
||||||
#:comment-parent
|
#:comment-parent
|
||||||
|
|
||||||
;; indices
|
;; indices
|
||||||
#:add-index
|
#:make-index
|
||||||
#:remove-index
|
#:add-to-index
|
||||||
|
#:remove-from-index
|
||||||
#:render-index
|
#:render-index
|
||||||
#:find-index
|
#:find-index
|
||||||
))
|
#:index-url
|
||||||
|
|
||||||
|
#:index-id
|
||||||
|
#:index-posts
|
||||||
|
|
||||||
|
;; plugins
|
||||||
|
#:load-plugins))
|
||||||
|
|
15
src/plugins.lisp
Normal file
15
src/plugins.lisp
Normal 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)))
|
|
@ -4,10 +4,16 @@
|
||||||
"The name of a directory containing templates for HTML generation.")
|
"The name of a directory containing templates for HTML generation.")
|
||||||
|
|
||||||
(defparameter *theme-dir* (merge-pathnames
|
(defparameter *theme-dir* (merge-pathnames
|
||||||
(concatenate 'string "themes/" *current-theme*)
|
(concatenate 'string "themes/" *current-theme* "/")
|
||||||
(asdf:system-source-directory 'coleslaw))
|
(asdf:system-source-directory 'coleslaw))
|
||||||
"The directory containing the current theme and other site templates.")
|
"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*))
|
(defun theme-package (&key (name *current-theme*))
|
||||||
(find-package (string-upcase (concatenate 'string "coleslaw.theme." name))))
|
(find-package (string-upcase (concatenate 'string "coleslaw.theme." name))))
|
||||||
|
|
||||||
|
|
|
@ -16,20 +16,20 @@ e.g. \"Brit Butler (year)\".")
|
||||||
"A string containing the (optional) license of the site,
|
"A string containing the (optional) license of the site,
|
||||||
e.g. \"CC-BY-SA\". Otherwise, standard copyright is assumed.")
|
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.")
|
"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.")
|
"The directory which will be watched for new posts.")
|
||||||
|
|
||||||
(defun static-init ()
|
(defun static-init ()
|
||||||
(setf *storage* (make-hash-table))
|
(setf *storage* (make-hash-table))
|
||||||
(loop for table in '(:authors :comments :posts :credentials)
|
(loop for table in '(:authors :comments :posts :credentials)
|
||||||
do (unless (gethash table *storage*)
|
do (setf (gethash table *storage*) (make-hash-table)))
|
||||||
(setf (gethash table *storage*) (make-hash-table))))
|
(setf (gethash :indices *storage*) (make-hash-table :test #'equal)))
|
||||||
(unless (gethash :indices *storage*)
|
|
||||||
(setf (gethash :indices *storage*)
|
|
||||||
(make-hash-table :test #'equal))))
|
|
||||||
|
|
||||||
(defmethod start-coleslaw (&rest options)
|
(defmethod start-coleslaw (&rest options)
|
||||||
)
|
)
|
||||||
|
@ -43,14 +43,71 @@ e.g. \"CC-BY-SA\". Otherwise, standard copyright is assumed.")
|
||||||
(defmethod set-credentials (name credentials)
|
(defmethod set-credentials (name credentials)
|
||||||
(setf (gethash name (gethash :credentials *storage*)) 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)
|
(defmethod render-page (content)
|
||||||
(let ((result (funcall (find-symbol "BASE" (theme-package))
|
(let ((result (funcall (find-symbol "BASE" (theme-package))
|
||||||
(list :title *site-title*
|
(list :title *site-title*
|
||||||
:siteroot *site-root*
|
:siteroot *site-root*
|
||||||
:head-inject nil
|
:head-inject (apply #'concatenate 'string
|
||||||
:navigation nil
|
(gethash :head *storage*))
|
||||||
|
:navigation *site-navigation*
|
||||||
:content content
|
:content content
|
||||||
:body-inject nil
|
:body-inject (apply #'concatenate 'string
|
||||||
|
(gethash :body *storage*))
|
||||||
:license *site-license*
|
:license *site-license*
|
||||||
:credits *site-credits*))))
|
:credits *site-credits*))))
|
||||||
result))
|
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))))
|
||||||
|
|
|
@ -45,9 +45,9 @@
|
||||||
(defmethod render-index (id page)
|
(defmethod render-index (id page)
|
||||||
(let* ((index-posts (index-posts (find-index id)))
|
(let* ((index-posts (index-posts (find-index id)))
|
||||||
(start (* 10 (1- page)))
|
(start (* 10 (1- page)))
|
||||||
(end (if (< (+ start 9) (length index-posts))
|
(end (if (> (length index-posts) (+ start 9))
|
||||||
(+ start 9)
|
(+ start 9)
|
||||||
(- (length index-posts) start)))
|
(length index-posts)))
|
||||||
(posts (subseq index-posts start end))
|
(posts (subseq index-posts start end))
|
||||||
(content (funcall (find-symbol "INDEX" (theme-package))
|
(content (funcall (find-symbol "INDEX" (theme-package))
|
||||||
(list :taglinks (taglinks)
|
(list :taglinks (taglinks)
|
||||||
|
@ -65,4 +65,4 @@
|
||||||
content))
|
content))
|
||||||
|
|
||||||
(defmethod index-url (id page)
|
(defmethod index-url (id page)
|
||||||
(concatenate 'string *site-root* "/" id "/" (write-to-string page)))
|
(concatenate 'string *site-root* "/" id "/" (write-to-string page) ".html"))
|
||||||
|
|
|
@ -32,7 +32,8 @@
|
||||||
(loop for tag in (post-tags post) do
|
(loop for tag in (post-tags post) do
|
||||||
(remove-from-index (concatenate 'string "tag/" tag) post))
|
(remove-from-index (concatenate 'string "tag/" tag) post))
|
||||||
(remove-from-index (concatenate 'string "date/"
|
(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)))
|
(setf (find-post id) nil)))
|
||||||
|
|
||||||
(defmethod render-post (id)
|
(defmethod render-post (id)
|
||||||
|
@ -52,4 +53,4 @@
|
||||||
(let ((post (find-post id)))
|
(let ((post (find-post id)))
|
||||||
(concatenate 'string *site-root* "/"
|
(concatenate 'string *site-root* "/"
|
||||||
(year-month (post-date post)) "/"
|
(year-month (post-date post)) "/"
|
||||||
(escape (post-title post)))))
|
(escape (post-title post)) ".html")))
|
||||||
|
|
Loading…
Add table
Reference in a new issue