More path stuff including some methods

This commit is contained in:
Bruce Hill 2024-09-09 01:14:33 -04:00
parent 13a9304dec
commit 11c560ebcc
4 changed files with 258 additions and 1 deletions

208
builtins/path.c Normal file
View File

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

35
builtins/path.h Normal file
View File

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

View File

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

View File

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