aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-10-29 14:36:49 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-10-29 14:36:49 -0400
commit7cd67dd7f3ebf38a2a65c6756090936f9a1b3b03 (patch)
tree6407b1b0836ea0d474b4822ef888cb6c6eaba7ae
parente3c1dd2df5a593829a4d5864f8ff7ea4582da55c (diff)
Add file globbing
-rw-r--r--docs/paths.md47
-rw-r--r--environment.c1
-rw-r--r--stdlib/paths.c20
-rw-r--r--stdlib/paths.h1
-rw-r--r--test/paths.tm2
5 files changed, 70 insertions, 1 deletions
diff --git a/docs/paths.md b/docs/paths.md
index 8fb427b2..86ec2f26 100644
--- a/docs/paths.md
+++ b/docs/paths.md
@@ -288,6 +288,53 @@ A list of file paths.
---
+### `glob`
+
+**Description:**
+Perform a globbing operation and return an array of matching paths. Some glob
+specific details:
+
+- The paths "." and ".." are *not* included in any globbing results.
+- Files or directories that begin with "." will not match `*`, but will match `.*`.
+- Globs do support `{a,b}` syntax for matching files that match any of several
+ choices of patterns.
+- The shell-style syntax `**` for matching subdirectories is not supported.
+
+**Signature:**
+```tomo
+func glob(path: Path -> [Path])
+```
+
+**Parameters:**
+
+- `path`: The path of the directory which may contain special globbing characters
+ like `*`, `?`, or `{...}`
+
+**Returns:**
+A list of file paths that match the glob.
+
+**Example:**
+```tomo
+# Current directory includes: foo.txt, baz.txt, qux.jpg, .hidden
+>> (./*):glob()
+= [(./foo.txt), (./baz.txt), (./qux.jpg)]
+
+>> (./*.txt):glob()
+= [(./foo.txt), (./baz.txt)]
+
+>> (./*.{txt,jpg}):glob()
+= [(./foo.txt), (./baz.txt), (./qux.jpg)]
+
+>> (./.*):glob()
+= [(./.hidden)]
+
+# Globs with no matches return an empty array:
+>> (./*.xxx):glob()
+= []
+```
+
+---
+
### `is_directory`
**Description:**
diff --git a/environment.c b/environment.c
index ffc23df1..7f8d71c2 100644
--- a/environment.c
+++ b/environment.c
@@ -302,6 +302,7 @@ env_t *new_compilation_unit(CORD libname)
{"exists", "Path$exists", "func(path:Path -> Bool)"},
{"extension", "Path$extension", "func(path:Path, full=yes -> Text)"},
{"files", "Path$children", "func(path:Path, include_hidden=no -> [Path])"},
+ {"glob", "Path$glob", "func(path:Path -> [Path])"},
{"is_directory", "Path$is_directory", "func(path:Path, follow_symlinks=yes -> Bool)"},
{"is_file", "Path$is_file", "func(path:Path, follow_symlinks=yes -> Bool)"},
{"is_pipe", "Path$is_pipe", "func(path:Path, follow_symlinks=yes -> Bool)"},
diff --git a/stdlib/paths.c b/stdlib/paths.c
index e8a1e1f6..8f6e1359 100644
--- a/stdlib/paths.c
+++ b/stdlib/paths.c
@@ -3,6 +3,7 @@
#include <errno.h>
#include <fcntl.h>
#include <gc.h>
+#include <glob.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
@@ -535,6 +536,24 @@ public OptionalClosure_t Path$by_line(Path_t path)
return (Closure_t){.fn=(void*)_next_line, .userdata=wrapper};
}
+public Array_t Path$glob(Path_t path)
+{
+ glob_t glob_result;
+ int status = glob(Text$as_c_string(path), GLOB_BRACE | GLOB_TILDE | GLOB_TILDE_CHECK, NULL, &glob_result);
+ if (status != 0 && status != GLOB_NOMATCH)
+ fail("Failed to perform globbing");
+
+ Array_t glob_files = {};
+ for (size_t i = 0; i < glob_result.gl_pathc; i++) {
+ size_t len = strlen(glob_result.gl_pathv[i]);
+ if ((len >= 2 && glob_result.gl_pathv[i][len-1] == '.' && glob_result.gl_pathv[i][len-2] == '/')
+ || (len >= 2 && glob_result.gl_pathv[i][len-1] == '.' && glob_result.gl_pathv[i][len-2] == '.' && glob_result.gl_pathv[i][len-3] == '/'))
+ continue;
+ Array$insert(&glob_files, (Text_t[1]){Text$from_str(glob_result.gl_pathv[i])}, I(0), sizeof(Text_t));
+ }
+ return glob_files;
+}
+
public const TypeInfo_t Path$info = {
.size=sizeof(Path_t),
.align=__alignof__(Path_t),
@@ -542,5 +561,4 @@ public const TypeInfo_t Path$info = {
.TextInfo={.lang="Path"},
};
-
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/stdlib/paths.h b/stdlib/paths.h
index 39089f54..551467e9 100644
--- a/stdlib/paths.h
+++ b/stdlib/paths.h
@@ -47,6 +47,7 @@ Path_t Path$parent(Path_t path);
Text_t Path$base_name(Path_t path);
Text_t Path$extension(Path_t path, bool full);
Closure_t Path$by_line(Path_t path);
+Array_t Path$glob(Path_t path);
#define Path$hash Text$hash
#define Path$compare Text$compare
diff --git a/test/paths.tm b/test/paths.tm
index 92a14640..4330e79f 100644
--- a/test/paths.tm
+++ b/test/paths.tm
@@ -133,3 +133,5 @@ func main():
>> (/foo/bar/baz) ++ (./.././qux/./../quux)
= (/foo/bar/quux)
+ !! Globbing:
+ >> (./*.tm):glob()