A.2 How to create an extension keyword

This section describes how to create a new keyword.

  1. Add the keyword.

    The first step is to add your keyword at the right place in use-package-keywords. This list determines the order in which things will happen in the expanded code. You should never change this order, but it gives you a framework within which to decide when your keyword should fire.

  2. Create a normalizer.

    The job of the normalizer is take a list of arguments (possibly nil), and turn it into the single argument (which could still be a list) that should appear in the final property list used by use-package.

    Define a normalizer for your keyword by defining a function named after the keyword, for example:

    (defun use-package-normalize/:pin (name-symbol keyword args)
      (use-package-only-one (symbol-name keyword) args
        (lambda (label arg)
          (cond
           ((stringp arg) arg)
           ((symbolp arg) (symbol-name arg))
           (t
            (use-package-error
             ":pin wants an archive name (a string)"))))))
    
  3. Create a handler.

    Once you have a normalizer, you must create a handler for the keyword.

    Handlers can affect the handling of keywords in two ways. First, they can modify the state plist before recursively processing the remaining keywords, to influence keywords that pay attention to the state (one example is the state keyword :deferred, not to be confused with the use-package keyword :defer). Then, once the remaining keywords have been handled and their resulting forms returned, the handlers may manipulate, extend, or just ignore those forms.

    The task of each handler is to return a list of forms representing code to be inserted. It does not need to be a progn list, as this is handled automatically in other places. Thus it is common to see the idiom of using use-package-concat to add new functionality before or after a code body, so that only the minimum code necessary is emitted as the result of a use-package expansion.

    This is an example handler:

    (defun use-package-handler/:pin (name-symbol keyword archive-name rest state)
      (let ((body (use-package-process-keywords name-symbol rest state)))
        ;; This happens at macro expansion time, not when the expanded code is
        ;; compiled or evaluated.
        (if (null archive-name)
            body
          (use-package-pin-package name-symbol archive-name)
          (use-package-concat
           body
           `((push '(,name-symbol . ,archive-name)
                   package-pinned-packages))))))
    
  4. Test it.

    After the keyword has been inserted into use-package-keywords, and a normalizer and a handler has been defined, you can now test the keyword by seeing how usages of the keyword will expand. For this, use M-x pp-macroexpand-last-sexp with the cursor set immediately after the (use-package …) expression.