aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-09-09 02:43:15 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-09-09 02:43:15 -0400
commita306f945571c8027badd2ca673304991f628900c (patch)
tree328f8c57bec5e0b71d7c589213badb3b4d03b39c
parent1fbe2cb5dd1aa4b20411ee0c3b00310677373a55 (diff)
Add mktemp functionality
-rw-r--r--builtins/path.c59
-rw-r--r--builtins/path.h5
-rw-r--r--compile.c3
-rw-r--r--environment.c2
-rw-r--r--test/paths.tm26
5 files changed, 91 insertions, 4 deletions
diff --git a/builtins/path.c b/builtins/path.c
index d5305e30..fa15519a 100644
--- a/builtins/path.c
+++ b/builtins/path.c
@@ -28,6 +28,18 @@ PUREFUNC public Path_t Path$escape_text(Text_t text)
return (Path_t)text;
}
+PUREFUNC public Path_t Path$concat(Path_t a, Path_t b)
+{
+ Path_t path = Text$concat(a, b);
+ 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);
+
+ return path;
+}
+
public Text_t Path$resolved(Path_t path, Path_t relative_to)
{
while (Text$has(path, Pattern("/../")))
@@ -129,13 +141,13 @@ 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));
+ fail("Could not write to file: %s\n%s", path_str, 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));
+ fail("Could not write to file: %s\n%s", path_str, strerror(errno));
}
public void Path$write(Path_t path, Text_t text, int permissions)
@@ -161,7 +173,6 @@ public Text_t Path$read(Path_t path)
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);
@@ -270,6 +281,48 @@ public Array_t Path$subdirectories(Path_t path, bool include_hidden)
return _filtered_children(path, include_hidden, S_IFDIR);
}
+public Path_t Path$unique_directory(Path_t path)
+{
+ if (Text$matches(path, Pattern("~/{..}")))
+ path = Paths(Text$format("%s", getenv("HOME")), Text$slice(path, I(2), I(-1)));
+ const char *path_str = Text$as_c_string(path);
+ size_t len = strlen(path_str);
+ if (len >= PATH_MAX) fail("Path is too long: %s", path_str);
+ char buf[PATH_MAX] = {};
+ strcpy(buf, path_str);
+ if (buf[len-1] == '/')
+ buf[--len] = '\0';
+ char *created = mkdtemp(buf);
+ if (!created) fail("Failed to create temporary directory: %s (%s)", path_str, strerror(errno));
+ return Text$format("%s/", created);
+}
+
+public Text_t Path$write_unique(Path_t path, Text_t text)
+{
+ if (Text$matches(path, Pattern("~/{..}")))
+ path = Paths(Text$format("%s", getenv("HOME")), Text$slice(path, I(2), I(-1)));
+ const char *path_str = Text$as_c_string(path);
+ size_t len = strlen(path_str);
+ if (len >= PATH_MAX) fail("Path is too long: %s", path_str);
+ char buf[PATH_MAX] = {};
+ strcpy(buf, path_str);
+
+ int64_t suffixlen = 0;
+ (void)Text$find(path, Pattern("{0+!X}{end}"), I(1), &suffixlen);
+ if (suffixlen < 0) suffixlen = 0;
+
+ int fd = mkstemps(buf, suffixlen);
+ if (fd == -1)
+ fail("Could not write to unique file: %s\n%s", buf, strerror(errno));
+
+ const char *str = Text$as_c_string(text);
+ size_t write_len = strlen(str);
+ ssize_t written = write(fd, str, write_len);
+ if (written != (ssize_t)write_len)
+ fail("Could not write to file: %s\n%s", buf, strerror(errno));
+ return Text$format("%s", buf);
+}
+
public const TypeInfo Path$info = {
.size=sizeof(Path_t),
.align=__alignof__(Path_t),
diff --git a/builtins/path.h b/builtins/path.h
index ea1e99ae..0826f00d 100644
--- a/builtins/path.h
+++ b/builtins/path.h
@@ -13,7 +13,8 @@
#define Path(text) ((Path_t)Text(text))
#define Paths(...) ((Path_t)Texts(__VA_ARGS__))
-Path_t Path$escape_text(Text_t text);
+PUREFUNC Path_t Path$concat(Path_t a, Path_t b);
+PUREFUNC 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);
@@ -30,6 +31,8 @@ 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);
+Path_t Path$unique_directory(Path_t path);
+Text_t Path$write_unique(Path_t path, Text_t text);
extern const TypeInfo Path$info;
diff --git a/compile.c b/compile.c
index 9fd8dd59..c3b6325a 100644
--- a/compile.c
+++ b/compile.c
@@ -1856,6 +1856,9 @@ CORD compile(env_t *env, ast_t *ast)
case BINOP_CONCAT: {
switch (operand_t->tag) {
case TextType: {
+ const char *lang = Match(operand_t, TextType)->lang;
+ if (streq(lang, "Path"))
+ return CORD_all("Path$concat(", lhs, ", ", rhs, ")");
return CORD_all("Text$concat(", lhs, ", ", rhs, ")");
}
case ArrayType: {
diff --git a/environment.c b/environment.c
index 0f5e1826..25b0cb79 100644
--- a/environment.c
+++ b/environment.c
@@ -264,7 +264,9 @@ env_t *new_compilation_unit(CORD *libname)
{"remove", "Path$remove", "func(path:Path, ignore_missing=no)"},
{"resolved", "Path$resolved", "func(path:Path, relative_to=(./))->Path"},
{"subdirectories", "Path$children", "func(path:Path, include_hidden=no)->[Path]"},
+ {"unique_directory", "Path$unique_directory", "func(path:Path)->Path"},
{"write", "Path$write", "func(path:Path, text:Text, permissions=0o644_i32)"},
+ {"write_unique", "Path$write_unique", "func(path:Path, text:Text)->Path"},
)},
{"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"},
diff --git a/test/paths.tm b/test/paths.tm
new file mode 100644
index 00000000..bc85c31a
--- /dev/null
+++ b/test/paths.tm
@@ -0,0 +1,26 @@
+# Tests for file paths
+func main():
+ >> (/):exists()
+ = yes
+ >> (~/):exists()
+ = yes
+
+ >> tmpdir := (/tmp/tomo-test-path-XXXXXX):unique_directory()
+ >> (/tmp):subdirectories():has(tmpdir)
+ = yes
+
+ >> tmpfile := (tmpdir++(./one.txt))
+ >> tmpfile:write("Hello world")
+ >> tmpfile:append("!")
+ >> tmpfile:read()
+ = "Hello world!"
+ >> tmpdir:files():has(tmpfile)
+ = yes
+
+ >> tmpfile:remove()
+
+ >> tmpdir:files():has(tmpfile)
+ = no
+
+ >> tmpdir:remove()
+