/src/theindex.inc
/draft/
/html.bk.bk/
+/source/sitemap.org
+/source/theindex.inc
+/source/theindex.org
+++ /dev/null
-<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" data-background-color="#111111" preserveAspectRatio="xMidYMid meet">
- <g>
- <title>Layer 1</title>
- <rect width="400" height="400" x="0.00002" y="0" fill="#111111" data-fill-palette-color="background" id="background"/>
- <g id="tight-bounds" transform="matrix(1.42967, 0, 0, 1.43769, 244.049, 9.88795)">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_1" x="-110.21414" y="7.95458">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_10">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_18">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_26">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_32">
- <svg width="154.0987" viewBox="2.049999952316284 -37.599998474121094 24 38.5" height="247.2" data-palette-color="#cfdfd5" id="svg_37">
- <g class="undefined-text-0" data-fill-palette-color="primary" id="svg_38">
- <path d="m24.8,-9l1.25,0.5q-0.55,2.25 -1.75,4q-1.2,1.75 -2.9,2.95q-1.7,1.2 -3.75,1.83q-2.05,0.62 -4.3,0.62l0,0q-2.85,0 -4.95,-0.93q-2.1,-0.92 -3.52,-2.47q-1.43,-1.55 -2.13,-3.55q-0.7,-2 -0.7,-4.15l0,0q0,-1.65 0.43,-3.35q0.42,-1.7 1.27,-3.18q0.85,-1.47 2.13,-2.57q1.27,-1.1 2.97,-1.55l0,0q-2.4,-1.5 -3.47,-3.45q-1.08,-1.95 -1.08,-4.1l0,0q0,-1.7 0.65,-3.38q0.65,-1.67 1.93,-2.95q1.27,-1.27 3.15,-2.07q1.87,-0.8 4.27,-0.8l0,0q1.45,0 2.75,0.33q1.3,0.32 2.3,1.07q1,0.75 1.6,1.93q0.6,1.17 0.6,2.87l0,0q0,1.75 -0.65,3.05q-0.65,1.3 -1.95,1.3l0,0q-0.55,0 -1.2,-0.28q-0.65,-0.27 -1.35,-0.97l0,0q0.7,-0.25 1.13,-1.28q0.42,-1.02 0.42,-2.07l0,0q0,-0.95 -0.32,-1.55q-0.33,-0.6 -0.83,-0.93q-0.5,-0.32 -1.05,-0.42q-0.55,-0.1 -1,-0.1l0,0q-1.1,0 -1.9,0.45q-0.8,0.45 -1.3,1.2q-0.5,0.75 -0.75,1.75q-0.25,1 -0.25,2.05l0,0q0,1.4 0.4,2.7q0.4,1.3 1.23,2.4q0.82,1.1 2.07,1.92q1.25,0.83 2.95,1.28l0,0q-1.6,0.4 -2.85,1.42q-1.25,1.03 -2.07,2.45q-0.83,1.43 -1.25,3.08q-0.43,1.65 -0.43,3.2l0,0q0,1.35 0.35,2.6q0.35,1.25 1.08,2.17q0.72,0.93 1.85,1.5q1.12,0.58 2.72,0.58l0,0q1.2,0 2.5,-0.35q1.3,-0.35 2.43,-1q1.12,-0.65 2,-1.6q0.87,-0.95 1.27,-2.15l0,0z" fill="#cfdfd5" data-fill-palette-color="primary" id="svg_39"/>
- </g>
- </svg>
- </svg>
- </svg>
- </svg>
- </svg>
- </svg>
- <rect width="154.0987" height="247.2" fill="none" visibility="hidden" id="svg_23" y="7.95458" x="-110.21414"/>
- </g>
- </g>
-
-</svg>
\ No newline at end of file
+++ /dev/null
-<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 8C3 7.06812 3 6.60218 3.15224 6.23463C3.35523 5.74458 3.74458 5.35523 4.23463 5.15224C4.60218 5 5.06812 5 6 5V5H18V5C18.9319 5 19.3978 5 19.7654 5.15224C20.2554 5.35523 20.6448 5.74458 20.8478 6.23463C21 6.60218 21 7.06812 21 8V16C21 16.9319 21 17.3978 20.8478 17.7654C20.6448 18.2554 20.2554 18.6448 19.7654 18.8478C19.3978 19 18.9319 19 18 19V19H6V19C5.06812 19 4.60218 19 4.23463 18.8478C3.74458 18.6448 3.35523 18.2554 3.15224 17.7654C3 17.3978 3 16.9319 3 16V8Z" stroke="#cfdfd5" stroke-width="2" stroke-linejoin="round"></path> <path d="M4 6L10.683 11.8476C11.437 12.5074 12.563 12.5074 13.317 11.8476L20 6" stroke="#cfdfd5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
\ No newline at end of file
+++ /dev/null
-<svg fill="#000000" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="4.8"><path d="M21.327 8.566c0-4.339-2.843-5.61-2.843-5.61-1.433-.658-3.894-.935-6.451-.956h-.063c-2.557.021-5.016.298-6.45.956 0 0-2.843 1.272-2.843 5.61 0 .993-.019 2.181.012 3.441.103 4.243.778 8.425 4.701 9.463 1.809.479 3.362.579 4.612.51 2.268-.126 3.541-.809 3.541-.809l-.075-1.646s-1.621.511-3.441.449c-1.804-.062-3.707-.194-3.999-2.409a4.523 4.523 0 0 1-.04-.621s1.77.433 4.014.536c1.372.063 2.658-.08 3.965-.236 2.506-.299 4.688-1.843 4.962-3.254.434-2.223.398-5.424.398-5.424zm-3.353 5.59h-2.081V9.057c0-1.075-.452-1.62-1.357-1.62-1 0-1.501.647-1.501 1.927v2.791h-2.069V9.364c0-1.28-.501-1.927-1.502-1.927-.905 0-1.357.546-1.357 1.62v5.099H6.026V8.903c0-1.074.273-1.927.823-2.558.566-.631 1.307-.955 2.228-.955 1.065 0 1.872.409 2.405 1.228l.518.869.519-.869c.533-.819 1.34-1.228 2.405-1.228.92 0 1.662.324 2.228.955.549.631.822 1.484.822 2.558v5.253z"></path></g><g id="SVGRepo_iconCarrier"><path d="M21.327 8.566c0-4.339-2.843-5.61-2.843-5.61-1.433-.658-3.894-.935-6.451-.956h-.063c-2.557.021-5.016.298-6.45.956 0 0-2.843 1.272-2.843 5.61 0 .993-.019 2.181.012 3.441.103 4.243.778 8.425 4.701 9.463 1.809.479 3.362.579 4.612.51 2.268-.126 3.541-.809 3.541-.809l-.075-1.646s-1.621.511-3.441.449c-1.804-.062-3.707-.194-3.999-2.409a4.523 4.523 0 0 1-.04-.621s1.77.433 4.014.536c1.372.063 2.658-.08 3.965-.236 2.506-.299 4.688-1.843 4.962-3.254.434-2.223.398-5.424.398-5.424zm-3.353 5.59h-2.081V9.057c0-1.075-.452-1.62-1.357-1.62-1 0-1.501.647-1.501 1.927v2.791h-2.069V9.364c0-1.28-.501-1.927-1.502-1.927-.905 0-1.357.546-1.357 1.62v5.099H6.026V8.903c0-1.074.273-1.927.823-2.558.566-.631 1.307-.955 2.228-.955 1.065 0 1.872.409 2.405 1.228l.518.869.519-.869c.533-.819 1.34-1.228 2.405-1.228.92 0 1.662.324 2.228.955.549.631.822 1.484.822 2.558v5.253z"></path></g></svg>
\ No newline at end of file
+++ /dev/null
-<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 455.731 455.731" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <rect x="0" y="0" style="fill:#111111;" width="455.731" height="455.731"></rect> <g> <path style="fill:#cfdfd5;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348 c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348 C391.986,303.103,357.971,220.923,296.208,159.16z"></path> <path style="fill:#cfdfd5;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348 C282.429,270.196,184.507,172.273,64.143,172.273z"></path> <circle style="fill:#cfdfd5;" cx="109.833" cy="346.26" r="46.088"></circle> </g> </g> </g></svg>
\ No newline at end of file
+++ /dev/null
-body {
- color: #cfdfd5;
- background-color: #111111;
- margin-left: auto;
- margin-right: auto;
- width: 90%;
- max-width: 90ch;
- /* font-size: 1.1em; */
- font-family: "Helvetica Neue", sans-serif;
-}
-
-a:link {
- color: #00c089;
-}
-
-a:visited {
- color: #5dc0aa;
-}
-
-time {
- color: #9ac8e0;
-}
-
-.home-link {
- float: left;
-}
-
-.other-links {
- float: right;
-}
-
-footer {
- font-size: smaller;
- text-align: center;
-}
-
-.icon-links {
- height: 40px;
- text-decoration: none;
-}
-
-blockquote {
- border-left-style: solid;
- padding-left: 1em;
-}
-
-dt {
- font-weight: bold;
-}
-
-pre {
- overflow: auto;
- border-top-style: solid;
- border-top-width: 2px;
- border-left-style: solid;
- border-left-width: 2px;
- border-right-style: solid;
- border-right-width: 1px;
- border-bottom-style: solid;
- border-bottom-width: 1px;
- padding: 0.8em;
- background-color: #222522;
-}
-
-.footpara {
- display: inline;
-}
-
-code {
- color: #78afff;
- font-family: Hack,monospace;
-}
-
-h2 {
- color: #7fc500;
-}
-h3 {
- color: #5dc0aa;
-}
-h4 {
- color: #af9fff;
-}
-h5 {
- color: #7fcfdf;
-}
-h6 {
- color: #cfc04f;
-}
-
-/*
-Generated with `M-x org-html-htmlize-generate-css` after loading the
-`ef-bio` theme by Protesilaos Stavrou.
-*/
-.org-builtin {
- /* font-lock-builtin-face */
- color: #3fb83f;
- font-weight: bold;
-}
-.org-comment {
- /* font-lock-comment-face */
- color: #b7a07f;
- font-style: italic;
-}
-.org-comment-delimiter {
- /* font-lock-comment-delimiter-face */
- color: #b7a07f;
- font-style: italic;
-}
-.org-constant {
- /* font-lock-constant-face */
- color: #37aff6;
-}
-.org-doc {
- /* font-lock-doc-face */
- color: #7fc07f;
- font-style: italic;
-}
-.org-doc-markup {
- /* font-lock-doc-markup-face */
- color: #37aff6;
-}
-.org-escape {
- /* font-lock-escape-face */
- color: #cfc04f;
-}
-.org-function-call {
- /* font-lock-function-call-face */
- color: #7fc500;
-}
-.org-function-name {
- /* font-lock-function-name-face */
- color: #7fc500;
-}
-.org-preprocessor {
- /* font-lock-preprocessor-face */
- color: #3fb83f;
-}
-.org-property-name {
- /* font-lock-property-name-face */
- color: #78afff;
-}
-.org-property-use {
- /* font-lock-property-use-face */
- color: #78afff;
-}
-.org-rainbow-delimiters-depth-1 {
- /* rainbow-delimiters-depth-1-face */
- color: #00c089;
-}
-.org-rainbow-delimiters-depth-2 {
- /* rainbow-delimiters-depth-2-face */
- color: #7fc500;
-}
-.org-rainbow-delimiters-depth-3 {
- /* rainbow-delimiters-depth-3-face */
- color: #5dc0aa;
-}
-.org-rainbow-delimiters-depth-4 {
- /* rainbow-delimiters-depth-4-face */
- color: #af9fff;
-}
-.org-rainbow-delimiters-depth-5 {
- /* rainbow-delimiters-depth-5-face */
- color: #7fcfdf;
-}
-.org-rainbow-delimiters-depth-6 {
- /* rainbow-delimiters-depth-6-face */
- color: #cfc04f;
-}
-.org-rainbow-delimiters-depth-7 {
- /* rainbow-delimiters-depth-7-face */
- color: #37aff6;
-}
-.org-rainbow-delimiters-depth-8 {
- /* rainbow-delimiters-depth-8-face */
- color: #6fc5ef;
-}
-.org-rainbow-delimiters-depth-9 {
- /* rainbow-delimiters-depth-9-face */
- color: #d38faf;
-}
-.org-rainbow-delimiters-mismatched {
- /* rainbow-delimiters-mismatched-face */
- color: #ffffff;
- background-color: #bd1f30;
-}
-.org-string {
- /* font-lock-string-face */
- color: #af9fff;
-}
-.org-type {
- /* font-lock-type-face */
- color: #7fcfdf;
-}
-.org-variable-name {
- /* font-lock-variable-name-face */
- color: #78afff;
-}
-.org-variable-use {
- /* font-lock-variable-use-face */
- color: #78afff;
-}
-.org-warning-1 {
- /* font-lock-warning-face */
- color: #cfc04f;
-}
-.org-negation-char {
- /* font-lock-negation-char-face */
- font-weight: bold;
-}
-.org-regexp {
- /* font-lock-regexp-face */
- color: #af9fff;
-}
-.org-regexp-grouping-backslash {
- /* font-lock-regexp-grouping-backslash */
- color: #cfc04f;
-}
-.org-regexp-grouping-construct {
- /* font-lock-regexp-grouping-construct */
- color: #3fb83f;
-}
-
-.timestamp {
- color: #5dc0aa;
-}
-
-.subtitle {
- font-style: italic;
-}
--- /dev/null
+;;; esy-publish.el --- Simple Static Site Generator -*- lexical-binding:t -*-
+
+;; Copyright (C) 2023 Eshel Yaron
+
+;; Author: Eshel Yaron <me@eshelyaron.com>
+;; Maintainer: Eshel Yaron <me@eshelyaron.com>
+;; Keywords: languages extensions
+;; URL: http://git.eshelyaron.com/gitweb/?p=esy-publish.git
+;; Package-Version: 0.4.0
+;; Package-Requires: ((emacs "28.2"))
+
+;; This file is NOT part of GNU Emacs.
+
+;;; Commentary:
+
+;; Build a static websites from Org files
+
+;;; Code:
+
+(require 'org)
+(require 'ox-publish)
+(require 'ox-html)
+(require 'org-transclusion)
+(require 'dom)
+(require 'xref)
+(require 'htmlize)
+(require 'rainbow-delimiters)
+
+(defvar esy-publish-did-setup-p nil)
+
+(defvar esy-publish-elisp-directory
+ (file-name-as-directory (file-name-directory (expand-file-name load-file-name))))
+
+(defvar esy-publish-root-directory esy-publish-elisp-directory)
+
+(defvar esy-publish-drafts-directory
+ (file-name-as-directory (expand-file-name "drafts"
+ esy-publish-root-directory)))
+
+(defvar esy-publish-source-directory
+ (file-name-as-directory (expand-file-name "source"
+ esy-publish-root-directory)))
+
+(defvar esy-publish-notes-source-directory
+ (file-name-as-directory (expand-file-name "notes"
+ esy-publish-source-directory)))
+
+(defvar esy-publish-posts-source-directory
+ (file-name-as-directory (expand-file-name "posts"
+ esy-publish-source-directory)))
+
+(defvar esy-publish-remote-directory
+ "/rsync:root@direct.eshelyaron.com:/var/www/html")
+
+(defvar esy-publish-local-directory
+ (file-name-as-directory (expand-file-name "local"
+ esy-publish-root-directory)))
+
+(defvar esy-publish-local-posts-directory
+ (file-name-as-directory (expand-file-name "posts"
+ esy-publish-local-directory)))
+
+(defvar esy-publish-keywords '("emacs"
+ "prolog"
+ "language"
+ "politics"
+ "ai"
+ "lisp"))
+
+(defvar esy-publish-notes-completion-history nil)
+
+(defconst esy-publish-post-metadata-line
+ (concat "\n"
+ "@@html:" "<div class=\"metadata\">" "@@"
+ "Created on [{{{date}}}], "
+ "last updated [{{{modification-time(%Y-%m-%d, t)}}}]"
+ "@@html:" "</div>" "@@"))
+
+(defun esy-publish--title-to-file-base-name (title)
+ (downcase (string-join (string-split title (rx (+ (not alnum))) t)
+ "-")))
+(defun esy-publish--dom-to-string (&rest doms)
+ (with-temp-buffer
+ (mapc #'dom-print doms)
+ (buffer-string)))
+
+(defun esy-publish--file-url (file)
+ (concat
+ "https://eshelyaron.com/"
+ (let ((path (file-relative-name file esy-publish-local-directory)))
+ (if (string= (file-name-base file) "index")
+ (file-name-directory path)
+ path))))
+
+;;;###autoload
+(defun esy-publish-create-post (title subtitle description keywords)
+ (interactive (list (read-string "Post title: ")
+ (read-string "Post subtitle: ")
+ (read-string "Post description: ")
+ (completing-read-multiple "Post keywords: " esy-publish-keywords)))
+ (esy-publish-setup)
+ (let* ((date (format-time-string "%F"))
+ (base (concat date "-" (esy-publish--title-to-file-base-name title)))
+ (file (expand-file-name (file-name-with-extension base "org")
+ esy-publish-drafts-directory)))
+ (if (file-exists-p file)
+ (error "Post already exists!")
+ (find-file file)
+ (insert
+ "#+TITLE: " title "\n"
+ "#+SUBTITLE: " subtitle "\n"
+ "#+DESCRIPTION: " description "\n"
+ "#+KEYWORDS: " (string-join keywords ",") "\n"
+ "#+DATE: " date "\n"
+ esy-publish-post-metadata-line "\n")
+ (set-buffer-modified-p nil))))
+
+(defun org-dblock-write:posts (params)
+ (let* ((dir (plist-get params :dir))
+ (limit (plist-get params :limit))
+ (all-posts (reverse (directory-files dir nil (rx bos digit (+ any) ".org" eos))))
+ (posts (if limit (take limit all-posts) all-posts)))
+ (dolist (post posts)
+ (let ((file (expand-file-name post dir)))
+ (insert "- ["
+ "[file:" (file-relative-name file) "]"
+ "[" (substring post 0 10) " ~ " (org-get-title file) "]"
+ "]\n")))
+ (when (and limit (< limit (length all-posts)))
+ (insert "- [[file:" (file-relative-name dir) "][...older posts]]"))
+ (delete-blank-lines)))
+
+(defun esy-publish-insert-posts-dblock (limit)
+ (interactive (list (when current-prefix-arg
+ (prefix-numeric-value current-prefix-arg))))
+ (org-create-dblock (list :name "posts"
+ :dir esy-publish-posts-source-directory
+ :limit limit))
+ (org-update-dblock))
+
+(defun org-dblock-write:notes (params)
+ (let* ((dir (plist-get params :dir))
+ (notes (delete "index.org"
+ (directory-files dir nil
+ (rx bos alnum (+ any)
+ ".org" eos)))))
+ (dolist (note notes)
+ (let* ((file (expand-file-name note dir))
+ (buffer (find-file-noselect file))
+ (titles (with-current-buffer buffer
+ (org-collect-keywords '("TITLE" "SUBTITLE"))))
+ (title (car (alist-get "TITLE" titles nil nil #'string=)))
+ (subtitle (car (alist-get "SUBTITLE" titles nil nil #'string=))))
+ (insert "- [[file:" (file-relative-name file) "][" title "]] :: " subtitle "\n")))
+ (delete-blank-lines)))
+
+(defun esy-publish-insert-notes-dblock ()
+ (interactive)
+ (org-create-dblock (list :name "notes"
+ :dir esy-publish-notes-source-directory))
+ (org-update-dblock))
+
+(defun org-dblock-write:links-to-note (params)
+ (let* ((dir (plist-get params :dir))
+ (files (sort
+ (delete-dups
+ (mapcar #'xref-location-group
+ (mapcar #'xref-match-item-location
+ (xref-matches-in-directory (rx "[[note:" (literal (file-name-base (buffer-file-name))) "][")
+ "*.org"
+ dir
+ nil))))
+ #'string-lessp) ))
+ (dolist (file files)
+ (let* ((full (expand-file-name file dir))
+ (titles (with-current-buffer (find-file-noselect full)
+ (org-collect-keywords '("TITLE" "SUBTITLE"))))
+ (title (car (alist-get "TITLE" titles nil nil #'string=)))
+ (subtitle (car (alist-get "SUBTITLE" titles nil nil #'string=))))
+ (insert "- [[file:" (file-relative-name full) "][" title "]] :: " subtitle "\n")))
+ (delete-blank-lines)))
+
+(defun esy-publish-insert-links-to-note-dblock ()
+ (interactive)
+ (org-create-dblock (list :name "links-to-note"
+ :dir esy-publish-notes-source-directory))
+ (org-update-dblock))
+
+(defun esy-publish-follow-note-link (path arg)
+ (org-link-open-as-file
+ (expand-file-name (file-name-with-extension path "org")
+ esy-publish-notes-source-directory)
+ arg))
+
+(defun esy-publish-export-note-link (path description backend &optional _info)
+ (when (eq backend 'html)
+ (esy-publish--dom-to-string
+ `(a ((href . ,(concat "/notes/" path ".html"))
+ (class . "note-link")
+ (title . ,(let* ((file (expand-file-name (file-name-with-extension path "org")
+ esy-publish-notes-source-directory))
+ (buffer (find-file-noselect file))
+ (titles (with-current-buffer buffer
+ (org-collect-keywords '("TITLE"))))
+ (title (car (alist-get "TITLE" titles nil nil #'string=))))
+ (concat "Notes about " title))))
+ ,description))))
+
+(defun esy-publish--note-titles ()
+ (mapcar (lambda (file)
+ (cons (org-get-title
+ (expand-file-name file esy-publish-notes-source-directory))
+ (file-name-base file)))
+ (delete "index.org"
+ (directory-files esy-publish-notes-source-directory
+ nil (rx bos alnum (+ any)
+ ".org" eos)))))
+
+(defun esy-publish-create-empty-note (title)
+ (interactive (list (read-string "Note subject: ")))
+ (let ((base (esy-publish--title-to-file-base-name title)))
+ (with-temp-buffer
+ (insert
+ "#+TITLE: " title "\n"
+ "#+SUBTITLE: " (read-string "Subtitle: ") "\n"
+ "#+DESCRIPTION: Eshel Yaron's notes about " title "\n"
+ "#+KEYWORDS: " (read-string "Keywords: ") "\n"
+ "#+DATE: " (format-time-string "%F") "\n"
+ "\n" "\n"
+ "* References in published posts" "\n"
+ "#+BEGIN: links-to-note :dir \"" esy-publish-posts-source-directory "\"" "\n"
+ "#+END:" "\n"
+ "* References in other notes" "\n"
+ "#+BEGIN: links-to-note :dir \"" esy-publish-notes-source-directory "\"" "\n"
+ "#+END:" "\n")
+ (write-file (expand-file-name
+ (file-name-with-extension base "org")
+ esy-publish-notes-source-directory)))
+ base))
+
+(defun esy-publish-complete-note-link (&optional _arg)
+ (let* ((title-name-alist (esy-publish--note-titles))
+ (max-title-width (apply #'max
+ (mapcar #'length
+ (mapcar #'car
+ title-name-alist))))
+ (completion-extra-properties
+ (list
+ :annotation-function
+ (lambda (title)
+ (let ((subtitle (with-current-buffer
+ (find-file-noselect
+ (expand-file-name
+ (file-name-with-extension
+ (alist-get title
+ title-name-alist
+ nil nil
+ #'string=)
+ "org")
+ esy-publish-notes-source-directory))
+ (car (alist-get "SUBTITLE"
+ (org-collect-keywords
+ '("SUBTITLE"))
+ nil nil #'string=)))))
+ (concat (make-string (1+ (- max-title-width
+ (length title)))
+ ?\s)
+ subtitle)))))
+ (title (completing-read "Note: "
+ title-name-alist
+ nil 'confirm nil
+ 'esy-publish-notes-completion-history))
+ (name (or (alist-get title
+ title-name-alist
+ nil nil
+ #'string=)
+ (esy-publish-create-empty-note title))))
+ (concat "note:" name)))
+
+(defun esy-publish-describe-note-link (loc &optional _desc)
+ (org-get-title (expand-file-name (file-name-with-extension (substring loc 5) "org")
+ esy-publish-notes-source-directory)))
+
+(defun esy-publish-store-note-link ()
+ (when (and (derived-mode-p 'org-mode)
+ (buffer-file-name)
+ (equal (file-name-as-directory (expand-file-name (file-name-directory (buffer-file-name))))
+ esy-publish-notes-source-directory))
+ (let* ((note (file-name-base (buffer-file-name)))
+ (link (concat "note:" note))
+ (description (org-get-title)))
+ (org-link-store-props
+ :type "note"
+ :link link
+ :description description))))
+
+(defface esy-publish-note-link
+ '((t :underline (:style wave) :slant italic))
+ "Face applied to \"note:\" links.")
+
+;;;###autoload
+(defun esy-publish-setup ()
+ (unless esy-publish-did-setup-p
+ (dolist (cell '(("posts" . esy-publish-insert-posts-dblock)
+ ("notes" . esy-publish-insert-notes-dblock)
+ ("links-to-note" . esy-publish-insert-links-to-note-dblock)))
+ (org-dynamic-block-define (car cell) (cdr cell)))
+ (org-link-set-parameters "note"
+ :follow #'esy-publish-follow-note-link
+ :export #'esy-publish-export-note-link
+ :store #'esy-publish-store-note-link
+ :complete #'esy-publish-complete-note-link
+ :insert-description #'esy-publish-describe-note-link
+ :face 'esy-publish-note-link)
+ (function-put 'esy/init-step 'doc-string-elt 2)
+ (setq esy-publish-did-setup-p t)))
+
+(defun esy-publish--post-to-feed-item (file)
+ (with-current-buffer (find-file-noselect
+ (expand-file-name
+ file esy-publish-local-posts-directory))
+ (let ((dom (libxml-parse-html-region (point-min) (point-max))))
+ `(item nil
+ (title nil ,(string-join (dom-strings (car (dom-by-tag dom 'title)))))
+ (author nil ,user-full-name)
+ (category nil ,(car (string-split (dom-attr (seq-find (lambda (m) (pcase m (`(meta ((name . "keywords") . ,_)) t))) (dom-by-tag dom 'meta)) 'content) " ")))
+ (link nil ,(concat "https://eshelyaron.com/posts/" file))
+ (guid ((isPermaLink . "true")) ,(concat "https://eshelyaron.com/posts/" file))
+ (pubDate nil ,(substring file 0 10))
+ (description nil ,(format "<![CDATA[%s]]>" (esy-publish--dom-to-string (car (dom-by-id dom "content")))))))))
+
+(defun esy-publish--finalize-sitemap (plist)
+ (let ((locs (mapcar #'esy-publish--file-url
+ (directory-files-recursively esy-publish-local-directory
+ (rx ".html" eos)))))
+ (with-temp-buffer
+ (insert "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n")
+ (dom-print `(urlset ((xlmns . "http://www.sitemaps.org/schemas/sitemap/0.9"))
+ ,@(mapcar (lambda (loc)
+ `(url nil (loc nil ,loc)))
+ locs))
+ t t)
+ (write-file (expand-file-name "sitemap.xml" (plist-get plist :publishing-directory))))))
+
+(defun esy-publish--finalize-feed (plist)
+ (let ((posts (reverse (directory-files esy-publish-local-posts-directory
+ nil
+ (rx bos digit (+ any) ".html" eos)))))
+ (with-temp-buffer
+ (insert "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n")
+ (dom-print `(rss ((version . "2.0"))
+ (channel nil
+ (title nil ,user-full-name)
+ (generator nil "GNU Emacs")
+ (link nil "https://eshelyaron.com")
+ (description nil "RSS Feed of eshelyaron.com")
+ (pubDate nil ,(format-time-string "%F"))
+ ,@(mapcar #'esy-publish--post-to-feed-item posts)))
+ t t)
+ (write-file (expand-file-name "rss.xml" (plist-get plist :publishing-directory))))))
+
+(defun esy-publish--transclude-config (&rest _)
+ (with-current-buffer
+ (find-file-noselect
+ (expand-file-name "esy.org" esy-publish-source-directory))
+ (org-transclusion-add-all)))
+
+(defun esy-publish--prepare-indices (&rest _)
+ (dolist (dir (list esy-publish-notes-source-directory
+ esy-publish-posts-source-directory
+ esy-publish-source-directory))
+ (with-current-buffer (find-file-noselect (expand-file-name "index.org" dir))
+ (org-update-all-dblocks))))
+
+(defun esy-publish--prepare-notes-links (&rest _)
+ (dolist (note (delete "index.org"
+ (directory-files esy-publish-notes-source-directory
+ t
+ (rx bos alnum (+ any)
+ ".org" eos))))
+ (with-current-buffer (find-file-noselect note)
+ (org-update-all-dblocks))))
+
+(defun esy-publish--prepare (&rest _)
+ (esy-publish--transclude-config)
+ (esy-publish--prepare-indices)
+ (esy-publish--prepare-notes-links))
+
+(defun esy-publish--add-canonical-tags (_plist)
+ (dolist (file (directory-files-recursively esy-publish-local-directory
+ (rx ".html" eos)))
+ (with-current-buffer (find-file-noselect file)
+ (goto-char (point-min))
+ (when (search-forward "<!-- insert canonical tag here -->" nil t)
+ (replace-match (format "<link rel=\"canonical\" href=\"%s\" />"
+ (esy-publish--file-url file))
+ nil t))
+ (basic-save-buffer))))
+
+(defun esy-publish--finalize (plist)
+ (esy-publish--add-canonical-tags plist)
+ (esy-publish--finalize-feed plist)
+ (esy-publish--finalize-sitemap plist))
+
+;;;###autoload
+(defun esy-publish (&optional force)
+ (interactive "P")
+ (esy-publish-setup)
+ (let* ((org-export-with-sub-superscripts '{})
+ (org-export-with-section-numbers nil)
+ (org-export-with-toc nil)
+ (org-export-with-smart-quotes t)
+ (org-html-htmlize-output-type 'css)
+ (org-html-metadata-timestamp-format "%Y-%m-%d")
+ (org-time-stamp-formats '("%Y-%m-%d" . "%Y-%m-%d %H:%M"))
+ (org-confirm-babel-evaluate nil)
+ (org-src-lang-modes nil)
+ (prog-mode-hook '(rainbow-delimiters-mode))
+ (make-backup-files nil)
+ (org-publish-project-alist
+ (list '("all" :components ("assets" "org"))
+ (list "assets"
+ :base-directory esy-publish-source-directory
+ :publishing-directory esy-publish-local-directory
+ :base-extension "svg\\|ico\\|css\\|png"
+ :publishing-function #'org-publish-attachment)
+ (list "org"
+ :completion-function #'esy-publish--finalize
+ :base-directory esy-publish-source-directory
+ :publishing-directory esy-publish-local-directory
+ :preparation-function #'esy-publish--prepare
+ :completion-function #'ignore
+ :base-extension "org"
+ :recursive t
+ :exclude nil
+ :include nil
+ :publishing-function #'org-html-publish-to-html
+ :html-doctype "html5"
+ :html-html5-fancy t
+ :auto-sitemap t
+ :sitemap-title "Sitemap for eshelyaron.com"
+ :makeindex t
+ :with-date t
+ :html-head-include-default-style nil
+ :html-head-include-scripts nil
+ :html-use-infojs nil
+ :html-link-org-files-as-html t
+ :html-head (esy-publish--dom-to-string '(link ((rel . "stylesheet")
+ (href . "/style.css")
+ (type . "text/css"))))
+ :html-head-extra (concat (esy-publish--dom-to-string
+ '(link ((rel . "alternate")
+ (href . "/rss.xml")
+ (type . "application/rss+xml")
+ (title . "RSS feed of eshelyaron.com"))))
+ "\n<!-- insert canonical tag here -->")
+ :html-preamble-format
+ (list
+ (list
+ "en"
+ (esy-publish--dom-to-string
+ '(nav ((id . "icon-links")
+ (class . "icon-links"))
+ (div ((class . "home-link"))
+ (a ((href . "/"))
+ (img ((src . "/home.svg")
+ (height . "35")
+ (width . "35")
+ (alt . "Home")))))
+ (div ((class . "other-links"))
+ (a ((href . "mailto:me@eshelyaron.com"))
+ (img ((src . "/mail.svg")
+ (height . "30")
+ (width . "30")
+ (alt . "Mail"))))
+ " "
+ (a ((href . "https://emacs.ch/@eshel")
+ (rel . "me"))
+ (img ((src . "/mastodon.svg")
+ (height . "28")
+ (width . "28")
+ (alt . "Mastodon"))))
+ " "
+ (a ((href . "/rss.xml"))
+ (img ((src . "/rss.svg")
+ (height . "30")
+ (width . "30")
+ (alt . "RSS Feed"))))))
+ '(hr nil))))
+ :html-postamble t
+ :html-postamble-format
+ (list
+ (list
+ "en"
+ (esy-publish--dom-to-string
+ '(footer ((id . "footer")
+ (class . "footer"))
+ (hr nil)
+ "© "
+ (time ((class . "copyright-year")) "2023")
+ " %a"))))))))
+ (org-publish "all" force)))
+
+(defun esy-publish-all ()
+ (esy-publish t))
+
+;;;###autoload
+(defun esy-publish-to-remote ()
+ (interactive)
+ (compile (format "rsync -vrz %s %s"
+ esy-publish-local-directory
+ esy-publish-remote-directory)))
+
+(provide 'esy-publish)
+;;; esy-publish.el ends here
+++ /dev/null
-#+TITLE: Eshel Yaron
-#+AUTHOR: Eshel Yaron
-#+DESCRIPTION: Personal home page of Eshel Yaron
-#+KEYWORDS: eshel language emacs programming prolog
-#+OPTIONS: toc:nil ^:{}
-
-Welcome to [[./index.org][my website]], the one true source of reliable curated information about me and my activities.
-
-#+begin_src prolog
- ?- likes('Eshel', Stuff).
- Stuff = 'logic programming';
- Stuff = 'linguistics';
- Stuff = 'cognition';
- Stuff = 'functional programming';
- Stuff = 'The cyber';
- Stuff = 'type systems';
- Stuff = 'coffee ☕️';
- Stuff = 'free software'.
-#+end_src
-
-* Recent Posts
-:PROPERTIES:
-:CUSTOM_ID: recent-posts
-:END:
-#+BEGIN: posts :dir "/Users/eshelyaron/checkouts/eshelyaron.com/org/posts" :limit 5
-#+END:
-
-
-* Projects
-:PROPERTIES:
-:CUSTOM_ID: projects
-:END:
-
-** =sweep=: SWI-Prolog Embedded in Emacs
-:PROPERTIES:
-:CUSTOM_ID: sweep
-:END:
-
-[[https://git.sr.ht/~eshel/sweep][Sweep]] is an Emacs module which uses the C interfaces of both
-SWI-Prolog and Emacs to bring the two together into one address space.
-
-For more details, see [[file:sweep.org][the Sweep manual]].
-
-** Sourcehut GraphQL client for SWI-Prolog
-:PROPERTIES:
-:CUSTOM_ID: sourcehut-pl
-:END:
-
-[[https://git.sr.ht/~eshel/sourcehut.pl][sourcehut.pl]] - a SWI-Prolog package for interacting with the GraphQL
-API of sourcehut instances.
-
-=sourcehut.pl= can be used to automate maintenance tasks for project
-hosted on sourcehut, for example to attach a build artifact to a given
-tag of a git repository from within SWI-Prolog:
-
-#+begin_src prolog
- ?- sourcehut_git_repository("eshel", "sourcehut.pl", Repo, []),
- get_dict(id, Repo, RepoId),
- sourcehut_git_upload_artifact(RepoId, "v0.1.2", "/tmp/foo/baz.txt", Artifact, []).
-#+end_src
-
-** [[https://git.sr.ht/~eshel/eshellisp][eshellisp]]
-:PROPERTIES:
-:CUSTOM_ID: eshellisp
-:END:
-
-A [[https://git.sr.ht/~eshel/eshellisp][Scheme Lisp interpreter implemented in SWI-Prolog]].
-
-#+begin_src sh
- $ cat scheme/repl.scm
- (define repl () (write (eval (read))) (repl))
- (repl)
-
- $ ./eshellisp scheme/repl.scm
- % (cons (+ 1 2) 4)
- % (3 . 4)
-#+end_src
-
-** [[https://git.sr.ht/~eshel/flymake-swi-prolog][flymake-swi-prolog.el]] and [[https://git.sr.ht/~eshel/diagnostics.pl][diagnostics.pl]]
-:PROPERTIES:
-:CUSTOM_ID: flymake-swi-prolog
-:END:
-
-=diagnostics.pl= is a SWI-Prolog package implementing a simple and
-extensible inteface for diagnosing issues with SWI-Prolog source code,
-exposing by default the powerful analysis used by the built-in
-SWI-Prolog IDE.
-
-=flymake-swi-prolog= is an Emacs Lisp package implementing a Flymake
-backend that leverages =diagnostics.pl= to provide diagnostics for
-SWI-Prolog source code in =prolog-mode= Emacs buffers.
-
-** [[https://git.sr.ht/~eshel/ropes.pl][ropes.pl]]
-:PROPERTIES:
-:CUSTOM_ID: ropes-pl
-:END:
-
-A (SWI-)Prolog implemantation of the [[https://en.wikipedia.org/wiki/Rope_(data_structure)][rope data structure]] for efficient
-massive string editing.
-
-Based on [[https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.14.9450&rep=rep1&type=pdf][Ropes: An alternative to strings]].
-
-#+begin_src prolog
-test(edit) :-
- string_rope("Hello, World!" , HW),
- string_rope("Here be dragons", HD),
- rope_split(HD, 8, _, D), % "Here be " + "dragons"
- rope_split(HW, 7, H, W), % "Hello, " + "World!"
- rope_split(W , 5, _, E), % "World" + "!"
- rope_concat(H , D, HS),
- rope_concat(HS, E, HE),
- rope_string(HE, HF),
- assertion(HF == "Hello, dragons!").
-#+end_src
-
-** [[https://github.com/oskardrums/ebpf][Erlang eBPF library]]
-:PROPERTIES:
-:CUSTOM_ID: erlang-ebpf
-:END:
-
-A low level interface to the [[https://ebpf.io/][Linux eBPF system]] for [[https://www.erlang.org/][Erlang]].
-
-
-** [[https://git.sr.ht/~eshel/eshelyaron.com][This website]]
-:PROPERTIES:
-:CUSTOM_ID: this-website
-:END:
-
-My first taste of web development, a practice that I have always
-refrained from conducting. Created with pure [[https://orgmode.org/][org-mode]] to ease the
-landing.
-
-Inspired by:
-- https://taingram.org/
-- https://sachachua.com/blog/
-- https://occasionallycogent.com/
+++ /dev/null
-#+TITLE: Take on Recursion
-#+SUBTITLE: My take on a recursive Elisp function for accumulating a list while walking up an AST
-#+DESCRIPTION: A post by Eshel Yaron describing his take on a recursive Elisp function for accumulating a list while walking up an AST
-#+KEYWORDS: emacs
-#+DATE: 2023-04-01
-
-Over at [[https://takeonrules.com]], Jeremy Friesen has [[https://takeonrules.com/2023/03/25/using-built-in-emacs-29-tree-sitter-package-to-get-qualified-ruby-function-name/][a couple of]] [[https://takeonrules.com/2023/03/25/using-built-in-emacs-29-tree-sitter-package-to-get-qualified-ruby-function-name/][recent posts]]
-about an Emacs command he wrote for grabbing the qualified name of the function
-at point in Ruby files.
-
-I don't know much about Ruby, but the way he implemented this command caught my
-attention because he's relying on the new =treesit= package from Emacs 29 to
-examine the syntax tree of his Ruby code, and I've been playing around with this
-nice parsing framework myself recently.
-
-Apparently, Ruby /functions/ can reside within nested /modules/. Jeremy's
-command thus leverages the function ~treesit-defun-at-point~ to obtain the
-syntax tree node corresponding to the Ruby function at point, and calls a
-recursive helper function called ~jf/treesit/module_space~ that walks up the
-syntax tree and accumulates the names of the enclosing modules it finds along
-the way to the root of the syntax tree. There's just a tiny problem which is
-that this helper function accumulates the module names in a rather awkward
-manner... As Jeremy notes:
-
-#+begin_quote
-The list returned by ~jf/treesit/module_space~ is ~'(nil ("Hello" ("World")))~;
-which is a ugly but workable. Perhaps someone will write to me with a refactor
-of this code.
-#+end_quote
-
-Let's have a look at this function's implementation:
-
-#+begin_src emacs-lisp
- (defun jf/treesit/module_space (node)
- (when-let* ((parent (treesit-parent-until
- node
- (lambda (n) (member (treesit-node-type n)
- '("class" "module" "assignment")))))
- (parent_name (treesit-node-text
- (car
- (treesit-filter-child
- parent
- (lambda (n)
- (member (treesit-node-type n)
- '("constant" "scope_resolution"))))))))
- (list (jf/treesit/module_space parent) parent_name)))
-#+end_src
-
-Basically what this does is climbing up the syntax tree with
-~treesit-parent-until~ to find the next module boundary (if there is one) and
-then grabbing its name with ~treesit-node-text~. Now all that's left is to
-perform a recursive call and return its result along with the found module name,
-but the way it bundles them together as two elements of a ~list~ causes the
-result to be a nested tree structure, instead of a simple list. Then, to obtain
-the desired list of module names, the calling command needs to apply the
-~-flatten~ function to the tree that ~jf/treesit/module_space~ returns.
-
-Here's my take on this function:
-
-#+begin_src emacs-lisp
- (defun esy/treesit/module_space (node &optional acc)
- (if-let ((parent (treesit-parent-until
- node
- (lambda (n) (member (treesit-node-type n)
- '("class" "module" "assignment")))))
- (parent_name (treesit-node-text
- (car
- (treesit-filter-child
- parent
- (lambda (n)
- (member (treesit-node-type n)
- '("constant" "scope_resolution"))))))))
- (esy/treesit/module_space parent (cons parent_name acc))
- acc))
-#+end_src
-
-It's extremely similar to the original version, but the crucial difference is
-that we use an accumulator argument ~ACC~ to hold the result of our syntax tree
-traversal as we climb up to the root. At each step in the way, ~ACC~ holds a
-list of module names which is the ~cdr~ of the list that we'll eventually
-return. We extend this list with the name of the next parent module during the
-recursive call, and when we reach the root of the syntax tree we simply return
-the accumulated list.
-
-For example, if we have Ruby code that looks something like this:
-
-#+begin_src ruby
-module Foo
- module Bar
- module Baz
- def spam
- :true
- end
- end
- end
-end
-#+end_src
-
-With point inside the function definition we get a simple list:
-
-#+begin_src emacs-lisp
- (esy/treesit/module_space (treesit-defun-at-point))
- ⇒ ("Foo" "Bar" "Baz")
-#+end_src
-
-Which is a little cleaner than what we get with ~jf/treesit/module_space~:
-
-#+begin_src emacs-lisp
- (jf/treesit/module_space (treesit-defun-at-point))
- ⇒ (((nil "Foo") "Bar") "Baz")
-#+end_src
+++ /dev/null
-#+TITLE: All Posts
-#+DESCRIPTION: Eshel Yaron's blog posts
-#+KEYWORDS: eshel language emacs programming prolog
-
-#+BEGIN: posts :dir "/Users/eshelyaron/checkouts/eshelyaron.com/org/posts" :limit nil
-#+END:
+++ /dev/null
-#+TITLE: Public PGP Key
-#+AUTHOR: Eshel Yaron
-#+OPTIONS: toc:nil ^:{}
-
-* My PGP public key
-
-#+begin_src shell :results code :exports both
- gpg --armor --export me@eshelyaron.com
-#+end_src
+++ /dev/null
-../sweep/README.org
\ No newline at end of file
+++ /dev/null
-;;; publish.el --- generate and publish my site -*- lexical-binding: t -*-
-
-;; Copyright (C) 2021-2022 Eshel Yaron
-
-;;; Commentary:
-
-;;; Code:
-
-(package-initialize)
-
-(use-package htmlize :ensure t :demand t)
-(use-package rainbow-delimiters :ensure t :demand t)
-(use-package org-transclusion :ensure t :demand t)
-
-(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
-
-(require 'org)
-(require 'ox-publish)
-(require 'ox-html)
-(require 'dom)
-
-(org-babel-do-load-languages
- 'org-babel-load-languages
- '((emacs-lisp . t)
- (shell . t)))
-
-(setq org-confirm-babel-evaluate nil
- make-backup-files nil)
-
-(defun esy/dom-to-string (&rest doms)
- (with-temp-buffer
- (mapc #'dom-print doms)
- (buffer-string)))
-
-(defvar esy/publish-remote-directory
- "/rsync:root@direct.eshelyaron.com:/var/www/html")
-
-(defvar esy/publish-root-directory
- (file-name-directory load-file-name))
-
-(defvar esy/publish-src-directory
- (file-name-as-directory (expand-file-name
- "src"
- esy/publish-root-directory)))
-
-(defvar esy/publish-org-posts-directory
- (file-name-as-directory (expand-file-name
- "posts"
- esy/publish-src-directory)))
-
-(defvar esy/publish-out-directory
- (file-name-as-directory (expand-file-name
- "html"
- esy/publish-root-directory)))
-
-(defvar esy/publish-out-posts-directory
- (file-name-as-directory (expand-file-name
- "posts"
- esy/publish-out-directory)))
-
-(defun esy/publish-post-to-dom (file)
- (with-current-buffer (find-file-noselect
- (expand-file-name
- file esy/publish-out-posts-directory))
- (let ((dom (libxml-parse-html-region (point-min) (point-max))))
- `(item nil
- (title nil ,(string-join (dom-strings (car (dom-by-tag dom 'title)))))
- (author nil ,user-full-name)
- (category nil ,(car (string-split (dom-attr (seq-find (lambda (m) (pcase m (`(meta ((name . "keywords") . ,_)) t))) (dom-by-tag dom 'meta)) 'content) " ")))
- (link nil ,(concat "https://eshelyaron.com/posts/" file))
- (guid ((isPermaLink . "true")) ,(concat "https://eshelyaron.com/posts/" file))
- (pubDate nil ,(substring file 0 10))
- (description nil ,(format "<![CDATA[%s]]>" (esy/dom-to-string (car (dom-by-id dom "content")))))))))
-
-(defun esy/publish-file-url (file)
- (concat "https://eshelyaron.com/"
- (let ((path (file-relative-name file esy/publish-out-directory)))
- (if (string= (file-name-base file) "index")
- (file-name-directory path)
- path))))
-
-(defun esy/publish-sitemap (plist)
- (let ((locs (mapcar #'esy/publish-file-url
- (directory-files-recursively esy/publish-out-directory
- (rx ".html" eos)))))
- (with-temp-buffer
- (insert "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n")
- (dom-print `(urlset ((xlmns . "http://www.sitemaps.org/schemas/sitemap/0.9"))
- ,@(mapcar (lambda (loc)
- `(url nil (loc nil ,loc)))
- locs))
- t t)
- (write-file (expand-file-name "sitemap.xml" (plist-get plist :publishing-directory))))))
-
-(defun esy/publish-posts (plist)
- (let ((posts (reverse (directory-files esy/publish-out-posts-directory
- nil
- (rx bos digit (+ any) ".html" eos)))))
- (with-temp-buffer
- (insert "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n")
- (dom-print `(rss ((version . "2.0"))
- (channel nil
- (title nil ,user-full-name)
- (generator nil "GNU Emacs")
- (link nil "https://eshelyaron.com")
- (description nil "RSS Feed of eshelyaron.com")
- (pubDate nil ,(format-time-string "%F"))
- ,@(mapcar #'esy/publish-post-to-dom posts)))
- t t)
- (write-file (expand-file-name "rss.xml" (plist-get plist :publishing-directory))))))
-
-(defun esy/org-dblock-insert-posts (limit)
- (interactive (list (when current-prefix-arg
- (prefix-numeric-value current-prefix-arg))))
- (org-create-dblock (list :name "posts"
- :dir esy/publish-org-posts-directory
- :limit limit))
- (org-update-dblock))
-
-(org-dynamic-block-define "posts" #'esy/org-dblock-insert-posts)
-
-(defun org-dblock-write:posts (params)
- (let* ((dir (plist-get params :dir))
- (limit (plist-get params :limit))
- (all-posts (reverse (directory-files dir nil (rx bos digit (+ any) ".org" eos))))
- (posts (if limit (take limit all-posts) all-posts)))
- (dolist (post posts)
- (let ((file (expand-file-name post dir)))
- (insert "- [[file:" (file-relative-name file) "][" (substring post 0 10) " ~ " (org-get-title file) "]]"))
- (newline))
- (when (and limit (< limit (length all-posts)))
- (insert "- [[file:" (file-relative-name dir) "][...older posts]]"))
- (delete-blank-lines)))
-
-
-(defun esy/publish-prepare (&rest _)
- (esy/publish-transclude-config)
- (esy/publish-posts-prepare-index))
-
-(function-put 'esy/init-step 'doc-string-elt 2)
-
-(defun esy/publish-transclude-config (&rest _)
- (with-current-buffer (find-file-noselect (expand-file-name "esy.org" esy/publish-src-directory))
- (org-transclusion-add-all)))
-
-(defun esy/publish-posts-prepare-index (&rest _)
- (dolist (dir (list esy/publish-org-posts-directory esy/publish-src-directory))
- (with-current-buffer (find-file-noselect (expand-file-name "index.org" dir))
- (org-update-all-dblocks))))
-
-(defun esy/publish-add-canonical-tags (_plist)
- (dolist (file (directory-files-recursively esy/publish-out-directory
- (rx ".html" eos)))
- (with-current-buffer (find-file-noselect file)
- (when (search-forward "<!-- insert canonical tag here -->" nil t)
- (replace-match (format "<link rel=\"canonical\" href=\"%s\" />"
- (esy/publish-file-url file))
- nil t))
- (basic-save-buffer))))
-
-(defun esy/publish-completion-function (plist)
- (esy/publish-add-canonical-tags plist)
- (esy/publish-posts plist)
- (esy/publish-sitemap plist))
-
-(defun esy/publish-to-remote ()
- (let ((files (directory-files-recursively esy/publish-out-directory
- (rx (or ".html"
- ".ico"
- ".svg"
- ".png"
- ".xml"
- ".css")
- eos))))
- (dolist-with-progress-reporter (file files) "Copying files"
- (copy-file file (expand-file-name
- (file-relative-name file
- esy/publish-out-directory)
- esy/publish-remote-directory)
- t))))
-
-(defun esy/publish (&optional modified-only)
- (interactive "P")
- (let* ((publishing-directory esy/publish-out-directory)
- (org-export-with-sub-superscripts '{})
- (org-export-with-section-numbers nil)
- (org-export-with-toc nil)
- (org-export-with-smart-quotes t)
- (org-html-htmlize-output-type 'css)
- (org-time-stamp-formats '("%Y-%m-%d" . "%Y-%m-%d %H:%M"))
- (org-publish-project-alist
- (list '("all" :components ("assets" "org"))
- (list "assets"
- :base-directory esy/publish-src-directory
- :publishing-directory publishing-directory
- :base-extension "svg\\|ico\\|css\\|png"
- :publishing-function #'org-publish-attachment)
- (list "org"
- :completion-function #'esy/publish-completion-function
- :base-directory esy/publish-src-directory
- :publishing-directory publishing-directory
- :preparation-function #'esy/publish-prepare
- :completion-function #'ignore
- :base-extension "org"
- :recursive t
- :exclude nil
- :include nil
- :publishing-function #'org-html-publish-to-html
- :html-doctype "html5"
- :html-html5-fancy t
- :auto-sitemap t
- :sitemap-title "Sitemap for eshelyaron.com"
- :makeindex t
- :with-date t
- :html-head-include-default-style nil
- :html-head-include-scripts nil
- :html-use-infojs nil
- :html-link-org-files-as-html t
- :html-head (esy/dom-to-string '(link ((rel . "stylesheet")
- (href . "/style.css")
- (type . "text/css"))))
- :html-head-extra (concat (esy/dom-to-string
- '(link ((rel . "alternate")
- (href . "/rss.xml")
- (type . "application/rss+xml")
- (title . "RSS feed of eshelyaron.com"))))
- "\n<!-- insert canonical tag here -->")
- :html-preamble-format
- (list
- (list
- "en"
- (esy/dom-to-string
- '(nav ((id . "icon-links")
- (class . "icon-links"))
- (div ((class . "home-link"))
- (a ((href . "/"))
- (img ((src . "/home.svg")
- (height . "35")
- (width . "35")
- (alt . "Home")))))
- (div ((class . "other-links"))
- (a ((href . "mailto:me@eshelyaron.com"))
- (img ((src . "/mail.svg")
- (height . "30")
- (width . "30")
- (alt . "Mail"))))
- " "
- (a ((href . "https://emacs.ch/@eshel")
- (rel . "me"))
- (img ((src . "/mastodon.svg")
- (height . "28")
- (width . "28")
- (alt . "Mastodon"))))
- " "
- (a ((href . "/rss.xml"))
- (img ((src . "/rss.svg")
- (height . "30")
- (width . "30")
- (alt . "RSS Feed"))))))
- '(hr nil))))
- :html-postamble t
- :html-postamble-format
- (list
- (list
- "en"
- (esy/dom-to-string
- '(footer ((id . "footer")
- (class . "footer"))
- (hr nil)
- "© "
- (time ((class . "copyright-year")) "2023")
- " %a"))))))))
- (org-publish "all" (not modified-only))))
-
-(defun esy/publish-update () (esy/publish t))
-
-(provide 'publish)
-;;; publish.el ends here
--- /dev/null
+#+TITLE: ChangeLog
+#+AUTHOR: Eshel Yaron
+#+DESCRIPTION: change log for eshelyaron.com
+#+KEYWORDS: changelog,git,emacs,org mode
+#+OPTIONS: \n:t
+
+#+begin_src shell :results raw :exports both
+ git log --name-status --pretty=format:"* %s%n%nCommit %h at [%cs] changed these files:%n" --since=2023-04-09
+#+end_src
--- /dev/null
+#+TITLE: GNU Emacs Configuration
+#+AUTHOR: Eshel Yaron
+#+DESCRIPTION: Personal GNU Emacs configuration of Eshel Yaron
+#+KEYWORDS: eshel emacs configuration literate org mode elisp
+
+I've recently moved from a literate Emacs configuration based on Org
+mode to a simpler =init.el= file, reproduced below:
+
+#+transclude: [[file:~/checkouts/eshelyaron.com/dotfiles/.emacs.d/init.el]] :src emacs-lisp
--- /dev/null
+<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" data-background-color="#111111" preserveAspectRatio="xMidYMid meet">
+ <g>
+ <title>Layer 1</title>
+ <rect width="400" height="400" x="0.00002" y="0" fill="#111111" data-fill-palette-color="background" id="background"/>
+ <g id="tight-bounds" transform="matrix(1.42967, 0, 0, 1.43769, 244.049, 9.88795)">
+ <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_1" x="-110.21414" y="7.95458">
+ <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_10">
+ <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_18">
+ <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_26">
+ <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_32">
+ <svg width="154.0987" viewBox="2.049999952316284 -37.599998474121094 24 38.5" height="247.2" data-palette-color="#cfdfd5" id="svg_37">
+ <g class="undefined-text-0" data-fill-palette-color="primary" id="svg_38">
+ <path d="m24.8,-9l1.25,0.5q-0.55,2.25 -1.75,4q-1.2,1.75 -2.9,2.95q-1.7,1.2 -3.75,1.83q-2.05,0.62 -4.3,0.62l0,0q-2.85,0 -4.95,-0.93q-2.1,-0.92 -3.52,-2.47q-1.43,-1.55 -2.13,-3.55q-0.7,-2 -0.7,-4.15l0,0q0,-1.65 0.43,-3.35q0.42,-1.7 1.27,-3.18q0.85,-1.47 2.13,-2.57q1.27,-1.1 2.97,-1.55l0,0q-2.4,-1.5 -3.47,-3.45q-1.08,-1.95 -1.08,-4.1l0,0q0,-1.7 0.65,-3.38q0.65,-1.67 1.93,-2.95q1.27,-1.27 3.15,-2.07q1.87,-0.8 4.27,-0.8l0,0q1.45,0 2.75,0.33q1.3,0.32 2.3,1.07q1,0.75 1.6,1.93q0.6,1.17 0.6,2.87l0,0q0,1.75 -0.65,3.05q-0.65,1.3 -1.95,1.3l0,0q-0.55,0 -1.2,-0.28q-0.65,-0.27 -1.35,-0.97l0,0q0.7,-0.25 1.13,-1.28q0.42,-1.02 0.42,-2.07l0,0q0,-0.95 -0.32,-1.55q-0.33,-0.6 -0.83,-0.93q-0.5,-0.32 -1.05,-0.42q-0.55,-0.1 -1,-0.1l0,0q-1.1,0 -1.9,0.45q-0.8,0.45 -1.3,1.2q-0.5,0.75 -0.75,1.75q-0.25,1 -0.25,2.05l0,0q0,1.4 0.4,2.7q0.4,1.3 1.23,2.4q0.82,1.1 2.07,1.92q1.25,0.83 2.95,1.28l0,0q-1.6,0.4 -2.85,1.42q-1.25,1.03 -2.07,2.45q-0.83,1.43 -1.25,3.08q-0.43,1.65 -0.43,3.2l0,0q0,1.35 0.35,2.6q0.35,1.25 1.08,2.17q0.72,0.93 1.85,1.5q1.12,0.58 2.72,0.58l0,0q1.2,0 2.5,-0.35q1.3,-0.35 2.43,-1q1.12,-0.65 2,-1.6q0.87,-0.95 1.27,-2.15l0,0z" fill="#cfdfd5" data-fill-palette-color="primary" id="svg_39"/>
+ </g>
+ </svg>
+ </svg>
+ </svg>
+ </svg>
+ </svg>
+ </svg>
+ <rect width="154.0987" height="247.2" fill="none" visibility="hidden" id="svg_23" y="7.95458" x="-110.21414"/>
+ </g>
+ </g>
+
+</svg>
\ No newline at end of file
--- /dev/null
+#+TITLE: Eshel Yaron
+#+AUTHOR: Eshel Yaron
+#+DESCRIPTION: Personal home page of Eshel Yaron
+#+KEYWORDS: eshel language emacs programming prolog
+#+OPTIONS: toc:nil ^:{}
+
+Hi there, I'm @@html:<a class="h-card" href="https://eshelyaron.com/">@@Eshel Yaron@@html:</a>@@ and
+this is my website--the one true source of reliable curated information about me and my activities.
+
+#+begin_src prolog
+ ?- likes('Eshel', Stuff).
+ Stuff = 'logic programming';
+ Stuff = 'linguistics';
+ Stuff = 'cognition';
+ Stuff = 'functional programming';
+ Stuff = 'The cyber';
+ Stuff = 'type systems';
+ Stuff = 'coffee ☕️';
+ Stuff = 'free software'.
+#+end_src
+
+* Recent Posts
+:PROPERTIES:
+:CUSTOM_ID: recent-posts
+:END:
+
+These are some of the posts I've published here recently:
+
+#+BEGIN: posts :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/posts" :limit 5
+#+END:
+
+You can also to check out [[file:changelog.org][the site's ChangeLog page]] for updates about
+this website itself.
+
+* Projects
+:PROPERTIES:
+:CUSTOM_ID: projects
+:END:
+
+** =sweep=: SWI-Prolog Embedded in Emacs
+:PROPERTIES:
+:CUSTOM_ID: sweep
+:END:
+
+[[https://git.sr.ht/~eshel/sweep][Sweep]] is an Emacs module which uses the C interfaces of both
+SWI-Prolog and Emacs to bring the two together into one address space.
+
+For more details, see [[file:sweep.org][the Sweep manual]].
+
+** Sourcehut GraphQL client for SWI-Prolog
+:PROPERTIES:
+:CUSTOM_ID: sourcehut-pl
+:END:
+
+[[https://git.sr.ht/~eshel/sourcehut.pl][sourcehut.pl]] - a SWI-Prolog package for interacting with the GraphQL
+API of sourcehut instances.
+
+=sourcehut.pl= can be used to automate maintenance tasks for project
+hosted on sourcehut, for example to attach a build artifact to a given
+tag of a git repository from within SWI-Prolog:
+
+#+begin_src prolog
+ ?- sourcehut_git_repository("eshel", "sourcehut.pl", Repo, []),
+ get_dict(id, Repo, RepoId),
+ sourcehut_git_upload_artifact(RepoId, "v0.1.2", "/tmp/foo/baz.txt", Artifact, []).
+#+end_src
+
+** [[https://git.sr.ht/~eshel/eshellisp][eshellisp]]
+:PROPERTIES:
+:CUSTOM_ID: eshellisp
+:END:
+
+A [[https://git.sr.ht/~eshel/eshellisp][Scheme Lisp interpreter implemented in SWI-Prolog]].
+
+#+begin_src sh
+ $ cat scheme/repl.scm
+ (define repl () (write (eval (read))) (repl))
+ (repl)
+
+ $ ./eshellisp scheme/repl.scm
+ % (cons (+ 1 2) 4)
+ % (3 . 4)
+#+end_src
+
+** [[https://git.sr.ht/~eshel/flymake-swi-prolog][flymake-swi-prolog.el]] and [[https://git.sr.ht/~eshel/diagnostics.pl][diagnostics.pl]]
+:PROPERTIES:
+:CUSTOM_ID: flymake-swi-prolog
+:END:
+
+=diagnostics.pl= is a SWI-Prolog package implementing a simple and
+extensible inteface for diagnosing issues with SWI-Prolog source code,
+exposing by default the powerful analysis used by the built-in
+SWI-Prolog IDE.
+
+=flymake-swi-prolog= is an Emacs Lisp package implementing a Flymake
+backend that leverages =diagnostics.pl= to provide diagnostics for
+SWI-Prolog source code in =prolog-mode= Emacs buffers.
+
+** [[https://git.sr.ht/~eshel/ropes.pl][ropes.pl]]
+:PROPERTIES:
+:CUSTOM_ID: ropes-pl
+:END:
+
+A (SWI-)Prolog implemantation of the [[https://en.wikipedia.org/wiki/Rope_(data_structure)][rope data structure]] for efficient
+massive string editing.
+
+Based on [[https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.14.9450&rep=rep1&type=pdf][Ropes: An alternative to strings]].
+
+#+begin_src prolog
+test(edit) :-
+ string_rope("Hello, World!" , HW),
+ string_rope("Here be dragons", HD),
+ rope_split(HD, 8, _, D), % "Here be " + "dragons"
+ rope_split(HW, 7, H, W), % "Hello, " + "World!"
+ rope_split(W , 5, _, E), % "World" + "!"
+ rope_concat(H , D, HS),
+ rope_concat(HS, E, HE),
+ rope_string(HE, HF),
+ assertion(HF == "Hello, dragons!").
+#+end_src
+
+** [[https://github.com/oskardrums/ebpf][Erlang eBPF library]]
+:PROPERTIES:
+:CUSTOM_ID: erlang-ebpf
+:END:
+
+A low level interface to the [[https://ebpf.io/][Linux eBPF system]] for [[https://www.erlang.org/][Erlang]].
+
+
+** [[https://git.sr.ht/~eshel/eshelyaron.com][This website]]
+:PROPERTIES:
+:CUSTOM_ID: this-website
+:END:
+
+My first taste of web development, a practice that I have always
+refrained from conducting. Created with pure [[https://orgmode.org/][org-mode]] to ease the
+landing.
+
+Inspired by:
+- https://taingram.org/
+- https://sachachua.com/blog/
+- https://occasionallycogent.com/
--- /dev/null
+<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 8C3 7.06812 3 6.60218 3.15224 6.23463C3.35523 5.74458 3.74458 5.35523 4.23463 5.15224C4.60218 5 5.06812 5 6 5V5H18V5C18.9319 5 19.3978 5 19.7654 5.15224C20.2554 5.35523 20.6448 5.74458 20.8478 6.23463C21 6.60218 21 7.06812 21 8V16C21 16.9319 21 17.3978 20.8478 17.7654C20.6448 18.2554 20.2554 18.6448 19.7654 18.8478C19.3978 19 18.9319 19 18 19V19H6V19C5.06812 19 4.60218 19 4.23463 18.8478C3.74458 18.6448 3.35523 18.2554 3.15224 17.7654C3 17.3978 3 16.9319 3 16V8Z" stroke="#cfdfd5" stroke-width="2" stroke-linejoin="round"></path> <path d="M4 6L10.683 11.8476C11.437 12.5074 12.563 12.5074 13.317 11.8476L20 6" stroke="#cfdfd5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
\ No newline at end of file
--- /dev/null
+<svg fill="#000000" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="4.8"><path d="M21.327 8.566c0-4.339-2.843-5.61-2.843-5.61-1.433-.658-3.894-.935-6.451-.956h-.063c-2.557.021-5.016.298-6.45.956 0 0-2.843 1.272-2.843 5.61 0 .993-.019 2.181.012 3.441.103 4.243.778 8.425 4.701 9.463 1.809.479 3.362.579 4.612.51 2.268-.126 3.541-.809 3.541-.809l-.075-1.646s-1.621.511-3.441.449c-1.804-.062-3.707-.194-3.999-2.409a4.523 4.523 0 0 1-.04-.621s1.77.433 4.014.536c1.372.063 2.658-.08 3.965-.236 2.506-.299 4.688-1.843 4.962-3.254.434-2.223.398-5.424.398-5.424zm-3.353 5.59h-2.081V9.057c0-1.075-.452-1.62-1.357-1.62-1 0-1.501.647-1.501 1.927v2.791h-2.069V9.364c0-1.28-.501-1.927-1.502-1.927-.905 0-1.357.546-1.357 1.62v5.099H6.026V8.903c0-1.074.273-1.927.823-2.558.566-.631 1.307-.955 2.228-.955 1.065 0 1.872.409 2.405 1.228l.518.869.519-.869c.533-.819 1.34-1.228 2.405-1.228.92 0 1.662.324 2.228.955.549.631.822 1.484.822 2.558v5.253z"></path></g><g id="SVGRepo_iconCarrier"><path d="M21.327 8.566c0-4.339-2.843-5.61-2.843-5.61-1.433-.658-3.894-.935-6.451-.956h-.063c-2.557.021-5.016.298-6.45.956 0 0-2.843 1.272-2.843 5.61 0 .993-.019 2.181.012 3.441.103 4.243.778 8.425 4.701 9.463 1.809.479 3.362.579 4.612.51 2.268-.126 3.541-.809 3.541-.809l-.075-1.646s-1.621.511-3.441.449c-1.804-.062-3.707-.194-3.999-2.409a4.523 4.523 0 0 1-.04-.621s1.77.433 4.014.536c1.372.063 2.658-.08 3.965-.236 2.506-.299 4.688-1.843 4.962-3.254.434-2.223.398-5.424.398-5.424zm-3.353 5.59h-2.081V9.057c0-1.075-.452-1.62-1.357-1.62-1 0-1.501.647-1.501 1.927v2.791h-2.069V9.364c0-1.28-.501-1.927-1.502-1.927-.905 0-1.357.546-1.357 1.62v5.099H6.026V8.903c0-1.074.273-1.927.823-2.558.566-.631 1.307-.955 2.228-.955 1.065 0 1.872.409 2.405 1.228l.518.869.519-.869c.533-.819 1.34-1.228 2.405-1.228.92 0 1.662.324 2.228.955.549.631.822 1.484.822 2.558v5.253z"></path></g></svg>
\ No newline at end of file
--- /dev/null
+#+TITLE: Emacs
+#+SUBTITLE: The advanced, extensible, customizable, self-documenting editor
+#+DESCRIPTION: Eshel Yaron's notes about Emacs
+#+KEYWORDS: notes, emacs
+#+DATE: 2023-04-12
+
+The [[note:gnu][GNU]] [[https://www.gnu.org/software/emacs/][website]] describes Emacs as "an extensible, customizable,
+free/libre text editor".
+
+
+* References in published posts
+#+BEGIN: links-to-note :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/posts"
+#+END:
+
+* References in other notes
+#+BEGIN: links-to-note :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/notes"
+#+END:
--- /dev/null
+#+TITLE: GNU
+#+SUBTITLE: GNU's Not Unix
+#+DESCRIPTION: Eshel Yaron's notes about GNU
+#+KEYWORDS: notes, gnu
+#+DATE: 2023-04-12
+
+
+* References in published posts
+#+BEGIN: links-to-note :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/posts"
+#+END:
+
+* References in other notes
+#+BEGIN: links-to-note :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/notes"
+#+END:
--- /dev/null
+#+TITLE: All Notes
+#+DESCRIPTION: Eshel Yaron's notes
+#+KEYWORDS: eshel, language, emacs, programming, prolog, knowledge, management, notes
+
+#+BEGIN: notes :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/notes"
+#+END:
--- /dev/null
+#+TITLE: Take on Recursion
+#+SUBTITLE: My take on a recursive Elisp function for accumulating a list while walking up an AST
+#+DESCRIPTION: A post by Eshel Yaron describing his take on a recursive Elisp function for accumulating a list while walking up an AST
+#+KEYWORDS: emacs
+#+DATE: 2023-04-01
+
+@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
+
+Over at [[https://takeonrules.com]], Jeremy Friesen has [[https://takeonrules.com/2023/03/25/using-built-in-emacs-29-tree-sitter-package-to-get-qualified-ruby-function-name/][a couple of]] [[https://takeonrules.com/2023/03/25/using-built-in-emacs-29-tree-sitter-package-to-get-qualified-ruby-function-name/][recent posts]]
+about an Emacs command he wrote for grabbing the qualified name of the function
+at point in Ruby files.
+
+I don't know much about Ruby, but the way he implemented this command caught my
+attention because he's relying on the new =treesit= package from Emacs 29 to
+examine the syntax tree of his Ruby code, and I've been playing around with this
+nice parsing framework myself recently.
+
+Apparently, Ruby /functions/ can reside within nested /modules/. Jeremy's
+command thus leverages the function ~treesit-defun-at-point~ to obtain the
+syntax tree node corresponding to the Ruby function at point, and calls a
+recursive helper function called ~jf/treesit/module_space~ that walks up the
+syntax tree and accumulates the names of the enclosing modules it finds along
+the way to the root of the syntax tree. There's just a tiny problem which is
+that this helper function accumulates the module names in a rather awkward
+manner... As Jeremy notes:
+
+#+begin_quote
+The list returned by ~jf/treesit/module_space~ is ~'(nil ("Hello" ("World")))~;
+which is a ugly but workable. Perhaps someone will write to me with a refactor
+of this code.
+#+end_quote
+
+Let's have a look at this function's implementation:
+
+#+begin_src emacs-lisp
+ (defun jf/treesit/module_space (node)
+ (when-let* ((parent (treesit-parent-until
+ node
+ (lambda (n) (member (treesit-node-type n)
+ '("class" "module" "assignment")))))
+ (parent_name (treesit-node-text
+ (car
+ (treesit-filter-child
+ parent
+ (lambda (n)
+ (member (treesit-node-type n)
+ '("constant" "scope_resolution"))))))))
+ (list (jf/treesit/module_space parent) parent_name)))
+#+end_src
+
+Basically what this does is climbing up the syntax tree with
+~treesit-parent-until~ to find the next module boundary (if there is one) and
+then grabbing its name with ~treesit-node-text~. Now all that's left is to
+perform a recursive call and return its result along with the found module name,
+but the way it bundles them together as two elements of a ~list~ causes the
+result to be a nested tree structure, instead of a simple list. Then, to obtain
+the desired list of module names, the calling command needs to apply the
+~-flatten~ function to the tree that ~jf/treesit/module_space~ returns.
+
+Here's my take on this function:
+
+#+begin_src emacs-lisp
+ (defun esy/treesit/module_space (node &optional acc)
+ (if-let ((parent (treesit-parent-until
+ node
+ (lambda (n) (member (treesit-node-type n)
+ '("class" "module" "assignment")))))
+ (parent_name (treesit-node-text
+ (car
+ (treesit-filter-child
+ parent
+ (lambda (n)
+ (member (treesit-node-type n)
+ '("constant" "scope_resolution"))))))))
+ (esy/treesit/module_space parent (cons parent_name acc))
+ acc))
+#+end_src
+
+It's extremely similar to the original version, but the crucial difference is
+that we use an accumulator argument ~ACC~ to hold the result of our syntax tree
+traversal as we climb up to the root. At each step in the way, ~ACC~ holds a
+list of module names which is the ~cdr~ of the list that we'll eventually
+return. We extend this list with the name of the next parent module during the
+recursive call, and when we reach the root of the syntax tree we simply return
+the accumulated list.
+
+For example, if we have Ruby code that looks something like this:
+
+#+begin_src ruby
+module Foo
+ module Bar
+ module Baz
+ def spam
+ :true
+ end
+ end
+ end
+end
+#+end_src
+
+With point inside the function definition we get a simple list:
+
+#+begin_src emacs-lisp
+ (esy/treesit/module_space (treesit-defun-at-point))
+ ⇒ ("Foo" "Bar" "Baz")
+#+end_src
+
+Which is a little cleaner than what we get with ~jf/treesit/module_space~:
+
+#+begin_src emacs-lisp
+ (jf/treesit/module_space (treesit-defun-at-point))
+ ⇒ (((nil "Foo") "Bar") "Baz")
+#+end_src
--- /dev/null
+#+TITLE: The Self-Healing Code Fallacy
+#+SUBTITLE: Remarks about a new CI step that makes your code fix itself before its merged
+#+DESCRIPTION: A post by Eshel Yaron about Self-Healing Code, a new GitLab CI step that supposedly makes your code fix itself before its merged
+#+KEYWORDS: ai
+#+DATE: 2023-04-05
+
+@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
+
+Yesterday, a colleague of mine shared [[https://twitter.com/minchoi/status/1643215812298260480][a tweet]] in our =#random= Slack
+channel about [[https://gitlab.com/min.choi/selfhealing-gitlab-ci/][a new AI-powered plugin for GitLab CI]] that supposedly
+makes your code /self-healing/.
+
+Here's the tweet from Min Choi:
+#+begin_quote
+Say goodbye to manual code fixes! With self-healing code @GitLab CI
+pipeline, powered by @LangChainAI and @OpenAI , you can automatically
+detect and fix issues in your code. This is the future of DevOps!
+
+Building on the GitHub action pipeline created by @xpluscal
+#+end_quote
+
+He goes on to explain all the nitty-gritty details of how it works:
+
+#+begin_quote
+First, a code push triggers the GitLab CI pipeline where Build job
+fails due to code error, which trigger the SelfHeal job.
+
+Using combination of LLMChain prompt and GPT API query, SelfHeal job
+fixes the code, commits, then push
+
+Second, the code push from SelfHeal job triggers a new pipeline with
+the fixed code.
+
+This time Build job passes. SelfHeal job is skipped since Build job
+passed.
+
+Entire processes was fully automated after the initial code
+push. Possibilities are endless in this age of AI. I will keep you
+guys posted as updates are made.
+#+end_quote
+
+* AI Naivety
+
+This hits me as classic case of /AI naivety/. People see LLMs do
+things that they didn't think were possible, and do it fast. That
+makes them think that LLMs can possibly do /anything at all/. They
+forget--that some things remain impossible. It's as if they were
+introduced to some deeper truth that suddenly invalidates the
+constraints of the old world. In the new world, AI can decide the
+halting problem. Just give it a program and it'll tell you! It seems
+to work well for these 10 lines Python snippets that we tested it on,
+doesn't it? And fuck it, who's to say if it gets it wrong.
+
+* A Bad Metaphor
+
+Back to the self-healing code from Min Choi's tweet, the biggest
+problem I have with this idea is the premise that fixing code is
+somewhat similar to how living organisms heal. Healing is when your
+body restores a previous "good" state after enduring some trauma. If
+your code would self-heal whenever you broke it, that'd just mean it's
+reverting your changes time after time. You'd be stuck with a
+self-preserving, "Hello, World!"-printing, CI-passing piece of
+nothing. To fix code you must start from a rigorous specification of
+what it means for the code to be fixed. Min Choi's self-healing code
+needs to first read the developer's mind in order to figure out what
+it should heal /into/, because the code ultimately conveys the
+developer's intent. No problem for the almighty AI, right?
+
+Contrast this wishful-thinking, eye-shutting, based approach with
+something like [[https://pumpkin.uwplse.org/][PUMPKIN PATCH]] from Talia Ringer et al., where
+/automatic proof repair/ (a much more suitable term then "self-healing
+code") is done in the setting of a formal specification of what the
+program must do (or rather, what it must /be/--what type it should
+inhabit).
+
+* Conclusion
+
+LLMs are cool, they really are, but they can't read your mind, and
+they can't make your code self-heal, whatever that means. Do
+understand their function and utility so you can leverage them where
+appropriate, but don't expect them to do the impossible.
--- /dev/null
+#+TITLE: Making Shell Scripts Executable Just-in-Time
+#+SUBTITLE: A different take on adding exec permissions to shell script in Emacs
+#+DESCRIPTION: Blog post by Eshel Yaron about a different take on adding exec permissions to shell script in Emacs
+#+KEYWORDS: emacs lisp
+#+DATE: 2023-04-08
+
+@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
+
+In my work I often need to write small programs or scripts that
+accomplish very specific tasks. Many of these involve fetching and
+analyzing data from JSON-based APIs, and I tend to use shell scripts
+for that sorta thing. I mostly rely on ~curl~, ~jq~, and ~parallel~,
+as well as the other usual suspects ~grep~, ~sed~, ~tr~, ~head~,
+~sort~, ~uniq~, and the occasional ~awk~.
+
+I usually begin writing these scripts as one-liners in an [[info:emacs#Interactive Shell][Emacs Shell
+buffer]] before moving to a =.sh= file. There I can generalize that
+one-liner and format it nicely, and at some point also test it. Of
+course, to test it I need to run it, and that requires giving the
+newly created shell script /executable permissions/.
+
+In Emacs, there are quite a few ways one can go about making their
+script executable. You can, for instance, do ~M-! M-n chmod +x RET~,
+but if you ask me that's too much typing for such a common task!
+Worse, it's not very /Emacsy/ either. Instead, the way I've been
+doing this for a long time was jumping to Dired with ~C-x C-j~, and
+immediately typing ~M +x RET~ to make the file executable. That works
+well and doesn't require me to type ~chmod~, but it still takes some
+typing and--crucially--it forces me to switch to Dired, when I really
+just wanted was to test my shell script from its own buffer.
+
+Recently, after going through that flow one too many times, I figured
+/there must be a better way/. Ideally I would have simply liked to
+hit ~C-c C-x~ (AKA ~executable-interpret~) in my shell script buffer
+without the need to explicitly make it executable beforehand. I
+wasn't surprised to quickly discover that Emacs comes with a dedicated
+solution for this problem built-in. Typing ~C-h f~ followed by ~exec
+file TAB~ lists among the completion candidates a function
+~executable-make-buffer-file-executable-if-script-p~ that, as its
+lengthy name suggest, makes the visited file executable if it happens
+to be a script.
+
+The standard piece of advice regarding this function, that you find
+written throughout the interwebs by Emacs users and developers alike,
+is that one should put this function in their after ~after-save-hook~.
+
+In 2003, Stefan Monnier [[https://lists.gnu.org/archive/html/emacs-devel/2003-05/msg00249.html][wrote on the emacs-devel mailing list]]:
+
+#+begin_quote
+we have make-buffer-file-executable-if-script-p and I recommend
+everybody add it to his after-save-hook.
+#+end_quote
+
+This solves the problem of manually changing a shell script's
+permissions prior to running it, because the script is made executable
+the second you save it--and saving the shell script buffer to a file
+is anyway a prerequisite for running it.
+
+In the 20 years that passed since Stefan brought up that nifty trick,
+this advice was echoed in many esteemed Emacs blogs:
+
+- Micky Petersen [[https://www.masteringemacs.org/article/script-files-executable-automatically][suggested it]] in his /Mastering Emacs/ book,
+- Marcin Borkowski mentioned it among other [[https://mbork.pl/2015-01-10_A_few_random_Emacs_tips][random Emacs tips]],
+- Oleh Krehel included it in his [[https://oremacs.com/2016/01/18/emacs-rhythmbox/][Emacs as system-wide Rhythmbox]] post, and
+- Bozhidar Batsov [[https://emacsredux.com/blog/2021/09/29/make-script-files-executable-automatically/][wrote about it on his blog]] as well.
+
+Yet, the function itself predates Stefan's comment. It appears that
+originally Noah Friedman wrote it all the way back in the year 2000
+when it was added to Emacs by Dave Love--tracing the function's
+history by going to its definition in =executable.el= and hitting ~M-h
+C-x v h~ reveals the following commit:
+
+#+begin_src diff
+ commit 778e1d17edb36cc53fd7419436311f2e2bc622ff
+ Author: Dave Love <fx@gnu.org>
+ Date: Fri Jun 9 09:38:58 2000 +0000
+
+ ...
+ (make-buffer-file-executable-if-script-p): New function from Noah
+ Friedman.
+
+ diff --git a/lisp/progmodes/executable.el b/lisp/progmodes/executable.el
+ --- a/lisp/progmodes/executable.el
+ +++ b/lisp/progmodes/executable.el
+ @@ -264,1 +270,16 @@
+ -
+ +(defun make-buffer-file-executable-if-script-p ()
+ + "Make file executable according to umask if not already executable.
+ +If file already has any execute bits set at all, do not change existing
+ +file modes."
+ + (and (save-excursion
+ + (save-restriction
+ + (widen)
+ + (goto-char (point-min))
+ + (save-match-data
+ + (looking-at "^#!"))))
+ + (let* ((current-mode (file-modes (buffer-file-name)))
+ + (add-mode (logand ?\111 (default-file-modes))))
+ + (or (/= (logand ?\111 current-mode) 0)
+ + (zerop add-mode)
+ + (set-file-modes (buffer-file-name)
+ + (logior current-mode add-mode))))))
+#+end_src
+
+As we see in the above patch, back in 2000 this function would simply
+look at the start of your buffer, and if begins with a /shebang/ it'd
+ensure that the file has executable permissions. Other than the name
+of the function becoming yet a little longer (the ~executable-~ prefix
+was added, so to follow Elisp namespacing conventions), not much has
+changed in terms of its implementation since then.
+
+Although, as I described earlier, putting
+~executable-make-buffer-file-executable-if-script-p~ into one's
+~after-save-hook~ is a practice promoted by many esteemed members of
+the Emacs community (heck, it even made it to [[https://sachachua.com/dotemacs/index.html][Sacha Chua's config]]), I
+felt uneasy about this solution. I save lots of files, and only a
+minuscule fraction of them are scripts that I wanna make executable.
+That means that the vast majority of my save operations will involve
+some futile busywork and incur a (tiny, but still) needless
+performance penalty. I also think that the way this function decides
+whether or not to make a file executable is too coarse. It doesn't
+examine the file's extension for example, nor does it take into
+account the buffer's major mode. Is it always TRT to make every file
+that starts with a ~#~ and a ~!~ executable? I'm not sure, really.
+It's probably fine, but it makes my security-spidey-sense tingle
+nonetheless.
+
+The solution I came up with is both more conservative (no chance of
+random files becoming executable against my wishes) and more efficient
+(no penalizing all file saves for a few odd scripts). Instead of
+adding ~executable-make-buffer-file-executable-if-script-p~ to my
+after ~after-save-hook~, I settled on adding it as a /advice/ to the
+command ~executable-interpret~. Here's the relevant excerpt from
+[[file:../esy.org][my config]]:
+
+#+begin_src emacs-lisp
+ (with-eval-after-load 'executable
+ (define-advice executable-interpret (:before (&rest _) ensure-executable)
+ (unless (file-exists-p buffer-file-name)
+ (basic-save-buffer))
+ (executable-make-buffer-file-executable-if-script-p)))
+#+end_src
+
+This ~:before~ advice takes care of making the visited executable
+exactly when I'm trying to actually execute it--just in time.
--- /dev/null
+#+TITLE: Optimizing Project Selection in Emacs
+#+SUBTITLE: Leveraging a new Emacs customization option to streamline project selection
+#+DESCRIPTION: A post by Eshel Yaron about leveraging a new Emacs customization option to streamline project selection
+#+KEYWORDS: emacs,lisp
+#+DATE: 2023-04-11
+
+@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
+
+Emacs has a brand new user option for customizing the interface used
+for project selection, e.g. when switching from one project to
+another. I always considered the way Emacs handles project selection
+a bit awkward, so I was glad to see this addition. The new
+alternative has some quirks of its own though, so I set out to do a
+bit of Elisp hacking in hopes of making this part of my Emacs workflow
+behave just right.
+
+* Project Prompting
+
+Yesterday [2023-04-10], Spencer Baugh [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62759][submitted an Emacs patch]] adding a new user
+option to =project.el=, [[info:emacs#Projects][Emacs's bulit-in project isolation package]].
+
+The new user option, ~project-prompter~, determines how Emacs prompts for
+selecting a project in some =project.el= commands, such as ~C-x p p~.
+Previously, these commands would call the function ~project-prompt-project-dir~
+to do that job. Let's have a look at that function's definition:
+
+#+begin_src emacs-lisp
+ (defun project-prompt-project-dir ()
+ "Prompt the user for a directory that is one of the known project roots.
+ The project is chosen among projects known from the project list,
+ see `project-list-file'.
+ It's also possible to enter an arbitrary directory not in the list."
+ (project--ensure-read-project-list)
+ (let* ((dir-choice "... (choose a dir)")
+ (choices
+ ;; XXX: Just using this for the category (for the substring
+ ;; completion style).
+ (project--file-completion-table
+ (append project--list `(,dir-choice))))
+ (pr-dir ""))
+ (while (equal pr-dir "")
+ ;; If the user simply pressed RET, do this again until they don't.
+ (setq pr-dir (completing-read "Select project: " choices nil t)))
+ (if (equal pr-dir dir-choice)
+ (read-directory-name "Select directory: " default-directory nil t)
+ pr-dir)))
+#+end_src
+
+/XXX/ marks the hack. The call to ~project--file-completion-table~ creates a
+completion table with category ~project-file~, which forces the completion style
+~substring~. This is meant to overcome the fact that
+~project-prompt-project-dir~ uses ~completing-read~ with full directory paths as
+completion candidates--a total pain with Emacs's default completion styles.
+
+In fact, there are several hacks in this definition that lead to a slightly
+awkward user experience. The next hack allows choosing an arbitrary directory
+instead of a known project root directory--to do that
+~project-prompt-project-dir~ invokes ~completing-read~ with a [[info:elisp#Basic Completion][completion table]]
+that consists of the known project root directories along with a dummy candidate
+~"... (choose a dir)"~, which always looks a bit out of place in my completions
+buffer.
+
+But the way ~project-prompt-project-dir~ handles empty minibuffer input is even
+more baffling--it completely disregards it and prompts you again, in a loop,
+until you enter something else or quit with ~C-g~. How's that useful? If the
+empty input makes no sense--tell me so! Signal an error, do /something/.
+Better yet, provide a /default selection/ on empty input. That's exactly what
+~completing-read~'s ~DEFAULT~ argument is there for--just use it. Instead this
+function swallows my keystroke with no feedback. I don't like that.
+
+Still, these are minor inconveniences. My deeper, conceptual, problem with
+~project-prompt-project-dir~ is that it prompts me for a /directory/ when really
+what I want to choose is a /project/. Of course, in =project.el= there's a
+1-to-1 correspondence between projects and their root directories, but this
+behavior prevents a useful abstraction.
+
+* New Possibilities
+
+With Spencer's patch, the new ~project-prompter~ user option specifies the
+function responsible for letting us select a project. By default to
+~project-prompt-project-dir~ so to retain the current behavior for unwary users,
+while adding a new alternative prompting function called
+~project-prompt-project-name~.
+
+The new alternative let's us select a project /by name/, rather than /by root
+directory/. This is a win in my opinion because it enforces a nice abstraction
+(projects are distinct from their root directories). It's also more practical
+because we can give projects indicative, clearly distinct names even if they
+reside in directories with generic and similar names.
+
+Unfortunately, ~project-prompt-project-name~ inherits most of the problems I
+described earlier from ~project-prompt-project-dir~ by virtue of copy-pasta:
+
+#+begin_src emacs-lisp
+ (defun project-prompt-project-name ()
+ "Prompt the user for a project, by name, that is one of the known project roots.
+ The project is chosen among projects known from the project list,
+ see `project-list-file'.
+ It's also possible to enter an arbitrary directory not in the list."
+ (let* ((dir-choice "... (choose a dir)")
+ (choices
+ (let (ret)
+ (dolist (dir (project-known-project-roots))
+ ;; we filter out directories that no longer map to a project,
+ ;; since they don't have a clean project-name.
+ (if-let (proj (project--find-in-directory dir))
+ (push (cons (project-name proj) proj) ret)))
+ ret))
+ ;; XXX: Just using this for the category (for the substring
+ ;; completion style).
+ (table (project--file-completion-table (cons dir-choice choices)))
+ (pr-name ""))
+ (while (equal pr-name "")
+ ;; If the user simply pressed RET, do this again until they don't.
+ (setq pr-name (completing-read "Select project: " table nil t)))
+ (if (equal pr-name dir-choice)
+ (read-directory-name "Select directory: " default-directory nil t)
+ (let ((proj (assoc pr-name choices)))
+ (if (stringp proj) proj (project-root (cdr proj)))))))
+#+end_src
+
+This could use some polish. Spencer [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62759#13][put it nicely]] in response to his patch
+landing on Emacs master:
+
+#+begin_quote
+I was expecting to need to iterate through some review cycles :)
+#+end_quote
+
+* Casual Contributor Cap
+
+Ideally, I'd channel my dissatisfaction with the current implementation to
+crafting a follow up patch to Spencer's addition. Alas, [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62708#8][I've maxed out my
+casual Emacs contributor plan]] and I'm currently waiting for the FSF to process
+my copyright assignment papers before I can contribute to Emacs development
+again. Even if ~project-prompt-project-name~ isn't quite my cap of tea, I can
+still leverage the new ~project-prompter~ user option with a custom
+project-prompting function of my own. Enter my new =init.el=
+resident -- ~esy/read-project-by-name~:
+
+#+begin_src emacs-lisp
+ (defvar esy/project-name-history nil)
+
+ (defvar esy/projects-directory "~/checkouts/")
+
+ (defun esy/read-project-by-name ()
+ "Read a project name and return its root directory.
+
+ If no known project matches the selected name, prompt for a
+ sub-directory of `esy/projects-directory' using the selected name
+ as the initial input for completion, and return that directory."
+ (let* ((name-dir-alist
+ (mapcar (lambda (dir)
+ (cons (project-name (project-current nil dir))
+ dir))
+ (project-known-project-roots)))
+ (current (project-current))
+ (default (and current (project-name current)))
+ (name (completing-read (format-prompt "Project" default)
+ name-dir-alist
+ nil nil nil
+ 'esy/project-name-history
+ default)))
+ (or (alist-get name name-dir-alist nil nil #'string=)
+ (let* ((dir (read-directory-name "Project root directory: "
+ esy/projects-directory
+ nil t name))
+ (project (project-current nil dir)))
+ (when project (project-remember-project project))
+ dir))))
+#+end_src
+
+Similarly to ~project-prompt-project-name~, this function let's me select a
+project by name, instead of having to specify its root directory. Where
+~esy/read-project-by-name~ differs is in how it handles edge cases, namely:
+
+1. empty minibuffer input, and
+2. unknown project names.
+
+On empty input (that is, if I press ~RET~ without inserting anything in the
+minibuffer first), the current project is selected as the default. I use
+~format-prompt~ to have the minibuffer prompt reflect the default choice.
+
+If I insert an unknown project name, that's taken to mean that I want to select
+a new project. In that case ~esy/read-project-by-name~ invokes
+~read-directory-name~ to let me specify the root directory of that new project.
+Most of my project directories live under ~~/checkouts/~, so the prompt for the
+new project's root directory starts from there. Moreover, the unknown project
+name that I've inserted first is placed in the minibuffer right after
+~~/checkouts/~, so it acts as a hint for further completion operations.
+
+For example, let's say I have a new Git repository that I've just cloned into
+~~/checkouts/foobar/~. If I want to do some project-wide task with it, maybe
+searching for a regular expression or starting a dedicated shell buffer, I can
+hit ~C-x p p~ and type ~foobar RET~. Now I get the ~Project root directory:~
+prompt, and the initial input is already ~~/checkouts/foobar~, so I just press
+~RET~ again and I'm there.
+
+But what if I can't remember exactly where I've cloned that new repo into, was
+it ~foobar~ or ~foobaz~? Well, no problem. If I do ~fooba RET~ at the prompt
+from ~C-x p p~ I get the same prompt for directory as before, except now the
+initial input is ~~/checkouts/fooba~. Hitting ~TAB~ completes this to
+~~/checkouts/foobar/~ and we're good to go. Contrast this behavior with how
+~project-prompt-project-dir~ and ~project-prompt-project-name~ handle unknown
+projects--they both simply say ~No match~ in response to ~C-x p p foobar RET~.
+
+* Conclusion
+
+Emacs's =project.el= got a cool new enhancement. It still isn't perfect, but it
+is more /extensible/ than ever, which means I can better tailor it to my needs.
--- /dev/null
+#+TITLE: All Posts
+#+DESCRIPTION: Eshel Yaron's blog posts
+#+KEYWORDS: eshel language emacs programming prolog
+
+#+BEGIN: posts :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/posts" :limit nil
+#+END:
--- /dev/null
+#+TITLE: Public PGP Key
+#+AUTHOR: Eshel Yaron
+#+OPTIONS: toc:nil ^:{}
+
+* My PGP public key
+
+#+begin_src shell :results code :exports both
+ gpg --armor --export me@eshelyaron.com
+#+end_src
--- /dev/null
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 455.731 455.731" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <rect x="0" y="0" style="fill:#111111;" width="455.731" height="455.731"></rect> <g> <path style="fill:#cfdfd5;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348 c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348 C391.986,303.103,357.971,220.923,296.208,159.16z"></path> <path style="fill:#cfdfd5;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348 C282.429,270.196,184.507,172.273,64.143,172.273z"></path> <circle style="fill:#cfdfd5;" cx="109.833" cy="346.26" r="46.088"></circle> </g> </g> </g></svg>
\ No newline at end of file
--- /dev/null
+body {
+ color: #cfdfd5;
+ background-color: #111111;
+ margin-left: auto;
+ margin-right: auto;
+ width: 90%;
+ max-width: 82ch;
+ /* font-size: 1.1em; */
+ font-family: "Helvetica Neue", sans-serif;
+}
+
+a:link {
+ color: #00c089;
+}
+
+a:visited {
+ color: #5dc0aa;
+}
+
+time {
+ color: #9ac8e0;
+}
+
+.content {
+ line-height: 1.35;
+}
+
+.home-link {
+ float: left;
+}
+
+.other-links {
+ float: right;
+}
+
+footer {
+ font-size: smaller;
+ text-align: center;
+}
+
+.icon-links {
+ height: 40px;
+ text-decoration: none;
+}
+
+blockquote {
+ border-left-style: solid;
+ padding-left: 1em;
+}
+
+dt {
+ font-weight: bold;
+}
+
+pre {
+ overflow: auto;
+ border-top-style: solid;
+ border-top-width: 2px;
+ border-left-style: solid;
+ border-left-width: 2px;
+ border-right-style: solid;
+ border-right-width: 1px;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ padding: 0.8em;
+ background-color: #222522;
+}
+
+.footpara {
+ display: inline;
+}
+
+code {
+ color: #78afff;
+ font-family: Hack,monospace;
+}
+
+h2 {
+ color: #7fc500;
+}
+h3 {
+ color: #5dc0aa;
+}
+h4 {
+ color: #af9fff;
+}
+h5 {
+ color: #7fcfdf;
+}
+h6 {
+ color: #cfc04f;
+}
+
+/*
+Generated with `M-x org-html-htmlize-generate-css` after loading the
+`ef-bio` theme by Protesilaos Stavrou.
+*/
+.org-builtin {
+ /* font-lock-builtin-face */
+ color: #3fb83f;
+ font-weight: bold;
+}
+.org-comment {
+ /* font-lock-comment-face */
+ color: #b7a07f;
+ font-style: italic;
+}
+.org-comment-delimiter {
+ /* font-lock-comment-delimiter-face */
+ color: #b7a07f;
+ font-style: italic;
+}
+.org-constant {
+ /* font-lock-constant-face */
+ color: #37aff6;
+}
+.org-doc {
+ /* font-lock-doc-face */
+ color: #7fc07f;
+ font-style: italic;
+}
+.org-doc-markup {
+ /* font-lock-doc-markup-face */
+ color: #37aff6;
+}
+.org-escape {
+ /* font-lock-escape-face */
+ color: #cfc04f;
+}
+.org-function-call {
+ /* font-lock-function-call-face */
+ color: #7fc500;
+}
+.org-function-name {
+ /* font-lock-function-name-face */
+ color: #7fc500;
+}
+.org-preprocessor {
+ /* font-lock-preprocessor-face */
+ color: #3fb83f;
+}
+.org-property-name {
+ /* font-lock-property-name-face */
+ color: #78afff;
+}
+.org-property-use {
+ /* font-lock-property-use-face */
+ color: #78afff;
+}
+.org-rainbow-delimiters-depth-1 {
+ /* rainbow-delimiters-depth-1-face */
+ color: #00c089;
+}
+.org-rainbow-delimiters-depth-2 {
+ /* rainbow-delimiters-depth-2-face */
+ color: #7fc500;
+}
+.org-rainbow-delimiters-depth-3 {
+ /* rainbow-delimiters-depth-3-face */
+ color: #5dc0aa;
+}
+.org-rainbow-delimiters-depth-4 {
+ /* rainbow-delimiters-depth-4-face */
+ color: #af9fff;
+}
+.org-rainbow-delimiters-depth-5 {
+ /* rainbow-delimiters-depth-5-face */
+ color: #7fcfdf;
+}
+.org-rainbow-delimiters-depth-6 {
+ /* rainbow-delimiters-depth-6-face */
+ color: #cfc04f;
+}
+.org-rainbow-delimiters-depth-7 {
+ /* rainbow-delimiters-depth-7-face */
+ color: #37aff6;
+}
+.org-rainbow-delimiters-depth-8 {
+ /* rainbow-delimiters-depth-8-face */
+ color: #6fc5ef;
+}
+.org-rainbow-delimiters-depth-9 {
+ /* rainbow-delimiters-depth-9-face */
+ color: #d38faf;
+}
+.org-rainbow-delimiters-mismatched {
+ /* rainbow-delimiters-mismatched-face */
+ color: #ffffff;
+ background-color: #bd1f30;
+}
+.org-string {
+ /* font-lock-string-face */
+ color: #af9fff;
+}
+.org-type {
+ /* font-lock-type-face */
+ color: #7fcfdf;
+}
+.org-variable-name {
+ /* font-lock-variable-name-face */
+ color: #78afff;
+}
+.org-variable-use {
+ /* font-lock-variable-use-face */
+ color: #78afff;
+}
+.org-warning-1 {
+ /* font-lock-warning-face */
+ color: #cfc04f;
+}
+.org-negation-char {
+ /* font-lock-negation-char-face */
+ font-weight: bold;
+}
+.org-regexp {
+ /* font-lock-regexp-face */
+ color: #af9fff;
+}
+.org-regexp-grouping-backslash {
+ /* font-lock-regexp-grouping-backslash */
+ color: #cfc04f;
+}
+.org-regexp-grouping-construct {
+ /* font-lock-regexp-grouping-construct */
+ color: #3fb83f;
+}
+.org-diff-added {
+ /* diff-added */
+ color: #a0e0a0;
+ background-color: #003b1f;
+}
+.org-diff-changed {
+ /* diff-changed */
+ color: #efef80;
+ background-color: #363300;
+}
+.org-diff-changed-unspecified {
+ /* diff-changed-unspecified */
+ color: #efef80;
+ background-color: #363300;
+}
+.org-diff-error {
+ /* diff-error */
+ color: #ef6560;
+ font-weight: bold;
+}
+.org-diff-file-header {
+ /* diff-file-header */
+ font-weight: bold;
+}
+.org-diff-function {
+ /* diff-function */
+ background-color: #303230;
+}
+.org-diff-hunk-header {
+ /* diff-hunk-header */
+ background-color: #303230;
+ font-weight: bold;
+}
+.org-diff-index {
+ /* diff-index */
+ font-style: italic;
+}
+.org-diff-indicator-added {
+ /* diff-indicator-added */
+ color: #a0e0a0;
+ background-color: #003b1f;
+}
+.org-diff-indicator-changed {
+ /* diff-indicator-changed */
+ color: #efef80;
+ background-color: #363300;
+}
+.org-diff-indicator-removed {
+ /* diff-indicator-removed */
+ color: #ffbfbf;
+ background-color: #4e1119;
+}
+.org-diff-nonexistent {
+ /* diff-nonexistent */
+ font-weight: bold;
+}
+.org-diff-refine-added {
+ /* diff-refine-added */
+ color: #a0e0a0;
+ background-color: #03512f;
+}
+.org-diff-refine-changed {
+ /* diff-refine-changed */
+ color: #efef80;
+ background-color: #4a4a00;
+}
+.org-diff-refine-removed {
+ /* diff-refine-removed */
+ color: #ffbfbf;
+ background-color: #751a1f;
+}
+.org-diff-removed {
+ /* diff-removed */
+ color: #ffbfbf;
+ background-color: #4e1119;
+}
+
+.timestamp {
+ color: #5dc0aa;
+}
+
+.subtitle {
+ text-decoration-line: underline;
+ text-decoration-color: #9acd32;
+ font-style: italic;
+}
+
+.metadata {
+ color: #989898;
+ font-style: italic;
+ font-size: smaller;
+}
--- /dev/null
+../sweep/README.org
\ No newline at end of file
+++ /dev/null
-#+TITLE: ChangeLog
-#+AUTHOR: Eshel Yaron
-#+DESCRIPTION: change log for eshelyaron.com
-#+KEYWORDS: changelog,git,emacs,org mode
-#+OPTIONS: \n:t
-
-#+begin_src shell :results raw :exports both
- git log --name-status --pretty=format:"* %s%n%nCommit %h at [%cs] changed these files:%n" --since=2023-04-09
-#+end_src
+++ /dev/null
-#+TITLE: GNU Emacs Configuration
-#+AUTHOR: Eshel Yaron
-#+DESCRIPTION: Personal GNU Emacs configuration of Eshel Yaron
-#+KEYWORDS: eshel emacs configuration literate org mode elisp
-
-I've recently moved from a literate Emacs configuration based on Org
-mode to a simpler =init.el= file, reproduced below:
-
-#+transclude: [[file:~/checkouts/eshelyaron.com/dotfiles/.emacs.d/init.el]] :src emacs-lisp
+++ /dev/null
-<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" data-background-color="#111111" preserveAspectRatio="xMidYMid meet">
- <g>
- <title>Layer 1</title>
- <rect width="400" height="400" x="0.00002" y="0" fill="#111111" data-fill-palette-color="background" id="background"/>
- <g id="tight-bounds" transform="matrix(1.42967, 0, 0, 1.43769, 244.049, 9.88795)">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_1" x="-110.21414" y="7.95458">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_10">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_18">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_26">
- <svg viewBox="0 0 154.0987012987013 247.2" height="247.2" width="154.0987" id="svg_32">
- <svg width="154.0987" viewBox="2.049999952316284 -37.599998474121094 24 38.5" height="247.2" data-palette-color="#cfdfd5" id="svg_37">
- <g class="undefined-text-0" data-fill-palette-color="primary" id="svg_38">
- <path d="m24.8,-9l1.25,0.5q-0.55,2.25 -1.75,4q-1.2,1.75 -2.9,2.95q-1.7,1.2 -3.75,1.83q-2.05,0.62 -4.3,0.62l0,0q-2.85,0 -4.95,-0.93q-2.1,-0.92 -3.52,-2.47q-1.43,-1.55 -2.13,-3.55q-0.7,-2 -0.7,-4.15l0,0q0,-1.65 0.43,-3.35q0.42,-1.7 1.27,-3.18q0.85,-1.47 2.13,-2.57q1.27,-1.1 2.97,-1.55l0,0q-2.4,-1.5 -3.47,-3.45q-1.08,-1.95 -1.08,-4.1l0,0q0,-1.7 0.65,-3.38q0.65,-1.67 1.93,-2.95q1.27,-1.27 3.15,-2.07q1.87,-0.8 4.27,-0.8l0,0q1.45,0 2.75,0.33q1.3,0.32 2.3,1.07q1,0.75 1.6,1.93q0.6,1.17 0.6,2.87l0,0q0,1.75 -0.65,3.05q-0.65,1.3 -1.95,1.3l0,0q-0.55,0 -1.2,-0.28q-0.65,-0.27 -1.35,-0.97l0,0q0.7,-0.25 1.13,-1.28q0.42,-1.02 0.42,-2.07l0,0q0,-0.95 -0.32,-1.55q-0.33,-0.6 -0.83,-0.93q-0.5,-0.32 -1.05,-0.42q-0.55,-0.1 -1,-0.1l0,0q-1.1,0 -1.9,0.45q-0.8,0.45 -1.3,1.2q-0.5,0.75 -0.75,1.75q-0.25,1 -0.25,2.05l0,0q0,1.4 0.4,2.7q0.4,1.3 1.23,2.4q0.82,1.1 2.07,1.92q1.25,0.83 2.95,1.28l0,0q-1.6,0.4 -2.85,1.42q-1.25,1.03 -2.07,2.45q-0.83,1.43 -1.25,3.08q-0.43,1.65 -0.43,3.2l0,0q0,1.35 0.35,2.6q0.35,1.25 1.08,2.17q0.72,0.93 1.85,1.5q1.12,0.58 2.72,0.58l0,0q1.2,0 2.5,-0.35q1.3,-0.35 2.43,-1q1.12,-0.65 2,-1.6q0.87,-0.95 1.27,-2.15l0,0z" fill="#cfdfd5" data-fill-palette-color="primary" id="svg_39"/>
- </g>
- </svg>
- </svg>
- </svg>
- </svg>
- </svg>
- </svg>
- <rect width="154.0987" height="247.2" fill="none" visibility="hidden" id="svg_23" y="7.95458" x="-110.21414"/>
- </g>
- </g>
-
-</svg>
\ No newline at end of file
+++ /dev/null
-#+TITLE: Eshel Yaron
-#+AUTHOR: Eshel Yaron
-#+DESCRIPTION: Personal home page of Eshel Yaron
-#+KEYWORDS: eshel language emacs programming prolog
-#+OPTIONS: toc:nil ^:{}
-
-Hi there, I'm @@html:<a class="h-card" href="https://eshelyaron.com/">@@Eshel Yaron@@html:</a>@@ and
-this is my website--the one true source of reliable curated information about me and my activities.
-
-#+begin_src prolog
- ?- likes('Eshel', Stuff).
- Stuff = 'logic programming';
- Stuff = 'linguistics';
- Stuff = 'cognition';
- Stuff = 'functional programming';
- Stuff = 'The cyber';
- Stuff = 'type systems';
- Stuff = 'coffee ☕️';
- Stuff = 'free software'.
-#+end_src
-
-* Recent Posts
-:PROPERTIES:
-:CUSTOM_ID: recent-posts
-:END:
-
-These are some of the posts I've published here recently:
-
-#+BEGIN: posts :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/posts" :limit 5
-#+END:
-
-You can also to check out [[file:changelog.org][the site's ChangeLog page]] for updates about
-this website itself.
-
-* Projects
-:PROPERTIES:
-:CUSTOM_ID: projects
-:END:
-
-** =sweep=: SWI-Prolog Embedded in Emacs
-:PROPERTIES:
-:CUSTOM_ID: sweep
-:END:
-
-[[https://git.sr.ht/~eshel/sweep][Sweep]] is an Emacs module which uses the C interfaces of both
-SWI-Prolog and Emacs to bring the two together into one address space.
-
-For more details, see [[file:sweep.org][the Sweep manual]].
-
-** Sourcehut GraphQL client for SWI-Prolog
-:PROPERTIES:
-:CUSTOM_ID: sourcehut-pl
-:END:
-
-[[https://git.sr.ht/~eshel/sourcehut.pl][sourcehut.pl]] - a SWI-Prolog package for interacting with the GraphQL
-API of sourcehut instances.
-
-=sourcehut.pl= can be used to automate maintenance tasks for project
-hosted on sourcehut, for example to attach a build artifact to a given
-tag of a git repository from within SWI-Prolog:
-
-#+begin_src prolog
- ?- sourcehut_git_repository("eshel", "sourcehut.pl", Repo, []),
- get_dict(id, Repo, RepoId),
- sourcehut_git_upload_artifact(RepoId, "v0.1.2", "/tmp/foo/baz.txt", Artifact, []).
-#+end_src
-
-** [[https://git.sr.ht/~eshel/eshellisp][eshellisp]]
-:PROPERTIES:
-:CUSTOM_ID: eshellisp
-:END:
-
-A [[https://git.sr.ht/~eshel/eshellisp][Scheme Lisp interpreter implemented in SWI-Prolog]].
-
-#+begin_src sh
- $ cat scheme/repl.scm
- (define repl () (write (eval (read))) (repl))
- (repl)
-
- $ ./eshellisp scheme/repl.scm
- % (cons (+ 1 2) 4)
- % (3 . 4)
-#+end_src
-
-** [[https://git.sr.ht/~eshel/flymake-swi-prolog][flymake-swi-prolog.el]] and [[https://git.sr.ht/~eshel/diagnostics.pl][diagnostics.pl]]
-:PROPERTIES:
-:CUSTOM_ID: flymake-swi-prolog
-:END:
-
-=diagnostics.pl= is a SWI-Prolog package implementing a simple and
-extensible inteface for diagnosing issues with SWI-Prolog source code,
-exposing by default the powerful analysis used by the built-in
-SWI-Prolog IDE.
-
-=flymake-swi-prolog= is an Emacs Lisp package implementing a Flymake
-backend that leverages =diagnostics.pl= to provide diagnostics for
-SWI-Prolog source code in =prolog-mode= Emacs buffers.
-
-** [[https://git.sr.ht/~eshel/ropes.pl][ropes.pl]]
-:PROPERTIES:
-:CUSTOM_ID: ropes-pl
-:END:
-
-A (SWI-)Prolog implemantation of the [[https://en.wikipedia.org/wiki/Rope_(data_structure)][rope data structure]] for efficient
-massive string editing.
-
-Based on [[https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.14.9450&rep=rep1&type=pdf][Ropes: An alternative to strings]].
-
-#+begin_src prolog
-test(edit) :-
- string_rope("Hello, World!" , HW),
- string_rope("Here be dragons", HD),
- rope_split(HD, 8, _, D), % "Here be " + "dragons"
- rope_split(HW, 7, H, W), % "Hello, " + "World!"
- rope_split(W , 5, _, E), % "World" + "!"
- rope_concat(H , D, HS),
- rope_concat(HS, E, HE),
- rope_string(HE, HF),
- assertion(HF == "Hello, dragons!").
-#+end_src
-
-** [[https://github.com/oskardrums/ebpf][Erlang eBPF library]]
-:PROPERTIES:
-:CUSTOM_ID: erlang-ebpf
-:END:
-
-A low level interface to the [[https://ebpf.io/][Linux eBPF system]] for [[https://www.erlang.org/][Erlang]].
-
-
-** [[https://git.sr.ht/~eshel/eshelyaron.com][This website]]
-:PROPERTIES:
-:CUSTOM_ID: this-website
-:END:
-
-My first taste of web development, a practice that I have always
-refrained from conducting. Created with pure [[https://orgmode.org/][org-mode]] to ease the
-landing.
-
-Inspired by:
-- https://taingram.org/
-- https://sachachua.com/blog/
-- https://occasionallycogent.com/
+++ /dev/null
-<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 8C3 7.06812 3 6.60218 3.15224 6.23463C3.35523 5.74458 3.74458 5.35523 4.23463 5.15224C4.60218 5 5.06812 5 6 5V5H18V5C18.9319 5 19.3978 5 19.7654 5.15224C20.2554 5.35523 20.6448 5.74458 20.8478 6.23463C21 6.60218 21 7.06812 21 8V16C21 16.9319 21 17.3978 20.8478 17.7654C20.6448 18.2554 20.2554 18.6448 19.7654 18.8478C19.3978 19 18.9319 19 18 19V19H6V19C5.06812 19 4.60218 19 4.23463 18.8478C3.74458 18.6448 3.35523 18.2554 3.15224 17.7654C3 17.3978 3 16.9319 3 16V8Z" stroke="#cfdfd5" stroke-width="2" stroke-linejoin="round"></path> <path d="M4 6L10.683 11.8476C11.437 12.5074 12.563 12.5074 13.317 11.8476L20 6" stroke="#cfdfd5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
\ No newline at end of file
+++ /dev/null
-<svg fill="#000000" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="4.8"><path d="M21.327 8.566c0-4.339-2.843-5.61-2.843-5.61-1.433-.658-3.894-.935-6.451-.956h-.063c-2.557.021-5.016.298-6.45.956 0 0-2.843 1.272-2.843 5.61 0 .993-.019 2.181.012 3.441.103 4.243.778 8.425 4.701 9.463 1.809.479 3.362.579 4.612.51 2.268-.126 3.541-.809 3.541-.809l-.075-1.646s-1.621.511-3.441.449c-1.804-.062-3.707-.194-3.999-2.409a4.523 4.523 0 0 1-.04-.621s1.77.433 4.014.536c1.372.063 2.658-.08 3.965-.236 2.506-.299 4.688-1.843 4.962-3.254.434-2.223.398-5.424.398-5.424zm-3.353 5.59h-2.081V9.057c0-1.075-.452-1.62-1.357-1.62-1 0-1.501.647-1.501 1.927v2.791h-2.069V9.364c0-1.28-.501-1.927-1.502-1.927-.905 0-1.357.546-1.357 1.62v5.099H6.026V8.903c0-1.074.273-1.927.823-2.558.566-.631 1.307-.955 2.228-.955 1.065 0 1.872.409 2.405 1.228l.518.869.519-.869c.533-.819 1.34-1.228 2.405-1.228.92 0 1.662.324 2.228.955.549.631.822 1.484.822 2.558v5.253z"></path></g><g id="SVGRepo_iconCarrier"><path d="M21.327 8.566c0-4.339-2.843-5.61-2.843-5.61-1.433-.658-3.894-.935-6.451-.956h-.063c-2.557.021-5.016.298-6.45.956 0 0-2.843 1.272-2.843 5.61 0 .993-.019 2.181.012 3.441.103 4.243.778 8.425 4.701 9.463 1.809.479 3.362.579 4.612.51 2.268-.126 3.541-.809 3.541-.809l-.075-1.646s-1.621.511-3.441.449c-1.804-.062-3.707-.194-3.999-2.409a4.523 4.523 0 0 1-.04-.621s1.77.433 4.014.536c1.372.063 2.658-.08 3.965-.236 2.506-.299 4.688-1.843 4.962-3.254.434-2.223.398-5.424.398-5.424zm-3.353 5.59h-2.081V9.057c0-1.075-.452-1.62-1.357-1.62-1 0-1.501.647-1.501 1.927v2.791h-2.069V9.364c0-1.28-.501-1.927-1.502-1.927-.905 0-1.357.546-1.357 1.62v5.099H6.026V8.903c0-1.074.273-1.927.823-2.558.566-.631 1.307-.955 2.228-.955 1.065 0 1.872.409 2.405 1.228l.518.869.519-.869c.533-.819 1.34-1.228 2.405-1.228.92 0 1.662.324 2.228.955.549.631.822 1.484.822 2.558v5.253z"></path></g></svg>
\ No newline at end of file
+++ /dev/null
-#+TITLE: Take on Recursion
-#+SUBTITLE: My take on a recursive Elisp function for accumulating a list while walking up an AST
-#+DESCRIPTION: A post by Eshel Yaron describing his take on a recursive Elisp function for accumulating a list while walking up an AST
-#+KEYWORDS: emacs
-#+DATE: 2023-04-01
-
-@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
-
-Over at [[https://takeonrules.com]], Jeremy Friesen has [[https://takeonrules.com/2023/03/25/using-built-in-emacs-29-tree-sitter-package-to-get-qualified-ruby-function-name/][a couple of]] [[https://takeonrules.com/2023/03/25/using-built-in-emacs-29-tree-sitter-package-to-get-qualified-ruby-function-name/][recent posts]]
-about an Emacs command he wrote for grabbing the qualified name of the function
-at point in Ruby files.
-
-I don't know much about Ruby, but the way he implemented this command caught my
-attention because he's relying on the new =treesit= package from Emacs 29 to
-examine the syntax tree of his Ruby code, and I've been playing around with this
-nice parsing framework myself recently.
-
-Apparently, Ruby /functions/ can reside within nested /modules/. Jeremy's
-command thus leverages the function ~treesit-defun-at-point~ to obtain the
-syntax tree node corresponding to the Ruby function at point, and calls a
-recursive helper function called ~jf/treesit/module_space~ that walks up the
-syntax tree and accumulates the names of the enclosing modules it finds along
-the way to the root of the syntax tree. There's just a tiny problem which is
-that this helper function accumulates the module names in a rather awkward
-manner... As Jeremy notes:
-
-#+begin_quote
-The list returned by ~jf/treesit/module_space~ is ~'(nil ("Hello" ("World")))~;
-which is a ugly but workable. Perhaps someone will write to me with a refactor
-of this code.
-#+end_quote
-
-Let's have a look at this function's implementation:
-
-#+begin_src emacs-lisp
- (defun jf/treesit/module_space (node)
- (when-let* ((parent (treesit-parent-until
- node
- (lambda (n) (member (treesit-node-type n)
- '("class" "module" "assignment")))))
- (parent_name (treesit-node-text
- (car
- (treesit-filter-child
- parent
- (lambda (n)
- (member (treesit-node-type n)
- '("constant" "scope_resolution"))))))))
- (list (jf/treesit/module_space parent) parent_name)))
-#+end_src
-
-Basically what this does is climbing up the syntax tree with
-~treesit-parent-until~ to find the next module boundary (if there is one) and
-then grabbing its name with ~treesit-node-text~. Now all that's left is to
-perform a recursive call and return its result along with the found module name,
-but the way it bundles them together as two elements of a ~list~ causes the
-result to be a nested tree structure, instead of a simple list. Then, to obtain
-the desired list of module names, the calling command needs to apply the
-~-flatten~ function to the tree that ~jf/treesit/module_space~ returns.
-
-Here's my take on this function:
-
-#+begin_src emacs-lisp
- (defun esy/treesit/module_space (node &optional acc)
- (if-let ((parent (treesit-parent-until
- node
- (lambda (n) (member (treesit-node-type n)
- '("class" "module" "assignment")))))
- (parent_name (treesit-node-text
- (car
- (treesit-filter-child
- parent
- (lambda (n)
- (member (treesit-node-type n)
- '("constant" "scope_resolution"))))))))
- (esy/treesit/module_space parent (cons parent_name acc))
- acc))
-#+end_src
-
-It's extremely similar to the original version, but the crucial difference is
-that we use an accumulator argument ~ACC~ to hold the result of our syntax tree
-traversal as we climb up to the root. At each step in the way, ~ACC~ holds a
-list of module names which is the ~cdr~ of the list that we'll eventually
-return. We extend this list with the name of the next parent module during the
-recursive call, and when we reach the root of the syntax tree we simply return
-the accumulated list.
-
-For example, if we have Ruby code that looks something like this:
-
-#+begin_src ruby
-module Foo
- module Bar
- module Baz
- def spam
- :true
- end
- end
- end
-end
-#+end_src
-
-With point inside the function definition we get a simple list:
-
-#+begin_src emacs-lisp
- (esy/treesit/module_space (treesit-defun-at-point))
- ⇒ ("Foo" "Bar" "Baz")
-#+end_src
-
-Which is a little cleaner than what we get with ~jf/treesit/module_space~:
-
-#+begin_src emacs-lisp
- (jf/treesit/module_space (treesit-defun-at-point))
- ⇒ (((nil "Foo") "Bar") "Baz")
-#+end_src
+++ /dev/null
-#+TITLE: The Self-Healing Code Fallacy
-#+SUBTITLE: Remarks about a new CI step that makes your code fix itself before its merged
-#+DESCRIPTION: A post by Eshel Yaron about Self-Healing Code, a new GitLab CI step that supposedly makes your code fix itself before its merged
-#+KEYWORDS: ai
-#+DATE: 2023-04-05
-
-@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
-
-Yesterday, a colleague of mine shared [[https://twitter.com/minchoi/status/1643215812298260480][a tweet]] in our =#random= Slack
-channel about [[https://gitlab.com/min.choi/selfhealing-gitlab-ci/][a new AI-powered plugin for GitLab CI]] that supposedly
-makes your code /self-healing/.
-
-Here's the tweet from Min Choi:
-#+begin_quote
-Say goodbye to manual code fixes! With self-healing code @GitLab CI
-pipeline, powered by @LangChainAI and @OpenAI , you can automatically
-detect and fix issues in your code. This is the future of DevOps!
-
-Building on the GitHub action pipeline created by @xpluscal
-#+end_quote
-
-He goes on to explain all the nitty-gritty details of how it works:
-
-#+begin_quote
-First, a code push triggers the GitLab CI pipeline where Build job
-fails due to code error, which trigger the SelfHeal job.
-
-Using combination of LLMChain prompt and GPT API query, SelfHeal job
-fixes the code, commits, then push
-
-Second, the code push from SelfHeal job triggers a new pipeline with
-the fixed code.
-
-This time Build job passes. SelfHeal job is skipped since Build job
-passed.
-
-Entire processes was fully automated after the initial code
-push. Possibilities are endless in this age of AI. I will keep you
-guys posted as updates are made.
-#+end_quote
-
-* AI Naivety
-
-This hits me as classic case of /AI naivety/. People see LLMs do
-things that they didn't think were possible, and do it fast. That
-makes them think that LLMs can possibly do /anything at all/. They
-forget--that some things remain impossible. It's as if they were
-introduced to some deeper truth that suddenly invalidates the
-constraints of the old world. In the new world, AI can decide the
-halting problem. Just give it a program and it'll tell you! It seems
-to work well for these 10 lines Python snippets that we tested it on,
-doesn't it? And fuck it, who's to say if it gets it wrong.
-
-* A Bad Metaphor
-
-Back to the self-healing code from Min Choi's tweet, the biggest
-problem I have with this idea is the premise that fixing code is
-somewhat similar to how living organisms heal. Healing is when your
-body restores a previous "good" state after enduring some trauma. If
-your code would self-heal whenever you broke it, that'd just mean it's
-reverting your changes time after time. You'd be stuck with a
-self-preserving, "Hello, World!"-printing, CI-passing piece of
-nothing. To fix code you must start from a rigorous specification of
-what it means for the code to be fixed. Min Choi's self-healing code
-needs to first read the developer's mind in order to figure out what
-it should heal /into/, because the code ultimately conveys the
-developer's intent. No problem for the almighty AI, right?
-
-Contrast this wishful-thinking, eye-shutting, based approach with
-something like [[https://pumpkin.uwplse.org/][PUMPKIN PATCH]] from Talia Ringer et al., where
-/automatic proof repair/ (a much more suitable term then "self-healing
-code") is done in the setting of a formal specification of what the
-program must do (or rather, what it must /be/--what type it should
-inhabit).
-
-* Conclusion
-
-LLMs are cool, they really are, but they can't read your mind, and
-they can't make your code self-heal, whatever that means. Do
-understand their function and utility so you can leverage them where
-appropriate, but don't expect them to do the impossible.
+++ /dev/null
-#+TITLE: Making Shell Scripts Executable Just-in-Time
-#+SUBTITLE: A different take on adding exec permissions to shell script in Emacs
-#+DESCRIPTION: Blog post by Eshel Yaron about a different take on adding exec permissions to shell script in Emacs
-#+KEYWORDS: emacs lisp
-#+DATE: 2023-04-08
-
-@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
-
-In my work I often need to write small programs or scripts that
-accomplish very specific tasks. Many of these involve fetching and
-analyzing data from JSON-based APIs, and I tend to use shell scripts
-for that sorta thing. I mostly rely on ~curl~, ~jq~, and ~parallel~,
-as well as the other usual suspects ~grep~, ~sed~, ~tr~, ~head~,
-~sort~, ~uniq~, and the occasional ~awk~.
-
-I usually begin writing these scripts as one-liners in an [[info:emacs#Interactive Shell][Emacs Shell
-buffer]] before moving to a =.sh= file. There I can generalize that
-one-liner and format it nicely, and at some point also test it. Of
-course, to test it I need to run it, and that requires giving the
-newly created shell script /executable permissions/.
-
-In Emacs, there are quite a few ways one can go about making their
-script executable. You can, for instance, do ~M-! M-n chmod +x RET~,
-but if you ask me that's too much typing for such a common task!
-Worse, it's not very /Emacsy/ either. Instead, the way I've been
-doing this for a long time was jumping to Dired with ~C-x C-j~, and
-immediately typing ~M +x RET~ to make the file executable. That works
-well and doesn't require me to type ~chmod~, but it still takes some
-typing and--crucially--it forces me to switch to Dired, when I really
-just wanted was to test my shell script from its own buffer.
-
-Recently, after going through that flow one too many times, I figured
-/there must be a better way/. Ideally I would have simply liked to
-hit ~C-c C-x~ (AKA ~executable-interpret~) in my shell script buffer
-without the need to explicitly make it executable beforehand. I
-wasn't surprised to quickly discover that Emacs comes with a dedicated
-solution for this problem built-in. Typing ~C-h f~ followed by ~exec
-file TAB~ lists among the completion candidates a function
-~executable-make-buffer-file-executable-if-script-p~ that, as its
-lengthy name suggest, makes the visited file executable if it happens
-to be a script.
-
-The standard piece of advice regarding this function, that you find
-written throughout the interwebs by Emacs users and developers alike,
-is that one should put this function in their after ~after-save-hook~.
-
-In 2003, Stefan Monnier [[https://lists.gnu.org/archive/html/emacs-devel/2003-05/msg00249.html][wrote on the emacs-devel mailing list]]:
-
-#+begin_quote
-we have make-buffer-file-executable-if-script-p and I recommend
-everybody add it to his after-save-hook.
-#+end_quote
-
-This solves the problem of manually changing a shell script's
-permissions prior to running it, because the script is made executable
-the second you save it--and saving the shell script buffer to a file
-is anyway a prerequisite for running it.
-
-In the 20 years that passed since Stefan brought up that nifty trick,
-this advice was echoed in many esteemed Emacs blogs:
-
-- Micky Petersen [[https://www.masteringemacs.org/article/script-files-executable-automatically][suggested it]] in his /Mastering Emacs/ book,
-- Marcin Borkowski mentioned it among other [[https://mbork.pl/2015-01-10_A_few_random_Emacs_tips][random Emacs tips]],
-- Oleh Krehel included it in his [[https://oremacs.com/2016/01/18/emacs-rhythmbox/][Emacs as system-wide Rhythmbox]] post, and
-- Bozhidar Batsov [[https://emacsredux.com/blog/2021/09/29/make-script-files-executable-automatically/][wrote about it on his blog]] as well.
-
-Yet, the function itself predates Stefan's comment. It appears that
-originally Noah Friedman wrote it all the way back in the year 2000
-when it was added to Emacs by Dave Love--tracing the function's
-history by going to its definition in =executable.el= and hitting ~M-h
-C-x v h~ reveals the following commit:
-
-#+begin_src diff
- commit 778e1d17edb36cc53fd7419436311f2e2bc622ff
- Author: Dave Love <fx@gnu.org>
- Date: Fri Jun 9 09:38:58 2000 +0000
-
- ...
- (make-buffer-file-executable-if-script-p): New function from Noah
- Friedman.
-
- diff --git a/lisp/progmodes/executable.el b/lisp/progmodes/executable.el
- --- a/lisp/progmodes/executable.el
- +++ b/lisp/progmodes/executable.el
- @@ -264,1 +270,16 @@
- -
- +(defun make-buffer-file-executable-if-script-p ()
- + "Make file executable according to umask if not already executable.
- +If file already has any execute bits set at all, do not change existing
- +file modes."
- + (and (save-excursion
- + (save-restriction
- + (widen)
- + (goto-char (point-min))
- + (save-match-data
- + (looking-at "^#!"))))
- + (let* ((current-mode (file-modes (buffer-file-name)))
- + (add-mode (logand ?\111 (default-file-modes))))
- + (or (/= (logand ?\111 current-mode) 0)
- + (zerop add-mode)
- + (set-file-modes (buffer-file-name)
- + (logior current-mode add-mode))))))
-#+end_src
-
-As we see in the above patch, back in 2000 this function would simply
-look at the start of your buffer, and if begins with a /shebang/ it'd
-ensure that the file has executable permissions. Other than the name
-of the function becoming yet a little longer (the ~executable-~ prefix
-was added, so to follow Elisp namespacing conventions), not much has
-changed in terms of its implementation since then.
-
-Although, as I described earlier, putting
-~executable-make-buffer-file-executable-if-script-p~ into one's
-~after-save-hook~ is a practice promoted by many esteemed members of
-the Emacs community (heck, it even made it to [[https://sachachua.com/dotemacs/index.html][Sacha Chua's config]]), I
-felt uneasy about this solution. I save lots of files, and only a
-minuscule fraction of them are scripts that I wanna make executable.
-That means that the vast majority of my save operations will involve
-some futile busywork and incur a (tiny, but still) needless
-performance penalty. I also think that the way this function decides
-whether or not to make a file executable is too coarse. It doesn't
-examine the file's extension for example, nor does it take into
-account the buffer's major mode. Is it always TRT to make every file
-that starts with a ~#~ and a ~!~ executable? I'm not sure, really.
-It's probably fine, but it makes my security-spidey-sense tingle
-nonetheless.
-
-The solution I came up with is both more conservative (no chance of
-random files becoming executable against my wishes) and more efficient
-(no penalizing all file saves for a few odd scripts). Instead of
-adding ~executable-make-buffer-file-executable-if-script-p~ to my
-after ~after-save-hook~, I settled on adding it as a /advice/ to the
-command ~executable-interpret~. Here's the relevant excerpt from
-[[file:../esy.org][my config]]:
-
-#+begin_src emacs-lisp
- (with-eval-after-load 'executable
- (define-advice executable-interpret (:before (&rest _) ensure-executable)
- (unless (file-exists-p buffer-file-name)
- (basic-save-buffer))
- (executable-make-buffer-file-executable-if-script-p)))
-#+end_src
-
-This ~:before~ advice takes care of making the visited executable
-exactly when I'm trying to actually execute it--just in time.
+++ /dev/null
-#+TITLE: Optimizing Project Selection in Emacs
-#+SUBTITLE: Leveraging a new Emacs customization option to streamline project selection
-#+DESCRIPTION: A post by Eshel Yaron about leveraging a new Emacs customization option to streamline project selection
-#+KEYWORDS: emacs,lisp
-#+DATE: 2023-04-11
-
-@@html:<div class="metadata">@@Created on [{{{date}}}], last updated [{{{modification-time(%Y-%m-%d, t)}}}]@@html:</div>@@
-
-Emacs has a brand new user option for customizing the interface used
-for project selection, e.g. when switching from one project to
-another. I always considered the way Emacs handles project selection
-a bit awkward, so I was glad to see this addition. The new
-alternative has some quirks of its own though, so I set out to do a
-bit of Elisp hacking in hopes of making this part of my Emacs workflow
-behave just right.
-
-* Project Prompting
-
-Yesterday [2023-04-10], Spencer Baugh [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62759][submitted an Emacs patch]] adding a new user
-option to =project.el=, [[info:emacs#Projects][Emacs's bulit-in project isolation package]].
-
-The new user option, ~project-prompter~, determines how Emacs prompts for
-selecting a project in some =project.el= commands, such as ~C-x p p~.
-Previously, these commands would call the function ~project-prompt-project-dir~
-to do that job. Let's have a look at that function's definition:
-
-#+begin_src emacs-lisp
- (defun project-prompt-project-dir ()
- "Prompt the user for a directory that is one of the known project roots.
- The project is chosen among projects known from the project list,
- see `project-list-file'.
- It's also possible to enter an arbitrary directory not in the list."
- (project--ensure-read-project-list)
- (let* ((dir-choice "... (choose a dir)")
- (choices
- ;; XXX: Just using this for the category (for the substring
- ;; completion style).
- (project--file-completion-table
- (append project--list `(,dir-choice))))
- (pr-dir ""))
- (while (equal pr-dir "")
- ;; If the user simply pressed RET, do this again until they don't.
- (setq pr-dir (completing-read "Select project: " choices nil t)))
- (if (equal pr-dir dir-choice)
- (read-directory-name "Select directory: " default-directory nil t)
- pr-dir)))
-#+end_src
-
-/XXX/ marks the hack. The call to ~project--file-completion-table~ creates a
-completion table with category ~project-file~, which forces the completion style
-~substring~. This is meant to overcome the fact that
-~project-prompt-project-dir~ uses ~completing-read~ with full directory paths as
-completion candidates--a total pain with Emacs's default completion styles.
-
-In fact, there are several hacks in this definition that lead to a slightly
-awkward user experience. The next hack allows choosing an arbitrary directory
-instead of a known project root directory--to do that
-~project-prompt-project-dir~ invokes ~completing-read~ with a [[info:elisp#Basic Completion][completion table]]
-that consists of the known project root directories along with a dummy candidate
-~"... (choose a dir)"~, which always looks a bit out of place in my completions
-buffer.
-
-But the way ~project-prompt-project-dir~ handles empty minibuffer input is even
-more baffling--it completely disregards it and prompts you again, in a loop,
-until you enter something else or quit with ~C-g~. How's that useful? If the
-empty input makes no sense--tell me so! Signal an error, do /something/.
-Better yet, provide a /default selection/ on empty input. That's exactly what
-~completing-read~'s ~DEFAULT~ argument is there for--just use it. Instead this
-function swallows my keystroke with no feedback. I don't like that.
-
-Still, these are minor inconveniences. My deeper, conceptual, problem with
-~project-prompt-project-dir~ is that it prompts me for a /directory/ when really
-what I want to choose is a /project/. Of course, in =project.el= there's a
-1-to-1 correspondence between projects and their root directories, but this
-behavior prevents a useful abstraction.
-
-* New Possibilities
-
-With Spencer's patch, the new ~project-prompter~ user option specifies the
-function responsible for letting us select a project. By default to
-~project-prompt-project-dir~ so to retain the current behavior for unwary users,
-while adding a new alternative prompting function called
-~project-prompt-project-name~.
-
-The new alternative let's us select a project /by name/, rather than /by root
-directory/. This is a win in my opinion because it enforces a nice abstraction
-(projects are distinct from their root directories). It's also more practical
-because we can give projects indicative, clearly distinct names even if they
-reside in directories with generic and similar names.
-
-Unfortunately, ~project-prompt-project-name~ inherits most of the problems I
-described earlier from ~project-prompt-project-dir~ by virtue of copy-pasta:
-
-#+begin_src emacs-lisp
- (defun project-prompt-project-name ()
- "Prompt the user for a project, by name, that is one of the known project roots.
- The project is chosen among projects known from the project list,
- see `project-list-file'.
- It's also possible to enter an arbitrary directory not in the list."
- (let* ((dir-choice "... (choose a dir)")
- (choices
- (let (ret)
- (dolist (dir (project-known-project-roots))
- ;; we filter out directories that no longer map to a project,
- ;; since they don't have a clean project-name.
- (if-let (proj (project--find-in-directory dir))
- (push (cons (project-name proj) proj) ret)))
- ret))
- ;; XXX: Just using this for the category (for the substring
- ;; completion style).
- (table (project--file-completion-table (cons dir-choice choices)))
- (pr-name ""))
- (while (equal pr-name "")
- ;; If the user simply pressed RET, do this again until they don't.
- (setq pr-name (completing-read "Select project: " table nil t)))
- (if (equal pr-name dir-choice)
- (read-directory-name "Select directory: " default-directory nil t)
- (let ((proj (assoc pr-name choices)))
- (if (stringp proj) proj (project-root (cdr proj)))))))
-#+end_src
-
-This could use some polish. Spencer [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62759#13][put it nicely]] in response to his patch
-landing on Emacs master:
-
-#+begin_quote
-I was expecting to need to iterate through some review cycles :)
-#+end_quote
-
-* Casual Contributor Cap
-
-Ideally, I'd channel my dissatisfaction with the current implementation to
-crafting a follow up patch to Spencer's addition. Alas, [[https://debbugs.gnu.org/cgi/bugreport.cgi?bug=62708#8][I've maxed out my
-casual Emacs contributor plan]] and I'm currently waiting for the FSF to process
-my copyright assignment papers before I can contribute to Emacs development
-again. Even if ~project-prompt-project-name~ isn't quite my cap of tea, I can
-still leverage the new ~project-prompter~ user option with a custom
-project-prompting function of my own. Enter my new =init.el=
-resident -- ~esy/read-project-by-name~:
-
-#+begin_src emacs-lisp
- (defvar esy/project-name-history nil)
-
- (defvar esy/projects-directory "~/checkouts/")
-
- (defun esy/read-project-by-name ()
- "Read a project name and return its root directory.
-
- If no known project matches the selected name, prompt for a
- sub-directory of `esy/projects-directory' using the selected name
- as the initial input for completion, and return that directory."
- (let* ((name-dir-alist
- (mapcar (lambda (dir)
- (cons (project-name (project-current nil dir))
- dir))
- (project-known-project-roots)))
- (current (project-current))
- (default (and current (project-name current)))
- (name (completing-read (format-prompt "Project" default)
- name-dir-alist
- nil nil nil
- 'esy/project-name-history
- default)))
- (or (alist-get name name-dir-alist nil nil #'string=)
- (let* ((dir (read-directory-name "Project root directory: "
- esy/projects-directory
- nil t name))
- (project (project-current nil dir)))
- (when project (project-remember-project project))
- dir))))
-#+end_src
-
-Similarly to ~project-prompt-project-name~, this function let's me select a
-project by name, instead of having to specify its root directory. Where
-~esy/read-project-by-name~ differs is in how it handles edge cases, namely:
-
-1. empty minibuffer input, and
-2. unknown project names.
-
-On empty input (that is, if I press ~RET~ without inserting anything in the
-minibuffer first), the current project is selected as the default. I use
-~format-prompt~ to have the minibuffer prompt reflect the default choice.
-
-If I insert an unknown project name, that's taken to mean that I want to select
-a new project. In that case ~esy/read-project-by-name~ invokes
-~read-directory-name~ to let me specify the root directory of that new project.
-Most of my project directories live under ~~/checkouts/~, so the prompt for the
-new project's root directory starts from there. Moreover, the unknown project
-name that I've inserted first is placed in the minibuffer right after
-~~/checkouts/~, so it acts as a hint for further completion operations.
-
-For example, let's say I have a new Git repository that I've just cloned into
-~~/checkouts/foobar/~. If I want to do some project-wide task with it, maybe
-searching for a regular expression or starting a dedicated shell buffer, I can
-hit ~C-x p p~ and type ~foobar RET~. Now I get the ~Project root directory:~
-prompt, and the initial input is already ~~/checkouts/foobar~, so I just press
-~RET~ again and I'm there.
-
-But what if I can't remember exactly where I've cloned that new repo into, was
-it ~foobar~ or ~foobaz~? Well, no problem. If I do ~fooba RET~ at the prompt
-from ~C-x p p~ I get the same prompt for directory as before, except now the
-initial input is ~~/checkouts/fooba~. Hitting ~TAB~ completes this to
-~~/checkouts/foobar/~ and we're good to go. Contrast this behavior with how
-~project-prompt-project-dir~ and ~project-prompt-project-name~ handle unknown
-projects--they both simply say ~No match~ in response to ~C-x p p foobar RET~.
-
-* Conclusion
-
-Emacs's =project.el= got a cool new enhancement. It still isn't perfect, but it
-is more /extensible/ than ever, which means I can better tailor it to my needs.
+++ /dev/null
-#+TITLE: All Posts
-#+DESCRIPTION: Eshel Yaron's blog posts
-#+KEYWORDS: eshel language emacs programming prolog
-
-#+BEGIN: posts :dir "/Users/eshelyaron/checkouts/eshelyaron.com/src/posts" :limit nil
-#+END:
+++ /dev/null
-#+TITLE: Public PGP Key
-#+AUTHOR: Eshel Yaron
-#+OPTIONS: toc:nil ^:{}
-
-* My PGP public key
-
-#+begin_src shell :results code :exports both
- gpg --armor --export me@eshelyaron.com
-#+end_src
+++ /dev/null
-<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 455.731 455.731" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <rect x="0" y="0" style="fill:#111111;" width="455.731" height="455.731"></rect> <g> <path style="fill:#cfdfd5;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348 c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348 C391.986,303.103,357.971,220.923,296.208,159.16z"></path> <path style="fill:#cfdfd5;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348 C282.429,270.196,184.507,172.273,64.143,172.273z"></path> <circle style="fill:#cfdfd5;" cx="109.833" cy="346.26" r="46.088"></circle> </g> </g> </g></svg>
\ No newline at end of file
+++ /dev/null
-body {
- color: #cfdfd5;
- background-color: #111111;
- margin-left: auto;
- margin-right: auto;
- width: 90%;
- max-width: 82ch;
- /* font-size: 1.1em; */
- font-family: "Helvetica Neue", sans-serif;
-}
-
-a:link {
- color: #00c089;
-}
-
-a:visited {
- color: #5dc0aa;
-}
-
-time {
- color: #9ac8e0;
-}
-
-.content {
- line-height: 1.35;
-}
-
-.home-link {
- float: left;
-}
-
-.other-links {
- float: right;
-}
-
-footer {
- font-size: smaller;
- text-align: center;
-}
-
-.icon-links {
- height: 40px;
- text-decoration: none;
-}
-
-blockquote {
- border-left-style: solid;
- padding-left: 1em;
-}
-
-dt {
- font-weight: bold;
-}
-
-pre {
- overflow: auto;
- border-top-style: solid;
- border-top-width: 2px;
- border-left-style: solid;
- border-left-width: 2px;
- border-right-style: solid;
- border-right-width: 1px;
- border-bottom-style: solid;
- border-bottom-width: 1px;
- padding: 0.8em;
- background-color: #222522;
-}
-
-.footpara {
- display: inline;
-}
-
-code {
- color: #78afff;
- font-family: Hack,monospace;
-}
-
-h2 {
- color: #7fc500;
-}
-h3 {
- color: #5dc0aa;
-}
-h4 {
- color: #af9fff;
-}
-h5 {
- color: #7fcfdf;
-}
-h6 {
- color: #cfc04f;
-}
-
-/*
-Generated with `M-x org-html-htmlize-generate-css` after loading the
-`ef-bio` theme by Protesilaos Stavrou.
-*/
-.org-builtin {
- /* font-lock-builtin-face */
- color: #3fb83f;
- font-weight: bold;
-}
-.org-comment {
- /* font-lock-comment-face */
- color: #b7a07f;
- font-style: italic;
-}
-.org-comment-delimiter {
- /* font-lock-comment-delimiter-face */
- color: #b7a07f;
- font-style: italic;
-}
-.org-constant {
- /* font-lock-constant-face */
- color: #37aff6;
-}
-.org-doc {
- /* font-lock-doc-face */
- color: #7fc07f;
- font-style: italic;
-}
-.org-doc-markup {
- /* font-lock-doc-markup-face */
- color: #37aff6;
-}
-.org-escape {
- /* font-lock-escape-face */
- color: #cfc04f;
-}
-.org-function-call {
- /* font-lock-function-call-face */
- color: #7fc500;
-}
-.org-function-name {
- /* font-lock-function-name-face */
- color: #7fc500;
-}
-.org-preprocessor {
- /* font-lock-preprocessor-face */
- color: #3fb83f;
-}
-.org-property-name {
- /* font-lock-property-name-face */
- color: #78afff;
-}
-.org-property-use {
- /* font-lock-property-use-face */
- color: #78afff;
-}
-.org-rainbow-delimiters-depth-1 {
- /* rainbow-delimiters-depth-1-face */
- color: #00c089;
-}
-.org-rainbow-delimiters-depth-2 {
- /* rainbow-delimiters-depth-2-face */
- color: #7fc500;
-}
-.org-rainbow-delimiters-depth-3 {
- /* rainbow-delimiters-depth-3-face */
- color: #5dc0aa;
-}
-.org-rainbow-delimiters-depth-4 {
- /* rainbow-delimiters-depth-4-face */
- color: #af9fff;
-}
-.org-rainbow-delimiters-depth-5 {
- /* rainbow-delimiters-depth-5-face */
- color: #7fcfdf;
-}
-.org-rainbow-delimiters-depth-6 {
- /* rainbow-delimiters-depth-6-face */
- color: #cfc04f;
-}
-.org-rainbow-delimiters-depth-7 {
- /* rainbow-delimiters-depth-7-face */
- color: #37aff6;
-}
-.org-rainbow-delimiters-depth-8 {
- /* rainbow-delimiters-depth-8-face */
- color: #6fc5ef;
-}
-.org-rainbow-delimiters-depth-9 {
- /* rainbow-delimiters-depth-9-face */
- color: #d38faf;
-}
-.org-rainbow-delimiters-mismatched {
- /* rainbow-delimiters-mismatched-face */
- color: #ffffff;
- background-color: #bd1f30;
-}
-.org-string {
- /* font-lock-string-face */
- color: #af9fff;
-}
-.org-type {
- /* font-lock-type-face */
- color: #7fcfdf;
-}
-.org-variable-name {
- /* font-lock-variable-name-face */
- color: #78afff;
-}
-.org-variable-use {
- /* font-lock-variable-use-face */
- color: #78afff;
-}
-.org-warning-1 {
- /* font-lock-warning-face */
- color: #cfc04f;
-}
-.org-negation-char {
- /* font-lock-negation-char-face */
- font-weight: bold;
-}
-.org-regexp {
- /* font-lock-regexp-face */
- color: #af9fff;
-}
-.org-regexp-grouping-backslash {
- /* font-lock-regexp-grouping-backslash */
- color: #cfc04f;
-}
-.org-regexp-grouping-construct {
- /* font-lock-regexp-grouping-construct */
- color: #3fb83f;
-}
-.org-diff-added {
- /* diff-added */
- color: #a0e0a0;
- background-color: #003b1f;
-}
-.org-diff-changed {
- /* diff-changed */
- color: #efef80;
- background-color: #363300;
-}
-.org-diff-changed-unspecified {
- /* diff-changed-unspecified */
- color: #efef80;
- background-color: #363300;
-}
-.org-diff-error {
- /* diff-error */
- color: #ef6560;
- font-weight: bold;
-}
-.org-diff-file-header {
- /* diff-file-header */
- font-weight: bold;
-}
-.org-diff-function {
- /* diff-function */
- background-color: #303230;
-}
-.org-diff-hunk-header {
- /* diff-hunk-header */
- background-color: #303230;
- font-weight: bold;
-}
-.org-diff-index {
- /* diff-index */
- font-style: italic;
-}
-.org-diff-indicator-added {
- /* diff-indicator-added */
- color: #a0e0a0;
- background-color: #003b1f;
-}
-.org-diff-indicator-changed {
- /* diff-indicator-changed */
- color: #efef80;
- background-color: #363300;
-}
-.org-diff-indicator-removed {
- /* diff-indicator-removed */
- color: #ffbfbf;
- background-color: #4e1119;
-}
-.org-diff-nonexistent {
- /* diff-nonexistent */
- font-weight: bold;
-}
-.org-diff-refine-added {
- /* diff-refine-added */
- color: #a0e0a0;
- background-color: #03512f;
-}
-.org-diff-refine-changed {
- /* diff-refine-changed */
- color: #efef80;
- background-color: #4a4a00;
-}
-.org-diff-refine-removed {
- /* diff-refine-removed */
- color: #ffbfbf;
- background-color: #751a1f;
-}
-.org-diff-removed {
- /* diff-removed */
- color: #ffbfbf;
- background-color: #4e1119;
-}
-
-.timestamp {
- color: #5dc0aa;
-}
-
-.subtitle {
- text-decoration-line: underline;
- text-decoration-color: #9acd32;
- font-style: italic;
-}
-
-.metadata {
- color: #989898;
- font-style: italic;
- font-size: smaller;
-}
+++ /dev/null
-../sweep/README.org
\ No newline at end of file