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
base by `doom!' and for calculating how many packages exist.")
(defvar doom--module nil)
(defvar doom--refresh-p nil)
(setq load-prefer-newer (or noninteractive doom-debug-mode)
@ -206,20 +207,20 @@ This aggressively reloads core autoload files."
:error))))))
(when (or force-p (not doom-modules))
(setq doom-modules nil)
(funcall load-fn (expand-file-name "init.el" doom-emacs-dir))
(funcall load-fn (doom-module-path :private user-login-name "init.el") t)
(let (noninteractive)
(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
(cl-loop for file
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)))
(mapc load-fn (file-expand-wildcards (expand-file-name "autoload/*.el" doom-core-dir))))
(doom|finalize))
(when (or force-p (not doom-packages))
(setq doom-packages nil)
(funcall load-fn (expand-file-name "packages.el" doom-core-dir))
(dolist (file (doom--module-paths "packages.el"))
(funcall load-fn file t)))))
(cl-loop for (module . submodule) in (doom--module-pairs)
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)
"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)
collect (intern (file-name-nondirectory path)) into paths
finally return (cons mode paths))))
((listp m)
(doom-module-enable mode (car m) (cdr m)))
(t
(doom--enable-module mode m))))))
(doom-module-enable mode m))))))
(defun doom-module-path (module submodule &optional file)
"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)
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)
"Returns t if MODULE->SUBMODULE is present in `doom-modules'."
(and doom-modules
(gethash (cons module submodule) doom-modules)))
(and (doom-module-flags module submodule) t))
(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 ()
"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)
(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 ()
(message "Loaded %s packages in %.03fs"
@ -307,14 +317,14 @@ MODULES is an malformed plist of modules to load."
(doom-initialize-modules modules)
(when (and 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)
(setq doom-modules ',doom-modules)
(unless noninteractive
(load ,(doom-module-path :private user-login-name "init") t t)
,@(cl-loop for (module . submodule) in (doom--module-pairs)
collect `(require! ,module ,submodule t))
collect `(require! ,module ,submodule nil t))
(when (display-graphic-p)
(require 'server)
@ -362,6 +372,32 @@ to have them return non-nil (or exploit that to overwrite Doom's config)."
(t
(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)
"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))
(let ((file (expand-file-name (concat filename ".el") path)))
(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
(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).
The module is only loaded once. If RELOAD-P is non-nil, load it again."
(let ((loaded-p (doom-module-loaded-p module submodule)))
(when (or reload-p (not loaded-p))
(unless loaded-p
(doom--enable-module module submodule t))
(doom-module-enable module submodule flags))
`(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
(lwarn 'doom-modules :error
"%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)
"Convenience macro wrapper for `doom-module-loaded-p'."
(doom-module-loaded-p module submodule))
(defmacro featurep! (module &optional submodule flag)
"A convenience macro wrapper for `doom-module-loaded-p'. It is evaluated at
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
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))