Add support for module flags in doom! macro #158

For example:

  (doom! :feature (version-control +git))

I leave it to modules to interpret these flags, and they can be detected
through one of the following:

 + (featurep! :feature version-control +git)
 + (featurep! +git) -- syntactic sugar, only available from within
   modules.
 + (doom-module-flags :feature version-control) -- returns a list of
   flags for this module.

Flags are also available from packages.el files.
This commit is contained in:
Henrik Lissner 2017-07-29 00:11:07 +02:00
parent 6e8726a624
commit 0b7b8800a2

View file

@ -90,6 +90,7 @@ missing) and shouldn't be deleted.")
"A backup of `load-path' before it was altered by `doom-initialize'. Used as a "A backup of `load-path' before it was altered by `doom-initialize'. Used as a
base by `doom!' and for calculating how many packages exist.") base by `doom!' and for calculating how many packages exist.")
(defvar doom--module nil)
(defvar doom--refresh-p nil) (defvar doom--refresh-p nil)
(setq load-prefer-newer (or noninteractive doom-debug-mode) (setq load-prefer-newer (or noninteractive doom-debug-mode)
@ -206,20 +207,20 @@ This aggressively reloads core autoload files."
:error)))))) :error))))))
(when (or force-p (not doom-modules)) (when (or force-p (not doom-modules))
(setq doom-modules nil) (setq doom-modules nil)
(funcall load-fn (expand-file-name "init.el" doom-emacs-dir)) (let (noninteractive)
(funcall load-fn (doom-module-path :private user-login-name "init.el") t) (funcall load-fn (expand-file-name "init.el" doom-emacs-dir))
(funcall load-fn (doom-module-path :private user-login-name "init.el") t))
(when load-p (when load-p
(cl-loop for file (mapc load-fn (file-expand-wildcards (expand-file-name "autoload/*.el" doom-core-dir))))
in (append (nreverse (file-expand-wildcards (expand-file-name "core*.el" doom-core-dir)))
(file-expand-wildcards (expand-file-name "autoload/*.el" doom-core-dir))
(doom--module-paths "config.el"))
do (funcall load-fn file t)))
(doom|finalize)) (doom|finalize))
(when (or force-p (not doom-packages)) (when (or force-p (not doom-packages))
(setq doom-packages nil) (setq doom-packages nil)
(funcall load-fn (expand-file-name "packages.el" doom-core-dir)) (funcall load-fn (expand-file-name "packages.el" doom-core-dir))
(dolist (file (doom--module-paths "packages.el")) (cl-loop for (module . submodule) in (doom--module-pairs)
(funcall load-fn file t))))) for path = (doom-module-path module submodule "packages.el")
do
(let ((doom--module (cons module submodule)))
(funcall load-fn path t))))))
(defun doom-initialize-modules (modules) (defun doom-initialize-modules (modules)
"Adds MODULES to `doom-modules'. MODULES must be in mplist format. "Adds MODULES to `doom-modules'. MODULES must be in mplist format.
@ -240,8 +241,10 @@ This aggressively reloads core autoload files."
if (file-directory-p path) if (file-directory-p path)
collect (intern (file-name-nondirectory path)) into paths collect (intern (file-name-nondirectory path)) into paths
finally return (cons mode paths)))) finally return (cons mode paths))))
((listp m)
(doom-module-enable mode (car m) (cdr m)))
(t (t
(doom--enable-module mode m)))))) (doom-module-enable mode m))))))
(defun doom-module-path (module submodule &optional file) (defun doom-module-path (module submodule &optional file)
"Get the full path to a module: e.g. :lang emacs-lisp maps to "Get the full path to a module: e.g. :lang emacs-lisp maps to
@ -253,10 +256,24 @@ This aggressively reloads core autoload files."
(expand-file-name (concat module "/" submodule "/" file) (expand-file-name (concat module "/" submodule "/" file)
doom-modules-dir)) doom-modules-dir))
(defun doom-module-flags (module submodule)
"Returns a list of flags provided for MODULE SUBMODULE."
(and (hash-table-p doom-modules)
(gethash (cons module submodule) doom-modules)))
(defun doom-module-loaded-p (module submodule) (defun doom-module-loaded-p (module submodule)
"Returns t if MODULE->SUBMODULE is present in `doom-modules'." "Returns t if MODULE->SUBMODULE is present in `doom-modules'."
(and doom-modules (and (doom-module-flags module submodule) t))
(gethash (cons module submodule) doom-modules)))
(defun doom-module-enable (module submodule &optional flags)
"Adds MODULE and SUBMODULE to `doom-modules', overwriting it if it exists.
MODULE is a keyword, SUBMODULE is a symbol. e.g. :lang 'emacs-lisp.
Used by `require!' and `depends-on!'."
(puthash (cons module submodule)
(doom-enlist (or flags t))
doom-modules))
(defun doom--module-pairs () (defun doom--module-pairs ()
"Returns `doom-modules' as a list of (MODULE . SUBMODULE) cons cells. The list "Returns `doom-modules' as a list of (MODULE . SUBMODULE) cons cells. The list
@ -276,14 +293,7 @@ added, if the file exists."
(when (file-exists-p path) (when (file-exists-p path)
(push path paths)))))) (push path paths))))))
(defun doom--enable-module (module submodule &optional force-p)
"Adds MODULE and SUBMODULE to `doom-modules', if it isn't already there (or if
FORCE-P is non-nil). MODULE is a keyword, SUBMODULE is a symbol. e.g. :lang
'emacs-lisp.
Used by `require!' and `depends-on!'."
(unless (or force-p (doom-module-loaded-p module submodule))
(puthash (cons module submodule) t doom-modules)))
(defun doom--display-benchmark () (defun doom--display-benchmark ()
(message "Loaded %s packages in %.03fs" (message "Loaded %s packages in %.03fs"
@ -307,14 +317,14 @@ MODULES is an malformed plist of modules to load."
(doom-initialize-modules modules) (doom-initialize-modules modules)
(when (and user-login-name (when (and user-login-name
(not (doom-module-loaded-p :private (intern user-login-name)))) (not (doom-module-loaded-p :private (intern user-login-name))))
(doom--enable-module :private user-login-name)) (doom-module-enable :private user-login-name))
`(let (file-name-handler-alist) `(let (file-name-handler-alist)
(setq doom-modules ',doom-modules) (setq doom-modules ',doom-modules)
(unless noninteractive (unless noninteractive
(load ,(doom-module-path :private user-login-name "init") t t) (load ,(doom-module-path :private user-login-name "init") t t)
,@(cl-loop for (module . submodule) in (doom--module-pairs) ,@(cl-loop for (module . submodule) in (doom--module-pairs)
collect `(require! ,module ,submodule t)) collect `(require! ,module ,submodule nil t))
(when (display-graphic-p) (when (display-graphic-p)
(require 'server) (require 'server)
@ -362,6 +372,32 @@ to have them return non-nil (or exploit that to overwrite Doom's config)."
(t (t
(error "'%s' isn't a valid hook for def-package-hook!" when)))) (error "'%s' isn't a valid hook for def-package-hook!" when))))
(defmacro def-feature! (feature)
"Defines (and conditionally loads) FEATURE.
FEATURE is a symbol representing a file in the current module, denoted by a '+'
prefix. e.g. +git. Flags are set in `doom!'.
For example:
;; init.el
(doom! :lang (haskell +intero))
;; modules/lang/haskell/config.el
(def-feature! +intero) ;;
(def-feature! +dante)
lang/haskell/+intero.el will be loaded.
Flags can be detected with `featurep!'. e.g. '(featurep! :lang haskell +intero)'
Or more concisely (if from inside a module) '(featurep! +intero)'."
`(cond ((featurep! ,(car doom--module) ,(cdr doom--module) ,feature)
(load! ,feature))
(doom-debug-mode
(lwarn 'doom-module-feature :warning
"Feature %s in '%s %s' is not enabled"
',feature ,(car doom--module) ',(cdr doom--module)))))
(defmacro load! (filesym &optional path noerror) (defmacro load! (filesym &optional path noerror)
"Load a file relative to the current executing file (`load-file-name'). "Load a file relative to the current executing file (`load-file-name').
@ -386,28 +422,40 @@ If NOERROR is non-nil, don't throw an error if the file doesn't exist."
(error "Could not find %s" filename)) (error "Could not find %s" filename))
(let ((file (expand-file-name (concat filename ".el") path))) (let ((file (expand-file-name (concat filename ".el") path)))
(if (file-exists-p file) (if (file-exists-p file)
`(load ,(file-name-sans-extension file) ,noerror ,(not doom-debug-mode)) `(load ,(file-name-sans-extension file) ,noerror
,(not doom-debug-mode))
(unless noerror (unless noerror
(error "Could not load! file %s" file)))))) (error "Could not load! file %s" file))))))
(defmacro require! (module submodule &optional reload-p) (defmacro require! (module submodule &optional flags reload-p)
"Loads the module specified by MODULE (a property) and SUBMODULE (a symbol). "Loads the module specified by MODULE (a property) and SUBMODULE (a symbol).
The module is only loaded once. If RELOAD-P is non-nil, load it again." The module is only loaded once. If RELOAD-P is non-nil, load it again."
(let ((loaded-p (doom-module-loaded-p module submodule))) (let ((loaded-p (doom-module-loaded-p module submodule)))
(when (or reload-p (not loaded-p)) (when (or reload-p (not loaded-p))
(unless loaded-p (unless loaded-p
(doom--enable-module module submodule t)) (doom-module-enable module submodule flags))
`(condition-case-unless-debug ex `(condition-case-unless-debug ex
(load! config ,(doom-module-path module submodule) t) (let ((doom--module ',(cons module submodule)))
(load! config ,(doom-module-path module submodule) t))
('error ('error
(lwarn 'doom-modules :error (lwarn 'doom-modules :error
"%s in '%s %s' -> %s" "%s in '%s %s' -> %s"
(car ex) ,module ',submodule (error-message-string ex))))))) (car ex) ,module ',submodule
(error-message-string ex)))))))
(defmacro featurep! (module submodule) (defmacro featurep! (module &optional submodule flag)
"Convenience macro wrapper for `doom-module-loaded-p'." "A convenience macro wrapper for `doom-module-loaded-p'. It is evaluated at
(doom-module-loaded-p module submodule)) compile-time/macro-expansion time."
(unless submodule
(unless doom--module
(error "featurep! was used incorrectly (doom--module wasn't unset)"))
(setq flag module
module (car doom--module)
submodule (cdr doom--module)))
(if flag
(and (memq flag (doom-module-flags module submodule)) t)
(doom-module-loaded-p module submodule)))
;; ;;
@ -461,7 +509,7 @@ Only use this macro in a module's packages.el file.
MODULE is a keyword, and SUBMODULE is a symbol. Under the hood, this simply MODULE is a keyword, and SUBMODULE is a symbol. Under the hood, this simply
loads MODULE SUBMODULE's packages.el file." loads MODULE SUBMODULE's packages.el file."
(doom--enable-module module submodule) (doom-module-enable module submodule)
`(load! packages ,(doom-module-path module submodule) t)) `(load! packages ,(doom-module-path module submodule) t))