;;; acdw.el -- bits and bobs -*- lexical-binding: t; -*- ;; by C. Duckworth (provide 'acdw) (require 'cl-lib) ;;; Define both a directory and a function expanding to a file in that directory (defmacro +define-dir (name directory &optional docstring inhibit-mkdir) "Define a variable and function NAME expanding to DIRECTORY. DOCSTRING is applied to the variable. Ensure DIRECTORY exists in the filesystem, unless INHIBIT-MKDIR is non-nil." (declare (indent 2) (doc-string 3)) (unless inhibit-mkdir (make-directory (eval directory) :parents)) `(progn (defvar ,name ,directory ,(concat docstring (when docstring "\n") "Defined by `/define-dir'.")) (defun ,name (file &optional mkdir) ,(concat "Expand FILE relative to variable `" (symbol-name name) "'.\n" "If MKDIR is non-nil, the directory is created.\n" "Defined by `/define-dir'.") (let ((file-name (expand-file-name (convert-standard-filename file) ,name))) (when mkdir (make-directory (file-name-directory file-name) :parents)) file-name)))) ;;; Convenience macros (defun eval-after-init (fn) "Evaluate FN after inititation, or now if Emacs is initialized. FN is called with no arguments." (if after-init-time (funcall fn) (add-hook 'after-init-hook fn))) (defmacro eval-after (features &rest body) "Evaluate BODY, but only after loading FEATURES. FEATURES can be an atom or a list; as an atom it works like `with-eval-after-load'. The special feature `init' will evaluate BODY after Emacs is finished initializing." (declare (indent 1) (debug (form def-body))) (if (eq features 'init) `(eval-after-init (lambda () ,@body)) (unless (listp features) (setq features (list features))) (if (null features) (macroexp-progn body) (let* ((this (car features)) (rest (cdr features))) `(with-eval-after-load ',this (eval-after ,rest ,@body)))))) ;;; Convenience functions (defun define-keys (maps &rest keydefs) "Define KEYDEFS in MAPS. Convenience wrapper around `define-key'." (unless (zerop (mod (length keydefs) 2)) (user-error "Wrong number of arguments: %S" (length keydefs))) (dolist (map (if (or (atom maps) (eq (car maps) 'keymap)) (list maps) maps)) (cl-loop for (key def) on keydefs by #'cddr do (let ((key (if (stringp key) (kbd key) key))) (define-key (if (symbolp map) (symbol-value map) map) key def))))) (defmacro setq-local-hook (hook &rest args) "Run `setq-local' on ARGS when running HOOK." (declare (indent 1)) (let ((fn (intern (format "%s-setq-local" hook)))) (when (and (fboundp fn) (functionp fn)) (setq args (append (function-get fn 'setq-local-hook-settings) args))) (unless (and (< 0 (length args)) (zerop (mod (length args) 2))) (user-error "Wrong number of arguments: %S" (length args))) `(progn (defun ,fn () ,(format "Set local variables after `%s'." hook) (setq-local ,@args)) (function-put ',fn 'setq-local-hook-settings ',args) (add-hook ',hook #',fn)))) (unless (fboundp 'ensure-list) ;; Just in case we're using an old version of Emacs. (defun ensure-list (object) "Return OBJECT as a list. If OBJECT is already a list, return OBJECT itself. If it's not a list, return a one-element list containing OBJECT." (if (listp object) object (list object)))) (defun add-to-list* (lists &rest things) "Add THINGS to LISTS. LISTS can be one list variable or a list. Each thing of THINGS can be either a variablel (the thing), or a list of the form (ELEMENT &optional APPEND COMPARE-FN), which is passed to `add-to-list'." (dolist (l (ensure-list lists)) (dolist (thing things) (apply #'add-to-list l (ensure-list thing))))) (defun add-hook* (hooks &rest functions) "Add FUNCTIONS to HOOKS. Each function in FUNCTIONS can be a singleton or a list of the form (FUNCTION &optional DEPTH LOCAL)." (dolist (hook (ensure-list hooks)) (dolist (fn functions) (apply #'add-hook hook (ensure-list fn)))))