- Description
- Install
- Usage
- Customize
- Syntaxes
- Basic keywords
- Configure variables keywords
- Configure list keywords
- Condition keywords
- Byte compile keywords
- Documentation keywords
- System keywords
- Information
leaf.el
is yet another use-package.
leaf
solves the stress that I feel while using the use-package for 2.5 years.
By developing from scratch, we have a cleaner and more predictable implementation than use-package
.
This makes it easy to maintain and add new keywords. (see leaf-keywords.el)
leaf
has keywords almost identical to use-package
, but some of usage of the keywords is different.
The quickest way to solve problem is to use macroexpand-1
to see the unfolded result if it is not what you intended.
And also there are also a number of samples in this README and more in the test file.
In addition, since it works with Emacs-23, you can use your usual init.el as usual, even if you are temporarily using fossil-like Emacs on loan.
(Of course, there are not many packages that work perfectly with Emacs-23. These packages will be installed by the package manager and will probably report an error.
leaf
processes this and cancels the configuration for that package.
It then attempts to process the next package.)
Currently, leaf.el
and leaf-keywords.el
has below rich keywords.
(leaf-available-keywords)
;; => (:disabled
;; :leaf-protect
;; :load-path :leaf-autoload
;; :doc :file :url
;; :defun :defvar
;; :preface
;; :when :unless :if
;; :ensure :package :straight :el-get
;; :after
;; :commands
;; :bind :bind* :mode :interpreter :magic :magic-fallback :hook
;; :advice :advice-remove
;; :hydra :combo :combo* :smartrep :smartrep* :chord :chord*
;; :leaf-defer
;; :pre-setq :init
;; :require
;; :custom :custom-face :setq :setq-default
;; :diminish :delight
;; :config)
leaf.el
and leaf-keywords.el
can install with package.el from MELPA, so sample instration code is below.
In order to work from Emacs-23, the package manager and the key binding manager
that accompanies leaf
must also be developed with the assumption that they will work from Emacs-23.
I have plans to develop it, but it’s not finished yet.
Package to be developed
- feather.el instead of
package.el
- leaf-key.el instead of
bind-key
-> (Achieved! Nowleaf
builtin)
(prog1 "leaf"
(prog1 "install leaf"
(custom-set-variables
'(package-archives '(("org" . "https://orgmode.org/elpa/")
("melpa" . "https://melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/"))))
(package-initialize)
(unless (package-installed-p 'leaf)
(package-refresh-contents)
(package-install 'leaf)))
(leaf leaf-keywords
:ensure t
:config
;; optional packages if you want to use :hydra, :el-get,,,
(leaf hydra :ensure t)
(leaf el-get :ensure t
:custom ((el-get-git-shallow-clone . t)))
;; initialize leaf-keywords.el
(leaf-keywords-init)))
Put leaf.el
at any folder added load-path
.
Then (require 'leaf)
and use like use-pacakge
.
(In this example, you installed/loaded leaf directly, so you can configure package.el
using leaf
.)
;; add to load-path
;; (locate-user-emacs-file "site-lisp/leaf.el")
;; => "~/.emacs.d/local/26.1/site-lisp/leaf.el"
(prog1 "leaf"
(add-to-list 'load-path (locate-user-emacs-file "site-lisp/leaf.el"))
(require 'leaf)
(leaf package
:custom ((package-archives . '(("org" . "https://orgmode.org/elpa/")
("melpa" . "https://melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/"))))
:config
(package-initialize))
(leaf leaf-keywords
:ensure t
:config
;; optional packages if you want to use :hydra, :el-get,,,
(leaf hydra :ensure t)
(leaf el-get :ensure t
:custom ((el-get-git-shallow-clone . t)))
;; initialize leaf-keywords.el
(leaf-keywords-init)))
Use leaf
in your init.el like use-package
.
You declaratively tell the leaf
to configure the package using special keywords.
leaf
converts your declaration into Elisp for Emacs to understand, and Emacs executes it to configure the package.
- leaf-defaults: Default arguments for all leaf-block.
- leaf-expand-{{keyword}}: If nil, not to expand that keyword.
- leaf-expand-minimally: If nil, disable keywords that are not needed for debugging.
All below examples are excerpts from leaf-tests.el
.
These examples are defined in the following format. We expect FORM will be expanded to EXPECT.
(cort-deftest-with-macroexpand TESTCASE-NAME
'((FORM ; will be expand by `macroexpand-1'
EXPECT) ; expect FORM's expansion will be EXPECT (test by `equal')
(FORM
EXPECT)
...))
(cort-deftest-with-macroexpand-let TESTCASE-NAME
LETFORM
'((FORM ; will be expand by `macroexpand-1' in LETFORM
EXPECT) ; expect FORM's expansion will be EXPECT (test by `equal')
(FORM
EXPECT)
...))
Unlike use-package, leaf
will convert to nil
when used without any keywords.
(cort-deftest-with-macroexpand leaf/none
'(((leaf leaf)
(prog1 'leaf))))
If you want to require
, you must use the :require
keyword explicitly.
This is ideally the exact opposite of using the :no-require
keyword in the use-package
if you does not want to require
it.
The leaf’s :require
keyword is powerful, specify t
to require the package, and specify multi symbols to require
all of them.
Since the priority is lower than that of the conditional branch keyword described later,
it is possible to assign whether to require
or not by the conditional branch keyword.
(cort-deftest-with-macroexpand leaf/require
'(((leaf leaf
:init (leaf-pre-init)
:require t
:config (leaf-init))
(prog1 'leaf
(leaf-pre-init)
(require 'leaf)
(leaf-init)))
((leaf leaf
:init (leaf-pre-init)
:require nil
:config (leaf-init))
(prog1 'leaf
(leaf-pre-init)
(leaf-init)))
((leaf leaf
:init (leaf-pre-init)
:require leaf leaf-polyfill
:config (leaf-init))
(prog1 'leaf
(leaf-pre-init)
(require 'leaf)
(require 'leaf-polyfill)
(leaf-init)))
((leaf leaf
:init (leaf-pre-init)
:require t
:require leaf-polyfill
:config (leaf-init))
(prog1 'leaf
(leaf-pre-init)
(require 'leaf)
(require 'leaf-polyfill)
(leaf-init)))
((leaf leaf
:init (leaf-pre-init)
:require t leaf-polyfill
:config (leaf-init))
(prog1 'leaf
(leaf-pre-init)
(require 'leaf)
(require 'leaf-polyfill)
(leaf-init)))
((leaf leaf
:init (leaf-pre-init)
:require (leaf leaf-polyfill leaf-sub leaf-subsub)
:config (leaf-init))
(prog1 'leaf
(leaf-pre-init)
(require 'leaf)
(require 'leaf-polyfill)
(require 'leaf-sub)
(require 'leaf-subsub)
(leaf-init)))))
:package
provide package.el
frontend.
Because leaf-keywords.el has :el-get
keyword, :package
provide package.el
frontend.
By the mechanism described below, :ensure
is an alias to :package
,
you can also use :ensure
as :package
.
(cort-deftest-with-macroexpand leaf/package
'(((leaf leaf
:package t
:config (leaf-init))
(prog1 'leaf
(leaf-handler-package leaf leaf nil)
(leaf-init)))
((leaf leaf
:package t leaf-browser
:config (leaf-init))
(prog1 'leaf
(leaf-handler-package leaf leaf nil)
(leaf-handler-package leaf leaf-browser nil)
(leaf-init)))
((leaf leaf
:package feather leaf-key leaf-browser
:config (leaf-init))
(prog1 'leaf
(leaf-handler-package leaf feather nil)
(leaf-handler-package leaf leaf-key nil)
(leaf-handler-package leaf leaf-browser nil)
(leaf-init)))
((leaf leaf
:package (t . pin))
(prog1 'leaf
(leaf-handler-package leaf leaf pin)))))
(cort-deftest-with-macroexpand leaf/handler-package
'(((leaf macrostep :ensure t)
(prog1 'macrostep
(leaf-handler-package macrostep macrostep nil))
((leaf-handler-package macrostep macrostep nil)
(unless
(package-installed-p 'macrostep)
(condition-case err
(progn
(unless (assoc 'macrostep package-archive-contents)
(package-refresh-contents))
(package-install 'macrostep))
(error
(condition-case err
(progn
(package-refresh-contents)
(package-install 'macrostep))
(error
(leaf-error "In `macrostep' block, failed to :package of macrostep. Error msg: %s"
(error-message-string err)))))))))))
These keywords are provided to directly describe elisp with various settings that leaf
does not support.
These keywords are provided to control where the arguments expand,
:preface
expands before the conditional branch keyword (:if
when
unless
):init
expands after the conditional branch keyword before:require
:config
expands after:require
You don’t need to put progn
because leaf
can receive multiple S-expressions, but you can do so if you prefer it.
(cort-deftest-with-macroexpand leaf/preface
'(((leaf leaf
:init (leaf-pre-init)
:require t
:config (leaf-init))
(prog1 'leaf
(leaf-pre-init)
(require 'leaf)
(leaf-init)))
((leaf leaf
:preface (progn
(leaf-pre-init)
(leaf-pre-init-after))
:require t
:config (leaf-init))
(prog1 'leaf
(progn
(leaf-pre-init)
(leaf-pre-init-after))
(require 'leaf)
(leaf-init)))
((leaf leaf
:preface
(leaf-pre-init)
(leaf-pre-init-after)
:require t
:config (leaf-init))
(prog1 'leaf
(leaf-pre-init)
(leaf-pre-init-after)
(require 'leaf)
(leaf-init)))
((leaf leaf
:preface (preface-init)
:when (some-condition)
:require t
:init (package-preconfig)
:config (package-init))
(prog1 'leaf
(preface-init)
(when (some-condition)
(package-preconfig)
(require 'leaf)
(package-init))))))
commands
keyword configures autoload
for its leaf-block name.
(cort-deftest-with-macroexpand leaf/commands
'(((leaf leaf
:commands leaf
:config (leaf-init))
(prog1 'leaf
(autoload #'leaf "leaf" nil t)
(eval-after-load 'leaf
'(progn
(leaf-init)))))
((leaf leaf
:commands leaf leaf-pairp leaf-plist-get)
(prog1 'leaf
(autoload #'leaf "leaf" nil t)
(autoload #'leaf-pairp "leaf" nil t)
(autoload #'leaf-plist-get "leaf" nil t)))
((leaf leaf
:commands leaf (leaf-pairp leaf-plist-get))
(prog1 'leaf
(autoload #'leaf "leaf" nil t)
(autoload #'leaf-pairp "leaf" nil t)
(autoload #'leaf-plist-get "leaf" nil t)))
((leaf leaf
:commands leaf (leaf-pairp leaf-plist-get (leaf
(leaf-pairp
(leaf-pairp
(leaf-insert-after))))))
(prog1 'leaf
(autoload #'leaf "leaf" nil t)
(autoload #'leaf-pairp "leaf" nil t)
(autoload #'leaf-plist-get "leaf" nil t)
(autoload #'leaf-insert-after "leaf" nil t)))))
:after
keyword configure loading order.
Currently it does not support ~:or~ in ~:after~ like use-package.
(cort-deftest-with-macroexpand leaf/after
'(((leaf leaf-browser
:after leaf
:require t
:config (leaf-browser-init))
(prog1 'leaf-browser
(eval-after-load 'leaf
'(progn
(require 'leaf-browser)
(leaf-browser-init)))))
((leaf leaf-browser
:after leaf org orglyth
:require t
:config (leaf-browser-init))
(prog1 'leaf-browser
(eval-after-load 'orglyth
'(eval-after-load 'org
'(eval-after-load 'leaf
'(progn
(require 'leaf-browser)
(leaf-browser-init)))))))
((leaf leaf-browser
:after leaf (org orglyth)
:require t
:config (leaf-browser-init))
(prog1 'leaf-browser
(eval-after-load 'orglyth
'(eval-after-load 'org
'(eval-after-load 'leaf
'(progn
(require 'leaf-browser)
(leaf-browser-init)))))))
((leaf leaf-browser
:after leaf (org orglyth
(org
(org
(org-ex))))
:require t
:config (leaf-browser-init))
(prog1 'leaf-browser
(eval-after-load 'org-ex
'(eval-after-load 'orglyth
'(eval-after-load 'org
'(eval-after-load 'leaf
'(progn
(require 'leaf-browser)
(leaf-browser-init))))))))))
:bind
and :bind*
provide frontend for keybind manager.
When defined globally, key bindings and their corresponding functions are specified in dotted pairs.
To set it to a specific map, place the map name as a keyword or symbol at the top of the list.
These pair and list can also be used in list. Note that these require a symbol with the map name at the top of the list.
If you omit :package
, use leaf-block name as :package
to lazy load.
(cort-deftest-with-macroexpand leaf/bind
'(((leaf macrostep
:package t
:bind (("C-c e" . macrostep-expand)))
(prog1 'macrostep
(autoload #'macrostep-expand "macrostep" nil t)
(leaf-handler-package macrostep macrostep nil)
(leaf-keys (("C-c e" . macrostep-expand)))))
((leaf macrostep
:package t
:bind ("C-c e" . macrostep-expand))
(prog1 'macrostep
(autoload #'macrostep-expand "macrostep" nil t)
(leaf-handler-package macrostep macrostep nil)
(leaf-keys
(("C-c e" . macrostep-expand)))))
((leaf color-moccur
:bind
("M-s O" . moccur)
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all))
(prog1 'color-moccur
(autoload #'moccur "color-moccur" nil t)
(autoload #'isearch-moccur "color-moccur" nil t)
(autoload #'isearch-moccur-all "color-moccur" nil t)
(leaf-keys (("M-s O" . moccur)
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all)))))
((leaf color-moccur
:bind (("M-s O" . moccur)
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all)))
(prog1 'color-moccur
(autoload #'moccur "color-moccur" nil t)
(autoload #'isearch-moccur "color-moccur" nil t)
(autoload #'isearch-moccur-all "color-moccur" nil t)
(leaf-keys (("M-s O" . moccur)
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all)))))
((leaf color-moccur
:bind
("M-s" . nil)
("M-s o" . isearch-moccur)
("M-s i" . isearch-moccur-all))
(prog1 'color-moccur
(autoload #'isearch-moccur "color-moccur" nil t)
(autoload #'isearch-moccur-all "color-moccur" nil t)
(leaf-keys (("M-s")
("M-s o" . isearch-moccur)
("M-s i" . isearch-moccur-all)))))
((leaf color-moccur
:bind (("M-s" . nil)
("M-s o" . isearch-moccur)
("M-s i" . isearch-moccur-all)))
(prog1 'color-moccur
(autoload #'isearch-moccur "color-moccur" nil t)
(autoload #'isearch-moccur-all "color-moccur" nil t)
(leaf-keys (("M-s")
("M-s o" . isearch-moccur)
("M-s i" . isearch-moccur-all)))))
((leaf color-moccur
:bind
("M-s O" . moccur)
(:isearch-mode-map
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all)))
(prog1 'color-moccur
(autoload #'moccur "color-moccur" nil t)
(autoload #'isearch-moccur "color-moccur" nil t)
(autoload #'isearch-moccur-all "color-moccur" nil t)
(leaf-keys (("M-s O" . moccur)
(:isearch-mode-map
:package color-moccur
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all))))))
((leaf color-moccur
:bind
("M-s O" . moccur)
(:isearch-mode-map
:package isearch
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all)))
(prog1 'color-moccur
(autoload #'moccur "color-moccur" nil t)
(autoload #'isearch-moccur "color-moccur" nil t)
(autoload #'isearch-moccur-all "color-moccur" nil t)
(leaf-keys (("M-s O" . moccur)
(:isearch-mode-map
:package isearch
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all))))))
((leaf color-moccur
:bind (("M-s O" . moccur)
(:isearch-mode-map
:package isearch
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all))))
(prog1 'color-moccur
(autoload #'moccur "color-moccur" nil t)
(autoload #'isearch-moccur "color-moccur" nil t)
(autoload #'isearch-moccur-all "color-moccur" nil t)
(leaf-keys (("M-s O" . moccur)
(:isearch-mode-map
:package isearch
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all))))))
;; you also use symbol instead of keyword to specify keymap
((leaf color-moccur
:bind (("M-s O" . moccur)
(isearch-mode-map
:package isearch
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all))))
(prog1 'color-moccur
(autoload #'moccur "color-moccur" nil t)
(autoload #'isearch-moccur "color-moccur" nil t)
(autoload #'isearch-moccur-all "color-moccur" nil t)
(leaf-keys (("M-s O" . moccur)
(isearch-mode-map
:package isearch
("M-o" . isearch-moccur)
("M-O" . isearch-moccur-all))))))))
:advice
provide frontend of advice-add
, and :advice-remove
provide frontend of advice-remove
.
:advice
keyword accept list of (WHERE SYMBOL FUNCTION)
or nested it.
You can use all WHERE
symbol such as
(:around
:before
:after
:override
:after-until
:after-while
:before-until
:before-while
:filter-args
:filter-return
)
SYMBOL
is the adviced function symbol, FUNCTION
is advice function symbol or lambda form.
:advice-remove
must not specify WHERE
keyword.
(cort-deftest-with-macroexpand leaf/advice
'(((leaf leaf
:preface
(defun matu (x)
(princ (format ">>%s<<" x))
nil)
(defun matu-around0 (f &rest args)
(prog2
(princ "around0 ==>")
(apply f args)
(princ "around0 <==")))
(defun matu-before0 (&rest args)
(princ "before0:"))
:advice
(:around matu matu-around0)
(:before matu matu-before0))
(prog1 'leaf
(autoload #'matu-around0 "leaf" nil t)
(autoload #'matu-before0 "leaf" nil t)
(defun matu (x)
(princ
(format ">>%s<<" x))
nil)
(defun matu-around0
(f &rest args)
(prog2
(princ "around0 ==>")
(apply f args)
(princ "around0 <==")))
(defun matu-before0
(&rest args)
(princ "before0:"))
(advice-add 'matu :around #'matu-around0)
(advice-add 'matu :before #'matu-before0)))
((leaf leaf
:preface
(defun matu (x)
(princ (format ">>%s<<" x))
nil)
(defun matu-around0 (f &rest args)
(prog2
(princ "around0 ==>")
(apply f args)
(princ "around0 <==")))
(defun matu-before0 (&rest args)
(princ "before0:"))
:advice ((:around matu matu-around0)
(:before matu matu-before0)))
(prog1 'leaf
(autoload #'matu-around0 "leaf" nil t)
(autoload #'matu-before0 "leaf" nil t)
(defun matu (x)
(princ
(format ">>%s<<" x))
nil)
(defun matu-around0
(f &rest args)
(prog2
(princ "around0 ==>")
(apply f args)
(princ "around0 <==")))
(defun matu-before0
(&rest args)
(princ "before0:"))
(advice-add 'matu :around #'matu-around0)
(advice-add 'matu :before #'matu-before0)))
((leaf leaf
:preface
(defun matu (x)
(princ (format ">>%s<<" x))
nil)
(defun matu-around0 (f &rest args)
(prog2
(princ "around0 ==>")
(apply f args)
(princ "around0 <==")))
(defun matu-before0 (&rest args)
(princ "before0:"))
:advice ((:around matu matu-around0)
(:before matu matu-before0)
(:around matu (lambda (f &rest args)
(prog2
(princ "around1 ==>")
(apply f args)
(princ "around1 <=="))))))
(prog1 'leaf
(autoload #'matu-around0 "leaf" nil t)
(autoload #'matu-before0 "leaf" nil t)
(defun matu
(x)
(princ
(format ">>%s<<" x))
nil)
(defun matu-around0
(f &rest args)
(prog2
(princ "around0 ==>")
(apply f args)
(princ "around0 <==")))
(defun matu-before0
(&rest args)
(princ "before0:"))
(advice-add 'matu :around #'matu-around0)
(advice-add 'matu :before #'matu-before0)
(advice-add 'matu :around (function
(lambda
(f &rest args)
(prog2
(princ "around1 ==>")
(apply f args)
(princ "around1 <==")))))))))
(cort-deftest-with-macroexpand leaf/advice-remove
'(((leaf leaf
:advice-remove
(matu matu-around0)
(matu matu-before0))
(prog1 'leaf
(autoload (function matu-before0) "leaf" nil t)
(autoload #'matu-around0 "leaf" nil t)
(advice-remove 'matu #'matu-around0)
(advice-remove 'matu #'matu-before0)))
((leaf leaf
:advice-remove ((:around matu matu-around0)
(:before matu matu-before0)))
(prog1 'leaf
(autoload #'matu "leaf" nil t)
(advice-remove ':around #'matu)
(advice-remove ':before #'matu)))))
Now that the proper Elisp packaging practices have become widely known,
it is a best practice to use custom-set-variables
to customize packages.
Unlike use-package, you must specify a dot pair.
You can of course set multiple variables and set the evaluation result of the S expression to a variable.
The value set to custom-face
should also be quoed to emphasize uniformity as leaf
.
(cort-deftest-with-macroexpand leaf/custom
'(((leaf flyspell-correct-ivy
:bind (("C-M-i" . flyspell-correct-wrapper))
:custom ((flyspell-correct-interface . #'flyspell-correct-ivy)))
(prog1 'flyspell-correct-ivy
(autoload #'flyspell-correct-wrapper "flyspell-correct-ivy" nil t)
(leaf-keys (("C-M-i" . flyspell-correct-wrapper)))
(eval-after-load 'flyspell-correct-ivy
'(progn
(custom-set-variables
'(flyspell-correct-interface #'flyspell-correct-ivy "Customized with leaf in flyspell-correct-ivy block"))))))
((leaf leaf
:custom ((leaf-backend-ensure . 'feather)))
(prog1 'leaf
(custom-set-variables
'(leaf-backend-ensure 'feather "Customized with leaf in leaf block"))))
((leaf leaf
:custom ((leaf-backend-ensure . 'feather)
(leaf-backend-bind . 'bind-key)
(leaf-backend-bind* . 'bind-key)))
(prog1 'leaf
(custom-set-variables
'(leaf-backend-ensure 'feather "Customized with leaf in leaf block")
'(leaf-backend-bind 'bind-key "Customized with leaf in leaf block")
'(leaf-backend-bind* 'bind-key "Customized with leaf in leaf block"))))
((leaf leaf
:custom
(leaf-backend-ensure . 'feather)
(leaf-backend-bind . 'bind-key)
(leaf-backend-bind* . 'bind-key))
(prog1 'leaf
(custom-set-variables
'(leaf-backend-ensure 'feather "Customized with leaf in leaf block")
'(leaf-backend-bind 'bind-key "Customized with leaf in leaf block")
'(leaf-backend-bind* 'bind-key "Customized with leaf in leaf block"))))
((leaf buffer.c
:custom ((cursor-type . nil)))
(prog1 'buffer\.c
(custom-set-variables
'(cursor-type nil "Customized with leaf in buffer.c block"))))))
(cort-deftest-with-macroexpand leaf/custom-face
'(((leaf eruby-mode
:custom-face
(eruby-standard-face . '((t (:slant italic)))))
(prog1 'eruby-mode
(custom-set-faces
'(eruby-standard-face ((t (:slant italic)))))))))
These keywords provide a front end to just setq
, setq-default
.
Because there are packages in the world that must be setq
before doing require
them,
the :pre-setq
keyword is also provided to accommodate them.
The argument specified for :pre-setq
is expanded before :require
.
You can of course configure multiple variables adn set the evaluation result of some S expression to variable.
(cort-deftest-with-macroexpand leaf/setq
'(((leaf alloc
:setq `((gc-cons-threshold . ,(* 512 1024 1024))
(garbage-collection-messages . t))
:require t)
(prog1 'alloc
(require 'alloc)
(setq gc-cons-threshold 536870912)
(setq garbage-collection-messages t)))
((leaf alloc
:setq ((gc-cons-threshold . 536870912)
(garbage-collection-messages . t))
:require t)
(prog1 'alloc
(require 'alloc)
(setq gc-cons-threshold 536870912)
(setq garbage-collection-messages t)))
((leaf leaf
:setq
(leaf-backend-bind . 'bind-key)
(leaf-backend-bind* . 'bind-key)
:require t)
(prog1 'leaf
(require 'leaf)
(setq leaf-backend-bind 'bind-key)
(setq leaf-backend-bind* 'bind-key)))))
(cort-deftest-with-macroexpand leaf/pre-setq
'(((leaf alloc
:pre-setq `((gc-cons-threshold . ,(* 512 1024 1024))
(garbage-collection-messages . t))
:require t)
(prog1 'alloc
(setq gc-cons-threshold 536870912)
(setq garbage-collection-messages t)
(require 'alloc)))))
(cort-deftest-with-macroexpand leaf/setq-default
'(((leaf alloc
:setq-default `((gc-cons-threshold . ,(* 512 1024 1024))
(garbage-collection-messages . t))
:require t)
(prog1 'alloc
(require 'alloc)
(setq-default gc-cons-threshold 536870912)
(setq-default garbage-collection-messages t)))))
:mode
keyword define auto-mode-alist
. Specifies the major-mode to enable by file extension.
:interpreter
keyword define interpreter-mode-alist
. Specifies the major-mode to enable by file shebang.
If you pass symbol to these keyword, use leaf block name as major-mode. If you want to specify major-mode, pass dotted pair value.
(cort-deftest-with-macroexpand leaf/mode
'(((leaf web-mode
:mode "\\.js\\'" "\\.p?html?\\'")
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'auto-mode-alist '("\\.js\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.p?html?\\'" . web-mode))))
((leaf web-mode
:mode ("\\.js\\'" "\\.p?html?\\'"))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'auto-mode-alist '("\\.js\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.p?html?\\'" . web-mode))))
((leaf web-mode
:mode (("\\.js\\'" "\\.p?html?\\'") . web-mode))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'auto-mode-alist '("\\.js\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.p?html?\\'" . web-mode))))
((leaf web-mode
:mode (("\\.html\\'" . web-mode)
(("\\.js\\'" "\\.p?html?\\'") . web-mode)))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.js\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.p?html?\\'" . web-mode))))))
(cort-deftest-with-macroexpand leaf/interpreter
'(((leaf ruby-mode
:mode "\\.rb\\'" "\\.rb2\\'" ("\\.rbg\\'" . rb-mode)
:interpreter "ruby")
(prog1 'ruby-mode
(autoload #'ruby-mode "ruby-mode" nil t)
(autoload #'rb-mode "ruby-mode" nil t)
(add-to-list 'auto-mode-alist '("\\.rb\\'" . ruby-mode))
(add-to-list 'auto-mode-alist '("\\.rb2\\'" . ruby-mode))
(add-to-list 'auto-mode-alist '("\\.rbg\\'" . rb-mode))
(add-to-list 'interpreter-mode-alist '("ruby" . ruby-mode))))
((leaf web-mode
:interpreter "js" "p?html?")
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'interpreter-mode-alist '("js" . web-mode))
(add-to-list 'interpreter-mode-alist '("p?html?" . web-mode))))
((leaf web-mode
:interpreter ("js" "p?html?"))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'interpreter-mode-alist '("js" . web-mode))
(add-to-list 'interpreter-mode-alist '("p?html?" . web-mode))))
((leaf web-mode
:interpreter (("js" "p?html?") . web-mode))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'interpreter-mode-alist '("js" . web-mode))
(add-to-list 'interpreter-mode-alist '("p?html?" . web-mode))))))
:magic
keyword define magic-mode-alist
. It is used to determine major-mode in binary header byte.
:magic-fallback
keyward also define magic-fallback-alist
.
(cort-deftest-with-macroexpand leaf/magic
'(((leaf pdf-tools
:magic ("%PDF" . pdf-view-mode)
:config
(pdf-tools-install))
(prog1 'pdf-tools
(autoload #'pdf-view-mode "pdf-tools" nil t)
(add-to-list 'magic-mode-alist '("%PDF" . pdf-view-mode))
(eval-after-load 'pdf-tools
'(progn
(pdf-tools-install)))))
((leaf web-mode
:magic "js" "p?html?")
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'magic-mode-alist '("js" . web-mode))
(add-to-list 'magic-mode-alist '("p?html?" . web-mode))))
((leaf web-mode
:magic ("js" "p?html?"))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'magic-mode-alist '("js" . web-mode))
(add-to-list 'magic-mode-alist '("p?html?" . web-mode))))
((leaf web-mode
:magic (("js" "p?html?") . web-mode))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'magic-mode-alist '("js" . web-mode))
(add-to-list 'magic-mode-alist '("p?html?" . web-mode))))))
(cort-deftest-with-macroexpand leaf/magic-fallback
'(((leaf pdf-tools
:magic-fallback ("%PDF" . pdf-view-mode)
:config
(pdf-tools-install))
(prog1 'pdf-tools
(autoload #'pdf-view-mode "pdf-tools" nil t)
(add-to-list 'magic-fallback-mode-alist '("%PDF" . pdf-view-mode))
(eval-after-load 'pdf-tools
'(progn
(pdf-tools-install)))))
((leaf web-mode
:magic-fallback "js" "p?html?")
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'magic-fallback-mode-alist '("js" . web-mode))
(add-to-list 'magic-fallback-mode-alist '("p?html?" . web-mode))))
((leaf web-mode
:magic-fallback ("js" "p?html?"))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'magic-fallback-mode-alist '("js" . web-mode))
(add-to-list 'magic-fallback-mode-alist '("p?html?" . web-mode))))
((leaf web-mode
:magic-fallback (("js" "p?html?") . web-mode))
(prog1 'web-mode
(autoload #'web-mode "web-mode" nil t)
(add-to-list 'magic-fallback-mode-alist '("js" . web-mode))
(add-to-list 'magic-fallback-mode-alist '("p?html?" . web-mode))))))
:hook
keyword define add-hook
via (add-to-list *-hook
).
Unlike use-package, you must spesify the full hook name. It makes easy to jump definition.
(cort-deftest-with-macroexpand leaf/hook
'(((leaf ace-jump-mode
:hook cc-mode-hook
:config (ace-jump-mode))
(prog1 'ace-jump-mode
(autoload #'ace-jump-mode "ace-jump-mode" nil t)
(add-hook 'cc-mode-hook #'ace-jump-mode)
(eval-after-load 'ace-jump-mode
'(progn
(ace-jump-mode)))))
((leaf ace-jump-mode
:hook cc-mode-hook)
(prog1 'ace-jump-mode
(autoload #'ace-jump-mode "ace-jump-mode" nil t)
(add-hook 'cc-mode-hook #'ace-jump-mode)))
((leaf ace-jump-mode
:hook cc-mode-hook prog-mode-hook)
(prog1 'ace-jump-mode
(autoload #'ace-jump-mode "ace-jump-mode" nil t)
(add-hook 'cc-mode-hook #'ace-jump-mode)
(add-hook 'prog-mode-hook #'ace-jump-mode)))
((leaf ace-jump-mode
:hook cc-mode-hook (prog-mode-hook . my-ace-jump-mode))
(prog1 'ace-jump-mode
(autoload #'ace-jump-mode "ace-jump-mode" nil t)
(autoload #'my-ace-jump-mode "ace-jump-mode" nil t)
(add-hook 'cc-mode-hook #'ace-jump-mode)
(add-hook 'prog-mode-hook #'my-ace-jump-mode)))
((leaf ace-jump-mode
:hook ((cc-mode-hook prog-mode-hook) . my-ace-jump-mode))
(prog1 'ace-jump-mode
(autoload #'my-ace-jump-mode "ace-jump-mode" nil t)
(add-hook 'cc-mode-hook #'my-ace-jump-mode)
(add-hook 'prog-mode-hook #'my-ace-jump-mode)))))
Unlike use-package, you must specify the full path.
Use backquotes if you want the path to be relative to the current .emacs.d
, such as use-package.
(cort-deftest-with-macroexpand leaf/load-path
'(((leaf leaf
:load-path "~/.emacs.d/elpa-archive/leaf.el/"
:require t
:config (leaf-init))
(prog1 'leaf
(add-to-list 'load-path "~/.emacs.d/elpa-archive/leaf.el/")
(require 'leaf)
(leaf-init)))
((leaf leaf
:load-path
"~/.emacs.d/elpa-archive/leaf.el/"
"~/.emacs.d/elpa-archive/leaf-browser.el/"
:require t
:config (leaf-init))
(prog1 'leaf
(add-to-list 'load-path "~/.emacs.d/elpa-archive/leaf.el/")
(add-to-list 'load-path "~/.emacs.d/elpa-archive/leaf-browser.el/")
(require 'leaf)
(leaf-init)))
((leaf leaf
:load-path ("~/.emacs.d/elpa-archive/leaf.el/"
"~/.emacs.d/elpa-archive/leaf-browser.el/")
:require t
:config (leaf-init))
(prog1 'leaf
(add-to-list 'load-path "~/.emacs.d/elpa-archive/leaf.el/")
(add-to-list 'load-path "~/.emacs.d/elpa-archive/leaf-browser.el/")
(require 'leaf)
(leaf-init)))
((leaf leaf
:load-path ("~/.emacs.d/elpa-archive/leaf.el/")
:load-path `(,(mapcar (lambda (elm)
(concat "~/.emacs.d/elpa-archive/" elm "/"))
'("leaf.el" "leaf-broser.el" "orglyth.el")))
:require t
:config (leaf-init))
(prog1 'leaf
(add-to-list 'load-path "~/.emacs.d/elpa-archive/leaf.el/")
(add-to-list 'load-path "~/.emacs.d/elpa-archive/leaf-broser.el/")
(add-to-list 'load-path "~/.emacs.d/elpa-archive/orglyth.el/")
(require 'leaf)
(leaf-init)))))
The :disabled
keyword provides the ability to temporarily nil
the output of that leaf
block.
You can use multiple values for the :disabled
keyword, or multiple :disabled
keyword,
but :disabled
only respects the value specified at the top.
It can also be said that old values can be overridden by described above.
As you can see from the internal structure of :disabled
,
you do not need to pass an exact t
to convert it to nil
because it is comparing it by unless
.
(defvar leaf-keywords
(cdt
'(:dummy
:disabled (unless (eval (car leaf--value)) `(,@leaf--body))
...)))
(cort-deftest-with-macroexpand leaf/disabled
'(((leaf leaf :disabled t :config (leaf-init))
nil)
((leaf leaf :disabled nil :config (leaf-init))
(prog1 'leaf
(leaf-init)))
((leaf leaf :disabled nil t :config (leaf-init))
(prog1 'leaf
(leaf-init)))
((leaf leaf :disabled t :disabled nil :config (leaf-init))
nil)))
:if
, :when
, :unless
keywords expect sexp return boolean or just boolean value
and wrap converted sexp specified function.
If specified multiple those keywords, evaluate sexp in and
.
(cort-deftest-with-macroexpand leaf/if
'(((leaf leaf
:if leafp
:require t
:config (leaf-init))
(prog1 'leaf
(if leafp
(progn
(require 'leaf)
(leaf-init)))))
((leaf leaf
:if leafp leaf-avairablep (window-system)
:require t
:config (leaf-init))
(prog1 'leaf
(if (and leafp leaf-avairablep (window-system))
(progn
(require 'leaf)
(leaf-init)))))
((leaf leaf
:if leafp leaf-avairablep (window-system)
:when leaf-browserp
:require t
:config (leaf-init))
(prog1 'leaf
(when leaf-browserp
(if (and leafp leaf-avairablep (window-system))
(progn
(require 'leaf)
(leaf-init))))))
((leaf leaf
:if leafp leaf-avairablep (window-system)
:when leaf-browserp
:load-path "~/.emacs.d/elpa-archive/leaf.el/"
:preface (leaf-load)
:require t
:config (leaf-init))
(prog1 'leaf
(add-to-list 'load-path "~/.emacs.d/elpa-archive/leaf.el/")
(leaf-load)
(when leaf-browserp
(if (and leafp leaf-avairablep (window-system))
(progn
(require 'leaf)
(leaf-init))))))))
(cort-deftest-with-macroexpand leaf/when
'(((leaf leaf
:when leafp
:require t
:config (leaf-init))
(prog1 'leaf
(when leafp
(require 'leaf)
(leaf-init))))
((leaf leaf
:when leafp leaf-avairablep (window-system)
:require t
:config (leaf-init))
(prog1 'leaf
(when (and leafp leaf-avairablep (window-system))
(require 'leaf)
(leaf-init))))))
(cort-deftest-with-macroexpand leaf/unless
'(((leaf leaf
:unless leafp
:require t
:config (leaf-init))
(prog1 'leaf
(unless leafp
(require 'leaf)
(leaf-init))))
((leaf leaf
:unless leafp leaf-avairablep (window-system)
:require t
:config (leaf-init))
(prog1 'leaf
(unless (and leafp leaf-avairablep (window-system))
(require 'leaf)
(leaf-init))))))
To suppress byte compilation warnings, you must make the appropriate declarations in Elisp to tell Emacs that you are making the appropriate calls.
This is usually done by a declare-function
and an empty defvar
, and leaf
provides a frontend of it.
(cort-deftest-with-macroexpand leaf/defun
'(((leaf leaf
:defun leaf leaf-normalize-plist leaf-merge-dupkey-values-plist)
(prog1 'leaf
(declare-function leaf "leaf")
(declare-function leaf-normalize-plist "leaf")
(declare-function leaf-merge-dupkey-values-plist "leaf")))
((leaf leaf
:defun (leaf leaf-normalize-plist leaf-merge-dupkey-values-plist))
(prog1 'leaf
(declare-function leaf "leaf")
(declare-function leaf-normalize-plist "leaf")
(declare-function leaf-merge-dupkey-values-plist "leaf")))
((leaf leaf
:defun ((lbrowser-open lbrowser-close) . leaf-browser))
(prog1 'leaf
(declare-function lbrowser-open "leaf-browser")
(declare-function lbrowser-close "leaf-browser")))))
(cort-deftest-with-macroexpand leaf/defvar
'(((leaf leaf
:defvar leaf leaf-normalize-plist leaf-merge-dupkey-values-plist)
(prog1 'leaf
(defvar leaf)
(defvar leaf-normalize-plist)
(defvar leaf-merge-dupkey-values-plist)))
((leaf leaf
:defvar (leaf leaf-normalize-plist leaf-merge-dupkey-values-plist))
(prog1 'leaf
(defvar leaf)
(defvar leaf-normalize-plist)
(defvar leaf-merge-dupkey-values-plist)))
((leaf leaf
:defvar (leaf
(leaf-normalize-plist
(leaf-merge-dupkey-values-plist))))
(prog1 'leaf
(defvar leaf)
(defvar leaf-normalize-plist)
(defvar leaf-merge-dupkey-values-plist)))))
The leaf can describe the document systematically.
It should be possible to develop additional packages that use the value specified for the document keyword, which is not currently used.
The arguments specified for this keyword have no effect on the result of the conversion.
(cort-deftest-with-macroexpand leaf/doc
'(((leaf leaf
:doc "Symplify init.el configuration"
:config (leaf-init))
(prog1 'leaf
(leaf-init)))
((leaf leaf
:file "~/.emacs.d/elpa/leaf.el/leaf.el"
:config (leaf-init))
(prog1 'leaf
(leaf-init)))
((leaf leaf
:url "https://github.com/conao3/leaf.el"
:config (leaf-init))
(prog1 'leaf
(leaf-init)))
((leaf leaf
:doc "Symplify init.el configuration"
:file "~/.emacs.d/elpa/leaf.el/leaf.el"
:url "https://github.com/conao3/leaf.el"
:config (leaf-init))
(prog1 'leaf
(leaf-init)))
((leaf leaf
:doc "Symplify init.el configuration"
"
(leaf leaf
:doc \"Symplify init.el configuration\"
:config (leaf-init))
=> (progn
(leaf-init))"
"
(leaf leaf
:disabled nil
:config (leaf-init))
=> (progn
(leaf-init))"
:file "~/.emacs.d/elpa/leaf.el/leaf.el"
:url "https://github.com/conao3/leaf.el"
:config (leaf-init))
(prog1 'leaf
(leaf-init)))))
System keywords enabled by defalts on all leaf-block.
If you disable temporary, pass these keyword to nil
,
or add nil
to leaf-defaults
to disable all leaf-block
or set leaf-expand-leaf-protect
to nil.
If the leaf fails at the top of the configuration file, most of the configuration file will not be read.
Therefore, it simply reports an error and expands the error-handling block that moves execution to the next leaf-block.
(cort-deftest-with-macroexpand-let leaf/leaf-protect
((leaf-expand-leaf-protect t))
'(((leaf leaf
:config (leaf-init))
(prog1 'leaf
(leaf-handler-leaf-protect leaf
(leaf-init))))
((leaf leaf
:leaf-protect nil
:config (leaf-init))
(prog1 'leaf
(leaf-init)))
((leaf leaf
:leaf-protect t nil
:config (leaf-init))
(prog1 'leaf
(leaf-handler-leaf-protect leaf
(leaf-init))))
((leaf-handler-leaf-protect leaf
(leaf-load)
(leaf-init))
(condition-case err
(progn
(leaf-load)
(leaf-init))
(error
(leaf-error "Error in `leaf' block. Error msg: %s"
(error-message-string err)))))))
leaf-blocks with :bind
or :mode
can often delay loading or configuration evaluation.
The keywords that enable this feature are defined below and expand as follows
(defcustom leaf-defer-keywords (cdr '(:dummy
:bind :bind*
:mode :interpreter :magic :magic-fallback
:hook :commands))
"Specifies a keyword to perform a deferred load.
`leaf' blocks are lazily loaded by their package name
with values for these keywords."
:type 'sexp
:group 'leaf)
(cort-deftest-with-macroexpand leaf/leaf-defer
'(((leaf leaf
:commands leaf
:config (leaf-init))
(prog1 'leaf
(autoload #'leaf "leaf" nil t)
(eval-after-load 'leaf
'(progn
(leaf-init)))))
((leaf leaf
:leaf-defer nil
:commands leaf
:config (leaf-init))
(prog1 'leaf
(autoload #'leaf "leaf" nil t)
(leaf-init)))))
For keywords that set functions, leaf
can auto-expand the autoload expression
enable lazy loading without relying on magic comments, ;;;Autoload
.
In some cases, you may want to disable this auto-expansion. (I can’t think of that case, but it’s provided as a function.)
(cort-deftest-with-macroexpand leaf/leaf-autoload
'(((leaf leaf
:commands leaf
:config (leaf-init))
(prog1 'leaf
(autoload #'leaf "leaf" nil t)
(eval-after-load 'leaf
'(progn
(leaf-init)))))
((leaf leaf
:leaf-autoload nil
:commands leaf
:config (leaf-init))
(prog1 'leaf
(eval-after-load 'leaf
'(progn
(leaf-init)))))))
I love OSS and I am dreaming of working on it as full-time job.
With your support, I will be able to spend more time at OSS!
All feedback and suggestions are welcome!
You can use github issues, but you can also use Slack if you want a more casual conversation.
We welcome PR! But It is need sign to FSF.
Travis Cl test leaf-test.el
with all Emacs version 23 or above.
I think that it is difficult to prepare the environment locally, so I think that it is good to throw PR and test Travis for the time being! Feel free throw PR!
leaf.el
creates the intended elisp code from DSL with a simple mechanism.
It is clear what internal conversion is done and it is also easy to customize it.
- Append
leaf-defaults
andleaf-system-defaults
toleaf
arguments. - Because
leaf
receives arguments too many format, normalize as plist.- Normalize plist by
leaf-normalize-plist
. - Sort plist by
leaf-keyword
.(:bind ("M-s O" . moccur) (:isearch-mode-map :package isearch ("M-o" . isearch-moccur) ("M-O" . isearch-moccur-all))) ;; => (:leaf-protect (t) ;; :leaf-autoload (t) ;; :bind (("M-s O" . moccur) ;; (:isearch-mode-map ;; :package isearch ;; ("M-o" . isearch-moccur) ;; ("M-O" . isearch-moccur-all))) ;; :leaf-defer (t))
- Normalize plist by
- Run normalizer, and process keyword using below variables
Variable Name Description leaf–raw The all leaf arguments leaf–name The name of leaf-block leaf–key The :keyword of current processing leaf–keyname The :keyword name as string of current processing leaf–value The arguments which is current processed leaf–body The result of the following keywords and arguments leaf–rest The following keywords and arguments leaf–autoload The list of pair (fn . pkg)
- Apply the normalized values to the keyword specific normalizer.
The definition is
leaf-normalize
, overwriteleaf--value
. - Run conversion process keyword.
The conversion definition is
leaf-keywords
, overriddenleaf--body
- Wrap finaly
leaf--body
withprog1
.
leaf
normalize argument with leaf-normalize
, and conversion with leaf-keywords
.
So, pushing new element these variable, leaf
can recognize new keywords.
In leaf-keywords.el, you can see practical example, and you can PR it.
Note that leaf only contains keywords for packages that come with the Emacs standard, and that keywords that depend on external packages are added to its repository.
Bundling Emacs-22.1 on macOS 10.13 (High Sierra), we support this.
The Emacs-22 docker image is not available and is not currently being tested. So We don’t know if it works or not.
If we can get the Emacs-22 docker image, we will resume support for Emacs-22.
Now, leaf
support Emacs-23 or above.
To make leaf
dependent only on packages that are itself and packages attached to and Emacs,
we have removed the back-end selection for bind-key
and leaf-key
for :bind
and the back-end selection for package.el
, feathre.el
, and el-get
for :ensure
.
You should now use the external package specific keywords, such as
:bind-key
and :el-get
, :feather
, defined in leaf-keywords.el.
Therefore, the keyword :ensure
has been changed to :package
.
This has no effect because we have defined alias.
In order to realize the philosophy of “Leaf of setting”,
we changed it so as not to require
by default.
If you want to request explicitly use the :require t
flag.
;; behavior of leaf v2.0
(leaf foo)
=> (progn)
(leaf foo :require t)
=> (progn
(require 'foo))
;; behavior of leaf v1.0
(leaf foo)
=> (progn
(require 'foo))
(leaf foo :require t)
=> (progn
(require 'foo))
Affero General Public License Version 3 (AGPLv3) Copyright (c) Naoya Yamashita - https://conao3.com https://github.com/conao3/leaf.el/blob/master/LICENSE
- Naoya Yamashita (conao3)
Advice and comments given by Emacs-JP’s forum member has been a great help in developing leaf.el
.
Thank you very much!!