wal.sh Project Configuration
Introduction
This file contains the configuration for publishing the wal.sh website using Org mode, along with supplementary documentation and scripts. This version focuses on local publishing.
Theme
;; Theme configuration for wal.sh project (use-package doom-themes :ensure t :config ;; Custom face for wal.sh project indication using --secondary-color (defface wal-sh/project-indicator '((t (:background "#3498db" :foreground "#ffffff" :box (:line-width -1 :style released-button)))) "Face for indicating wal.sh project in mode-line.") ;; Add wal.sh project indicator to mode-line (setq-default mode-line-format (cons '(:eval (when (and (buffer-file-name) (string-prefix-p wal-sh/base (buffer-file-name))) (propertize " wal.sh " 'face 'wal-sh/project-indicator))) mode-line-format)) ;; Optional: Add a subtle blue tint to the entire buffer (defun wal-sh/apply-overlay () (when (and (buffer-file-name) (string-prefix-p wal-sh/base (buffer-file-name))) (let ((overlay (make-overlay (point-min) (point-max)))) (overlay-put overlay 'face '(:background "#f0f8ff")) ; Very light blue background (overlay-put overlay 'wal-sh t) (add-hook 'after-change-functions (lambda (&rest _) (remove-overlays (point-min) (point-max) 'wal-sh t)) nil t)))) (add-hook 'find-file-hook 'wal-sh/apply-overlay))
Setup and Dependencies
Package Management
;;; Package management configuration (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) (package-refresh-contents) (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) (eval-when-compile (require 'use-package)) (defun ensure-package-installed (&rest packages) "Ensure every package is installed, install if it's not." (mapcar (lambda (package) (unless (package-installed-p package) (package-install package))) packages)) (defun always-yes-p (&rest _) t) (advice-add 'yes-or-no-p :override #'always-yes-p) (advice-add 'y-or-n-p :override #'always-yes-p) ;; Make sure to have use-package installed (ensure-package-installed 'use-package) (ensure-package-installed 'jq-mode 'json-mode 'mermaid-mode 'ob-graphql 'ob-http 'ob-mermaid 'ob-restclient 'ob-sql-mode 'org-ref 'htmlize) ;; Useful modes for working with various data formats and languages (use-package jq-mode) (use-package json-mode) (use-package mermaid-mode) (use-package ob-graphql) (use-package ob-http) (use-package ob-mermaid) (use-package ob-restclient) (use-package ob-sql-mode) (use-package org-ref) (use-package racket-mode :ensure t) (use-package racket-mode :ensure t) (setq org-babel-default-header-args:scheme '((:impl . "racket") (:cmd . "racket"))) (use-package geiser :ensure t :config (setq geiser-active-implementations '(guile))) (use-package geiser-guile :ensure t) (setq org-babel-scheme-default-implementation 'guile) (setq geiser-default-implementation 'guile) (setq org-babel-default-header-args:scheme '((:cmd . "guile") (:implementation . "guile"))) ;; HTML and exporting support in Org mode (use-package htmlize :ensure t :config (require 'ox-html)) (setq org-confirm-babel-evaluate nil) ;; Org-mode configuration (use-package org :ensure t :config ;; Set org-directory to the base directory of the current file (setq org-directory (file-name-directory (or (buffer-file-name) default-directory))) ;; Set default files and directories based on the org-directory (setq org-default-notes-file (concat org-directory "notes.org")) (setq org-agenda-files (list org-directory)) (setq org-journal-file-format "%Y%m%d.org") ;; Set Guile as the default Scheme implementation for Org-babel (setq org-babel-scheme-default-implementation 'guile) ;; Explicitly set the Scheme command for Org-babel (setq org-babel-scheme-cmd "guile") ;; Enable useful Org-babel languages (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (python . t) (shell . t) (js . t) (sql . t) (http . t) (dot . t) (mermaid . t) (clojure . t) (scheme . t) (graphql . t) (restclient . t))) ;; Allow execution without confirmation (setq org-confirm-babel-evaluate nil) ;; Use Python 3 for Org-babel Python code blocks (setq org-babel-python-command "python3") ;; Enable inline image display after executing code blocks (add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images) ;; Enable syntax highlighting in source blocks (setq org-src-fontify-natively t) ;; Additional useful settings (setq org-src-tab-acts-natively t) ; Make TAB act as if it were issued in the language's major mode (setq org-src-preserve-indentation t) ; Preserve source block indentation (setq org-edit-src-content-indentation 0) ; Set indentation for edited source blocks ;; Enable Org tempo expansion for easy block insertion (require 'org-tempo) ;; Custom templates for org-structure-template-alist (add-to-list 'org-structure-template-alist '("sh" . "src shell")) (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp")) (add-to-list 'org-structure-template-alist '("py" . "src python")) (add-to-list 'org-structure-template-alist '("rkt" . "src racket")) ;; Enable visual-line-mode for Org buffers (add-hook 'org-mode-hook #'visual-line-mode) ;; Enable auto-fill-mode for Org buffers (add-hook 'org-mode-hook #'auto-fill-mode) ;; Set a reasonable fill-column value (setq-default fill-column 80)) ;; Ensure Racket mode is available (use-package racket-mode :ensure t :mode "\\.rkt\\'" :config (setq racket-program "racket")) ; Set path to Racket executable if needed ;; Configure Geiser for Scheme support (use-package geiser :ensure t :config (setq geiser-active-implementations '(guile racket))) (use-package geiser-guile :ensure t) (use-package geiser-racket :ensure t) ;; Optional: Additional configurations for enhancing Org-mode demos ;; Visual enhancements for demos (use-package org-bullets :ensure t :config (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1)))) ;; Table of contents generation in Org-mode documents (use-package toc-org :ensure t :config (add-hook 'org-mode-hook 'toc-org-mode)) (use-package simple-httpd :ensure t) (setq sh-basic-offset 2 sh-indentation 2 sh-indent-for-case-label 0 sh-indent-for-case-alt '+)
Project Variables
;;; Project variables (defvar wal-sh/base (or (getenv "WAL_SH_BASE_DIRECTORY") (expand-file-name default-directory)) "Base directory for wal.sh project.") (defvar wal-sh/local-output (or (getenv "WAL_SH_PUBLISH_LOCAL_DIRECTORY") (expand-file-name "~/public_html")) "Local output directory for wal.sh project.") (setq wal-sh/publishing-directory wal-sh/local-output)
HTML Generation Functions
Utility Functions
(defun wal-sh/slugify-id (id) "Convert ID to a URL-friendly slug." (let ((slug (downcase (replace-regexp-in-string "[^a-zA-Z0-9]+" "-" id)))) (if (string-prefix-p "-" slug) (substring slug 1) slug)))
Header and Navigation
(defun wal-sh/org-html-sitewide-header (info) "Generate the sitewide header HTML." (format "<header class=\"site-header\"> <div class=\"container\"> <nav> <ul> <li><a href=\"/\">Home</a></li> <li><a href=\"/activity-summary/\">Current</a></li> <li><a href=\"/research/\">Research</a></li> <li><a href=\"/events/\">Events</a></li> </ul> </nav> </div> </header>"))
Breadcrumbs
(defun wal-sh/org-html-breadcrumbs (info) "Generate breadcrumbs for the current page." (let* ((file (plist-get info :input-file)) (project-base (expand-file-name wal-sh/base)) (relative-path (file-relative-name file project-base)) (parts (split-string relative-path "/")) (breadcrumbs '()) (path "/") (is-root-index (string= relative-path "index.org"))) (unless is-root-index (push "<a href=\"/\">home</a>" breadcrumbs) (dolist (part parts) (let* ((is-last (eq part (car (last parts)))) (is-index (string= (file-name-sans-extension part) "index")) (display-name (downcase (file-name-sans-extension part))) (mod-time (format-time-string "%Y-%m-%d" (nth 5 (file-attributes file)))) (new-path-component (if (or is-last is-index) (concat (url-encode-url display-name) ".html") (url-encode-url display-name)))) (setq path (if (string= path "/") (concat path new-path-component) (concat path "/" new-path-component))) (unless (and is-index (not is-last)) (push (format "<a href=\"%s\">%s%s</a>" path (if is-index "index" display-name) (if is-last (format " (%s)" mod-time) "")) breadcrumbs))))) (if is-root-index "" (format "<div class=\"breadcrumbs\">%s</div>" (mapconcat 'identity (reverse breadcrumbs) " > ")))))
Preamble and Postamble
(defun wal-sh/org-html-preamble (info) "Combine the sitewide header with the page-specific header and breadcrumbs." (concat (wal-sh/org-html-sitewide-header info) (wal-sh/org-html-breadcrumbs info) (let* ((title (org-export-data (plist-get info :title) info))) (format "<header class=\"page-header\"> <h1 class=\"title\">%s</h1> </header>" title)))) (defun wal-sh/org-html-postamble (info) "Generate the postamble for the HTML export." (let* ((file (plist-get info :input-file)) (author (org-export-data (plist-get info :author) info)) (email (org-export-data (plist-get info :email) info)) (date (format-time-string "%Y-%m-%d %H:%M:%S" (nth 5 (file-attributes file))))) (format "<p class=\"author\">Author: %s</p> <p class=\"email\">%s</p> <p class=\"date\">Last Updated: %s</p>" author email date)))
Sitemap Generation
(defun wal-sh/generate-sitemap (title files) "Generate sitemap as XML." (concat "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">" (mapconcat (lambda (file) (format " <url> <loc>%s</loc> <lastmod>%s</lastmod> </url>" (concat (file-name-as-directory wal-sh/base-url) (file-name-sans-extension (file-relative-name file wal-sh/base)) ".html") (format-time-string "%Y-%m-%dT%H:%M:%S+00:00" (nth 5 (file-attributes file))))) files "") " </urlset>")) (defun wal-sh/publish-sitemap (project &optional sitemap-filename) "Publish sitemap for PROJECT with optional SITEMAP-FILENAME." (let* ((project-plist (cdr project)) (dir (file-name-as-directory (plist-get project-plist :base-directory))) (localdir (file-name-directory dir)) (exclude-regexp (plist-get project-plist :exclude)) (files (nreverse (org-publish-get-base-files project exclude-regexp))) (sitemap-filename (or sitemap-filename "sitemap.xml")) (sitemap-filepath (expand-file-name sitemap-filename dir)) (sitemap-function (or (plist-get project-plist :sitemap-function) 'wal-sh/generate-sitemap))) (with-temp-buffer (insert (funcall sitemap-function "Sitemap" files)) (write-file sitemap-filepath)) (message "Sitemap generated and saved to %s" sitemap-filepath)))
Org Publish Project Alist
(setq org-publish-project-alist `(("wal.sh-main" :base-directory ,wal-sh/base :base-extension "org" :publishing-directory ,wal-sh/publishing-directory :recursive t :publishing-function org-html-publish-to-html :headline-levels 4 :section-numbers nil :with-toc t :html-head-include-default-style nil :html-head-include-scripts nil :html-head "<link rel=\"stylesheet\" href=\"/static/css/style.css\" type=\"text/css\"/>" :html-preamble wal-sh/org-html-preamble :html-postamble wal-sh/org-html-postamble :exclude ,(regexp-opt '("README.org" "SECURITY.org"))) ("wal.sh-static" :base-directory ,(expand-file-name "static" wal-sh/base) :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf" :publishing-directory ,(expand-file-name "static" wal-sh/publishing-directory) :recursive t :publishing-function org-publish-attachment) ("wal.sh-sitemap" :base-directory ,wal-sh/base :publishing-directory ,wal-sh/publishing-directory :base-extension "org" :exclude ,(regexp-opt '("README.org" "SECURITY.org")) :publishing-function wal-sh/publish-(setq )itemap :sitemap-filename "sitemap.xml" :sitemap-function wal-sh/generate-sitemap :sitemap-ignore-case t) ("wal.sh" :components ("wal.sh-main" "wal.sh-static" "wal.sh-sitemap"))))
Publishing Functions
(defun wal-sh/publish-local () "Publish files in the wal.sh project to the local directory." (interactive) (org-publish "wal.sh")) (defun wal-sh/force-publish-local () "Force publish files in the wal.sh project to the local directory." (interactive) (let ((org-publish-use-timestamps-flag nil)) (wal-sh/publish-local))) (defun wal-sh/publish () "Publish files in the wal.sh project for production." (interactive) (let ((org-publish-project-alist (mapcar (lambda (project) (if (string= (car project) "wal.sh-main") (cons (car project) (plist-put (cdr project) :publishing-directory (or (getenv "WAL_SH_PUBLISH_DIRECTORY") wal-sh/publishing-directory))) project)) org-publish-project-alist))) (org-publish "wal.sh"))) (defun wal-sh/force-publish () "Force publish files in the wal.sh project for production." (interactive) (let ((org-publish-use-timestamps-flag nil)) (wal-sh/publish))) (defun wal-sh/clear-publish-cache () "Clear the org-publish cache." (interactive) (setq org-publish-cache nil) (setq org-publish-files-alist nil) (setq org-publish-project-alist nil) (org-publish-reset-cache) (message "Org publish cache cleared."))
Finalization
(provide 'project-config) ;;; project-config.el ends here
HTTP Server
(require 'simple-httpd) (defun wal-sh/httpd-start (port) (let ((inhibit-message nil)) ; enable messages for debugging (message "Attempting to start server on port %d" port) (setq httpd-port port) (unless (process-status "httpd") (httpd-start)) (message "Server status: %s" (process-status "httpd")))) (advice-add 'httpd-start :override #'wal-sh/httpd-start) (defun signal-handler (event) (httpd-stop) (kill-emacs 0)) (define-key special-event-map [sigusr1] 'signal-handler)
Usage Instructions
To generate the project-config.el file from this Org file, follow these steps:
- Save this content to a file named `project-config.org`.
- Open the file in Emacs and run: M-x org-babel-tangle Or, from the command line: emacs –batch –eval "(require 'org)" –eval "(org-babel-tangle-file \"project-config.org\")"
- This will create `project-config.el` in the same directory.
To publish the website locally, create a `publish.el` file with the following content:
;;; publish.el --- Publishing configuration for wal.sh website ;;; Commentary: ;; This file contains the configuration for publishing the wal.sh website. ;; It is designed to be run in batch mode by the Makefile. ;;; Code: ;; Suppress messages about indentation setup (setq inhibit-message t) ;; Load necessary packages (require 'package) (package-initialize) (setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("melpa" . "https://melpa.org/packages/"))) ;; Ensure required packages are installed (dolist (package '(org htmlize ox-html ox-rss)) (unless (package-installed-p package) (package-refresh-contents) (package-install package))) ;; Load org and other required packages (require 'org) (require 'ox-html) (require 'ox-rss) ;; Load the project configuration (load-file "project-config.el") ;; Suppress warnings about python-indent-offset (with-eval-after-load 'python (setq python-indent-guess-indent-offset-verbose nil)) ;; Define publishing function based on command-line argument (defun wal-sh/publish-website () "Publish the wal.sh website based on command-line argument." (let ((arg (car command-line-args-left))) (cond ((string= arg "publish-local") (wal-sh/publish-local)) ((string= arg "publish-local-force") (wal-sh/force-publish-local)) ((string= arg "publish") (wal-sh/publish)) ((string= arg "publish-force") (wal-sh/force-publish)) (t (message "Invalid publishing command. Use 'publish-local', 'publish-local-force', 'publish', or 'publish-force'.") (kill-emacs 1))))) ;; Run the publishing function (wal-sh/publish-website) ;; Generate DEF CON 32 Schedule if the file exists (let ((defcon-generator "events/defcon-schedule-generator.el")) (when (file-exists-p defcon-generator) (load-file defcon-generator) (when (fboundp 'defcon/org-generate-table) (with-current-buffer (find-file-noselect "events/defcon-32.org") (defcon/org-generate-table) (save-buffer) (kill-buffer))))) ;; Reset inhibit-message (setq inhibit-message nil) ;; Print a completion message (message "Publishing complete!") (provide 'publish) ;;; publish.el ends here
Then, run the following command:
emacs --batch -l publish.el
This will publish the website to the local output directory (default: ~/publichtml).