;;; Buid --- The builder for FG42 -*- lexical-binding: t; -*- ;; ;; Copyright (c) 2010-2022 Sameer Rahmani & Contributors ;; ;; Author: Sameer Rahmani ;; URL: https://gitlab.com/FG42/FG42 ;; Version: 3.0.0 ;; ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with thnis program. If not, see . ;; ;;; Commentary: ;; Cubes are the building blocks of any `FG42' editor. Each `cube' is a ;; unit which defines different abilities in a deterministic and idempotent ;; way. Cubes are composable and a composition of cubes creates an editor. ;; ;;; Code: (require 'ox-html) (require 'cubes/all) (require 'fg42/build/core) (require 'fg42/build/utils) (require 'fg42/build/ox-template) (defvar cube-template (from-root "/docs/site/templates/cube.org")) (defvar cubes-index-template (from-root "/docs/site/templates/cubes.org")) (defun fg42/build-docs-for-cube (build-dir cube) "Build the given CUBE's doc in BUILD-DIR" (let* ((cubes-dir (expand-file-name "site/cubes/" build-dir)) (props (symbol-plist cube)) (docs (plist-get props :docs)) (cube-file (concat (expand-file-name (symbol-name cube) cubes-dir) ".org"))) (copy-template cube-template cube-file `((:title . ,(plist-get props :title)) (:docs . ,docs))))) (defun fg42/build-docs-copy-base (build-dir) "Copy the base structure of the website to the BUILD-DIR." (copy-directory (from-docs "/pages") (expand-file-name "site/pages" build-dir) nil t)) (defun fg42/build-prepare-docs (build-dir host) "Prepare the documents and the website in the given BUILD-DIR." (fg42/build-docs-copy-base build-dir) ;; Build the org files for each cube (message "Processing the docs for all the cubes") (mapcar (lambda (cube) (fg42/build-docs-for-cube build-dir cube)) fg42/available-cubes) ;; Build the cubes list index file (message "Processing cubes index file") (copy-template cubes-index-template (expand-file-name "site/cubes/index.org" build-dir) (list (cons :links (mapconcat (lambda (c) (format "- [[%s][%s]]\n" ;; href (format "%s/cubes/%s.html" host c) ;; title (plist-get (symbol-plist c) :title))) (sort fg42/available-cubes 'string<)))))) (defun all-org-files () "Return a list of all the org files in the orgs directory." (split-string (shell-command-to-string (format "find %s -iname \"*.org\"" org-directory)) "\n" t)) (defun get-all-tags () "Return a list of all the tags in the org files." (seq-reduce ;; all-tags is in (tags . tags->files) form (lambda (all-tags file) (if (get-file-global-props file "PAGE") ;; Ignore pages all-tags (with-temp-buffer (insert-file-contents file) (let ((tags (mapcar #'car (org-get-buffer-tags)))) (seq-reduce (lambda (result tag) (let ((tag-list (car result)) (tag->file (cdr result))) (cons ;; Tag list (if (member tag tag-list) tag-list (sort (cons tag tag-list) 'string<)) ;; tag->file (cons (cons tag ;; Current value of the the given tag (all the files ;; that contain that tag) (append (list file) (cdr (assoc tag tag->file)))) tag->file)))) tags all-tags))))) (all-org-files) '())) (defun get-all-posts () "Return all the post org files. Not pages." (let ((files (all-org-files))) (seq-reduce (lambda (result file) (let ((is-page? (string= (get-file-global-props file "PAGE") "true"))) (if (not is-page?) ;; It's a post (cons (list ;; This is not effecient since we parse the file ;; on each query, but who cares :D ? (->epoch (get-file-global-props file "DATE")) (get-file-global-props file "TITLE") (replace-regexp-in-string "\\.org" ".html" (file-name-nondirectory file))) result) result))) files '()))) (defun get-all-sorted-posts () "Return all posts in sorted order." (sort (get-all-posts) (lambda (x y) (> (car x) (car y))))) (defun latest-org-list (base-url) "Return a list of links (using BASE-URL) to the tags in org format." (let ((posts (get-all-sorted-posts))) (mapconcat (lambda (post) (format " - [[%s/essays/%s][%s]]" base-url (nth 2 post) (nth 1 post))) posts "\n"))) (defun get-all-categories () "Return all the categories of the org files." (seq-reduce ;; all-cats is in (cats . cat->files) form (lambda (all-cats file) (let ((is-page? (string= (get-file-global-props file "PAGE") "true")) (cat (get-file-global-props file "CATEGORY")) (cat-list (car all-cats)) (cat->file (cdr all-cats))) (if (not is-page?) (cons ;; Category list (if (member cat cat-list) cat-list (sort (cons cat cat-list) 'string<)) ;; cat->file (cons (cons cat ;; Current value of the the given cat (all the files ;; under that category) (append (list file) (cdr (assoc cat cat->file)))) cat->file)) all-cats))) (all-org-files) '())) (defun category-org-list () "Return a list of links to the categories in org format." (let ((categories (get-all-categories))) (mapconcat (lambda (cat) (let ((count (length (cdr (assoc cat (cdr categories)))))) (format " - [[./%s.html][%s(%s)]]" cat cat count))) (car categories) "\n"))) (defun tags-org-list () "Return a list of links to the tags in org format." (let ((tags (get-all-tags))) (mapconcat (lambda (tag) (let ((count (length (cdr (assoc tag (cdr tags)))))) (format " - [[./%s.html][%s(%s)]]" tag tag count))) (car tags) "\n"))) (defun create-tag-pages (build-dir) "Create all the tag files in the BUILD-DIR." (let ((tags (get-all-tags))) (mapcar (lambda (tag) (let ((out (format "%s/site/tags/%s.org" build-dir (or tag "Uncategorized"))) (files (cdr (assoc tag (cdr tags))))) (copy-template (from-docs "/templates/links_template.org") out (list (cons :links (mapconcat (lambda (file-pair) (format "- [[file:..%s][%s]]" (replace-regexp-in-string (regexp-quote (from-root "/build/site")) "" (cdr file-pair) nil 'literal) (get-file-global-props (cdr file-pair) "TITLE"))) (sort (mapcar #'pair-file-with-date files) (lambda (x y) (> (car x) (car y)))) "\n")) (cons :title (or tag "Uncategorized")))))) (car tags)))) (defun create-category-pages (build-dir) "Create all the category files in the BUILD-DIR." (let ((tags (get-all-categories))) (mapcar (lambda (tag) (let ((out (format "%s/site/categories/%s.org" build-dir (or tag "Uncategorized"))) (files (cdr (assoc tag (cdr tags))))) (when (null tag) (message "[Error]: The following files are missig a category: %s" (mapconcat (lambda (x) (format " %s\n" x)) (sort (mapcar #'pair-file-with-date files) (lambda (x y) (> (car x) (car y)))))) (error "The above list of files are missing a category")) (copy-template (from-docs "/templates/links_template.org") out (list (cons :links (mapconcat (lambda (file-pair) (format "- [[file:..%s][%s]]" (replace-regexp-in-string (regexp-quote (from-root "/build/site")) "" (cdr file-pair) nil 'literal) (get-file-global-props (cdr file-pair) "TITLE"))) (sort (mapcar #'pair-file-with-date files) (lambda (x y) (> (car x) (car y)))) "\n")) (cons :title (or tag "Uncategorized")))))) (car tags)))) (defun fg42/build-docs (build-dir) "Build the documents and the website in the given BUILD-DIR." (let ((base-url (if (prod-p) "https://fg42.org" "http://localhost:3003")) (stage1-dir (expand-file-name "site" build-dir)) (final-dir (expand-file-name "site-build" build-dir))) ;; Apply all the actions (mapcar (lambda (f) (funcall f build-dir base-url)) fg42/build-docs-actions) ;; Copy the assets (copy-directory (from-docs "/assets") (expand-file-name "assets" stage1-dir) nil t) ;; We will use the org-agenda to extract all the tags ;;; Set the dir containing org the org-files (setq org-directory (expand-file-name "site" build-dir)) ;;; Discover all the org files (setq org-agenda-files (all-org-files)) (setf user-full-name fg42/build-author-name) (setf user-mail-address fg42/build-author-email) ;; Disable default header links (top, next) (setf org-html-home/up-format "") (setf org-html-link-up "") (setf org-html-link-home "") (setf org-html-scripts "") (copy-template (from-docs "/config.org") (expand-file-name "config.org" build-dir) '()) (message "Creating the main index file") (copy-template (from-docs "/templates/index.org") (expand-file-name "site/index.org" build-dir) `((:links . ,(latest-org-list base-url)) (:base-url . ,base-url))) (message "Creating categories") (copy-template (from-docs "/templates/categories.org") (expand-file-name "site/categories/index.org" build-dir) `((:links . ,(category-org-list)))) (create-category-pages build-dir) (message "Creating tags") (copy-template (from-docs "/templates/tags.org") (expand-file-name "site/tags/index.org" build-dir) `((:links . ,(tags-org-list)))) (create-tag-pages build-dir) (setq org-html-preamble #'preamble-fn) (setq org-html-htmlize-output-type nil) (setq org-latex-listings t) (setq org-publish-project-alist `(("fg42.org" :base-directory ,stage1-dir :root-directory ,stage1-dir :recursive t :base-extension "org" :publishing-directory ,final-dir ;; Exclude the blog archive index autogenerated below ;; Note that the regexp is relative to :base-directory ;; :exclude "^index.org" :section-numbers nil :with-author t :with-drawers t :html-format-drawer-function custom-drawer-format :with-properties t :with-tags t :with-timestamps t :with-toc nil :base-url ,base-url :html-link-home "/" :html-template ,(from-docs "/templates/blog.html") :html-page-preamble-template ,(from-docs "/templates/page-preamble.html") :html-post-preabmle-template ,(from-docs "/templates/post-preamble.html") :html-tags-template ,(from-docs "/templates/tags.html") :publishing-function org-html-publish-to-templated-html :auto-sitemap t :htmlized-source nil :sitemap-folders ignore :sitemap-style list :sitemap-title "FG42, The ultimate editor for true believers" :sitemap-filename "sitemap.inc" :sitemap-sort-files anti-chronologically :html-format-headline-function headline-format :makeindex nil) ("statics" :base-directory ,stage1-dir :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|svg" :publishing-directory ,final-dir :recursive t :publishing-function org-publish-attachment) ("build" :components ("fg42.org" "statics")))) (org-publish-project "build" t nil) (message "Build complete.") )) (provide 'fg42/build/docs) ;;; docs.el ends here