aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/compat-28.1.1.0/compat-macs.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/compat-28.1.1.0/compat-macs.el')
-rw-r--r--elpa/compat-28.1.1.0/compat-macs.el367
1 files changed, 367 insertions, 0 deletions
diff --git a/elpa/compat-28.1.1.0/compat-macs.el b/elpa/compat-28.1.1.0/compat-macs.el
new file mode 100644
index 0000000..e1dcf81
--- /dev/null
+++ b/elpa/compat-28.1.1.0/compat-macs.el
@@ -0,0 +1,367 @@
+;;; compat-macs.el --- Compatibility Macros -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021, 2022 Free Software Foundation, Inc.
+
+;; Author: Philip Kaludercic <philipk@posteo.net>
+;; Keywords: lisp
+
+;; 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 <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; These macros are used to define compatibility functions, macros and
+;; advice.
+
+;;; Code:
+
+(defmacro compat--ignore (&rest _)
+ "Ignore all arguments."
+ nil)
+
+(defvar compat--generate-function #'compat--generate-minimal
+ "Function used to generate compatibility code.
+The function must take six arguments: NAME, DEF-FN, INSTALL-FN,
+CHECK-FN, ATTR and TYPE. The resulting body is constructed by
+invoking the functions DEF-FN (passed the \"realname\" and the
+version number, returning the compatibility definition), the
+INSTALL-FN (passed the \"realname\" and returning the
+installation code), CHECK-FN (passed the \"realname\" and
+returning a check to see if the compatibility definition should
+be installed). ATTR is a plist used to modify the generated
+code. The following attributes are handled, all others are
+ignored:
+
+- :min-version :: Prevent the compatibility definition from begin
+ installed in versions older than indicated (string).
+
+- :max-version :: Prevent the compatibility definition from begin
+ installed in versions newer than indicated (string).
+
+- :feature :: The library the code is supposed to be loaded
+ with (via `eval-after-load').
+
+- :cond :: Only install the compatibility code, iff the value
+ evaluates to non-nil.
+
+ For prefixed functions, this can be interpreted as a test to
+ `defalias' an existing definition or not.
+
+- :no-highlight :: Do not highlight this definition as
+ compatibility function.
+
+- :version :: Manual specification of the version the compatee
+ code was defined in (string).
+
+- :realname :: Manual specification of a \"realname\" to use for
+ the compatibility definition (symbol).
+
+- :notes :: Additional notes that a developer using this
+ compatibility function should keep in mind.
+
+- :prefix :: Add a `compat-' prefix to the name, and define the
+ compatibility code unconditionally.
+
+TYPE is used to set the symbol property `compat-type' for NAME.")
+
+(defun compat--generate-minimal (name def-fn install-fn check-fn attr type)
+ "Generate a leaner compatibility definition.
+See `compat-generate-function' for details on the arguments NAME,
+DEF-FN, INSTALL-FN, CHECK-FN, ATTR and TYPE."
+ (let* ((min-version (plist-get attr :min-version))
+ (max-version (plist-get attr :max-version))
+ (feature (plist-get attr :feature))
+ (cond (plist-get attr :cond))
+ (version (or (plist-get attr :version)
+ (let ((file (or (bound-and-true-p byte-compile-current-file)
+ load-file-name
+ (buffer-file-name))))
+ ;; Guess the version from the file the macro is
+ ;; being defined in.
+ (cond
+ ((not file) emacs-version)
+ ((string-match
+ "compat-\\([[:digit:]]+\\)\\.\\(?:elc?\\)\\'"
+ file)
+ (match-string 1 file))
+ ((error "No version number could be extracted"))))))
+ (realname (or (plist-get attr :realname)
+ (intern (format "compat--%S" name))))
+ (check (cond
+ ((or (and min-version
+ (version< emacs-version min-version))
+ (and max-version
+ (version< max-version emacs-version)))
+ '(compat--ignore))
+ ((plist-get attr :prefix)
+ '(progn))
+ ((and version (version<= version emacs-version) (not cond))
+ '(compat--ignore))
+ (`(when (and ,(if cond cond t)
+ ,(funcall check-fn)))))))
+ (cond
+ ((and (plist-get attr :prefix) (memq type '(func macro))
+ (string-match "\\`compat-\\(.+\\)\\'" (symbol-name name))
+ (let* ((actual-name (intern (match-string 1 (symbol-name name))))
+ (body (funcall install-fn actual-name version)))
+ (when (and (version<= version emacs-version)
+ (fboundp actual-name))
+ `(,@check
+ ,(if feature
+ ;; See https://nullprogram.com/blog/2018/02/22/:
+ `(eval-after-load ,feature `(funcall ',(lambda () ,body)))
+ body))))))
+ ((plist-get attr :realname)
+ `(progn
+ ,(funcall def-fn realname version)
+ (,@check
+ ,(let ((body (funcall install-fn realname version)))
+ (if feature
+ ;; See https://nullprogram.com/blog/2018/02/22/:
+ `(eval-after-load ,feature `(funcall ',(lambda () ,body)))
+ body)))))
+ ((let* ((body (if (eq type 'advice)
+ `(,@check
+ ,(funcall def-fn realname version)
+ ,(funcall install-fn realname version))
+ `(,@check ,(funcall def-fn name version)))))
+ (if feature
+ ;; See https://nullprogram.com/blog/2018/02/22/:
+ `(eval-after-load ,feature `(funcall ',(lambda () ,body)))
+ body))))))
+
+(defun compat--generate-minimal-no-prefix (name def-fn install-fn check-fn attr type)
+ "Generate a leaner compatibility definition.
+See `compat-generate-function' for details on the arguments NAME,
+DEF-FN, INSTALL-FN, CHECK-FN, ATTR and TYPE."
+ (unless (plist-get attr :prefix)
+ (compat--generate-minimal name def-fn install-fn check-fn attr type)))
+
+(defun compat--generate-verbose (name def-fn install-fn check-fn attr type)
+ "Generate a more verbose compatibility definition, fit for testing.
+See `compat-generate-function' for details on the arguments NAME,
+DEF-FN, INSTALL-FN, CHECK-FN, ATTR and TYPE."
+ (let* ((min-version (plist-get attr :min-version))
+ (max-version (plist-get attr :max-version))
+ (feature (plist-get attr :feature))
+ (cond (plist-get attr :cond))
+ (version (or (plist-get attr :version)
+ (let ((file (or (bound-and-true-p byte-compile-current-file)
+ load-file-name
+ (buffer-file-name))))
+ ;; Guess the version from the file the macro is
+ ;; being defined in.
+ (cond
+ ((not file) emacs-version)
+ ((string-match
+ "compat-\\([[:digit:]]+\\)\\.\\(?:elc?\\)\\'"
+ file)
+ (match-string 1 file))
+ ((error "No version number could be extracted"))))))
+ (realname (or (plist-get attr :realname)
+ (intern (format "compat--%S" name))))
+ (body `(progn
+ (unless (or (null (get ',name 'compat-def))
+ (eq (get ',name 'compat-def) ',realname))
+ (error "Duplicate compatibility definition: %s (was %s, now %s)"
+ ',name (get ',name 'compat-def) ',realname))
+ (put ',name 'compat-def ',realname)
+ ,(funcall install-fn realname version))))
+ `(progn
+ (put ',realname 'compat-type ',type)
+ (put ',realname 'compat-version ,version)
+ (put ',realname 'compat-min-version ,min-version)
+ (put ',realname 'compat-max-version ,max-version)
+ (put ',realname 'compat-doc ,(plist-get attr :note))
+ ,(funcall def-fn realname version)
+ (,@(cond
+ ((or (and min-version
+ (version< emacs-version min-version))
+ (and max-version
+ (version< max-version emacs-version)))
+ '(compat--ignore))
+ ((plist-get attr :prefix)
+ '(progn))
+ ((and version (version<= version emacs-version) (not cond))
+ '(compat--ignore))
+ (`(when (and ,(if cond cond t)
+ ,(funcall check-fn)))))
+ ,(if feature
+ ;; See https://nullprogram.com/blog/2018/02/22/:
+ `(eval-after-load ,feature `(funcall ',(lambda () ,body)))
+ body)))))
+
+(defun compat-generate-common (name def-fn install-fn check-fn attr type)
+ "Common code for generating compatibility definitions.
+See `compat-generate-function' for details on the arguments NAME,
+DEF-FN, INSTALL-FN, CHECK-FN, ATTR and TYPE."
+ (when (and (plist-get attr :cond) (plist-get attr :prefix))
+ (error "A prefixed function %s cannot have a condition" name))
+ (funcall compat--generate-function
+ name def-fn install-fn check-fn attr type))
+
+(defun compat-common-fdefine (type name arglist docstring rest)
+ "Generate compatibility code for a function NAME.
+TYPE is one of `func', for functions and `macro' for macros, and
+`advice' ARGLIST is passed on directly to the definition, and
+DOCSTRING is prepended with a compatibility note. REST contains
+the remaining definition, that may begin with a property list of
+attributes (see `compat-generate-common')."
+ (let ((oldname name) (body rest))
+ (while (keywordp (car body))
+ (setq body (cddr body)))
+ ;; It might be possible to set these properties otherwise. That
+ ;; should be looked into and implemented if it is the case.
+ (when (and (listp (car-safe body)) (eq (caar body) 'declare))
+ (when (version<= emacs-version "25")
+ (delq (assq 'side-effect-free (car body)) (car body))
+ (delq (assq 'pure (car body)) (car body))))
+ ;; Check if we want an explicitly prefixed function
+ (when (plist-get rest :prefix)
+ (setq name (intern (format "compat-%s" name))))
+ (compat-generate-common
+ name
+ (lambda (realname version)
+ `(,(cond
+ ((memq type '(func advice)) 'defun)
+ ((eq type 'macro) 'defmacro)
+ ((error "Unknown type")))
+ ,realname ,arglist
+ ;; Prepend compatibility notice to the actual
+ ;; documentation string.
+ ,(let ((type (cond
+ ((eq type 'func) "function")
+ ((eq type 'macro) "macro")
+ ((eq type 'advice) "advice")
+ ((error "Unknown type")))))
+ (if version
+ (format
+ "[Compatibility %s for `%S', defined in Emacs %s]\n\n%s"
+ type oldname version docstring)
+ (format
+ "[Compatibility %s for `%S']\n\n%s"
+ type oldname docstring)))
+ ;; Advice may use the implicit variable `oldfun', but
+ ;; to avoid triggering the byte compiler, we make
+ ;; sure the argument is used at least once.
+ ,@(if (eq type 'advice)
+ (cons '(ignore oldfun) body)
+ body)))
+ (lambda (realname _version)
+ (cond
+ ((memq type '(func macro))
+ ;; Functions and macros are installed by
+ ;; aliasing the name of the compatible
+ ;; function to the name of the compatibility
+ ;; function.
+ `(defalias ',name #',realname))
+ ((eq type 'advice)
+ `(advice-add ',name :around #',realname))))
+ (lambda ()
+ (cond
+ ((memq type '(func macro))
+ `(not (fboundp ',name)))
+ ((eq type 'advice) t)))
+ rest type)))
+
+(defmacro compat-defun (name arglist docstring &rest rest)
+ "Define NAME with arguments ARGLIST as a compatibility function.
+The function must be documented in DOCSTRING. REST may begin
+with a plist, that is interpreted by the macro but not passed on
+to the actual function. See `compat-generate-common' for a
+listing of attributes.
+
+The definition will only be installed, if the version this
+function was defined in, as indicated by the `:version'
+attribute, is greater than the current Emacs version."
+ (declare (debug (&define name (&rest symbolp)
+ stringp
+ [&rest keywordp sexp]
+ def-body))
+ (doc-string 3) (indent 2))
+ (compat-common-fdefine 'func name arglist docstring rest))
+
+(defmacro compat-defmacro (name arglist docstring &rest rest)
+ "Define NAME with arguments ARGLIST as a compatibility macro.
+The macro must be documented in DOCSTRING. REST may begin
+with a plist, that is interpreted by this macro but not passed on
+to the actual macro. See `compat-generate-common' for a
+listing of attributes.
+
+The definition will only be installed, if the version this
+function was defined in, as indicated by the `:version'
+attribute, is greater than the current Emacs version."
+ (declare (debug compat-defun) (doc-string 3) (indent 2))
+ (compat-common-fdefine 'macro name arglist docstring rest))
+
+(defmacro compat-advise (name arglist docstring &rest rest)
+ "Define NAME with arguments ARGLIST as a compatibility advice.
+The advice function must be documented in DOCSTRING. REST may
+begin with a plist, that is interpreted by this macro but not
+passed on to the actual advice function. See
+`compat-generate-common' for a listing of attributes. The advice
+wraps the old definition, that is accessible via using the symbol
+`oldfun'.
+
+The advice will only be installed, if the version this function
+was defined in, as indicated by the `:version' attribute, is
+greater than the current Emacs version."
+ (declare (debug compat-defun) (doc-string 3) (indent 2))
+ (compat-common-fdefine 'advice name (cons 'oldfun arglist) docstring rest))
+
+(defmacro compat-defvar (name initval docstring &rest attr)
+ "Declare compatibility variable NAME with initial value INITVAL.
+The obligatory documentation string DOCSTRING must be given.
+
+The remaining arguments ATTR form a plist, modifying the
+behaviour of this macro. See `compat-generate-common' for a
+listing of attributes. Furthermore, `compat-defvar' also handles
+the attribute `:local' that either makes the variable permanent
+local with a value of `permanent' or just buffer local with any
+non-nil value."
+ (declare (debug (name form stringp [&rest keywordp sexp]))
+ (doc-string 3) (indent 2))
+ ;; Check if we want an explicitly prefixed function
+ (let ((oldname name))
+ (when (plist-get attr :prefix)
+ (setq name (intern (format "compat-%s" name))))
+ (compat-generate-common
+ name
+ (lambda (realname version)
+ (let ((localp (plist-get attr :local)))
+ `(progn
+ (,(if (plist-get attr :constant) 'defconst 'defvar)
+ ,realname ,initval
+ ;; Prepend compatibility notice to the actual
+ ;; documentation string.
+ ,(if version
+ (format
+ "[Compatibility variable for `%S', defined in Emacs %s]\n\n%s"
+ oldname version docstring)
+ (format
+ "[Compatibility variable for `%S']\n\n%s"
+ oldname docstring)))
+ ;; Make variable as local if necessary
+ ,(cond
+ ((eq localp 'permanent)
+ `(put ',realname 'permanent-local t))
+ (localp
+ `(make-variable-buffer-local ',realname))))))
+ (lambda (realname _version)
+ `(defvaralias ',name ',realname))
+ (lambda ()
+ `(not (boundp ',name)))
+ attr 'variable)))
+
+(provide 'compat-macs)
+;;; compat-macs.el ends here