Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to use djula as template engine #107

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion coleslaw.asd
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
:cl-fad
:cl-ppcre
:closer-mop
:cl-unicode)
:cl-unicode
:djula)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If at all possible, I would prefer we load djula in an eval-when in the plugin as we do with other plugins that have additional library dependencies. If it is necessary to also initialize/compile templates at that time, the data necessary to do so can be passed into the enable method in the plugin and they can be compiled then.

:serial t
:components ((:file "packages")
(:file "util")
Expand Down
1 change: 1 addition & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ There are also many *optional* config parameters such as:
* `:separator` => to set the separator for content metadata, default: ";;;;;"
* `:sitenav` => to provide relevant links and ease navigation
* `:staging-dir` => for Coleslaw to do intermediate work, default: "/tmp/coleslaw"
* `:template-engine` => to set the template engine coleslaw should use, default: cl-closure

[plugin-use]: https://github.com/redline6561/coleslaw/blob/master/docs/plugin-use.md
31 changes: 26 additions & 5 deletions docs/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ template engine and how you can influence the resulting HTML.

## High-Level Overview

Themes are written using [Closure Templates][clt]. Those templates are
then compiled into functions that Lisp calls with the blog data to get
HTML. Since the Lisp code to use theme functions is already written,
your theme must follow a few rules.
Themes are written using [Closure Templates][clt] or using
[Djula][djula].

## Closure templates
Closure templates are then compiled into functions that Lisp calls with
the blog data to get HTML. Since the Lisp code to use theme functions is
already written, your theme must follow a few rules.

Every theme **must** be in a folder under "themes/" named after the
theme. The theme's templates must start with a namespace declaration
Expand Down Expand Up @@ -67,7 +70,23 @@ template hacking. There is plenty of advice on CSS styling on the web.
I'm no expert but feel free to send pull requests modifying a theme's
CSS or improving this section, perhaps by recommending a CSS resource.

## Creating a Theme from Scratch (with code)
## Djula templates

Djula templates are somewhat more traditional than closure templates.
They are inspired by Django templates. For more info see the Djula
[documentation][djula_doc]. Instead of having a base template that
gets called with data from the child template, Djula uses an extend
mechanism. A template extends from an other template and defines blocks
that the extended templates uses.

Because you extend templates in djula you only have to define a post.html and
index.html template in the theme folder. The variables passed to this template
are the same as with closure. So post.html gets the post and base variables and
index.html gets the index and base variables.

For more information see the hyde-djula theme on how djula works.

## Creating a Theme from Scratch using Closure (with code)

### Step 1. Create the directory.

Expand Down Expand Up @@ -214,5 +233,7 @@ between the pages so navigation is cumbersome but adding links is simple.
Just do: `<a href="{$config.domain}/{$object.url}">{$object.name}</a>`.

[clt]: https://developers.google.com/closure/templates/
[djula]: https://github.com/mmontone/djula
[djula_doc]: https://mmontone.github.io/djula/doc/build/html/index.html
[ovr]: https://github.com/redline6561/coleslaw/blob/master/docs/overview.md
[hck]: https://github.com/redline6561/coleslaw/blob/master/docs/hacking.md
45 changes: 45 additions & 0 deletions plugins/cl-closure.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload :cl-closure))

(defpackage :coleslaw-cl-closure
(:use :cl)
(:import-from :closure-template #:compile-template)
(:import-from :local-time #:format-rfc1123-timestring)
(:import-from :coleslaw #:find-theme
#:app-path
#:render
#:find-injections
#:*config*
#:theme
#:do-files
#:render
#:theme-package)
(:export #:enable))

(in-package :coleslaw-cl-closure)

(defclass cl-closure () ())

(defmethod coleslaw:compile-theme ((cl-closure cl-closure) theme)
"Compile the templates used by cl-closure"
(do-files (file (app-path "themes/auxiliary")
"cl-closure")
(compile-template :common-lisp-backend file))
(do-files (file (find-theme theme) "tmpl")
(compile-template :common-lisp-backend file)))

(defmethod coleslaw:render-page ((template cl-closure) content &optional theme-fn &rest render-args)
(apply (or theme-fn (get-theme-fn 'theme 'base))
:config *config*
:content content
:pubdate (format-rfc1123-timestring nil (local-time:now))
:injections (find-injections content)
:raw (apply #'render content render-args)
render-args))

(defmethod coleslaw:get-theme-fn ((template cl-closure) name &optional (package (theme *config*)))
(let ((closure-func (find-symbol (princ-to-string name) (theme-package package))))
(lambda (&rest template-args)
(funcall closure-func template-args))))

(defun enable ())
57 changes: 57 additions & 0 deletions plugins/djula.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
(eval-when (:compile-toplevel :load-toplevel)
(ql:quickload :djula))

(defpackage :coleslaw-djula
(:use :cl :cl-ppcre)
(:import-from :djula #:compile-template*
#:render-template*)
(:import-from :local-time #:format-rfc1123-timestring)
(:import-from :coleslaw #:get-theme-fn
#:render-page
#:find-theme
#:app-path
#:render
#:find-injections
#:*config*
#:theme
#:do-files)
(:import-from :cl-ppcre #:create-scanner
#:regex-replace)
(:export #:enable))

(in-package :coleslaw-djula)

(defclass djula ()
((templates :initform (make-hash-table :test #'equalp) :accessor templates)))

(defparameter +remove-extension+ (create-scanner "(.*)\\..*"))

(defun compile-djula (djula file &optional (filename (regex-replace +remove-extension+ (file-namestring file) "\\1")))
(setf (gethash filename
(templates djula)) (compile-template* file)))

(defmethod coleslaw:compile-theme ((djula djula) theme)
(do-files (file (app-path "themes/auxiliary")
"djula")
(compile-djula djula file))
(do-files (file (find-theme theme) "html")
(compile-djula djula file)))

(defmethod coleslaw:render-page ((djula djula) content &optional theme-fn &rest render-args)
(apply (or theme-fn #'render)
content
:config *config*
:content content
:pubdate (format-rfc1123-timestring nil (local-time:now))
:injections (find-injections content)
render-args))

(defmethod coleslaw:get-theme-fn ((djula djula) name &optional package)
(declare (ignore package))
(break)
(let ((theme (gethash (string name) (templates djula))))
(lambda (&rest template-args)
(with-output-to-string (stream)
(apply #'render-template* theme stream template-args)))))

(defun enable ())
5 changes: 3 additions & 2 deletions plugins/sitemap.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
#:page-url
#:find-all
#:publish
#:theme-fn
#:get-theme-fn
#:add-document
#:template-engine
#:write-document)
(:import-from :alexandria #:hash-table-values)
(:export #:enable))
Expand All @@ -25,6 +26,6 @@
(let* ((base-urls '("" "sitemap.xml"))
(urls (mapcar #'page-url (hash-table-values coleslaw::*site*)))
(sitemap (make-instance 'sitemap :urls (append base-urls urls))))
(write-document sitemap (theme-fn 'sitemap "sitemap"))))
(write-document sitemap (get-theme-fn (template-engine *config*) 'sitemap "sitemap"))))

(defun enable ())
13 changes: 8 additions & 5 deletions plugins/static-pages.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
#:find-all
#:render
#:publish
#:theme-fn
#:get-theme-fn
#:render-text
#:write-document))
#:write-document
#:template-engine)
(:import-from :djula #:render-template*))

(in-package :coleslaw-static-pages)

Expand All @@ -24,11 +26,12 @@
format (alexandria:make-keyword (string-upcase format))
coleslaw::text (render-text coleslaw::text format))))

(defmethod render ((object page) &key next prev)
(defmethod render ((object page) &rest rest &key next prev)
;; For the time being, we'll re-use the normal post theme.
(declare (ignore next prev))
(funcall (theme-fn 'post) (list :config *config*
:post object)))
(apply (get-theme-fn (template-engine *config*) 'post) :config *config*
:post object
rest))

(defmethod publish ((doc-type (eql (find-class 'page))))
(dolist (page (find-all 'page))
Expand Down
35 changes: 22 additions & 13 deletions src/coleslaw.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,27 @@
(defvar *last-revision* nil
"The git revision prior to the last push. For use with GET-UPDATED-FILES.")

(defvar *templating-engine* nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer the templating engine (and any related vars like *djula-post-template* and *djula-index-template*) be kept in the config object if at all possible. Sure, it's one big nasty God object, but it's in keeping with the conventions elsewhere in the project.

"The templating engine to use. This will be set during the main routine.
Possible options at this time are djula and cl-closure.")

(defvar *djula-post-template* nil
"The template to use for rendering a post using a djula template.")

(defvar *djula-index-template* nil
"The template to use for rendering the index using a djula template.")

(defun main (repo-dir &optional oldrev)
"Load the user's config file, then compile and deploy the blog stored
in REPO-DIR. Optionally, OLDREV is the revision prior to the last push."
(load-config repo-dir)
(let ((*templating-engine* (template-engine *config*)))
(setf *last-revision* oldrev)
(load-content)
(compile-theme (theme *config*))
(compile-theme (template-engine *config*) (theme *config*))
(let ((dir (staging-dir *config*)))
(compile-blog dir)
(deploy dir)))
(deploy dir))))

(defun load-content ()
"Load all content stored in the blog's repo."
Expand Down Expand Up @@ -55,17 +66,15 @@ in REPO-DIR. Optionally, OLDREV is the revision prior to the last push."
(let ((current-working-directory (cl-fad:pathname-directory-pathname path)))
(unless *config*
(load-config (namestring current-working-directory))
(compile-theme (theme *config*)))
(compile-theme (template-engine *config*) (theme *config*)))
(let* ((file (rel-path (repo-dir *config*) path))
(content (construct content-type (read-content file))))
(write-file "tmp.html" (render-page content)))))
(write-file "tmp.html" (render-page (template-engine *config*) content)))))

(defun render-page (content &optional theme-fn &rest render-args)
"Render the given CONTENT to HTML using THEME-FN if supplied.
Additional args to render CONTENT can be passed via RENDER-ARGS."
(funcall (or theme-fn (theme-fn 'base))
(list :config *config*
:content content
:raw (apply 'render content render-args)
:pubdate (format-rfc1123-timestring nil (local-time:now))
:injections (find-injections content))))
(defgeneric render-page (template-engine content &optional theme-fn &rest render-args)
(:documentation "Render a page using the given theme. TEMPLATE-ENGINE should be
an instance of a template-engine object specified in one of the plugins. This
object is stored in the config object. CONTENT should be the object to render as
main part of the page, THEME-FN should be the function to render the page with
and RENDER-ARGS should be additional arguments that should be passed to the
render function(s)."))
27 changes: 22 additions & 5 deletions src/config.lisp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
(in-package :coleslaw)

(defclass blog ()
((author :initarg :author :reader author)
((master :initarg :master :reader master)
(author :initarg :author :reader author)
(charset :initarg :charset :reader charset)
(deploy-dir :initarg :deploy-dir :reader deploy-dir)
(domain :initarg :domain :reader domain)
Expand All @@ -16,8 +17,10 @@
(sitenav :initarg :sitenav :reader sitenav)
(staging-dir :initarg :staging-dir :reader staging-dir)
(theme :initarg :theme :reader theme)
(template-engine :initarg :template-engine :accessor template-engine)
(title :initarg :title :reader title))
(:default-initargs
:master nil
:feeds nil
:license nil
:plugins nil
Expand All @@ -26,7 +29,23 @@
:lang "en"
:page-ext "html"
:separator ";;;;;"
:staging-dir "/tmp/coleslaw"))
:staging-dir "/tmp/coleslaw"
:template-engine 'cl-closure))

(defmethod initialize-instance :after ((config blog) &rest rest)
"Initialize config object by creating the correct template-engine class and
setting it to template-engine"
(declare (ignore rest))
(when (master config) (setf *config* config))
(load-plugins (cons `(,(format nil
"~:@(~A~)"
(string (template-engine config))))
(plugins config)))
(let ((theme-class (intern (string-upcase (template-engine config))
(format nil
"~:@(coleslaw-~A~)"
(string (template-engine config))))))
(setf (template-engine config) (make-instance theme-class))))

(defun dir-slot-reader (config name)
"Take CONFIG and NAME, and return a directory pathname for the matching SLOT."
Expand Down Expand Up @@ -84,6 +103,4 @@ doesn't exist, use the .coleslawrc in the home directory."
preferred over the home directory if provided."
(with-open-file (in (discover-config-path repo-dir) :external-format :utf-8)
(let ((config-form (read in)))
(setf *config* (construct 'blog config-form)
(repo-dir *config*) repo-dir)))
(load-plugins (plugins *config*)))
(construct 'blog (append `(:master t :repo ,repo-dir) config-form)))))
4 changes: 2 additions & 2 deletions src/documents.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ is provided, it overrides the route used."
"Write the given DOCUMENT to disk as HTML. If THEME-FN is present,
use it as the template passing any RENDER-ARGS."
(let ((html (if (or theme-fn render-args)
(apply #'render-page document theme-fn render-args)
(render-page document nil)))
(apply #'render-page (template-engine *config*) document theme-fn render-args)
(render-page (template-engine *config*) document nil)))
(url (namestring (page-url document))))
(write-file (rel-path (staging-dir *config*) url) html)))

Expand Down
7 changes: 5 additions & 2 deletions src/feeds.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@

(defmethod publish ((doc-type (eql (find-class 'feed))))
(dolist (feed (find-all 'feed))
(write-document feed (theme-fn (feed-format feed) "feeds"))))
(write-document feed
(get-theme-fn (template-engine *config*)
(feed-format feed)
"feeds"))))

;;; Tag Feeds

Expand All @@ -34,4 +37,4 @@

(defmethod publish ((doc-type (eql (find-class 'tag-feed))))
(dolist (feed (find-all 'tag-feed))
(write-document feed (theme-fn (feed-format feed) "feeds"))))
(write-document feed (get-theme-fn (template-engine *config*) (feed-format feed) "feeds"))))
9 changes: 5 additions & 4 deletions src/indexes.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
(with-slots (url) object
(setf url (compute-url object slug))))

(defmethod render ((object index) &key prev next)
(funcall (theme-fn 'index) (list :tags (find-all 'tag-index)
(defmethod render ((object index) &rest rest)
(apply (get-theme-fn (template-engine *config*) 'index)
:tags (find-all 'tag-index)
:months (find-all 'month-index)
:config *config*
:index object
:prev prev
:next next)))
(append (find-all 'month-index)
rest)))

;;; Index by Tag

Expand Down
Loading