25

GitHub - conao3/leaf.el: Support your setting init.el like use-package

 4 years ago
source link: https://github.com/conao3/leaf.el
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

README.org

https://raw.githubusercontent.com/conao3/files/master/blob/headers/png/leaf.el.png https://img.shields.io/github/license/conao3/leaf.el.svg?style=flat-square https://img.shields.io/github/tag/conao3/leaf.el.svg?style=flat-square https://img.shields.io/travis/conao3/leaf.el/master.svg?style=flat-square https://img.shields.io/codacy/grade/1a6befcbdfdb4b1fb37a6f7f040a75b2.svg?logo=codacy&style=flat-square https://img.shields.io/badge/patreon-@conao3-orange.svg?logo=patreon&style=flat-square https://img.shields.io/badge/twitter-@conao__3-blue.svg?logo=twitter&style=flat-square https://img.shields.io/badge/chat-on_slack-blue.svg?logo=slack&style=flat-square

Table of Contents

Description

leaf.el is yet another use-package.

leaf solves the stress of using use-package for 2 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-22, 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-22. 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.)

Install

Put leaf.el at any folder added load-path. Then (require 'leaf) and use like use-pacakge.

In order to work from Emacs-22, the package manager and the key binding manager that accompanies leaf must also be developed with the assumption that they will work from Emacs-22.

I have plans to develop it, but it’s not finished yet. It is now necessary to use these packages to avoid trouble.

Package to be developed

;; add to load-path
;; (locate-user-emacs-file "site-lisp/leaf.el")
;;  => "~/.emacs.d/local/26.1/site-lisp/leaf.el"

(prog1 "Load leaf.el"
  (add-to-list 'load-path (locate-user-emacs-file "site-lisp/leaf.el"))
  (require 'leaf)
  (leaf leaf
    :doc "Symplify your init.el configuration"
    :doc "Initialize leaf dependent packages"
    :config
    (leaf package
      :custom ((package-archives . '(("org"   . "https://orgmode.org/elpa/")
                                     ("melpa" . "https://melpa.org/packages/")
                                     ("gnu"   . "https://elpa.gnu.org/packages/"))))
      :config
      (package-initialize))))

Usage

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.

Customize

  • leaf-defaults: Default arguments for all leaf-block.
  • leaf-expand-{{keyword}}: If nil, not to expand that keyword.

Syntaxes

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)

    ...))

Basic keywords

none (keyword)

Unlike use-package, leaf will convert to nil when used without any keywords.

(cort-deftest-with-macroexpand leaf/none
  '(((leaf leaf)
     (prog1 'leaf))))

:require keyword

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, :ensure keywords

: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)))))

(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)))))))))))

:preface, :init, :config keywords

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

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

: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, :bind* keywords

: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, :advice-remove keywords

: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)))))

Configure variables keywords

:custom, :custom-face keywords

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)))))))))

:pre-setq, :setq, :setq-default keywords

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)))))

Configure list keywords

:mode, :interpreter keywords

: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))))))

(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))))))

:magic, :magic-fallback keywords

: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))))))

(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))))))

:hook keyword

: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)))))

:load-path keyword

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)))))

Condition keywords

:disabled keyword

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

: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))))))

Byte compile keywords

:defun, :defvar keywords

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")))))

(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)))))

Documentation keywords

:doc, :file, :url keywords

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

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.

:leaf-protect keyword

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-defer keyword

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)))))

:leaf-autoload keyword

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)))))))

Information

Donation

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!

https://c5.patreon.com/external/logo/become_a_patron_button.png

Community

All feedback and suggestions are welcome!

You can use github issues, but you can also use Slack if you want a more casual conversation.

Contribution

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.

leaf.el mechanism

  1. Append leaf-defaults and leaf-system-defaults to leaf arguments.
  2. Because leaf receives arguments too many format, normalize as plist.
    1. Normalize plist by leaf-normalize-plist.
    2. 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))
              
  3. Run normalizer, and process keyword using below variables Variable NameDescriptionleaf–rawThe all leaf argumentsleaf–nameThe name of leaf-blockleaf–keyThe :keyword of current processingleaf–keynameThe :keyword name as string of current processingleaf–valueThe arguments which is current processedleaf–bodyThe result of the following keywords and argumentsleaf–restThe following keywords and argumentsleaf–autoloadThe list of pair (fn . pkg)
  4. Apply the normalized values to the keyword specific normalizer.

    The definition is leaf-normalize, overwrite leaf--value.

  5. Run conversion process keyword.

    The conversion definition is leaf-keywords, overridden leaf--body

  6. Wrap finaly leaf--body with prog1.

Adding new keywords

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.

Why We support Emacs-22?

Bundling Emacs-22.1 on macOS 10.13 (High Sierra), we support this.

Migration

leaf v2.0 to v3.0

Drop bind-key.el support for :bind and faether.el support for :ensure

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.

leaf v1.0 to v2.0

Change not to require by default

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))

License

Affero General Public License Version 3 (AGPLv3)
Copyright (c) Naoya Yamashita - https://conao3.com
https://github.com/conao3/leaf.el/blob/master/LICENSE

Author

Contributors

Special Thanks

Advice and comments given by Emacs-JP’s forum member has been a great help in developing leaf.el.

Thank you very much!!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK