diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2024-09-09 01:14:33 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2024-09-09 01:14:33 -0400 |
| commit | 11c560ebccfa3eabb939dafc4139be92edd26842 (patch) | |
| tree | 43af12019ce7d133a539a0b3a9388e07e82acec6 | |
| parent | 13a9304decb86450a2a0e9cc756da4c2d373c929 (diff) | |
More path stuff including some methods
| -rw-r--r-- | builtins/path.c | 208 | ||||
| -rw-r--r-- | builtins/path.h | 35 | ||||
| -rw-r--r-- | environment.c | 12 | ||||
| -rw-r--r-- | parse.c | 4 |
4 files changed, 258 insertions, 1 deletions
diff --git a/builtins/path.c b/builtins/path.c new file mode 100644 index 00000000..9e4b5c44 --- /dev/null +++ b/builtins/path.c @@ -0,0 +1,208 @@ +// A lang for filesystem paths +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <stdbool.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistr.h> + +#include "array.h" +#include "files.h" +#include "functions.h" +#include "integers.h" +#include "path.h" +#include "text.h" +#include "types.h" +#include "util.h" + +PUREFUNC public Path_t Path$escape_text(Text_t text) +{ + if (Text$has(text, Pattern("/")) || Text$has(text, Pattern(";"))) + fail("Invalid path component: %k", &text); + else if (Text$matches(text, Pattern(".")) || Text$matches(text, Pattern(".."))) + fail("Invalid path component: %k", &text); + return (Path_t)text; +} + +public Text_t Path$resolved(Path_t path, Path_t relative_to) +{ + while (Text$has(path, Pattern("/../"))) + path = Text$replace(path, Pattern("{!/}/../"), Text(""), Text(""), false); + + while (Text$has(path, Pattern("/./"))) + path = Text$replace(path, Pattern("/./"), Text("/"), Text(""), false); + + const char *path_str = Text$as_c_string(path); + const char *relative_to_str = Text$as_c_string(relative_to); + const char *resolved_path = resolve_path(path_str, relative_to_str, relative_to_str); + if (resolved_path) { + return (Path_t)(Text$from_str(resolved_path)); + } else if (path_str[0] == '/') { + return path; + } else if (path_str[0] == '~' && path_str[1] == '/') { + return (Path_t)Text$format("%s%s", getenv("HOME"), path_str + 1); + } else { + return Paths(Path$resolved(relative_to, Path(".")), Path("/"), path); + } +} + +public Text_t Path$relative(Path_t path, Path_t relative_to) +{ + path = Path$resolved(path, relative_to); + relative_to = Path$resolved(relative_to, Path(".")); + if (Text$matches(path, Patterns(Pattern("{start}"), relative_to, Pattern("{..}")))) + return Text$slice(path, I(relative_to.length + 2), I(-1)); + return path; +} + +public bool Path$exists(Path_t path) +{ + struct stat sb; + return (stat(Text$as_c_string(path), &sb) == 0); +} + +public bool Path$is_file(Path_t path, bool follow_symlinks) +{ + struct stat sb; + const char *path_str = Text$as_c_string(path); + int status = follow_symlinks ? stat(path_str, &sb) : lstat(path_str, &sb); + if (status != 0) return false; + return (sb.st_mode & S_IFMT) == S_IFREG; +} + +public bool Path$is_directory(Path_t path, bool follow_symlinks) +{ + struct stat sb; + const char *path_str = Text$as_c_string(path); + int status = follow_symlinks ? stat(path_str, &sb) : lstat(path_str, &sb); + if (status != 0) return false; + return (sb.st_mode & S_IFMT) == S_IFDIR; +} + +public bool Path$is_pipe(Path_t path, bool follow_symlinks) +{ + struct stat sb; + const char *path_str = Text$as_c_string(path); + int status = follow_symlinks ? stat(path_str, &sb) : lstat(path_str, &sb); + if (status != 0) return false; + return (sb.st_mode & S_IFMT) == S_IFIFO; +} + +public bool Path$is_socket(Path_t path, bool follow_symlinks) +{ + struct stat sb; + const char *path_str = Text$as_c_string(path); + int status = follow_symlinks ? stat(path_str, &sb) : lstat(path_str, &sb); + if (status != 0) return false; + return (sb.st_mode & S_IFMT) == S_IFSOCK; +} + +public bool Path$is_symlink(Path_t path) +{ + struct stat sb; + const char *path_str = Text$as_c_string(path); + int status = stat(path_str, &sb); + if (status != 0) return false; + return (sb.st_mode & S_IFMT) == S_IFLNK; +} + +static void _write(Path_t path, Text_t text, int mode, int permissions) +{ + const char *path_str = Text$as_c_string(path); + int fd = open(path_str, mode, permissions); + if (fd == -1) + fail("Could not write to file: %s\n%s", strerror(errno)); + + const char *str = Text$as_c_string(text); + size_t len = strlen(str); + ssize_t written = write(fd, str, len); + if (written != (ssize_t)len) + fail("Could not write to file: %s\n%s", strerror(errno)); +} + +public void Path$write(Path_t path, Text_t text, int permissions) +{ + _write(path, text, O_WRONLY | O_CREAT, permissions); +} + +public void Path$append(Path_t path, Text_t text, int permissions) +{ + _write(path, text, O_WRONLY | O_APPEND | O_CREAT, permissions); +} + +public Text_t Path$read(Path_t path) +{ + int fd = open(Text$as_c_string(path), O_RDONLY); + if (fd == -1) + fail("Could not read file: %k (%s)", &path, strerror(errno)); + + struct stat sb; + if (fstat(fd, &sb) != 0) + fail("Could not read file: %k (%s)", &path, strerror(errno)); + + if ((sb.st_mode & S_IFMT) == S_IFREG) { // Use memory mapping if it's a real file: + printf("USING MMAP\n"); + const char *mem = mmap(NULL, (size_t)sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + char *gc_mem = GC_MALLOC_ATOMIC((size_t)sb.st_size+1); + memcpy(gc_mem, mem, (size_t)sb.st_size); + gc_mem[sb.st_size] = '\0'; + close(fd); + return Text$from_strn(gc_mem, (size_t)sb.st_size); + } else { + const size_t chunk_size = 256; + char *buf = GC_MALLOC_ATOMIC(chunk_size); + Text_t contents = Text(""); + ssize_t just_read; + do { + just_read = read(fd, buf, chunk_size); + if (just_read < 0) + fail("Failed while reading file: %k (%s)", &path, strerror(errno)); + else if (just_read == 0) + break; + + if (u8_check((uint8_t*)buf, (size_t)just_read) != NULL) + fail("File does not contain valid UTF8 data!"); + contents = Texts(contents, Text$from_strn(buf, (size_t)just_read)); + buf = GC_MALLOC_ATOMIC(chunk_size); + } while (just_read > 0); + close(fd); + return contents; + } +} + +public void Path$remove(Path_t path, bool ignore_missing) +{ + const char *path_str = Text$as_c_string(path); + struct stat sb; + if (lstat(path_str, &sb) != 0) { + if (!ignore_missing) + fail("Could not remove file: %s (%s)", path_str, strerror(errno)); + } + + if ((sb.st_mode & S_IFMT) == S_IFREG || (sb.st_mode & S_IFMT) == S_IFLNK) { + if (unlink(path_str) != 0 && !ignore_missing) + fail("Could not remove file: %s (%s)", path_str, strerror(errno)); + } else if ((sb.st_mode & S_IFMT) == S_IFDIR) { + if (rmdir(path_str) != 0 && !ignore_missing) + fail("Could not remove directory: %s (%s)", path_str, strerror(errno)); + } else { + fail("Could not remove path: %s (not a file or directory)", path_str, strerror(errno)); + } +} + +public void Path$create_directory(Path_t path, int permissions) +{ + if (mkdir(Text$as_c_string(path), (mode_t)permissions) != 0) + fail("Could not create directory: %k (%s)", &path, strerror(errno)); +} + +public const TypeInfo Path$info = { + .size=sizeof(Path_t), + .align=__alignof__(Path_t), + .tag=TextInfo, + .TextInfo={.lang="Path"}, +}; + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/builtins/path.h b/builtins/path.h new file mode 100644 index 00000000..54632d42 --- /dev/null +++ b/builtins/path.h @@ -0,0 +1,35 @@ +#pragma once + +// A lang for filesystem paths + +#include <gc/cord.h> +#include <stdbool.h> +#include <stdint.h> + +#include "types.h" +#include "datatypes.h" + +#define Path_t Text_t +#define Path(text) ((Path_t)Text(text)) +#define Paths(...) ((Path_t)Texts(__VA_ARGS__)) + +Path_t Path$escape_text(Text_t text); +Path_t Path$resolved(Path_t path, Path_t relative_to); +Path_t Path$relative(Path_t path, Path_t relative_to); +bool Path$exists(Path_t path); +bool Path$is_file(Path_t path, bool follow_symlinks); +bool Path$is_directory(Path_t path, bool follow_symlinks); +bool Path$is_pipe(Path_t path, bool follow_symlinks); +bool Path$is_socket(Path_t path, bool follow_symlinks); +bool Path$is_symlink(Path_t path); + +void Path$write(Path_t path, Text_t text, int permissions); +void Path$append(Path_t path, Text_t text, int permissions); +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); + +extern const TypeInfo Path$info; + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 + diff --git a/environment.c b/environment.c index d7f6c6f7..03d713f3 100644 --- a/environment.c +++ b/environment.c @@ -249,9 +249,19 @@ env_t *new_compilation_unit(CORD *libname) {"escape_text", "Pattern$escape_text", "func(text:Text)->Pattern"}, )}, {"Path", Type(TextType, .lang="Path", .env=namespace_env(env, "Path")), "Text_t", "Text$info", TypedArray(ns_entry_t, + {"append", "Path$append", "func(path:Path, text:Text, permissions=0o644_i32)"}, + {"create_directory", "Path$create_directory", "func(path:Path, permissions=0o644_i32)"}, {"escape_text", "Path$escape_text", "func(text:Text)->Path"}, - {"resolved", "Path$resolved", "func(path:Path, relative_to=./)->Path"}, + {"exists", "Path$exists", "func(path:Path)->Bool"}, + {"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"}, + {"remove", "Path$remove", "func(path:Path, ignore_missing=no)"}, + {"resolved", "Path$resolved", "func(path:Path, relative_to=./)->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, {"escape_text", "Shell$escape_text", "func(text:Text)->Shell"}, @@ -1321,6 +1321,10 @@ PARSER(parse_path) { } case '$': { const char *interp_start = pos; + + if (pos > chunk_start) + chunk_text = CORD_asprintf("%r%.*s", chunk_text, (size_t)(pos - chunk_start), chunk_start); + if (chunk_text) { ast_t *literal = NewAST(ctx->file, chunk_start, pos, TextLiteral, .cord=chunk_text); chunks = new(ast_list_t, .ast=literal, .next=chunks); |
