diff options
| -rw-r--r-- | docs/paths.md | 47 | ||||
| -rw-r--r-- | environment.c | 6 | ||||
| -rw-r--r-- | stdlib/optionals.h | 14 | ||||
| -rw-r--r-- | stdlib/paths.c | 19 | ||||
| -rw-r--r-- | stdlib/paths.h | 4 |
5 files changed, 57 insertions, 33 deletions
diff --git a/docs/paths.md b/docs/paths.md index 6408cc8a..a4437c6f 100644 --- a/docs/paths.md +++ b/docs/paths.md @@ -119,11 +119,12 @@ The base name of the file or directory. ### `by_line` **Description:** -Returns an iterator that can be used to iterate over a file one line at a time. +Returns an iterator that can be used to iterate over a file one line at a time, +or returns a null value if the file could not be opened. **Usage:** ```markdown -by_line(path: Path) -> func()->Text? +by_line(path: Path) -> (func()->Text?)? ``` **Parameters:** @@ -131,11 +132,20 @@ by_line(path: Path) -> func()->Text? - `path`: The path of the file. **Returns:** -An iterator that can be used to get lines from a file one at a time. +An iterator that can be used to get lines from a file one at a time or a null +value if the file couldn't be read. **Example:** ```markdown -for line in (/dev/stdin):by_line(): +# Safely handle file not being readable: +if lines := (./file.txt):by_line(): + for line in lines: + say(line:upper()) +else: + say("Couldn't read file!") + +# Assume the file is readable and error if that's not the case: +for line in (/dev/stdin):by_line()!: say(line:upper()) ``` @@ -414,11 +424,12 @@ The path of the parent directory. ### `read` **Description:** -Reads the contents of the file at the specified path. +Reads the contents of the file at the specified path or a null value if the +file could not be read. **Usage:** ```markdown -read(path: Path) -> Text +read(path: Path) -> Text? ``` **Parameters:** @@ -426,22 +437,29 @@ read(path: Path) -> Text - `path`: The path of the file to read. **Returns:** -The contents of the file. If the file does not exist, an error will be raised. +The contents of the file. If the file could not be read, a null value will be +returned. If the file can be read, but is not valid UTF8 data, an error will be +raised. **Example:** ```markdown -content := (./file.txt):read() +>> (./hello.txt):read() += "Hello"? + +>> (./nosuchfile.xxx):read() += !Text ``` --- ### `read_bytes` **Description:** -Reads the contents of the file at the specified path. +Reads the contents of the file at the specified path or a null value if the +file could not be read. **Usage:** ```markdown -read_bytes(path: Path) -> [Byte] +read_bytes(path: Path) -> [Byte]? ``` **Parameters:** @@ -449,11 +467,16 @@ read_bytes(path: Path) -> [Byte] - `path`: The path of the file to read. **Returns:** -The byte contents of the file. If the file does not exist, an error will be raised. +The byte contents of the file. If the file cannot be read, a null value will be +returned. **Example:** ```markdown -content := (./file.txt):read_bytes() +>> (./hello.txt):read() += [72[B], 101[B], 108[B], 108[B], 111[B]]? + +>> (./nosuchfile.xxx):read() += ![Byte] ``` --- diff --git a/environment.c b/environment.c index e0781468..6810f37a 100644 --- a/environment.c +++ b/environment.c @@ -262,7 +262,7 @@ env_t *new_compilation_unit(CORD *libname) {"append", "Path$append", "func(path:Path, text:Text, permissions=0o644[32])"}, {"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=0o644[32])"}, {"base_name", "Path$base_name", "func(path:Path)->Text"}, - {"by_line", "Path$by_line", "func(path:Path)->func()->Text?"}, + {"by_line", "Path$by_line", "func(path:Path)->(func()->Text?)?"}, {"children", "Path$children", "func(path:Path, include_hidden=no)->[Path]"}, {"create_directory", "Path$create_directory", "func(path:Path, permissions=0o644[32])"}, {"escape_int", "Int$value_as_text", "func(i:Int)->Path"}, @@ -277,8 +277,8 @@ env_t *new_compilation_unit(CORD *libname) {"is_socket", "Path$is_socket", "func(path:Path, follow_symlinks=yes)->Bool"}, {"is_symlink", "Path$is_symlink", "func(path:Path)->Bool"}, {"parent", "Path$parent", "func(path:Path)->Path"}, - {"read", "Path$read", "func(path:Path)->Text"}, - {"read_bytes", "Path$read_bytes", "func(path:Path)->[Byte]"}, + {"read", "Path$read", "func(path:Path)->Text?"}, + {"read_bytes", "Path$read_bytes", "func(path:Path)->[Byte]?"}, {"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"}, diff --git a/stdlib/optionals.h b/stdlib/optionals.h index e37d5345..b67badc4 100644 --- a/stdlib/optionals.h +++ b/stdlib/optionals.h @@ -9,13 +9,17 @@ #include "util.h" #define OptionalBool_t uint8_t +#define OptionalArray_t Array_t +#define OptionalTable_t Table_t +#define OptionalText_t Text_t +#define OptionalClosure_t Closure_t extern const OptionalBool_t NULL_BOOL; -extern const Table_t NULL_TABLE; -extern const Array_t NULL_ARRAY; -extern const Int_t NULL_INT; -extern const Closure_t NULL_CLOSURE; -extern const Text_t NULL_TEXT; +extern const OptionalTable_t NULL_TABLE; +extern const OptionalArray_t NULL_ARRAY; +extern const OptionalInt_t NULL_INT; +extern const OptionalClosure_t NULL_CLOSURE; +extern const OptionalText_t NULL_TEXT; PUREFUNC bool is_null(const void *obj, const TypeInfo *non_optional_type); Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo *type); diff --git a/stdlib/paths.c b/stdlib/paths.c index bebb9389..afdd5426 100644 --- a/stdlib/paths.c +++ b/stdlib/paths.c @@ -244,16 +244,16 @@ public void Path$append_bytes(Path_t path, Array_t bytes, int permissions) _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); } -public Array_t Path$read_bytes(Path_t path) +public OptionalArray_t Path$read_bytes(Path_t path) { path = Path$_expand_home(path); int fd = open(Text$as_c_string(path), O_RDONLY); if (fd == -1) - fail("Could not read file: %k (%s)", &path, strerror(errno)); + return NULL_ARRAY; struct stat sb; if (fstat(fd, &sb) != 0) - fail("Could not read file: %k (%s)", &path, strerror(errno)); + return NULL_ARRAY; if ((sb.st_mode & S_IFMT) == S_IFREG) { // Use memory mapping if it's a real file: const char *mem = mmap(NULL, (size_t)sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); @@ -269,7 +269,7 @@ public Array_t Path$read_bytes(Path_t path) char chunk[256]; ssize_t just_read = read(fd, chunk, sizeof(chunk)); if (just_read < 0) - fail("Failed while reading file: %k (%s)", &path, strerror(errno)); + return NULL_ARRAY; else if (just_read == 0) { if (errno == EAGAIN || errno == EINTR) continue; @@ -287,17 +287,14 @@ public Array_t Path$read_bytes(Path_t path) break; } close(fd); - - if (u8_check((uint8_t*)content, len) != NULL) - fail("File does not contain valid UTF8 data!"); - return (Array_t){.data=content, .atomic=1, .stride=1, .length=len}; } } -public Text_t Path$read(Path_t path) +public OptionalText_t Path$read(Path_t path) { Array_t bytes = Path$read_bytes(path); + if (bytes.length < 0) return NULL_TEXT; return Text$from_bytes(bytes); } @@ -480,13 +477,13 @@ static Text_t _next_line(FILE **f) return line_text; } -public Closure_t Path$by_line(Path_t path) +public OptionalClosure_t Path$by_line(Path_t path) { path = Path$_expand_home(path); FILE *f = fopen(Text$as_c_string(path), "r"); if (f == NULL) - fail("Could not read file: %k (%s)", &path, strerror(errno)); + return NULL_CLOSURE; FILE **wrapper = GC_MALLOC(sizeof(FILE*)); *wrapper = f; diff --git a/stdlib/paths.h b/stdlib/paths.h index d06d4fdc..65d98a0c 100644 --- a/stdlib/paths.h +++ b/stdlib/paths.h @@ -29,8 +29,8 @@ void Path$write(Path_t path, Text_t text, int permissions); void Path$write_bytes(Path_t path, Array_t bytes, int permissions); void Path$append(Path_t path, Text_t text, int permissions); void Path$append_bytes(Path_t path, Array_t bytes, int permissions); -Text_t Path$read(Path_t path); -Array_t Path$read_bytes(Path_t path); +OptionalText_t Path$read(Path_t path); +OptionalArray_t Path$read_bytes(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); |
