aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-09-09 02:02:08 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-09-09 02:02:08 -0400
commit1fbe2cb5dd1aa4b20411ee0c3b00310677373a55 (patch)
tree5362da93fdf255fd08b748e9a72b7179a719f4e0
parent6752c60f32d0e154e54f30fd2b3e22904e56ea64 (diff)
For parsing paths, use nested parens: (./foo), also add some methods
-rw-r--r--builtins/path.c27
-rw-r--r--builtins/path.h2
-rw-r--r--environment.c6
-rw-r--r--parse.c29
4 files changed, 45 insertions, 19 deletions
diff --git a/builtins/path.c b/builtins/path.c
index ba14c3d8..d5305e30 100644
--- a/builtins/path.c
+++ b/builtins/path.c
@@ -220,7 +220,7 @@ public void Path$create_directory(Path_t path, int permissions)
fail("Could not create directory: %k (%s)", &path, strerror(errno));
}
-public Array_t Path$children(Path_t path, bool include_hidden)
+static Array_t _filtered_children(Path_t path, bool include_hidden, mode_t filter)
{
if (Text$matches(path, Pattern("~/{..}")))
path = Paths(Text$format("%s", getenv("HOME")), Text$slice(path, I(2), I(-1)));
@@ -240,13 +240,36 @@ public Array_t Path$children(Path_t path, bool include_hidden)
continue;
if (streq(dir->d_name, ".") || streq(dir->d_name, ".."))
continue;
- Path_t child = Text$format("%.*s/%s", path_len, path_str, dir->d_name);
+
+ const char *child_str = heap_strf("%.*s/%s", path_len, path_str, dir->d_name);
+ struct stat sb;
+ if (stat(child_str, &sb) != 0)
+ continue;
+ if (!((sb.st_mode & S_IFMT) & filter))
+ continue;
+
+ Path_t child = Text$format("%s%s", child_str, ((sb.st_mode & S_IFMT) == S_IFDIR) ? "/" : ""); // Trailing slash for dirs
Array$insert(&children, &child, I(0), sizeof(Path_t));
}
closedir(d);
return children;
}
+public Array_t Path$children(Path_t path, bool include_hidden)
+{
+ return _filtered_children(path, include_hidden, (mode_t)-1);
+}
+
+public Array_t Path$files(Path_t path, bool include_hidden)
+{
+ return _filtered_children(path, include_hidden, S_IFREG);
+}
+
+public Array_t Path$subdirectories(Path_t path, bool include_hidden)
+{
+ return _filtered_children(path, include_hidden, S_IFDIR);
+}
+
public const TypeInfo Path$info = {
.size=sizeof(Path_t),
.align=__alignof__(Path_t),
diff --git a/builtins/path.h b/builtins/path.h
index 9a37d92d..ea1e99ae 100644
--- a/builtins/path.h
+++ b/builtins/path.h
@@ -28,6 +28,8 @@ Text_t Path$read(Path_t path);
void Path$remove(Path_t path, bool ignore_missing);
void Path$create_directory(Path_t path, int permissions);
Array_t Path$children(Path_t path, bool include_hidden);
+Array_t Path$files(Path_t path, bool include_hidden);
+Array_t Path$subdirectories(Path_t path, bool include_hidden);
extern const TypeInfo Path$info;
diff --git a/environment.c b/environment.c
index d7a1ea1a..0f5e1826 100644
--- a/environment.c
+++ b/environment.c
@@ -254,14 +254,16 @@ env_t *new_compilation_unit(CORD *libname)
{"create_directory", "Path$create_directory", "func(path:Path, permissions=0o644_i32)"},
{"escape_text", "Path$escape_text", "func(text:Text)->Path"},
{"exists", "Path$exists", "func(path:Path)->Bool"},
+ {"files", "Path$children", "func(path:Path, include_hidden=no)->[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_socket", "Path$is_socket", "func(path:Path, follow_symlinks=yes)->Bool"},
{"is_symlink", "Path$is_symlink", "func(path:Path)->Bool"},
{"read", "Path$read", "func(path:Path)->Text"},
- {"relative", "Path$relative", "func(path:Path, relative_to=./)->Path"},
+ {"relative", "Path$relative", "func(path:Path, relative_to=(./))->Path"},
{"remove", "Path$remove", "func(path:Path, ignore_missing=no)"},
- {"resolved", "Path$resolved", "func(path:Path, relative_to=./)->Path"},
+ {"resolved", "Path$resolved", "func(path:Path, relative_to=(./))->Path"},
+ {"subdirectories", "Path$children", "func(path:Path, include_hidden=no)->[Path]"},
{"write", "Path$write", "func(path:Path, text:Text, permissions=0o644_i32)"},
)},
{"Shell", Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell")), "Shell_t", "Shell$info", TypedArray(ns_entry_t,
diff --git a/parse.c b/parse.c
index a3f8149b..eee8401e 100644
--- a/parse.c
+++ b/parse.c
@@ -1297,22 +1297,21 @@ PARSER(parse_text) {
}
PARSER(parse_path) {
- // ("~/" / "./" / "../" / "/") *([^ \t\r\n\\;] / "\" .)
+ // "(" ("~/" / "./" / "../" / "/") ... ")"
const char *start = pos;
- if (!(match(&pos, "~/")
- || match(&pos, "./")
- || match(&pos, "../")
- || match(&pos, "/")))
+ if (!(match(&pos, "(~/")
+ || match(&pos, "(./")
+ || match(&pos, "(../")
+ || match(&pos, "(/")))
return NULL;
- const char *chunk_start = start;
+ const char *chunk_start = start + 1;
ast_list_t *chunks = NULL;
CORD chunk_text = CORD_EMPTY;
- int depths[] = {[(int)'('] = 0, [(int)'{'] = 0, [(int)'['] = 0};
+ int paren_depth = 1;
while (pos < ctx->file->text + ctx->file->len) {
switch (*pos) {
- case '\r': case '\n': case ';': case ':': goto end_of_path;
case '\\': {
++pos;
chunk_text = CORD_asprintf("%r%.*s%c", chunk_text, (size_t)(pos - chunk_start), chunk_start, *pos);
@@ -1338,15 +1337,15 @@ PARSER(parse_path) {
chunk_start = pos;
continue;
}
- case ')': case '}': case ']': {
- if (depths[(int)*pos] <= 0)
- goto end_of_path;
- depths[(int)*pos] -= 1;
+ case '(': {
+ paren_depth += 1;
++pos;
continue;
}
- case '(': case '{': case '[': {
- depths[(int)*pos] += 1;
+ case ')': {
+ paren_depth -= 1;
+ if (paren_depth == 0)
+ goto end_of_path;
++pos;
continue;
}
@@ -1363,7 +1362,7 @@ PARSER(parse_path) {
chunks = new(ast_list_t, .ast=literal, .next=chunks);
}
- match(&pos, ";"); // optional trailing semicolon
+ expect_closing(ctx, &pos, ")", "I was expecting a ')' to finish this path");
REVERSE_LIST(chunks);
return NewAST(ctx->file, start, pos, TextJoin, .lang="Path", .children=chunks);