1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-07 06:50:23 -08:00

Began work on modular handling of keywords

This commit is contained in:
John Wiegley 2015-03-19 22:26:53 -05:00
parent 94ad68330d
commit f1ab3291f6

View file

@ -41,6 +41,7 @@
(require 'bytecomp) (require 'bytecomp)
(require 'diminish nil t) (require 'diminish nil t)
(require 'bytecomp) (require 'bytecomp)
(eval-when-compile (require 'cl))
(declare-function package-installed-p 'package) (declare-function package-installed-p 'package)
@ -92,6 +93,35 @@ the user specified."
:type 'boolean :type 'boolean
:group 'use-package) :group 'use-package)
(defcustom use-package-keywords
'(:disabled
:pin
:ensure
:if
:when
:unless
:requires
:load-path
:no-require
:preface
:bind
:bind*
:bind-keymap
:bind-keymap*
:interpreter
:mode
:commands
:defines
:functions
:defer
:demand
:init
:config
:diminish)
"Establish which keywords are valid, and the order they are processed in."
:type '(repeat symbol)
:group 'use-package)
(defcustom use-package-expand-minimally nil (defcustom use-package-expand-minimally nil
"If non-nil, make the expanded code as minimal as possible. "If non-nil, make the expanded code as minimal as possible.
This disables: This disables:
@ -103,6 +133,11 @@ then your byte-compiled init file is as minimal as possible."
:type 'boolean :type 'boolean
:group 'use-package) :group 'use-package)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Utility functions
;;
(defun use-package-expand (name label form) (defun use-package-expand (name label form)
"FORM is a list of forms, so `((foo))' if only `foo' is being called." "FORM is a list of forms, so `((foo))' if only `foo' is being called."
(declare (indent 1)) (declare (indent 1))
@ -169,15 +204,62 @@ ARGS is a list of forms, so `((foo))' if only `foo' is being called."
"Report MSG as an error, so the user knows it came from this package." "Report MSG as an error, so the user knows it came from this package."
(error "use-package: %s" msg)) (error "use-package: %s" msg))
(defun use-package-normalize-form (label args) (defun use-package-plist-delete (plist property)
"Given a list of forms, return it wrapped in `progn'." "Delete PROPERTY from PLIST.
(unless (listp (car args)) This is in contrast to merely setting it to 0."
(use-package-error (concat label " wants a sexp or list of sexps"))) (let (p)
(mapcar #'(lambda (form) (while plist
(if (and (consp form) (if (not (eq property (car plist)))
(eq (car form) 'use-package)) (setq p (plist-put p (car plist) (nth 1 plist))))
(macroexpand form) (setq plist (cddr plist)))
form)) args)) p))
(defun use-package-split-list (pred xs)
(let ((ys (list nil)) (zs (list nil)) flip)
(dolist (x xs)
(if flip
(nconc zs (list x))
(if (funcall pred x)
(progn
(setq flip t)
(nconc zs (list x)))
(nconc ys (list x)))))
(cons (cdr ys) (cdr zs))))
(defun use-package-keyword-index (keyword)
(loop named outer
with index = 0
for k in use-package-keywords do
(if (eq k keyword)
(return-from outer index))
(incf index)))
(defun use-package-sort-keywords (plist)
(let (plist-grouped)
(while plist
(push (cons (car plist) (cadr plist))
plist-grouped)
(setq plist (cddr plist)))
(append
(sort plist-grouped
#'(lambda (l r) (< (use-package-keyword-index (car l))
(use-package-keyword-index (car r))))))))
(defsubst use-package-cat-maybes (&rest elems)
"Delete all empty lists from ELEMS (nil or (list nil)), and append them."
(apply #'nconc (delete nil (delete (list nil) elems))))
(defconst use-package-font-lock-keywords
'(("(\\(use-package\\)\\_>[ \t']*\\(\\(?:\\sw\\|\\s_\\)+\\)?"
(1 font-lock-keyword-face)
(2 font-lock-constant-face nil t))))
(font-lock-add-keywords 'emacs-lisp-mode use-package-font-lock-keywords)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Normalization functions
;;
(defsubst use-package-normalize-value (label arg) (defsubst use-package-normalize-value (label arg)
"Normalize a value." "Normalize a value."
@ -187,37 +269,23 @@ ARGS is a list of forms, so `((foo))' if only `foo' is being called."
`(funcall #',arg)) `(funcall #',arg))
(t arg))) (t arg)))
(defun use-package-normalize-diminish (name-symbol label arg &optional recursed) (defun use-package-normalize-paths (label arg &optional recursed)
"Normalize the arguments to diminish down to a list of one of two forms: "Normalize a list of filesystem paths."
SYMBOL
(SYMBOL . STRING)"
(cond (cond
((symbolp arg) ((or (symbolp arg) (functionp arg))
(list arg)) (let ((value (use-package-normalize-value label arg)))
(use-package-normalize-paths label (eval value))))
((stringp arg) ((stringp arg)
(list (cons (intern (concat (symbol-name name-symbol) "-mode")) arg))) (let ((path (if (file-name-absolute-p arg)
((and (consp arg) (stringp (cdr arg))) arg
(list arg)) (expand-file-name arg user-emacs-directory))))
(list path)))
((and (not recursed) (listp arg) (listp (cdr arg))) ((and (not recursed) (listp arg) (listp (cdr arg)))
(mapcar #'(lambda (x) (car (use-package-normalize-diminish (mapcar #'(lambda (x)
name-symbol label x t))) arg)) (car (use-package-normalize-paths label x t))) arg))
(t (t
(use-package-error (use-package-error
(concat label " wants a string, symbol, " (concat label " wants a directory path, or list of paths")))))
"(symbol . string) or list of these")))))
(defun use-package-only-one (label args f)
"Call F on the first member of ARGS if it has exactly one element."
(declare (indent 1))
(cond
((and (listp args) (listp (cdr args))
(= (length args) 1))
(funcall f label (car args)))
(t
(use-package-error
(concat label " wants exactly one argument")))))
(put 'use-package-only-one 'lisp-indent-function 'defun)
(defun use-package-as-one (label args f) (defun use-package-as-one (label args f)
"Call F on the first element of ARGS if it has one element, or all of ARGS." "Call F on the first element of ARGS if it has one element, or all of ARGS."
@ -253,6 +321,23 @@ ARGS is a list of forms, so `((foo))' if only `foo' is being called."
(use-package-error (use-package-error
(concat label " wants a string, (string . symbol) or list of these"))))) (concat label " wants a string, (string . symbol) or list of these")))))
(defun use-package-normalize-binder (name-symbol keyword args)
(use-package-as-one (symbol-name keyword) args
(lambda (label arg)
(use-package-normalize-pairs name-symbol label arg nil t))))
(defalias 'use-package-normalize/:bind 'use-package-normalize-binder)
(defalias 'use-package-normalize/:bind* 'use-package-normalize-binder)
(defalias 'use-package-normalize/:bind-keymap 'use-package-normalize-binder)
(defalias 'use-package-normalize/:bind-keymap* 'use-package-normalize-binder)
(defun use-package-normalize-mode (name-symbol keyword args)
(use-package-as-one (symbol-name keyword) args
(apply-partially #'use-package-normalize-pairs name-symbol)))
(defalias 'use-package-normalize/:mode 'use-package-normalize-mode)
(defalias 'use-package-normalize/:interpreter 'use-package-normalize-mode)
(defun use-package-normalize-symbols (label arg &optional recursed) (defun use-package-normalize-symbols (label arg &optional recursed)
"Normalize a list of symbols." "Normalize a list of symbols."
(cond (cond
@ -264,110 +349,179 @@ ARGS is a list of forms, so `((foo))' if only `foo' is being called."
(use-package-error (use-package-error
(concat label " wants a symbol, or list of symbols"))))) (concat label " wants a symbol, or list of symbols")))))
(defun use-package-normalize-paths (label arg &optional recursed) (defun use-package-normalize-symlist (name-symbol keyword args)
"Normalize a list of filesystem paths." (use-package-as-one (symbol-name keyword) args
#'use-package-normalize-symbols))
(defalias 'use-package-normalize/:commands 'use-package-normalize-symlist)
(defalias 'use-package-normalize/:defines 'use-package-normalize-symlist)
(defalias 'use-package-normalize/:functions 'use-package-normalize-symlist)
(defalias 'use-package-normalize/:requires 'use-package-normalize-symlist)
(defun use-package-only-one (label args f)
"Call F on the first member of ARGS if it has exactly one element."
(declare (indent 1))
(cond (cond
((or (symbolp arg) (functionp arg)) ((and (listp args) (listp (cdr args))
(let ((value (use-package-normalize-value label arg))) (= (length args) 1))
(use-package-normalize-paths label (eval value)))) (funcall f label (car args)))
((stringp arg)
(let ((path (if (file-name-absolute-p arg)
arg
(expand-file-name arg user-emacs-directory))))
(list path)))
((and (not recursed) (listp arg) (listp (cdr arg)))
(mapcar #'(lambda (x)
(car (use-package-normalize-paths label x t))) arg))
(t (t
(use-package-error (use-package-error
(concat label " wants a directory path, or list of paths"))))) (concat label " wants exactly one argument")))))
(defun use-package-split-list (pred xs) (put 'use-package-only-one 'lisp-indent-function 'defun)
(let ((ys (list nil)) (zs (list nil)) flip)
(dolist (x xs) (defun use-package-normalize-predicate (name-symbol keyword args)
(if flip (if (null args)
(nconc zs (list x)) t
(if (funcall pred x) (use-package-only-one (symbol-name keyword) args
(progn #'use-package-normalize-value)))
(setq flip t)
(nconc zs (list x))) (defalias 'use-package-normalize/:defer 'use-package-normalize-predicate)
(nconc ys (list x))))) (defalias 'use-package-normalize/:demand 'use-package-normalize-predicate)
(cons (cdr ys) (cdr zs)))) (defalias 'use-package-normalize/:disabled 'use-package-normalize-predicate)
(defalias 'use-package-normalize/:no-require 'use-package-normalize-predicate)
(defun use-package-normalize/:ensure (name-symbol keyword args)
(if (null args)
t
(use-package-only-one (symbol-name keyword) args
(lambda (label arg)
(if (symbolp arg)
arg
(use-package-error
(concat ":ensure wants an optional package name "
"(an unquoted symbol name)")))))))
(defun use-package-normalize-test (name-symbol keyword args)
(use-package-only-one (symbol-name keyword) args
#'use-package-normalize-value))
(defalias 'use-package-normalize/:if 'use-package-normalize-test)
(defalias 'use-package-normalize/:when 'use-package-normalize-test)
(defun use-package-normalize/:unless (name-symbol keyword args)
(not (use-package-only-one (symbol-name keyword) args
#'use-package-normalize-value)))
(defun use-package-normalize-diminish (name-symbol label arg &optional recursed)
"Normalize the arguments to diminish down to a list of one of two forms:
SYMBOL
(SYMBOL . STRING)"
(cond
((symbolp arg)
(list arg))
((stringp arg)
(list (cons (intern (concat (symbol-name name-symbol) "-mode")) arg)))
((and (consp arg) (stringp (cdr arg)))
(list arg))
((and (not recursed) (listp arg) (listp (cdr arg)))
(mapcar #'(lambda (x) (car (use-package-normalize-diminish
name-symbol label x t))) arg))
(t
(use-package-error
(concat label " wants a string, symbol, "
"(symbol . string) or list of these")))))
(defun use-package-normalize/:diminish (name-symbol keyword args)
(use-package-as-one (symbol-name keyword) args
(apply-partially #'use-package-normalize-diminish name-symbol)))
(defun use-package-normalize-form (label args)
"Given a list of forms, return it wrapped in `progn'."
(unless (listp (car args))
(use-package-error (concat label " wants a sexp or list of sexps")))
(mapcar #'(lambda (form)
(if (and (consp form)
(eq (car form) 'use-package))
(macroexpand form)
form)) args))
(defun use-package-normalize-forms (name-symbol keyword args)
(use-package-normalize-form (symbol-name keyword) args))
(defalias 'use-package-normalize/:preface 'use-package-normalize-forms)
(defalias 'use-package-normalize/:init 'use-package-normalize-forms)
(defalias 'use-package-normalize/:config 'use-package-normalize-forms)
(defun use-package-normalize/:load-path (name-symbol keyword args)
(use-package-as-one (symbol-name keyword) args
#'use-package-normalize-paths))
(defun use-package-normalize/:pin (name-symbol keyword args)
(use-package-only-one (symbol-name keyword) args
(lambda (label arg)
(cond
((stringp arg) arg)
((symbolp arg) (symbol-name arg))
(t
(use-package-error
":pin wants an archive name (a string)"))))))
(defun use-package-normalize-plist (name-symbol input) (defun use-package-normalize-plist (name-symbol input)
"Given a pseudo-plist, normalize it to a regular plist." "Given a pseudo-plist, normalize it to a regular plist."
(if (null input) (unless (null input)
nil (let* ((keyword (car input))
(let* ((head (car input))
(xs (use-package-split-list #'keywordp (cdr input))) (xs (use-package-split-list #'keywordp (cdr input)))
(args (car xs)) (args (car xs))
(tail (cdr xs))) (tail (cdr xs))
(append (normalizer (intern (concat "use-package-normalize/"
(list (symbol-name keyword))))
(cond ((memq head '(:when :unless)) :if) (arg
(t head)) (cond
(pcase head ((functionp normalizer)
((or :bind :bind* :bind-keymap :bind-keymap*) (funcall normalizer name-symbol keyword args))
(use-package-as-one (symbol-name head) args ((= (length args) 1)
(lambda (label arg) (car args))
(use-package-normalize-pairs name-symbol label arg nil t)))) (t
args))))
(if (memq keyword use-package-keywords)
(cons keyword
(cons arg (use-package-normalize-plist name-symbol tail)))
(use-package-error (format "Unrecognized keyword: %s" keyword))))))
((or :interpreter :mode) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(use-package-as-one (symbol-name head) args ;;
(apply-partially #'use-package-normalize-pairs name-symbol))) ;; Keyword processing
;;
((or :commands :defines :functions :requires) (defun use-package-process-keywords (name-symbol plist state)
(use-package-as-one (symbol-name head) args "Process the next keyword in the free-form property list PLIST.
#'use-package-normalize-symbols)) The values in the PLIST have each been normalized by the function
use-package-normalize/KEYWORD (minus the colon).
((or :defer :demand :disabled :no-require) STATE is a property list that the function may modify and/or
(if (null args) query. This is useful if a package defines multiple keywords and
t wishes them to have some kind of stateful interaction.
(use-package-only-one (symbol-name head) args
#'use-package-normalize-value)))
(:ensure Unless the KEYWORD being processed intends to ignore remaining
(if (null args) keywords, it must call this function recursively, passing in the
t plist with its keyword and argument removed, and passing in the
(use-package-only-one (symbol-name head) args next value for the STATE."
(lambda (label arg) (let ((plist* (use-package-sort-keywords
(if (symbolp arg) (use-package-normalize-plist name-symbol plist))))
arg (unless (null plist*)
(use-package-error (let* ((keyword (car plist*))
(concat ":ensure wants an optional package name " (arg (cadr plist*))
"(an unquoted symbol name)"))))))) (rest (cddr plist*)))
(unless (keywordp keyword)
(use-package-error (format "%s is not a keyword" keyword)))
(let* ((handler (concat "use-package-handler/"
(symbol-name keyword)))
(handler-sym (intern handler)))
(if (functionp handler-sym)
(funcall handler-sym name-symbol keyword arg rest state)
(use-package-error
(format "Keyword handler not defined: %s" handler))))))))
((or :if :when :unless) (defun use-package-handler/:if (name-symbol keyword pred rest state)
(use-package-only-one (symbol-name head) args `((when ,pred
#'use-package-normalize-value)) ,@(use-package-process-keywords name-symbol rest state))))
(:diminish ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(use-package-as-one (symbol-name head) args ;;
(apply-partially #'use-package-normalize-diminish name-symbol))) ;; The main macro
;;
((or :preface :init :config)
(use-package-normalize-form (symbol-name head) args))
(:load-path
(use-package-as-one (symbol-name head) args
#'use-package-normalize-paths))
(:pin
(use-package-only-one (symbol-name head) args
(lambda (label arg)
(cond
((stringp arg) arg)
((symbolp arg) (symbol-name arg))
(t
(use-package-error
":pin wants an archive name (a string)"))))))
(_ (use-package-error (format "Unrecognized keyword: %s" head)))))
(use-package-normalize-plist name-symbol tail)))))
(defsubst use-package-cat-maybes (&rest elems)
"Delete all empty lists from ELEMS (nil or (list nil)), and append them."
(apply #'nconc (delete nil (delete (list nil) elems))))
(defun use--package (name name-symbol name-string args) (defun use--package (name name-symbol name-string args)
"See docstring for `use-package'." "See docstring for `use-package'."
@ -471,18 +625,22 @@ ARGS is a list of forms, so `((foo))' if only `foo' is being called."
(apply (apply
#'nconc #'nconc
(mapcar #'(lambda (command) (mapcar #'(lambda (command)
`((unless (fboundp ',command) (append
(autoload #',command ,name-string nil t)) `((unless (fboundp ',command)
(declare-function ,command ,name-string))) (autoload #',command ,name-string nil t)))
(when (bound-and-true-p byte-compile-current-file)
`((eval-when-compile
(declare-function ,command ,name-string))))))
commands))) commands)))
(when (bound-and-true-p byte-compile-current-file)
(mapcar #'(lambda (fn) `(eval-when-compile
(declare-function ,fn ,name-string)))
(plist-get args :functions)))
(if (numberp deferral) (if (numberp deferral)
`((run-with-idle-timer ,deferral nil #'require ',name-symbol nil t))) `((run-with-idle-timer ,deferral nil #'require ',name-symbol nil t)))
(when (bound-and-true-p byte-compile-current-file)
(mapcar #'(lambda (fn) `(declare-function ,fn ,name-string))
(plist-get args :functions)))
;; (if (and defer-loading config-body) ;; (if (and defer-loading config-body)
;; `((defalias ',config-defun #'(lambda () ,config-body*)))) ;; `((defalias ',config-defun #'(lambda () ,config-body*))))
@ -606,6 +764,11 @@ this file. Usage:
(put 'use-package 'lisp-indent-function 'defun) (put 'use-package 'lisp-indent-function 'defun)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Special support for autoloading keymaps
;;
(defun use-package-autoload-keymap (keymap-symbol package override) (defun use-package-autoload-keymap (keymap-symbol package override)
"Loads PACKAGE and then binds the key sequence used to invoke "Loads PACKAGE and then binds the key sequence used to invoke
this function to KEYMAP-SYMBOL. It then simulates pressing the this function to KEYMAP-SYMBOL. It then simulates pressing the
@ -632,13 +795,6 @@ deferred until the prefix key sequence is pressed."
(error "use-package: package %s failed to define keymap %s" (error "use-package: package %s failed to define keymap %s"
package keymap-symbol)))) package keymap-symbol))))
(defconst use-package-font-lock-keywords
'(("(\\(use-package\\)\\_>[ \t']*\\(\\(?:\\sw\\|\\s_\\)+\\)?"
(1 font-lock-keyword-face)
(2 font-lock-constant-face nil t))))
(font-lock-add-keywords 'emacs-lisp-mode use-package-font-lock-keywords)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; :pin and :ensure support ;; :pin and :ensure support