Skip to content

Commit

Permalink
Copy static files, render tags and timestamps.
Browse files Browse the repository at this point in the history
  • Loading branch information
svetlyak40wt committed Apr 1, 2024
1 parent 0a09111 commit c4d10b7
Show file tree
Hide file tree
Showing 17 changed files with 388 additions and 70 deletions.
12 changes: 12 additions & 0 deletions docs/difference-from-coleslaw.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(defvar *templates-difference*
"Variable `config` was renamed to `site`.
`post` renamed to `content`
`pubdate` renamed to `site.pubdate`.
For templates base on Closure Template, StatiCL defines these filters:
* date - formats the a timestamp in this as YYYY-MM-DD
* datetime - formats a timestamp as YYYY-MM-DD HH:MM
To define additional filters, inherit your template class from CLOSURE-TEMPLATE and define a method for REGISTER-USER-FILTERS generic-function.
")
12 changes: 12 additions & 0 deletions example/blog/first.post
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
;;;;;
title: Second version of Ultralisp.org is available now!
description: An announce of the new version on Ultralisp - frequently updated Common Lisp libraries distribution. Ultralisp allows everyone to host list libraries, it is like PyPi hosting from Python world.
tags: release, my-projects, ultralisp
created-at: 2019-02-03 15:00
format: md
;;;;;

I believe, that software should evolve and evolve quickly.
One of the reasons why Common Lisp seems strange to newcomers is its
ecosystem. It takes a long time to add a new library and make it useful
to other common lispers.
123 changes: 88 additions & 35 deletions src/content.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,22 @@
(:import-from #:staticl/content/reader
#:read-content-file)
(:import-from #:local-time
#:+iso-8601-date-format+
#:format-timestring
#:universal-to-timestamp
#:timestamp)
(:import-from #:serapeum
#:soft-list-of)
(:import-from #:utilities.print-items
#:print-items-mixin
#:print-items)
(:import-from #:closer-mop
#:class-slots
#:slot-definition-initargs)
(:import-from #:staticl/tag
#:tag)
(:import-from #:staticl/format
#:to-html)
(:export #:supported-content-types
#:content-type
#:content
Expand Down Expand Up @@ -63,7 +72,10 @@


(defclass content (print-items-mixin)
())
((metadata :initform (make-hash-table :test 'equal)
:type hash-table
:reader content-metadata
:documentation "A hash with additional fields specified in the file's header.")))


(defclass content-with-title-mixin ()
Expand All @@ -78,7 +90,7 @@

(defclass content-with-tags-mixin ()
((tags :initarg :tags
:type (soft-list-of string)
:type (soft-list-of tag)
:reader content-tags)))


Expand Down Expand Up @@ -106,36 +118,40 @@


(defmethod print-items append ((obj content-from-file))
(list (list :file (list :after :title) "= ~S" (content-file obj))))


;; (defmethod print-object ((obj content) stream)
;; (print-unreadable-object (obj stream :type t)
;; (when (and (slot-boundp obj 'title)
;; (slot-boundp obj 'file))
;; (format stream "~S :file ~S"
;; (content-title obj)
;; (content-file obj)))))


(defmethod initialize-instance ((obj content) &rest initargs)
(apply #'call-next-method
obj
(normalize-plist initargs
:created-at (lambda (value)
(etypecase value
(null value)
(string
(universal-to-timestamp
(org.shirakumo.fuzzy-dates:parse value)))
(local-time:timestamp
value)))
:tags (lambda (value)
(etypecase value
(string
(mapcar #'str:trim
(str:split "," value
:omit-nulls t))))))))
`(((:file (:after :title)) " file = ~S" ,(content-file obj))))


(defmethod initialize-instance ((obj content) &rest initargs &key &allow-other-keys)
(let* ((normalized-args
(normalize-plist initargs
:created-at (lambda (value)
(etypecase value
(null value)
(string
(universal-to-timestamp
(org.shirakumo.fuzzy-dates:parse value)))
(local-time:timestamp
value)))
:tags (lambda (value)
(etypecase value
(string
(loop for tag-name in (str:split "," value
:omit-nulls t)
collect (make-instance 'tag
:name (str:trim tag-name))))))))
(result (apply #'call-next-method obj normalized-args))
(all-initargs
(loop for slot in (class-slots (class-of obj))
appending (slot-definition-initargs slot))))

;; Write unknown initargs into the metadata slot
(loop for (key value) on initargs by #'cddr
unless (member key all-initargs)
do (setf (gethash (string-downcase key)
(content-metadata result))
value))

(values result)))


(defgeneric supported-content-types (site)
Expand Down Expand Up @@ -217,20 +233,57 @@
(vars (dict "site" site-vars
"content" content-vars))
(template-name (content-template content)))

(staticl/theme:render theme template-name vars stream))))


(defgeneric preprocess (site plugin content-objects)
(:documentation "Returns an additional list content objects such as RSS feeds or sitemaps."))


(defmethod template-vars ((content content) &key (hash (dict)))
(setf (gethash "metadata" hash)
(content-metadata content))

(if (next-method-p)
(call-next-method content :hash hash)
(values hash)))


(defmethod template-vars ((content content-from-file) &key (hash (dict)))
(setf (gethash "title" hash)
(content-title content)
(gethash "html" hash)
(staticl/format:to-html (content-text content)
(content-format content)))
(to-html (content-text content)
(content-format content))
(gethash "created-at" hash)
(content-created-at content)

;; (gethash "created-at" hash)
;; (format-timestring nil (content-created-at content)
;; :format local-time:+iso-8601-format+)
;; ;; Also we provide only a date, because it might be more convinitent
;; ;; to render it in short format:
;; (gethash "created-at-date" hash)
;; (format-timestring nil (content-created-at content)
;; :format +iso-8601-date-format+)

;; ;; And in case if user wants a complied to ISO format:
;; (gethash "created-at-iso" hash)
;; (format-timestring nil (content-created-at content)
;; :format local-time:+iso-8601-format+)
)

(if (next-method-p)
(call-next-method content :hash hash)
(values hash)))


(defmethod template-vars ((content content-with-tags-mixin) &key (hash (dict)))
(setf (gethash "tags" hash)
(mapcar #'template-vars
(content-tags content)))

(if (next-method-p)
(call-next-method content :hash hash)
(values hash)))
7 changes: 5 additions & 2 deletions src/content/defaults.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
(:import-from #:staticl/site
#:site)
(:import-from #:staticl/content/page
#:page-type))
#:page-type)
(:import-from #:staticl/content/post
#:post-type))
(in-package #:staticl/content/defaults)


(defmethod supported-content-types ((site site))
(list (make-instance 'page-type)))
(list (make-instance 'page-type)
(make-instance 'post-type)))
22 changes: 22 additions & 0 deletions src/content/post.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(uiop:define-package #:staticl/content/post
(:use #:cl)
(:import-from #:staticl/content
#:content-from-file
#:content-type)
(:export #:post-type
#:post))
(in-package #:staticl/content/post)


(defclass post (content-from-file)
()
(:default-initargs
;; In coleslaw page and post share the same template
:template "post"))


(defclass post-type (content-type)
()
(:default-initargs
:type "post"
:content-class 'post))
8 changes: 7 additions & 1 deletion src/core.lisp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(uiop:define-package #:staticl
(:use #:cl)
(:import-from #:staticl/site
#:site-theme
#:site-plugins
#:make-site)
(:import-from #:staticl/content
Expand All @@ -9,6 +10,8 @@
#:preprocess)
(:import-from #:serapeum
#:->)
(:import-from #:staticl/theme
#:copy-static)
(:nicknames #:staticl/core)
(:export #:generate
#:stage))
Expand All @@ -24,7 +27,7 @@
(root-dir *default-pathname-defaults*)
(stage-dir (merge-pathnames (make-pathname :directory '(:relative "stage"))
(uiop:ensure-directory-pathname root-dir))))
(let* ((site (make-site root-dir))
(let* ((site (make-site root-dir))
(initial-content (read-contents site))
(plugins (site-plugins site))
(additional-content
Expand All @@ -35,4 +38,7 @@
additional-content)))
(loop for content in all-content
do (write-content site content stage-dir))

(copy-static (site-theme site)
stage-dir)
(values)))
19 changes: 11 additions & 8 deletions src/site.lisp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(uiop:define-package #:staticl/site
(:use #:cl)
(:import-from #:log)
(:import-from #:local-time)
(:import-from #:serapeum
#:dict
#:soft-list-of
Expand All @@ -17,13 +18,12 @@
#:template-vars
#:load-theme
#:theme)
(:export
#:site
#:site-content-root
#:site-title
#:make-site
#:site-plugins
#:site-theme))
(:export #:site
#:site-content-root
#:site-title
#:make-site
#:site-plugins
#:site-theme))
(in-package #:staticl/site)


Expand Down Expand Up @@ -105,4 +105,7 @@

(defmethod template-vars ((site site) &key (hash (dict)))
(setf (gethash "title" hash)
(site-title site)))
(site-title site)
(gethash "pubdate" hash)
(local-time:now))
(values hash))
23 changes: 23 additions & 0 deletions src/tag.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(uiop:define-package #:staticl/tag
(:use #:cl)
(:import-from #:serapeum
#:dict)
(:import-from #:staticl/theme
#:template-vars)
(:export #:tag-name
#:tag))
(in-package #:staticl/tag)


(defclass tag ()
((name :initarg :name
:type string
:reader tag-name))
(:default-initargs
:name (error ":NAME is required argument for a tag.")))


(defmethod template-vars ((tag tag) &key (hash (dict)))
(setf (gethash "name" hash)
(tag-name tag))
(values hash))
26 changes: 25 additions & 1 deletion src/theme.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
#:print-items)
(:export #:theme
#:template-vars
#:render))
#:render
#:list-static
#:copy-static))
(in-package #:staticl/theme)


Expand Down Expand Up @@ -85,3 +87,25 @@
(remove-if #'null
(list site-root
builtin-themes-dir))))))


(defgeneric list-static (theme)
(:documentation "Returns a list of static files such as CSS, JS, images.
Each list item should be a list of two items where first
item is an absolute pathname and second is a pathname relative
to the root of the site."))


(defgeneric copy-static (theme stage-dir)
(:documentation "Copies static files such as CSS, JS, images into the STAGE-DIR.
Usually it is enough to define a method for LIST-STATIC generic-function.")
(:method ((theme theme) (stage-dir pathname))
(loop with target-dir = (uiop:ensure-directory-pathname stage-dir)
for (source-filename relative-filename) in (list-static theme)
for target-filename = (merge-pathnames relative-filename
target-dir)
do (ensure-directories-exist target-filename)
(uiop:copy-file source-filename
target-filename))))
Loading

0 comments on commit c4d10b7

Please sign in to comment.