aboutsummaryrefslogtreecommitdiff
path: root/src/stdlib/paths.c
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2026-01-02 15:10:48 -0500
committerBruce Hill <bruce@bruce-hill.com>2026-01-02 15:10:48 -0500
commit9653a7c2e53e2bc5e8f146a7d9ea1e71eed19e08 (patch)
tree7f026a142b4f8efcdbf517cc58adc97eb3b37cd5 /src/stdlib/paths.c
parente4d5bf73e4ad9dc51f923a32903011edfeae2908 (diff)
parentce49f93da58d007c0a52ee82e2421adfe06012f9 (diff)
Merge branch 'dev' into constructive-reals
Diffstat (limited to 'src/stdlib/paths.c')
-rw-r--r--src/stdlib/paths.c105
1 files changed, 93 insertions, 12 deletions
diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c
index ed8383fd..dcedcd03 100644
--- a/src/stdlib/paths.c
+++ b/src/stdlib/paths.c
@@ -98,7 +98,9 @@ Path_t Path$from_str(const char *str) {
}
public
-Path_t Path$from_text(Text_t text) { return Path$from_str(Text$as_c_string(text)); }
+Path_t Path$from_text(Text_t text) {
+ return Path$from_str(Text$as_c_string(text));
+}
public
Path_t Path$expand_home(Path_t path) {
@@ -324,6 +326,79 @@ Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions) {
return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
}
+typedef struct {
+ const char *path_str;
+ int fd;
+ int mode;
+ int permissions;
+} writer_data_t;
+
+static Result_t _write_bytes_to_fd(List_t bytes, bool close_file, void *userdata) {
+ writer_data_t *data = userdata;
+ if (bytes.length > 0) {
+ if (data->fd == -1) {
+ data->fd = open(data->path_str, data->mode, data->permissions);
+ if (data->fd == -1) {
+ if (errno == EMFILE || errno == ENFILE) {
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles
+ // that will be closed by GC finalizers.
+ GC_gcollect();
+ data->fd = open(data->path_str, data->mode, data->permissions);
+ }
+ if (data->fd == -1)
+ return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")");
+ }
+ }
+
+ if (bytes.stride != 1) List$compact(&bytes, 1);
+ ssize_t written = write(data->fd, bytes.data, (size_t)bytes.length);
+ if (written != (ssize_t)bytes.length)
+ return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")");
+ }
+ // After first successful write, all writes are appends
+ data->mode = (O_WRONLY | O_CREAT | O_APPEND);
+
+ if (close_file && data->fd != -1) {
+ if (close(data->fd) == -1)
+ return FailureResult("Failed to close file: ", data->path_str, " (", strerror(errno), ")");
+ data->fd = -1;
+ }
+ return SuccessResult;
+}
+
+static Result_t _write_text_to_fd(Text_t text, bool close_file, void *userdata) {
+ return _write_bytes_to_fd(Text$utf8(text), close_file, userdata);
+}
+
+static void _writer_cleanup(writer_data_t *data) {
+ if (data && data->fd != -1) {
+ close(data->fd);
+ data->fd = -1;
+ }
+}
+
+public
+Closure_t Path$byte_writer(Path_t path, bool append, int permissions) {
+ path = Path$expand_home(path);
+ const char *path_str = Path$as_c_string(path);
+ int mode = append ? (O_WRONLY | O_CREAT | O_APPEND) : (O_WRONLY | O_CREAT | O_TRUNC);
+ writer_data_t *userdata =
+ new (writer_data_t, .fd = -1, .path_str = path_str, .mode = mode, .permissions = permissions);
+ GC_register_finalizer(userdata, (void *)_writer_cleanup, NULL, NULL, NULL);
+ return (Closure_t){.fn = _write_bytes_to_fd, .userdata = userdata};
+}
+
+public
+Closure_t Path$writer(Path_t path, bool append, int permissions) {
+ path = Path$expand_home(path);
+ const char *path_str = Path$as_c_string(path);
+ int mode = append ? (O_WRONLY | O_CREAT | O_APPEND) : (O_WRONLY | O_CREAT | O_TRUNC);
+ writer_data_t *userdata =
+ new (writer_data_t, .fd = -1, .path_str = path_str, .mode = mode, .permissions = permissions);
+ GC_register_finalizer(userdata, (void *)_writer_cleanup, NULL, NULL, NULL);
+ return (Closure_t){.fn = _write_text_to_fd, .userdata = userdata};
+}
+
public
OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) {
path = Path$expand_home(path);
@@ -331,8 +406,8 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) {
int fd = open(path_str, O_RDONLY);
if (fd == -1) {
if (errno == EMFILE || errno == ENFILE) {
- // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will
- // be closed by GC finalizers.
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that
+ // will be closed by GC finalizers.
GC_gcollect();
fd = open(path_str, O_RDONLY);
}
@@ -574,7 +649,9 @@ OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes) {
}
public
-OptionalPath_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); }
+OptionalPath_t Path$write_unique(Path_t path, Text_t text) {
+ return Path$write_unique_bytes(path, Text$utf8(text));
+}
public
OptionalPath_t Path$parent(Path_t path) {
@@ -619,7 +696,7 @@ bool Path$has_extension(Path_t path, Text_t extension) {
if (extension.length == 0)
return !Text$has(Text$from(last, I(2)), Text(".")) || Text$equal_values(last, Text(".."));
- if (!Text$starts_with(extension, Text("."), NULL)) extension = Texts(Text("."), extension);
+ if (!Text$starts_with(extension, Text("."), NULL)) extension = Text$concat(Text("."), extension);
return Text$ends_with(Text$from(last, I(2)), extension, NULL);
}
@@ -638,7 +715,9 @@ Path_t Path$child(Path_t path, Text_t name) {
}
public
-Path_t Path$sibling(Path_t path, Text_t name) { return Path$child(Path$parent(path), name); }
+Path_t Path$sibling(Path_t path, Text_t name) {
+ return Path$child(Path$parent(path), name);
+}
public
OptionalPath_t Path$with_extension(Path_t path, Text_t extension, bool replace) {
@@ -705,8 +784,8 @@ OptionalClosure_t Path$by_line(Path_t path) {
FILE *f = fopen(path_str, "r");
if (f == NULL) {
if (errno == EMFILE || errno == ENFILE) {
- // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will
- // be closed by GC finalizers.
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that
+ // will be closed by GC finalizers.
GC_gcollect();
f = fopen(path_str, "r");
}
@@ -726,8 +805,8 @@ OptionalList_t Path$lines(Path_t path) {
FILE *f = fopen(path_str, "r");
if (f == NULL) {
if (errno == EMFILE || errno == ENFILE) {
- // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will
- // be closed by GC finalizers.
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that
+ // will be closed by GC finalizers.
GC_gcollect();
f = fopen(path_str, "r");
}
@@ -794,7 +873,9 @@ int Path$print(FILE *f, Path_t path) {
}
public
-const char *Path$as_c_string(Path_t path) { return String(path); }
+const char *Path$as_c_string(Path_t path) {
+ return String(path);
+}
public
Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) {
@@ -808,7 +889,7 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) {
&& (path->components.length == 0 || !Text$equal_values(*(Text_t *)(path->components.data), Text(".."))))
text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text);
- if (color) text = Texts(Text("\033[32;1m"), text, Text("\033[m"));
+ if (color) text = Text$concat(Text("\033[32;1m"), text, Text("\033[m"));
return text;
}