Eventually, I want to autoload some of this stuff, so that users in interactive sessions can safely load it without side effects (useful when writing their own CLIs or editing Doom's source).
212 lines
9.2 KiB
EmacsLisp
212 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.
|
|
(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
|