Website instance definitions for the Org-Roam-Blog Engine Package.
§ Site struct
(defun org-roam-blog--content-wrap-default (site template context) "Default content wrapper for ORB SITE. Renders CONTENT in TEMPLATE placed between `wrapper-top` and `wrapper-bottom` mustache partials. They must exist inside `src-template-dir' of the SITE. Returns a closure with a signature expected by `mustache-render' for \"wrapper\" tags." (let ((template-dirlist (list (org-roam-blog-site-src-template-dir site)))) (lambda (template context) (let ((mustache-partial-paths template-dirlist)) (concat (mustache-render "{{> wrapper-top}}" context) (mustache-render template context) (mustache-render "{{> wrapper-bottom}}" context)))))) (defun org-roam-blog--render-default (site template context) "Default render function for ORB SITE. Merges CONTEXT with `site-top-context' and processes through wrapped TEMPLATE." (mustache-render (concat "{{#orb-site-wrapped}}" template "{{/orb-site-wrapped}}") (ht-merge context (org-roam-blog-site-top-context site) (ht ("orb-site-wrapped" (funcall #'org-roam-blog--content-wrap-default site template context))))))
(cl-defstruct (org-roam-blog-site (:constructor org-roam-blog-site--create) (:copier nil)) (staging-dir nil :type string) (scratch-dir nil :type string) (src-root-dir nil :type string) (src-template-dir nil :type string) (index-ht (ht-create) :type hash-table) (top-context (ht-create) :type hash-table) (entry-registry (ht-create) :type hash-table) (content-wrap-fn #'org-roam-blog--content-wrap-default :type function) (render-fn #'org-roam-blog--render-default :type function))
Blog site constructor with automatic folder selection.
(cl-defun org-roam-blog-site-create (&rest args) (let ((site (apply #'org-roam-blog-site--create args))) (unless (org-roam-blog-site-staging-dir site) (setf (org-roam-blog-site-staging-dir site) (read-directory-name "Select staging directory (output location):"))) (unless (org-roam-blog-site-src-root-dir site) (setf (org-roam-blog-site-src-root-dir site) (read-directory-name "Select root files source directory:"))) (unless (org-roam-blog-site-src-template-dir site) (setf (org-roam-blog-site-src-template-dir site) (read-directory-name "Select template files source directory:"))) site))
§ Page rendering for site
Public -render
func:
(defsubst org-roam-blog-render (site template context) "Public shortcut to wrap and output CONTEXT to specific content mustache TEMPLATE for a SITE." (funcall (org-roam-blog-site-render-fn site) site template context))
Public -stage
. Llooks as it will be the final method to output each page
of a site.
(defsubst org-roam-blog-stage (fname site template context &optional subdir) "Output rendered page as FNAME file under `scratch-dir' of the SITE. Extend the staging path with SUBDIR when specified. Pipes SITE, TEMPLATE and CONTEXT through `org-roam-blog-render'. Note that the `scratch-dir' is meant to be (r)sync-ed with the final `staging-dir' by the top-level SITE generating routines like e.g. `org-roam-blog-stage-site', that will create/process/sync/erase another `scratch-dir' (setting it to nil for the processed site). For debugging the output of particular pages/sections of the SITE through calls to `org-roam-blog-stage', set its `scratch-dir' field manually." (let* ((subdir (or subdir "")) (subdir (f-expand subdir (org-roam-blog-site-scratch-dir site))) (_ (f-mkdir subdir)) (scratch-path (f-expand fname subdir))) (f-write-text (org-roam-blog-render site template context) 'utf-8 scratch-path)))
I keep the &optional subdir
functionality in org-roam-blog-stage
for
creation of intermediate subdirectories. At the same time, large relative
pathname should is available for each entry context object, see e.g. definition of
org-roam-blog--relative-entry-url
.
§ Index regitstrarion and staging
(defsubst org-roam-blog-register-index (site index) "Register INDEX in the index-ht of the SITE, using slug of the INDEX as key." (ht-set! (org-roam-blog-site-index-ht site) (org-roam-blog-index-slug index) index))
A shortcut macro for index registration is defined hereby:
(defmacro org-roam-blog-reg (site &rest kwargs) "Shortcut macro for immediate registration of Org Roam Blog index defined by KWARGS for the specified SITE." (let ((index (gensym "orb-index-"))) `(let ((,index (org-roam-blog-index-create ,@kwargs))) (org-roam-blog-register-index ,site ,index))))
For any index context-fn
should return a list of context-groups,
that is - paginated entry list. The groups (or feeds, or index views)
are staged with the following routine:
(defsubst org-roam-blog-site--stage-index-groups (site index &optional entry-list) "Stage the grouped preview pages (feeds) for the INDEX of a SITE. ENTRY-LIST may come optionally pre-built." (if (and (org-roam-blog-index-template index) (org-roam-blog-index-context-fn index)) (let ((template (org-roam-blog-index-template index)) (context (funcall (org-roam-blog-index-context-fn index) index entry-list)) (subdir (org-roam-blog-index-slug index))) (cl-loop for context-group in context do (org-roam-blog-stage (format "%s-%s.html" org-roam-blog-index-filename-prefix (ht-get context-group "page")) site template context-group subdir)) (org-roam-blog-stage (format "%s.html" org-roam-blog-index-filename-prefix) site template (car context) subdir)) (cl-block no-index-page-output (when (null (org-roam-blog-index-template index)) (message "no template defined for %s" (org-roam-blog-index-title index))) (when (null (org-roam-blog-index-context-fn index)) (message "no context-fn defined for %s" (org-roam-blog-index-title index))))))
The next function stages individual entry pages of an index.
Similar to previous one, it iterates over a flattened collecton of
context objects for an entry-list
of an index
.
(defsubst org-roam-blog-site--stage-index-entries (site index &optional entry-list) "Stage the individual entry pages the INDEX of a SITE. ENTRY-LIST may come optionally pre-built." (if (and (org-roam-blog-index-entry-template index) (org-roam-blog-index-entry-context-fn index)) (let ((template (org-roam-blog-index-entry-template index)) (entry-context-list (org-roam-blog--build-entry-context-list index entry-list)) (subdir (if-let ((index-slug (org-roam-blog-index-slug index)) (entry-dir (org-roam-blog-index-entry-dir index))) (concat index-slug "/" entry-dir) index-slug))) (cl-loop for (node context) in entry-context-list ;;FIXME: capture counters through `org-roam-blog--build-entry-context-list' ;; for counter from 1 to (length entry-context-list) do (org-roam-blog-stage (funcall (org-roam-blog-index-entry-fname-fn index) node) site template context subdir))) (cl-block no-entry-pages-output (when (null (org-roam-blog-index-entry-template index)) (message "no entry-template defined in %s" (org-roam-blog-index-title index))) (when (null (org-roam-blog-index-entry-context-fn index)) (message "no entry-context-fn defined in %s" (org-roam-blog-index-title index))))))
This stages an index
utilizing the previous definitions.
The entry-list
is built just once:
(defsubst org-roam-blog-site--stage-index (site index) "Stage the INDEX of a SITE: process the feed view for entry groups and individual page for each entry within an INDEX." (let ((entry-list (org-roam-blog--index-entry-list index))) (org-roam-blog-site--stage-index-groups site index entry-list) (org-roam-blog-site--stage-index-entries site index entry-list)))
§ Site Staging
Global Id registry for the entries. Will be used for inter-linking of related entries within the exported site.
(defsubst org-roam-blog-site--process-registry (site) "Make all individual entry Id's point to their lead index in the SITE's `entry-registry' hash table field." (cl-loop for index in (ht-values (org-roam-blog-site-index-ht site)) do (when (org-roam-blog-index-leading index) (cl-block process-registry (message "processing %s into global entry registry" (org-roam-blog-index-title index)) (let ((nodelist (flatten-list (org-roam-blog--index-entry-list index)))) (cl-loop for node in nodelist do (ht-set! (org-roam-blog-site-entry-registry site) (org-roam-node-id node) index)))))))
Topmost routine for site staging.
(defsubst org-roam-blog-stage-site (site) ; TODO: &optional index "Main staging routine for a SITE. I'm using intermediate scratch temporary directory and rsync (default for `org-roam-blog-local-sync-command'), in order to also clean up orphans from the final `org-roam-blog-site-staging-dir'." ;; update global context with the stamp of generation time (ht-set! (org-roam-blog-site-top-context site) "top-gen-timestamp" (format-time-string "%Y-%m-%d %H:%M")) ;; create and prepopulate scratch dir (setf (org-roam-blog-site-scratch-dir site) (make-temp-file "orb-" t)) (shell-command (format "%s %s %s" org-roam-blog-local-sync-command (fname-with-tslash (org-roam-blog-site-src-root-dir site)) (fname-no-tslash (org-roam-blog-site-scratch-dir site)))) ;; generate global entry registry for the site (org-roam-blog-site--process-registry site) ;; set global context (org-roam-blog-g-entries-set (org-roam-blog-site-entry-registry site)) (org-roam-blog-g-site-set site) ;; output indexes contents for the site (cl-loop for index in (ht-values (org-roam-blog-site-index-ht site)) do (cl-block stage-index (message "staging index %s" (org-roam-blog-index-title index)) (org-roam-blog-site--stage-index site index))) ;; reset global context (org-roam-blog-g-reset) ;; sync with the staging directory (unless (f-exists? (org-roam-blog-site-staging-dir site)) (f-mkdir (org-roam-blog-site-staging-dir site))) (shell-command (format "%s %s %s" org-roam-blog-local-sync-command (fname-with-tslash (org-roam-blog-site-scratch-dir site)) (fname-no-tslash (org-roam-blog-site-staging-dir site)))) ;; erase the intermediate scratch dir (f-delete (org-roam-blog-site-scratch-dir site) t) (setf (org-roam-blog-site-scratch-dir site) nil))