;;; acdw.el --- My Emacs extras -*- lexical-binding: t; -*- ;;; Code: (defmacro defdir (name directory &optional docstring makedir) "Define a variable and a function NAME expanding to DIRECTORY. DOCSTRING is applied to the variable; its default is DIRECTORY's path. If MAKEDIR is non-nil, the directory and its parents will be created." (declare (indent 2) (doc-string 3)) `(progn (defvar ,name (expand-file-name ,directory) ,(concat (or docstring (format "%s" directory)) "\n" "Defined by `defdir'.")) (defun ,name (file &optional mkdir) ,(concat "Expand FILE relative to variable `" (symbol-name name) "'.\n" "If MKDIR is non-nil, parent directories are created.\n" "Defined by `defdir'.") (let ((file-name (expand-file-name (convert-standard-filename file) ,name))) (when mkdir (make-directory (file-name-directory file-name) :parents)) file-name)) ,(if makedir `(make-directory ,directory :parents) `(unless (file-exists-p ,directory) (warn "Directory `%s' doesn't exist." ,directory))))) (defun choose-executable (&rest programs) "Return the first of PROGRAMS that exists in the system's $PATH. Each of PROGRAMS can be a single string, or a list. If it's a list then its car will be tested with `executable-find', and the entire list returned. This enables passing arguments to a calling function." (seq-find (lambda (x) (executable-find (car (ensure-list x)))) programs)) (defun file-string (file) "Return the contents of FILE as a string." (with-current-buffer (find-file-noselect file) (buffer-string))) (defun unsmartify-region (begin end) "Replace \"smart\" punctuation with \"dumb\" counterparts." (interactive "*r") (save-excursion (goto-char begin) (while (re-search-forward "[“”‘’–—]" end t) (let ((replace (pcase (match-string 0) ((or "“" "”") "\"") ((or "‘" "’") "'") ("–" "--") ("—" "---")))) (replace-match replace nil nil))))) (defun ++concat (func strings) "Concat STRINGS processed by FUNC. Each of STRINGS can be a bare string or a list. Strings are passed through as-is, but lists are passed to FUNC first as arguments. Finally, all the resulting strings are `mapconcat'-ed together. As a special case, if `:separator' is the first of STRINGS, the string following will be used as a separator. Otherwise, a newline will be used." (let (separator) (when (eq (car strings) :separator) (setq separator (cadr strings) strings (cddr strings))) (mapconcat (lambda (s) (cond ((listp s) (apply func s)) ((stringp s) s) (t (user-error "Bad argument: %S" s)))) strings (or separator "\n")))) (defun format-concat (&rest strings) "Concatenate formatted STRINGS. Each of STRINGS can be a bare string or a list. Bare strings are passed as-is to `mapconcat' for concatenation and separation. Lists, however, are passed to `format' first. If `:separator' is the first of STRINGS, the next string will be used as a separator." (++concat #'format strings)) (provide 'acdw) ;;; acdw.el ends here