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

Reply via email to