branch: externals/compat commit 10e6509878001c77bb5a00b51b33210c3347db97 Author: Daniel Mendler <m...@daniel-mendler.de> Commit: Daniel Mendler <m...@daniel-mendler.de>
compat-30: Add require-with-check --- NEWS.org | 1 + compat-30.el | 25 +++++++++++++++++++++++++ compat-tests.el | 20 ++++++++++++++++++++ compat.texi | 14 ++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/NEWS.org b/NEWS.org index 90da0c285d..043d45ac15 100644 --- a/NEWS.org +++ b/NEWS.org @@ -10,6 +10,7 @@ - compat-30: New function =merge-ordered-lists=. - compat-30: New variables =completion-lazy-hilit= and =completion-lazy-hilit-fn= and new function =completion-lazy-hilit=. +- compat-30: New function =require-with-check=. * Release of "Compat" Version 29.1.4.4 diff --git a/compat-30.el b/compat-30.el index 90b8a11a44..36e6a19d02 100644 --- a/compat-30.el +++ b/compat-30.el @@ -27,6 +27,31 @@ ;; TODO Update to 30.1 as soon as the Emacs emacs-30 branch version bumped (compat-version "30.0.50") +;;;; Defined in files.el + +(compat-defun require-with-check (feature &optional filename noerror) ;; <compat-tests:require-with-check> + "If FEATURE is not already loaded, load it from FILENAME. +This is like `require' except if FEATURE is already a member of the list +`features’, then we check if this was provided by a different file than the +one that we would load now (presumably because `load-path' has been +changed since the file was loaded). +If it's the case, we either signal an error (the default), or forcibly reload +the new file (if NOERROR is equal to `reload'), or otherwise emit a warning." + (let ((lh load-history) + (res (require feature filename (if (eq noerror 'reload) nil noerror)))) + ;; If the `feature' was not yet provided, `require' just loaded the right + ;; file, so we're done. + (when (eq lh load-history) + ;; If `require' did nothing, we need to make sure that was warranted. + (let ((fn (locate-file (or filename (symbol-name feature)) + load-path (get-load-suffixes)))) + (cond + ((assoc fn load-history) nil) ;We loaded the right file. + ((eq noerror 'reload) (load fn nil 'nomessage)) + (t (funcall (if noerror #'warn #'error) + "Feature provided by other file: %S" feature))))) + res)) + ;;;; Defined in minibuffer.el (compat-defvar completion-lazy-hilit nil ;; <compat-tests:completion-lazy-hilit> diff --git a/compat-tests.el b/compat-tests.el index 2ebd9bfa97..12e52ea370 100644 --- a/compat-tests.el +++ b/compat-tests.el @@ -3064,5 +3064,25 @@ '((E C D) (B A) (A C) (D B)) (lambda (_) (error "cycle"))))) +(ert-deftest compat-require-with-check () + ;; TODO enable test on Emacs 30 as soon as the CI supports it. + (static-if (< emacs-major-version 30) + (ert-with-temp-directory dir1 + (ert-with-temp-directory dir2 + (dolist (dir (list dir1 dir2)) + (with-temp-buffer + (insert "(provide 'compat-reload)") + (write-region (point-min) (point-max) + (file-name-concat dir "compat-reload.el")))) + (should-not (require-with-check 'compat-does-not-exist nil 'noerror)) + (should-not (require-with-check 'compat-does-not-exist "compat-does-not-exist.el" 'noerror)) + (let ((load-path (cons dir1 load-path))) + (should-equal 'compat-reload (require-with-check 'compat-reload)) + (should-equal 'compat-reload (require-with-check 'compat-reload))) + (let ((load-path (cons dir2 load-path))) + (should-error (require-with-check 'compat-reload)) + (should-equal 'compat-reload (require-with-check 'compat-reload nil 'noerror)) + (should-equal 'compat-reload (require-with-check 'compat-reload nil 'reload))))))) + (provide 'compat-tests) ;;; compat-tests.el ends here diff --git a/compat.texi b/compat.texi index 39a9c79557..95c9257b79 100644 --- a/compat.texi +++ b/compat.texi @@ -3348,6 +3348,20 @@ older than 30.1. Note that due to upstream changes, it might happen that there will be the need for changes, so use these functions with care. +@c copied from lispref/loading.texi +@defun require-with-check feature &optional filename noerror +This function works like @code{require}, except if @var{feature} is +already loaded (i.e.@: is already a member of the list in +@code{features}, see below). If @var{feature} is already loaded, this +function checks if @var{feature} was provided by a file different from +@var{filename}, and if so, it by default signals an error. If the +value of the optional argument @var{noerror} is @code{reload}, the +function doesn't signal an error, but instead forcibly reloads +@var{filename}; if @var{noerror} is some other non-@code{nil} value, +the function emits a warning about @var{feature} being already +provided by another file. +@end defun + @defun merge-ordered-lists lists &optional error-function Merge @var{lists} in a consistent order. @var{lists} is a list of lists of elements. Merge them into a single list containing the same