Refactor core-cli
Moved to separate files for better organization.
This commit is contained in:
parent
57579b883b
commit
77d2d84e14
11 changed files with 966 additions and 955 deletions
|
@ -76,7 +76,6 @@ ready to be pasted in a bug report on github."
|
|||
(defun doom/info ()
|
||||
"Collects some debug information about your Emacs session, formats it into
|
||||
markdown and copies it to your clipboard, ready to be pasted into bug reports!"
|
||||
(declare (interactive-only t))
|
||||
(interactive)
|
||||
(message "Generating Doom info...")
|
||||
(if noninteractive
|
||||
|
|
|
@ -446,160 +446,8 @@ calls."
|
|||
(doom--delete-package-files name)))))
|
||||
|
||||
|
||||
;;
|
||||
;; Package Management
|
||||
;;
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-packages-install (&optional auto-accept-p)
|
||||
"Interactive command for installing missing packages."
|
||||
(print! "Looking for packages to install...")
|
||||
(let ((packages (reverse (doom-get-missing-packages))))
|
||||
(cond ((not packages)
|
||||
(print! (green "No packages to install!"))
|
||||
nil)
|
||||
|
||||
((not (or auto-accept-p
|
||||
(y-or-n-p
|
||||
(format "%s packages will be installed:\n\n%s\n\nProceed?"
|
||||
(length packages)
|
||||
(mapconcat
|
||||
(lambda (pkg)
|
||||
(format "+ %s (%s)"
|
||||
(car pkg)
|
||||
(cond ((doom-package-different-recipe-p (car pkg))
|
||||
"new recipe")
|
||||
((doom-package-different-backend-p (car pkg))
|
||||
(if (plist-get (cdr pkg) :recipe)
|
||||
"ELPA->QUELPA"
|
||||
"QUELPA->ELPA"))
|
||||
((plist-get (cdr pkg) :recipe)
|
||||
"QUELPA")
|
||||
("ELPA"))))
|
||||
(cl-sort (cl-copy-list packages) #'string-lessp
|
||||
:key #'car)
|
||||
"\n")))))
|
||||
(user-error "Aborted!"))
|
||||
|
||||
((let (success)
|
||||
(doom-refresh-packages-maybe doom-debug-mode)
|
||||
(dolist (pkg packages)
|
||||
(print! "Installing %s" (car pkg))
|
||||
(doom--condition-case!
|
||||
(let ((result
|
||||
(or (and (package-installed-p (car pkg))
|
||||
(not (doom-package-different-backend-p (car pkg)))
|
||||
(not (doom-package-different-recipe-p (car pkg)))
|
||||
'already-installed)
|
||||
(and (doom-install-package (car pkg) (cdr pkg))
|
||||
(setq success t)
|
||||
'success)
|
||||
'failure))
|
||||
(pin-label
|
||||
(and (plist-member (cdr pkg) :pin)
|
||||
(format " [pinned: %s]" (plist-get (cdr pkg) :pin)))))
|
||||
(print! "%s%s"
|
||||
(pcase result
|
||||
(`already-installed (dark (white "⚠ ALREADY INSTALLED")))
|
||||
(`success (green "✓ DONE"))
|
||||
(`failure (red "✕ FAILED")))
|
||||
(or pin-label "")))))
|
||||
(print! (bold (green "Finished!")))
|
||||
(when success
|
||||
(set-file-times doom-packages-dir)
|
||||
(doom-delete-autoloads-file doom-package-autoload-file))
|
||||
success)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-packages-update (&optional auto-accept-p)
|
||||
"Interactive command for updating packages."
|
||||
(print! "Looking for outdated packages...")
|
||||
(let ((packages (cl-sort (cl-copy-list (doom-get-outdated-packages)) #'string-lessp
|
||||
:key #'car)))
|
||||
(cond ((not packages)
|
||||
(print! (green "Everything is up-to-date"))
|
||||
nil)
|
||||
|
||||
((not (or auto-accept-p
|
||||
(y-or-n-p
|
||||
(format "%s packages will be updated:\n\n%s\n\nProceed?"
|
||||
(length packages)
|
||||
(let ((max-len
|
||||
(or (car (sort (mapcar (lambda (it) (length (symbol-name (car it)))) packages)
|
||||
#'>))
|
||||
10)))
|
||||
(mapconcat
|
||||
(lambda (pkg)
|
||||
(format (format "+ %%-%ds %%-%ds -> %%s" (+ max-len 2) 14)
|
||||
(symbol-name (car pkg))
|
||||
(package-version-join (cadr pkg))
|
||||
(package-version-join (cl-caddr pkg))))
|
||||
packages
|
||||
"\n"))))))
|
||||
(user-error "Aborted!"))
|
||||
|
||||
((let (success)
|
||||
(dolist (pkg packages)
|
||||
(print! "Updating %s" (car pkg))
|
||||
(doom--condition-case!
|
||||
(print!
|
||||
(let ((result (doom-update-package (car pkg) t)))
|
||||
(when result (setq success t))
|
||||
(color (if result 'green 'red)
|
||||
(if result "✓ DONE" "✕ FAILED"))))))
|
||||
(print! (bold (green "Finished!")))
|
||||
(when success
|
||||
(set-file-times doom-packages-dir)
|
||||
(doom-delete-autoloads-file doom-package-autoload-file))
|
||||
success)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-packages-autoremove (&optional auto-accept-p)
|
||||
"Interactive command for auto-removing orphaned packages."
|
||||
(print! "Looking for orphaned packages...")
|
||||
(let ((packages (doom-get-orphaned-packages)))
|
||||
(cond ((not packages)
|
||||
(print! (green "No unused packages to remove"))
|
||||
nil)
|
||||
|
||||
((not
|
||||
(or auto-accept-p
|
||||
(y-or-n-p
|
||||
(format "%s packages will be deleted:\n\n%s\n\nProceed?"
|
||||
(length packages)
|
||||
(mapconcat
|
||||
(lambda (sym)
|
||||
(let ((backend (doom-package-backend sym)))
|
||||
(format "+ %s (%s)" sym
|
||||
(if (doom-package-different-backend-p sym)
|
||||
(pcase backend
|
||||
(`quelpa "QUELPA->ELPA")
|
||||
(`elpa "ELPA->QUELPA")
|
||||
(_ "removed"))
|
||||
(upcase (symbol-name backend))))))
|
||||
(sort (cl-copy-list packages) #'string-lessp)
|
||||
"\n")))))
|
||||
(user-error "Aborted!"))
|
||||
|
||||
((let (success)
|
||||
(dolist (pkg packages)
|
||||
(doom--condition-case!
|
||||
(let ((result (doom-delete-package pkg t)))
|
||||
(if result (setq success t))
|
||||
(print! (color (if result 'green 'red)
|
||||
"%s %s"
|
||||
(if result "✓ Removed" "✕ Failed to remove")
|
||||
pkg)))))
|
||||
(print! (bold (green "Finished!")))
|
||||
(when success
|
||||
(set-file-times doom-packages-dir)
|
||||
(doom-delete-autoloads-file doom-package-autoload-file))
|
||||
success)))))
|
||||
|
||||
|
||||
;;
|
||||
;; Make package.el cooperate with Doom
|
||||
;;
|
||||
|
||||
;; Updates QUELPA after deleting a package
|
||||
;;;###autoload
|
||||
|
|
361
core/cli/autoloads.el
Normal file
361
core/cli/autoloads.el
Normal file
|
@ -0,0 +1,361 @@
|
|||
;;; core/cli/autoloads.el -*- lexical-binding: t; -*-
|
||||
|
||||
(dispatcher! (autoloads a) (doom-reload-autoloads nil 'force)
|
||||
"Regenerates Doom's autoloads file.
|
||||
|
||||
This file tells Emacs where to find your module's autoloaded functions and
|
||||
plugins.")
|
||||
|
||||
;; external variables
|
||||
(defvar autoload-timestamps)
|
||||
(defvar generated-autoload-load-name)
|
||||
(defvar generated-autoload-file)
|
||||
|
||||
|
||||
;;
|
||||
;; Helpers
|
||||
|
||||
(defvar doom-autoload-excluded-packages '(marshal gh)
|
||||
"Packages that 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.")
|
||||
|
||||
(defun doom-delete-autoloads-file (file)
|
||||
"Delete FILE (an autoloads file), and delete the accompanying *.elc file, if
|
||||
it exists."
|
||||
(cl-check-type file string)
|
||||
(when (file-exists-p file)
|
||||
(when-let* ((buf (find-buffer-visiting doom-autoload-file)))
|
||||
(with-current-buffer buf
|
||||
(set-buffer-modified-p nil))
|
||||
(kill-buffer buf))
|
||||
(delete-file file)
|
||||
(ignore-errors (delete-file (byte-compile-dest-file file)))
|
||||
(message "Deleted old %s" (file-name-nondirectory file))))
|
||||
|
||||
(defun doom--warn-refresh-session ()
|
||||
(message "Detected a running Emacs session.\n")
|
||||
(message "Restart Emacs or use `M-x doom/reload' for changes to take effect."))
|
||||
|
||||
(defun doom--do-load (&rest files)
|
||||
(if (and noninteractive (not (daemonp)))
|
||||
(progn
|
||||
(require 'server)
|
||||
(when (server-running-p)
|
||||
(add-hook 'kill-emacs-hook #'doom--warn-refresh-session)))
|
||||
(dolist (file files)
|
||||
(load-file (byte-compile-dest-file file)))))
|
||||
|
||||
(defun doom--byte-compile-file (file)
|
||||
(let ((short-name (file-name-nondirectory file))
|
||||
(byte-compile-dynamic-docstrings t))
|
||||
(condition-case e
|
||||
(when (byte-compile-file file)
|
||||
;; Give autoloads file a chance to report error
|
||||
(load (if doom-debug-mode
|
||||
file
|
||||
(byte-compile-dest-file file))
|
||||
nil t)
|
||||
(unless noninteractive
|
||||
(message "Finished compiling %s" short-name)))
|
||||
((debug error)
|
||||
(let ((backup-file (concat file ".bk")))
|
||||
(message "Copied backup to %s" backup-file)
|
||||
(copy-file file backup-file 'overwrite))
|
||||
(doom-delete-autoloads-file file)
|
||||
(signal 'doom-autoload-error (list short-name e))))))
|
||||
|
||||
(defun doom-reload-autoloads (&optional file force-p)
|
||||
"Reloads FILE (an autoload file), if it needs reloading.
|
||||
|
||||
FILE should be one of `doom-autoload-file' or `doom-package-autoload-file'. If
|
||||
it is nil, it will try to reload both. If FORCE-P (universal argument) do it
|
||||
even if it doesn't need reloading!"
|
||||
(or (null file)
|
||||
(stringp file)
|
||||
(signal 'wrong-type-argument (list 'stringp file)))
|
||||
(if (stringp file)
|
||||
(cond ((file-equal-p file doom-autoload-file)
|
||||
(doom-reload-doom-autoloads force-p))
|
||||
((file-equal-p file doom-package-autoload-file)
|
||||
(doom-reload-package-autoloads force-p))
|
||||
((error "Invalid autoloads file: %s" file)))
|
||||
(doom-reload-doom-autoloads force-p)
|
||||
(doom-reload-package-autoloads force-p)))
|
||||
|
||||
|
||||
;;
|
||||
;; Doom autoloads
|
||||
|
||||
(defun doom--file-cookie-p (file)
|
||||
"Returns the return value of the ;;;###if predicate form in FILE."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents-literally file nil 0 256)
|
||||
(if (and (re-search-forward "^;;;###if " nil t)
|
||||
(<= (line-number-at-pos) 3))
|
||||
(let ((load-file-name file))
|
||||
(eval (sexp-at-point)))
|
||||
t)))
|
||||
|
||||
(defun doom--generate-header (func)
|
||||
(goto-char (point-min))
|
||||
(insert ";; -*- lexical-binding:t -*-\n"
|
||||
";; This file is autogenerated by `" (symbol-name func) "', DO NOT EDIT !!\n\n"))
|
||||
|
||||
(defun doom--generate-autoloads (targets)
|
||||
(require 'autoload)
|
||||
(dolist (file targets)
|
||||
(let* ((file (file-truename file))
|
||||
(generated-autoload-file doom-autoload-file)
|
||||
(generated-autoload-load-name (file-name-sans-extension file))
|
||||
(noninteractive (not doom-debug-mode))
|
||||
autoload-timestamps)
|
||||
(print!
|
||||
(cond ((not (doom--file-cookie-p file))
|
||||
"⚠ Ignoring %s")
|
||||
((autoload-generate-file-autoloads file (current-buffer))
|
||||
(yellow "✕ Nothing in %s"))
|
||||
((green "✓ Scanned %s")))
|
||||
(if (file-in-directory-p file default-directory)
|
||||
(file-relative-name file)
|
||||
(abbreviate-file-name file))))))
|
||||
|
||||
(defun doom--expand-autoloads ()
|
||||
(let ((load-path (append doom-modules-dirs load-path))
|
||||
cache)
|
||||
(while (re-search-forward "^\\s-*(autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t)
|
||||
(let ((path (match-string 1)))
|
||||
(replace-match
|
||||
(or (cdr (assoc path cache))
|
||||
(when-let* ((libpath (locate-library path))
|
||||
(libpath (file-name-sans-extension libpath)))
|
||||
(push (cons path (abbreviate-file-name libpath)) cache)
|
||||
libpath)
|
||||
path)
|
||||
t t nil 1)))))
|
||||
|
||||
(defun doom--generate-autodefs (targets enabled-targets)
|
||||
(goto-char (point-max))
|
||||
(search-backward ";;;***" nil t)
|
||||
(save-excursion (insert "\n"))
|
||||
(dolist (path targets)
|
||||
(insert
|
||||
(with-temp-buffer
|
||||
(insert-file-contents path)
|
||||
(let ((member-p (or (member path enabled-targets)
|
||||
(file-in-directory-p path doom-core-dir)))
|
||||
forms)
|
||||
(while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t)
|
||||
(let* ((sexp (sexp-at-point))
|
||||
(pred (match-string 1))
|
||||
(type (car sexp))
|
||||
(name (doom-unquote (cadr sexp)))
|
||||
(origin (cond ((doom-module-from-path path))
|
||||
((file-in-directory-p path doom-private-dir)
|
||||
`(:private . ,(intern (file-name-base path))))
|
||||
((file-in-directory-p path doom-emacs-dir)
|
||||
`(:core . ,(intern (file-name-base path))))))
|
||||
(doom-file-form
|
||||
`(put ',name 'doom-file ,(abbreviate-file-name path))))
|
||||
(cond ((memq type '(defun defmacro cl-defun cl-defmacro))
|
||||
(cl-destructuring-bind (_ name arglist &rest body) sexp
|
||||
(let ((docstring (if (stringp (car body))
|
||||
(pop body)
|
||||
"No documentation.")))
|
||||
(push (cond ((not (and member-p
|
||||
(or (null pred)
|
||||
(let ((load-file-name path))
|
||||
(eval (read pred) t)))))
|
||||
(push doom-file-form forms)
|
||||
(setq docstring (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
|
||||
origin docstring))
|
||||
(condition-case-unless-debug e
|
||||
(append (list (pcase type
|
||||
(`defun 'defmacro)
|
||||
(`cl-defun `cl-defmacro)
|
||||
(_ type))
|
||||
name arglist docstring)
|
||||
(cl-loop for arg in arglist
|
||||
if (and (symbolp arg)
|
||||
(not (keywordp arg))
|
||||
(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)))))
|
||||
('error
|
||||
(message "Ignoring autodef %s (%s)"
|
||||
name e)
|
||||
nil)))
|
||||
((make-autoload sexp (abbreviate-file-name (file-name-sans-extension path)))))
|
||||
forms)
|
||||
(push `(put ',name 'doom-module ',origin) forms))))
|
||||
|
||||
((eq type 'defalias)
|
||||
(cl-destructuring-bind (type name target &optional docstring) sexp
|
||||
(let ((name (doom-unquote name))
|
||||
(target (doom-unquote target)))
|
||||
(unless (and member-p
|
||||
(or (null pred)
|
||||
(let ((load-file-name path))
|
||||
(eval (read pred) t))))
|
||||
(setq target #'ignore))
|
||||
(push doom-file-form forms)
|
||||
(push `(put ',name 'doom-module ',origin) forms)
|
||||
(push `(defalias ',name #',target ,docstring)
|
||||
forms))))
|
||||
|
||||
((and member-p
|
||||
(or (null pred)
|
||||
(eval (read pred) t)))
|
||||
(push sexp forms)))))
|
||||
(if forms
|
||||
(concat (string-join (mapcar #'prin1-to-string (reverse forms)) "\n")
|
||||
"\n")
|
||||
""))))))
|
||||
|
||||
(defun doom--cleanup-autoloads ()
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^;;\\(;[^\n]*\\| no-byte-compile: t\\)\n" nil t)
|
||||
(replace-match "" t t)))
|
||||
|
||||
(defun doom-reload-doom-autoloads (&optional force-p)
|
||||
"Refreshes the autoloads.el file, specified by `doom-autoload-file', if
|
||||
necessary (or if FORCE-P is non-nil).
|
||||
|
||||
It scans and reads core/autoload/*.el, modules/*/*/autoload.el and
|
||||
modules/*/*/autoload/*.el, and generates `doom-autoload-file'. This file tells
|
||||
Emacs where to find lazy-loaded functions.
|
||||
|
||||
This should be run whenever your `doom!' block, or a module autoload file, is
|
||||
modified."
|
||||
(let* ((default-directory doom-emacs-dir)
|
||||
(doom-modules (doom-modules))
|
||||
(targets
|
||||
(file-expand-wildcards
|
||||
(expand-file-name "autoload/*.el" doom-core-dir)))
|
||||
(enabled-targets (copy-sequence targets))
|
||||
case-fold-search)
|
||||
(dolist (path (doom-module-load-path t))
|
||||
(let* ((auto-dir (expand-file-name "autoload" path))
|
||||
(auto-file (expand-file-name "autoload.el" path))
|
||||
(module (doom-module-from-path auto-file))
|
||||
(module-p (or (doom-module-p (car module) (cdr module))
|
||||
(file-equal-p path doom-private-dir))))
|
||||
(when (file-exists-p auto-file)
|
||||
(push auto-file targets)
|
||||
(if module-p (push auto-file enabled-targets)))
|
||||
(dolist (file (doom-files-in auto-dir :match "\\.el$" :full t))
|
||||
(push file targets)
|
||||
(if module-p (push file enabled-targets)))))
|
||||
(if (and (not force-p)
|
||||
(not doom-emacs-changed-p)
|
||||
(file-exists-p doom-autoload-file)
|
||||
(not (file-newer-than-file-p (expand-file-name "init.el" doom-private-dir)
|
||||
doom-autoload-file))
|
||||
(not (cl-loop for file in targets
|
||||
if (file-newer-than-file-p file doom-autoload-file)
|
||||
return t)))
|
||||
(progn (print! (green "Doom core autoloads is up-to-date"))
|
||||
(doom-initialize-autoloads doom-autoload-file)
|
||||
nil)
|
||||
(doom-delete-autoloads-file doom-autoload-file)
|
||||
(message "Generating new autoloads.el")
|
||||
(make-directory (file-name-directory doom-autoload-file) t)
|
||||
(with-temp-file doom-autoload-file
|
||||
(doom--generate-header 'doom-reload-doom-autoloads)
|
||||
(save-excursion
|
||||
(doom--generate-autoloads (reverse enabled-targets)))
|
||||
;; Replace autoload paths (only for module autoloads) with absolute
|
||||
;; paths for faster resolution during load and simpler `load-path'
|
||||
(save-excursion
|
||||
(doom--expand-autoloads)
|
||||
(print! (green "✓ Expanded module autoload paths")))
|
||||
;; Generates stub definitions for functions/macros defined in disabled
|
||||
;; modules, so that you will never get a void-function when you use
|
||||
;; them.
|
||||
(save-excursion
|
||||
(doom--generate-autodefs (reverse targets) enabled-targets)
|
||||
(print! (green "✓ Generated autodefs")))
|
||||
;; Remove byte-compile inhibiting file variables so we can byte-compile
|
||||
;; the file, and autoload comments.
|
||||
(doom--cleanup-autoloads)
|
||||
(print! (green "✓ Clean up autoloads")))
|
||||
;; Byte compile it to give the file a chance to reveal errors.
|
||||
(doom--byte-compile-file doom-autoload-file)
|
||||
(doom--do-load doom-autoload-file)
|
||||
t)))
|
||||
|
||||
|
||||
;;
|
||||
;; Package autoloads
|
||||
|
||||
(defun doom--generate-package-autoloads ()
|
||||
(dolist (spec (doom-get-package-alist))
|
||||
(if-let* ((pkg (car spec))
|
||||
(desc (cdr spec)))
|
||||
(unless (memq pkg doom-autoload-excluded-packages)
|
||||
(let ((file (concat (package--autoloads-file-name desc) ".el")))
|
||||
(when (file-exists-p file)
|
||||
(insert "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n")
|
||||
(insert-file-contents file)
|
||||
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t)
|
||||
(unless (nth 8 (syntax-ppss))
|
||||
(replace-match "" t t)))
|
||||
(unless (bolp) (insert "\n"))
|
||||
(insert ")\n"))))
|
||||
(message "Couldn't find package desc for %s" (car spec)))))
|
||||
|
||||
(defun doom--generate-var-cache ()
|
||||
(doom-initialize-packages)
|
||||
(prin1 `(setq load-path ',load-path
|
||||
auto-mode-alist ',auto-mode-alist
|
||||
Info-directory-list ',Info-directory-list
|
||||
doom-disabled-packages ',doom-disabled-packages
|
||||
package-activated-list ',package-activated-list)
|
||||
(current-buffer)))
|
||||
|
||||
(defun doom--cleanup-package-autoloads ()
|
||||
(while (re-search-forward "^\\s-*\\((\\(?:add-to-list\\|\\(?:when\\|if\\) (boundp\\)\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)" nil t)
|
||||
(goto-char (match-beginning 1))
|
||||
(kill-sexp)))
|
||||
|
||||
(defun doom-reload-package-autoloads (&optional force-p)
|
||||
"Compiles `doom-package-autoload-file' from the autoloads files of all
|
||||
installed packages. It also caches `load-path', `Info-directory-list',
|
||||
`doom-disabled-packages', `package-activated-list' and `auto-mode-alist'.
|
||||
|
||||
Will do nothing if none of your installed packages have been modified. If
|
||||
FORCE-P (universal argument) is non-nil, regenerate it anyway.
|
||||
|
||||
This should be run whenever your `doom!' block or update your packages."
|
||||
(if (and (not force-p)
|
||||
(not doom-emacs-changed-p)
|
||||
(file-exists-p doom-package-autoload-file)
|
||||
(not (file-newer-than-file-p doom-packages-dir doom-package-autoload-file))
|
||||
(not (ignore-errors
|
||||
(cl-loop for key being the hash-keys of (doom-modules)
|
||||
for path = (doom-module-path (car key) (cdr key) "packages.el")
|
||||
if (file-newer-than-file-p path doom-package-autoload-file)
|
||||
return t))))
|
||||
(ignore (print! (green "Doom package autoloads is up-to-date"))
|
||||
(doom-initialize-autoloads doom-package-autoload-file))
|
||||
(let (case-fold-search)
|
||||
(doom-delete-autoloads-file doom-package-autoload-file)
|
||||
(with-temp-file doom-package-autoload-file
|
||||
(doom--generate-header 'doom-reload-package-autoloads)
|
||||
(save-excursion
|
||||
;; Cache the important and expensive-to-initialize state here.
|
||||
(doom--generate-var-cache)
|
||||
(print! (green "✓ Cached package state"))
|
||||
;; Loop through packages and concatenate all their autoloads files.
|
||||
(doom--generate-package-autoloads)
|
||||
(print! (green "✓ Package autoloads included")))
|
||||
;; Remove `load-path' and `auto-mode-alist' modifications (most of them,
|
||||
;; at least); they are cached later, so all those membership checks are
|
||||
;; unnecessary overhead.
|
||||
(doom--cleanup-package-autoloads)
|
||||
(print! (green "✓ Removed load-path/auto-mode-alist entries"))))
|
||||
(doom--byte-compile-file doom-package-autoload-file)
|
||||
(doom--do-load doom-package-autoload-file)
|
||||
t))
|
176
core/cli/byte-compile.el
Normal file
176
core/cli/byte-compile.el
Normal file
|
@ -0,0 +1,176 @@
|
|||
;;; core/cli/byte-compile.el -*- lexical-binding: t; -*-
|
||||
|
||||
(dispatcher! (compile c) (doom-byte-compile args)
|
||||
"Byte-compiles your config or selected modules.
|
||||
|
||||
compile [TARGETS...]
|
||||
compile :core :private lang/python
|
||||
compile feature lang
|
||||
|
||||
Accepts :core, :private and :plugins as special arguments, indicating you want
|
||||
to byte-compile Doom's core files, your private config or your ELPA plugins,
|
||||
respectively.")
|
||||
|
||||
(dispatcher! (recompile rc) (doom-byte-compile args 'recompile)
|
||||
"Re-byte-compiles outdated *.elc files.")
|
||||
|
||||
(dispatcher! clean (doom-clean-byte-compiled-files)
|
||||
"Delete all *.elc files.")
|
||||
|
||||
|
||||
;;
|
||||
;; Helpers
|
||||
|
||||
(defun doom-byte-compile (&optional modules recompile-p)
|
||||
"Byte compiles your emacs configuration.
|
||||
|
||||
init.el is always byte-compiled by this.
|
||||
|
||||
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-clean-byte-compiled-files' 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)
|
||||
(total-ok 0)
|
||||
(total-fail 0)
|
||||
(total-noop 0)
|
||||
compile-plugins-p
|
||||
targets)
|
||||
(dolist (module (delete-dups modules) (nreverse targets))
|
||||
(pcase module
|
||||
(":core" (push doom-core-dir targets))
|
||||
(":private" (push doom-private-dir targets))
|
||||
(":plugins"
|
||||
(cl-loop for (_name . desc) in (doom-get-package-alist)
|
||||
do (package--compile desc))
|
||||
(setq compile-plugins-p t
|
||||
modules (delete ":plugins" modules)))
|
||||
((pred file-directory-p)
|
||||
(push module targets))
|
||||
((pred (string-match "^\\([^/]+\\)/\\([^/]+\\)$"))
|
||||
(push (doom-module-locate-path
|
||||
(doom-keyword-intern (match-string 1 module))
|
||||
(intern (match-string 2 module)))
|
||||
targets))))
|
||||
(cl-block 'byte-compile
|
||||
;; If we're just here to byte-compile our plugins, we're done!
|
||||
(and (not modules)
|
||||
compile-plugins-p
|
||||
(cl-return-from 'byte-compile t))
|
||||
(unless (or (equal modules '(":core"))
|
||||
recompile-p)
|
||||
(unless (and (not doom-auto-accept)
|
||||
(y-or-n-p
|
||||
(concat "Warning: byte compiling is for advanced users. It will interfere with your\n"
|
||||
"efforts to debug issues. It is not recommended you do it if you frequently\n"
|
||||
"tinker with your Emacs config.\n\n"
|
||||
"Alternatively, use `bin/doom compile :core` instead to byte-compile only the\n"
|
||||
"Doom core files, as these don't change often.\n\n"
|
||||
"If you have issues, please make sure byte-compilation isn't the cause by using\n"
|
||||
"`bin/doom clean` to clear out your *.elc files.\n\n"
|
||||
"Byte-compile anyway?")))
|
||||
(message "Aborting.")
|
||||
(cl-return-from 'byte-compile)))
|
||||
(and (not recompile-p)
|
||||
(or (null modules) (equal modules '(":core")))
|
||||
(doom-clean-byte-compiled-files))
|
||||
(let (doom-emacs-changed-p
|
||||
noninteractive)
|
||||
;; 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.
|
||||
(unless (and (doom-initialize-autoloads doom-autoload-file)
|
||||
(doom-initialize-autoloads doom-package-autoload-file))
|
||||
(doom-reload-autoloads))
|
||||
(doom-initialize)
|
||||
(doom-initialize-modules 'force))
|
||||
;; If no targets were supplied, then we use your module list.
|
||||
(unless modules
|
||||
(setq targets (append (list doom-core-dir)
|
||||
(doom-module-load-path))))
|
||||
;; Assemble el files we want to compile; taking into account that
|
||||
;; MODULES may be a list of MODULE/SUBMODULE strings from the command
|
||||
;; line.
|
||||
(let ((target-files (doom-files-in targets :depth 1 :match "\\.el$"))
|
||||
(load-path load-path)
|
||||
kill-emacs-hook kill-buffer-query-functions)
|
||||
(unless target-files
|
||||
(if targets
|
||||
(message "Couldn't find any valid targets")
|
||||
(message "No targets to %scompile" (if recompile-p "re" "")))
|
||||
(cl-return-from 'byte-compile))
|
||||
(require 'use-package)
|
||||
(condition-case e
|
||||
(let ((use-package-defaults use-package-defaults)
|
||||
(use-package-expand-minimally t))
|
||||
;; 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)
|
||||
;; Always compile private init file
|
||||
(push (expand-file-name "init.el" doom-private-dir) target-files)
|
||||
(push (expand-file-name "init.el" doom-emacs-dir) target-files)
|
||||
(dolist (target (cl-delete-duplicates (mapcar #'file-truename target-files) :test #'equal))
|
||||
(if (or (not recompile-p)
|
||||
(let ((elc-file (byte-compile-dest-file target)))
|
||||
(and (file-exists-p elc-file)
|
||||
(file-newer-than-file-p target elc-file))))
|
||||
(let ((result (if (or (string-match-p "/\\(?:packages\\|doctor\\)\\.el$" target)
|
||||
(not (doom--file-cookie-p target)))
|
||||
'no-byte-compile
|
||||
(byte-compile-file target)))
|
||||
(short-name (if (file-in-directory-p target doom-emacs-dir)
|
||||
(file-relative-name target doom-emacs-dir)
|
||||
(abbreviate-file-name target))))
|
||||
(cl-incf
|
||||
(cond ((eq result 'no-byte-compile)
|
||||
(print! (dark (white "⚠ Ignored %s")) short-name)
|
||||
total-noop)
|
||||
((null result)
|
||||
(print! (red "✕ Failed to compile %s") short-name)
|
||||
total-fail)
|
||||
(t
|
||||
(print! (green "✓ Compiled %s") short-name)
|
||||
(quiet! (load target t t))
|
||||
total-ok))))
|
||||
(cl-incf total-noop)))
|
||||
(print! (bold (color (if (= total-fail 0) 'green 'red)
|
||||
"%s %d/%d file(s) (%d ignored)"))
|
||||
(if recompile-p "Recompiled" "Compiled")
|
||||
total-ok (- (length target-files) total-noop)
|
||||
total-noop))
|
||||
((debug error)
|
||||
(print! (red "\nThere were breaking errors.\n\n%s")
|
||||
"Reverting changes...")
|
||||
(signal 'doom-error (list 'byte-compile e))))))))
|
||||
|
||||
(defun doom-clean-byte-compiled-files ()
|
||||
"Delete all the compiled elc files in your Emacs configuration and private
|
||||
module. This does not include your byte-compiled, third party packages.'"
|
||||
(cl-loop with default-directory = doom-emacs-dir
|
||||
for path in (append (doom-files-in doom-emacs-dir :match "\\.elc$" :depth 0)
|
||||
(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))
|
||||
for truepath = (file-truename path)
|
||||
if (file-exists-p path)
|
||||
do (delete-file path)
|
||||
and do
|
||||
(print! (green "✓ Deleted %s")
|
||||
(if (file-in-directory-p truepath default-directory)
|
||||
(file-relative-name truepath)
|
||||
(abbreviate-file-name truepath)))
|
||||
finally do (print! (bold (green "Everything is clean")))))
|
7
core/cli/debug.el
Normal file
7
core/cli/debug.el
Normal file
|
@ -0,0 +1,7 @@
|
|||
;;; core/cli/debug.el -*- lexical-binding: t; -*-
|
||||
|
||||
(dispatcher! info (doom/info)
|
||||
"Output system info in markdown for bug reports.")
|
||||
|
||||
(dispatcher! (version v) (doom/version)
|
||||
"Reports the version of Doom and Emacs.")
|
167
core/cli/packages.el
Normal file
167
core/cli/packages.el
Normal file
|
@ -0,0 +1,167 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/cli/packages.el
|
||||
|
||||
(dispatcher! (install i) (doom--do #'doom-packages-install)
|
||||
"Installs requested packages that aren't installed.")
|
||||
|
||||
(dispatcher! (update u) (doom--do #'doom-packages-update)
|
||||
"Updates packages.")
|
||||
|
||||
(dispatcher! (autoremove r) (doom--do #'doom-packages-autoremove)
|
||||
"Removes packages that are no longer needed.")
|
||||
|
||||
|
||||
;;
|
||||
;; Helpers
|
||||
|
||||
(defsubst doom--do (fn)
|
||||
(doom-reload-doom-autoloads)
|
||||
(when (funcall fn doom-auto-accept)
|
||||
(doom-reload-package-autoloads)))
|
||||
|
||||
|
||||
;;
|
||||
;; Library
|
||||
|
||||
(defun doom-packages-install (&optional auto-accept-p)
|
||||
"Interactive command for installing missing packages."
|
||||
(print! "Looking for packages to install...")
|
||||
(let ((packages (reverse (doom-get-missing-packages))))
|
||||
(cond ((not packages)
|
||||
(print! (green "No packages to install!"))
|
||||
nil)
|
||||
|
||||
((not (or auto-accept-p
|
||||
(y-or-n-p
|
||||
(format "%s packages will be installed:\n\n%s\n\nProceed?"
|
||||
(length packages)
|
||||
(mapconcat
|
||||
(lambda (pkg)
|
||||
(format "+ %s (%s)"
|
||||
(car pkg)
|
||||
(cond ((doom-package-different-recipe-p (car pkg))
|
||||
"new recipe")
|
||||
((doom-package-different-backend-p (car pkg))
|
||||
(if (plist-get (cdr pkg) :recipe)
|
||||
"ELPA->QUELPA"
|
||||
"QUELPA->ELPA"))
|
||||
((plist-get (cdr pkg) :recipe)
|
||||
"QUELPA")
|
||||
("ELPA"))))
|
||||
(cl-sort (cl-copy-list packages) #'string-lessp
|
||||
:key #'car)
|
||||
"\n")))))
|
||||
(user-error "Aborted!"))
|
||||
|
||||
((let (success)
|
||||
(doom-refresh-packages-maybe doom-debug-mode)
|
||||
(dolist (pkg packages)
|
||||
(print! "Installing %s" (car pkg))
|
||||
(doom--condition-case!
|
||||
(let ((result
|
||||
(or (and (doom-package-installed-p (car pkg))
|
||||
(not (doom-package-different-backend-p (car pkg)))
|
||||
(not (doom-package-different-recipe-p (car pkg)))
|
||||
'already-installed)
|
||||
(and (doom-install-package (car pkg) (cdr pkg))
|
||||
(setq success t)
|
||||
'success)
|
||||
'failure))
|
||||
(pin-label
|
||||
(and (plist-member (cdr pkg) :pin)
|
||||
(format " [pinned: %s]" (plist-get (cdr pkg) :pin)))))
|
||||
(print! "%s%s"
|
||||
(pcase result
|
||||
(`already-installed (dark (white "⚠ ALREADY INSTALLED")))
|
||||
(`success (green "✓ DONE"))
|
||||
(`failure (red "✕ FAILED")))
|
||||
(or pin-label "")))))
|
||||
(print! (bold (green "Finished!")))
|
||||
(when success
|
||||
(set-file-times doom-packages-dir)
|
||||
(doom-delete-autoloads-file doom-package-autoload-file))
|
||||
success)))))
|
||||
|
||||
(defun doom-packages-update (&optional auto-accept-p)
|
||||
"Interactive command for updating packages."
|
||||
(print! "Looking for outdated packages...")
|
||||
(let ((packages (cl-sort (cl-copy-list (doom-get-outdated-packages)) #'string-lessp
|
||||
:key #'car)))
|
||||
(cond ((not packages)
|
||||
(print! (green "Everything is up-to-date"))
|
||||
nil)
|
||||
|
||||
((not (or auto-accept-p
|
||||
(y-or-n-p
|
||||
(format "%s packages will be updated:\n\n%s\n\nProceed?"
|
||||
(length packages)
|
||||
(let ((max-len
|
||||
(or (car (sort (mapcar (lambda (it) (length (symbol-name (car it)))) packages)
|
||||
#'>))
|
||||
10)))
|
||||
(mapconcat
|
||||
(lambda (pkg)
|
||||
(format (format "+ %%-%ds %%-%ds -> %%s" (+ max-len 2) 14)
|
||||
(symbol-name (car pkg))
|
||||
(package-version-join (cadr pkg))
|
||||
(package-version-join (cl-caddr pkg))))
|
||||
packages
|
||||
"\n"))))))
|
||||
(user-error "Aborted!"))
|
||||
|
||||
((let (success)
|
||||
(dolist (pkg packages)
|
||||
(print! "Updating %s" (car pkg))
|
||||
(doom--condition-case!
|
||||
(print!
|
||||
(let ((result (doom-update-package (car pkg) t)))
|
||||
(when result (setq success t))
|
||||
(color (if result 'green 'red)
|
||||
(if result "✓ DONE" "✕ FAILED"))))))
|
||||
(print! (bold (green "Finished!")))
|
||||
(when success
|
||||
(set-file-times doom-packages-dir)
|
||||
(doom-delete-autoloads-file doom-package-autoload-file))
|
||||
success)))))
|
||||
|
||||
(defun doom-packages-autoremove (&optional auto-accept-p)
|
||||
"Interactive command for auto-removing orphaned packages."
|
||||
(print! "Looking for orphaned packages...")
|
||||
(let ((packages (doom-get-orphaned-packages)))
|
||||
(cond ((not packages)
|
||||
(print! (green "No unused packages to remove"))
|
||||
nil)
|
||||
|
||||
((not
|
||||
(or auto-accept-p
|
||||
(y-or-n-p
|
||||
(format "%s packages will be deleted:\n\n%s\n\nProceed?"
|
||||
(length packages)
|
||||
(mapconcat
|
||||
(lambda (sym)
|
||||
(let ((backend (doom-package-backend sym)))
|
||||
(format "+ %s (%s)" sym
|
||||
(if (doom-package-different-backend-p sym)
|
||||
(pcase backend
|
||||
(`quelpa "QUELPA->ELPA")
|
||||
(`elpa "ELPA->QUELPA")
|
||||
(_ "removed"))
|
||||
(upcase (symbol-name backend))))))
|
||||
(sort (cl-copy-list packages) #'string-lessp)
|
||||
"\n")))))
|
||||
(user-error "Aborted!"))
|
||||
|
||||
((let (success)
|
||||
(dolist (pkg packages)
|
||||
(doom--condition-case!
|
||||
(let ((result (doom-delete-package pkg t)))
|
||||
(if result (setq success t))
|
||||
(print! (color (if result 'green 'red)
|
||||
"%s %s"
|
||||
(if result "✓ Removed" "✕ Failed to remove")
|
||||
pkg)))))
|
||||
(print! (bold (green "Finished!")))
|
||||
(when success
|
||||
(set-file-times doom-packages-dir)
|
||||
(doom-delete-autoloads-file doom-package-autoload-file))
|
||||
success)))))
|
85
core/cli/patch-macos.el
Normal file
85
core/cli/patch-macos.el
Normal file
|
@ -0,0 +1,85 @@
|
|||
;;; core/cli/patch-macos.el -*- lexical-binding: t; -*-
|
||||
|
||||
(dispatcher! (patch-macos)
|
||||
(doom-patch-macos (or (member "--undo" args)
|
||||
(member "-u" args))
|
||||
(doom--find-emacsapp-path))
|
||||
"Patches Emacs.app to respect your shell environment.
|
||||
|
||||
A common issue with GUI Emacs on MacOS is that it launches in an environment
|
||||
independent of your shell configuration, including your PATH and any other
|
||||
utilities like rbenv, rvm or virtualenv.
|
||||
|
||||
This patch fixes this by patching Emacs.app (in /Applications or
|
||||
~/Applications). It will:
|
||||
|
||||
1. Move Contents/MacOS/Emacs to Contents/MacOS/RunEmacs
|
||||
2. And replace Contents/MacOS/Emacs with the following wrapper script:
|
||||
|
||||
#!/bin/bash
|
||||
args=\"$@\"
|
||||
pwd=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\"; pwd -P)\"
|
||||
exec \"$SHELL\" -l -c \"$pwd/RunEmacs $args\"
|
||||
|
||||
This ensures that Emacs is always aware of your shell environment, regardless of
|
||||
how it is launched.
|
||||
|
||||
It can be undone with the --undo or -u options.
|
||||
|
||||
Alternatively, you can install the exec-path-from-shell Emacs plugin, which will
|
||||
scrape your shell environment remotely, at startup. However, this can be slow
|
||||
depending on your shell configuration and isn't always reliable.")
|
||||
|
||||
|
||||
;;
|
||||
;; Library
|
||||
|
||||
(defun doom--find-emacsapp-path ()
|
||||
(or (getenv "EMACS_APP_PATH")
|
||||
(cl-loop for dir in (list "/usr/local/opt/emacs"
|
||||
"/usr/local/opt/emacs-plus"
|
||||
"/Applications"
|
||||
"~/Applications")
|
||||
for appdir = (concat dir "/Emacs.app")
|
||||
if (file-directory-p appdir)
|
||||
return appdir)
|
||||
(user-error "Couldn't find Emacs.app")))
|
||||
|
||||
(defun doom-patch-macos (undo-p appdir)
|
||||
"Patches Emacs.app to respect your shell environment."
|
||||
(unless IS-MAC
|
||||
(user-error "You don't seem to be running MacOS"))
|
||||
(unless (file-directory-p appdir)
|
||||
(user-error "Couldn't find '%s'" appdir))
|
||||
(let ((oldbin (expand-file-name "Contents/MacOS/Emacs" appdir))
|
||||
(newbin (expand-file-name "Contents/MacOS/RunEmacs" appdir)))
|
||||
(cond (undo-p
|
||||
(unless (file-exists-p newbin)
|
||||
(user-error "Emacs.app is not patched"))
|
||||
(copy-file newbin oldbin 'ok-if-already-exists nil nil 'preserve-permissions)
|
||||
(unless (file-exists-p oldbin)
|
||||
(error "Failed to copy %s to %s" newbin oldbin))
|
||||
(delete-file newbin)
|
||||
(message "%s successfully unpatched" appdir))
|
||||
|
||||
((file-exists-p newbin)
|
||||
(user-error "%s is already patched" appdir))
|
||||
|
||||
((or doom-auto-accept
|
||||
(y-or-n-p
|
||||
(concat "Doom would like to patch your Emacs.app bundle so that it respects\n"
|
||||
"your shell configuration. For more information on why and how, run\n\n"
|
||||
" bin/doom help patch-macos\n\n"
|
||||
"Patch Emacs.app?")))
|
||||
(message "Patching '%s'" appdir)
|
||||
(copy-file oldbin newbin nil nil nil 'preserve-permissions)
|
||||
(unless (file-exists-p newbin)
|
||||
(error "Failed to copy %s to %s" oldbin newbin))
|
||||
(with-temp-buffer
|
||||
(insert "#!/bin/bash\n"
|
||||
"args=\"$@\"\n"
|
||||
"pwd=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\"; pwd -P)\"\n"
|
||||
"exec \"$SHELL\" -l -c \"$pwd/RunEmacs $args\"")
|
||||
(write-file oldbin)
|
||||
(chmod oldbin (file-modes newbin)))
|
||||
(message "%s successfully patched" appdir)))))
|
59
core/cli/quickstart.el
Normal file
59
core/cli/quickstart.el
Normal file
|
@ -0,0 +1,59 @@
|
|||
;;; core/cli/quickstart.el -*- lexical-binding: t; -*-
|
||||
|
||||
(dispatcher! (quickstart qs) (doom-quickstart)
|
||||
"Quickly deploy a private module and Doom.
|
||||
|
||||
This deploys a barebones config to ~/.doom.d. The destination can be changed
|
||||
with the -p option, e.g.
|
||||
|
||||
doom -p ~/.config/doom quickstart
|
||||
|
||||
This command will refuse to overwrite the private directory if it already
|
||||
exists.")
|
||||
|
||||
|
||||
;;
|
||||
;; Library
|
||||
|
||||
(defun doom-quickstart ()
|
||||
"Quickly deploy a private module and Doom.
|
||||
|
||||
This deploys a barebones config to `doom-private-dir', installs all missing
|
||||
packages and regenerates the autoloads file."
|
||||
;; Create `doom-private-dir'
|
||||
(let ((short-private-dir (abbreviate-file-name doom-private-dir)))
|
||||
(if (file-directory-p doom-private-dir)
|
||||
(print! (yellow "%s directory already exists. Skipping.") short-private-dir)
|
||||
(print! "Creating %s" short-private-dir)
|
||||
(make-directory doom-private-dir t)
|
||||
(print! (green "Done!")))
|
||||
;; Create init.el
|
||||
(let ((init-file (expand-file-name "init.el" doom-private-dir)))
|
||||
(if (file-exists-p init-file)
|
||||
(print! (yellow "%sinit.el already exists. Skipping.") short-private-dir)
|
||||
(print! "Copying init.example.el to %s" short-private-dir)
|
||||
(copy-file (expand-file-name "init.example.el" doom-emacs-dir)
|
||||
init-file)
|
||||
(print! (green "Done!"))))
|
||||
;; Create config.el
|
||||
(let ((config-file (expand-file-name "config.el" doom-private-dir)))
|
||||
(if (file-exists-p config-file)
|
||||
(print! "%sconfig.el already exists. Skipping." short-private-dir)
|
||||
(print! "Deploying empty config.el file in %s" short-private-dir)
|
||||
(with-temp-file config-file (insert ""))
|
||||
(print! (green "Done!")))))
|
||||
;; Ask if Emacs.app should be patched
|
||||
(when IS-MAC
|
||||
(message "MacOS detected")
|
||||
(condition-case e
|
||||
(doom-patch-macos nil (doom--find-emacsapp-path))
|
||||
(user-error (message "%s" (error-message-string e)))))
|
||||
;; Install Doom packages
|
||||
(print! "Installing plugins")
|
||||
(doom-packages-install doom-auto-accept)
|
||||
(print! "Regenerating autoloads files")
|
||||
(doom-reload-autoloads nil 'force-p)
|
||||
(print! (bold (green "\nFinished! Doom is ready to go!\n")))
|
||||
(with-temp-buffer
|
||||
(doom-template-insert "QUICKSTART_INTRO")
|
||||
(print! (buffer-string))))
|
|
@ -1,4 +1,11 @@
|
|||
;;; core/core-tests.el -*- lexical-binding: t; -*-
|
||||
;;; core/cli/test.el -*- lexical-binding: t; -*-
|
||||
|
||||
(dispatcher! test (doom-run-tests args)
|
||||
"Run Doom unit tests.")
|
||||
|
||||
|
||||
;;
|
||||
;; Library
|
||||
|
||||
(defun doom-run-tests (&optional modules)
|
||||
"Run all loaded tests, specified by MODULES (a list of module cons cells) or
|
||||
|
@ -25,9 +32,7 @@ If neither is available, run all tests in all enabled modules."
|
|||
nconc (cl-loop for dir in doom-modules-dirs
|
||||
for path = (expand-file-name arg dir)
|
||||
if (file-directory-p path)
|
||||
nconc
|
||||
(doom-files-in
|
||||
path :type 'dirs :depth 1 :full t))
|
||||
nconc (doom-files-in path :type 'dirs :depth 1 :full t))
|
||||
finally do (setq argv nil))))
|
||||
|
||||
(modules ; cons-cells given to MODULES
|
||||
|
@ -54,7 +59,7 @@ If neither is available, run all tests in all enabled modules."
|
|||
|
||||
|
||||
;;
|
||||
(defmacro def-test! (_name &rest _body))
|
||||
;; Test library
|
||||
|
||||
(defmacro insert! (&rest text)
|
||||
"Insert TEXT in buffer, then move cursor to last {0} marker."
|
||||
|
@ -62,6 +67,3 @@ If neither is available, run all tests in all enabled modules."
|
|||
(insert ,@text)
|
||||
(when (search-backward "{0}" nil t)
|
||||
(replace-match "" t t))))
|
||||
|
||||
(provide 'core-tests)
|
||||
;;; core-tests.el ends here
|
76
core/cli/upgrade.el
Normal file
76
core/cli/upgrade.el
Normal file
|
@ -0,0 +1,76 @@
|
|||
;;; core/cli/upgrade.el -*- lexical-binding: t; -*-
|
||||
|
||||
(dispatcher! (upgrade up) (doom-upgrade)
|
||||
"Checks out the latest Doom on this branch.")
|
||||
|
||||
|
||||
;;
|
||||
;; Quality of Life Commands
|
||||
|
||||
(defvar doom-repo-url "https://github.com/hlissner/doom-emacs"
|
||||
"TODO")
|
||||
(defvar doom-repo-remote "_upgrade"
|
||||
"TODO")
|
||||
|
||||
(defun doom--working-tree-dirty-p (dir)
|
||||
(with-temp-buffer
|
||||
(let ((default-directory dir))
|
||||
(if (zerop (process-file "git" nil (current-buffer) nil
|
||||
"status" "--porcelain" "-uno"))
|
||||
(string-match-p "[^ \t\n]" (buffer-string))
|
||||
(error "Failed to check working tree in %s" dir)))))
|
||||
|
||||
(defun doom-upgrade ()
|
||||
"Upgrade Doom to the latest version non-destructively."
|
||||
(require 'vc-git)
|
||||
(let* ((gitdir (expand-file-name ".git" doom-emacs-dir))
|
||||
(branch (vc-git--symbolic-ref doom-emacs-dir))
|
||||
(default-directory doom-emacs-dir))
|
||||
(unless (file-exists-p gitdir)
|
||||
(error "Couldn't find %s. Was Doom cloned properly?"
|
||||
(abbreviate-file-name gitdir)))
|
||||
(unless branch
|
||||
(error "Couldn't detect what branch you're using. Is Doom detached?"))
|
||||
(when (doom--working-tree-dirty-p doom-emacs-dir)
|
||||
(user-error "Refusing to upgrade because Doom has been modified. Stash or undo your changes"))
|
||||
(with-temp-buffer
|
||||
(let ((buf (current-buffer)))
|
||||
(condition-case-unless-debug e
|
||||
(progn
|
||||
(process-file "git" nil buf nil "remote" "remove" doom-repo-remote)
|
||||
(unless (zerop (process-file "git" nil buf nil "remote" "add"
|
||||
doom-repo-remote doom-repo-url))
|
||||
(error "Failed to add %s to remotes" doom-repo-remote))
|
||||
(unless (zerop (process-file "git" nil buf nil "fetch" "--tags"
|
||||
doom-repo-remote branch))
|
||||
(error "Failed to fetch from upstream"))
|
||||
(let ((current-rev (vc-git-working-revision doom-emacs-dir))
|
||||
(rev (string-trim (shell-command-to-string (format "git rev-parse %s/%s" doom-repo-remote branch)))))
|
||||
(unless rev
|
||||
(error "Couldn't detect Doom's version. Is %s a repo?"
|
||||
(abbreviate-file-name doom-emacs-dir)))
|
||||
(when (equal current-rev rev)
|
||||
(user-error "Doom is up to date!"))
|
||||
(message "Updates for Doom are available!\n\n Old revision: %s\n New revision: %s\n"
|
||||
current-rev rev)
|
||||
(message "Comparision diff: https://github.com/hlissner/doom-emacs/compare/%s...%s\n"
|
||||
(substring current-rev 0 10) (substring rev 0 10))
|
||||
;; TODO Display newsletter diff
|
||||
(unless (or doom-auto-accept (y-or-n-p "Proceed?"))
|
||||
(user-error "Aborted"))
|
||||
(message "Removing byte-compiled files from your config (if any)")
|
||||
(doom-clean-byte-compiled-files)
|
||||
(unless (zerop (process-file "git" nil buf nil "reset" "--hard"
|
||||
(format "%s/%s" doom-repo-remote branch)))
|
||||
(error "An error occurred while checking out the latest commit\n\n%s"
|
||||
(buffer-string)))
|
||||
(unless (equal (vc-git-working-revision doom-emacs-dir) rev)
|
||||
(error "Failed to checkout latest commit.\n\n%s" (buffer-string)))
|
||||
(doom-refresh 'force)
|
||||
(message "Done! Please restart Emacs for changes to take effect")))
|
||||
(user-error
|
||||
(message "%s Aborting." (error-message-string e)))
|
||||
(error
|
||||
(message "There was an unexpected error.\n\n%s\n\nOutput:\n%s"
|
||||
(car e)
|
||||
(buffer-string))))))))
|
819
core/core-cli.el
819
core/core-cli.el
|
@ -3,9 +3,10 @@
|
|||
;; Eagerly load these libraries because this module may be loaded in a session
|
||||
;; that hasn't been fully initialized (where autoloads files haven't been
|
||||
;; generated or `load-path' populated).
|
||||
(load! "autoload/packages")
|
||||
(load! "autoload/debug")
|
||||
(load! "autoload/files")
|
||||
(load! "autoload/message")
|
||||
(load! "autoload/packages")
|
||||
|
||||
|
||||
;;
|
||||
|
@ -38,7 +39,7 @@ commands like `doom-packages-install', `doom-packages-update' and
|
|||
omitted, show all available commands, their aliases and brief descriptions."
|
||||
(if command
|
||||
(princ (doom--dispatch-format desc))
|
||||
(print! (bold "%-10s\t%s\t%s" "Command:" "Alias" "Description"))
|
||||
(print! (bold "%-10s\t%s\t%s") "Command:" "Alias" "Description")
|
||||
(dolist (spec (cl-sort doom--dispatch-command-alist #'string-lessp
|
||||
:key #'car))
|
||||
(cl-destructuring-bind (command &key desc _body) spec
|
||||
|
@ -80,7 +81,7 @@ BODY will be run when this dispatcher is called."
|
|||
(setf (alist-get alias doom--dispatch-alias-alist) ',cmd))))
|
||||
`((setf (alist-get ',cmd doom--dispatch-command-alist)
|
||||
(list :desc ,docstring
|
||||
:body (lambda (args) ,form))))))))
|
||||
:body (lambda (args) (ignore args) ,form))))))))
|
||||
|
||||
|
||||
;;
|
||||
|
@ -99,142 +100,28 @@ additional overhead for be started this way. For the best performance, it
|
|||
is best to run Doom out of ~/.emacs.d and ~/.doom.d.")
|
||||
|
||||
(dispatcher! (doctor doc) :noop
|
||||
"Checks for issues with your current Doom config.")
|
||||
"Checks for issues with your current Doom config.
|
||||
|
||||
Also checks for missing optional dependencies for all enabled modules.")
|
||||
|
||||
(dispatcher! (help h) :noop
|
||||
"Look up additional information about a command.")
|
||||
|
||||
|
||||
;; Real dispatchers
|
||||
(dispatcher! (quickstart qs) (doom-quickstart)
|
||||
"Quickly deploy a private module and Doom.
|
||||
;;
|
||||
;; Real dispatch commands
|
||||
|
||||
This deploys a barebones config to ~/.doom.d. The destination can be changed
|
||||
with the -p option, e.g.
|
||||
|
||||
doom -p ~/.config/doom quickstart
|
||||
|
||||
This command will refuse to overwrite the private directory if it already
|
||||
exists.")
|
||||
|
||||
(dispatcher! (install i) (doom--do 'doom-packages-install)
|
||||
"Installs requested plugins that aren't installed.")
|
||||
|
||||
(dispatcher! (update u) (doom--do 'doom-packages-update)
|
||||
"Installs requested plugins that aren't installed.")
|
||||
|
||||
(dispatcher! (autoremove r) (doom--do 'doom-packages-autoremove)
|
||||
"Installs requested plugins that aren't installed.")
|
||||
|
||||
(dispatcher! (autoloads a) (doom-reload-autoloads nil 'force)
|
||||
"Regenerates Doom's autoloads file.
|
||||
|
||||
This file tells Emacs where to find your module's autoloaded functions and
|
||||
plugins.")
|
||||
|
||||
|
||||
(dispatcher! (upgrade up) (doom-upgrade)
|
||||
"Checks out the latest Doom on this branch.")
|
||||
|
||||
(dispatcher! (compile c) (doom-byte-compile args)
|
||||
"Byte-compiles your config or selected modules.
|
||||
|
||||
compile [TARGETS...]
|
||||
compile :core :private lang/python
|
||||
compile feature lang
|
||||
|
||||
Accepts :core, :private and :plugins as special arguments, indicating you want
|
||||
to byte-compile Doom's core files, your private config or your ELPA plugins,
|
||||
respectively.")
|
||||
|
||||
(dispatcher! (compile c) (doom-byte-compile args)
|
||||
"Byte-compiles your config or selected modules.
|
||||
|
||||
compile [TARGETS...]
|
||||
compile :core :private lang/python
|
||||
compile feature lang
|
||||
|
||||
Accepts :core, :private and :plugins as special arguments, indicating you want
|
||||
to byte-compile Doom's core files, your private config or your ELPA plugins,
|
||||
respectively.")
|
||||
|
||||
(dispatcher! (recompile rc) (doom-byte-compile args 'recompile)
|
||||
"Re-byte-compiles outdated *.elc files.")
|
||||
|
||||
(dispatcher! clean (doom-clean-byte-compiled-files)
|
||||
"Delete all *.elc files.")
|
||||
|
||||
|
||||
(dispatcher! test
|
||||
(progn (require 'core-tests)
|
||||
(doom-run-tests args))
|
||||
"Run Doom unit tests.")
|
||||
|
||||
(dispatcher! info (doom/info)
|
||||
"Output system info in markdown for bug reports.")
|
||||
|
||||
(dispatcher! (version v) (doom/version)
|
||||
"Reports the version of Doom and Emacs.")
|
||||
|
||||
(dispatcher! (refresh re) (doom-refresh 'force)
|
||||
"Refresh Doom. Same as autoremove+install+autoloads.
|
||||
|
||||
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)")
|
||||
|
||||
(dispatcher! (patch-macos)
|
||||
(doom-patch-macos (or (member "--undo" args)
|
||||
(member "-u" args))
|
||||
(doom--find-emacsapp-path))
|
||||
"Patches Emacs.app to respect your shell environment.
|
||||
|
||||
A common issue with GUI Emacs on MacOS is that it launches in an environment
|
||||
independent of your shell configuration, including your PATH and any other
|
||||
utilities like rbenv, rvm or virtualenv.
|
||||
|
||||
This patch fixes this by patching Emacs.app (in /Applications or
|
||||
~/Applications). It will:
|
||||
|
||||
1. Move Contents/MacOS/Emacs to Contents/MacOS/RunEmacs
|
||||
2. And replace Contents/MacOS/Emacs with the following wrapper script:
|
||||
|
||||
#!/bin/bash
|
||||
args=\"$@\"
|
||||
pwd=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\"; pwd -P)\"
|
||||
exec \"$SHELL\" -l -c \"$pwd/RunEmacs $args\"
|
||||
|
||||
This ensures that Emacs is always aware of your shell environment, regardless of
|
||||
how it is launched.
|
||||
|
||||
It can be undone with the --undo or -u options.
|
||||
|
||||
Alternatively, you can install the exec-path-from-shell Emacs plugin, which will
|
||||
scrape your shell environment remotely, at startup. However, this can be slow
|
||||
depending on your shell configuration and isn't always reliable.")
|
||||
(load! "cli/autoloads")
|
||||
(load! "cli/byte-compile")
|
||||
(load! "cli/debug")
|
||||
(load! "cli/packages")
|
||||
(load! "cli/patch-macos")
|
||||
(load! "cli/quickstart")
|
||||
(load! "cli/upgrade")
|
||||
(load! "cli/test")
|
||||
|
||||
|
||||
;;
|
||||
;; Quality of Life Commands
|
||||
;;
|
||||
|
||||
(defvar doom-repo-url "https://github.com/hlissner/doom-emacs"
|
||||
"TODO")
|
||||
(defvar doom-repo-remote "_upgrade"
|
||||
"TODO")
|
||||
|
||||
(defun doom--working-tree-dirty-p (dir)
|
||||
(with-temp-buffer
|
||||
(let ((default-directory dir))
|
||||
(if (zerop (process-file "git" nil (current-buffer) nil
|
||||
"status" "--porcelain" "-uno"))
|
||||
(string-match-p "[^ \t\n]" (buffer-string))
|
||||
(error "Failed to check working tree in %s" dir)))))
|
||||
|
||||
(defun doom-refresh (&optional force-p)
|
||||
"Ensure Doom is in a working state by checking autoloads and packages, and
|
||||
recompiling any changed compiled files. This is the shotgun solution to most
|
||||
|
@ -249,672 +136,16 @@ problems with doom."
|
|||
(doom-reload-package-autoloads force-p)
|
||||
(doom-byte-compile nil 'recompile)))
|
||||
|
||||
(defun doom-upgrade ()
|
||||
"Upgrade Doom to the latest version non-destructively."
|
||||
(require 'vc-git)
|
||||
(let* ((gitdir (expand-file-name ".git" doom-emacs-dir))
|
||||
(branch (vc-git--symbolic-ref doom-emacs-dir))
|
||||
(default-directory doom-emacs-dir))
|
||||
(unless (file-exists-p gitdir)
|
||||
(error "Couldn't find %s. Was Doom cloned properly?"
|
||||
(abbreviate-file-name gitdir)))
|
||||
(unless branch
|
||||
(error "Couldn't detect what branch you're using. Is Doom detached?"
|
||||
(abbreviate-file-name doom-emacs-dir)))
|
||||
(when (doom--working-tree-dirty-p doom-emacs-dir)
|
||||
(user-error "Refusing to upgrade because Doom has been modified. Stash or undo your changes"))
|
||||
(with-temp-buffer
|
||||
(let ((buf (current-buffer)))
|
||||
(condition-case-unless-debug e
|
||||
(progn
|
||||
(process-file "git" nil buf nil "remote" "remove" doom-repo-remote)
|
||||
(unless (zerop (process-file "git" nil buf nil "remote" "add"
|
||||
doom-repo-remote doom-repo-url))
|
||||
(error "Failed to add %s to remotes" doom-repo-remote))
|
||||
(unless (zerop (process-file "git" nil buf nil "fetch" "--tags"
|
||||
doom-repo-remote branch))
|
||||
(error "Failed to fetch from upstream"))
|
||||
(let ((current-rev (vc-git-working-revision doom-emacs-dir))
|
||||
(rev (string-trim (shell-command-to-string (format "git rev-parse %s/%s" doom-repo-remote branch)))))
|
||||
(unless rev
|
||||
(error "Couldn't detect Doom's version. Is %s a repo?"
|
||||
(abbreviate-file-name doom-emacs-dir)))
|
||||
(when (equal current-rev rev)
|
||||
(user-error "Doom is up to date!"))
|
||||
(message "Updates for Doom are available!\n\n Old revision: %s\n New revision: %s\n"
|
||||
current-rev rev)
|
||||
(message "Comparision diff: https://github.com/hlissner/doom-emacs/compare/%s...%s\n"
|
||||
(substring current-rev 0 10) (substring rev 0 10))
|
||||
;; TODO Display newsletter diff
|
||||
(unless (or doom-auto-accept (y-or-n-p "Proceed?"))
|
||||
(user-error "Aborted"))
|
||||
(message "Removing byte-compiled files from your config (if any)")
|
||||
(doom-clean-byte-compiled-files)
|
||||
(unless (zerop (process-file "git" nil buf nil "reset" "--hard"
|
||||
(format "%s/%s" doom-repo-remote branch)))
|
||||
(error "An error occurred while checking out the latest commit\n\n%s"
|
||||
(buffer-string)))
|
||||
(unless (equal (vc-git-working-revision doom-emacs-dir) rev)
|
||||
(error "Failed to checkout latest commit.\n\n%s" (buffer-string)))
|
||||
(doom-refresh 'force)
|
||||
(message "Done! Please restart Emacs for changes to take effect")))
|
||||
(user-error
|
||||
(message "%s Aborting." (error-message-string e)))
|
||||
(error
|
||||
(message "There was an unexpected error.\n\n%s -> %s\n\nOutput:\n%s"
|
||||
(car e)
|
||||
(buffer-string))))))))
|
||||
(dispatcher! (refresh re) (doom-refresh 'force)
|
||||
"Refresh Doom. Same as autoremove+install+autoloads.
|
||||
|
||||
(defun doom--find-emacsapp-path ()
|
||||
(or (getenv "EMACS_APP_PATH")
|
||||
(cl-loop for dir in (list "/usr/local/opt/emacs"
|
||||
"/usr/local/opt/emacs-plus"
|
||||
"/Applications"
|
||||
"~/Applications")
|
||||
for appdir = (concat dir "/Emacs.app")
|
||||
if (file-directory-p appdir)
|
||||
return appdir)
|
||||
(user-error "Couldn't find Emacs.app")))
|
||||
This is the equivalent of running autoremove, install, autoloads, then
|
||||
recompile. Run this whenever you:
|
||||
|
||||
(defun doom-quickstart ()
|
||||
"Quickly deploy a private module and Doom.
|
||||
|
||||
This deploys a barebones config to `doom-private-dir', installs all missing
|
||||
packages and regenerates the autoloads file."
|
||||
;; Create `doom-private-dir'
|
||||
(let ((short-private-dir (abbreviate-file-name doom-private-dir)))
|
||||
(if (file-directory-p doom-private-dir)
|
||||
(print! (yellow "%s directory already exists. Skipping." short-private-dir))
|
||||
(print! "Creating %s" short-private-dir)
|
||||
(make-directory doom-private-dir t)
|
||||
(print! (green "Done!")))
|
||||
;; Create init.el
|
||||
(let ((init-file (expand-file-name "init.el" doom-private-dir)))
|
||||
(if (file-exists-p init-file)
|
||||
(print! (yellow "%sinit.el already exists. Skipping." short-private-dir))
|
||||
(print! "Copying init.example.el to %s" short-private-dir)
|
||||
(copy-file (expand-file-name "init.example.el" doom-emacs-dir)
|
||||
init-file)
|
||||
(print! (green "Done!"))))
|
||||
;; Create config.el
|
||||
(let ((config-file (expand-file-name "config.el" doom-private-dir)))
|
||||
(if (file-exists-p config-file)
|
||||
(print! "%sconfig.el already exists. Skipping." short-private-dir)
|
||||
(print! "Deploying empty config.el file in %s" short-private-dir)
|
||||
(with-temp-file config-file (insert ""))
|
||||
(print! (green "Done!")))))
|
||||
;; Ask if Emacs.app should be patched
|
||||
(when IS-MAC
|
||||
(message "MacOS detected")
|
||||
(condition-case e
|
||||
(doom-patch-macos nil (doom--find-emacsapp-path))
|
||||
(user-error (message "%s" (error-message-string e)))))
|
||||
;; Install Doom packages
|
||||
(print! "Installing plugins")
|
||||
(doom-packages-install doom-auto-accept)
|
||||
(print! "Regenerating autoloads files")
|
||||
(doom-reload-autoloads nil 'force-p)
|
||||
(print! (bold (green "\nFinished! Doom is ready to go!\n")))
|
||||
(with-temp-buffer
|
||||
(doom-template-insert "QUICKSTART_INTRO")
|
||||
(print! (buffer-string))))
|
||||
|
||||
(defun doom-patch-macos (undo-p appdir)
|
||||
"Patches Emacs.app to respect your shell environment."
|
||||
(unless IS-MAC
|
||||
(user-error "You don't seem to be running MacOS"))
|
||||
(unless (file-directory-p appdir)
|
||||
(user-error "Couldn't find '%s'" appdir))
|
||||
(let ((oldbin (expand-file-name "Contents/MacOS/Emacs" appdir))
|
||||
(newbin (expand-file-name "Contents/MacOS/RunEmacs" appdir)))
|
||||
(cond (undo-p
|
||||
(unless (file-exists-p newbin)
|
||||
(user-error "Emacs.app is not patched"))
|
||||
(copy-file newbin oldbin 'ok-if-already-exists nil nil 'preserve-permissions)
|
||||
(unless (file-exists-p oldbin)
|
||||
(error "Failed to copy %s to %s" newbin oldbin))
|
||||
(delete-file newbin)
|
||||
(message "%s successfully unpatched" appdir))
|
||||
|
||||
((file-exists-p newbin)
|
||||
(user-error "%s is already patched" appdir))
|
||||
|
||||
((or doom-auto-accept
|
||||
(y-or-n-p
|
||||
(concat "Doom would like to patch your Emacs.app bundle so that it respects\n"
|
||||
"your shell configuration. For more information on why and how, run\n\n"
|
||||
" bin/doom help patch-macos\n\n"
|
||||
"Patch Emacs.app?")))
|
||||
(message "Patching '%s'" appdir)
|
||||
(copy-file oldbin newbin nil nil nil 'preserve-permissions)
|
||||
(unless (file-exists-p newbin)
|
||||
(error "Failed to copy %s to %s" oldbin newbin))
|
||||
(with-temp-buffer
|
||||
(insert "#!/bin/bash\n"
|
||||
"args=\"$@\"\n"
|
||||
"pwd=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\"; pwd -P)\"\n"
|
||||
"exec \"$SHELL\" -l -c \"$pwd/RunEmacs $args\"")
|
||||
(write-file oldbin)
|
||||
(chmod oldbin (file-modes newbin)))
|
||||
(message "%s successfully patched" appdir)))))
|
||||
|
||||
|
||||
;;
|
||||
;; Autoload file generation
|
||||
;;
|
||||
|
||||
(defvar doom-autoload-excluded-packages '(marshal gh)
|
||||
"Packages that 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.")
|
||||
|
||||
(defun doom-delete-autoloads-file (file)
|
||||
"Delete FILE (an autoloads file), and delete the accompanying *.elc file, if
|
||||
it exists."
|
||||
(cl-check-type file string)
|
||||
(when (file-exists-p file)
|
||||
(when-let* ((buf (find-buffer-visiting doom-autoload-file)))
|
||||
(with-current-buffer buf
|
||||
(set-buffer-modified-p nil))
|
||||
(kill-buffer buf))
|
||||
(delete-file file)
|
||||
(ignore-errors (delete-file (byte-compile-dest-file file)))
|
||||
(message "Deleted old %s" (file-name-nondirectory file))))
|
||||
|
||||
(defun doom--do (fn)
|
||||
(doom-reload-doom-autoloads)
|
||||
(when (funcall fn doom-auto-accept)
|
||||
(doom-reload-package-autoloads)))
|
||||
|
||||
(defun doom--warn-refresh-session ()
|
||||
(message "Detected a running Emacs session.\n")
|
||||
(message "Restart Emacs or use `M-x doom/reload' for changes to take effect."))
|
||||
|
||||
(defun doom--do-load (&rest files)
|
||||
(if (and noninteractive (not (daemonp)))
|
||||
(progn
|
||||
(require 'server)
|
||||
(when (server-running-p)
|
||||
(add-hook 'kill-emacs-hook #'doom--warn-refresh-session)))
|
||||
(dolist (file files)
|
||||
(load-file (byte-compile-dest-file file)))))
|
||||
|
||||
(defun doom--byte-compile-file (file)
|
||||
(let ((short-name (file-name-nondirectory file))
|
||||
(byte-compile-dynamic-docstrings t))
|
||||
(condition-case e
|
||||
(when (byte-compile-file file)
|
||||
;; Give autoloads file a chance to report error
|
||||
(load (if doom-debug-mode
|
||||
file
|
||||
(byte-compile-dest-file file))
|
||||
nil t)
|
||||
(unless noninteractive
|
||||
(message "Finished compiling %s" short-name)))
|
||||
((debug error)
|
||||
(let ((backup-file (concat file ".bk")))
|
||||
(message "Copied backup to %s" backup-file)
|
||||
(copy-file file backup-file 'overwrite))
|
||||
(doom-delete-autoloads-file file)
|
||||
(signal 'doom-autoload-error (list short-name e))))))
|
||||
|
||||
(defun doom-reload-autoloads (&optional file force-p)
|
||||
"Reloads FILE (an autoload file), if it needs reloading.
|
||||
|
||||
FILE should be one of `doom-autoload-file' or `doom-package-autoload-file'. If
|
||||
it is nil, it will try to reload both. If FORCE-P (universal argument) do it
|
||||
even if it doesn't need reloading!"
|
||||
(or (null file)
|
||||
(stringp file)
|
||||
(signal 'wrong-type-argument (list 'stringp file)))
|
||||
(if (stringp file)
|
||||
(cond ((file-equal-p file doom-autoload-file)
|
||||
(doom-reload-doom-autoloads force-p))
|
||||
((file-equal-p file doom-package-autoload-file)
|
||||
(doom-reload-package-autoloads force-p))
|
||||
((error "Invalid autoloads file: %s" file)))
|
||||
(doom-reload-doom-autoloads force-p)
|
||||
(doom-reload-package-autoloads force-p)))
|
||||
|
||||
|
||||
;;
|
||||
;; Doom autoloads
|
||||
;;
|
||||
|
||||
(defun doom--file-cookie-p (file)
|
||||
"Returns the return value of the ;;;###if predicate form in FILE."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents-literally file nil 0 256)
|
||||
(if (and (re-search-forward "^;;;###if " nil t)
|
||||
(<= (line-number-at-pos) 3))
|
||||
(let ((load-file-name file))
|
||||
(eval (sexp-at-point)))
|
||||
t)))
|
||||
|
||||
(defun doom--generate-header (func)
|
||||
(goto-char (point-min))
|
||||
(insert ";; -*- lexical-binding:t -*-\n"
|
||||
";; This file is autogenerated by `" (symbol-name func) "', DO NOT EDIT !!\n\n"))
|
||||
|
||||
(defun doom--generate-autoloads (targets)
|
||||
(require 'autoload)
|
||||
(dolist (file targets)
|
||||
(let* ((file (file-truename file))
|
||||
(generated-autoload-file doom-autoload-file)
|
||||
(generated-autoload-load-name (file-name-sans-extension file))
|
||||
(noninteractive (not doom-debug-mode))
|
||||
autoload-timestamps)
|
||||
(print!
|
||||
(cond ((not (doom--file-cookie-p file))
|
||||
"⚠ Ignoring %s")
|
||||
((autoload-generate-file-autoloads file (current-buffer))
|
||||
(yellow "✕ Nothing in %%s"))
|
||||
((green "✓ Scanned %%s")))
|
||||
(if (file-in-directory-p file default-directory)
|
||||
(file-relative-name file)
|
||||
(abbreviate-file-name file))))))
|
||||
|
||||
(defun doom--expand-autoloads ()
|
||||
(let ((load-path (append doom-modules-dirs load-path))
|
||||
cache)
|
||||
(while (re-search-forward "^\\s-*(autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t)
|
||||
(let ((path (match-string 1)))
|
||||
(replace-match
|
||||
(or (cdr (assoc path cache))
|
||||
(when-let* ((libpath (locate-library path))
|
||||
(libpath (file-name-sans-extension libpath)))
|
||||
(push (cons path (abbreviate-file-name libpath)) cache)
|
||||
libpath)
|
||||
path)
|
||||
t t nil 1)))))
|
||||
|
||||
(defun doom--generate-autodefs (targets enabled-targets)
|
||||
(goto-char (point-max))
|
||||
(search-backward ";;;***" nil t)
|
||||
(save-excursion (insert "\n"))
|
||||
(dolist (path targets)
|
||||
(insert
|
||||
(with-temp-buffer
|
||||
(insert-file-contents path)
|
||||
(let ((member-p (or (member path enabled-targets)
|
||||
(file-in-directory-p path doom-core-dir)))
|
||||
forms)
|
||||
(while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t)
|
||||
(let* ((sexp (sexp-at-point))
|
||||
(pred (match-string 1))
|
||||
(type (car sexp))
|
||||
(name (doom-unquote (cadr sexp)))
|
||||
(origin (cond ((doom-module-from-path path))
|
||||
((file-in-directory-p path doom-private-dir)
|
||||
`(:private . ,(intern (file-name-base path))))
|
||||
((file-in-directory-p path doom-emacs-dir)
|
||||
`(:core . ,(intern (file-name-base path))))))
|
||||
(doom-file-form
|
||||
`(put ',name 'doom-file ,(abbreviate-file-name path))))
|
||||
(cond ((memq type '(defun defmacro cl-defun cl-defmacro))
|
||||
(cl-destructuring-bind (type name arglist &rest body) sexp
|
||||
(let ((docstring (if (stringp (car body))
|
||||
(pop body)
|
||||
"No documentation.")))
|
||||
(push (cond ((not (and member-p
|
||||
(or (null pred)
|
||||
(let ((load-file-name path))
|
||||
(eval (read pred) t)))))
|
||||
(push doom-file-form forms)
|
||||
(setq docstring (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
|
||||
origin docstring))
|
||||
(condition-case-unless-debug e
|
||||
(append (list (pcase type
|
||||
(`defun 'defmacro)
|
||||
(`cl-defun `cl-defmacro)
|
||||
(_ type))
|
||||
name arglist docstring)
|
||||
(cl-loop for arg in arglist
|
||||
if (and (symbolp arg)
|
||||
(not (keywordp arg))
|
||||
(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)))))
|
||||
('error
|
||||
(message "Ignoring autodef %s (%s)"
|
||||
name e)
|
||||
nil)))
|
||||
((make-autoload sexp (abbreviate-file-name (file-name-sans-extension path)))))
|
||||
forms)
|
||||
(push `(put ',name 'doom-module ',origin) forms))))
|
||||
|
||||
((eq type 'defalias)
|
||||
(cl-destructuring-bind (type name target &optional docstring) sexp
|
||||
(let ((name (doom-unquote name))
|
||||
(target (doom-unquote target)))
|
||||
(unless (and member-p
|
||||
(or (null pred)
|
||||
(let ((load-file-name path))
|
||||
(eval (read pred) t))))
|
||||
(setq target #'ignore))
|
||||
(push doom-file-form forms)
|
||||
(push `(put ',name 'doom-module ',origin) forms)
|
||||
(push `(defalias ',name #',target ,docstring)
|
||||
forms))))
|
||||
|
||||
((and member-p
|
||||
(or (null pred)
|
||||
(eval (read pred) t)))
|
||||
(push sexp forms)))))
|
||||
(if forms
|
||||
(concat (string-join (mapcar #'prin1-to-string (reverse forms)) "\n")
|
||||
"\n")
|
||||
""))))))
|
||||
|
||||
(defun doom--cleanup-autoloads ()
|
||||
(goto-char (point-min))
|
||||
(when (re-search-forward "^;;\\(;[^\n]*\\| no-byte-compile: t\\)\n" nil t)
|
||||
(replace-match "" t t)))
|
||||
|
||||
(defun doom-reload-doom-autoloads (&optional force-p)
|
||||
"Refreshes the autoloads.el file, specified by `doom-autoload-file', if
|
||||
necessary (or if FORCE-P is non-nil).
|
||||
|
||||
It scans and reads core/autoload/*.el, modules/*/*/autoload.el and
|
||||
modules/*/*/autoload/*.el, and generates `doom-autoload-file'. This file tells
|
||||
Emacs where to find lazy-loaded functions.
|
||||
|
||||
This should be run whenever your `doom!' block, or a module autoload file, is
|
||||
modified."
|
||||
(let* ((default-directory doom-emacs-dir)
|
||||
(doom-modules (doom-modules))
|
||||
(targets
|
||||
(file-expand-wildcards
|
||||
(expand-file-name "autoload/*.el" doom-core-dir)))
|
||||
(enabled-targets (copy-sequence targets))
|
||||
case-fold-search)
|
||||
(dolist (path (doom-module-load-path t))
|
||||
(let* ((auto-dir (expand-file-name "autoload" path))
|
||||
(auto-file (expand-file-name "autoload.el" path))
|
||||
(module (doom-module-from-path auto-file))
|
||||
(module-p (or (doom-module-p (car module) (cdr module))
|
||||
(file-equal-p path doom-private-dir))))
|
||||
(when (file-exists-p auto-file)
|
||||
(push auto-file targets)
|
||||
(if module-p (push auto-file enabled-targets)))
|
||||
(dolist (file (doom-files-in auto-dir :match "\\.el$" :full t))
|
||||
(push file targets)
|
||||
(if module-p (push file enabled-targets)))))
|
||||
(if (and (not force-p)
|
||||
(not doom-emacs-changed-p)
|
||||
(file-exists-p doom-autoload-file)
|
||||
(not (file-newer-than-file-p (expand-file-name "init.el" doom-private-dir)
|
||||
doom-autoload-file))
|
||||
(not (cl-loop for file in targets
|
||||
if (file-newer-than-file-p file doom-autoload-file)
|
||||
return t)))
|
||||
(progn (print! (green "Doom core autoloads is up-to-date"))
|
||||
(doom-initialize-autoloads doom-autoload-file)
|
||||
nil)
|
||||
(doom-delete-autoloads-file doom-autoload-file)
|
||||
(message "Generating new autoloads.el")
|
||||
(make-directory (file-name-directory doom-autoload-file) t)
|
||||
(with-temp-file doom-autoload-file
|
||||
(doom--generate-header 'doom-reload-doom-autoloads)
|
||||
(save-excursion
|
||||
(doom--generate-autoloads (reverse enabled-targets)))
|
||||
;; Replace autoload paths (only for module autoloads) with absolute
|
||||
;; paths for faster resolution during load and simpler `load-path'
|
||||
(save-excursion
|
||||
(doom--expand-autoloads)
|
||||
(print! (green "✓ Expanded module autoload paths")))
|
||||
;; Generates stub definitions for functions/macros defined in disabled
|
||||
;; modules, so that you will never get a void-function when you use
|
||||
;; them.
|
||||
(save-excursion
|
||||
(doom--generate-autodefs (reverse targets) enabled-targets)
|
||||
(print! (green "✓ Generated autodefs")))
|
||||
;; Remove byte-compile inhibiting file variables so we can byte-compile
|
||||
;; the file, and autoload comments.
|
||||
(doom--cleanup-autoloads)
|
||||
(print! (green "✓ Clean up autoloads")))
|
||||
;; Byte compile it to give the file a chance to reveal errors.
|
||||
(doom--byte-compile-file doom-autoload-file)
|
||||
(doom--do-load doom-autoload-file)
|
||||
t)))
|
||||
|
||||
|
||||
;;
|
||||
;; Package autoloads
|
||||
;;
|
||||
|
||||
(defun doom--generate-package-autoloads ()
|
||||
(dolist (spec (doom-get-package-alist))
|
||||
(if-let* ((pkg (car spec))
|
||||
(desc (cdr spec)))
|
||||
(unless (memq pkg doom-autoload-excluded-packages)
|
||||
(let ((file (concat (package--autoloads-file-name desc) ".el")))
|
||||
(when (file-exists-p file)
|
||||
(insert "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n")
|
||||
(insert-file-contents file)
|
||||
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t)
|
||||
(unless (nth 8 (syntax-ppss))
|
||||
(replace-match "" t t)))
|
||||
(unless (bolp) (insert "\n"))
|
||||
(insert ")\n"))))
|
||||
(message "Couldn't find package desc for %s" (car spec)))))
|
||||
|
||||
(defun doom--generate-var-cache ()
|
||||
(doom-initialize-packages)
|
||||
(prin1 `(setq load-path ',load-path
|
||||
auto-mode-alist ',auto-mode-alist
|
||||
Info-directory-list ',Info-directory-list
|
||||
doom-disabled-packages ',doom-disabled-packages
|
||||
package-activated-list ',package-activated-list)
|
||||
(current-buffer)))
|
||||
|
||||
(defun doom--cleanup-package-autoloads ()
|
||||
(while (re-search-forward "^\\s-*\\((\\(?:add-to-list\\|\\(?:when\\|if\\) (boundp\\)\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)" nil t)
|
||||
(goto-char (match-beginning 1))
|
||||
(kill-sexp)))
|
||||
|
||||
(defun doom-reload-package-autoloads (&optional force-p)
|
||||
"Compiles `doom-package-autoload-file' from the autoloads files of all
|
||||
installed packages. It also caches `load-path', `Info-directory-list',
|
||||
`doom-disabled-packages', `package-activated-list' and `auto-mode-alist'.
|
||||
|
||||
Will do nothing if none of your installed packages have been modified. If
|
||||
FORCE-P (universal argument) is non-nil, regenerate it anyway.
|
||||
|
||||
This should be run whenever your `doom!' block or update your packages."
|
||||
(if (and (not force-p)
|
||||
(not doom-emacs-changed-p)
|
||||
(file-exists-p doom-package-autoload-file)
|
||||
(not (file-newer-than-file-p doom-packages-dir doom-package-autoload-file))
|
||||
(not (ignore-errors
|
||||
(cl-loop for key being the hash-keys of (doom-modules)
|
||||
for path = (doom-module-path (car key) (cdr key) "packages.el")
|
||||
if (file-newer-than-file-p path doom-package-autoload-file)
|
||||
return t))))
|
||||
(ignore (print! (green "Doom package autoloads is up-to-date"))
|
||||
(doom-initialize-autoloads doom-package-autoload-file))
|
||||
(let (case-fold-search)
|
||||
(doom-delete-autoloads-file doom-package-autoload-file)
|
||||
(with-temp-file doom-package-autoload-file
|
||||
(doom--generate-header 'doom-reload-package-autoloads)
|
||||
(save-excursion
|
||||
;; Cache the important and expensive-to-initialize state here.
|
||||
(doom--generate-var-cache)
|
||||
(print! (green "✓ Cached package state"))
|
||||
;; Loop through packages and concatenate all their autoloads files.
|
||||
(doom--generate-package-autoloads)
|
||||
(print! (green "✓ Package autoloads included")))
|
||||
;; Remove `load-path' and `auto-mode-alist' modifications (most of them,
|
||||
;; at least); they are cached later, so all those membership checks are
|
||||
;; unnecessary overhead.
|
||||
(doom--cleanup-package-autoloads)
|
||||
(print! (green "✓ Removed load-path/auto-mode-alist entries"))))
|
||||
(doom--byte-compile-file doom-package-autoload-file)
|
||||
(doom--do-load doom-package-autoload-file)
|
||||
t))
|
||||
|
||||
|
||||
;;
|
||||
;; Byte compilation
|
||||
;;
|
||||
|
||||
(defun doom-byte-compile (&optional modules recompile-p)
|
||||
"Byte compiles your emacs configuration.
|
||||
|
||||
init.el is always byte-compiled by this.
|
||||
|
||||
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-clean-byte-compiled-files' 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)
|
||||
(total-ok 0)
|
||||
(total-fail 0)
|
||||
(total-noop 0)
|
||||
compile-plugins-p
|
||||
targets)
|
||||
(dolist (module (delete-dups modules) (nreverse targets))
|
||||
(pcase module
|
||||
(":core" (push doom-core-dir targets))
|
||||
(":private" (push doom-private-dir targets))
|
||||
(":plugins"
|
||||
(cl-loop for (name . desc) in (doom-get-package-alist)
|
||||
do (package--compile desc))
|
||||
(setq compile-plugins-p t
|
||||
modules (delete ":plugins" modules)))
|
||||
((pred file-directory-p)
|
||||
(push module targets))
|
||||
((pred (string-match "^\\([^/]+\\)/\\([^/]+\\)$"))
|
||||
(push (doom-module-locate-path
|
||||
(doom-keyword-intern (match-string 1 module))
|
||||
(intern (match-string 2 module)))
|
||||
targets))))
|
||||
(cl-block 'byte-compile
|
||||
;; If we're just here to byte-compile our plugins, we're done!
|
||||
(and (not modules)
|
||||
compile-plugins-p
|
||||
(cl-return-from 'byte-compile t))
|
||||
(unless (or (equal modules '(":core"))
|
||||
recompile-p)
|
||||
(unless (and (not doom-auto-accept)
|
||||
(y-or-n-p
|
||||
(concat "Warning: byte compiling is for advanced users. It will interfere with your\n"
|
||||
"efforts to debug issues. It is not recommended you do it if you frequently\n"
|
||||
"tinker with your Emacs config.\n\n"
|
||||
"Alternatively, use `bin/doom compile :core` instead to byte-compile only the\n"
|
||||
"Doom core files, as these don't change often.\n\n"
|
||||
"If you have issues, please make sure byte-compilation isn't the cause by using\n"
|
||||
"`bin/doom clean` to clear out your *.elc files.\n\n"
|
||||
"Byte-compile anyway?")))
|
||||
(message "Aborting.")
|
||||
(cl-return-from 'byte-compile)))
|
||||
(and (not recompile-p)
|
||||
(or (null modules) (equal modules '(":core")))
|
||||
(doom-clean-byte-compiled-files))
|
||||
(let (doom-emacs-changed-p
|
||||
noninteractive)
|
||||
;; 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.
|
||||
(unless (and (doom-initialize-autoloads doom-autoload-file)
|
||||
(doom-initialize-autoloads doom-package-autoload-file))
|
||||
(doom-reload-autoloads))
|
||||
(doom-initialize)
|
||||
(doom-initialize-modules 'force))
|
||||
;; If no targets were supplied, then we use your module list.
|
||||
(unless modules
|
||||
(setq targets (append (list doom-core-dir)
|
||||
(doom-module-load-path))))
|
||||
;; Assemble el files we want to compile; taking into account that
|
||||
;; MODULES may be a list of MODULE/SUBMODULE strings from the command
|
||||
;; line.
|
||||
(let ((target-files (doom-files-in targets :depth 1 :match "\\.el$"))
|
||||
(load-path load-path)
|
||||
kill-emacs-hook kill-buffer-query-functions)
|
||||
(unless target-files
|
||||
(if targets
|
||||
(message "Couldn't find any valid targets")
|
||||
(message "No targets to %scompile" (if recompile-p "re" "")))
|
||||
(cl-return-from 'byte-compile))
|
||||
(require 'use-package)
|
||||
(condition-case e
|
||||
(let ((use-package-defaults use-package-defaults)
|
||||
(use-package-expand-minimally t))
|
||||
;; 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)
|
||||
;; Always compile private init file
|
||||
(push (expand-file-name "init.el" doom-private-dir) target-files)
|
||||
(push (expand-file-name "init.el" doom-emacs-dir) target-files)
|
||||
(dolist (target (cl-delete-duplicates (mapcar #'file-truename target-files) :test #'equal))
|
||||
(if (or (not recompile-p)
|
||||
(let ((elc-file (byte-compile-dest-file target)))
|
||||
(and (file-exists-p elc-file)
|
||||
(file-newer-than-file-p target elc-file))))
|
||||
(let ((result (if (or (string-match-p "/\\(?:packages\\|doctor\\)\\.el$" target)
|
||||
(not (doom--file-cookie-p target)))
|
||||
'no-byte-compile
|
||||
(byte-compile-file target)))
|
||||
(short-name (if (file-in-directory-p target doom-emacs-dir)
|
||||
(file-relative-name target doom-emacs-dir)
|
||||
(abbreviate-file-name target))))
|
||||
(cl-incf
|
||||
(cond ((eq result 'no-byte-compile)
|
||||
(print! (dark (white "⚠ Ignored %s" short-name)))
|
||||
total-noop)
|
||||
((null result)
|
||||
(print! (red "✕ Failed to compile %s" short-name))
|
||||
total-fail)
|
||||
(t
|
||||
(print! (green "✓ Compiled %s" short-name))
|
||||
(quiet! (load target t t))
|
||||
total-ok))))
|
||||
(cl-incf total-noop)))
|
||||
(print!
|
||||
(bold
|
||||
(color (if (= total-fail 0) 'green 'red)
|
||||
"%s %d/%d file(s) (%d ignored)"
|
||||
(if recompile-p "Recompiled" "Compiled")
|
||||
total-ok (- (length target-files) total-noop)
|
||||
total-noop))))
|
||||
((debug error)
|
||||
(print! (red "\n%s\n\n%%s" "There were breaking errors.")
|
||||
"Reverting changes...")
|
||||
(signal 'doom-error (list 'byte-compile e))))))))
|
||||
|
||||
(defun doom-clean-byte-compiled-files ()
|
||||
"Delete all the compiled elc files in your Emacs configuration and private
|
||||
module. This does not include your byte-compiled, third party packages.'"
|
||||
(cl-loop with default-directory = doom-emacs-dir
|
||||
for path in (append (doom-files-in doom-emacs-dir :match "\\.elc$" :depth 0)
|
||||
(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))
|
||||
for truepath = (file-truename path)
|
||||
if (file-exists-p path)
|
||||
do (delete-file path)
|
||||
and do
|
||||
(print! (green "✓ Deleted %%s")
|
||||
(if (file-in-directory-p truepath default-directory)
|
||||
(file-relative-name truepath)
|
||||
(abbreviate-file-name truepath)))
|
||||
finally do (print! (bold (green "Everything is clean")))))
|
||||
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)")
|
||||
|
||||
(provide 'core-cli)
|
||||
;;; core-cli.el ends here
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue