summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--autoload.c23
-rw-r--r--glob.c157
-rw-r--r--stdlib/glob.tl93
-rw-r--r--tests/011/place.tl9
-rw-r--r--tests/018/glob.tl111
-rw-r--r--txr.1222
6 files changed, 610 insertions, 5 deletions
diff --git a/autoload.c b/autoload.c
index 81641d4e..fcc5e014 100644
--- a/autoload.c
+++ b/autoload.c
@@ -973,6 +973,28 @@ static val csort_instantiate(void)
return nil;
}
+static val glob_set_entries(val fun)
+{
+ val sys_name[] = {
+ lit("brace-expand"),
+ nil
+ };
+ val name[] = {
+ lit("glob*"),
+ nil
+ };
+ autoload_sys_set(al_fun, sys_name, fun);
+ autoload_set(al_fun, name, fun);
+ return nil;
+}
+
+static val glob_instantiate(void)
+{
+ load(scat2(stdlib_path, lit("glob")));
+ return nil;
+}
+
+
val autoload_reg(val (*instantiate)(void),
val (*set_entries)(val))
{
@@ -1043,6 +1065,7 @@ void autoload_init(void)
autoload_reg(expander_let_instantiate, expander_let_set_entries);
autoload_reg(load_args_instantiate, load_args_set_entries);
autoload_reg(csort_instantiate, csort_set_entries);
+ autoload_reg(glob_instantiate, glob_set_entries);
reg_fun(intern(lit("autoload-try-fun"), system_package), func_n1(autoload_try_fun));
}
diff --git a/glob.c b/glob.c
index 01efb446..f263c042 100644
--- a/glob.c
+++ b/glob.c
@@ -29,6 +29,7 @@
#include <wchar.h>
#include <signal.h>
#include <stdlib.h>
+#include <string.h>
#include <glob.h>
#include "config.h"
#include "lib.h"
@@ -38,10 +39,18 @@
#include "signal.h"
#include "unwind.h"
#include "glob.h"
+#include "txr.h"
+
+#define GLOB_XNOBRACE (1 << 30)
+#define GLOB_XSTAR (1 << 29)
static val s_errfunc;
static uw_frame_t *s_exit_point;
+static int super_glob(const char *pattern, int flags,
+ int (*errfunc) (const char *epath, int eerrno),
+ glob_t *pglob);
+
static int errfunc_thunk(const char *errpath, int errcode)
{
val result = t;
@@ -70,6 +79,9 @@ val glob_wrap(val pattern, val flags, val errfun)
val self = lit("glob");
cnum c_flags = c_num(default_arg(flags, zero), self);
glob_t gl;
+ int (*globfn)(const char *, int,
+ int (*) (const char *, int),
+ glob_t *) = if3((c_flags & GLOB_XSTAR) != 0, super_glob, glob);
if (s_errfunc)
uw_throwf(error_s, lit("~a: glob cannot be re-entered from "
@@ -77,11 +89,14 @@ val glob_wrap(val pattern, val flags, val errfun)
s_errfunc = default_null_arg(errfun);
- c_flags &= ~GLOB_APPEND;
+ c_flags &= ~(GLOB_APPEND | GLOB_XSTAR | GLOB_XNOBRACE);
+
+ if (globfn == super_glob)
+ c_flags &= ~GLOB_BRACE;
if (stringp(pattern)) {
char *pat_u8 = utf8_dup_to(c_str(pattern, self));
- (void) glob(pat_u8, c_flags, s_errfunc ? errfunc_thunk : 0, &gl);
+ (void) globfn(pat_u8, c_flags, s_errfunc ? errfunc_thunk : 0, &gl);
free(pat_u8);
} else {
seq_iter_t iter;
@@ -90,7 +105,7 @@ val glob_wrap(val pattern, val flags, val errfun)
while (seq_get(&iter, &elem)) {
char *pat_u8 = utf8_dup_to(c_str(elem, self));
- (void) glob(pat_u8, c_flags, s_errfunc ? errfunc_thunk : 0, &gl);
+ (void) globfn(pat_u8, c_flags, s_errfunc ? errfunc_thunk : 0, &gl);
if (s_exit_point)
break;
c_flags |= GLOB_APPEND;
@@ -119,6 +134,140 @@ val glob_wrap(val pattern, val flags, val errfun)
}
}
+static const char *super_glob_find_inner(const char *pattern)
+{
+ enum state { init, bsl, cls } st = init, pst;
+ int ch;
+
+ for (; (ch = *pattern) != 0; pattern++) {
+ switch (st) {
+ case init:
+ if (strncmp(pattern, "/**/", 4) == 0)
+ return pattern + 1;
+ switch (ch) {
+ case '\\':
+ pst = init;
+ st = bsl;
+ break;
+ case '[':
+ st = cls;
+ break;
+ }
+ break;
+ case bsl:
+ st = pst;
+ break;
+ case cls:
+ switch (ch) {
+ case '\\':
+ pst = cls;
+ st = bsl;
+ break;
+ case ']':
+ st = init;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int super_glob_rec(const char *pattern, int flags,
+ int (*errfunc) (const char *epath, int eerrno),
+ glob_t *pglob, size_t star_limit)
+{
+ const char *dblstar = 0;
+
+ if (strncmp(pattern, "**/", 3) == 0 || strcmp(pattern, "**") == 0) {
+ dblstar = pattern;
+ } else if ((dblstar = super_glob_find_inner(pattern)) != 0) {
+ /* nothing */
+ } else if (strlen(pattern) >= 3) {
+ const char *end = pattern + strlen(pattern);
+ if (strcmp(end - 3, "/**") == 0)
+ dblstar = end - 2;
+ }
+
+ if (dblstar == 0) {
+ return glob(pattern, flags, errfunc, pglob);
+ } else {
+ size_t i, base_len = strlen(pattern);
+ size_t ds_off = dblstar - pattern;
+ size_t tail_off = ds_off + 2;
+ size_t limit = star_limit > 10 ? 10 : star_limit;
+
+ for (i = 0; i < limit; i++) {
+ size_t space = base_len - 3 + i * 2;
+ char *pat_copy = coerce(char *, chk_malloc(space + 2));
+ size_t j;
+ char *out = pat_copy + ds_off;
+ int res;
+
+ strncpy(pat_copy, pattern, ds_off);
+
+ for (j = 0; j < i; j++) {
+ if (j > 0)
+ *out++ = '/';
+ *out++ = '*';
+ }
+
+ if (i == 0 && pattern[tail_off] == '/')
+ strcpy(out, pattern + tail_off + 1);
+ else
+ strcpy(out, pattern + tail_off);
+
+ if (i > 0)
+ flags |= GLOB_APPEND;
+
+ res = super_glob_rec(pat_copy, flags, errfunc, pglob, star_limit - i);
+
+ free(pat_copy);
+
+ if (res && res != GLOB_NOMATCH)
+ return res;
+ }
+
+ return 0;
+ }
+}
+
+static int glob_path_cmp(const void *ls, const void *rs)
+{
+ const char *lstr = *convert(const char * const *, ls);
+ const char *rstr = *convert(const char * const *, rs);
+
+ for (; *lstr && *rstr; lstr++, rstr++)
+ {
+ if (*lstr == *rstr)
+ continue;
+ if (*lstr == '/')
+ return -1;
+ if (*rstr == '/')
+ return 1;
+ if (*lstr < *rstr)
+ return -1;
+ if (*lstr > *rstr)
+ return 1;
+ }
+
+ return lstr ? 1 : rstr ? -1 : 0;
+}
+
+static int super_glob(const char *pattern, int flags,
+ int (*errfunc) (const char *epath, int eerrno),
+ glob_t *pglob)
+{
+ int res = super_glob_rec(pattern, flags | GLOB_NOSORT, errfunc, pglob, 48);
+
+ if (res == 0 && (flags & GLOB_NOSORT) == 0) {
+ qsort(pglob->gl_pathv, pglob->gl_pathc,
+ sizeof pglob->gl_pathv[0], glob_path_cmp);
+ }
+
+ return 0;
+}
+
void glob_init(void)
{
prot1(&s_errfunc);
@@ -149,4 +298,6 @@ void glob_init(void)
#ifdef GLOB_ONLYDIR
reg_varl(intern(lit("glob-onlydir"), user_package), num_fast(GLOB_ONLYDIR));
#endif
+ reg_varl(intern(lit("glob-xstar"), system_package), num(GLOB_XSTAR));
+ reg_varl(intern(lit("glob-xnobrace"), user_package), num(GLOB_XNOBRACE));
}
diff --git a/stdlib/glob.tl b/stdlib/glob.tl
new file mode 100644
index 00000000..c23845a3
--- /dev/null
+++ b/stdlib/glob.tl
@@ -0,0 +1,93 @@
+;; Copyright 2023
+;; Kaz Kylheku <kaz@kylheku.com>
+;; Vancouver, Canada
+;; All rights reserved.
+;;
+;; Redistribution and use in source and binary forms, with or without
+;; modification, are permitted provided that the following conditions are met:
+;;
+;; 1. Redistributions of source code must retain the above copyright notice,
+;; this list of conditions and the following disclaimer.
+;;
+;; 2. Redistributions in binary form must reproduce the above copyright notice,
+;; this list of conditions and the following disclaimer in the documentation
+;; and/or other materials provided with the distribution.
+;;
+;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+;; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+;; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+;; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+;; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+;; POSSIBILITY OF SUCH DAMAGE.
+
+(defun brace-expand (str)
+ (bexp-expand (bexp-parse str)))
+
+(defstruct bexp-parse-ctx ()
+ str
+ toks)
+
+(defun bexp-parse (str)
+ (let ((ctx (new bexp-parse-ctx
+ str str
+ toks (remqual "" (tok #/([{},]|{}|\\\\|\\.)/ t str)))))
+ (build
+ (whilet ((next (pop ctx.toks)))
+ (add
+ (if (equal next "{")
+ (bexp-parse-brace ctx)
+ next))))))
+
+(defun bexp-parse-brace (ctx)
+ (buildn
+ (caseq (whilet ((next (pop ctx.toks)))
+ (casequal next
+ ("{" (add (bexp-parse-brace ctx)))
+ ("}" (return :ok))
+ (t (add next))))
+ (:ok
+ (cond
+ ((memqual "," (get))
+ (flow (get)
+ (split* @1 (op where (op equal ",")))
+ (cons '/)))
+ (t
+ (add* "{")
+ (add "}")
+ (get))))
+ (nil
+ (add* "{")
+ (get)))))
+
+(defun bexp-expand (tree : (path (new list-builder)))
+ (build
+ (match-case tree
+ (() (add (cat-str path.(get))))
+ (((/ . @alt) . @rest)
+ (let ((saved-path path.(get)))
+ (each ((elem alt))
+ path.(oust saved-path)
+ (pend (bexp-expand (cons elem rest) path)))))
+ ((@(consp @succ) . @rest)
+ (pend (bexp-expand (append succ rest) path)))
+ ((@head . @rest)
+ path.(add head)
+ (pend (bexp-expand rest path))))))
+
+
+(defun glob* (pattern-or-patterns : (flags 0))
+ (let ((xflags (logior flags sys:glob-xstar))
+ (patterns (if (listp pattern-or-patterns)
+ pattern-or-patterns
+ (list pattern-or-patterns))))
+ (if (or (logtest flags glob-xnobrace)
+ (null (find-if (op find #\{) patterns)))
+ (glob patterns xflags)
+ (let ((xpatterns [mappend brace-expand patterns]))
+ (append-each ((p xpatterns))
+ (glob p xflags))))))
diff --git a/tests/011/place.tl b/tests/011/place.tl
new file mode 100644
index 00000000..eb0dcb46
--- /dev/null
+++ b/tests/011/place.tl
@@ -0,0 +1,9 @@
+(load "../common")
+
+(defvar h (hash))
+
+(mtest
+ (let ((x 0)) (ensure (gethash h (pinc x)) "a") x) 1
+ [h 0] "a"
+ (let ((x 0)) (ensure (gethash h 0) (pinc x)) x) 0
+ [h 0] "a")
diff --git a/tests/018/glob.tl b/tests/018/glob.tl
new file mode 100644
index 00000000..35cd24e3
--- /dev/null
+++ b/tests/018/glob.tl
@@ -0,0 +1,111 @@
+(load "../common")
+
+(mtest
+ (sys:brace-expand "~/{Downloads,Pictures}/*.{jpg,gif,png}")
+ #"~/Downloads/*.jpg ~/Downloads/*.gif ~/Downloads/*.png \
+ ~/Pictures/*.jpg ~/Pictures/*.gif ~/Pictures/*.png"
+ (sys:brace-expand "It{{em,alic}iz,erat}e{d,}, please.")
+ ("Itemized, please." "Itemize, please." "Italicized, please."
+ "Italicize, please." "Iterated, please." "Iterate, please.")
+ (sys:brace-expand "{,{,gotta have{ ,\\, again\\, }}more }cowbell!")
+ ("cowbell!" "more cowbell!" "gotta have more cowbell!"
+ "gotta have\\, again\\, more cowbell!")
+ (sys:brace-expand "{}} some }{,{\\\\{ edge, edge} \\,}{ cases, {here} \\\\\\\\\\}")
+ ("{}} some }{,{\\\\ edge \\,}{ cases, {here} \\\\\\\\\\}"
+ "{}} some }{,{\\\\ edge \\,}{ cases, {here} \\\\\\\\\\}"))
+
+(mtest
+ (glob* "tests/**/002") ("tests/002")
+ (glob* "tests/**/{003,004}") ("tests/003" "tests/004"))
+
+(chdir "tests/002/proc")
+
+(mtest
+ (glob* "**")
+ ("1/status" "1/tasks" "103/status" "103/tasks" "103" "1068/status"
+ "1068/tasks" "1068" "1235/status" "1235/tasks" "1235" "1236/status"
+ "1236/tasks" "1236" "15812/status" "15812/tasks" "15812" "16/status"
+ "16/tasks" "1620/status" "1620/tasks" "1620" "1624/status" "1624/tasks"
+ "16248/status" "16248/tasks" "16248" "16249/status" "16249/tasks"
+ "16249" "1624" "1645/status" "1645/tasks" "1645" "16598/tasks"
+ "16598" "1665/status" "1665/tasks" "1665" "1698/status" "1698/tasks"
+ "1698" "16" "17/status" "17/tasks" "175/status" "175/tasks" "175"
+ "1766/status" "1766/tasks" "1766" "1790/status" "1790/tasks"
+ "1791/status" "1791/tasks" "1791" "17" "1790" "1821/status" "1821/tasks"
+ "1821" "1839/status" "1839/tasks" "1839" "1851/status" "1851/tasks"
+ "1851" "186/status" "186/tasks" "18614/tasks" "186" "18614" "1887/status"
+ "1887/tasks" "1902/status" "1902/tasks" "1921/status" "1921/tasks"
+ "1925/status" "1925/tasks" "1925" "1926/status" "1926/tasks"
+ "1927/status" "1927/tasks" "1927" "1928/status" "1928/tasks"
+ "1928" "1929/status" "1929/tasks" "1930/status" "1930/tasks"
+ "1930" "1931/status" "1931/tasks" "1931" "1932/status" "1932/tasks"
+ "1936/status" "1936/tasks" "1963/status" "1963/tasks" "1989/status"
+ "1989/tasks" "1" "1887" "1902" "1921" "1926" "1929" "1932" "1936"
+ "1963" "1989" "2/status" "2/tasks" "2008/status" "2008/tasks"
+ "2008" "2027/status" "2027/tasks" "2027" "2041/status" "2041/tasks"
+ "2041" "2052/status" "2052/tasks" "2052" "2062/status" "2062/tasks"
+ "2062" "2124/status" "2124/tasks" "2124" "2184/status" "2184/tasks"
+ "2184" "2354/status" "2354/tasks" "2354" "24134/tasks" "24134"
+ "2551/status" "2551/tasks" "2551" "2579/status" "2579/tasks"
+ "2579" "2625/status" "2625/tasks" "2625" "2626/status" "2626/tasks"
+ "2626" "2631/status" "2631/tasks" "2631" "2634/status" "2634/tasks"
+ "2634" "2636/status" "2636/tasks" "2636" "2638/status" "2638/tasks"
+ "2638" "2644/status" "2644/tasks" "2644" "2661/status" "2661/tasks"
+ "2661" "2685/status" "2685/tasks" "2685" "2689/status" "2689/tasks"
+ "2689" "2691/status" "2691/tasks" "2691" "2693/status" "2693/tasks"
+ "2693" "2695/status" "2695/tasks" "2695" "2698/status" "2698/tasks"
+ "2698" "2701/status" "2701/tasks" "2701" "2707/status" "2707/tasks"
+ "2707" "27121/status" "27121/tasks" "27121" "2717/status" "2717/tasks"
+ "2717" "2718/status" "2718/tasks" "2718" "2720/status" "2720/tasks"
+ "2720" "2722/status" "2722/tasks" "2722" "27243/status" "27243/tasks"
+ "2726/status" "2726/tasks" "2726" "2728/status" "2728/tasks"
+ "2728" "27682/status" "27682/tasks" "27682" "27684/status" "27684/tasks"
+ "27684" "27685/status" "27685/tasks" "27685" "28/status" "28/tasks"
+ "28" "29/status" "29/tasks" "29840/status" "29840/tasks" "29840"
+ "2" "27243" "29" "3/status" "3/tasks" "30737/status" "30737/tasks"
+ "30737" "31905/status" "31905/tasks" "31905" "31907/status" "31907/tasks"
+ "31907" "31908/status" "31908/tasks" "31908" "32672/status" "32672/tasks"
+ "32672" "32674/status" "32674/tasks" "32674" "32675/status" "32675/tasks"
+ "3" "32675" "4/status" "4/tasks" "4" "5/status" "5/tasks" "5"
+ "870/status" "870/tasks" "870")
+ (glob "**/")
+ ("1/" "103/" "1068/" "1235/" "1236/" "15812/" "16/" "1620/" "1624/"
+ "16248/" "16249/" "1645/" "16598/" "1665/" "1698/" "17/" "175/"
+ "1766/" "1790/" "1791/" "1821/" "1839/" "1851/" "186/" "18614/"
+ "1887/" "1902/" "1921/" "1925/" "1926/" "1927/" "1928/" "1929/"
+ "1930/" "1931/" "1932/" "1936/" "1963/" "1989/" "2/" "2008/"
+ "2027/" "2041/" "2052/" "2062/" "2124/" "2184/" "2354/" "24134/"
+ "2551/" "2579/" "2625/" "2626/" "2631/" "2634/" "2636/" "2638/"
+ "2644/" "2661/" "2685/" "2689/" "2691/" "2693/" "2695/" "2698/"
+ "2701/" "2707/" "27121/" "2717/" "2718/" "2720/" "2722/" "27243/"
+ "2726/" "2728/" "27682/" "27684/" "27685/" "28/" "29/" "29840/"
+ "3/" "30737/" "31905/" "31907/" "31908/" "32672/" "32674/" "32675/"
+ "4/" "5/" "870/"))
+
+(chdir "../..")
+
+(mtest
+ (glob* "**/3*/**")
+ ("002/proc/3/status" "002/proc/3/tasks" "002/proc/3/" "002/proc/30737/status"
+ "002/proc/30737/tasks" "002/proc/30737/" "002/proc/31905/status"
+ "002/proc/31905/tasks" "002/proc/31905/" "002/proc/31907/status"
+ "002/proc/31907/tasks" "002/proc/31907/" "002/proc/31908/status"
+ "002/proc/31908/tasks" "002/proc/31908/" "002/proc/32672/status"
+ "002/proc/32672/tasks" "002/proc/32672/" "002/proc/32674/status"
+ "002/proc/32674/tasks" "002/proc/32674/" "002/proc/32675/status"
+ "002/proc/32675/tasks" "002/proc/32675/")
+ (glob* "**/{3,4,5}*/**")
+ ("002/proc/3/status" "002/proc/3/tasks" "002/proc/3/" "002/proc/30737/status"
+ "002/proc/30737/tasks" "002/proc/30737/" "002/proc/31905/status"
+ "002/proc/31905/tasks" "002/proc/31905/" "002/proc/31907/status"
+ "002/proc/31907/tasks" "002/proc/31907/" "002/proc/31908/status"
+ "002/proc/31908/tasks" "002/proc/31908/" "002/proc/32672/status"
+ "002/proc/32672/tasks" "002/proc/32672/" "002/proc/32674/status"
+ "002/proc/32674/tasks" "002/proc/32674/" "002/proc/32675/status"
+ "002/proc/32675/tasks" "002/proc/32675/" "002/proc/4/status"
+ "002/proc/4/tasks" "002/proc/4/" "002/proc/5/status" "002/proc/5/tasks"
+ "002/proc/5/")
+ (glob* "**/{3,4,5}*/**" glob-xnobrace)
+ nil
+ (len (glob* "**/proc/**/**"))
+ 547)
diff --git a/txr.1 b/txr.1
index 555bbed1..83b82a28 100644
--- a/txr.1
+++ b/txr.1
@@ -75854,7 +75854,9 @@ header:
.codn GLOB_NOSORT ,
etc.
-These values are passed as the optional second argument of the
+These values are passed as an argument to the optional
+.meta flags
+argument of the
.code glob
function. They are bitmasks and so multiple values can be combined
using the
@@ -75881,9 +75883,37 @@ flag is not represented as a \*(TX variable. The
function uses it internally when calling the C library function
multiple times, due to having been given multiple patterns.
-.coNP Function @ glob
+.coNP Variable @ glob-xnobrace
+.desc
+This value holds an integer bitmask value that may be given as an argument to
+the optional
+.meta flags
+parameter of the
+.code glob*
+function. It may be used alone, combine with the other
+.code glob
+mask values using the
+.code logior
+function.
+
+If used with
+.codn glob* ,
+it disables brace expansion, which is enabled in
+.code glob*
+by default.
+
+If used with the
+.code glob
+function, it has no effect.
+
+This value is a \*(TL extension; it does not appear in the API of the
+.code glob
+C function.
+
+.coNP Functions @ glob and @ glob*
.synb
.mets (glob >> { pattern | << patterns } >> [ flags <> [ errfun ]])
+.mets (glob* >> { pattern | << patterns } >> [ flags <> [ errfun ]])
.syne
.desc
The
@@ -75901,6 +75931,38 @@ representing the matching pathnames in the file system.
If there are no matches, then an empty list is returned.
+The
+.code glob*
+function is a \*(TL extension built on
+.codn glob .
+
+The
+.code glob*
+functions supports a
+.code **
+("double star")
+pattern which matches zero or more path components. The double
+star match is described in detail below.
+
+The
+.code glob*
+function also supports brace expansion, independently of whether or not
+.code glob
+supports brace expansion. Brace expansion is enabled by default in
+.code glob*
+and can be disabled using the
+.code glob-xnobrace
+flag. Brace expansion is described in detail below.
+
+Lastly, the
+.code glob*
+function performs a path-aware sort of the emerging path names that
+is not influenced by locale, whereas the sort performed by
+.code glob
+is influenced by locale, defaulting to a lexicographic sort in the
+.str C
+locale.
+
The optional
.meta flags
argument defaults to zero. If given, it may be a bitwise combination of the
@@ -75964,6 +76026,162 @@ function, and the meaning of all the
.meta flags
arguments are given in the documentation for the C function.
+The
+.code glob*
+function supports brace expansion, which is enabled by default,
+and can be disabled with
+.codn glob-xnobrace .
+
+On some platforms, such as the GNU C Library, the
+.code glob
+function also supports brace expansion. If available, then the
+.code glob-brace
+variable has a nonzero value and must be included in the
+.meta flags
+argument.
+
+These two brace expansion features are independent; the \*(TL
+.code glob*
+function does not rely on
+.code glob
+for brace expansion, even if it is available.
+
+The brace expansion supported by
+.code glob*
+is a string generation mechanism driven by a syntax which specifies
+comma-separated elements enclosed in braces.
+
+When a single brace expansion appears in a pattern, that pattern turns
+into a list of patterns. There are as many elements in the list
+as there are elements between the braces. Each element replaces the
+braces with a different element from between the braces.
+
+For instance,
+.str x{a,b}y
+denotes the list of strings
+.codn "(\(dqxay\(dq \(dqxby\(dq)" .
+The there are two elements in the list because the braces contain
+two elements. The first string replaces
+.str {a,b}
+with
+.str a
+and the second replaces it with
+.strn b .
+
+When multiple braces occur in a pattern, then all combinations
+(Cartesian product) of the braces is produced.
+
+Braces may also nest. When the element of a brace itself uses braces, then that
+element is subject to brace expansion. The elements which emerge then become
+items of the enclosing brace, as if they were comma-separated elements.
+For instance
+.str x{a,{b,c}y}z
+is equivalent to
+.str x{a,by,cy}z
+which then expands to the three strings
+.strn xaz ,
+.str xbyz
+and
+.strn xcyz .
+
+Braces may be escaped by a backslash to disable their special meaning.
+Likewise, the commas may be escaped by a backslash to preserve their special
+meaning. Brace expansion preserves these backslashes; they appear in the
+resulting patterns, and must be recognized and removed by subsequent
+processing.
+
+When the
+.meta pattern
+arguments of
+.code glob*
+use brace expansion, they those arguments produce multiple patterns.
+The order of these patterns is preserved: the patterns are matched
+in that order. For each pattern, the matching path names are sorted,
+unless the
+.code glob-nosort
+flag is in effect.
+
+The
+.code **
+("double star")
+operator recognized by
+.code glob*
+matches zero or more path components. It may be used more than once.
+It cannot be combined with other characters or globbing operators.
+
+It is valid for
+.str **
+to be an entire pattern. This expands the relative path names of
+all files, directories and other objects in the current directory
+and its children.
+
+Otherwise the double star may appear at the start of a pattern if it is
+followed by a slash; at the end of a patter if it is preceded by a slash,
+or in the middle of a pattern if it is surrounded by two slashes.
+The double star is not recognized in a bracket-enclosed character
+class.
+
+Thus, the following examples all contain one double star:
+
+.verb
+ **
+ foo/**
+ **/bar
+ here/**/there
+.brev
+
+These do not contain a double star; the two asterisks in these
+patterns will be passed to the underlying
+.code glob
+function without being processed as a double star by
+.codn glob* ,
+with unspecified consequences:
+
+.verb
+ foo**
+ **bar
+ here**/there
+ etc/**conf
+ foo[/**/]bar
+.brev
+
+Each double star matches a maximum of ten path components,
+and all of the double stars in a single pattern together do not
+match more than 48 components. Using more than three double
+stars in a pattern is not recommended for performance reasons.
+
+If the double star is followed by a slash, it matches only
+directories.
+
+The
+.code glob*
+function sorts paths in such a way that the slash character
+is ranked lower than all other characters. Thus the path
+.str test/
+sorts before
+.str test-data/
+even though in ASCII and Unicode, the
+.code -
+character has a lower code than the
+.code /
+character.
+
+.TP* Examples:
+
+.verb
+ ;; find all jpg and gif paths under the current directory,
+ ;; (up to ten levels deep).
+ (glob* "**/*.{jpg,gif}")
+
+ ;; find all "2023" directories under the current directory,
+ ;; which have .jpg or .gif files under them, listing those
+ ;; .jpg and .gif paths:
+ (glob* "**/2023/**/*.{jpg,gif}")
+
+ ;; find all "2023" directories under the current directory.
+ (glob* "**/2023/**/")
+.brev
+
.coNP Variables @, fnm-pathname @, fnm-noescape @, fnm-period @, fnm-leading-dir @ fnm-casefold and @ fnm-extmatch
.desc
These variables take on the values of the corresponding C preprocessor