284 lines
12 KiB
Org Mode
284 lines
12 KiB
Org Mode
#+TITLE: My Emacs Bootstrap
|
||
#+AUTHOR: Howard X. Abrams
|
||
#+DATE: 2021-10-08
|
||
|
||
A literate programming file for bootstraping my Emacs Configuration.
|
||
|
||
#+begin_src emacs-lisp :exports none
|
||
;;; bootstrap.el --- file for bootstraping my Emacs Configuration
|
||
;;
|
||
;; © 2021-2023 Howard X. Abrams
|
||
;; Licensed under a Creative Commons Attribution 4.0 International License.
|
||
;; 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:
|
||
#+end_src
|
||
* Introduction
|
||
This file contains all the variable definitions and library loading for the other files in my project.
|
||
|
||
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.
|
||
|
||
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8][this essay]].
|
||
|
||
** OS Path and Native Compilation
|
||
Helper functions to allow code for specific operating systems:
|
||
#+begin_src emacs-lisp
|
||
(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))
|
||
#+end_src
|
||
|
||
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]]):
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun set-exec-path-from-shell ()
|
||
"Set up Emacs' `exec-path' and PATH environment variable to match
|
||
that used by the user's shell.
|
||
|
||
The MacOS, where GUI apps are not started from a shell, requires this."
|
||
(interactive)
|
||
(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))))
|
||
#+end_src
|
||
|
||
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=:
|
||
|
||
#+begin_src emacs-lisp
|
||
(when (ha-running-on-macos?)
|
||
(add-to-list 'exec-path "/usr/local/bin")
|
||
(add-to-list 'exec-path (concat invocation-directory "bin") t))
|
||
#+end_src
|
||
|
||
Getting tired off all the packages that I load spewing a bunch of warnings that I can't do anything about:
|
||
#+begin_src emacs-lisp
|
||
(when (and (fboundp 'native-comp-available-p)
|
||
(native-comp-available-p))
|
||
(setq native-comp-async-report-warnings-errors nil
|
||
native-comp-deferred-compilation t))
|
||
#+end_src
|
||
** GNU Pretty Good Privacy
|
||
On Linux, GPG is pretty straight-forward, but on the Mac, I often have troubles doing:
|
||
#+begin_src sh
|
||
brew install gpg
|
||
#+end_src
|
||
Next, on every reboot, start the agent:
|
||
#+begin_src sh
|
||
/usr/local/Cellar/gnupg/2.3.6/bin/gpg-agent --daemon
|
||
#+end_src
|
||
|
||
Since =brew link gpg= doesn’t always work, this helper function may find the executable:
|
||
#+begin_src emacs-lisp
|
||
(defun executable (path)
|
||
"Return PATH if executable, see `file-executable-p'."
|
||
(let ((epath (first (file-expand-wildcards path))))
|
||
(when (and epath (file-executable-p epath)) epath)))
|
||
|
||
(use-package epa-file
|
||
:straight (:type built-in)
|
||
:custom (epg-gpg-program (or (executable "/usr/local/Cellar/gnupg/*/bin/gpg")
|
||
(executable "/usr/local/bin/gpg")
|
||
(executable "/usr/local/opt/gpg")
|
||
(executable "/usr/bin/pgp")))
|
||
:config (epa-file-enable))
|
||
#+end_src
|
||
** Basic Libraries
|
||
The following packages come with Emacs, but seems like they still need loading:
|
||
#+begin_src emacs-lisp
|
||
(use-package cl-lib
|
||
:straight (:type built-in)
|
||
:init (defun first (elt) (car elt))
|
||
:commands (first))
|
||
|
||
(require 'subr-x)
|
||
#+end_src
|
||
Ugh. Why am I getting a missing =first= function error? I define a simple implementation, that the CL library will overwrite ... at some point.
|
||
|
||
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
|
||
(use-package dash)
|
||
#+end_src
|
||
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.
|
||
|
||
The [[https://github.com/magnars/s.el][s.el]] project is a simpler string manipulation library that I (and other projects) use:
|
||
#+begin_src emacs-lisp
|
||
(use-package s)
|
||
#+end_src
|
||
|
||
Manipulate file paths with the [[https://github.com/rejeep/f.el][f.el]] project:
|
||
#+begin_src emacs-lisp
|
||
(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")))))
|
||
#+end_src
|
||
** My Code Location
|
||
Much of my more complicated code comes from my website essays and other projects. The destination shows up here:
|
||
#+begin_src emacs-lisp
|
||
(add-to-list 'load-path (f-expand "~/.emacs.d/elisp"))
|
||
#+end_src
|
||
|
||
Hopefully, this will tie me over while I transition.
|
||
** Emacs Server Control
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(defun ha-emacs-for-work? ()
|
||
"Return non-nil when the Emacs application's location matches as one for work.
|
||
Based on initially running the app with a `FOR_WORK' environment variable."
|
||
(and (f-dir? "~/work")
|
||
(getenv "FOR_WORK")))
|
||
#+end_src
|
||
|
||
And now start the server with an appropriate tag name:
|
||
#+begin_src emacs-lisp
|
||
(if (not (ha-emacs-for-work?))
|
||
(setq server-name "personal")
|
||
(setq server-name "work")
|
||
(when (ha-running-on-macos?)
|
||
(set-exec-path-from-shell)))
|
||
|
||
(server-start)
|
||
#+end_src
|
||
* Load the Rest
|
||
The following loads the rest of my org-mode literate files. I add new filesas they are /ready/:
|
||
#+begin_src emacs-lisp
|
||
(defvar ha-hamacs-files (flatten-list
|
||
`("ha-private.org"
|
||
"ha-config.org"
|
||
,(when (display-graphic-p)
|
||
"ha-display.org")
|
||
"ha-org.org"
|
||
,(when (display-graphic-p)
|
||
"ha-org-word-processor.org")
|
||
"ha-org-clipboard.org"
|
||
"ha-capturing-notes.org"
|
||
"ha-agendas.org"
|
||
"ha-data.org"
|
||
"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?)
|
||
'("ha-org-sprint.org"
|
||
;; "ha-programming-ruby.org"
|
||
"ha-work.org")
|
||
;; Personal Editor
|
||
'("ha-org-journaling.org"
|
||
"ha-irc.org"
|
||
"ha-org-publishing.org"
|
||
"ha-email.org"
|
||
"ha-aux-apps.org"
|
||
"ha-feed-reader.org"))
|
||
"ha-dashboard.org"))
|
||
"List of org files that complete the hamacs project.")
|
||
#+end_src
|
||
|
||
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)
|
||
(directory-files "~/other/hamacs" nil)
|
||
(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:
|
||
#+begin_src emacs-lisp
|
||
(defun ha-hamacs-load (file)
|
||
"Load or reload 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)))
|
||
(when (f-exists? full-file)
|
||
(ignore-errors
|
||
(org-babel-load-file full-file)))))
|
||
#+end_src
|
||
|
||
And we can now reload /all/ startup files:
|
||
#+begin_src emacs-lisp
|
||
(defun ha-hamacs-reload-all ()
|
||
"Reload our entire ecosystem of configuration files."
|
||
(interactive)
|
||
(dolist (file (ha-hamacs-files))
|
||
(unless (equal file "bootstrap.org")
|
||
(ha-hamacs-load file))))
|
||
#+end_src
|
||
|
||
And do it:
|
||
#+begin_src emacs-lisp
|
||
(ha-hamacs-reload-all)
|
||
#+end_src
|
||
|
||
* Technical Artifacts :noexport:
|
||
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
|
||
|
||
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~
|
||
|
||
#+DESCRIPTION: A literate programming file for bootstrapping my environment.
|
||
|
||
#+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
|
||
|
||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|