2023-12-03 18:57:36 +00:00
#+title : My Emacs Bootstrap
#+author : Howard X. Abrams
#+date : 2021-10-08
#+tags : emacs
2021-11-02 00:27:14 +00:00
A literate programming file for bootstraping my Emacs Configuration.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :exports none
2022-03-09 18:45:37 +00:00
;;; bootstrap.el --- file for bootstraping my Emacs Configuration
;;
2023-02-23 17:35:36 +00:00
;; © 2021-2023 Howard X. Abrams
2022-06-18 00:25:47 +00:00
;; Licensed under a Creative Commons Attribution 4.0 International License.
2022-03-09 18:45:37 +00:00
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams >
;; Maintainer: Howard X. Abrams
;; Created: October 8, 2021
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; ~/other/hamacs/bootstrap.org
;; And tangle the file to recreate this one.
;;
;;; Code:
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Introduction
This file contains all the variable definitions and library loading for the other files in my project.
2023-02-23 17:28:00 +00:00
I'm installing everything using the [[https://github.com/raxod502/straight.el#getting-started ][straight.el ]] for package installation and management. This is initialization code configured in [[file:initialize ][initialize ]], and calls to =use-package= now accepts a =:straight= parameter that allows me to retrieve special versions of some packages.
2022-03-03 23:16:50 +00:00
2021-11-02 00:27:14 +00:00
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8 ][this essay ]].
2021-12-08 21:56:30 +00:00
2023-12-22 04:26:51 +00:00
* Initial Settings
2021-12-08 21:56:30 +00:00
** OS Path and Native Compilation
2021-12-13 18:45:32 +00:00
Helper functions to allow code for specific operating systems:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-13 18:45:32 +00:00
(defun ha-running-on-macos? ()
"Return non-nil if running on Mac OS systems."
(equal system-type 'darwin))
(defun ha-running-on-linux? ()
"Return non-nil if running on Linux systems."
(equal system-type 'gnu/linux))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-13 18:45:32 +00:00
2022-06-18 00:25:47 +00:00
With the way I start Emacs, I may not have the =PATH= I /actually/ use (from the shell) available, so we'll force it (code taken [[https://www.emacswiki.org/emacs/ExecPath ][from here ]]):
2021-12-08 21:56:30 +00:00
2024-02-23 00:55:56 +00:00
#+begin_src emacs-lisp
2021-12-08 21:56:30 +00:00
(defun set-exec-path-from-shell ()
"Set up Emacs' `exec-path' and PATH environment variable to match
that used by the user's shell.
2022-06-18 00:25:47 +00:00
The MacOS, where GUI apps are not started from a shell, requires this."
2021-12-08 21:56:30 +00:00
(interactive)
2022-01-03 06:42:22 +00:00
(let* ((path-from-shell (shell-command-to-string "echo $PATH"))
(trimmed-path (replace-regexp-in-string (rx (zero-or-more space) eol)
"" path-from-shell))
(in-fish? (string-match (rx "fish" eol)
(shell-command-to-string "echo $SHELL")))
(separator (if in-fish? " " ":"))
(env-path (if in-fish? (replace-regexp-in-string " " ":" trimmed-path) trimmed-path)))
(message "PATH=%s" path-from-shell)
(setenv "PATH" env-path)
(setq exec-path (split-string trimmed-path separator))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-08 21:56:30 +00:00
Clear up a Mac-specific issue that sometimes arises since I'm switching to [[http://akrl.sdf.org/gccemacs.html ][native compilation project ]], as the =Emacs.app= that I use doesn't have its =bin= directory, e.g. =Emacs.app/Contents/MacOS/bin= :
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-13 18:45:32 +00:00
(when (ha-running-on-macos?)
2022-01-03 06:42:22 +00:00
(add-to-list 'exec-path "/usr/local/bin")
2023-11-06 17:36:06 +00:00
(add-to-list 'exec-path "/opt/homebrew/bin")
2021-12-08 21:56:30 +00:00
(add-to-list 'exec-path (concat invocation-directory "bin") t))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-08 21:56:30 +00:00
Getting tired off all the packages that I load spewing a bunch of warnings that I can't do anything about:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-13 18:45:32 +00:00
(when (and (fboundp 'native-comp-available-p)
(native-comp-available-p))
(setq native-comp-async-report-warnings-errors nil
native-comp-deferred-compilation t))
2022-06-18 00:25:47 +00:00
#+end_src
2023-12-22 04:26:51 +00:00
* Basic Libraries
2021-11-02 00:27:14 +00:00
The following packages come with Emacs, but seems like they still need loading:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-01-06 23:36:39 +00:00
(use-package cl-lib
:straight (:type built-in)
:init (defun first (elt) (car elt))
:commands (first))
(require 'subr-x)
2022-06-18 00:25:47 +00:00
#+end_src
2022-01-06 23:36:39 +00:00
Ugh. Why am I getting a missing =first= function error? I define a simple implementation, that the CL library will overwrite ... at some point.
2022-06-18 00:25:47 +00:00
While most libraries will take care of their dependencies, I want to install /my dependent libraries/ , e.g, [[https://github.com/magnars/.emacs.d/ ][Magnar Sveen ]]'s Clojure-inspired [[https://github.com/magnars/dash.el ][dash.el ]] project:
#+begin_src emacs-lisp
2024-02-23 00:55:56 +00:00
(use-package dash)
2022-06-18 00:25:47 +00:00
#+end_src
2022-01-06 23:36:39 +00:00
Sure this package is essentially syntactic sugar, and to help /share/ my configuration, I attempt to use =thread-last= instead of =->>= , but, I still like it.
2021-11-02 00:27:14 +00:00
The [[https://github.com/magnars/s.el ][s.el ]] project is a simpler string manipulation library that I (and other projects) use:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2024-02-23 00:55:56 +00:00
(use-package s)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
Manipulate file paths with the [[https://github.com/rejeep/f.el ][f.el ]] project:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-10-18 03:49:00 +00:00
(use-package f)
#+end_src
The [[help:shell-command ][shell-command ]] function is useful, but having it split the output into a list is a helpful abstraction:
#+begin_src emacs-lisp
(defun shell-command-to-list (command)
"Return list of lines from running COMMAND in shell."
(thread-last command
shell-command-to-string
s-lines
(-map 's-trim)
(-remove 's-blank-str?)))
#+end_src
And let’ s see the results:
#+begin_src emacs-lisp :tangle no
(ert-deftest shell-command-to-list-test ()
(should (equal '("hello world")
(shell-command-to-list "echo hello world")))
;; We don't need blank lines:
(should (equal '("hello world" "goodbye for now")
(shell-command-to-list "echo '\n\nhello world\n\ngoodbye for now\n\n'"))
;; No output? Return null:
(should (null (shell-command-to-list "echo")))
;; No line should contain carriage returns:
(should (null (seq-filter
(lambda (line) (s-contains? "\n" line))
(shell-command-to-list "ls")))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
** My Code Location
2022-06-18 00:25:47 +00:00
Much of my more complicated code comes from my website essays and other projects. The destination shows up here:
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(add-to-list 'load-path (f-expand "~/.emacs.d/elisp"))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
Hopefully, this will tie me over while I transition.
2023-12-22 04:26:51 +00:00
* Emacs Server Control
2022-10-03 17:10:38 +00:00
I actually run two instances of Emacs on some systems, where one instance has all my work-related projects, perspectives, and packages installed (like LSP), and my personal instance has other packages running (like IRC and Mail). I need a function that can make that distinction, and based on that, it will set =server-start= appropriately, so that =emacsclient= can call into the correct one.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-01-03 06:42:22 +00:00
(defun ha-emacs-for-work? ()
2024-02-24 06:20:25 +00:00
"Return non-nil when the Emacs instance is for work.
Matches based on a `FOR_WORK' environment variable."
2022-01-03 06:42:22 +00:00
(and (f-dir? "~/work")
2022-10-03 17:10:38 +00:00
(getenv "FOR_WORK")))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-06 00:06:55 +00:00
2022-04-01 18:33:24 +00:00
And now start the server with an appropriate tag name:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-01-03 06:42:22 +00:00
(if (not (ha-emacs-for-work?))
(setq server-name "personal")
2021-11-06 00:06:55 +00:00
(setq server-name "work")
2022-01-03 06:42:22 +00:00
(when (ha-running-on-macos?)
(set-exec-path-from-shell)))
2021-11-06 00:06:55 +00:00
2022-01-03 06:42:22 +00:00
(server-start)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Load the Rest
2024-02-09 20:05:13 +00:00
The following /defines/ the rest of my org-mode literate files, that I load later with the =ha-hamacs-load= function:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
(defvar ha-hamacs-files (flatten-list
`("ha-private.org"
"ha-config.org"
2024-02-09 20:05:13 +00:00
;; "ha-leader.org"
"ha-evil.org"
;; "ha-meow.org"
2023-12-22 05:22:16 +00:00
"ha-applications.org"
2022-09-21 06:09:42 +00:00
,(when (display-graphic-p)
"ha-display.org")
2022-06-18 00:25:47 +00:00
"ha-org.org"
2022-09-21 06:09:42 +00:00
,(when (display-graphic-p)
"ha-org-word-processor.org")
"ha-org-clipboard.org"
"ha-capturing-notes.org"
"ha-agendas.org"
2022-10-18 03:49:00 +00:00
"ha-data.org"
2022-09-21 06:09:42 +00:00
"ha-passwords.org"
"ha-eshell.org"
"ha-remoting.org"
"ha-programming.org"
"ha-programming-elisp.org"
"ha-programming-python.org"
,(if (ha-emacs-for-work?)
2023-04-01 23:29:40 +00:00
'("ha-org-sprint.org"
;; "ha-programming-ruby.org"
"ha-work.org")
2022-09-21 06:09:42 +00:00
;; Personal Editor
'("ha-org-journaling.org"
2024-03-22 20:43:12 +00:00
;; "ha-irc.org"
2022-09-21 06:09:42 +00:00
"ha-org-publishing.org"
"ha-email.org"
"ha-aux-apps.org"
2022-11-03 03:59:00 +00:00
"ha-feed-reader.org"))
"ha-dashboard.org"))
2021-11-10 01:18:52 +00:00
"List of org files that complete the hamacs project.")
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-10 01:18:52 +00:00
2022-09-02 23:05:23 +00:00
The list of /hamacs/ org-formatted files stored in =ha-hamacs-files= is selectively short, and doesn’ t include all files, for instance, certain languages that I’ m learning aren’ t automatically included. The function, =ha-hamacs-files= will return the list loaded at startup, as well as with an optional parameter, return them all.
#+begin_src emacs-lisp
(defun ha-hamacs-files (&optional all)
"Return a list of my org files in my `hamacs' directory."
(if (not all)
ha-hamacs-files
(thread-last (rx ".org" string-end)
2024-02-22 00:28:57 +00:00
(directory-files hamacs-source-dir nil)
2022-09-02 23:05:23 +00:00
(append ha-hamacs-files)
(--filter (not (string-match (rx "README") it)))
(-uniq))))
#+end_src
With this function, we can test/debug/reload any individual file, via:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-10 01:18:52 +00:00
(defun ha-hamacs-load (file)
2024-02-24 06:20:25 +00:00
"Load or reload an org-mode FILE containing literate
Emacs configuration code."
(interactive (list (completing-read "Org file: "
(ha-hamacs-files :all))))
2021-11-18 20:12:19 +00:00
(let ((full-file (f-join hamacs-source-dir file)))
(when (f-exists? full-file)
2023-04-03 16:24:52 +00:00
(ignore-errors
(org-babel-load-file full-file)))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-10 01:18:52 +00:00
2024-02-22 00:28:57 +00:00
And the ability to edit the file:
#+begin_src emacs-lisp
(defun ha-hamacs-find-file (file)
2024-02-24 06:20:25 +00:00
"Call `find-file' on relative org-mode FILE containing
literate Emacs configuration code."
(interactive (list (completing-read "Org file: "
(ha-hamacs-files :all))))
2024-02-22 00:28:57 +00:00
(let ((full-file (f-join hamacs-source-dir file)))
(find-file full-file)))
#+end_src
2023-07-05 20:20:55 +00:00
And this similar function, will /tangle/ one of my files. Notice that in order to increase the speed of the tangling process (and not wanting to pollute a project perspective), I use a /temporary buffer/ instead of =find-file= .
#+begin_src emacs-lisp
2024-02-24 06:20:25 +00:00
(defun ha-hamacs-tangle (file)
"Tangle an org-mode FILE containing literate Emacs
configuration code."
(interactive (list (completing-read "Org file: "
(ha-hamacs-files :all))))
(let ((full-file (f-join hamacs-source-dir file))
(target (file-name-concat "~/emacs.d/elisp"
(concat (file-name-sans-extension file)
".el"))))
(when (f-exists? full-file)
(ignore-errors
(with-temp-buffer
(insert-file-contents full-file)
(with-current-buffer (concat temporary-file-directory file)
(org-babel-tangle nil target (rx "emacs-lisp"))))))))
2023-07-05 20:20:55 +00:00
#+end_src
2022-09-02 23:05:23 +00:00
And we can now reload /all/ startup files:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-24 00:29:52 +00:00
(defun ha-hamacs-reload-all ()
"Reload our entire ecosystem of configuration files."
(interactive)
2022-09-02 23:05:23 +00:00
(dolist (file (ha-hamacs-files))
2022-05-11 21:35:48 +00:00
(unless (equal file "bootstrap.org")
(ha-hamacs-load file))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-24 00:29:52 +00:00
2023-07-05 20:20:55 +00:00
And we can tangle /all/ the files:
#+begin_src emacs-lisp
(defun ha-hamacs-tangle-all ()
"Tangle all my Org initialization/configuration files."
(interactive)
(dolist (file (ha-hamacs-files))
(unless (equal file "bootstrap.org")
(ha-hamacs-tangle file))))
#+end_src
2021-11-24 00:29:52 +00:00
And do it:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-24 00:29:52 +00:00
(ha-hamacs-reload-all)
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-09 15:59:23 +00:00
2021-11-02 00:27:14 +00:00
* Technical Artifacts :noexport:
2022-06-18 00:25:47 +00:00
Let's provide a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'bootstrap)
;;; bootstrap.el ends here
#+end_src
2021-11-02 00:27:14 +00:00
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
2024-03-07 04:02:25 +00:00
#+description : A literate programming file for bootstrapping my environment.
2021-11-02 00:27:14 +00:00
2024-03-07 04:02:25 +00:00
#+property : header-args:sh :tangle no
#+property : header-args:emacs-lisp :tangle yes
#+property : header-args :results none :eval no-export :comments no mkdirp yes
2021-11-02 00:27:14 +00:00
2024-03-07 04:02:25 +00:00
#+options : num:nil toc:t todo:nil tasks:nil tags:nil date:nil
#+options : skip:nil author:nil email:nil creator:nil timestamp:nil
#+infojs_opt : view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js