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) " &gt; ")))))

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:

  1. Save this content to a file named `project-config.org`.
  2. 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\")"
  3. 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).

Author: Jason Walsh

jwalsh@nexus

Last Updated: 2025-07-30 13:45:27

build: 2025-12-23 09:11 | sha: a10ddd7