More path stuff including some methods
This commit is contained in:
parent
13a9304dec
commit
11c560ebcc
208
builtins/path.c
Normal file
208
builtins/path.c
Normal 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
35
builtins/path.h
Normal 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
|
||||
|
@ -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"},
|
||||
|
4
parse.c
4
parse.c
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user