Add mktemp functionality

This commit is contained in:
Bruce Hill 2024-09-09 02:43:15 -04:00
parent 1fbe2cb5dd
commit a306f94557
5 changed files with 91 additions and 4 deletions

View File

@ -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),

View File

@ -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;

View File

@ -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: {

View File

@ -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"},

26
test/paths.tm Normal file
View File

@ -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()