diff --git a/builtins/path.c b/builtins/path.c index ba14c3d..d5305e3 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 9a37d92..ea1e99a 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 d7a1ea1..0f5e182 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 a3f8149..eee8401 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);