mirror of
https://github.com/doomemacs/doomemacs
synced 2025-08-03 12:27:26 -05:00
This prevents edge cases where these directories are created with permissions that prevent Emacs from writing to them. This can happy either due to an overly-restrictive default umask, `set-default-file-modes` call, or if `doom-profiles-save` is instructed to write a file whose parent doesn't exist yet. Fix: #8134
214 lines
9.2 KiB
EmacsLisp
214 lines
9.2 KiB
EmacsLisp
;;; lisp/doom-cli.el --- API+DSL for Doom's CLI framework -*- lexical-binding: t; -*-
|
|
;;; Commentary:
|
|
;;
|
|
;; The heart of Doom's CLI framework. This is safe to load in interactive
|
|
;; sessions (for API access and syntax highlighting), but much of the API
|
|
;; expects a noninteractive session, so take care when testing code!
|
|
;;
|
|
;;; Code:
|
|
|
|
(unless noninteractive
|
|
(error "Don't load doom-cli in an interactive session!"))
|
|
|
|
;; PERF: Deferring the GC in non-interactive sessions isn't as important, but
|
|
;; still yields a notable benefit. Still, avoid setting it to high here, as
|
|
;; runaway memory usage is a real risk in longer sessions.
|
|
(setq gc-cons-threshold 134217728 ; 128mb
|
|
;; Backported from 29 (see emacs-mirror/emacs@73a384a98698)
|
|
gc-cons-percentage 1.0)
|
|
|
|
;; REVIEW: Remove these later. The endpoints should be responsibile for
|
|
;; ensuring they exist. For now, they exist to quell file errors.
|
|
(with-file-modes #o700
|
|
(mapc (doom-rpartial #'make-directory 'parents)
|
|
(list doom-local-dir
|
|
doom-data-dir
|
|
doom-cache-dir
|
|
doom-state-dir)))
|
|
|
|
;; HACK: bin/doom suppresses loading of site files so they can be loaded
|
|
;; manually, here. Why? To suppress the otherwise unavoidable output they
|
|
;; commonly produce (like deprecation notices, file-loaded messages, and
|
|
;; linter warnings). This output pollutes the output of doom's CLI (or
|
|
;; scripts derived from it) with potentially confusing or alarming -- but
|
|
;; always unimportant -- information to the user.
|
|
(quiet!
|
|
(require 'cl nil t) ; "Package cl is deprecated"
|
|
(unless site-run-file ; unset in doom.el
|
|
(when-let ((site-run-file (get 'site-run-file 'initial-value)))
|
|
(load site-run-file t inhibit-message))))
|
|
|
|
(setq-default
|
|
;; PERF: Don't generate superfluous files when writing temp buffers.
|
|
make-backup-files nil
|
|
;; COMPAT: Stop user configuration from interfering with package management.
|
|
enable-dir-local-variables nil
|
|
;; PERF: Reduce ambiguity, embrace specificity, enjoy predictability.
|
|
case-fold-search nil
|
|
;; UX: Don't clog the user's trash with our CLI refuse.
|
|
delete-by-moving-to-trash nil)
|
|
|
|
;; Load just the... bear necessities~
|
|
(require 'seq)
|
|
(require 'map)
|
|
|
|
;; Suppress any possible coding system prompts during CLI sessions.
|
|
(set-language-environment "UTF-8")
|
|
|
|
;; Load and set up our debugger first, so backtraces can be made more
|
|
;; presentable and logged to file.
|
|
(doom-require 'doom-lib 'debug)
|
|
(if init-file-debug (doom-debug-mode +1))
|
|
|
|
;; Then load the rest of Doom's libs eagerly, since autoloads may not be
|
|
;; generated/loaded yet.
|
|
(doom-require 'doom-lib 'process)
|
|
(doom-require 'doom-lib 'system)
|
|
(doom-require 'doom-lib 'git)
|
|
(doom-require 'doom-lib 'plist)
|
|
(doom-require 'doom-lib 'files)
|
|
(doom-require 'doom-lib 'print)
|
|
(doom-require 'doom-lib 'autoloads)
|
|
|
|
;; Ensure straight and core packages are ready to go for CLI commands.
|
|
(require 'doom-cli-lib)
|
|
;; Last minute initialization at the end of loading this file.
|
|
(with-eval-after-load 'doom-cli
|
|
(doom-run-hooks 'doom-before-init-hook))
|
|
|
|
|
|
;;
|
|
;;; Predefined CLIs (:help, :version, and :dump)
|
|
|
|
(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).")
|
|
|
|
;; When __DOOMDUMP is set, doomscripts trigger this special handler.
|
|
(defcli! (:root :dump)
|
|
((pretty? ("--pretty") "Pretty print output")
|
|
&context context
|
|
&args commands)
|
|
"Dump metadata to stdout for other commands to read."
|
|
(let* ((prefix (doom-cli-context-prefix context))
|
|
(command (cons prefix commands)))
|
|
(funcall (if pretty? #'pp #'prin1)
|
|
(cond ((equal commands '("-")) (hash-table-values doom-cli--table))
|
|
(commands (doom-cli-find command))
|
|
((doom-cli-find (list prefix)))))
|
|
(terpri)
|
|
;; Kill manually so we don't save output to logs.
|
|
(let (kill-emacs) (kill-emacs 0))))
|
|
|
|
(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?)))
|
|
((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.")))))
|
|
|
|
(provide 'doom-cli)
|
|
;;; doom-cli.el ends here
|