From: Eshel Yaron Date: Sun, 16 Apr 2023 15:10:35 +0000 (+0300) Subject: Support building the site interactively X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=fb818d9b0a58d342c26c79f4983f02345fb23df5;p=esy-publish.git Support building the site interactively --- diff --git a/.gitignore b/.gitignore index c4ea624..f95f262 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ /src/theindex.inc /draft/ /html.bk.bk/ +/source/sitemap.org +/source/theindex.inc +/source/theindex.org diff --git a/assets/favicon.ico b/assets/favicon.ico deleted file mode 100644 index f744115..0000000 Binary files a/assets/favicon.ico and /dev/null differ diff --git a/assets/home.svg b/assets/home.svg deleted file mode 100644 index 222fdec..0000000 --- a/assets/home.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - Layer 1 - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/mail.svg b/assets/mail.svg deleted file mode 100644 index 5752d47..0000000 --- a/assets/mail.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/mastodon.svg b/assets/mastodon.svg deleted file mode 100644 index 350c222..0000000 --- a/assets/mastodon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/rss.svg b/assets/rss.svg deleted file mode 100644 index 3ea2cd9..0000000 --- a/assets/rss.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/style.css b/assets/style.css deleted file mode 100644 index ab2cabd..0000000 --- a/assets/style.css +++ /dev/null @@ -1,230 +0,0 @@ -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; -} diff --git a/esy-publish.el b/esy-publish.el new file mode 100644 index 0000000..02b0451 --- /dev/null +++ b/esy-publish.el @@ -0,0 +1,515 @@ +;;; esy-publish.el --- Simple Static Site Generator -*- lexical-binding:t -*- + +;; Copyright (C) 2023 Eshel Yaron + +;; Author: Eshel Yaron +;; Maintainer: Eshel Yaron +;; 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:" "
" "@@" + "Created on [{{{date}}}], " + "last updated [{{{modification-time(%Y-%m-%d, t)}}}]" + "@@html:" "
" "@@")) + +(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 "" (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 "\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 "\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 "" nil t) + (replace-match (format "" + (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") + :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 diff --git a/org/index.org b/org/index.org deleted file mode 100644 index f02c566..0000000 --- a/org/index.org +++ /dev/null @@ -1,136 +0,0 @@ -#+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/ diff --git a/org/posts/2023-04-01-take-on-recursion.org b/org/posts/2023-04-01-take-on-recursion.org deleted file mode 100644 index bd463d5..0000000 --- a/org/posts/2023-04-01-take-on-recursion.org +++ /dev/null @@ -1,111 +0,0 @@ -#+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 diff --git a/org/posts/index.org b/org/posts/index.org deleted file mode 100644 index 9677968..0000000 --- a/org/posts/index.org +++ /dev/null @@ -1,6 +0,0 @@ -#+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: diff --git a/org/pub.org b/org/pub.org deleted file mode 100644 index c761cb3..0000000 --- a/org/pub.org +++ /dev/null @@ -1,9 +0,0 @@ -#+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 diff --git a/org/sweep.org b/org/sweep.org deleted file mode 120000 index b077d19..0000000 --- a/org/sweep.org +++ /dev/null @@ -1 +0,0 @@ -../sweep/README.org \ No newline at end of file diff --git a/publish.el b/publish.el deleted file mode 100644 index e194fce..0000000 --- a/publish.el +++ /dev/null @@ -1,278 +0,0 @@ -;;; 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 "" (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 "\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 "\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 "" nil t) - (replace-match (format "" - (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") - :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 diff --git a/source/changelog.org b/source/changelog.org new file mode 100644 index 0000000..40405b4 --- /dev/null +++ b/source/changelog.org @@ -0,0 +1,9 @@ +#+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 diff --git a/source/esy.org b/source/esy.org new file mode 100644 index 0000000..91c715e --- /dev/null +++ b/source/esy.org @@ -0,0 +1,9 @@ +#+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 diff --git a/source/favicon.ico b/source/favicon.ico new file mode 100644 index 0000000..f744115 Binary files /dev/null and b/source/favicon.ico differ diff --git a/source/home.svg b/source/home.svg new file mode 100644 index 0000000..222fdec --- /dev/null +++ b/source/home.svg @@ -0,0 +1,25 @@ + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/index.org b/source/index.org new file mode 100644 index 0000000..225ec28 --- /dev/null +++ b/source/index.org @@ -0,0 +1,142 @@ +#+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:@@Eshel Yaron@@html:@@ 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/ diff --git a/source/mail.svg b/source/mail.svg new file mode 100644 index 0000000..5752d47 --- /dev/null +++ b/source/mail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/mastodon.svg b/source/mastodon.svg new file mode 100644 index 0000000..350c222 --- /dev/null +++ b/source/mastodon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/notes/emacs.org b/source/notes/emacs.org new file mode 100644 index 0000000..bc6b464 --- /dev/null +++ b/source/notes/emacs.org @@ -0,0 +1,17 @@ +#+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: diff --git a/source/notes/gnu.org b/source/notes/gnu.org new file mode 100644 index 0000000..096c3a6 --- /dev/null +++ b/source/notes/gnu.org @@ -0,0 +1,14 @@ +#+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: diff --git a/source/notes/index.org b/source/notes/index.org new file mode 100644 index 0000000..caba0b2 --- /dev/null +++ b/source/notes/index.org @@ -0,0 +1,6 @@ +#+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: diff --git a/source/posts/2023-04-01-take-on-recursion.org b/source/posts/2023-04-01-take-on-recursion.org new file mode 100644 index 0000000..fd6d419 --- /dev/null +++ b/source/posts/2023-04-01-take-on-recursion.org @@ -0,0 +1,113 @@ +#+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:@@ + +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 diff --git a/source/posts/2023-04-05-the-self-healing-code-fallacy.org b/source/posts/2023-04-05-the-self-healing-code-fallacy.org new file mode 100644 index 0000000..e651d1b --- /dev/null +++ b/source/posts/2023-04-05-the-self-healing-code-fallacy.org @@ -0,0 +1,81 @@ +#+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:@@ + +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. diff --git a/source/posts/2023-04-08-making-shell-scripts-executable-just-in-time.org b/source/posts/2023-04-08-making-shell-scripts-executable-just-in-time.org new file mode 100644 index 0000000..fa0618d --- /dev/null +++ b/source/posts/2023-04-08-making-shell-scripts-executable-just-in-time.org @@ -0,0 +1,145 @@ +#+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:@@ + +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 + 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. diff --git a/source/posts/2023-04-11-optimizing-project-selection-in-emacs.org b/source/posts/2023-04-11-optimizing-project-selection-in-emacs.org new file mode 100644 index 0000000..71690dd --- /dev/null +++ b/source/posts/2023-04-11-optimizing-project-selection-in-emacs.org @@ -0,0 +1,209 @@ +#+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:@@ + +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. diff --git a/source/posts/index.org b/source/posts/index.org new file mode 100644 index 0000000..7e139c3 --- /dev/null +++ b/source/posts/index.org @@ -0,0 +1,6 @@ +#+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: diff --git a/source/pub.org b/source/pub.org new file mode 100644 index 0000000..c761cb3 --- /dev/null +++ b/source/pub.org @@ -0,0 +1,9 @@ +#+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 diff --git a/source/rss.svg b/source/rss.svg new file mode 100644 index 0000000..3ea2cd9 --- /dev/null +++ b/source/rss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/source/style.css b/source/style.css new file mode 100644 index 0000000..6a5655a --- /dev/null +++ b/source/style.css @@ -0,0 +1,318 @@ +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; +} diff --git a/source/sweep.org b/source/sweep.org new file mode 120000 index 0000000..b077d19 --- /dev/null +++ b/source/sweep.org @@ -0,0 +1 @@ +../sweep/README.org \ No newline at end of file diff --git a/src/changelog.org b/src/changelog.org deleted file mode 100644 index 40405b4..0000000 --- a/src/changelog.org +++ /dev/null @@ -1,9 +0,0 @@ -#+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 diff --git a/src/esy.org b/src/esy.org deleted file mode 100644 index 91c715e..0000000 --- a/src/esy.org +++ /dev/null @@ -1,9 +0,0 @@ -#+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 diff --git a/src/favicon.ico b/src/favicon.ico deleted file mode 100644 index f744115..0000000 Binary files a/src/favicon.ico and /dev/null differ diff --git a/src/home.svg b/src/home.svg deleted file mode 100644 index 222fdec..0000000 --- a/src/home.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - Layer 1 - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/index.org b/src/index.org deleted file mode 100644 index 225ec28..0000000 --- a/src/index.org +++ /dev/null @@ -1,142 +0,0 @@ -#+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:@@Eshel Yaron@@html:@@ 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/ diff --git a/src/mail.svg b/src/mail.svg deleted file mode 100644 index 5752d47..0000000 --- a/src/mail.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/mastodon.svg b/src/mastodon.svg deleted file mode 100644 index 350c222..0000000 --- a/src/mastodon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/posts/2023-04-01-take-on-recursion.org b/src/posts/2023-04-01-take-on-recursion.org deleted file mode 100644 index fd6d419..0000000 --- a/src/posts/2023-04-01-take-on-recursion.org +++ /dev/null @@ -1,113 +0,0 @@ -#+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:@@ - -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 diff --git a/src/posts/2023-04-05-the-self-healing-code-fallacy.org b/src/posts/2023-04-05-the-self-healing-code-fallacy.org deleted file mode 100644 index e651d1b..0000000 --- a/src/posts/2023-04-05-the-self-healing-code-fallacy.org +++ /dev/null @@ -1,81 +0,0 @@ -#+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:@@ - -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. diff --git a/src/posts/2023-04-08-making-shell-scripts-executable-just-in-time.org b/src/posts/2023-04-08-making-shell-scripts-executable-just-in-time.org deleted file mode 100644 index fa0618d..0000000 --- a/src/posts/2023-04-08-making-shell-scripts-executable-just-in-time.org +++ /dev/null @@ -1,145 +0,0 @@ -#+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:@@ - -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 - 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. diff --git a/src/posts/2023-04-11-optimizing-project-selection-in-emacs.org b/src/posts/2023-04-11-optimizing-project-selection-in-emacs.org deleted file mode 100644 index 71690dd..0000000 --- a/src/posts/2023-04-11-optimizing-project-selection-in-emacs.org +++ /dev/null @@ -1,209 +0,0 @@ -#+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:@@ - -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. diff --git a/src/posts/index.org b/src/posts/index.org deleted file mode 100644 index 7e139c3..0000000 --- a/src/posts/index.org +++ /dev/null @@ -1,6 +0,0 @@ -#+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: diff --git a/src/pub.org b/src/pub.org deleted file mode 100644 index c761cb3..0000000 --- a/src/pub.org +++ /dev/null @@ -1,9 +0,0 @@ -#+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 diff --git a/src/rss.svg b/src/rss.svg deleted file mode 100644 index 3ea2cd9..0000000 --- a/src/rss.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/style.css b/src/style.css deleted file mode 100644 index 6a5655a..0000000 --- a/src/style.css +++ /dev/null @@ -1,318 +0,0 @@ -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; -} diff --git a/src/sweep.org b/src/sweep.org deleted file mode 120000 index b077d19..0000000 --- a/src/sweep.org +++ /dev/null @@ -1 +0,0 @@ -../sweep/README.org \ No newline at end of file