branch: scratch/hook-helpers commit 93ae501db07671d7ae08b1b7b4700212303549b7 Author: Ian Dunn <du...@gnu.org> Commit: Ian Dunn <du...@gnu.org>
Finished last commit. --- README.org | 95 +++++++++++++++++++++++++++------------------------ hook-helpers-tests.el | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 44 deletions(-) diff --git a/README.org b/README.org index 9da1813..2af2a8e 100644 --- a/README.org +++ b/README.org @@ -3,7 +3,7 @@ #+EMAIL: du...@gnu.org * Copying -Copyright (C) 2016 Ian Dunn +Copyright (C) 2016-2017 Ian Dunn #+BEGIN_QUOTE This program is free software: you can redistribute it and/or modify @@ -28,13 +28,13 @@ they don’t do this, then it will be an anonymous function. If the anonymous function is modified, then the function can’t be removed. With a function outside of the ~add-hook~ call, it looks messy. -The ~define-hook-helper~ macro is a solution to this. Think of it as an -anaphoric ~add-hook~, but one that can be called many times without risking -redundant hook functions. It gives a cleaner look and feel to Emacs -configuration files, and could even be used in actual libraries. +Hook Helpers are a solution to this. A "hook helper" is an anonymous, +modifiable function created for the sole purpose of being attached to a hook. +This combines the two commonly used methods mentioned above. The functions +don't exist, so they don't get in the way of ~C-h f~, but they can be removed or +modified as needed. -The purpose of this package is to build upon add-hook and remove-hook. When you -have something like the following: +Let's look at an example: #+BEGIN_SRC emacs-lisp (defun my/after-init-hook () @@ -64,57 +64,64 @@ Which handles everything for you. * Usage -Under the hood, ~define-hook-helper~ creates a new function, called -~hook-helper--HOOK~. +There are two macros in hook helpers: ~define-hook-helper~ and ~create-hook-helper~. + +The former is a basic case; it creates and adds a helper for a single hook. +Most hooks have the -hook suffix, so we take advantage of that here for a little +less typing. In order to add a helper to a non-standard hook, use the ~:suffix~ +argument: #+BEGIN_SRC emacs-lisp -(define-hook-helper after-init () - (set-scroll-bar-mode nil)) +(define-hook-helper my () + :suffix function + :append t + (message "Hello!")) #+END_SRC -The above creates the function ‘hook-helper--after-init’. +We also introduce the ~:append~ keyword above, which does exactly what it sounds +like. -** "But doesn't that mean I can only use this once per hook?" +There's one more keyword for ~define-hook-helper~: ~:name~. This specifies an +additional name for the new helper. Without this, its helper ID is just the +name of the hook; with a ~:name~, its ID is HOOK-NAME/NAME. -I thought about that too, and devised a solution. ~define-hook-helper~ accepts -several keywords: name, suffix, and append. Append does exactly what it says on -the tin: It appends the hook, passing the argument straight to ~add-hook~. - -Name tacks a name onto the defined hook function. For instance, take the -previous example: +The work horse of hook helpers is ~create-hook-helper~. This is the generic case, +capable of adding itself to any number of hooks: #+BEGIN_SRC emacs-lisp -(define-hook-helper after-init () - :name env - (set-scroll-bar-mode nil)) +(create-hook-helper new-helper () + :hooks (hook-1-hook + (hook-2-hook . t) + hook-3-function) + (message "Look at all that we can do!")) #+END_SRC -This creates the function =hook-helper--after-init/env=, thus allowing -anyone to use ~define-hook-helper~ as many times as they like, without fear -of name clobbering. +This creates a new hook helper called "new-helper", and adds it to ~hook-1-hook~, +~hook-2-hook~, and ~hook-3-function~, appending to the latter. -Arguments to the new function may also be specified. Let's say you've got a -hook like so: +The ~:hooks~ keyword can have the following form: -#+BEGIN_SRC emacs-lisp -(define-hook-helper after-make-frame (frame) - :suffix "functions" - (set-frame-parameter frame 'alpha '(90 50))) -#+END_SRC +- A single hook +- A cons cell (HOOK . APPEND) +- A list containing a mixture of the above two forms -Also note the ~suffix~ keyword. This tells ~define-hook-helper~ to add the -helper to the variable ~after-make-frame-functions~ instead of -~after-make-frame-hook~. The arguments tells it to create a function with -one argument, ~frame~. +This is called a "hook spec". -** Removing the Function -To remove the new function from the hook, you can use the function -~remove-hook-helper~. It works just as ~define-hook-helper~: +** Adding and Removing Helpers +To add or remove helpers, the functions add-hook-helper and remove-hook-helper +are provided. #+BEGIN_SRC emacs-lisp -(remove-hook-helper text-mode) -(remove-hook-helper after-init - :name env) -(remove-hook-helper after-make-frame - :suffix "functions") +(add-hook-helper 'test-helper '(hook-the-first hook-the-second)) +(remove-hook-helper 'test-helper 'hook-the-second) #+END_SRC + +As you can see, each of them takes the same arguments: a symbol denoting the +helper to add or remove, and a quoted hook spec. + +** Seeing all the Available Helpers + +Seeing lambda functions in your hooks can be confusing. While we don't have a +solution for that, we do have ~describe-hook-helpers~, an interactive function +that creates a pretty buffer containing all the defined hook helpers, grouped by +the hooks to which they are attached. diff --git a/hook-helpers-tests.el b/hook-helpers-tests.el new file mode 100644 index 0000000..9d732fc --- /dev/null +++ b/hook-helpers-tests.el @@ -0,0 +1,92 @@ +;;; hook-helpers-tests.el --- Tests for hook helpers + +;; Copyright (C) 2016,2017 Ian Dunn + +;; Author: Ian Dunn <du...@gnu.org> +;; Keywords: development, hooks +;; URL: https://savannah.nongnu.org/projects/hook-helpers-el/ +;; Version: 1.0 + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: + +(require 'hook-helpers) +(require 'ert) + +(ert-deftest hkhlp-normalize-hook-spec-test () + (should (equal (hkhlp-normalize-hook-spec 'c++-mode-hook) + '((c++-mode-hook)))) + (should (equal (hkhlp-normalize-hook-spec '(c++-mode-hook . t)) + '((c++-mode-hook . t)))) + (should (equal (hkhlp-normalize-hook-spec '(c++-mode-hook org-mode-hook)) + '((c++-mode-hook) (org-mode-hook)))) + (should (equal (hkhlp-normalize-hook-spec '((c++-mode-hook . t) org-mode-hook)) + '((c++-mode-hook . t) (org-mode-hook)))) + (should (equal (hkhlp-normalize-hook-spec '(c++-mode-hook (org-mode-hook . t))) + '((c++-mode-hook) (org-mode-hook . t)))) + (should (equal (hkhlp-normalize-hook-spec '((c++-mode-hook . t) + (org-mode-hook . t))) + '((c++-mode-hook . t) (org-mode-hook . t))))) + +(ert-deftest hkhlp-create-hook-helper-test () + (let ((test-hook nil)) + (create-hook-helper test-helper () + "Test Hook Helper" + :hooks (test-hook) + (message "This is a test")) + (should (equal test-hook (list + (lambda () "Test Hook Helper" (message "This is a test"))))))) + +(ert-deftest hkhlp-add-hook-helper () + (let ((test-hook nil) + (test-2-hook nil)) + (create-hook-helper test-helper () + "Test Hook Helper" + :hooks (test-hook) + (message "This is a test")) + (should (equal test-hook (list + (lambda () "Test Hook Helper" (message "This is a test"))))) + (add-hook-helper 'test-helper '(test-2-hook)) + (should (equal test-2-hook test-hook)))) + +(ert-deftest hkhlp-remove-hook-helper () + (let ((test-hook nil)) + (create-hook-helper test-helper () + "Test Hook Helper" + :hooks (test-hook) + (message "This is a test")) + (should (equal test-hook (list + (lambda () "Test Hook Helper" (message "This is a test"))))) + (remove-hook-helper 'test-helper 'test-hook) + (should (equal test-hook nil)))) + +(ert-deftest hkhlp-update-hook-helper () + (let ((test-hook nil) + (old-func (lambda () "Test Hook Helper" (message "This is a test"))) + (new-func (lambda () "Test Hook Helper" (message "This is another test")))) + (create-hook-helper test-helper () + "Test Hook Helper" + :hooks (test-hook) + (message "This is a test")) + (should (equal test-hook (list old-func))) + (create-hook-helper test-helper () + "Test Hook Helper" + :hooks (test-hook) + (message "This is another test")) + (should (equal test-hook (list new-func))))) + +;;; hook-helpers-tests.el ends here