refactor!: restructure Doom core
BREAKING CHANGE: This restructures the project in preparation for Doom to be split into two repos. Users that have reconfigured Doom's CLI stand a good chance of seeing breakage, especially if they've referred to any core-* feature, e.g. (after! core-cli-ci ...) To fix it, simply s/core-/doom-/, i.e. (after! doom-cli-ci ...) What this commit specifically changes is: - Renames all core features from core-* to doom-* - Moves core/core-* -> lisp/doom-* - Moves core/autoloads/* -> lisp/lib/* - Moves core/templates -> templates/ Ref: #4273
This commit is contained in:
parent
a9866e37e4
commit
b9933e6637
69 changed files with 147 additions and 145 deletions
241
lisp/cli/autoloads.el
Normal file
241
lisp/cli/autoloads.el
Normal file
|
@ -0,0 +1,241 @@
|
|||
;;; lisp/cli/autoloads.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-autoloads-excluded-packages ()
|
||||
"What packages whose autoloads files we won't index.
|
||||
|
||||
These packages have silly or destructive autoload files that try to load
|
||||
everyone in the universe and their dog, causing errors that make babies cry. No
|
||||
one wants that.")
|
||||
|
||||
(defvar doom-autoloads-excluded-files ()
|
||||
"List of regexps whose matching files won't be indexed for autoloads.")
|
||||
|
||||
(defvar doom-autoloads-cached-vars
|
||||
'(doom-modules
|
||||
doom-disabled-packages
|
||||
native-comp-deferred-compilation-deny-list
|
||||
load-path
|
||||
auto-mode-alist
|
||||
interpreter-mode-alist
|
||||
Info-directory-list)
|
||||
"A list of variables to be cached in `doom-autoloads-file'.")
|
||||
|
||||
(defvar doom-autoloads-files ()
|
||||
"A list of additional files or file globs to scan for autoloads.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Library
|
||||
|
||||
(defun doom-autoloads-reload (&optional file)
|
||||
"Regenerates Doom's autoloads and writes them to FILE."
|
||||
(unless file
|
||||
;; TODO Uncomment when profile system is implemented
|
||||
;; (make-directory doom-profile-dir t)
|
||||
;; (setq file (expand-file-name "init.el" doom-profile-dir))
|
||||
(setq file doom-autoloads-file))
|
||||
(print! (start "(Re)generating autoloads file..."))
|
||||
(print-group!
|
||||
(cl-check-type file string)
|
||||
(doom-initialize-packages)
|
||||
(and (print! (start "Generating autoloads file..."))
|
||||
(doom-autoloads--write
|
||||
file
|
||||
`((unless (equal doom-version ,doom-version)
|
||||
(signal 'doom-error
|
||||
(list "The installed version of Doom has changed since last 'doom sync' ran"
|
||||
"Run 'doom sync' to bring Doom up to speed"))))
|
||||
(cl-loop for var in doom-autoloads-cached-vars
|
||||
when (boundp var)
|
||||
collect `(set ',var ',(symbol-value var)))
|
||||
(doom-autoloads--scan
|
||||
(append (doom-glob doom-core-dir "lib/*.el")
|
||||
(cl-loop for dir
|
||||
in (append (cdr (doom-module-load-path 'all-p))
|
||||
(list doom-private-dir))
|
||||
if (doom-glob dir "autoload.el") collect (car it)
|
||||
if (doom-glob dir "autoload/*.el") append it)
|
||||
(mapcan #'doom-glob doom-autoloads-files))
|
||||
nil)
|
||||
(doom-autoloads--scan
|
||||
(mapcar #'straight--autoloads-file
|
||||
(seq-difference (hash-table-keys straight--build-cache)
|
||||
doom-autoloads-excluded-packages))
|
||||
doom-autoloads-excluded-files
|
||||
'literal)
|
||||
;; TODO Uncomment when profile system is implemented
|
||||
;; `((unless noninteractive (require 'doom-start)))
|
||||
)
|
||||
(print! (start "Byte-compiling autoloads file..."))
|
||||
(doom-autoloads--compile-file file)
|
||||
(print! (success "Generated %s")
|
||||
(relpath (byte-compile-dest-file file)
|
||||
doom-emacs-dir)))))
|
||||
|
||||
(defun doom-autoloads--write (file &rest forms)
|
||||
(make-directory (file-name-directory file) 'parents)
|
||||
(condition-case-unless-debug e
|
||||
(with-temp-file file
|
||||
(setq-local coding-system-for-write 'utf-8)
|
||||
(let ((standard-output (current-buffer))
|
||||
(print-quoted t)
|
||||
(print-level nil)
|
||||
(print-length nil))
|
||||
(insert ";; -*- lexical-binding: t; coding: utf-8; no-native-compile: t -*-\n"
|
||||
";; This file is autogenerated by 'doom sync', DO NOT EDIT IT!!\n")
|
||||
(dolist (form (delq nil forms))
|
||||
(mapc #'prin1 form))
|
||||
t))
|
||||
(error (delete-file file)
|
||||
(signal 'doom-autoload-error (list file e)))))
|
||||
|
||||
(defun doom-autoloads--compile-file (file)
|
||||
(condition-case-unless-debug e
|
||||
(let ((byte-compile-warnings (if init-file-debug byte-compile-warnings)))
|
||||
(and (byte-compile-file file)
|
||||
(load (byte-compile-dest-file file) nil t)))
|
||||
(error
|
||||
(delete-file (byte-compile-dest-file file))
|
||||
(signal 'doom-autoload-error (list file e)))))
|
||||
|
||||
(defun doom-autoloads--cleanup-form (form &optional expand)
|
||||
(let ((func (car-safe form)))
|
||||
(cond ((memq func '(provide custom-autoload))
|
||||
nil)
|
||||
((and (eq func 'add-to-list)
|
||||
(memq (doom-unquote (cadr form))
|
||||
doom-autoloads-cached-vars))
|
||||
nil)
|
||||
((not (eq func 'autoload))
|
||||
form)
|
||||
((and expand (not (file-name-absolute-p (nth 2 form))))
|
||||
(defvar doom--autoloads-path-cache nil)
|
||||
(setf (nth 2 form)
|
||||
(let ((path (nth 2 form)))
|
||||
(or (cdr (assoc path doom--autoloads-path-cache))
|
||||
(when-let* ((libpath (locate-library path))
|
||||
(libpath (file-name-sans-extension libpath))
|
||||
(libpath (abbreviate-file-name libpath)))
|
||||
(push (cons path libpath) doom--autoloads-path-cache)
|
||||
libpath)
|
||||
path)))
|
||||
form)
|
||||
(form))))
|
||||
|
||||
(defun doom-autoloads--scan-autodefs (file buffer module &optional module-enabled-p)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t)
|
||||
(let* ((standard-output buffer)
|
||||
(form (read (current-buffer)))
|
||||
(altform (match-string 1))
|
||||
(definer (car-safe form))
|
||||
(symbol (doom-unquote (cadr form))))
|
||||
(cond ((and (not module-enabled-p) altform)
|
||||
(print (read altform)))
|
||||
((memq definer '(defun defmacro cl-defun cl-defmacro))
|
||||
(print
|
||||
(if module-enabled-p
|
||||
(make-autoload form file)
|
||||
(seq-let (_ _ arglist &rest body) form
|
||||
(if altform
|
||||
(read altform)
|
||||
(append
|
||||
(list (pcase definer
|
||||
(`defun 'defmacro)
|
||||
(`cl-defun `cl-defmacro)
|
||||
(_ type))
|
||||
symbol arglist
|
||||
(format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
|
||||
module (if (stringp (car body))
|
||||
(pop body)
|
||||
"No documentation.")))
|
||||
(cl-loop for arg in arglist
|
||||
if (symbolp arg)
|
||||
if (not (keywordp arg))
|
||||
if (not (memq arg cl--lambda-list-keywords))
|
||||
collect arg into syms
|
||||
else if (listp arg)
|
||||
collect (car arg) into syms
|
||||
finally return (if syms `((ignore ,@syms)))))))))
|
||||
(print `(put ',symbol 'doom-module ',module)))
|
||||
((eq definer 'defalias)
|
||||
(seq-let (_ _ target docstring) form
|
||||
(unless module-enabled-p
|
||||
(setq target #'ignore
|
||||
docstring
|
||||
(format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
|
||||
module docstring)))
|
||||
(print `(put ',symbol 'doom-module ',module))
|
||||
(print `(defalias ',symbol #',(doom-unquote target) ,docstring))))
|
||||
(module-enabled-p (print form)))))))
|
||||
|
||||
(defvar autoload-timestamps)
|
||||
(defvar generated-autoload-load-name)
|
||||
(defun doom-autoloads--scan-file (file)
|
||||
(let* (;; Prevent `autoload-find-file' from firing file hooks, e.g. adding
|
||||
;; to recentf.
|
||||
find-file-hook
|
||||
write-file-functions
|
||||
;; Prevent a possible source of crashes when there's a syntax error in
|
||||
;; the autoloads file.
|
||||
debug-on-error
|
||||
;; Non-nil interferes with autoload generation in Emacs < 29. See
|
||||
;; radian-software/straight.el#904.
|
||||
(left-margin 0)
|
||||
;; The following bindings are in `package-generate-autoloads'.
|
||||
;; Presumably for a good reason, so I just copied them.
|
||||
(backup-inhibited t)
|
||||
(version-control 'never)
|
||||
case-fold-search ; reduce magic
|
||||
autoload-timestamps ; reduce noise in generated files
|
||||
;; So `autoload-generate-file-autoloads' knows where to write it
|
||||
(generated-autoload-load-name (file-name-sans-extension file))
|
||||
(target-buffer (current-buffer))
|
||||
(module (doom-module-from-path file))
|
||||
(module-enabled-p (and (or (memq (car module) '(:core :private))
|
||||
(doom-module-p (car module) (cdr module)))
|
||||
(doom-file-cookie-p file "if" t))))
|
||||
(save-excursion
|
||||
(when module-enabled-p
|
||||
(quiet! (autoload-generate-file-autoloads file target-buffer)))
|
||||
(doom-autoloads--scan-autodefs
|
||||
file target-buffer module module-enabled-p))))
|
||||
|
||||
(defun doom-autoloads--scan (files &optional exclude literal)
|
||||
"Scan and return all autoloaded forms in FILES.
|
||||
|
||||
Autoloads will be generated from autoload cookies in FILES (except those that
|
||||
match one of the regexps in EXCLUDE -- a list of strings). If LITERAL is
|
||||
non-nil, treat FILES as pre-generated autoload files instead."
|
||||
(require 'autoload)
|
||||
(let (autoloads)
|
||||
(dolist (file files (nreverse (delq nil autoloads)))
|
||||
(when (and (not (seq-find (doom-rpartial #'string-match-p file) exclude))
|
||||
(file-readable-p file))
|
||||
(doom-log "Scanning %s" file)
|
||||
(setq file (file-truename file))
|
||||
(with-temp-buffer
|
||||
(if literal
|
||||
(insert-file-contents file)
|
||||
(doom-autoloads--scan-file file))
|
||||
(save-excursion
|
||||
(while (re-search-forward "\\_<load-file-name\\_>" nil t)
|
||||
;; `load-file-name' is meaningless in a concatenated
|
||||
;; mega-autoloads file, but also essential in isolation, so we
|
||||
;; replace references to it with the file they came from.
|
||||
(let ((ppss (save-excursion (syntax-ppss))))
|
||||
(or (nth 3 ppss)
|
||||
(nth 4 ppss)
|
||||
(replace-match (prin1-to-string file) t t)))))
|
||||
(let ((load-file-name file)
|
||||
(load-path
|
||||
(append (list doom-private-dir)
|
||||
doom-modules-dirs
|
||||
load-path)))
|
||||
(condition-case _
|
||||
(while t
|
||||
(push (doom-autoloads--cleanup-form (read (current-buffer))
|
||||
(not literal))
|
||||
autoloads))
|
||||
(end-of-file))))))))
|
472
lisp/cli/ci.el
Normal file
472
lisp/cli/ci.el
Normal file
|
@ -0,0 +1,472 @@
|
|||
;;; lisp/cli/ci.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
(defvar doom-ci-commit-trailer-keys
|
||||
'(("Fix" ref hash url)
|
||||
("Ref" ref hash url)
|
||||
("Close" ref)
|
||||
("Revert" ref hash)
|
||||
("Amend" ref hash)
|
||||
("Co-authored-by" name)
|
||||
("Signed-off-by" name))
|
||||
"An alist of valid trailer keys and their accepted value types.
|
||||
|
||||
Accapted value types can be one or more of ref, hash, url, username, or name.")
|
||||
|
||||
(defvar doom-ci-commit-trailer-types
|
||||
'((ref . "^\\(https?://[^ ]+\\|[^/]+/[^/]+\\)?#[0-9]+$")
|
||||
(hash . "^\\(https?://[^ ]+\\|[^/]+/[^/]+@\\)?[a-z0-9]\\{12\\}$")
|
||||
(url . "^https?://")
|
||||
(name . "^[a-zA-Z0-9-_ \\.']+<[^@]+@[^.]+\\.[^>]+>$")
|
||||
(username . "^@[^a-zA-Z0-9_-]+$"))
|
||||
"An alist of valid trailer keys and their accepted value types.
|
||||
|
||||
Accapted value types can be one or more of ref, hash, url, username, or name.")
|
||||
|
||||
(defvar doom-ci-commit-types
|
||||
'(bump dev docs feat fix merge nit perf refactor release revert test tweak)
|
||||
"A list of valid commit types.")
|
||||
|
||||
(defvar doom-ci-commit-scopeless-types '(bump merge release revert)
|
||||
"A list of commit types whose scopes should be passed in its BODY.
|
||||
|
||||
Don't: \"bump(SCOPE): ...\"
|
||||
Do: \"bump: SCOPE\"")
|
||||
|
||||
(defvar doom-ci-commit-scopes '("ci" doom-ci-enforce-scopeless-types)
|
||||
"A list of valid commit scopes as strings, predicate functions, or lists.
|
||||
|
||||
These are checked against each item in the comma-delimited scope field of the
|
||||
current commit's message. E.g. 'fix(foo,bar,baz): ...' => foo, bar and baz
|
||||
|
||||
Each element of this list can be one of:
|
||||
|
||||
- A string, compared literally against the scope's name.
|
||||
- A function predicate, taking two arguments (a scope as a symbol, and a plist
|
||||
containing information about the current commit--see `doom-ci-commit-scopes'
|
||||
for more about its structure). These predicates should:
|
||||
- Return non-nil to immediately pass a scope.
|
||||
- Throw a `user-error' to immediately fail the scope.
|
||||
- Return nil to continue with the checks in this list.
|
||||
- A list, denoting type-specific scopes. Its CAR is the type as a symbol, and
|
||||
its CDR is a nested list of scopes as strings/predicates. E.g.
|
||||
|
||||
'(docs \"faq\" \"install\" check-docs)")
|
||||
|
||||
(defvar doom-ci-commit-rules
|
||||
;; TODO Extract into named functions
|
||||
(list (lambda! (&key subject)
|
||||
"If a fixup/squash commit, don't lint this commit"
|
||||
(when (string-match "^\\(\\(?:fixup\\|squash\\)!\\|FIXUP\\|WIP\\) " subject)
|
||||
(skip! (format "Found %S commit, skipping commit" (match-string 1 subject)))))
|
||||
|
||||
(lambda! (&key type subject)
|
||||
"Test SUBJECT length"
|
||||
(let ((len (length subject)))
|
||||
(cond ((memq type '(bump revert)))
|
||||
((<= len 10)
|
||||
(fail! "Subject is too short (<10) and should be more descriptive"))
|
||||
((<= len 20)
|
||||
(warn! "Subject is short (<20); are you sure it's descriptive enough?"))
|
||||
((> len 72)
|
||||
(fail! "Subject is %d characters, above the 72 maximum"
|
||||
len))
|
||||
((> len 50)
|
||||
(warn! "Subject is %d characters; <=50 is ideal"
|
||||
len)))))
|
||||
|
||||
(lambda! (&key type)
|
||||
"Ensure commit has valid type"
|
||||
(or (memq type doom-ci-commit-types)
|
||||
(if type
|
||||
(fail! "Invalid commit type: %s" type)
|
||||
(fail! "Commit has no detectable type"))))
|
||||
|
||||
(lambda! (&key summary)
|
||||
"Ensure commit has a summary"
|
||||
(when (or (not (stringp summary))
|
||||
(string-blank-p summary))
|
||||
(fail! "Commit has no summary")))
|
||||
|
||||
(lambda! (&key type summary subject)
|
||||
"Ensure summary isn't needlessly capitalized"
|
||||
(and (stringp summary)
|
||||
(string-match-p "^[A-Z][^-A-Z.]" summary)
|
||||
(fail! "%S in summary should not be capitalized"
|
||||
(car (split-string summary " ")))))
|
||||
|
||||
(lambda! (&rest plist &key type scopes)
|
||||
"Ensure scopes are valid"
|
||||
(dolist (scope scopes)
|
||||
(condition-case e
|
||||
(letf! (defun* check-rule (rule)
|
||||
(or (and (stringp rule)
|
||||
(string= rule scope))
|
||||
(and (functionp rule)
|
||||
(funcall rule scope plist))
|
||||
(and (listp rule)
|
||||
(eq type (car rule))
|
||||
(seq-find #'check-rule (cdr rule)))))
|
||||
(or (seq-find #'check-rule doom-ci-commit-scopes)
|
||||
(fail! "Invalid scope: %s" scope)))
|
||||
(user-error (fail! "%s" (error-message-string e))))))
|
||||
|
||||
(lambda! (&key scopes)
|
||||
"Esnure scopes are sorted correctly"
|
||||
(unless (equal scopes (sort (copy-sequence scopes) #'string-lessp))
|
||||
(fail! "Scopes are not in lexicographical order")))
|
||||
|
||||
(lambda! (&key type body)
|
||||
"Enforce 72 character line width for BODY"
|
||||
(catch 'result
|
||||
(with-temp-buffer
|
||||
(save-excursion (insert body))
|
||||
(while (re-search-forward "^[^\n]\\{73,\\}" nil t)
|
||||
(save-excursion
|
||||
(or
|
||||
;; Long bump lines are acceptable
|
||||
(let ((bump-re "\\(https?://.+\\|[^/]+\\)/[^/]+@[a-z0-9]\\{12\\}"))
|
||||
(re-search-backward (format "^%s -> %s$" bump-re bump-re) nil t))
|
||||
;; Long URLs are acceptable
|
||||
(re-search-backward "https?://[^ ]+\\{73,\\}" nil t)
|
||||
;; Lines that start with # or whitespace are comment or
|
||||
;; code blocks.
|
||||
(re-search-backward "^\\(?:#\\| +\\)" nil t)
|
||||
(throw 'result (fail! "Line(s) in commit body exceed 72 characters"))))))))
|
||||
|
||||
(lambda! (&key bang body type)
|
||||
"Ensure ! is accompanied by a 'BREAKING CHANGE:' in BODY"
|
||||
(if bang
|
||||
(cond ((not (string-match-p "^BREAKING CHANGE:" body))
|
||||
(fail! "'!' present in commit type, but missing 'BREAKING CHANGE:' in body"))
|
||||
((not (string-match-p "^BREAKING CHANGE: .+" body))
|
||||
(fail! "'BREAKING CHANGE:' present in commit body, but missing explanation")))
|
||||
(when (string-match-p "^BREAKING CHANGE:" body)
|
||||
(fail! "'BREAKING CHANGE:' present in body, but missing '!' after %S"
|
||||
type))))
|
||||
|
||||
(lambda! (&key type body)
|
||||
"Ensure bump commits have package ref lines"
|
||||
(and (eq type 'bump)
|
||||
(let ((bump-re "\\(?:https?://.+\\|[^/]+\\)/[^/]+@\\([a-z0-9]+\\)"))
|
||||
(not (string-match-p (concat "^" bump-re " -> " bump-re "$")
|
||||
body)))
|
||||
(fail! "Bump commit is missing commit hash diffs")))
|
||||
|
||||
(lambda! (&key body)
|
||||
"Ensure commit hashes in bump lines are 12 characters long"
|
||||
(with-temp-buffer
|
||||
(insert body)
|
||||
(let ((bump-re "\\<\\(?:https?://[^@]+\\|[^/]+\\)/[^/]+@\\([a-z0-9]+\\)")
|
||||
refs)
|
||||
(while (re-search-backward bump-re nil t)
|
||||
(when (/= (length (match-string 1)) 12)
|
||||
(push (match-string 0) refs)))
|
||||
(when refs
|
||||
(fail! "%d commit hash(s) not 12 characters long: %s"
|
||||
(length refs) (string-join (nreverse refs) ", "))))))
|
||||
|
||||
;; TODO Add bump validations for revert: type.
|
||||
(lambda! (&key body trailers)
|
||||
"Validate commit trailers."
|
||||
(let* ((keys (mapcar #'car doom-ci-commit-trailer-keys))
|
||||
(key-re (regexp-opt keys t))
|
||||
(lines
|
||||
;; Scan BODY because invalid trailers won't be in TRAILERS.
|
||||
(save-match-data
|
||||
(and (string-match "\n\\(\n[a-zA-Z][a-zA-Z-]*:? [^ ][^\n]+\\)+\n+\\'" body)
|
||||
(split-string (match-string 0 body) "\n" t)))))
|
||||
(dolist (line lines)
|
||||
(unless (string-match-p (concat "^" key-re ":? [^ ]") line)
|
||||
(fail! "Found %S, expected one of: %s"
|
||||
(truncate-string-to-width (string-trim line) 16 nil nil "…")
|
||||
(string-join keys ", ")))
|
||||
(when (and (string-match "^[^a-zA-Z-]+:? \\(.+\\)$" line)
|
||||
(string-match-p " " (match-string 1 line)))
|
||||
(fail! "%S has multiple references, but should only have one per line"
|
||||
(truncate-string-to-width (string-trim line) 20 nil nil "…")))
|
||||
(when (or (string-match (concat "^" key-re "\\(?:e?[sd]\\|ing\\)? [^ ]") line)
|
||||
(string-match (concat "^\\([a-zA-Z-]+\\) [^ \n]+$") line))
|
||||
(fail! "%S missing colon after %S"
|
||||
(truncate-string-to-width (string-trim line) 16 nil nil "…")
|
||||
(match-string 1 line))))
|
||||
(pcase-dolist (`(,key . ,value) trailers)
|
||||
(if (and (not (memq 'name (cdr (assoc key doom-ci-commit-trailer-keys))))
|
||||
(string-match-p " " value))
|
||||
(fail! "Found %S, but only one value allowed per trailer"
|
||||
(truncate-string-to-width (concat key ": " value) 20 nil nil "…"))
|
||||
(when-let (allowed-types (cdr (assoc key doom-ci-commit-trailer-keys)))
|
||||
(or (cl-loop for type in allowed-types
|
||||
if (cdr (assq type doom-ci-commit-trailer-types))
|
||||
if (string-match-p it value)
|
||||
return t)
|
||||
(fail! "%S expects one of %s, but got %S"
|
||||
key allowed-types value)))))))
|
||||
|
||||
;; TODO Check that bump/revert SUBJECT list: 1) valid modules and 2)
|
||||
;; modules whose files are actually being touched.
|
||||
|
||||
;; TODO Ensure your diff corraborates your SCOPE
|
||||
|
||||
)
|
||||
"A list of validator functions to run against a commit.
|
||||
|
||||
Each function is N-arity and is passed a plist with the following keys:
|
||||
|
||||
:bang
|
||||
(Boolean) If `t', the commit is declared to contain a breaking change.
|
||||
e.g. 'refactor!: this commit breaks everything'
|
||||
:body
|
||||
(String) Contains the whole BODY of a commit message, excluding the
|
||||
TRAILERS.
|
||||
:scopes
|
||||
(List<Symbol>) Contains a list of scopes, as symbols. e.g. with
|
||||
'feat(org,lsp): so on and so forth', this contains '(org lsp).
|
||||
:subject
|
||||
(String) Contains the whole first line of a commit message.
|
||||
:summary
|
||||
(String) Contains the summary following the type and scopes. e.g. In
|
||||
'feat(org): fix X, Y, and Z' the summary is 'fix X, Y, and Z.
|
||||
:trailers
|
||||
(Map<String, String>) Contains an alist of 'KEY: VALUE' trailers, i.e. All
|
||||
Fix, Ref, Close, Revert, etc lines with a valid value. This will be empty if
|
||||
the formatting of a commit's trailers is invalid.
|
||||
:type
|
||||
(Symbol) The type of commit this is. E.g. `feat', `fix', `bump', etc.
|
||||
|
||||
Each function should call `fail!' or `warn!' one or more times, or `skip!'
|
||||
(immediately returns). Each of these lexical functions take the same arguments
|
||||
as `format'.
|
||||
|
||||
Note: warnings are not considered failures.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;;; doom ci
|
||||
(defcli! (:before ci) (&args _)
|
||||
(when-let*
|
||||
((repo-root
|
||||
(if-let* ((result (sh! "git" "rev-parse" "--show-toplevel"))
|
||||
((zerop (car result))))
|
||||
(cdr result)
|
||||
default-directory))
|
||||
(local-config
|
||||
(car (or (doom-glob repo-root "ci.el")
|
||||
(doom-glob doom-private-dir "ci.el")))))
|
||||
(defgroup! :prefix '(doom ci)
|
||||
(load local-config nil t t))
|
||||
(print! (item "Loaded %S") local-config)))
|
||||
|
||||
(defcli! ci (&args _)
|
||||
"Commands that automate development processes."
|
||||
:partial t)
|
||||
|
||||
(defcli! (ci deploy-hooks) ((force ("--force")))
|
||||
"TODO"
|
||||
(let* ((repo-path (sh! "git" "rev-parse" "--show-toplevel"))
|
||||
(repo-path (if (zerop (car repo-path))
|
||||
(cdr repo-path)
|
||||
(user-error "Cannot locate a git repo in %s"
|
||||
(file-relative-name default-directory))))
|
||||
(submodule-p (string-empty-p (cdr (sh! "git" "rev-parse" "show-superproject-working-tree"))))
|
||||
(config-hooks-path (cdr (sh! "git" "config" "core.hooksPath")))
|
||||
(hooks-path (cdr (sh! "git" "rev-parse" "--git-path" "hooks"))))
|
||||
(unless (string-empty-p config-hooks-path)
|
||||
(or force
|
||||
(y-or-n-p
|
||||
(format (concat "Detected non-standard core.hookPath: %S\n\n"
|
||||
"Install Doom's commit-msg and pre-push git hooks anyway?")
|
||||
hooks-path))
|
||||
(user-error "Aborted")))
|
||||
(make-directory hooks-path 'parents)
|
||||
(print-group!
|
||||
(dolist (hook '("commit-msg" "pre-push"))
|
||||
(let* ((hook (doom-path hooks-path hook))
|
||||
(overwrite-p (file-exists-p hook)))
|
||||
(with-temp-file hook
|
||||
(insert "#!/usr/bin/env sh\n"
|
||||
(doom-path doom-emacs-dir "bin/doom")
|
||||
" --no-color ci hook " (file-name-base hook)
|
||||
" \"$@\""))
|
||||
(set-file-modes hook #o700)
|
||||
(print! (success "%s %s")
|
||||
(if overwrite-p "Overwrote" "Created")
|
||||
(path hook)))))))
|
||||
|
||||
(defcli! (ci lint-commits) (from &optional to)
|
||||
"TODO"
|
||||
(with-temp-buffer
|
||||
(insert
|
||||
(cdr (doom-call-process
|
||||
"git" "log"
|
||||
(format "%s...%s" from (or to (concat from "~1"))))))
|
||||
(doom-ci--lint
|
||||
(let (commits)
|
||||
(while (re-search-backward "^commit \\([a-z0-9]\\{40\\}\\)" nil t)
|
||||
(push (cons (match-string 1)
|
||||
(replace-regexp-in-string
|
||||
"^ " ""
|
||||
(save-excursion
|
||||
(buffer-substring-no-properties
|
||||
(search-forward "\n\n")
|
||||
(if (re-search-forward "\ncommit \\([a-z0-9]\\{40\\}\\)" nil t)
|
||||
(match-beginning 0)
|
||||
(point-max))))))
|
||||
commits))
|
||||
commits))))
|
||||
|
||||
;;; TODO
|
||||
(defstub! (ci run-tests))
|
||||
|
||||
;;; doom ci hook
|
||||
(defcli! (ci hook commit-msg) (file)
|
||||
"Run git commit-msg hook.
|
||||
|
||||
Lints the current commit message."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(doom-ci--lint
|
||||
`(("CURRENT" .
|
||||
,(buffer-substring
|
||||
(point-min)
|
||||
(if (re-search-forward "^# Please enter the commit message" nil t)
|
||||
(match-beginning 0)
|
||||
(point-max))))))))
|
||||
|
||||
(defcli! (ci hook pre-push) (remote url)
|
||||
"Run git pre-push hook.
|
||||
|
||||
Prevents pushing if there are unrebased or WIP commits."
|
||||
(with-temp-buffer
|
||||
(let ((z40 (make-string 40 ?0))
|
||||
line error)
|
||||
(while (setq line (ignore-errors (read-from-minibuffer "")))
|
||||
(catch 'continue
|
||||
(seq-let (local-ref local-sha remote-ref remote-sha)
|
||||
(split-string line " ")
|
||||
;; TODO Extract this branch detection to a variable
|
||||
(unless (or (string-match-p "^refs/heads/\\(master\\|main\\)$" remote-ref)
|
||||
(equal local-sha z40))
|
||||
(throw 'continue t))
|
||||
(print-group!
|
||||
(mapc (lambda (commit)
|
||||
(seq-let (hash msg) (split-string commit "\t")
|
||||
(setq error t)
|
||||
(print! (item "%S commit in %s"
|
||||
(car (split-string msg " "))
|
||||
(substring hash 0 12)))))
|
||||
(split-string
|
||||
(cdr (doom-call-process
|
||||
"git" "rev-list"
|
||||
"--grep" (concat "^" (regexp-opt '("WIP" "squash!" "fixup!" "FIXUP") t) " ")
|
||||
"--format=%H\t%s"
|
||||
(if (equal remote-sha z40)
|
||||
local-sha
|
||||
(format "%s..%s" remote-sha local-sha))))
|
||||
"\n" t))
|
||||
(when error
|
||||
(print! (error "Aborting push due to unrebased WIP, squash!, or fixup! commits"))
|
||||
(exit! 1)))))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(cl-defun doom-ci-enforce-scopeless-types (scope (&key type scopes summary &allow-other-keys))
|
||||
"Complain about scoped commit types that shouldn't be scoped."
|
||||
(when (memq type doom-ci-commit-scopeless-types)
|
||||
(user-error "Scopes for %s commits should go after the colon, not before"
|
||||
type)))
|
||||
|
||||
|
||||
(defun doom-ci--parse-commit (commit-msg)
|
||||
(with-temp-buffer
|
||||
(save-excursion (insert commit-msg))
|
||||
(append
|
||||
(let ((end
|
||||
(save-excursion
|
||||
(if (re-search-forward "\n\\(\n[a-zA-Z-]+: [^ ][^\n]+\\)+\n*\\'" nil t)
|
||||
(1+ (match-beginning 0))
|
||||
(point-max)))))
|
||||
`(:subject ,(buffer-substring (point-min) (line-end-position))
|
||||
:body ,(string-trim-right (buffer-substring (line-beginning-position 3) end))
|
||||
:trailers ,(save-match-data
|
||||
(cl-loop with footer = (buffer-substring end (point-max))
|
||||
for line in (split-string footer "\n" t)
|
||||
if (string-match "^\\([a-zA-Z-]+\\): \\(.+\\)$" line)
|
||||
collect (cons (match-string 1 line) (match-string 2 line))))))
|
||||
(save-match-data
|
||||
(when (looking-at "^\\([a-zA-Z0-9_-]+\\)\\(!?\\)\\(?:(\\([^)]+\\))\\)?: \\([^\n]+\\)")
|
||||
`(:type ,(intern (match-string 1))
|
||||
:bang ,(equal (match-string 2) "!")
|
||||
:summary ,(match-string 4)
|
||||
:scopes ,(ignore-errors (split-string (match-string 3) ",")))))
|
||||
(save-excursion
|
||||
(let ((bump-re "\\(\\(?:https?://.+\\|[^/ \n]+\\)/[^/ \n]+@[a-z0-9]\\{12\\}\\)")
|
||||
bumps)
|
||||
(while (re-search-forward (format "^\\s-*\\<%s -> %s\\>" bump-re bump-re) nil t)
|
||||
(cond ((rassoc (match-string 1) bumps)
|
||||
(setcdr (rassoc (match-string 1) bumps) (match-string 2)))
|
||||
((assoc (match-string 2) bumps)
|
||||
(setcar (assoc (match-string 2) bumps) (match-string 1)))
|
||||
((setf (alist-get (match-string 1) bumps nil nil #'equal)
|
||||
(match-string 2)))))
|
||||
`(:bumps ,(cl-sort (delete-dups bumps) #'string-lessp :key #'car)))))))
|
||||
|
||||
(defun doom-ci--parse-bumps (from end)
|
||||
(with-temp-buffer
|
||||
(save-excursion
|
||||
(insert
|
||||
(cdr (doom-call-process "git" "log" "--format=full" "--grep=\\(bump\\|revert\\):"
|
||||
(format "%s...%s" from end)))))
|
||||
(save-match-data
|
||||
(let (packages)
|
||||
(while (let ((bump-re "\\(\\(?:https?://.+\\|[^/ ]+\\)/[^/ ]+@[a-z0-9]\\{12\\}\\)"))
|
||||
(re-search-forward (format "^\\s-*\\<%s -> %s\\>" bump-re bump-re) nil t))
|
||||
(cond ((rassoc (match-string 1) packages)
|
||||
(setcdr (rassoc (match-string 1) packages) (match-string 2)))
|
||||
((assoc (match-string 2) packages)
|
||||
(setcar (assoc (match-string 2) packages) (match-string 1)))
|
||||
((setf (alist-get (match-string 1) packages nil nil #'equal)
|
||||
(match-string 2)))))
|
||||
(cl-sort (delete-dups packages) #'string-lessp :key #'car)))))
|
||||
|
||||
(defun doom-ci--lint (commits)
|
||||
(let ((warnings 0)
|
||||
(failures 0))
|
||||
(print! (start "Linting %d commits" (length commits)))
|
||||
(print-group!
|
||||
(pcase-dolist (`(,ref . ,commitmsg) commits)
|
||||
(let* ((commit (doom-ci--parse-commit commitmsg))
|
||||
(shortref (substring ref 0 7))
|
||||
(subject (plist-get commit :subject)))
|
||||
(cl-block 'linter
|
||||
(letf! ((defun skip! (reason &rest args)
|
||||
(print! (warn "Skipped because: %s") (apply #'format reason args))
|
||||
(cl-return-from 'linter))
|
||||
(defun warn! (reason &rest args)
|
||||
(cl-incf warnings)
|
||||
(print! (warn "%s") (apply #'format reason args)))
|
||||
(defun fail! (reason &rest args)
|
||||
(cl-incf failures)
|
||||
(print! (error "%s") (apply #'format reason args))))
|
||||
(print! (start "%s %s") shortref subject)
|
||||
(print-group!
|
||||
(mapc (doom-rpartial #'apply commit)
|
||||
doom-ci-commit-rules)))))))
|
||||
(let ((issues (+ warnings failures)))
|
||||
(if (= issues 0)
|
||||
(print! (success "There were no issues!"))
|
||||
(if (> warnings 0) (print! (warn "Warnings: %d" warnings)))
|
||||
(if (> failures 0) (print! (warn "Failures: %d" failures)))
|
||||
(print! "\nSee https://discourse.doomemacs.org/git-conventions")
|
||||
(unless (zerop failures)
|
||||
(exit! 1)))
|
||||
t)))
|
||||
|
||||
(provide 'doom-cli-ci)
|
||||
;;; ci.el ends here
|
217
lisp/cli/compile.el
Normal file
217
lisp/cli/compile.el
Normal file
|
@ -0,0 +1,217 @@
|
|||
;;; lisp/cli/commands/byte-compile.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;; None yet!
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! ((compile c))
|
||||
((recompile-p ("-r" "--recompile"))
|
||||
(core-p ("-c" "--core"))
|
||||
(private-p ("-p" "--private"))
|
||||
(verbose-p ("-v" "--verbose")))
|
||||
"Byte-compiles your config or selected modules.
|
||||
|
||||
compile [TARGETS...]
|
||||
compile :core :private lang/python
|
||||
compile feature lang
|
||||
|
||||
Accepts :core and :private as special arguments, which target Doom's core files
|
||||
and your private config files, respectively. To recompile your packages, use
|
||||
'doom build' instead."
|
||||
(doom-cli-compile
|
||||
(if (or core-p private-p)
|
||||
(append (if core-p (doom-glob doom-emacs-dir "init.el"))
|
||||
(if core-p (list doom-core-dir))
|
||||
(if private-p (list doom-private-dir)))
|
||||
(or (y-or-n-p
|
||||
(concat "WARNING: Changes made to your config after compiling it won't take effect until\n"
|
||||
"this command is rerun or you run 'doom clean'! It will also make error backtraces\n"
|
||||
"much more difficult to decipher.\n\n"
|
||||
"If you intend to use it anyway, remember this or it will come back to bite you!\n\n"
|
||||
"Continue anyway?"))
|
||||
(user-error "Aborted"))
|
||||
(append (doom-glob doom-emacs-dir "init.el")
|
||||
(list doom-core-dir)
|
||||
(seq-filter
|
||||
;; Only compile Doom's modules
|
||||
(doom-rpartial #'file-in-directory-p doom-emacs-dir)
|
||||
;; Omit `doom-private-dir', which is always first
|
||||
(cdr (doom-module-load-path)))))
|
||||
recompile-p
|
||||
verbose-p))
|
||||
|
||||
(defcli! clean ()
|
||||
"Delete all *.elc files."
|
||||
(doom-compile-clean))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(cl-defun doom-cli-compile (&optional targets recompile-p verbose-p)
|
||||
"Byte compiles your emacs configuration.
|
||||
|
||||
init.el is always byte-compiled by this.
|
||||
|
||||
If TARGETS is specified, as a list of direcotries
|
||||
|
||||
If MODULES is specified (a list of module strings, e.g. \"lang/php\"), those are
|
||||
byte-compiled. Otherwise, all enabled modules are byte-compiled, including Doom
|
||||
core. It always ignores unit tests and files with `no-byte-compile' enabled.
|
||||
|
||||
WARNING: byte-compilation yields marginal gains and makes debugging new issues
|
||||
difficult. It is recommended you don't use it unless you understand the
|
||||
reprecussions.
|
||||
|
||||
Use `doom-compile-clean' or `make clean' to reverse
|
||||
byte-compilation.
|
||||
|
||||
If RECOMPILE-P is non-nil, only recompile out-of-date files."
|
||||
(let* ((default-directory doom-emacs-dir)
|
||||
(targets (nreverse (delete-dups targets)))
|
||||
;; In case it is changed during compile-time
|
||||
(auto-mode-alist auto-mode-alist)
|
||||
kill-emacs-hook kill-buffer-query-functions)
|
||||
|
||||
(let ((after-load-functions
|
||||
(if (null targets)
|
||||
after-load-functions
|
||||
;; Assemble el files we want to compile, and preserve in the order
|
||||
;; they are loaded in, so we don't run into any scary catch-22s
|
||||
;; while byte-compiling, like missing macros.
|
||||
(cons (let ((target-dirs (seq-filter #'file-directory-p targets)))
|
||||
(lambda (path)
|
||||
(and (not (doom-compile--ignore-file-p path))
|
||||
(seq-find (doom-partial #'file-in-directory-p path)
|
||||
target-dirs)
|
||||
(cl-pushnew path targets))))
|
||||
after-load-functions))))
|
||||
(doom-log "Reloading Doom in preparation for byte-compilation")
|
||||
;; But first we must be sure that Doom and your private config have been
|
||||
;; fully loaded. Which usually aren't so in an noninteractive session.
|
||||
(let ((load-prefer-newer t)
|
||||
(noninteractive t))
|
||||
(require 'doom-start)
|
||||
(quiet! (doom-initialize-packages))
|
||||
(quiet! (doom-initialize-modules))))
|
||||
|
||||
(if (null targets)
|
||||
(print! (item "No targets to %scompile" (if recompile-p "re" "")))
|
||||
(print! (start "%scompiling your config...")
|
||||
(if recompile-p "Re" "Byte-"))
|
||||
|
||||
(dolist (dir
|
||||
(cl-remove-if-not #'file-directory-p targets)
|
||||
(setq targets (cl-remove-if #'file-directory-p targets)))
|
||||
(prependq! targets
|
||||
(doom-files-in
|
||||
dir :match "\\.el" :filter #'doom-compile--ignore-file-p)))
|
||||
|
||||
(print-group!
|
||||
(require 'use-package)
|
||||
(condition-case-unless-debug e
|
||||
(let* ((total-ok 0)
|
||||
(total-fail 0)
|
||||
(total-noop 0)
|
||||
(byte-compile-verbose nil)
|
||||
(byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
|
||||
(byte-compile-dynamic-docstrings t)
|
||||
(use-package-compute-statistics nil)
|
||||
(use-package-defaults use-package-defaults)
|
||||
(use-package-expand-minimally t)
|
||||
(targets (delete-dups targets))
|
||||
(modules (seq-group-by #'doom-module-from-path targets))
|
||||
(total-files (length targets))
|
||||
(total-modules (length modules))
|
||||
(i 0)
|
||||
last-module)
|
||||
;; Prevent packages from being loaded at compile time if they
|
||||
;; don't meet their own predicates.
|
||||
(push (list :no-require t
|
||||
(lambda (_name args)
|
||||
(or (when-let (pred (or (plist-get args :if)
|
||||
(plist-get args :when)))
|
||||
(not (eval pred t)))
|
||||
(when-let (pred (plist-get args :unless))
|
||||
(eval pred t)))))
|
||||
use-package-defaults)
|
||||
(dolist (module-files modules)
|
||||
(cl-incf i)
|
||||
(dolist (target (cdr module-files))
|
||||
(let ((elc-file (byte-compile-dest-file target)))
|
||||
(cl-incf
|
||||
(if (and recompile-p (not (file-newer-than-file-p target elc-file)))
|
||||
total-noop
|
||||
(pcase (if (not (doom-file-cookie-p target "if" t))
|
||||
'no-byte-compile
|
||||
(unless (equal last-module (car module-files))
|
||||
(print! (success "(% 3d/%d) Compiling %s")
|
||||
i total-modules
|
||||
(if-let (m (caar module-files))
|
||||
(format "%s %s module..." m (cdar module-files))
|
||||
(format "%d stand alone elisp files..."
|
||||
(length (cdr module-files))))
|
||||
(caar module-files) (cdar module-files))
|
||||
(setq last-module (car module-files)))
|
||||
(if verbose-p
|
||||
(byte-compile-file target)
|
||||
(quiet! (byte-compile-file target))))
|
||||
(`no-byte-compile
|
||||
(doom-log "(% 3d/%d) Ignored %s" i total-modules target)
|
||||
total-noop)
|
||||
(`nil
|
||||
(print! (error "(% 3d/%d) Failed to compile %s")
|
||||
i total-modules (relpath target))
|
||||
total-fail)
|
||||
(_ total-ok)))))))
|
||||
(print! (class (if (= total-fail 0) 'success 'warn)
|
||||
"%s %d/%d file(s) (%d ignored)")
|
||||
(if recompile-p "Recompiled" "Byte-compiled")
|
||||
total-ok total-files
|
||||
total-noop)
|
||||
(= total-fail 0))
|
||||
((debug error)
|
||||
(print! (error "There were breaking errors.\n\n%s")
|
||||
"Reverting changes...")
|
||||
(signal 'doom-error (list 'byte-compile e))))))))
|
||||
|
||||
(defun doom-compile--ignore-file-p (path)
|
||||
(let ((filename (file-name-nondirectory path)))
|
||||
(or (not (equal (file-name-extension path) "el"))
|
||||
(member filename (list "packages.el" "doctor.el"))
|
||||
(string-prefix-p "." filename)
|
||||
(string-prefix-p "test-" filename)
|
||||
(string-prefix-p "flycheck_" filename)
|
||||
(string-suffix-p ".example.el" filename))))
|
||||
|
||||
(defun doom-compile-clean ()
|
||||
"Delete all the compiled elc files in your Emacs configuration and private
|
||||
module. This does not include your byte-compiled, third party packages.'"
|
||||
(require 'doom-modules)
|
||||
(print! (start "Cleaning .elc files"))
|
||||
(print-group!
|
||||
(cl-loop with default-directory = doom-emacs-dir
|
||||
with success = 0
|
||||
with esc = (if init-file-debug "" "\033[1A")
|
||||
for path
|
||||
in (append (doom-glob doom-emacs-dir "*.elc")
|
||||
(doom-files-in doom-private-dir :match "\\.elc$" :depth 1)
|
||||
(doom-files-in doom-core-dir :match "\\.elc$")
|
||||
(doom-files-in doom-modules-dirs :match "\\.elc$" :depth 4))
|
||||
if (file-exists-p path)
|
||||
do (delete-file path)
|
||||
and do (print! (success "\033[KDeleted %s%s") (relpath path) esc)
|
||||
and do (cl-incf success)
|
||||
finally do
|
||||
(print! (if (> success 0)
|
||||
(success "\033[K%d elc files deleted" success)
|
||||
(item "\033[KNo elc files to clean"))))
|
||||
t))
|
||||
|
||||
(provide 'doom-cli-compile)
|
||||
;;; compile.el ends here
|
296
lisp/cli/doctor.el
Normal file
296
lisp/cli/doctor.el
Normal file
|
@ -0,0 +1,296 @@
|
|||
;;; lisp/cli/doctor.el --- userland heuristics and Emacs diagnostics -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
(defvar doom-doctor--warnings ())
|
||||
(defvar doom-doctor--errors ())
|
||||
|
||||
|
||||
;;
|
||||
;;; DSL
|
||||
|
||||
(defun elc-check-dir (dir)
|
||||
(dolist (file (directory-files-recursively dir "\\.elc$"))
|
||||
(when (file-newer-than-file-p (concat (file-name-sans-extension file) ".el")
|
||||
file)
|
||||
(warn! "%s is out-of-date" (abbreviate-file-name file)))))
|
||||
|
||||
(defmacro assert! (condition message &rest args)
|
||||
`(unless ,condition
|
||||
(error! ,message ,@args)))
|
||||
|
||||
(defmacro error! (&rest args)
|
||||
`(progn (unless inhibit-message (print! (error ,@args)))
|
||||
(push (format! (error ,@args)) doom-doctor--errors)))
|
||||
|
||||
(defmacro warn! (&rest args)
|
||||
`(progn (unless inhibit-message (print! (warn ,@args)))
|
||||
(push (format! (warn ,@args)) doom-doctor--warnings)))
|
||||
|
||||
(defmacro success! (&rest args)
|
||||
`(print! (green ,@args)))
|
||||
|
||||
(defmacro section! (&rest args)
|
||||
`(print! (bold (blue ,@args))))
|
||||
|
||||
(defmacro explain! (&rest args)
|
||||
`(print-group! (print! (fill (string-join (list ,@args) "\n")))))
|
||||
|
||||
|
||||
;;
|
||||
;;; CLI commands
|
||||
|
||||
(defcli! ((doctor doc)) ()
|
||||
"Diagnoses common issues on your system.
|
||||
|
||||
The Doom doctor is essentially one big, self-contained elisp shell script that
|
||||
uses a series of simple heuristics to diagnose common issues on your system.
|
||||
Issues that could intefere with Doom Emacs.
|
||||
|
||||
Doom modules may optionally have a doctor.el file to run their own heuristics
|
||||
in."
|
||||
:benchmark nil
|
||||
(print! "The doctor will see you now...\n")
|
||||
|
||||
;; REVIEW Refactor me
|
||||
(print! (start "Checking your Emacs version..."))
|
||||
(print-group!
|
||||
(cond
|
||||
((string= ".50" (substring emacs-version -3))
|
||||
(error! "Emacs development version detected (%s)" emacs-version)
|
||||
;; There are 2 newlines between each item to fight against
|
||||
;; the (fill-region) call in `doom--output-autofill'
|
||||
(explain! "Doom supports this version, but you are using a development version of Emacs! "
|
||||
"Be prepared for possibly weekly breakages that\n"
|
||||
"\t- you will have to investigate yourself."
|
||||
"\t- might appear, or be solved, on any Emacs update."
|
||||
"\t- might depend subtly on upstream packages updates.\n"
|
||||
"You might need to unpin packages to get a fix for a specific commit of Emacs, "
|
||||
"and you should be ready to downgrade Emacs if something is just not fixable."))
|
||||
(EMACS29+
|
||||
(warn! "Emacs %s detected" emacs-version)
|
||||
(explain! "Doom supports this version, but you are living on the edge! "
|
||||
"Be prepared for breakages in future versions of Emacs."))
|
||||
((< emacs-major-version 27)
|
||||
(error! "Emacs %s detected, Doom only supports 27.1 and newer"
|
||||
emacs-version))))
|
||||
|
||||
(print! (start "Checking for Doom's prerequisites..."))
|
||||
(print-group!
|
||||
(if (not (executable-find "git"))
|
||||
(error! "Couldn't find git on your machine! Doom's package manager won't work.")
|
||||
(save-match-data
|
||||
(let* ((version
|
||||
(cdr (doom-call-process "git" "version")))
|
||||
(version
|
||||
(and (string-match "git version \\([0-9]+\\(?:\\.[0-9]+\\)\\{2\\}\\)" version)
|
||||
(match-string 1 version))))
|
||||
(if version
|
||||
(when (version< version "2.23")
|
||||
(error! "Git %s detected! Doom requires git 2.23 or newer!"
|
||||
version))
|
||||
(warn! "Cannot determine Git version. Doom requires git 2.23 or newer!")))))
|
||||
|
||||
(unless (executable-find "rg")
|
||||
(error! "Couldn't find the `rg' binary; this a hard dependecy for Doom, file searches may not work at all")))
|
||||
|
||||
(print! (start "Checking for Emacs config conflicts..."))
|
||||
(when (file-exists-p "~/.emacs")
|
||||
(warn! "Detected an ~/.emacs file, which may prevent Doom from loading")
|
||||
(explain! "If Emacs finds an ~/.emacs file, it will ignore ~/.emacs.d, where Doom is "
|
||||
"typically installed. If you're seeing a vanilla Emacs splash screen, this "
|
||||
"may explain why. If you use Chemacs, you may ignore this warning."))
|
||||
|
||||
(print! (start "Checking for great Emacs features..."))
|
||||
(unless (functionp 'json-serialize)
|
||||
(warn! "Emacs was not built with native JSON support")
|
||||
(explain! "Users will see a substantial performance gain by building Emacs with "
|
||||
"jansson support (i.e. a native JSON library), particularly LSP users. "
|
||||
"You must install a prebuilt Emacs binary with this included, or compile "
|
||||
"Emacs with the --with-json option."))
|
||||
(when EMACS28+
|
||||
(unless NATIVECOMP
|
||||
(warn! "Emacs was not built with native compilation support")
|
||||
(explain! "Users will see a substantial performance gain by building Emacs with "
|
||||
"native compilation support, availible in emacs 28+."
|
||||
"You must install a prebuilt Emacs binary with this included, or compile "
|
||||
"Emacs with the --with-native-compilation option.")))
|
||||
|
||||
(print! (start "Checking for private config conflicts..."))
|
||||
(let* ((xdg-dir (concat (or (getenv "XDG_CONFIG_HOME")
|
||||
"~/.config")
|
||||
"/doom/"))
|
||||
(doom-dir (or (getenv "DOOMDIR")
|
||||
"~/.doom.d/"))
|
||||
(dir (if (file-directory-p xdg-dir)
|
||||
xdg-dir
|
||||
doom-dir)))
|
||||
(when (file-equal-p dir doom-emacs-dir)
|
||||
(print! (error "Doom was cloned to %S, not ~/.emacs.d or ~/.config/emacs"
|
||||
(path dir)))
|
||||
(explain! "Doom's source and your private Doom config have to live in separate directories. "
|
||||
"Putting them in the same directory (without changing the DOOMDIR environment "
|
||||
"variable) will cause errors on startup."))
|
||||
(when (and (not (file-equal-p xdg-dir doom-dir))
|
||||
(file-directory-p xdg-dir)
|
||||
(file-directory-p doom-dir))
|
||||
(print! (warn "Detected two private configs, in %s and %s")
|
||||
(abbreviate-file-name xdg-dir)
|
||||
doom-dir)
|
||||
(explain! "The second directory will be ignored, as it has lower precedence.")))
|
||||
|
||||
(print! (start "Checking for stale elc files..."))
|
||||
(elc-check-dir doom-emacs-dir)
|
||||
|
||||
(print! (start "Checking for problematic git global settings..."))
|
||||
(if (executable-find "git")
|
||||
(when (zerop (car (doom-call-process "git" "config" "--global" "--get-regexp" "^url\\.git://github\\.com")))
|
||||
(warn! "Detected insteadOf rules in your global gitconfig.")
|
||||
(explain! "Doom's package manager heavily relies on git. In particular, many of its packages "
|
||||
"are hosted on github. Rewrite rules like these will break it:\n\n"
|
||||
" [url \"git://github.com\"]\n"
|
||||
" insteadOf = https://github.com\n\n"
|
||||
"Please remove them from your gitconfig or use a conditional includeIf rule to "
|
||||
"only apply your rewrites to specific repositories. See "
|
||||
"'https://git-scm.com/docs/git-config#_includes' for more information."))
|
||||
(error! "Couldn't find the `git' binary; this a hard dependecy for Doom!"))
|
||||
|
||||
(print! (start "Checking Doom Emacs..."))
|
||||
(condition-case-unless-debug ex
|
||||
(print-group!
|
||||
(let ((noninteractive nil)
|
||||
kill-emacs-query-functions
|
||||
kill-emacs-hook)
|
||||
(defvar doom-reloading-p nil)
|
||||
(require 'doom-start)
|
||||
(doom-initialize-packages))
|
||||
|
||||
(print! (success "Initialized Doom Emacs %s") doom-version)
|
||||
(print!
|
||||
(if (hash-table-p doom-modules)
|
||||
(success "Detected %d modules" (hash-table-count doom-modules))
|
||||
(warn "Failed to load any modules. Do you have an private init.el?")))
|
||||
|
||||
(print! (success "Detected %d packages") (length doom-packages))
|
||||
|
||||
(print! (start "Checking Doom core for irregularities..."))
|
||||
(print-group!
|
||||
;; Check for oversized problem files in cache that may cause unusual/tremendous
|
||||
;; delays or freezing. This shouldn't happen often.
|
||||
(dolist (file (list "savehist" "projectile.cache"))
|
||||
(when-let (size (ignore-errors (doom-file-size file doom-cache-dir)))
|
||||
(when (> size 1048576) ; larger than 1mb
|
||||
(warn! "%s is too large (%.02fmb). This may cause freezes or odd startup delays"
|
||||
file (/ size 1024 1024.0))
|
||||
(explain! "Consider deleting it from your system (manually)"))))
|
||||
|
||||
(unless (ignore-errors (executable-find doom-projectile-fd-binary))
|
||||
(warn! "Couldn't find the `fd' binary; project file searches will be slightly slower"))
|
||||
|
||||
(require 'projectile)
|
||||
(when (projectile-project-root "~")
|
||||
(warn! "Your $HOME is recognized as a project root")
|
||||
(explain! "Emacs will assume $HOME is the root of any project living under $HOME. If this isn't\n"
|
||||
"desired, you will need to remove \".git\" from `projectile-project-root-files-bottom-up'\n"
|
||||
"(a variable), e.g.\n\n"
|
||||
" (after! projectile\n"
|
||||
" (setq projectile-project-root-files-bottom-up\n"
|
||||
" (remove \".git\" projectile-project-root-files-bottom-up)))"))
|
||||
|
||||
;; There should only be one
|
||||
(when (and (file-equal-p doom-private-dir "~/.config/doom")
|
||||
(file-directory-p "~/.doom.d"))
|
||||
(print! (warn "Both %S and '~/.doom.d' exist on your system")
|
||||
(path doom-private-dir))
|
||||
(explain! "Doom will only load one of these (~/.config/doom takes precedence). Possessing\n"
|
||||
"both is rarely intentional; you should one or the other."))
|
||||
|
||||
;; Check for fonts
|
||||
(if (not (executable-find "fc-list"))
|
||||
(warn! "Warning: unable to detect fonts because fontconfig isn't installed")
|
||||
;; all-the-icons fonts
|
||||
(when (and (pcase system-type
|
||||
(`gnu/linux (concat (or (getenv "XDG_DATA_HOME")
|
||||
"~/.local/share")
|
||||
"/fonts/"))
|
||||
(`darwin "~/Library/Fonts/"))
|
||||
(require 'all-the-icons nil t))
|
||||
(with-temp-buffer
|
||||
(let ((errors 0))
|
||||
(cl-destructuring-bind (status . output)
|
||||
(doom-call-process "fc-list" "" "file")
|
||||
(if (not (zerop status))
|
||||
(print! (error "There was an error running `fc-list'. Is fontconfig installed correctly?"))
|
||||
(insert (cdr (doom-call-process "fc-list" "" "file")))
|
||||
(dolist (font all-the-icons-font-names)
|
||||
(if (save-excursion (re-search-backward font nil t))
|
||||
(success! "Found font %s" font)
|
||||
(print! (warn "Warning: couldn't find %S font") font)))
|
||||
(when (> errors 0)
|
||||
(explain! "Some all-the-icons fonts were missing.\n\n"
|
||||
"You can install them by running `M-x all-the-icons-install-fonts' within Emacs.\n"
|
||||
"This could also mean you've installed them in non-standard locations, in which "
|
||||
"case feel free to ignore this warning.")))))))))
|
||||
|
||||
(print! (start "Checking for stale elc files in your DOOMDIR..."))
|
||||
(when (file-directory-p doom-private-dir)
|
||||
(print-group!
|
||||
(elc-check-dir doom-private-dir)))
|
||||
|
||||
(when doom-modules
|
||||
(print! (start "Checking your enabled modules..."))
|
||||
(advice-add #'require :around #'doom-shut-up-a)
|
||||
(maphash (lambda (key plist)
|
||||
(let (doom-local-errors
|
||||
doom-local-warnings)
|
||||
(let (doom-doctor--errors
|
||||
doom-doctor--warnings)
|
||||
(condition-case-unless-debug ex
|
||||
(let ((doctor-file (doom-module-path (car key) (cdr key) "doctor.el"))
|
||||
(packages-file (doom-module-path (car key) (cdr key) "packages.el")))
|
||||
(cl-loop with doom-output-indent = 6
|
||||
for name in (let (doom-packages
|
||||
doom-disabled-packages)
|
||||
(load packages-file 'noerror 'nomessage)
|
||||
(mapcar #'car doom-packages))
|
||||
unless (or (doom-package-get name :disable)
|
||||
(eval (doom-package-get name :ignore))
|
||||
(plist-member (doom-package-get name :recipe) :local-repo)
|
||||
(locate-library (symbol-name name))
|
||||
(doom-package-built-in-p name)
|
||||
(doom-package-installed-p name))
|
||||
do (print! (error "Missing emacs package: %S") name))
|
||||
(let ((inhibit-message t))
|
||||
(load doctor-file 'noerror 'nomessage)))
|
||||
(file-missing (error! "%s" (error-message-string ex)))
|
||||
(error (error! "Syntax error: %s" ex)))
|
||||
(when (or doom-doctor--errors doom-doctor--warnings)
|
||||
(print-group!
|
||||
(print! (start (bold "%s %s")) (car key) (cdr key))
|
||||
(print! "%s" (string-join (append doom-doctor--errors doom-doctor--warnings) "\n")))
|
||||
(setq doom-local-errors doom-doctor--errors
|
||||
doom-local-warnings doom-doctor--warnings)))
|
||||
(appendq! doom-doctor--errors doom-local-errors)
|
||||
(appendq! doom-doctor--warnings doom-local-warnings)))
|
||||
doom-modules)))
|
||||
(error
|
||||
(warn! "Attempt to load DOOM failed\n %s\n"
|
||||
(or (cdr-safe ex) (car ex)))
|
||||
(setq doom-modules nil)))
|
||||
|
||||
;; Final report
|
||||
(terpri)
|
||||
(dolist (msg (list (list doom-doctor--warnings "warning" 'yellow)
|
||||
(list doom-doctor--errors "error" 'red)))
|
||||
(when (car msg)
|
||||
(print! (color (nth 2 msg)
|
||||
(if (cdr msg)
|
||||
"There are %d %ss!"
|
||||
"There is %d %s!")
|
||||
(length (car msg)) (nth 1 msg)))))
|
||||
(unless (or doom-doctor--errors doom-doctor--warnings)
|
||||
(success! "Everything seems fine, happy Emacs'ing!"))
|
||||
(exit! :pager? "+G"))
|
||||
|
||||
(provide 'doom-cli-doctor)
|
||||
;;; doctor.el ends here
|
140
lisp/cli/env.el
Normal file
140
lisp/cli/env.el
Normal file
|
@ -0,0 +1,140 @@
|
|||
;;; lisp/cli/env.el --- envvar file generator -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
(defvar doom-env-file (doom-path doom-profile-data-dir "env")
|
||||
"The location of your envvar file, generated by `doom env`.
|
||||
|
||||
This file contains environment variables scraped from your shell environment,
|
||||
which is loaded at startup (if it exists). This is helpful if Emacs can't
|
||||
\(easily) be launched from the correct shell session (particularly for MacOS
|
||||
users).")
|
||||
|
||||
(defvar doom-env-deny
|
||||
'(;; Unix/shell state that shouldn't be persisted
|
||||
"^HOME$" "^\\(OLD\\)?PWD$" "^SHLVL$" "^PS1$" "^R?PROMPT$" "^TERM\\(CAP\\)?$"
|
||||
"^USER$" "^GIT_CONFIG" "^INSIDE_EMACS$"
|
||||
;; X server or services' variables that shouldn't be persisted
|
||||
"^DISPLAY$" "^DBUS_SESSION_BUS_ADDRESS$" "^XAUTHORITY$" "^XDG_SESSION_TYPE$"
|
||||
;; Windows+WSL envvars that shouldn't be persisted
|
||||
"^WSL_INTEROP$"
|
||||
;; ssh and gpg variables (likely to become stale)
|
||||
"^SSH_\\(AUTH_SOCK\\|AGENT_PID\\)$" "^\\(SSH\\|GPG\\)_TTY$"
|
||||
"^GPG_AGENT_INFO$"
|
||||
;; Internal Doom envvars
|
||||
"^DEBUG$" "^INSECURE$" "^\\(EMACS\\|DOOM\\)DIR$" "^__")
|
||||
"Environment variables to omit from envvar files.
|
||||
|
||||
Each string is a regexp, matched against variable names to omit from
|
||||
`doom-env-file'.")
|
||||
|
||||
(defvar doom-env-allow '()
|
||||
"Environment variables to include in envvar files.
|
||||
|
||||
This overrules `doom-env-deny'. Each string is a regexp, matched against
|
||||
variable names to omit from `doom-env-file'.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! env
|
||||
((allow-only ("--allow-all"))
|
||||
(deny-only ("--deny-all"))
|
||||
(output-file ("-o" path) "Write envvar file to non-standard PATH.")
|
||||
;; TODO (refresh? ("-r" "--refresh"))
|
||||
&multiple
|
||||
(rules ("-a" "--allow" "-d" "--deny" regexp) "Allow/deny envvars that match REGEXP"))
|
||||
"(Re)generates envvars file from your shell environment.
|
||||
|
||||
The envvars file is created by scraping the current shell environment into
|
||||
newline-delimited KEY=VALUE pairs. Typically by running '$SHELL -ic env' (or
|
||||
'$SHELL -c set' on windows). Doom loads this file at startup (if it exists) to
|
||||
ensure Emacs mirrors your shell environment (particularly to ensure PATH and
|
||||
SHELL are correctly set).
|
||||
|
||||
This is useful in cases where you cannot guarantee that Emacs (or the daemon)
|
||||
will be launched from the correct environment (e.g. on MacOS or through certain
|
||||
app launchers on Linux).
|
||||
|
||||
This file is automatically regenerated when you run this command or 'doom sync'.
|
||||
However, 'doom sync' will only regenerate this file if it exists.
|
||||
|
||||
Why this over exec-path-from-shell?
|
||||
|
||||
1. `exec-path-from-shell' spawns (at least) one process at startup to scrape
|
||||
your shell environment. This can be arbitrarily slow depending on the
|
||||
user's shell configuration. A single program (like pyenv or nvm) or config
|
||||
framework (like oh-my-zsh) could undo all of Doom's startup optimizations
|
||||
in one fell swoop.
|
||||
|
||||
2. `exec-path-from-shell' only scrapes some state from your shell. You have to
|
||||
be proactive in order to get it to capture all the envvars relevant to your
|
||||
development environment.
|
||||
|
||||
I'd rather it inherit your shell environment /correctly/ (and /completely/)
|
||||
or not at all. It frontloads the debugging process rather than hiding it
|
||||
until you least want to deal with it."
|
||||
(let ((env-file (doom-path (or output-file doom-env-file))))
|
||||
(with-temp-file env-file
|
||||
(setq-local coding-system-for-write 'utf-8-unix)
|
||||
(print! (start "%s envvars file")
|
||||
(if (file-exists-p env-file)
|
||||
"Regenerating"
|
||||
"Generating"))
|
||||
(print-group!
|
||||
(goto-char (point-min))
|
||||
(insert
|
||||
";; -*- mode: lisp-interaction; coding: utf-8-unix; -*-\n"
|
||||
";; ---------------------------------------------------------------------------\n"
|
||||
";; This file was auto-generated by `doom env'. It contains a list of environment\n"
|
||||
";; variables scraped from your default shell (based on your settings for \n"
|
||||
";; `doom-env-allow' and `doom-env-deny').\n"
|
||||
";;\n"
|
||||
(if (file-equal-p env-file doom-env-file)
|
||||
(concat ";; It is NOT safe to edit this file. Changes will be overwritten next time you\n"
|
||||
";; run 'doom sync'. To create a safe-to-edit envvar file use:\n;;\n"
|
||||
";; doom env -o ~/.doom.d/myenv\n;;\n"
|
||||
";; And load it with (doom-load-envvars-file \"~/.doom.d/myenv\").\n")
|
||||
(concat ";; This file is safe to edit by hand, but needs to be loaded manually with:\n;;\n"
|
||||
";; (doom-load-envvars-file \"path/to/this/file\")\n;;\n"
|
||||
";; Use 'doom env -o path/to/this/file' to regenerate it."))
|
||||
"\n")
|
||||
;; We assume that this noninteractive session was spawned from the user's
|
||||
;; interactive shell, so simply dump `process-environment' to a file.
|
||||
;;
|
||||
;; This should be well-formatted, in case humans want to hand-modify it.
|
||||
(let* ((denylist (remq nil (append (if deny-only '(".")) (list allow-only) doom-env-deny)))
|
||||
(allowlist (remq nil (append (if allow-only '(".")) (list deny-only) doom-env-allow))))
|
||||
(dolist (rule rules)
|
||||
(push (cdr rule) (if (member (car rule) '("-a" "--allow"))
|
||||
allowlist
|
||||
denylist)))
|
||||
(insert "(")
|
||||
(dolist (env (get 'process-environment 'initial-value))
|
||||
(catch 'skip
|
||||
(let* ((var (car (split-string env "=")))
|
||||
(pred (doom-rpartial #'string-match-p var)))
|
||||
(when (seq-find pred denylist)
|
||||
(if (seq-find pred allowlist)
|
||||
(doom-log "Whitelisted %s" var)
|
||||
(doom-log "Ignored %s" var)
|
||||
(throw 'skip t)))
|
||||
(insert (prin1-to-string env) "\n "))))
|
||||
(insert ")"))
|
||||
(print! (success "Generated %s") (path env-file))
|
||||
t))))
|
||||
|
||||
(defcli! (env (clear c)) ()
|
||||
"Deletes the default envvar file."
|
||||
(let ((env-file (abbreviate-file-name doom-env-file)))
|
||||
(unless (file-exists-p env-file)
|
||||
(user-error "No envvar file to delete: %s" env-file))
|
||||
(delete-file env-file)
|
||||
(print! (success "Deleted %s") (path env-file))))
|
||||
|
||||
(provide 'doom-cli-env)
|
||||
;;; env.el ends here
|
466
lisp/cli/help.el
Normal file
466
lisp/cli/help.el
Normal file
|
@ -0,0 +1,466 @@
|
|||
;;; lisp/cli/help.el -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This file defines special commands that the Doom CLI will invoke when a
|
||||
;; command is passed with -?, --help, or --version. They can also be aliased to
|
||||
;; a sub-command to make more of its capabilities accessible to users, with:
|
||||
;;
|
||||
;; (defalias! (myscript (help h)) (:help))
|
||||
;;
|
||||
;; You can define your own command-specific help handlers, e.g.
|
||||
;;
|
||||
;; (defcli! (:help myscript subcommand) () ...)
|
||||
;;
|
||||
;; And it will be invoked instead of the generic one.
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
(defvar doom-help-commands '("%p %c {-?,--help}")
|
||||
"A list of help commands recognized for the running script.
|
||||
|
||||
Recognizes %p (for the prefix) and %c (for the active command).")
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! (:root :help)
|
||||
((localonly? ("-g" "--no-global") "Hide global options")
|
||||
(manpage? ("--manpage") "Generate in manpage format")
|
||||
(commands? ("--commands") "List all known commands")
|
||||
&multiple
|
||||
(sections ("--synopsis" "--subcommands" "--similar" "--envvars"
|
||||
"--postamble")
|
||||
"Show only the specified sections.")
|
||||
&context context
|
||||
&args command)
|
||||
"Show documentation for a Doom CLI command.
|
||||
|
||||
OPTIONS:
|
||||
--synopsis, --subcommands, --similar, --envvars, --postamble
|
||||
TODO"
|
||||
(doom-cli-load-all)
|
||||
(when (doom-cli-context-error context)
|
||||
(terpri))
|
||||
(let* ((command (cons (doom-cli-context-prefix context) command))
|
||||
(cli (doom-cli-get command t))
|
||||
(rcli (doom-cli-get cli))
|
||||
(fallbackcli (cl-loop with targets = (doom-cli--command-expand (butlast command) t)
|
||||
for cmd in (cons command targets)
|
||||
if (doom-cli-get cmd t)
|
||||
return it)))
|
||||
(cond (commands?
|
||||
(let ((cli (or cli (doom-cli-get (doom-cli-context-prefix context)))))
|
||||
(print! "Commands under '%s':\n%s"
|
||||
(doom-cli-command-string cli)
|
||||
(indent (doom-cli-help--render-commands
|
||||
(or (doom-cli-subcommands cli)
|
||||
(user-error "No commands found"))
|
||||
:prefix (doom-cli-command cli)
|
||||
:inline? t
|
||||
:docs? t)))))
|
||||
((null sections)
|
||||
(if (null cli)
|
||||
(signal 'doom-cli-command-not-found-error command)
|
||||
(doom-cli-help--print cli context manpage? localonly?)
|
||||
(exit! :pager?)))
|
||||
(t
|
||||
(dolist (section sections)
|
||||
(unless (equal section (car sections)) (terpri))
|
||||
(pcase section
|
||||
("--synopsis"
|
||||
(print! "%s" (doom-cli-help--render-synopsis
|
||||
(doom-cli-help--synopsis cli)
|
||||
"Usage: ")))
|
||||
("--subcommands"
|
||||
(print! "%s\n%s" (bold "Available commands:")
|
||||
(indent (doom-cli-help--render-commands
|
||||
(doom-cli-subcommands rcli 1)
|
||||
:prefix command
|
||||
:grouped? t
|
||||
:docs? t)
|
||||
doom-print-indent-increment)))
|
||||
("--similar"
|
||||
(unless command
|
||||
(user-error "No command specified"))
|
||||
(let ((similar (doom-cli-help-similar-commands command 0.4)))
|
||||
(print! "Similar commands:")
|
||||
(if (not similar)
|
||||
(print! (indent (warn "Can't find any!")))
|
||||
(dolist (command (seq-take similar 10))
|
||||
(print! (indent (item "(%d%%) %s"))
|
||||
(* (car command) 100)
|
||||
(doom-cli-command-string (cdr command)))))))
|
||||
("--envvars"
|
||||
(let* ((key "ENVIRONMENT VARIABLES")
|
||||
(clis (if command (doom-cli-find command) (hash-table-values doom-cli--table)))
|
||||
(clis (seq-remove #'doom-cli-alias clis))
|
||||
(clis (seq-filter (fn! (cdr (assoc key (doom-cli-docs %)))) clis))
|
||||
(clis (seq-group-by #'doom-cli-command clis)))
|
||||
(print! "List of environment variables for %s:\n" command)
|
||||
(if (null clis)
|
||||
(print! (indent "None!"))
|
||||
(dolist (group clis)
|
||||
(print! (bold "%s%s:"
|
||||
(doom-cli-command-string (car group))
|
||||
(if (doom-cli-fn (doom-cli-get (car group)))
|
||||
"" " *")))
|
||||
(dolist (cli (cdr group))
|
||||
(print! (indent "%s") (markup (cdr (assoc key (doom-cli-docs cli))))))))))
|
||||
("--postamble"
|
||||
(print! "See %s for documentation."
|
||||
(join (cl-loop with spec =
|
||||
`((?p . ,(doom-cli-context-prefix context))
|
||||
(?c . ,(doom-cli-command-string (cdr (doom-cli-command (or cli fallbackcli))))))
|
||||
for cmd in doom-help-commands
|
||||
for formatted = (trim (format-spec cmd spec))
|
||||
collect (replace-regexp-in-string
|
||||
" +" " " (format "'%s'" formatted)))
|
||||
" or ")))))))))
|
||||
|
||||
(defcli! (:root :version)
|
||||
((simple? ("--simple"))
|
||||
&context context)
|
||||
"Show installed versions of Doom, Doom modules, and Emacs."
|
||||
(doom/version)
|
||||
(unless simple?
|
||||
(terpri)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (doom-path doom-emacs-dir "LICENSE"))
|
||||
(re-search-forward "^Copyright (c) ")
|
||||
(print! "%s\n" (trim (thing-at-point 'line t)))
|
||||
(print! (p "Doom Emacs uses the MIT license and is provided without warranty "
|
||||
"of any kind. You may redistribute and modify copies if "
|
||||
"given proper attribution. See the LICENSE file for details.")))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(defun doom-cli-help (cli)
|
||||
"Return an alist of documentation summarizing CLI (a `doom-cli')."
|
||||
(let* ((rcli (doom-cli-get cli))
|
||||
(docs (doom-cli-docs rcli)))
|
||||
`((command . ,(doom-cli-command-string cli))
|
||||
(summary . ,(or (cdr (assoc "SUMMARY" docs)) "TODO"))
|
||||
(description . ,(or (cdr (assoc "MAIN" docs)) "TODO"))
|
||||
(synopsis . ,(doom-cli-help--synopsis cli))
|
||||
(arguments . ,(doom-cli-help--arguments rcli))
|
||||
(options . ,(doom-cli-help--options rcli))
|
||||
(commands . ,(doom-cli-subcommands cli 1))
|
||||
(sections . ,(seq-filter #'cdr (cddr docs))))))
|
||||
|
||||
(defun doom-cli-help-similar-commands (command &optional maxscore)
|
||||
"Return N commands that are similar to COMMAND."
|
||||
(seq-take-while
|
||||
(fn! (>= (car %) (or maxscore 0.0)))
|
||||
(seq-sort-by
|
||||
#'car #'>
|
||||
(cl-loop with prefix = (seq-find #'doom-cli-get (nreverse (doom-cli--command-expand command t)))
|
||||
with input = (doom-cli-command-string (cdr (doom-cli--command command t)))
|
||||
for command in (hash-table-keys doom-cli--table)
|
||||
if (doom-cli-fn (doom-cli-get command))
|
||||
if (equal prefix (seq-take command (length prefix)))
|
||||
collect (cons (doom-cli-help--similarity
|
||||
input (doom-cli-command-string (cdr command)))
|
||||
command)))))
|
||||
|
||||
(defun doom-cli-help--similarity (s1 s2)
|
||||
;; Ratcliff-Obershelp similarity
|
||||
(let* ((s1 (downcase s1))
|
||||
(s2 (downcase s2))
|
||||
(s1len (length s1))
|
||||
(s2len (length s2)))
|
||||
(if (or (zerop s1len)
|
||||
(zerop s2len))
|
||||
0.0
|
||||
(/ (let ((i 0) (j 0) (score 0) jlast)
|
||||
(while (< i s1len)
|
||||
(unless jlast (setq jlast j))
|
||||
(if (and (< j s2len)
|
||||
(= (aref s1 i) (aref s2 j)))
|
||||
(progn (cl-incf score)
|
||||
(cl-incf i)
|
||||
(cl-incf j))
|
||||
(setq m 0)
|
||||
(cl-incf j)
|
||||
(when (>= j s2len)
|
||||
(setq j (or jlast j)
|
||||
jlast nil)
|
||||
(cl-incf i))))
|
||||
(* 2.0 score))
|
||||
(+ (length s1)
|
||||
(length s2))))))
|
||||
|
||||
;;; Help: printers
|
||||
;; TODO Parameterize optional args with `cl-defun'
|
||||
(defun doom-cli-help--print (cli context &optional manpage? noglobal?)
|
||||
"Write CLI's documentation in a manpage-esque format to stdout."
|
||||
(let-alist (doom-cli-help cli)
|
||||
(let* ((alist
|
||||
`(,@(if manpage?
|
||||
`((nil . ,(let* ((title (cadr (member "--load" command-line-args)))
|
||||
(width (floor (/ (- (doom-cli-context-width context)
|
||||
(length title))
|
||||
2.0))))
|
||||
;; FIXME Who am I fooling?
|
||||
(format (format "%%-%ds%%s%%%ds" width width)
|
||||
"DOOM(1)" title "DOOM(1)")))
|
||||
("NAME" . ,(concat .command " - " .summary))
|
||||
("SYNOPSIS" . ,(doom-cli-help--render-synopsis .synopsis nil t))
|
||||
("DESCRIPTION" . ,.description))
|
||||
`((nil . ,(doom-cli-help--render-synopsis .synopsis "Usage: "))
|
||||
(nil . ,(string-join (seq-remove #'string-empty-p (list .summary .description))
|
||||
"\n\n"))))
|
||||
("ARGUMENTS" . ,(doom-cli-help--render-arguments .arguments))
|
||||
("COMMANDS"
|
||||
. ,(doom-cli-help--render-commands
|
||||
.commands :prefix (doom-cli-command cli) :grouped? t :docs? t))
|
||||
("OPTIONS"
|
||||
. ,(doom-cli-help--render-options
|
||||
(if (or (not (doom-cli-fn cli)) noglobal?)
|
||||
`(,(assq 'local .options))
|
||||
.options)
|
||||
cli))))
|
||||
(command (doom-cli-command cli)))
|
||||
(letf! (defun printsection (section)
|
||||
(print! "%s\n"
|
||||
(if (null section)
|
||||
(dark "TODO")
|
||||
(markup
|
||||
(format-spec
|
||||
section `((?p . ,(car command))
|
||||
(?c . ,(doom-cli-command-string (cdr command))))
|
||||
'ignore)))))
|
||||
(pcase-dolist (`(,label . ,contents) alist)
|
||||
(when (and contents (not (string-blank-p contents)))
|
||||
(when label
|
||||
(print! (bold "%s%s") label (if manpage? "" ":")))
|
||||
(print-group-if! label (printsection contents))))
|
||||
(pcase-dolist (`(,label . ,contents) .sections)
|
||||
(when (and contents (not (assoc label alist)))
|
||||
(print! (bold "%s:") label)
|
||||
(print-group! (printsection contents))))))))
|
||||
|
||||
;;; Help: synopsis
|
||||
(defun doom-cli-help--synopsis (cli &optional all-options?)
|
||||
(let* ((rcli (doom-cli-get cli))
|
||||
(opts (doom-cli-help--options rcli))
|
||||
(opts (mapcar #'car (if all-options? (mapcan #'cdr opts) (alist-get 'local opts))))
|
||||
(opts (cl-loop for opt in opts
|
||||
for args = (cdar opt)
|
||||
for switches = (mapcar #'car opt)
|
||||
for multi? = (member "..." args)
|
||||
if args
|
||||
collect (format (if multi? "[%s %s]..." "[%s %s]")
|
||||
(string-join switches "|")
|
||||
(string-join (remove "..." args) "|"))
|
||||
else collect (format "[%s]" (string-join switches "|"))))
|
||||
(args (doom-cli-arguments rcli))
|
||||
(subcommands? (doom-cli-subcommands rcli 1 :predicate? t)))
|
||||
`((command . ,(doom-cli-command cli))
|
||||
(options ,@opts)
|
||||
(required ,@(mapcar (fn! (upcase (format "`%s'" %))) (if subcommands? '(command) (alist-get '&required args))))
|
||||
(optional ,@(mapcar (fn! (upcase (format "[`%s']" %)))(alist-get '&optional args)))
|
||||
(rest ,@(mapcar (fn! (upcase (format "[`%s'...]" %))) (if subcommands? '(args) (alist-get '&args args)))))))
|
||||
|
||||
(defun doom-cli-help--render-synopsis (synopsis &optional prefix)
|
||||
(let-alist synopsis
|
||||
(let ((doom-print-indent 0)
|
||||
(prefix (or prefix ""))
|
||||
(command (doom-cli-command-string .command)))
|
||||
(string-trim-right
|
||||
(format! "%s\n\n"
|
||||
(fill (concat (bold prefix)
|
||||
(format "%s " command)
|
||||
(markup
|
||||
(join (append .options
|
||||
(and .options
|
||||
(or .required
|
||||
.optional
|
||||
.rest)
|
||||
(list (dark "[--]")))
|
||||
.required
|
||||
.optional
|
||||
.rest))))
|
||||
80 (1+ (length (concat prefix command)))))))))
|
||||
|
||||
;;; Help: arguments
|
||||
(defun doom-cli-help--arguments (cli &optional all?)
|
||||
(doom-cli-help--parse-docs (doom-cli-find cli t) "ARGUMENTS"))
|
||||
|
||||
(defun doom-cli-help--render-arguments (arguments)
|
||||
(mapconcat (lambda (arg)
|
||||
(format! "%-20s\n%s"
|
||||
(underscore (car arg))
|
||||
(indent (if (equal (cdr arg) "TODO")
|
||||
(dark (cdr arg))
|
||||
(cdr arg))
|
||||
doom-print-indent-increment)))
|
||||
arguments
|
||||
"\n"))
|
||||
|
||||
;;; Help: commands
|
||||
(cl-defun doom-cli-help--render-commands (commands &key prefix grouped? docs? (inline? t))
|
||||
(with-temp-buffer
|
||||
(let* ((doom-print-indent 0)
|
||||
(commands (seq-group-by (fn! (if grouped? (doom-cli-prop (doom-cli-get % t) :group))) (nreverse commands)))
|
||||
(toplevel (assq nil commands))
|
||||
(rest (remove toplevel commands))
|
||||
(drop (if prefix (length prefix) 0))
|
||||
(minwidth
|
||||
(apply
|
||||
#'max (or (cl-loop for cmd in (apply #'append (mapcar #'cdr commands))
|
||||
for cmd = (seq-drop cmd drop)
|
||||
collect (length (doom-cli-command-string cmd)))
|
||||
(list 15))))
|
||||
(ellipsis (doom-print--style 'dark " […]"))
|
||||
(ellipsislen (- (length ellipsis) (if (eq doom-print-backend 'ansi) 2 4))))
|
||||
(dolist (group (cons toplevel rest))
|
||||
(let ((label (if (car-safe group) (cdr commands))))
|
||||
(when label
|
||||
(insert! ((bold "%s:") (car group)) "\n"))
|
||||
(print-group-if! label
|
||||
(dolist (command (cdr group))
|
||||
(let* ((cli (doom-cli-get command t))
|
||||
(rcli (doom-cli-get command))
|
||||
(summary (doom-cli-short-docs rcli))
|
||||
(subcommands? (doom-cli-subcommands cli 1 :predicate? t)))
|
||||
(insert! ((format "%%-%ds%%s%%s"
|
||||
(+ (- minwidth doom-print-indent)
|
||||
doom-print-indent-increment
|
||||
(if subcommands? ellipsislen 0)))
|
||||
(concat (doom-cli-command-string (seq-drop command drop))
|
||||
(if subcommands? ellipsis))
|
||||
(if inline? " " "\n")
|
||||
(indent (if (and (doom-cli-alias cli)
|
||||
(not (doom-cli-type rcli)))
|
||||
(dark "-> %s" (doom-cli-command-string cli))
|
||||
(when docs?
|
||||
(if summary (markup summary) (dark "TODO"))))))
|
||||
"\n")))
|
||||
(when (cdr rest)
|
||||
(insert "\n")))))
|
||||
(string-trim-right (buffer-string)))))
|
||||
|
||||
;;; Help: options
|
||||
(defun doom-cli-help--options (cli &optional noformatting?)
|
||||
"Return an alist summarizing CLI's options.
|
||||
|
||||
The alist's CAR are lists of formatted switches plus their arguments, e.g.
|
||||
'((\"`--foo'\" \"`BAR'\") ...). Their CDR is their formatted documentation."
|
||||
(let* ((docs (doom-cli-help--parse-docs (doom-cli-find cli t) "OPTIONS"))
|
||||
(docs (mapcar (fn! (cons (split-string (car %) ", ")
|
||||
(cdr %)))
|
||||
docs))
|
||||
(strfmt (if noformatting? "%s" "`%s'"))
|
||||
local-options
|
||||
global-options
|
||||
seen)
|
||||
(dolist (neighbor (nreverse (doom-cli-find cli)))
|
||||
(dolist (option (doom-cli-options neighbor))
|
||||
(when-let* ((switches (cl-loop for sw in (doom-cli-option-switches option)
|
||||
if (and (doom-cli-option-flag-p option)
|
||||
(string-prefix-p "--" sw))
|
||||
collect (format "--[no-]%s" (substring sw 2))
|
||||
else collect sw))
|
||||
(switches (seq-difference switches seen)))
|
||||
(dolist (switch switches) (push switch seen))
|
||||
(push (cons (cl-loop for switch in switches
|
||||
if (doom-cli-option-arguments option)
|
||||
collect (cons (format strfmt switch)
|
||||
(append (doom-cli-help--parse-args it noformatting?)
|
||||
(when (doom-cli-option-multiple-p option)
|
||||
(list "..."))))
|
||||
else collect (list (format strfmt switch)))
|
||||
(string-join
|
||||
(or (delq
|
||||
nil (cons (when-let (docs (doom-cli-option-docs option))
|
||||
(concat docs "."))
|
||||
(cl-loop for (flags . docs) in docs
|
||||
unless (equal (seq-difference flags switches) flags)
|
||||
collect docs)))
|
||||
'("TODO"))
|
||||
"\n\n"))
|
||||
(if (equal (doom-cli-command neighbor)
|
||||
(doom-cli-command cli))
|
||||
local-options
|
||||
global-options)))))
|
||||
`((local . ,(nreverse local-options))
|
||||
(global . ,(nreverse global-options)))))
|
||||
|
||||
(defun doom-cli-help--render-options (options &optional cli)
|
||||
(let ((doom-print-indent 0)
|
||||
(local (assq 'local options))
|
||||
(global (assq 'global options)))
|
||||
(when (or (cdr local) (cdr global))
|
||||
(letf! (defun printopts (opts)
|
||||
(pcase-dolist (`(,switches . ,docs) (cdr opts))
|
||||
(let (multiple?)
|
||||
(insert!
|
||||
("%s%s\n%s"
|
||||
(mapconcat
|
||||
(fn! (when (member "..." (cdr %))
|
||||
(setq multiple? t))
|
||||
(string-trim-right
|
||||
(format "%s %s"
|
||||
(doom-print--cli-markup (car %))
|
||||
(doom-print--cli-markup
|
||||
(string-join (remove "..." (cdr %)) "|")))))
|
||||
switches
|
||||
", ")
|
||||
(if multiple? ", ..." "")
|
||||
(indent (fill (markup docs)) doom-print-indent-increment))
|
||||
"\n\n"))))
|
||||
(with-temp-buffer
|
||||
(if (null (cdr local))
|
||||
(insert (if global "This command has no local options.\n" "") "\n")
|
||||
(printopts local))
|
||||
(when (cdr global)
|
||||
(insert! ((bold "Global options:\n")))
|
||||
(print-group! (printopts global)))
|
||||
(string-trim-right (buffer-string)))))))
|
||||
|
||||
;;; Help: internal
|
||||
(defun doom-cli-help--parse-args (args &optional noformatting?)
|
||||
(cl-loop for arg in args
|
||||
if (listp arg)
|
||||
collect (string-join (doom-cli-help--parse-args arg noformatting?) "|")
|
||||
else if (symbolp arg)
|
||||
collect (format (if noformatting? "%s" "`%s'") (upcase (symbol-name arg)))
|
||||
else collect arg))
|
||||
|
||||
(defun doom-cli-help--parse-docs (cli-list section-name)
|
||||
(cl-check-type section-name string)
|
||||
(let (alist)
|
||||
(dolist (cli cli-list (nreverse alist))
|
||||
(when-let (section (cdr (assoc section-name (doom-cli-docs cli))))
|
||||
(with-temp-buffer
|
||||
(save-excursion (insert section))
|
||||
(let ((lead (current-indentation))
|
||||
(buffer (current-buffer)))
|
||||
(while (not (eobp))
|
||||
(let ((heading (string-trim (buffer-substring (point-at-bol) (point-at-eol))))
|
||||
(beg (point-at-bol 2))
|
||||
end)
|
||||
(forward-line 1)
|
||||
(while (and (not (eobp))
|
||||
(/= (current-indentation) lead)
|
||||
(forward-line 1)))
|
||||
(setf (alist-get heading alist nil nil #'equal)
|
||||
(string-join
|
||||
(delq
|
||||
nil (list (alist-get heading alist nil nil #'equal)
|
||||
(let ((end (point)))
|
||||
(with-temp-buffer
|
||||
(insert-buffer-substring buffer beg end)
|
||||
(goto-char (point-min))
|
||||
(indent-rigidly (point-min) (point-max) (- (current-indentation)))
|
||||
(string-trim-right (buffer-string))))))
|
||||
"\n\n"))))))))))
|
||||
|
||||
(provide 'doom-cli-help)
|
||||
;;; help.el ends here
|
34
lisp/cli/info.el
Normal file
34
lisp/cli/info.el
Normal file
|
@ -0,0 +1,34 @@
|
|||
;;; lisp/cli/info.el --- information about your Doom install -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;; None yet!
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! info
|
||||
((format ("--lisp" "--json") "What format to dump info into")
|
||||
&context context)
|
||||
"Print detailed information about your config for bug reports."
|
||||
(with-temp-buffer
|
||||
(pcase format
|
||||
("--json"
|
||||
(require 'json)
|
||||
(insert (json-encode (doom-info)))
|
||||
(json-pretty-print-buffer))
|
||||
("--lisp"
|
||||
(pp (doom-info)))
|
||||
(_
|
||||
(insert (doom-info-string
|
||||
(if (doom-cli-context-pipe-p context :out t)
|
||||
72
|
||||
(doom-cli-context-width context))))))
|
||||
(print! "%s" (string-trim-right (buffer-string)))))
|
||||
|
||||
(provide 'doom-cli-info)
|
||||
;;; info.el ends here
|
132
lisp/cli/install.el
Normal file
132
lisp/cli/install.el
Normal file
|
@ -0,0 +1,132 @@
|
|||
;;; lisp/cli/install.el --- Doom Emacs install wizard -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
(load! "packages")
|
||||
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;; None yet!
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! ((install i))
|
||||
(&flags
|
||||
(config? ("--config" :yes) "Create `$DOOMDIR' or dummy files therein?")
|
||||
(envfile? ("--env" :yes) "(Re)generate an envvars file? (see `$ doom help env`)")
|
||||
(install? ("--install" :yes) "Auto-install packages?")
|
||||
(fonts? ("--fonts" :yes) "Install (or prompt to install) all-the-icons fonts?")
|
||||
(hooks? ("--hooks" :yes) "Deploy Doom's git hooks to itself?")
|
||||
&context context)
|
||||
"Installs and sets up Doom Emacs for the first time.
|
||||
|
||||
This command does the following:
|
||||
|
||||
1. Creates `$DOOMDIR' at ~/.doom.d,
|
||||
2. Copies ~/.emacs.d/templates/init.example.el to `$DOOMDIR'/init.el (if it
|
||||
doesn't exist),
|
||||
3. Creates dummy files for `$DOOMDIR'/{config,packages}.el,
|
||||
4. Prompts you to generate an envvar file (same as `$ doom env`),
|
||||
5. Installs any dependencies of enabled modules (specified by `$DOOMDIR'/init.el),
|
||||
6. And prompts to install all-the-icons' fonts
|
||||
|
||||
This command is idempotent and safe to reuse.
|
||||
|
||||
Change `$DOOMDIR' with the `--doomdir' option, e.g.
|
||||
|
||||
```
|
||||
$ doom --doomdir /other/doom/config install
|
||||
```"
|
||||
(print! (green "Installing Doom Emacs!\n"))
|
||||
(let ((default-directory doom-emacs-dir)
|
||||
(yes? (doom-cli-context-suppress-prompts-p context)))
|
||||
;; Create `doom-private-dir'
|
||||
(if (eq config? :no)
|
||||
(print! (warn "Not copying private config template, as requested"))
|
||||
;; Create DOOMDIR in ~/.config/doom if ~/.config/emacs exists.
|
||||
(when (and (not (file-directory-p doom-private-dir))
|
||||
(not (getenv "DOOMDIR")))
|
||||
(let ((xdg-config-dir (or (getenv "XDG_CONFIG_HOME") "~/.config")))
|
||||
(when (file-in-directory-p doom-emacs-dir xdg-config-dir)
|
||||
(setq doom-private-dir (expand-file-name "doom/" xdg-config-dir)))))
|
||||
|
||||
(if (file-directory-p doom-private-dir)
|
||||
(print! (item "Skipping %s (already exists)") (relpath doom-private-dir))
|
||||
(make-directory doom-private-dir 'parents)
|
||||
(print! (success "Created %s") (relpath doom-private-dir)))
|
||||
|
||||
;; Create init.el, config.el & packages.el
|
||||
(print-group!
|
||||
(mapc (lambda (file)
|
||||
(cl-destructuring-bind (filename . template) file
|
||||
(if (file-exists-p! filename doom-private-dir)
|
||||
(print! (item "Skipping %s (already exists)")
|
||||
(path filename))
|
||||
(print! (item "Creating %s%s") (relpath doom-private-dir) filename)
|
||||
(with-temp-file (doom-path doom-private-dir filename)
|
||||
(insert-file-contents template))
|
||||
(print! (success "Done!")))))
|
||||
`(("init.el" . ,(doom-path doom-emacs-dir "templates/init.example.el"))
|
||||
("config.el" . ,(doom-path doom-emacs-dir "templates/config.example.el"))
|
||||
("packages.el" . ,(doom-path doom-emacs-dir "templates/packages.example.el"))))))
|
||||
|
||||
;; In case no init.el was present the first time `doom-initialize-modules' was
|
||||
;; called in core.el (e.g. on first install)
|
||||
(doom-initialize-modules 'force 'no-config)
|
||||
|
||||
;; Ask if user would like an envvar file generated
|
||||
(if (eq envfile? :no)
|
||||
(print! (warn "Not generating envvars file, as requested"))
|
||||
(if (file-exists-p doom-env-file)
|
||||
(print! (item "Envvar file already exists, skipping"))
|
||||
(when (or yes? (y-or-n-p "Generate an envvar file? (see `doom help env` for details)"))
|
||||
(call! '(env)))))
|
||||
|
||||
;; Install Doom packages
|
||||
(if (eq install? :no)
|
||||
(print! (warn "Not installing plugins, as requested"))
|
||||
(print! "Installing plugins")
|
||||
(doom-packages-install))
|
||||
|
||||
(print! "Regenerating autoloads files")
|
||||
(doom-autoloads-reload)
|
||||
|
||||
(if (eq hooks? :no)
|
||||
(print! (warn "Not deploying commit-msg and pre-push git hooks, as requested"))
|
||||
(print! "Deploying commit-msg and pre-push git hooks")
|
||||
(print-group!
|
||||
(condition-case e
|
||||
(call! `(ci deploy-hooks ,@(if yes? '("--force"))))
|
||||
('user-error
|
||||
(print! (warn "%s") (error-message-string e))))))
|
||||
|
||||
(cond ((eq fonts? :no))
|
||||
(IS-WINDOWS
|
||||
(print! (warn "Doom cannot install all-the-icons' fonts on Windows!\n"))
|
||||
(print-group!
|
||||
(print!
|
||||
(concat "You'll have to do so manually:\n\n"
|
||||
" 1. Launch Doom Emacs\n"
|
||||
" 2. Execute 'M-x all-the-icons-install-fonts' to download the fonts\n"
|
||||
" 3. Open the download location in windows explorer\n"
|
||||
" 4. Open each font file to install them"))))
|
||||
((or yes? (y-or-n-p "Download and install all-the-icon's fonts?"))
|
||||
(require 'all-the-icons)
|
||||
(let ((window-system (cond (IS-MAC 'ns)
|
||||
(IS-LINUX 'x))))
|
||||
(all-the-icons-install-fonts 'yes))))
|
||||
|
||||
(when (file-exists-p "~/.emacs")
|
||||
(print! (warn "A ~/.emacs file was detected. This conflicts with Doom and should be deleted!")))
|
||||
|
||||
(print! (success "\nFinished! Doom is ready to go!\n"))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (doom-path doom-emacs-dir "templates/QUICKSTART_INTRO"))
|
||||
(print! "%s" (buffer-string)))))
|
||||
|
||||
(provide 'doom-cli-install)
|
||||
;;; install.el ends here
|
63
lisp/cli/make.el
Normal file
63
lisp/cli/make.el
Normal file
|
@ -0,0 +1,63 @@
|
|||
;;; lisp/cli/make.el --- file generation commands -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
(load! "make/completions")
|
||||
;; (load! "make/docs")
|
||||
;; (load! "make/manpage")
|
||||
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;; (defvar doom-make-codeowners ()
|
||||
;; "TODO")
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! make ()
|
||||
"(Re)Generate project files and boilerplate."
|
||||
:partial t)
|
||||
|
||||
;; TODO Finish and generalize me
|
||||
(defstub! (make codeowners) ()
|
||||
"TODO"
|
||||
(print! (start "Generating CODEOWNERS file"))
|
||||
(let ((codeowners (doom-path doom-emacs-dir ".github/CODEOWNERS")))
|
||||
(with-temp-file codeowners
|
||||
(insert-file-contents codeowners)
|
||||
(when (re-search-forward "^# Don't edit this by hand!" nil t)
|
||||
(goto-char (line-end-position))
|
||||
(delete-region (point) (point-max))
|
||||
(insert "\n")
|
||||
(dolist (path (cdr (doom-module-load-path (list doom-modules-dir))))
|
||||
(when (string-match "/modules/\\([^/]+\\)/\\([^/]+\\)/$" path)
|
||||
(insert (format "%-35s @doomemacs/maintainers @doomemacs/%s-%s\n"
|
||||
(concat (substring (match-string-no-properties 0 path) 1) "*")
|
||||
(match-string-no-properties 1 path)
|
||||
(match-string-no-properties 2 path)))))))))
|
||||
|
||||
;; TODO Finish me
|
||||
(defstub! (make changelog))
|
||||
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(defmacro doom-make--with-file (file &rest body)
|
||||
(declare (indent 1))
|
||||
`(let ((inhibit-read-only t))
|
||||
(with-current-buffer
|
||||
(or (get-file-buffer ,file)
|
||||
(find-file-noselect ,file))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
,@body
|
||||
(when (buffer-modified-p)
|
||||
(save-buffer))))))
|
||||
|
||||
(provide 'doom-cli-make)
|
||||
;;; make.el ends here
|
142
lisp/cli/make/completions.el
Normal file
142
lisp/cli/make/completions.el
Normal file
|
@ -0,0 +1,142 @@
|
|||
;;; lisp/cli/make/completions.el --- generate shell completion scripts -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;; (defvar doom-make-completions-zsh-spec
|
||||
;; '(("FILE" . "_files"))
|
||||
;; "TODO")
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! (make completions)
|
||||
((shell ("--zsh" "--bash") "Generate a particular flavor of completion files (defaults to $SHELL)")
|
||||
;; TODO (outfile ("-o" "--outfile" file))
|
||||
&context context &args args)
|
||||
"Generate completion scripts for a Doom-CLI script."
|
||||
;; (unless outfile
|
||||
;; (user-error "No destination file specified"))
|
||||
(let ((shell (or shell (file-name-base (getenv "SHELL"))))
|
||||
;; TODO Allow this command to read other Doom binscripts, which will
|
||||
;; dump their `doom-cli--table' if __DOOMDUMP is set.
|
||||
;; (table (read (letenv! (("__DOOMDUMP" "1")) (apply #'sh! script-file args))))
|
||||
)
|
||||
(print!
|
||||
"%s" (pcase (string-remove-prefix "--" shell)
|
||||
("zsh" (doom-make-completions-zsh context nil))
|
||||
("bash" (doom-make-completions-bash context nil))
|
||||
(_ (user-error "No support for %S shell at this time" shell))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; ZSH Helpers
|
||||
|
||||
;; TODO Write to OUTFILE when specified
|
||||
(defun doom-make-completions-zsh (context _outfile)
|
||||
(let* ((cli (doom-cli-get context))
|
||||
(prefix (doom-cli-context-prefix context))
|
||||
(options (doom-cli-help--options cli t))
|
||||
(commands (doom-cli-subcommands (list prefix))))
|
||||
(with-temp-buffer
|
||||
(insert "#compdef " (doom-cli-context-prefix context) "\n\n"
|
||||
"_globalargs=(\n ")
|
||||
(doom-make-completions--zsh-insert-options
|
||||
(append '(((("--help") ("-?")) . "Show help documentation")
|
||||
((("--version")) . "Show version information"))
|
||||
(alist-get 'global options))
|
||||
"\n ")
|
||||
(insert "\n)\n\n")
|
||||
(doom-make-completions--zsh-insert-command '("doom"))
|
||||
(mapc #'doom-make-completions--zsh-insert-command commands)
|
||||
;; (insert "\n\n_doom")
|
||||
(buffer-string))
|
||||
;; (set-file-modes outfile #o755)
|
||||
;; outfile
|
||||
))
|
||||
|
||||
(defun doom-make-completions--zsh-insert-options (options &optional cr)
|
||||
;; FIXME Refactor, generalize, and parameterize this mess
|
||||
(dolist (option options)
|
||||
(let* ((switches (cl-loop for (sw . args) in (car option)
|
||||
if (string-prefix-p "--[no-]" sw)
|
||||
collect (cons (concat "--" (string-remove-prefix "--[no-]" sw)) args)
|
||||
and collect (cons (concat "--no-" (string-remove-prefix "--[no-]" sw)) args)
|
||||
else collect (cons sw args)))
|
||||
(args (remove "..." (cdr (car switches))))
|
||||
(argspec (cl-loop for arg in args
|
||||
concat
|
||||
(format ":%s:%s"
|
||||
(replace-regexp-in-string
|
||||
":" ";" (shell-quote-argument arg))
|
||||
(or (plist-get (cdr (assoc (intern-soft (downcase (car args)))
|
||||
doom-cli-option-arg-types))
|
||||
:zshcomp)
|
||||
""))))
|
||||
(multiple? (member "..." (cdr (car switches)))))
|
||||
(insert (format "%s%s%s"
|
||||
(if multiple?
|
||||
"\\*"
|
||||
(format "'(%s)'" (mapconcat #'car switches " ")))
|
||||
(if (cdr switches)
|
||||
(format
|
||||
"{%s}" (combine-and-quote-strings
|
||||
(cl-loop for (sw . _) in switches
|
||||
if (and args (string-prefix-p "--" sw))
|
||||
collect (concat (shell-quote-argument sw) "=")
|
||||
else collect (shell-quote-argument sw))
|
||||
","))
|
||||
(format "%s%s" (caar switches)
|
||||
(if (and args (string-prefix-p "--" (caar switches)))
|
||||
"=" "")))
|
||||
(format "'[%s]%s'"
|
||||
(replace-regexp-in-string "'" "''" (cdr option))
|
||||
(or argspec "")))
|
||||
(or cr "\n")))))
|
||||
|
||||
(defun doom-make-completions--zsh-insert-command (command)
|
||||
(let* ((commandstr (doom-cli-command-string command))
|
||||
(options (alist-get 'local (doom-cli-help--options (doom-cli-get command) t)))
|
||||
(subcommands (doom-cli-subcommands command 1)))
|
||||
(insert "_" (replace-regexp-in-string "[- ]" "_" commandstr) "() {\n"
|
||||
" local line state\n"
|
||||
" _arguments -s -S -C \"${_globalargs[@]}\" \\\n ")
|
||||
(doom-make-completions--zsh-insert-options options " \\\n ")
|
||||
(insert "\"1: :->cmds\" \"*::arg:->args\"\n"
|
||||
" case $state in\n"
|
||||
" cmds)\n"
|
||||
" _values \"" commandstr "\" \\\n "
|
||||
(string-join
|
||||
(cl-loop for command in subcommands
|
||||
unless (string-prefix-p ":" (car command))
|
||||
collect (format "'%s[%s]' "
|
||||
(car (last command))
|
||||
(or (doom-cli-short-docs (doom-cli-get command))
|
||||
"TODO")))
|
||||
" \\\n ")
|
||||
"\n ;;\n"
|
||||
" args)\n"
|
||||
" case $line[1] in\n "
|
||||
(string-join
|
||||
(cl-loop for command in subcommands
|
||||
unless (string-prefix-p ":" (car command))
|
||||
collect (format "%s) _%s ;; "
|
||||
(car (last command))
|
||||
(replace-regexp-in-string "[- ]" "_" (doom-cli-command-string command))))
|
||||
"\n ")
|
||||
"\n esac\n"
|
||||
" ;;\n"
|
||||
" esac\n"
|
||||
"}\n")))
|
||||
|
||||
|
||||
;;
|
||||
;;; Bash helpers
|
||||
|
||||
(defun doom-make-completions-bash (context _outfile)
|
||||
(user-error "Bash completion exporter hasn't been implemented yet!"))
|
||||
|
||||
;;; completions.el ends here
|
813
lisp/cli/packages.el
Normal file
813
lisp/cli/packages.el
Normal file
|
@ -0,0 +1,813 @@
|
|||
;;; lisp/cli/packages.el --- package management commands -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;; None yet!
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! (:before (build b purge p)) (&context context)
|
||||
(require 'comp nil t)
|
||||
(doom-initialize-core-packages))
|
||||
|
||||
;; DEPRECATED Replace with "doom sync --rebuild"
|
||||
(defcli! ((build b))
|
||||
((rebuild-p ("-r") "Only rebuild packages that need rebuilding")
|
||||
(jobs ("-j" "--jobs" num) "How many CPUs to use for native compilation"))
|
||||
"Byte-compiles & symlinks installed packages.
|
||||
|
||||
This ensures that all needed files are symlinked from their package repo and
|
||||
their elisp files are byte-compiled. This is especially necessary if you upgrade
|
||||
Emacs (as byte-code is generally not forward-compatible)."
|
||||
:benchmark t
|
||||
(when jobs
|
||||
(setq native-comp-async-jobs-number (truncate jobs)))
|
||||
(when (doom-packages-build (not rebuild-p))
|
||||
(doom-autoloads-reload))
|
||||
t)
|
||||
|
||||
;; TODO Rename to "doom gc" and move to its own file
|
||||
(defcli! ((purge p))
|
||||
((nobuilds-p ("-b" "--no-builds") "Don't purge unneeded (built) packages")
|
||||
(noelpa-p ("-p" "--no-elpa") "Don't purge ELPA packages")
|
||||
(norepos-p ("-r" "--no-repos") "Don't purge unused straight repos")
|
||||
(noeln-p ("-e" "--no-eln") "Don't purge old ELN bytecode")
|
||||
(noregraft-p ("-g" "--no-regraft") "Regraft git repos (ie. compact them)"))
|
||||
"Deletes orphaned packages & repos, and compacts them.
|
||||
|
||||
Purges all installed ELPA packages (as they are considered temporary). Purges
|
||||
all orphaned package repos and builds. If -g/--regraft is supplied, the git
|
||||
repos among them will be regrafted and compacted to ensure they are as small as
|
||||
possible.
|
||||
|
||||
It is a good idea to occasionally run this doom purge -g to ensure your package
|
||||
list remains lean."
|
||||
:benchmark t
|
||||
(straight-check-all)
|
||||
(when (doom-packages-purge
|
||||
(not noelpa-p)
|
||||
(not norepos-p)
|
||||
(not nobuilds-p)
|
||||
(not noregraft-p)
|
||||
(not noeln-p))
|
||||
(doom-autoloads-reload))
|
||||
t)
|
||||
|
||||
(defstub! rollback) ; TODO Implement me post-3.0
|
||||
|
||||
|
||||
;;
|
||||
;;; Library
|
||||
|
||||
;; FIXME Enforce naming conventions for all functions below
|
||||
|
||||
(defun doom-packages--same-commit-p (abbrev-ref ref)
|
||||
(and (stringp abbrev-ref)
|
||||
(stringp ref)
|
||||
(string-match-p (concat "^" (regexp-quote abbrev-ref))
|
||||
ref)))
|
||||
|
||||
(defun doom-packages--abbrev-commit (commit &optional full)
|
||||
(if full commit (substring commit 0 7)))
|
||||
|
||||
(defun doom-packages--commit-log-between (start-ref end-ref)
|
||||
(straight--process-with-result
|
||||
(straight--process-run
|
||||
"git" "log" "--oneline" "--no-merges"
|
||||
end-ref (concat "^" (regexp-quote start-ref)))
|
||||
(if success
|
||||
(string-trim-right (or stdout ""))
|
||||
(format "ERROR: Couldn't collect commit list because: %s" stderr))))
|
||||
|
||||
(defmacro doom-packages--straight-with (form &rest body)
|
||||
(declare (indent 1))
|
||||
`(let-alist
|
||||
(let* ((buffer (straight--process-buffer))
|
||||
(start (with-current-buffer buffer (point-max)))
|
||||
(retval ,form)
|
||||
(output (with-current-buffer buffer (buffer-substring start (point-max)))))
|
||||
(save-match-data
|
||||
(list (cons 'it retval)
|
||||
(cons 'stdout (substring-no-properties output))
|
||||
(cons 'success (if (string-match "\n+\\[Return code: \\([0-9-]+\\)\\]\n+" output)
|
||||
(string-to-number (match-string 1 output))))
|
||||
(cons 'output (string-trim output
|
||||
"^\\(\\$ [^\n]+\n\\)*\n+"
|
||||
"\n+\\[Return code: [0-9-]+\\]\n+")))))
|
||||
,@body))
|
||||
|
||||
(defun doom-packages--barf-if-incomplete ()
|
||||
(let ((straight-safe-mode t))
|
||||
(condition-case _ (straight-check-all)
|
||||
(error (user-error "Package state is incomplete. Run 'doom sync' first")))))
|
||||
|
||||
(defmacro doom-packages--with-recipes (recipes binds &rest body)
|
||||
(declare (indent 2))
|
||||
(let ((recipe-var (make-symbol "recipe"))
|
||||
(recipes-var (make-symbol "recipes")))
|
||||
`(let* ((,recipes-var ,recipes)
|
||||
(built ())
|
||||
(straight-use-package-pre-build-functions
|
||||
(cons (lambda (pkg &rest _) (cl-pushnew pkg built :test #'equal))
|
||||
straight-use-package-pre-build-functions)))
|
||||
(dolist (,recipe-var ,recipes-var (nreverse built))
|
||||
(cl-block nil
|
||||
(straight--with-plist (append (list :recipe ,recipe-var) ,recipe-var)
|
||||
,(doom-enlist binds)
|
||||
,@body))))))
|
||||
|
||||
(defvar doom-packages--cli-updated-recipes nil)
|
||||
(defun doom-packages--cli-recipes-update ()
|
||||
"Updates straight and recipe repos."
|
||||
(unless doom-packages--cli-updated-recipes
|
||||
(straight--make-build-cache-available)
|
||||
(print! (start "Updating recipe repos..."))
|
||||
(print-group!
|
||||
(doom-packages--with-recipes
|
||||
(delq
|
||||
nil (mapcar (doom-rpartial #'gethash straight--repo-cache)
|
||||
(mapcar #'symbol-name straight-recipe-repositories)))
|
||||
(recipe package type local-repo)
|
||||
(let ((esc (unless init-file-debug "\033[1A"))
|
||||
(ref (straight-vc-get-commit type local-repo))
|
||||
newref output)
|
||||
(print! (start "\033[KUpdating recipes for %s...%s") package esc)
|
||||
(doom-packages--straight-with (straight-vc-fetch-from-remote recipe)
|
||||
(when .it
|
||||
(setq output .output)
|
||||
(straight-merge-package package)
|
||||
(unless (equal ref (setq newref (straight-vc-get-commit type local-repo)))
|
||||
(print! (success "\033[K%s updated (%s -> %s)")
|
||||
package
|
||||
(doom-packages--abbrev-commit ref)
|
||||
(doom-packages--abbrev-commit newref))
|
||||
(unless (string-empty-p output)
|
||||
(print-group! (print! (item "%s" output))))))))))
|
||||
(setq straight--recipe-lookup-cache (make-hash-table :test #'eq)
|
||||
doom-packages--cli-updated-recipes t)))
|
||||
|
||||
(defvar doom-packages--eln-output-expected nil)
|
||||
|
||||
(defvar doom-packages--eln-output-path (car (bound-and-true-p native-comp-eln-load-path)))
|
||||
|
||||
(defun doom-packages--eln-file-name (file)
|
||||
"Return the short .eln file name corresponding to `file'."
|
||||
(concat comp-native-version-dir "/"
|
||||
(file-name-nondirectory
|
||||
(comp-el-to-eln-filename file))))
|
||||
|
||||
(defun doom-packages--eln-output-file (eln-name)
|
||||
"Return the expected .eln file corresponding to `eln-name'."
|
||||
(concat doom-packages--eln-output-path eln-name))
|
||||
|
||||
(defun doom-packages--eln-error-file (eln-name)
|
||||
"Return the expected .error file corresponding to `eln-name'."
|
||||
(concat doom-packages--eln-output-path eln-name ".error"))
|
||||
|
||||
(defun doom-packages--find-eln-file (eln-name)
|
||||
"Find `eln-name' on the `native-comp-eln-load-path'."
|
||||
(cl-some (lambda (eln-path)
|
||||
(let ((file (concat eln-path eln-name)))
|
||||
(when (file-exists-p file)
|
||||
file)))
|
||||
native-comp-eln-load-path))
|
||||
|
||||
(defun doom-packages--elc-file-outdated-p (file)
|
||||
"Check whether the corresponding .elc for `file' is outdated."
|
||||
(let ((elc-file (byte-compile-dest-file file)))
|
||||
;; NOTE Ignore missing elc files, they could be missing due to
|
||||
;; `no-byte-compile'. Rebuilding unnecessarily is expensive.
|
||||
(when (and (file-exists-p elc-file)
|
||||
(file-newer-than-file-p file elc-file))
|
||||
(doom-log "%s is newer than %s" file elc-file)
|
||||
t)))
|
||||
|
||||
(defun doom-packages--eln-file-outdated-p (file)
|
||||
"Check whether the corresponding .eln for `file' is outdated."
|
||||
(let* ((eln-name (doom-packages--eln-file-name file))
|
||||
(eln-file (doom-packages--find-eln-file eln-name))
|
||||
(error-file (doom-packages--eln-error-file eln-name)))
|
||||
(cond (eln-file
|
||||
(when (file-newer-than-file-p file eln-file)
|
||||
(doom-log "%s is newer than %s" file eln-file)
|
||||
t))
|
||||
((file-exists-p error-file)
|
||||
(when (file-newer-than-file-p file error-file)
|
||||
(doom-log "%s is newer than %s" file error-file)
|
||||
t))
|
||||
(t
|
||||
(doom-log "%s doesn't exist" eln-name)
|
||||
t))))
|
||||
|
||||
(defun doom-packages--native-compile-done-h (file)
|
||||
"Callback fired when an item has finished async compilation."
|
||||
(when file
|
||||
(let* ((eln-name (doom-packages--eln-file-name file))
|
||||
(eln-file (doom-packages--eln-output-file eln-name))
|
||||
(error-file (doom-packages--eln-error-file eln-name)))
|
||||
(if (file-exists-p eln-file)
|
||||
(doom-log "Compiled %s" eln-file)
|
||||
(make-directory (file-name-directory error-file) 'parents)
|
||||
(write-region "" nil error-file)
|
||||
(doom-log "Wrote %s" error-file)))))
|
||||
|
||||
(defun doom-packages--native-compile-jobs ()
|
||||
"How many async native compilation jobs are queued or in-progress."
|
||||
(if (featurep 'comp)
|
||||
(+ (length comp-files-queue)
|
||||
(comp-async-runnings))
|
||||
0))
|
||||
|
||||
(defun doom-packages--wait-for-native-compile-jobs ()
|
||||
"Wait for all pending async native compilation jobs."
|
||||
(cl-loop with previous = 0
|
||||
with timeout = 30
|
||||
with timer = 0
|
||||
for pending = (doom-packages--native-compile-jobs)
|
||||
while (not (zerop pending))
|
||||
if (/= previous pending) do
|
||||
(print! (start "\033[KNatively compiling %d files...\033[1A" pending))
|
||||
(setq previous pending
|
||||
timer 0)
|
||||
else do
|
||||
(let ((inhibit-message t))
|
||||
(if (> timer timeout)
|
||||
(cl-loop for file-name being each hash-key of comp-async-compilations
|
||||
for prc = (gethash file-name comp-async-compilations)
|
||||
unless (process-live-p prc)
|
||||
do (setq timer 0)
|
||||
and do (print! (warn "Native compilation of %S timed out" (path file-name)))
|
||||
and return (kill-process prc))
|
||||
(cl-incf timer 0.1))
|
||||
(sleep-for 0.1))))
|
||||
|
||||
(defun doom-packages--write-missing-eln-errors ()
|
||||
"Write .error files for any expected .eln files that are missing."
|
||||
(when NATIVECOMP
|
||||
(cl-loop for file in doom-packages--eln-output-expected
|
||||
for eln-name = (doom-packages--eln-file-name file)
|
||||
for eln-file = (doom-packages--eln-output-file eln-name)
|
||||
for error-file = (doom-packages--eln-error-file eln-name)
|
||||
unless (or (file-exists-p eln-file)
|
||||
(file-newer-than-file-p error-file file))
|
||||
do (make-directory (file-name-directory error-file) 'parents)
|
||||
(write-region "" nil error-file)
|
||||
(doom-log "Wrote %s" error-file))
|
||||
(setq doom-packages--eln-output-expected nil)))
|
||||
|
||||
(defun doom-packages--compile-site-files ()
|
||||
"Queue async compilation for all non-doom Elisp files."
|
||||
(when NATIVECOMP
|
||||
(cl-loop with paths = (cl-loop for path in load-path
|
||||
unless (string-prefix-p doom-local-dir path)
|
||||
collect path)
|
||||
for file in (doom-files-in paths :match "\\.el\\(?:\\.gz\\)?$")
|
||||
if (and (file-exists-p (byte-compile-dest-file file))
|
||||
(not (doom-packages--find-eln-file (doom-packages--eln-file-name file)))
|
||||
(not (cl-some (lambda (re)
|
||||
(string-match-p re file))
|
||||
native-comp-deferred-compilation-deny-list))) do
|
||||
(doom-log "Compiling %s" file)
|
||||
(native-compile-async file))))
|
||||
|
||||
(defun doom-packages-install ()
|
||||
"Installs missing packages.
|
||||
|
||||
This function will install any primary package (i.e. a package with a `package!'
|
||||
declaration) or dependency thereof that hasn't already been."
|
||||
(doom-initialize-packages)
|
||||
(print! (start "Installing packages..."))
|
||||
(let ((pinned (doom-package-pinned-list)))
|
||||
(print-group!
|
||||
(add-hook 'native-comp-async-cu-done-functions #'doom-packages--native-compile-done-h)
|
||||
(if-let (built
|
||||
(doom-packages--with-recipes (doom-package-recipe-list)
|
||||
(recipe package type local-repo)
|
||||
(unless (file-directory-p (straight--repos-dir local-repo))
|
||||
(doom-packages--cli-recipes-update))
|
||||
(condition-case-unless-debug e
|
||||
(let ((straight-use-package-pre-build-functions
|
||||
(cons (lambda (pkg &rest _)
|
||||
(when-let (commit (cdr (assoc pkg pinned)))
|
||||
(print! (item "Checked out %s: %s") pkg commit)))
|
||||
straight-use-package-pre-build-functions)))
|
||||
(straight-use-package (intern package))
|
||||
;; HACK Line encoding issues can plague repos with dirty
|
||||
;; worktree prompts when updating packages or "Local
|
||||
;; variables entry is missing the suffix" errors when
|
||||
;; installing them (see hlissner/doom-emacs#2637), so
|
||||
;; have git handle conversion by force.
|
||||
(when (and IS-WINDOWS (stringp local-repo))
|
||||
(let ((default-directory (straight--repos-dir local-repo)))
|
||||
(when (file-in-directory-p default-directory straight-base-dir)
|
||||
(straight--process-run "git" "config" "core.autocrlf" "true")))))
|
||||
(error
|
||||
(signal 'doom-package-error (list package e))))))
|
||||
(progn
|
||||
(doom-packages--compile-site-files)
|
||||
(when NATIVECOMP
|
||||
(doom-packages--wait-for-native-compile-jobs)
|
||||
(doom-packages--write-missing-eln-errors))
|
||||
(print! (success "\033[KInstalled %d packages") (length built)))
|
||||
(print! (item "No packages need to be installed"))
|
||||
nil))))
|
||||
|
||||
|
||||
(defun doom-packages-build (&optional force-p)
|
||||
"(Re)build all packages."
|
||||
(doom-initialize-packages)
|
||||
(print! (start "(Re)building %spackages...") (if force-p "all " ""))
|
||||
(print-group!
|
||||
(let ((straight-check-for-modifications
|
||||
(when (file-directory-p (straight--modified-dir))
|
||||
'(find-when-checking)))
|
||||
(straight--allow-find
|
||||
(and straight-check-for-modifications
|
||||
(executable-find straight-find-executable)
|
||||
t))
|
||||
(straight--packages-not-to-rebuild
|
||||
(or straight--packages-not-to-rebuild (make-hash-table :test #'equal)))
|
||||
(straight--packages-to-rebuild
|
||||
(or (if force-p :all straight--packages-to-rebuild)
|
||||
(make-hash-table :test #'equal)))
|
||||
(recipes (doom-package-recipe-list)))
|
||||
(add-hook 'native-comp-async-cu-done-functions #'doom-packages--native-compile-done-h)
|
||||
(unless force-p
|
||||
(straight--make-build-cache-available))
|
||||
(if-let (built
|
||||
(doom-packages--with-recipes recipes (package local-repo recipe)
|
||||
(unless force-p
|
||||
;; Ensure packages with outdated files/bytecode are rebuilt
|
||||
(let* ((build-dir (straight--build-dir package))
|
||||
(repo-dir (straight--repos-dir local-repo))
|
||||
(build (if (plist-member recipe :build)
|
||||
(plist-get recipe :build)
|
||||
t))
|
||||
(want-byte-compile
|
||||
(or (eq build t)
|
||||
(memq 'compile build)))
|
||||
(want-native-compile
|
||||
(or (eq build t)
|
||||
(memq 'native-compile build))))
|
||||
(and (eq (car-safe build) :not)
|
||||
(setq want-byte-compile (not want-byte-compile)
|
||||
want-native-compile (not want-native-compile)))
|
||||
(unless NATIVECOMP
|
||||
(setq want-native-compile nil))
|
||||
(and (or want-byte-compile want-native-compile)
|
||||
(or (file-newer-than-file-p repo-dir build-dir)
|
||||
(file-exists-p (straight--modified-dir (or local-repo package)))
|
||||
(cl-loop with outdated = nil
|
||||
for file in (doom-files-in build-dir :match "\\.el$" :full t)
|
||||
if (or (if want-byte-compile (doom-packages--elc-file-outdated-p file))
|
||||
(if want-native-compile (doom-packages--eln-file-outdated-p file)))
|
||||
do (setq outdated t)
|
||||
(when want-native-compile
|
||||
(push file doom-packages--eln-output-expected))
|
||||
finally return outdated))
|
||||
(puthash package t straight--packages-to-rebuild))))
|
||||
(straight-use-package (intern package))))
|
||||
(progn
|
||||
(doom-packages--compile-site-files)
|
||||
(when NATIVECOMP
|
||||
(doom-packages--wait-for-native-compile-jobs)
|
||||
(doom-packages--write-missing-eln-errors))
|
||||
;; HACK Every time you save a file in a package that straight tracks,
|
||||
;; it is recorded in ~/.emacs.d/.local/straight/modified/.
|
||||
;; Typically, straight will clean these up after rebuilding, but
|
||||
;; Doom's use-case circumnavigates that, leaving these files
|
||||
;; there and causing a rebuild of those packages each time `doom
|
||||
;; sync' or similar is run, so we clean it up ourselves:
|
||||
(delete-directory (straight--modified-dir) 'recursive)
|
||||
(print! (success "\033[KRebuilt %d package(s)") (length built)))
|
||||
(print! (item "No packages need rebuilding"))
|
||||
nil))))
|
||||
|
||||
|
||||
|
||||
(defun doom-packages-update ()
|
||||
"Updates packages."
|
||||
(doom-initialize-packages)
|
||||
(doom-packages--barf-if-incomplete)
|
||||
(doom-packages--cli-recipes-update)
|
||||
(let* ((repo-dir (straight--repos-dir))
|
||||
(pinned (doom-package-pinned-list))
|
||||
(recipes (doom-package-recipe-list))
|
||||
(packages-to-rebuild (make-hash-table :test 'equal))
|
||||
(repos-to-rebuild (make-hash-table :test 'equal))
|
||||
(total (length recipes))
|
||||
(esc (unless init-file-debug "\033[1A"))
|
||||
(i 0)
|
||||
errors)
|
||||
(print! (start "Updating packages (this may take a while)..."))
|
||||
(doom-packages--with-recipes recipes (recipe package type local-repo)
|
||||
(cl-incf i)
|
||||
(print-group!
|
||||
(unless (straight--repository-is-available-p recipe)
|
||||
(print! (error "(%d/%d) Couldn't find local repo for %s") i total package)
|
||||
(cl-return))
|
||||
(when (gethash local-repo repos-to-rebuild)
|
||||
(puthash package t packages-to-rebuild)
|
||||
(print! (success "(%d/%d) %s was updated indirectly (with %s)") i total package local-repo)
|
||||
(cl-return))
|
||||
(let ((default-directory (straight--repos-dir local-repo)))
|
||||
(unless (file-in-directory-p default-directory repo-dir)
|
||||
(print! (warn "(%d/%d) Skipping %s because it is local") i total package)
|
||||
(cl-return))
|
||||
(when (eq type 'git)
|
||||
(unless (file-exists-p ".git")
|
||||
(error "%S is not a valid repository" package)))
|
||||
(condition-case-unless-debug e
|
||||
(let ((ref (straight-vc-get-commit type local-repo))
|
||||
(target-ref
|
||||
(cdr (or (assoc local-repo pinned)
|
||||
(assoc package pinned))))
|
||||
commits
|
||||
output)
|
||||
(or (cond
|
||||
((not (stringp target-ref))
|
||||
(print! (start "\033[K(%d/%d) Fetching %s...%s") i total package esc)
|
||||
(doom-packages--straight-with (straight-vc-fetch-from-remote recipe)
|
||||
(when .it
|
||||
(straight-merge-package package)
|
||||
;; (condition-case e
|
||||
;; (straight-merge-package package)
|
||||
;; (wrong-type-argument
|
||||
;; (if (not (equal (cdr e) '(arrayp nil)))
|
||||
;; (signal (car e) (cdr e))
|
||||
;; (delete-directory (straight--build-dir local-repo) t)
|
||||
;; (straight-use-package (intern package)))))
|
||||
(setq target-ref (straight-vc-get-commit type local-repo))
|
||||
(setq output (doom-packages--commit-log-between ref target-ref)
|
||||
commits (length (split-string output "\n" t)))
|
||||
(or (not (doom-packages--same-commit-p target-ref ref))
|
||||
(cl-return)))))
|
||||
|
||||
((doom-packages--same-commit-p target-ref ref)
|
||||
(print! (item "\033[K(%d/%d) %s is up-to-date...%s") i total package esc)
|
||||
(cl-return))
|
||||
|
||||
((if (straight-vc-commit-present-p recipe target-ref)
|
||||
(print! (start "\033[K(%d/%d) Checking out %s (%s)...%s")
|
||||
i total package (doom-packages--abbrev-commit target-ref) esc)
|
||||
(print! (start "\033[K(%d/%d) Fetching %s...%s") i total package esc)
|
||||
(and (straight-vc-fetch-from-remote recipe)
|
||||
(straight-vc-commit-present-p recipe target-ref)))
|
||||
(straight-vc-check-out-commit recipe target-ref)
|
||||
(or (not (eq type 'git))
|
||||
(setq output (doom-packages--commit-log-between ref target-ref)
|
||||
commits (length (split-string output "\n" t))))
|
||||
(doom-packages--same-commit-p target-ref (straight-vc-get-commit type local-repo)))
|
||||
|
||||
((print! (start "\033[K(%d/%d) Re-cloning %s...") i total local-repo esc)
|
||||
(let ((repo (straight--repos-dir local-repo))
|
||||
(straight-vc-git-default-clone-depth 'full))
|
||||
(delete-directory repo 'recursive)
|
||||
(print-group!
|
||||
(straight-use-package (intern package) nil 'no-build))
|
||||
(prog1 (file-directory-p repo)
|
||||
(or (not (eq type 'git))
|
||||
(setq output (doom-packages--commit-log-between ref target-ref)
|
||||
commits (length (split-string output "\n" t))))))))
|
||||
(progn
|
||||
(print! (warn "\033[K(%d/%d) Failed to fetch %s")
|
||||
i total local-repo)
|
||||
(unless (string-empty-p output)
|
||||
(print-group! (print! (item "%s" output))))
|
||||
(cl-return)))
|
||||
(puthash local-repo t repos-to-rebuild)
|
||||
(puthash package t packages-to-rebuild)
|
||||
(print! (success "\033[K(%d/%d) %s: %s -> %s%s")
|
||||
i total local-repo
|
||||
(doom-packages--abbrev-commit ref)
|
||||
(doom-packages--abbrev-commit target-ref)
|
||||
(if (and (integerp commits) (> commits 0))
|
||||
(format " [%d commit(s)]" commits)
|
||||
""))
|
||||
(unless (string-empty-p output)
|
||||
(let ((lines (split-string output "\n")))
|
||||
(setq output
|
||||
(if (> (length lines) 20)
|
||||
(concat (string-join (cl-subseq (butlast lines 1) 0 20) "\n")
|
||||
"\n[...]")
|
||||
output)))
|
||||
(print-group! (print! "%s" (indent output 2)))))
|
||||
(user-error
|
||||
(signal 'user-error (error-message-string e)))
|
||||
(error
|
||||
(signal 'doom-package-error (list package e)))))))
|
||||
(print-group!
|
||||
(princ "\033[K")
|
||||
(if (hash-table-empty-p packages-to-rebuild)
|
||||
(ignore (print! (success "All %d packages are up-to-date") total))
|
||||
(straight--transaction-finalize)
|
||||
(let ((default-directory (straight--build-dir)))
|
||||
(mapc (doom-rpartial #'delete-directory 'recursive)
|
||||
(hash-table-keys packages-to-rebuild)))
|
||||
(print! (success "Updated %d package(s)")
|
||||
(hash-table-count packages-to-rebuild))
|
||||
(doom-packages-build)
|
||||
t))))
|
||||
|
||||
|
||||
;;; PURGE (for the emperor)
|
||||
(defun doom-packages--purge-build (build)
|
||||
(let ((build-dir (straight--build-dir build)))
|
||||
(delete-directory build-dir 'recursive)
|
||||
(if (file-directory-p build-dir)
|
||||
(ignore (print! (error "Failed to purg build/%s" build)))
|
||||
(print! (success "Purged build/%s" build))
|
||||
t)))
|
||||
|
||||
(defun doom-packages--purge-builds (builds)
|
||||
(if (not builds)
|
||||
(prog1 0
|
||||
(print! (item "No builds to purge")))
|
||||
(print! (start "Purging straight builds..." (length builds)))
|
||||
(print-group!
|
||||
(length
|
||||
(delq nil (mapcar #'doom-packages--purge-build builds))))))
|
||||
|
||||
(cl-defun doom-packages--regraft-repo (repo)
|
||||
(unless repo
|
||||
(error "No repo specified for regrafting"))
|
||||
(let ((default-directory (straight--repos-dir repo)))
|
||||
(unless (file-directory-p ".git")
|
||||
(print! (warn "\033[Krepos/%s is not a git repo, skipping" repo))
|
||||
(cl-return))
|
||||
(unless (file-in-directory-p default-directory straight-base-dir)
|
||||
(print! (warn "\033[KSkipping repos/%s because it is local" repo))
|
||||
(cl-return))
|
||||
(let ((before-size (doom-directory-size default-directory)))
|
||||
(doom-call-process "git" "reset" "--hard")
|
||||
(doom-call-process "git" "clean" "-ffd")
|
||||
(if (not (zerop (car (doom-call-process "git" "replace" "--graft" "HEAD"))))
|
||||
(print! (item "\033[Krepos/%s is already compact\033[1A" repo))
|
||||
(doom-call-process "git" "reflog" "expire" "--expire=all" "--all")
|
||||
(doom-call-process "git" "gc" "--prune=now")
|
||||
(let ((after-size (doom-directory-size default-directory)))
|
||||
(if (equal after-size before-size)
|
||||
(print! (success "\033[Krepos/%s cannot be compacted further" repo))
|
||||
(print! (success "\033[KRegrafted repos/%s (from %0.1fKB to %0.1fKB)")
|
||||
repo before-size after-size)))))
|
||||
t))
|
||||
|
||||
(defun doom-packages--regraft-repos (repos)
|
||||
(if (not repos)
|
||||
(prog1 0
|
||||
(print! (item "No repos to regraft")))
|
||||
(print! (start "Regrafting %d repos..." (length repos)))
|
||||
(let ((before-size (doom-directory-size (straight--repos-dir))))
|
||||
(print-group!
|
||||
(prog1 (delq nil (mapcar #'doom-packages--regraft-repo repos))
|
||||
(princ "\033[K")
|
||||
(let ((after-size (doom-directory-size (straight--repos-dir))))
|
||||
(print! (success "Finished regrafting. Size before: %0.1fKB and after: %0.1fKB (%0.1fKB)")
|
||||
before-size after-size
|
||||
(- after-size before-size))))))))
|
||||
|
||||
(defun doom-packages--purge-repo (repo)
|
||||
(let ((repo-dir (straight--repos-dir repo)))
|
||||
(when (file-directory-p repo-dir)
|
||||
(delete-directory repo-dir 'recursive)
|
||||
(delete-file (straight--modified-file repo))
|
||||
(if (file-directory-p repo-dir)
|
||||
(ignore (print! (error "Failed to purge repos/%s" repo)))
|
||||
(print! (success "Purged repos/%s" repo))
|
||||
t))))
|
||||
|
||||
(defun doom-packages--purge-repos (repos)
|
||||
(if (not repos)
|
||||
(prog1 0
|
||||
(print! (item "No repos to purge")))
|
||||
(print! (start "Purging straight repositories..."))
|
||||
(print-group!
|
||||
(length
|
||||
(delq nil (mapcar #'doom-packages--purge-repo repos))))))
|
||||
|
||||
(defun doom-packages--purge-elpa ()
|
||||
(require 'doom-packages)
|
||||
(let ((dirs (doom-files-in package-user-dir :type t :depth 0)))
|
||||
(if (not dirs)
|
||||
(prog1 0
|
||||
(print! (item "No ELPA packages to purge")))
|
||||
(print! (start "Purging ELPA packages..."))
|
||||
(dolist (path dirs (length dirs))
|
||||
(condition-case e
|
||||
(print-group!
|
||||
(if (file-directory-p path)
|
||||
(delete-directory path 'recursive)
|
||||
(delete-file path))
|
||||
(print! (success "Deleted %s") (filename path)))
|
||||
(error
|
||||
(print! (error "Failed to delete %s because: %s")
|
||||
(filename path)
|
||||
e)))))))
|
||||
|
||||
(defun doom-packages--purge-eln ()
|
||||
(if-let (dirs
|
||||
(cl-delete (expand-file-name comp-native-version-dir doom-packages--eln-output-path)
|
||||
(directory-files doom-packages--eln-output-path t "^[^.]" t)
|
||||
:test #'file-equal-p))
|
||||
(progn
|
||||
(print! (start "Purging old native bytecode..."))
|
||||
(print-group!
|
||||
(dolist (dir dirs)
|
||||
(print! (item "Deleting %S") (relpath dir doom-packages--eln-output-path))
|
||||
(delete-directory dir 'recursive))
|
||||
(print! (success "Purged %d directory(ies)" (length dirs))))
|
||||
(length dirs))
|
||||
(print! (item "No ELN directories to purge"))
|
||||
0))
|
||||
|
||||
(defun doom-packages-purge (&optional elpa-p builds-p repos-p regraft-repos-p eln-p)
|
||||
"Auto-removes orphaned packages and repos.
|
||||
|
||||
An orphaned package is a package that isn't a primary package (i.e. doesn't have
|
||||
a `package!' declaration) or isn't depended on by another primary package.
|
||||
|
||||
If BUILDS-P, include straight package builds.
|
||||
If REPOS-P, include straight repos.
|
||||
If ELPA-P, include packages installed with package.el (M-x package-install)."
|
||||
(doom-initialize-packages)
|
||||
(doom-packages--barf-if-incomplete)
|
||||
(print! (start "Purging orphaned packages (for the emperor)..."))
|
||||
(cl-destructuring-bind (&optional builds-to-purge repos-to-purge repos-to-regraft)
|
||||
(let ((rdirs
|
||||
(and (or repos-p regraft-repos-p)
|
||||
(straight--directory-files (straight--repos-dir) nil nil 'sort))))
|
||||
(list (when builds-p
|
||||
(let ((default-directory (straight--build-dir)))
|
||||
(seq-filter #'file-directory-p
|
||||
(seq-remove (doom-rpartial #'gethash straight--profile-cache)
|
||||
(straight--directory-files default-directory nil nil 'sort)))))
|
||||
(when repos-p
|
||||
(seq-remove (doom-rpartial #'straight--checkhash straight--repo-cache)
|
||||
rdirs))
|
||||
(when regraft-repos-p
|
||||
(seq-filter (doom-rpartial #'straight--checkhash straight--repo-cache)
|
||||
rdirs))))
|
||||
(print-group!
|
||||
(delq
|
||||
nil (list
|
||||
(if (not builds-p)
|
||||
(ignore (print! (item "Skipping builds")))
|
||||
(and (/= 0 (doom-packages--purge-builds builds-to-purge))
|
||||
(straight-prune-build-cache)))
|
||||
(if (not elpa-p)
|
||||
(ignore (print! (item "Skipping elpa packages")))
|
||||
(/= 0 (doom-packages--purge-elpa)))
|
||||
(if (not repos-p)
|
||||
(ignore (print! (item "Skipping repos")))
|
||||
(/= 0 (doom-packages--purge-repos repos-to-purge)))
|
||||
(if (not regraft-repos-p)
|
||||
(ignore (print! (item "Skipping regrafting")))
|
||||
(doom-packages--regraft-repos repos-to-regraft))
|
||||
(when NATIVECOMP
|
||||
(if (not eln-p)
|
||||
(ignore (print! (item "Skipping native bytecode")))
|
||||
(doom-packages--purge-eln))))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Hacks
|
||||
|
||||
;; Straight was designed primarily for interactive use, in an interactive Emacs
|
||||
;; session, but Doom does its package management in the terminal. Some things
|
||||
;; must be modified get straight to behave and improve its UX for our users.
|
||||
|
||||
(defvar doom-cli--straight-auto-options
|
||||
'(("has diverged from"
|
||||
. "^Reset [^ ]+ to branch")
|
||||
("but recipe specifies a URL of"
|
||||
. "Delete remote \"[^\"]+\", re-create it with correct URL")
|
||||
("has a merge conflict:"
|
||||
. "^Abort merge$")
|
||||
("has a dirty worktree:"
|
||||
. "^Discard changes$")
|
||||
("^In repository \"[^\"]+\", [^ ]+ (on branch \"main\") is ahead of default branch \"master\""
|
||||
. "^Checkout branch \"master\"")
|
||||
("^In repository \"[^\"]+\", [^ ]+ (on branch \"[^\"]+\") is ahead of default branch \"[^\"]+\""
|
||||
. "^Checkout branch \"")
|
||||
("^In repository "
|
||||
. "^Reset branch \\|^Delete remote [^,]+, re-create it with correct URL"))
|
||||
"A list of regexps, mapped to regexps.
|
||||
|
||||
Their CAR is tested against the prompt, and CDR is tested against the presented
|
||||
option, and is used by `straight-vc-git--popup-raw' to select which option to
|
||||
recommend.
|
||||
|
||||
It may not be obvious to users what they should do for some straight prompts,
|
||||
so Doom will recommend the one that reverts a package back to its (or target)
|
||||
original state.")
|
||||
|
||||
;; FIXME Replace with a -j/--jobs option in 'doom sync' et co
|
||||
(defadvice! doom-cli--comp-use-all-cores-a (&rest _)
|
||||
"Default to using all cores, rather than half.
|
||||
Doom compiles packages ahead-of-time, in a dedicated noninteractive session, so
|
||||
it doesn't make sense to slack."
|
||||
:before #'comp-effective-async-max-jobs
|
||||
(setq comp-num-cpus (doom-system-cpus)))
|
||||
|
||||
;; HACK Remove dired & magit options from prompt, since they're inaccessible in
|
||||
;; noninteractive sessions.
|
||||
(advice-add #'straight-vc-git--popup-raw :override #'straight--popup-raw)
|
||||
|
||||
;; HACK Replace GUI popup prompts (which hang indefinitely in tty Emacs) with
|
||||
;; simple prompts.
|
||||
(defadvice! doom-cli--straight-fallback-to-y-or-n-prompt-a (fn &optional prompt noprompt?)
|
||||
:around #'straight-are-you-sure
|
||||
(or noprompt?
|
||||
(if noninteractive
|
||||
(y-or-n-p (format! "%s" (or prompt "")))
|
||||
(funcall fn prompt))))
|
||||
|
||||
(defun doom-cli--straight-recommended-option-p (prompt option)
|
||||
(cl-loop for (prompt-re . opt-re) in doom-cli--straight-auto-options
|
||||
if (string-match-p prompt-re prompt)
|
||||
return (string-match-p opt-re option)))
|
||||
|
||||
(defadvice! doom-cli--straight-fallback-to-tty-prompt-a (fn prompt actions)
|
||||
"Modifies straight to prompt on the terminal when in noninteractive sessions."
|
||||
:around #'straight--popup-raw
|
||||
(if (bound-and-true-p async-in-child-emacs)
|
||||
(error "Straight prompt: %s" prompt)
|
||||
(let ((doom-cli--straight-auto-options doom-cli--straight-auto-options))
|
||||
;; We can't intercept C-g, so no point displaying any options for this key
|
||||
;; when C-c is the proper way to abort batch Emacs.
|
||||
(delq! "C-g" actions 'assoc)
|
||||
;; HACK These are associated with opening dired or magit, which isn't
|
||||
;; possible in tty Emacs, so...
|
||||
(delq! "e" actions 'assoc)
|
||||
(delq! "g" actions 'assoc)
|
||||
(if (doom-cli-context-suppress-prompts-p doom-cli--context)
|
||||
(cl-loop for (_key desc func) in actions
|
||||
when desc
|
||||
when (doom-cli--straight-recommended-option-p prompt desc t)
|
||||
return (funcall func))
|
||||
(print! (start "%s") (red prompt))
|
||||
(print-group!
|
||||
(terpri)
|
||||
(let (recommended options)
|
||||
(print-group!
|
||||
(print! " 1) Abort")
|
||||
(cl-loop for (_key desc func) in actions
|
||||
when desc
|
||||
do (push func options)
|
||||
and do
|
||||
(print! "%2s) %s" (1+ (length options))
|
||||
(if (doom-cli--straight-recommended-option-p prompt desc)
|
||||
(progn
|
||||
(setq doom-cli--straight-auto-options nil
|
||||
recommended (length options))
|
||||
(green (concat desc " (Choose this if unsure)")))
|
||||
desc))))
|
||||
(terpri)
|
||||
(let* ((options
|
||||
(cons (lambda ()
|
||||
(let ((doom-output-indent 0))
|
||||
(terpri)
|
||||
(print! (warn "Aborted")))
|
||||
(doom-cli--exit 1))
|
||||
(nreverse options)))
|
||||
(prompt
|
||||
(format! "How to proceed? (%s%s) "
|
||||
(mapconcat #'number-to-string
|
||||
(number-sequence 1 (length options))
|
||||
", ")
|
||||
(if (not recommended) ""
|
||||
(format "; don't know? Pick %d" (1+ recommended)))))
|
||||
answer fn)
|
||||
(while (null (nth (setq answer (1- (read-number prompt))) options))
|
||||
(print! (warn "%s is not a valid answer, try again.") answer))
|
||||
(funcall (nth answer options)))))))))
|
||||
|
||||
(setq straight-arrow " > ")
|
||||
(defadvice! doom-cli--straight-respect-print-indent-a (string &rest objects)
|
||||
"Same as `message' (which see for STRING and OBJECTS) normally.
|
||||
However, in batch mode, print to stdout instead of stderr."
|
||||
:override #'straight--output
|
||||
(let ((msg (apply #'format string objects)))
|
||||
(save-match-data
|
||||
(when (string-match (format "^%s\\(.+\\)$" (regexp-quote straight-arrow)) msg)
|
||||
(setq msg (match-string 1 msg))))
|
||||
(and (string-match-p "^\\(Cloning\\|\\(Reb\\|B\\)uilding\\) " msg)
|
||||
(not (string-suffix-p "...done" msg))
|
||||
(doom-print (concat "> " msg)))))
|
||||
|
||||
(defadvice! doom-cli--straight-ignore-gitconfig-a (fn &rest args)
|
||||
"Prevent user and system git configuration from interfering with git calls."
|
||||
:around #'straight--process-call
|
||||
(letenv! (("GIT_CONFIG" nil)
|
||||
("GIT_CONFIG_NOSYSTEM" "1")
|
||||
("GIT_CONFIG_GLOBAL" (or (getenv "DOOMGITCONFIG")
|
||||
"/dev/null")))
|
||||
(apply fn args)))
|
||||
|
||||
(provide 'doom-cli-packages)
|
||||
;;; packages.el ends here
|
103
lisp/cli/run.el
Normal file
103
lisp/cli/run.el
Normal file
|
@ -0,0 +1,103 @@
|
|||
;;; lisp/cli/run.el --- launching Emacs in a sandbox -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;; None yet!
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! run
|
||||
(;; TODO Implement sandbox functionality post-3.0
|
||||
;; (daemon? ("--daemon"))
|
||||
;; (window-type ("--gui" "--tty"))
|
||||
;; (version ("--with-emacs" version))
|
||||
;; (doomversion ("--with-doom" version))
|
||||
;; (profile ("--profile" name))
|
||||
(repl? ("--repl") "Launch an elisp REPL")
|
||||
;; &multiple
|
||||
;; (calls ("-f" "--funcall" fn))
|
||||
;; (loads ("-l" "--load" file))
|
||||
;; (evals ( "--eval" form))
|
||||
&context context
|
||||
&input input
|
||||
&rest args)
|
||||
"Launch Doom Emacs or an Emacs sandbox
|
||||
|
||||
Opens from bin/doom's parent directory.
|
||||
|
||||
Keep in mind there is some overhead opening Doom this way. For the best
|
||||
performance, it is best to run Doom out of ~/.config/emacs or ~/.emacs.d."
|
||||
:benchmark nil
|
||||
;; TODO Implement sandbox functionality post-3.0
|
||||
;; (when version
|
||||
;; (unless (executable-find "nix-shell")
|
||||
;; (user-error "--emacs option is not supported without nix"))
|
||||
;; ...)
|
||||
(if repl?
|
||||
(if input
|
||||
;; Evaluate piped-in text directly, if given.
|
||||
(eval (read input) t)
|
||||
(doom-run-repl context))
|
||||
;; TODO Does this work on Windows?
|
||||
(let* ((tempdir (doom-path (temporary-file-directory) "doom.run"))
|
||||
(tempemacsdir (doom-path tempdir ".emacs.d")))
|
||||
(delete-directory tempdir t)
|
||||
(make-directory tempemacsdir t)
|
||||
(with-temp-file (doom-path tempemacsdir "early-init.el")
|
||||
(prin1 `(progn
|
||||
(setenv "HOME" ,(getenv "HOME"))
|
||||
(setq user-emacs-directory ,doom-emacs-dir)
|
||||
(load-file ,(doom-path doom-emacs-dir "early-init.el")))
|
||||
(current-buffer)))
|
||||
(exit! (format "HOME=%S %s %s"
|
||||
tempdir
|
||||
invocation-name
|
||||
(combine-and-quote-strings args))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(defun doom-run-repl (context)
|
||||
"Launch a rudimentary Elisp REPL."
|
||||
;; I wrote this for fun; not with any serious intention of adding a
|
||||
;; fully-fledged REPL to the Doom CLI. Still, I occasionally need to check
|
||||
;; something, and once this has nix integration and can sandbox Emacs versions
|
||||
;; separately, it may be useful for quick tests and demos.
|
||||
(let (form)
|
||||
(while (setq form (read-from-minibuffer "(elisp) $ "))
|
||||
(when (member form '(":quit" ":q"))
|
||||
(print! "\nGoodbye!")
|
||||
(exit! 0))
|
||||
(let (debug-on-error)
|
||||
(condition-case e
|
||||
(print! "%S" (eval (read form) t))
|
||||
(error
|
||||
(let* ((n 0)
|
||||
(frame (backtrace-frame n))
|
||||
(frame-list nil)
|
||||
(in-program-stack t))
|
||||
(while frame
|
||||
(when in-program-stack
|
||||
(push (cdr frame) frame-list))
|
||||
;; (when (eq (elt frame 1) 'doom-run-repl)
|
||||
;; (setq in-program-stack t))
|
||||
(when (eq (elt frame 1) 'doom-run-repl)
|
||||
(setq in-program-stack nil))
|
||||
(setq n (1+ n)
|
||||
frame (backtrace-frame n)))
|
||||
(let* ((depth doom-cli-backtrace-depth)
|
||||
(print-escape-newlines t))
|
||||
(print! (error "There was an unexpected error"))
|
||||
(print-group!
|
||||
(print! "%s %s" (bold "Message:") (error-message-string e))
|
||||
(print! "%s %S" (bold "Details:") (cdr e))))))))
|
||||
(terpri))))
|
||||
|
||||
(provide 'doom-cli-run)
|
||||
;;; run.el ends here
|
84
lisp/cli/sync.el
Normal file
84
lisp/cli/sync.el
Normal file
|
@ -0,0 +1,84 @@
|
|||
;;; lisp/cli/sync.el --- synchronize config command -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
(load! "packages")
|
||||
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
(defvar doom-after-sync-hook ()
|
||||
"Hooks run after 'doom sync' synchronizes the user's config with Doom.")
|
||||
|
||||
(defvar doom-before-sync-hook ()
|
||||
"Hooks run before 'doom sync' synchronizes the user's config with Doom.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defalias! (:before (sync s)) (:before build))
|
||||
|
||||
(defcli! ((sync s))
|
||||
((noenvvar? ("-e") "Don't regenerate the envvar file")
|
||||
(noelc? ("-c") "Don't recompile config")
|
||||
(update? ("-u") "Update installed packages after syncing")
|
||||
(purge? ("-p") "Purge orphaned package repos & regraft them")
|
||||
(jobs ("-j" "--jobs" num) "How many CPUs to use for native compilation"))
|
||||
"Synchronize your config with Doom Emacs.
|
||||
|
||||
This is the equivalent of running autoremove, install, autoloads, then
|
||||
recompile. Run this whenever you:
|
||||
|
||||
1. Modify your `doom!' block,
|
||||
2. Add or remove `package!' blocks to your config,
|
||||
3. Add or remove autoloaded functions in module autoloaded files.
|
||||
4. Update Doom outside of Doom (e.g. with git)
|
||||
|
||||
It will ensure that unneeded packages are removed, all needed packages are
|
||||
installed, autoloads files are up-to-date and no byte-compiled files have gone
|
||||
stale.
|
||||
|
||||
OPTIONS:
|
||||
-j, --jobs
|
||||
Defaults to the maximum number of threads (or 1, if your CPU's threadcount
|
||||
can't be determined)."
|
||||
:benchmark t
|
||||
(run-hooks 'doom-before-sync-hook)
|
||||
(add-hook 'kill-emacs-hook #'doom-sync--abort-warning-h)
|
||||
(when jobs
|
||||
(setq native-comp-async-jobs-number (truncate jobs)))
|
||||
(print! (start "Synchronizing %S profile..." )
|
||||
(if doom-profile
|
||||
(car (split-string doom-profile "@"))
|
||||
"default"))
|
||||
(unwind-protect
|
||||
(print-group!
|
||||
(when (and (not noenvvar?)
|
||||
(file-exists-p doom-env-file))
|
||||
(call! '(env)))
|
||||
(doom-packages-install)
|
||||
(doom-packages-build)
|
||||
(when update?
|
||||
(doom-packages-update))
|
||||
(doom-packages-purge purge? 'builds-p purge? purge? purge?)
|
||||
(run-hooks 'doom-after-sync-hook)
|
||||
(when (doom-autoloads-reload)
|
||||
(print! (item "Restart Emacs or use 'M-x doom/reload' for changes to take effect")))
|
||||
t)
|
||||
(remove-hook 'kill-emacs-hook #'doom-sync--abort-warning-h)))
|
||||
|
||||
;; DEPRECATED Remove when v3.0 is released
|
||||
(defobsolete! ((refresh re)) (sync) "v3.0.0")
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(defun doom-sync--abort-warning-h ()
|
||||
(print! (warn "Script was abruptly aborted, leaving Doom in an incomplete state!"))
|
||||
(print! (item "Run 'doom sync' to repair it.")))
|
||||
|
||||
(provide 'doom-cli-sync)
|
||||
;;; sync.el ends here
|
48
lisp/cli/test.el
Normal file
48
lisp/cli/test.el
Normal file
|
@ -0,0 +1,48 @@
|
|||
;;; lisp/cli/test.el -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; The heart of Doom's test DSL and framework. Powered by either ERT or
|
||||
;; Buttercup, this extends testing frameworks to allow for isolated execution
|
||||
;; contexts on several levels, a more sophisticated CLI for tests, and
|
||||
;; integration with Doom's profiles system so testing environments can be
|
||||
;; generated on-the-fly.
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
;; TODO Implement me
|
||||
(defvar doom-test-backend 'ert
|
||||
"One of `ert' or `buttercup'.")
|
||||
|
||||
;; TODO Implement me
|
||||
(defvar doom-test-isolation-level nil
|
||||
"Determines the testing strategy for tests.
|
||||
|
||||
Should be one of:
|
||||
|
||||
nil -- Run all tests in the same session.
|
||||
file -- Run each test file in isolated sessions.
|
||||
group -- Run each group of tests in isolated sessions.
|
||||
t -- Run each individual test in isolated sessions (very slow).")
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;; FIXME Will be fixed in v3.1
|
||||
(defstub! test
|
||||
((backend ("--ert" "--buttercup"))
|
||||
(jobs ("-j" "--jobs" int))
|
||||
&rest targets)
|
||||
"Run Doom unit tests.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
;; Nothing here yet
|
||||
|
||||
(provide 'doom-cli-test)
|
||||
;;; test.el ends here
|
168
lisp/cli/upgrade.el
Normal file
168
lisp/cli/upgrade.el
Normal file
|
@ -0,0 +1,168 @@
|
|||
;;; lisp/cli/upgrade.el -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
(load! "packages")
|
||||
(load! "compile")
|
||||
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
(defvar doom-upgrade-url "https://github.com/doomemacs/doomemacs"
|
||||
"The git repo url for Doom Emacs.")
|
||||
|
||||
(defvar doom-upgrade-remote "_upgrade"
|
||||
"The name to use as our staging remote.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! ((upgrade up))
|
||||
((packages? ("-p" "--packages") "Only upgrade packages, not Doom")
|
||||
(jobs ("-j" "--jobs" num) "How many CPUs to use for native compilation")
|
||||
&context context)
|
||||
"Updates Doom and packages.
|
||||
|
||||
This requires that ~/.emacs.d is a git repo, and is the equivalent of the
|
||||
following shell commands:
|
||||
|
||||
cd ~/.emacs.d
|
||||
git pull --rebase
|
||||
doom clean
|
||||
doom sync -u"
|
||||
(let* ((force? (doom-cli-context-suppress-prompts-p context))
|
||||
(sync-cmd (append '("sync" "-u") (if jobs `("-j" ,num)))))
|
||||
(cond
|
||||
(packages?
|
||||
(call! sync-cmd)
|
||||
(print! (success "Finished upgrading Doom Emacs")))
|
||||
|
||||
((doom-cli-upgrade force? force?)
|
||||
;; Reload Doom's CLI & libraries, in case there were any upstream changes.
|
||||
;; Major changes will still break, however
|
||||
(print! (item "Reloading Doom Emacs"))
|
||||
(exit! "doom" "upgrade" "-p"
|
||||
(if force? "--force")
|
||||
(if jobs (format "--jobs=%d" jobs))))
|
||||
|
||||
((print! "Doom is up-to-date!")
|
||||
(call! sync-cmd)))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(defun doom-cli-upgrade (&optional auto-accept-p force-p)
|
||||
"Upgrade Doom to the latest version non-destructively."
|
||||
(let ((default-directory doom-emacs-dir)
|
||||
process-file-side-effects)
|
||||
(print! (start "Preparing to upgrade Doom Emacs and its packages..."))
|
||||
|
||||
(let* (;; git name-rev may return BRANCH~X for detached HEADs and fully
|
||||
;; qualified refs in some other cases, so an effort to strip out all
|
||||
;; but the branch name is necessary. git symbolic-ref (or
|
||||
;; `vc-git--symbolic-ref') won't work; it can't deal with submodules.
|
||||
(branch (replace-regexp-in-string
|
||||
"^\\(?:[^/]+/[^/]+/\\)?\\(.+\\)\\(?:~[0-9]+\\)?$" "\\1"
|
||||
(cdr (doom-call-process "git" "name-rev" "--name-only" "HEAD"))))
|
||||
(target-remote (format "%s_%s" doom-upgrade-remote branch)))
|
||||
(unless branch
|
||||
(error (if (file-exists-p! ".git" doom-emacs-dir)
|
||||
"Couldn't find Doom's .git directory. Was Doom cloned properly?"
|
||||
"Couldn't detect what branch you're on. Is Doom detached?")))
|
||||
|
||||
;; We assume that a dirty .emacs.d is intentional and abort
|
||||
(when-let (dirty (doom-upgrade--working-tree-dirty-p default-directory))
|
||||
(if (not force-p)
|
||||
(user-error "%s\n\n%s\n\n %s"
|
||||
(format "Refusing to upgrade because %S has been modified."
|
||||
(abbreviate-file-name doom-emacs-dir))
|
||||
"Either stash/undo your changes or run 'doom upgrade -f' to discard local changes."
|
||||
(string-join dirty "\n"))
|
||||
(print! (item "You have local modifications in Doom's source. Discarding them..."))
|
||||
(doom-call-process "git" "reset" "--hard" (format "origin/%s" branch))
|
||||
(doom-call-process "git" "clean" "-ffd")))
|
||||
|
||||
(doom-call-process "git" "remote" "remove" doom-upgrade-remote)
|
||||
(unwind-protect
|
||||
(let (result)
|
||||
(or (zerop (car (doom-call-process "git" "remote" "add" doom-upgrade-remote doom-upgrade-url)))
|
||||
(error "Failed to add %s to remotes" doom-upgrade-remote))
|
||||
(or (zerop (car (setq result (doom-call-process "git" "fetch" "--force" "--tags" doom-upgrade-remote (format "%s:%s" branch target-remote)))))
|
||||
(error "Failed to fetch from upstream"))
|
||||
|
||||
(let ((this-rev (cdr (doom-call-process "git" "rev-parse" "HEAD")))
|
||||
(new-rev (cdr (doom-call-process "git" "rev-parse" target-remote))))
|
||||
(cond
|
||||
((and (null this-rev)
|
||||
(null new-rev))
|
||||
(error "Failed to get revisions for %s" target-remote))
|
||||
|
||||
((equal this-rev new-rev)
|
||||
(print! (success "Doom is already up-to-date!"))
|
||||
nil)
|
||||
|
||||
((print! (item "A new version of Doom Emacs is available!\n\n Old revision: %s (%s)\n New revision: %s (%s)\n"
|
||||
(substring this-rev 0 10)
|
||||
(cdr (doom-call-process "git" "log" "-1" "--format=%cr" "HEAD"))
|
||||
(substring new-rev 0 10)
|
||||
(cdr (doom-call-process "git" "log" "-1" "--format=%cr" target-remote))))
|
||||
(let ((diff-url
|
||||
(format "%s/compare/%s...%s"
|
||||
doom-upgrade-url
|
||||
this-rev
|
||||
new-rev)))
|
||||
(print! "Link to diff: %s" diff-url)
|
||||
(when (and (not auto-accept-p)
|
||||
(y-or-n-p "View the comparison diff in your browser?"))
|
||||
(print! (item "Opened github in your browser."))
|
||||
(browse-url diff-url)))
|
||||
|
||||
(if (not (or auto-accept-p
|
||||
(y-or-n-p "Proceed with upgrade?")))
|
||||
(ignore (print! (error "Aborted")))
|
||||
(print! (start "Upgrading Doom Emacs..."))
|
||||
(print-group!
|
||||
(doom-compile-clean)
|
||||
(let ((straight-recipe (doom-upgrade--get-straight-recipe)))
|
||||
(or (and (zerop (car (doom-call-process "git" "reset" "--hard" target-remote)))
|
||||
(equal (cdr (doom-call-process "git" "rev-parse" "HEAD")) new-rev))
|
||||
(error "Failed to check out %s" (substring new-rev 0 10)))
|
||||
;; HACK It's messy to use straight to upgrade straight, due
|
||||
;; to the potential for backwards incompatibility, so we
|
||||
;; staticly check if Doom's `package!' declaration for
|
||||
;; straight has changed. If it has, delete straight so
|
||||
;; 'doom upgrade's second stage will install the new
|
||||
;; version for us.
|
||||
;;
|
||||
;; Clumsy, but a better solution is in the works.
|
||||
(unless (equal straight-recipe (doom-upgrade--get-straight-recipe))
|
||||
(print! (item "Preparing straight for an update"))
|
||||
(delete-directory (doom-path straight-base-dir "straight/repos/straight.el")
|
||||
'recursive)))
|
||||
(print! (item "%s") (cdr result))
|
||||
t))))))
|
||||
(ignore-errors
|
||||
(doom-call-process "git" "branch" "-D" target-remote)
|
||||
(doom-call-process "git" "remote" "remove" doom-upgrade-remote))))))
|
||||
|
||||
(defun doom-upgrade--working-tree-dirty-p (dir)
|
||||
(cl-destructuring-bind (success . stdout)
|
||||
(doom-call-process "git" "status" "--porcelain" "-uno")
|
||||
(if (= 0 success)
|
||||
(split-string stdout "\n" t)
|
||||
(error "Failed to check working tree in %s" dir))))
|
||||
|
||||
(defun doom-upgrade--get-straight-recipe ()
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (doom-path doom-core-dir "packages.el"))
|
||||
(when (re-search-forward "(package! straight" nil t)
|
||||
(goto-char (match-beginning 0))
|
||||
(let ((sexp (sexp-at-point)))
|
||||
(plist-put sexp :recipe
|
||||
(eval (plist-get sexp :recipe)
|
||||
t))))))
|
||||
|
||||
;;; upgrade.el ends here
|
Loading…
Add table
Add a link
Reference in a new issue