summary refs log tree commit diff stats
path: root/lisp/+setup.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/+setup.el')
-rw-r--r--lisp/+setup.el216
1 files changed, 0 insertions, 216 deletions
diff --git a/lisp/+setup.el b/lisp/+setup.el deleted file mode 100644 index a08526a..0000000 --- a/lisp/+setup.el +++ /dev/null
@@ -1,216 +0,0 @@
1;;; +setup.el -- my `setup' commands -*- lexical-binding: t -*-
2
3;; Author: Case Duckworth <acdw@acdw.net>
4
5;; This file is NOT part of GNU Emacs.
6
7;;; License:
8;; Everyone is permitted to do whatever with this software, without
9;; limitation. This software comes without any warranty whatsoever,
10;; but with two pieces of advice:
11;; - Don't hurt yourself.
12;; - Make good choices.
13
14;;; Commentary:
15
16;; `setup', by Philip Kaludercic, is a wonderful package that works
17;; sort of like `use-package', but to my mind it's cleaner and easier
18;; to extend. These are my additions to the local macros provided by
19;; the package.
20
21;;; Code:
22
23(require 'el-patch)
24(require 'setup)
25(require 'straight)
26(require 'cl-lib)
27
28(defun +setup-warn (message &rest args)
29 "Warn the user that something bad happened in `setup'."
30 (display-warning 'setup (format message args)))
31
32(defun +setup-wrap-to-demote-errors (body name)
33 "Wrap BODY in a `with-demoted-errors' block.
34This behavior is prevented if `setup-attributes' contains the
35symbol `without-error-demotion'.
36
37This function differs from `setup-wrap-to-demote-errors' in that
38it includes the NAME of the setup form in the warning output."
39 (if (memq 'without-error-demotion setup-attributes)
40 body
41 `(with-demoted-errors ,(format "Error in setup form on line %d (%s): %%S"
42 (line-number-at-pos)
43 name)
44 ,body)))
45
46
47;;; New forms
48
49(setup-define :quit
50 'setup-quit
51 :documentation "Quit the current `setup' form.
52Good for commenting.")
53
54(setup-define :face
55 (lambda (face spec)
56 `(custom-set-faces (list ,face ,spec 'now "Customized by `setup'.")))
57 :documentation "Customize FACE with SPEC using `custom-set-faces'."
58 :repeatable t)
59
60(setup-define :load-after
61 (lambda (&rest features)
62 (let ((body `(require ',(setup-get 'feature))))
63 (dolist (feature (nreverse features))
64 (setq body `(with-eval-after-load ',feature ,body)))
65 body))
66 :documentation "Load the current feature after FEATURES.")
67
68(setup-define :load-from
69 (lambda (path)
70 `(let ((path* (expand-file-name ,path)))
71 (if (file-exists-p path*)
72 (add-to-list 'load-path path*)
73 ,(setup-quit))))
74 :documentation "Add PATH to load path.
75This macro can be used as NAME, and it will replace itself with
76the nondirectory part of PATH.
77If PATH does not exist, abort the evaluation."
78 :shorthand (lambda (args)
79 (intern
80 (file-name-nondirectory
81 (directory-file-name (cadr args))))))
82
83(setup-define :needs
84 (lambda (executable)
85 `(unless (executable-find ,executable)
86 ,(setup-quit)))
87 :documentation "If EXECUTABLE is not in the path, stop here."
88 :repeatable 1)
89
90
91;;; Package integrations
92
93;;; Straight.el
94
95(defun setup--straight-handle-arg (arg var)
96 (cond
97 ((and (boundp var) (symbol-value var)) t)
98 ((keywordp arg) (set var t))
99 ((functionp arg) (set var nil) (funcall arg))
100 ((listp arg) (set var nil) arg)))
101
102(with-eval-after-load 'straight
103 (setup-define :straight
104 (lambda (recipe &rest predicates)
105 (let* ((skp (make-symbol "straight-keyword-p"))
106 (straight-use-p
107 (cl-mapcar
108 (lambda (f) (setup--straight-handle-arg f skp))
109 predicates))
110 (form `(unless (and ,@straight-use-p
111 (condition-case e
112 (straight-use-package ',recipe)
113 (error
114 (+setup-warn ":straight error: %S"
115 ',recipe)
116 ,(setup-quit))
117 (:success t)))
118 ,(setup-quit))))
119 ;; Keyword arguments --- :quit is special and should short-circuit
120 (if (memq :quit predicates)
121 (setq form `,(setup-quit))
122 ;; Otherwise, handle the rest of them ...
123 (when-let ((after (cadr (memq :after predicates))))
124 (setq form `(with-eval-after-load ,(if (eq after t)
125 (setup-get 'feature)
126 after)
127 ,form))))
128 ;; Finally ...
129 form))
130 :documentation "Install RECIPE with `straight-use-package'.
131If PREDICATES are given, only install RECIPE if all of them return non-nil.
132The following keyword arguments are also recognized:
133- :quit --- immediately stop evaluating. Good for commenting.
134- :after FEATURE --- only install RECIPE after FEATURE is loaded.
135 If FEATURE is t, install RECIPE after the current feature."
136 :repeatable nil
137 :indent 1
138 :shorthand (lambda (sexp)
139 (let ((recipe (cadr sexp)))
140 (or (car-safe recipe) recipe)))))
141
142;;; Apheleia
143
144(setup-define :apheleia
145 (lambda (name formatter &optional mode -pend)
146 (let* ((mode (or mode (setup-get 'mode)))
147 (current-formatters (and -pend
148 (alist-get mode apheleia-formatters))))
149 `(with-eval-after-load 'apheleia
150 (setf (alist-get ',name apheleia-formatters)
151 ,formatter)
152 (setf (alist-get ',mode apheleia-mode-alist)
153 ',(pcase -pend
154 (:append (append (ensure-list current-formatters)
155 (list name)))
156 (:prepend (cons name (ensure-list current-formatters)))
157 ('nil name)
158 (_ (error "Improper `:apheleia' -PEND argument")))))))
159 :documentation
160 "Register a formatter to `apheleia''s lists.
161NAME is the name given to the formatter in `apheleia-formatters'
162and `apheleia-mode-alist'. FORMATTER is the command paired with
163NAME in `apheleia-formatters'. MODE is the mode or modes to add
164NAME to in `apheleia-mode-alist'. If MODE is not given or nil,
165use the setup form's MODE. Optional argument -PEND can be one of
166`:append' or `:prepend', and if given will append or prepend the
167given NAME to the current formatters for the MODE in
168`apheleia-mode-alist', rather than replace them (the default).
169
170Example:
171(setup
172 (:apheleia isort (\"isort\" \"--stdout\" \"-\")
173 python-mode))
174; =>
175(progn
176 (setf (alist-get 'isort apheleia-formatters)
177 '(\"isort\" \"--stdout\" \"-\"))
178 (setf (alist-get 'python-mode apheleia-mode-alist)
179 'isort))
180
181This form cannot be repeated, and it cannot be used as HEAD.")
182
183
184;;; Redefines of `setup' forms
185
186(setup-define :bind-into
187 (lambda (feature-or-map &rest rest)
188 (cl-loop for f/m in (ensure-list feature-or-map)
189 collect (if (string-match-p "-map\\'" (symbol-name f/m))
190 `(:with-map ,f/m (:bind ,@rest))
191 `(:with-feature ,f/m (:bind ,@rest)))
192 into forms
193 finally return `(progn ,@forms)))
194 :documentation "Bind into keys into the map(s) of FEATURE-OR-MAP.
195FEATURE-OR-MAP can be a feature or map name or a list of them.
196The arguments REST are handled as by `:bind'."
197 :debug '(sexp &rest form sexp)
198 :indent 1)
199
200(setup-define :require
201 (lambda (&rest features)
202 (require 'cl-lib)
203 (if features
204 `(progn ,@(cl-loop for feature in features collect
205 `(unless (require ',feature nil t)
206 ,(setup-quit))))
207 `(unless (require ',(setup-get 'feature) nil t)
208 ,(setup-quit))))
209 :documentation "Try to require FEATURE, or stop evaluating body.
210This macro can be used as NAME, and it will replace itself with
211the first FEATURE."
212 :repeatable nil
213 :shorthand #'cadr)
214
215(provide '+setup)
216;;; +setup.el ends here