From 4b275a237f019b9c289fa532f7dc1ca865e46578 Mon Sep 17 00:00:00 2001 From: Eshel Yaron Date: Sat, 8 Apr 2023 21:30:11 +0300 Subject: [PATCH] Added back /esy.html displaying my Emacs config + new blog post --- .gitignore | 4 + Makefile | 7 +- dotfiles | 2 +- publish.el | 14 +- src/esy.org | 9 ++ ...-shell-scripts-executable-just-in-time.org | 143 ++++++++++++++++++ src/style.css | 78 +++++++++- 7 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 src/esy.org create mode 100644 src/posts/2023-04-08-making-shell-scripts-executable-just-in-time.org diff --git a/.gitignore b/.gitignore index 4bc6c34..1730847 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ /org/sitemap.org /org/theindex.inc /org/theindex.org +/src/theindex.org +/src/sitemap.org +/src/theindex.inc +/draft/ diff --git a/Makefile b/Makefile index 85455b7..4634536 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ -.PHONY: local remote sweep +.PHONY: local remote sweep dotfiles remote: local emacs -Q --batch -l publish.el -f esy/publish-to-remote -local: sweep +local: sweep dotfiles emacs -Q --batch -l publish.el -f esy/publish sweep: git submodule update --remote -- sweep + +dotfiles: + git submodule update --remote -- dotfiles diff --git a/dotfiles b/dotfiles index 58ef067..b69b414 160000 --- a/dotfiles +++ b/dotfiles @@ -1 +1 @@ -Subproject commit 58ef0679439d4a756235774c5357e97859469911 +Subproject commit b69b414c0274b11708d3efc28538ec201be76159 diff --git a/publish.el b/publish.el index 497827f..994472c 100644 --- a/publish.el +++ b/publish.el @@ -10,6 +10,7 @@ (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) @@ -131,6 +132,17 @@ (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)) @@ -185,7 +197,7 @@ :completion-function #'esy/publish-completion-function :base-directory esy/publish-src-directory :publishing-directory publishing-directory - :preparation-function #'esy/publish-posts-prepare-index + :preparation-function #'esy/publish-prepare :completion-function #'ignore :base-extension "org" :recursive t diff --git a/src/esy.org b/src/esy.org new file mode 100644 index 0000000..91c715e --- /dev/null +++ b/src/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/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 new file mode 100644 index 0000000..3f2308e --- /dev/null +++ b/src/posts/2023-04-08-making-shell-scripts-executable-just-in-time.org @@ -0,0 +1,143 @@ +#+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 + +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/style.css b/src/style.css index ab2cabd..6ce3c2d 100644 --- a/src/style.css +++ b/src/style.css @@ -4,7 +4,7 @@ body { margin-left: auto; margin-right: auto; width: 90%; - max-width: 90ch; + max-width: 82ch; /* font-size: 1.1em; */ font-family: "Helvetica Neue", sans-serif; } @@ -220,6 +220,82 @@ Generated with `M-x org-html-htmlize-generate-css` after loading the /* 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; -- 2.39.2