On 2026-08-17 01:48, Sergey Poznyakoff wrote:
I'm OK with it either way, the
most important thing is being able to tell how excluded_file_name
took the decision: using a matching entry or because nothing matched
the file name.

OK, thanks, I installed the attached into Gnulib; if this won't do please let 
us know.
From a50726598270147223685f91ac9761ce8a052a0e Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Tue, 18 Aug 2026 01:20:04 -0700
Subject: [PATCH] exclude: new function excluded_file_name_status

From a suggestion by Sergey Poznyakoff in:
https://lists.gnu.org/r/bug-gnulib/2026-08/msg00217.html
* lib/exclude.c (EXCLUDE_INLINE): New macro.
(excluded_file_name_status): New function, extending
excluded_file_name which is now in exclude.h.
* lib/exclude.h (EXCLUDE_INLINE, EXCLUDED_EXCLUDED, EXCLUDED_MATCHED):
New macros.
(excluded_file_name): New function.
* tests/test-exclude.c (main): Test new function too.
All test scripts changed.
---
 ChangeLog              | 14 ++++++++++++++
 lib/exclude.c          | 24 +++++++++++++-----------
 lib/exclude.h          | 22 +++++++++++++++++++++-
 tests/test-exclude.c   |  6 ++++--
 tests/test-exclude1.sh | 12 ++++++------
 tests/test-exclude2.sh | 12 ++++++------
 tests/test-exclude3.sh | 12 ++++++------
 tests/test-exclude4.sh |  2 +-
 tests/test-exclude5.sh |  8 ++++----
 tests/test-exclude6.sh |  4 ++--
 tests/test-exclude7.sh |  4 ++--
 tests/test-exclude8.sh |  6 +++---
 12 files changed, 82 insertions(+), 44 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 752464e397..30ad4d78e8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2026-08-18  Paul Eggert  <[email protected]>
+
+	exclude: new function excluded_file_name_status
+	From a suggestion by Sergey Poznyakoff in:
+	https://lists.gnu.org/r/bug-gnulib/2026-08/msg00217.html
+	* lib/exclude.c (EXCLUDE_INLINE): New macro.
+	(excluded_file_name_status): New function, extending
+	excluded_file_name which is now in exclude.h.
+	* lib/exclude.h (EXCLUDE_INLINE, EXCLUDED_EXCLUDED, EXCLUDED_MATCHED):
+	New macros.
+	(excluded_file_name): New function.
+	* tests/test-exclude.c (main): Test new function too.
+	All test scripts changed.
+
 2026-08-17  Bruno Haible  <[email protected]>
 
 	Depend on 'bool' for use of 'false' or 'true'.
diff --git a/lib/exclude.c b/lib/exclude.c
index 6a8604ec1b..0519ffec8e 100644
--- a/lib/exclude.c
+++ b/lib/exclude.c
@@ -21,6 +21,7 @@
    Thanks to Phil Proudman <[email protected]>
    for improvement suggestions. */
 
+#define EXCLUDE_INLINE _GL_EXTERN_INLINE
 #include <config.h>
 
 #include <ctype.h>
@@ -455,17 +456,18 @@ file_name_matches (struct exclude_segment const *seg, char const *f,
   return false;
 }
 
-/* Return true if EX excludes F.  */
+/* Return the status of whether EX excludes F.  */
 
-bool
-excluded_file_name (struct exclude const *ex, char const *f)
+int
+excluded_file_name_status (struct exclude const *ex, char const *f)
 {
   /* If no patterns are given, the default is to include.  */
   if (!ex->head)
-    return false;
+    return 0;
 
   bool invert = false;
   char *filename = nullptr;
+  bool found = false;
 
   /* Scan through the segments, reporting the status of the first match.
      The segments are in reverse order, so this reports the status of
@@ -477,14 +479,13 @@ excluded_file_name (struct exclude const *ex, char const *f)
         {
           if (!filename)
             filename = xmalloc (strlen (f) + 1);
-          if (file_name_matches (seg, f, filename))
-            break;
+          found = file_name_matches (seg, f, filename);
         }
       else
-        {
-          if (file_pattern_matches (seg, f))
-            break;
-        }
+        found = file_pattern_matches (seg, f);
+
+      if (found)
+        break;
 
       if (! seg->next)
         {
@@ -500,7 +501,8 @@ excluded_file_name (struct exclude const *ex, char const *f)
     }
 
   free (filename);
-  return invert ^ ! (seg->options & EXCLUDE_INCLUDE);
+  return ((invert ^ ! (seg->options & EXCLUDE_INCLUDE) ? EXCLUDED_EXCLUDED : 0)
+          | (found ? EXCLUDED_MATCHED : 0));
 }
 
 /* Append to EX the exclusion PATTERN with OPTIONS.  */
diff --git a/lib/exclude.h b/lib/exclude.h
index c217a1e4e6..3c77493f75 100644
--- a/lib/exclude.h
+++ b/lib/exclude.h
@@ -30,6 +30,11 @@
 
 #include <stdio.h>
 
+_GL_INLINE_HEADER_BEGIN
+#ifndef EXCLUDE_INLINE
+# define EXCLUDE_INLINE _GL_INLINE
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -54,6 +59,12 @@ extern "C" {
 /* Allocate storage for the pattern */
 #define EXCLUDE_ALLOC     (1 << 26)
 
+
+/* Return status flags for excluded_file_name_status.  */
+#define EXCLUDED_EXCLUDED (1 << 0) /* The file name was excluded.  */
+#define EXCLUDED_MATCHED  (1 << 1) /* The file name was matched.  */
+
+
 struct exclude;
 
 bool fnmatch_pattern_has_wildcards (const char *, int) _GL_ATTRIBUTE_PURE;
@@ -68,13 +79,22 @@ int add_exclude_file (void (*) (struct exclude *, char const *, int),
                       struct exclude *, char const *, int, char);
 int add_exclude_fp (void (*) (struct exclude *, char const *, int, void *),
                     struct exclude *, FILE *, int, char, void *);
-bool excluded_file_name (struct exclude const *, char const *);
+int excluded_file_name_status (struct exclude const *, char const *);
 void exclude_add_pattern_buffer (struct exclude *ex, char *buf);
 bool exclude_fnmatch (char const *, char const *, int);
 
+/* Return true if EX excludes F.  */
+EXCLUDE_INLINE bool
+excluded_file_name (struct exclude const *ex, char const *f)
+{
+  return excluded_file_name_status (ex, f) & EXCLUDED_EXCLUDED;
+}
+
 
 #ifdef __cplusplus
 }
 #endif
 
+_GL_INLINE_HEADER_END
+
 #endif /* _GL_EXCLUDE_H */
diff --git a/tests/test-exclude.c b/tests/test-exclude.c
index 17c631c02a..e57b34d232 100644
--- a/tests/test-exclude.c
+++ b/tests/test-exclude.c
@@ -118,8 +118,10 @@ main (int argc, char **argv)
   for (; argc; --argc)
     {
       char *word = *++argv;
-
-      printf ("%s: %d\n", word, excluded_file_name (exclude, word));
+      bool excluded = excluded_file_name (exclude, word);
+      int status = excluded_file_name_status (exclude, word);
+      printf ("%s: %d %d%s\n", word, excluded, !!(status & EXCLUDED_MATCHED),
+              excluded != !!(status & EXCLUDED_EXCLUDED) ? " !" : "");
     }
 
   free_exclude (exclude);
diff --git a/tests/test-exclude1.sh b/tests/test-exclude1.sh
index 0e2058915c..890da3ba43 100755
--- a/tests/test-exclude1.sh
+++ b/tests/test-exclude1.sh
@@ -28,12 +28,12 @@ Baz
 EOT
 
 cat > expected <<EOT
-foo: 0
-foo*: 1
-bar: 1
-foobar: 0
-baz: 0
-bar/qux: 0
+foo: 0 0
+foo*: 1 1
+bar: 1 1
+foobar: 0 0
+baz: 0 0
+bar/qux: 0 0
 EOT
 
 ${CHECKER} test-exclude in -- foo 'foo*' bar foobar baz bar/qux > out || exit $?
diff --git a/tests/test-exclude2.sh b/tests/test-exclude2.sh
index 25b69f4b90..c67a5d7473 100755
--- a/tests/test-exclude2.sh
+++ b/tests/test-exclude2.sh
@@ -28,12 +28,12 @@ EOT
 # Test case-insensitive literal matches
 
 cat > expected <<EOT
-foo: 0
-foo*: 1
-bar: 1
-foobar: 0
-baz: 1
-bar/qux: 0
+foo: 0 0
+foo*: 1 1
+bar: 1 1
+foobar: 0 0
+baz: 1 1
+bar/qux: 0 0
 EOT
 
 ${CHECKER} test-exclude -casefold in -- foo 'foo*' bar foobar baz bar/qux > out || exit $?
diff --git a/tests/test-exclude3.sh b/tests/test-exclude3.sh
index bfd168c21b..e3f35d8ce2 100755
--- a/tests/test-exclude3.sh
+++ b/tests/test-exclude3.sh
@@ -28,12 +28,12 @@ Baz
 EOT
 
 cat > expected <<EOT
-foo: 1
-foo*: 0
-bar: 0
-foobar: 1
-baz: 1
-bar/qux: 1
+foo: 1 0
+foo*: 0 1
+bar: 0 1
+foobar: 1 0
+baz: 1 0
+bar/qux: 1 0
 EOT
 
 ${CHECKER} test-exclude -include in -- foo 'foo*' bar foobar baz bar/qux > out || exit $?
diff --git a/tests/test-exclude4.sh b/tests/test-exclude4.sh
index 55ce8cfb86..4f250db3a8 100755
--- a/tests/test-exclude4.sh
+++ b/tests/test-exclude4.sh
@@ -28,7 +28,7 @@ Baz
 EOT
 
 cat > expected <<EOT
-foobar: 1
+foobar: 1 1
 EOT
 
 ${CHECKER} test-exclude -wildcards in -- foobar > out || exit $?
diff --git a/tests/test-exclude5.sh b/tests/test-exclude5.sh
index 6978ba4cd6..8372d9463b 100755
--- a/tests/test-exclude5.sh
+++ b/tests/test-exclude5.sh
@@ -28,10 +28,10 @@ Baz
 EOT
 
 cat > expected <<EOT
-bar: 1
-bar/qux: 1
-barz: 0
-foo/bar: 1
+bar: 1 1
+bar/qux: 1 1
+barz: 0 0
+foo/bar: 1 1
 EOT
 
 ${CHECKER} test-exclude -leading_dir in -- bar bar/qux barz foo/bar > out || exit $?
diff --git a/tests/test-exclude6.sh b/tests/test-exclude6.sh
index 240115a4f6..8b882f4e53 100755
--- a/tests/test-exclude6.sh
+++ b/tests/test-exclude6.sh
@@ -28,8 +28,8 @@ Baz
 EOT
 
 cat > expected <<EOT
-bar: 1
-foo/bar: 0
+bar: 1 1
+foo/bar: 0 0
 EOT
 
 ${CHECKER} test-exclude -anchored in -- bar foo/bar > out || exit $?
diff --git a/tests/test-exclude7.sh b/tests/test-exclude7.sh
index cb701d2c86..a8953c9d7b 100755
--- a/tests/test-exclude7.sh
+++ b/tests/test-exclude7.sh
@@ -28,8 +28,8 @@ Baz
 EOT
 
 cat > expected <<EOT
-bar: 0
-bar: 1
+bar: 0 1
+bar: 1 1
 EOT
 
 ${CHECKER} test-exclude in -include in -- bar > out || exit $?
diff --git a/tests/test-exclude8.sh b/tests/test-exclude8.sh
index 02524d6e89..e7a304ab65 100755
--- a/tests/test-exclude8.sh
+++ b/tests/test-exclude8.sh
@@ -27,9 +27,9 @@ b[a\*]r
 EOT
 
 cat > expected <<'EOT'
-f*e: 1
-file: 0
-bar: 1
+f*e: 1 1
+file: 0 0
+bar: 1 1
 EOT
 
 ${CHECKER} test-exclude -wildcards in -- 'f*e' 'file' 'bar' > out || exit $?
-- 
2.55.0

Reply via email to