aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-03-16 16:11:43 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-03-16 16:11:43 -0400
commitb0b2504a947058649ca037ad34b579bb5c43f485 (patch)
treead1213824ca185d14cd33584562fa2f7921cb27e
parent88ab24c1336ef781c3473d122d0fb521cd842390 (diff)
Update file path API and docs
-rw-r--r--docs/paths.md314
-rw-r--r--environment.c18
-rw-r--r--stdlib/paths.c65
-rw-r--r--stdlib/paths.h6
4 files changed, 372 insertions, 31 deletions
diff --git a/docs/paths.md b/docs/paths.md
index 6e8d6fc6..14ec7832 100644
--- a/docs/paths.md
+++ b/docs/paths.md
@@ -37,33 +37,68 @@ intended. Paths can be created from text with slashes using
## Path Methods
+- [`func accessed(path:Path, follow_symlinks=yes -> Moment?)`](#accessed)
- [`func append(path: Path, text: Text, permissions: Int32 = 0o644[32] -> Void)`](#append)
- [`func append_bytes(path: Path, bytes: [Byte], permissions: Int32 = 0o644[32] -> Void)`](#append_bytes)
- [`func base_name(path: Path -> Text)`](#base_name)
- [`func by_line(path: Path -> func(->Text?)?)`](#by_line)
+- [`func can_execute(path:Path -> Bool)`](#can_execute)
+- [`func can_read(path:Path -> Bool)`](#can_read)
+- [`func can_write(path:Path -> Bool)`](#can_write)
+- [`func changed(path:Path, follow_symlinks=yes -> Moment?)`](#changed)
+- [`func child(path: Path, child:Text -> Path)`](#child)
- [`func children(path: Path, include_hidden=no -> [Path])`](#children)
- [`func create_directory(path: Path, permissions=0o755[32] -> Void)`](#create_directory)
- [`func exists(path: Path -> Bool)`](#exists)
- [`func extension(path: Path, full=yes -> Text)`](#extension)
- [`func files(path: Path, include_hidden=no -> [Path])`](#files)
+- [`func from_components(components:[Text] -> Path)`](#from_components)
- [`func glob(path: Path -> [Path])`](#glob)
+- [`func group(path: Path, follow_symlinks=yes -> Text?)`](#group)
- [`func is_directory(path: Path, follow_symlinks=yes -> Bool)`](#is_directory)
- [`func is_file(path: Path, follow_symlinks=yes -> Bool)`](#is_file)
- [`func is_socket(path: Path, follow_symlinks=yes -> Bool)`](#is_socket)
- [`func is_symlink(path: Path -> Bool)`](#is_symlink)
+- [`func modified(path:Path, follow_symlinks=yes -> Moment?)`](#modified)
+- [`func owner(path: Path, follow_symlinks=yes -> Text?)`](#owner)
- [`func parent(path: Path -> Path)`](#parent)
- [`func read(path: Path -> Text?)`](#read)
- [`func read_bytes(path: Path -> [Byte]?)`](#read_bytes)
-- [`func relative(path: Path, relative_to=(./) -> Path)`](#relative)
+- [`func relative_to(path: Path, relative_to=(./) -> Path)`](#relative_to)
- [`func remove(path: Path, ignore_missing=no -> Void)`](#remove)
- [`func resolved(path: Path, relative_to=(./) -> Path)`](#resolved)
+- [`func set_owner(path:Path, owner=none:Text, group=none:Text, follow_symlinks=yes)`](#set_owner)
- [`func subdirectories(path: Path, include_hidden=no -> [Path])`](#subdirectories)
- [`func unique_directory(path: Path -> Path)`](#unique_directory)
- [`func write(path: Path, text: Text, permissions=0o644[32] -> Void)`](#write)
-- [`func write(path: Path, bytes: [Byte], permissions=0o644[32] -> Void)`](#write_bytes)
+- [`func write_bytes(path: Path, bytes: [Byte], permissions=0o644[32] -> Void)`](#write_bytes)
- [`func write_unique(path: Path, text: Text -> Path)`](#write_unique)
- [`func write_unique_bytes(path: Path, bytes: [Byte] -> Path)`](#write_unique_bytes)
+### `accessed`
+Gets the file access time of a file.
+
+```tomo
+func accessed(path:Path, follow_symlinks: Bool = yes -> Moment?)
+```
+
+- `path`: The path of the file whose access time you want.
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
+
+**Returns:**
+A [Moment](moments.md) representing when the file or directory was last
+accessed, or `none` if no such file or directory exists.
+
+**Example:**
+```tomo
+>> (./file.txt):accessed()
+= Sun 16 Mar 2025 03:43:53 PM EDT EDT : Moment?
+>> (./not-a-file):accessed()
+= none : Moment?
+```
+
+---
+
### `append`
Appends the given text to the file at the specified path, creating the file if
it doesn't already exist. Failure to write will result in a runtime error.
@@ -74,7 +109,7 @@ func append(path: Path, text: Text, permissions: Int32 = 0o644[32] -> Void)
- `path`: The path of the file to append to.
- `text`: The text to append to the file.
-- `permissions` (optional): The permissions to set on the file if it is being created (default is `0o644`).
+- `permissions`: The permissions to set on the file if it is being created (default is `0o644`).
**Returns:**
Nothing.
@@ -96,7 +131,7 @@ func append_bytes(path: Path, bytes: [Byte], permissions: Int32 = 0o644[32] -> V
- `path`: The path of the file to append to.
- `bytes`: The bytes to append to the file.
-- `permissions` (optional): The permissions to set on the file if it is being created (default is `0o644`).
+- `permissions`: The permissions to set on the file if it is being created (default is `0o644`).
**Returns:**
Nothing.
@@ -158,6 +193,127 @@ for line in (/dev/stdin):by_line()!:
---
+### `can_execute`
+Returns whether or not a file can be executed by the current user/group.
+
+```tomo
+func can_execute(path: Path -> Bool)
+```
+
+- `path`: The path of the file to check.
+
+**Returns:**
+`yes` if the file or directory exists and the current user has execute permissions, otherwise `no`.
+
+**Example:**
+```tomo
+>> (/bin/sh):can_execute()
+= yes
+>> (/usr/include/stdlib.h):can_execute()
+= no
+>> (/non/existant/file):can_execute()
+= no
+```
+
+---
+
+### `can_read`
+Returns whether or not a file can be read by the current user/group.
+
+```tomo
+func can_read(path: Path -> Bool)
+```
+
+- `path`: The path of the file to check.
+
+**Returns:**
+`yes` if the file or directory exists and the current user has read permissions, otherwise `no`.
+
+**Example:**
+```tomo
+>> (/usr/include/stdlib.h):can_read()
+= yes
+>> (/etc/shadow):can_read()
+= no
+>> (/non/existant/file):can_read()
+= no
+```
+
+---
+
+### `can_write`
+Returns whether or not a file can be written by the current user/group.
+
+```tomo
+func can_write(path: Path -> Bool)
+```
+
+- `path`: The path of the file to check.
+
+**Returns:**
+`yes` if the file or directory exists and the current user has write permissions, otherwise `no`.
+
+**Example:**
+```tomo
+>> (/tmp):can_write()
+= yes
+>> (/etc/passwd):can_write()
+= no
+>> (/non/existant/file):can_write()
+= no
+```
+
+---
+
+### `changed`
+Gets the file change time of a file.
+
+**Note:** this is the
+["ctime"](https://en.wikipedia.org/wiki/Stat_(system_call)#ctime) of a file,
+which is _not_ the file creation time.
+
+```tomo
+func changed(path:Path, follow_symlinks: Bool = yes -> Moment?)
+```
+
+- `path`: The path of the file whose change time you want.
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
+
+**Returns:**
+A [Moment](moments.md) representing when the file or directory was last
+changed, or `none` if no such file or directory exists.
+
+**Example:**
+```tomo
+>> (./file.txt):changed()
+= Sun 16 Mar 2025 03:43:53 PM EDT EDT : Moment?
+>> (./not-a-file):changed()
+= none : Moment?
+```
+
+---
+
+### `child`
+Return a path that is a child of another path.
+
+```tomo
+func child(path: Path, child: Text -> [Path])
+```
+
+- `path`: The path of a directory.
+- `child`: The name of a child file or directory.
+
+**Returns:**
+A new path representing the child.
+
+**Example:**
+```tomo
+>> (./directory):child("file.txt")
+= (./directory/file.txt)
+```
+
+---
+
### `children`
Returns a list of children (files and directories) within the directory at the specified path. Optionally includes hidden files.
@@ -166,7 +322,7 @@ func children(path: Path, include_hidden=no -> [Path])
```
- `path`: The path of the directory.
-- `include_hidden` (optional): Whether to include hidden files, which start with a `.` (default is `no`).
+- `include_hidden`: Whether to include hidden files, which start with a `.` (default is `no`).
**Returns:**
A list of paths for the children.
@@ -188,7 +344,7 @@ func create_directory(path: Path, permissions=0o755[32] -> Void)
```
- `path`: The path of the directory to create.
-- `permissions` (optional): The permissions to set on the new directory (default is `0o644`).
+- `permissions`: The permissions to set on the new directory (default is `0o644`).
**Returns:**
Nothing.
@@ -224,11 +380,11 @@ func exists(path: Path -> Bool)
Returns the file extension of the file at the specified path. Optionally returns the full extension.
```tomo
-func extension(path: Path, full=yes -> Text)
+func extension(path: Path, full:Bool = yes -> Text)
```
- `path`: The path of the file.
-- `full` (optional): Whether to return everything after the first `.` in the
+- `full`: Whether to return everything after the first `.` in the
base name, or only the last part of the extension (default is `yes`).
**Returns:**
@@ -253,11 +409,11 @@ no file extension.
Returns a list of files within the directory at the specified path. Optionally includes hidden files.
```tomo
-func files(path: Path, include_hidden=no -> [Path])
+func files(path: Path, include_hidden: Bool = no -> [Path])
```
- `path`: The path of the directory.
-- `include_hidden` (optional): Whether to include hidden files (default is `no`).
+- `include_hidden`: Whether to include hidden files (default is `no`).
**Returns:**
A list of file paths.
@@ -270,6 +426,30 @@ A list of file paths.
---
+### `from_components`
+Returns a path built from an array of path components.
+
+```tomo
+func from_components(components: [Text] -> Path)
+```
+
+- `components`: An array of path components.
+
+**Returns:**
+A path representing the given components.
+
+**Example:**
+```tomo
+>> Path.from_components(["/", "usr", "include"])
+= /usr/include
+>> Path.from_components(["foo.txt"])
+= ./foo.txt
+>> Path.from_components(["~", ".local"])
+= ~/.local
+```
+
+---
+
### `glob`
Perform a globbing operation and return an array of matching paths. Some glob
specific details:
@@ -312,6 +492,29 @@ A list of file paths that match the glob.
---
+### `group`
+Get the owning group of a file or directory.
+
+```tomo
+func group(path: Path, follow_symlinks: Bool = yes -> Text?)
+```
+
+- `path`: The path whose owning group to get.
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
+
+**Returns:**
+The name of the group which owns the file or directory, or `none` if the path does not exist.
+
+**Example:**
+```tomo
+>> (/bin):group()
+= "root"
+>> (/non/existent/file):group()
+= none:Text
+```
+
+---
+
### `is_directory`
Checks if the path represents a directory. Optionally follows symbolic links.
@@ -320,7 +523,7 @@ func is_directory(path: Path, follow_symlinks=yes -> Bool)
```
- `path`: The path to check.
-- `follow_symlinks` (optional): Whether to follow symbolic links (default is `yes`).
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
**Returns:**
`True` if the path is a directory, `False` otherwise.
@@ -344,7 +547,7 @@ func is_file(path: Path, follow_symlinks=yes -> Bool)
```
- `path`: The path to check.
-- `follow_symlinks` (optional): Whether to follow symbolic links (default is `yes`).
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
**Returns:**
`True` if the path is a file, `False` otherwise.
@@ -368,7 +571,7 @@ func is_socket(path: Path, follow_symlinks=yes -> Bool)
```
- `path`: The path to check.
-- `follow_symlinks` (optional): Whether to follow symbolic links (default is `yes`).
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
**Returns:**
`True` if the path is a socket, `False` otherwise.
@@ -401,6 +604,53 @@ func is_symlink(path: Path -> Bool)
---
+### `modified`
+Gets the file modification time of a file.
+
+```tomo
+func modified(path:Path, follow_symlinks: Bool = yes -> Moment?)
+```
+
+- `path`: The path of the file whose modification time you want.
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
+
+**Returns:**
+A [Moment](moments.md) representing when the file or directory was last
+modified, or `none` if no such file or directory exists.
+
+**Example:**
+```tomo
+>> (./file.txt):modified()
+= Sun 16 Mar 2025 03:43:53 PM EDT EDT : Moment?
+>> (./not-a-file):modified()
+= none : Moment?
+```
+
+---
+
+### `owner`
+Get the owning user of a file or directory.
+
+```tomo
+func owner(path: Path, follow_symlinks: Bool = yes -> Text?)
+```
+
+- `path`: The path whose owner to get.
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
+
+**Returns:**
+The name of the user who owns the file or directory, or `none` if the path does not exist.
+
+**Example:**
+```tomo
+>> (/bin):owner()
+= "root"
+>> (/non/existent/file):owner()
+= none:Text
+```
+
+---
+
### `parent`
Returns the parent directory of the file or directory at the specified path.
@@ -471,15 +721,15 @@ returned.
---
-### `relative`
+### `relative_to`
Returns the path relative to a given base path. By default, the base path is the current directory.
```tomo
-func relative(path: Path, relative_to=(./) -> Path)
+func relative_to(path: Path, relative_to=(./) -> Path)
```
- `path`: The path to convert.
-- `relative_to` (optional): The base path for the relative path (default is `./`).
+- `relative_to`: The base path for the relative path (default is `./`).
**Returns:**
The relative path.
@@ -500,7 +750,7 @@ func remove(path: Path, ignore_missing=no -> Void)
```
- `path`: The path to remove.
-- `ignore_missing` (optional): Whether to ignore errors if the file or directory does not exist (default is `no`).
+- `ignore_missing`: Whether to ignore errors if the file or directory does not exist (default is `no`).
**Returns:**
Nothing.
@@ -520,7 +770,7 @@ func resolved(path: Path, relative_to=(./) -> Path)
```
- `path`: The path to resolve.
-- `relative_to` (optional): The base path for resolution (default is `./`).
+- `relative_to`: The base path for resolution (default is `./`).
**Returns:**
The resolved absolute path.
@@ -536,6 +786,28 @@ The resolved absolute path.
---
+### `set_owner`
+Set the owning user and/or group for a path.
+
+```tomo
+func set_owner(path:Path, owner: Text? = none:Text, group: Text? = none:Text, follow_symlinks: Bool = yes)
+```
+
+- `path`: The path to change the permissions for.
+- `owner`: If non-none, the new user to assign to be the owner of the file (default: `none`).
+- `group`: If non-none, the new group to assign to be the owner of the file (default: `none`).
+- `follow_symlinks`: Whether to follow symbolic links (default is `yes`).
+
+**Returns:**
+Nothing. If a path does not exist, a failure will be raised.
+
+**Example:**
+```tomo
+(./file.txt):set_owner(owner="root", group="wheel")
+```
+
+---
+
### `subdirectories`
Returns a list of subdirectories within the directory at the specified path. Optionally includes hidden subdirectories.
@@ -544,7 +816,7 @@ func subdirectories(path: Path, include_hidden=no -> [Path])
```
- `path`: The path of the directory.
-- `include_hidden` (optional): Whether to include hidden subdirectories (default is `no`).
+- `include_hidden`: Whether to include hidden subdirectories (default is `no`).
**Returns:**
A list of subdirectory paths.
@@ -595,7 +867,7 @@ func write(path: Path, text: Text, permissions=0o644[32] -> Void)
- `path`: The path of the file to write to.
- `text`: The text to write to the file.
-- `permissions` (optional): The permissions to set on the file if it is created (default is `0o644`).
+- `permissions`: The permissions to set on the file if it is created (default is `0o644`).
**Returns:**
Nothing.
@@ -618,7 +890,7 @@ func write(path: Path, bytes: [Byte], permissions=0o644[32] -> Void)
- `path`: The path of the file to write to.
- `bytes`: An array of bytes to write to the file.
-- `permissions` (optional): The permissions to set on the file if it is created (default is `0o644`).
+- `permissions`: The permissions to set on the file if it is created (default is `0o644`).
**Returns:**
Nothing.
diff --git a/environment.c b/environment.c
index 7212d262..78525b0f 100644
--- a/environment.c
+++ b/environment.c
@@ -334,45 +334,45 @@ env_t *new_compilation_unit(CORD libname)
{"year", "Moment$year", "func(moment:Moment,timezone=none:Text -> Int)"},
)},
{"Path", PATH_TYPE, "Path_t", "Path$info", TypedArray(ns_entry_t,
+ {"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Moment?)"},
{"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644))"},
{"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"},
{"base_name", "Path$base_name", "func(path:Path -> Text)"},
{"by_line", "Path$by_line", "func(path:Path -> func(->Text?)?)"},
+ {"can_execute", "Path$can_execute", "func(path:Path -> Bool)"},
+ {"can_read", "Path$can_read", "func(path:Path -> Bool)"},
+ {"can_write", "Path$can_write", "func(path:Path -> Bool)"},
+ {"changed", "Path$changed", "func(path:Path, follow_symlinks=yes -> Moment?)"},
{"child", "Path$with_component", "func(path:Path, child:Text -> Path)"},
{"children", "Path$children", "func(path:Path, include_hidden=no -> [Path])"},
- {"components", "Path$components", "func(path:Path -> [Text])"},
{"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"},
{"create_directory", "Path$create_directory", "func(path:Path, permissions=Int32(0o755))"},
- {"escape_int", "Int$value_as_text", "func(i:Int -> Path)"},
- {"escape_path", "Path$escape_path", "func(path:Path -> Path)"},
- {"escape_text", "Path$escape_text", "func(text:Text -> Path)"},
{"exists", "Path$exists", "func(path:Path -> Bool)"},
{"extension", "Path$extension", "func(path:Path, full=yes -> Text)"},
{"files", "Path$children", "func(path:Path, include_hidden=no -> [Path])"},
{"from_components", "Path$from_components", "func(components:[Text] -> Path)"},
{"glob", "Path$glob", "func(path:Path -> [Path])"},
+ {"group", "Path$group", "func(path:Path, follow_symlinks=yes -> Text?)"},
{"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_pipe", "Path$is_pipe", "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)"},
+ {"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Moment?)"},
+ {"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"},
{"parent", "Path$parent", "func(path:Path -> Path)"},
{"read", "Path$read", "func(path:Path -> Text?)"},
{"read_bytes", "Path$read_bytes", "func(path:Path, limit=none:Int -> [Byte]?)"},
- {"relative", "Path$relative", "func(path:Path, relative_to=(./) -> Path)"},
{"relative_to", "Path$relative_to", "func(path:Path, relative_to:Path -> Path)"},
{"remove", "Path$remove", "func(path:Path, ignore_missing=no)"},
{"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"},
+ {"set_owner", "Path$set_owner", "func(path:Path, owner=none:Text, group=none:Text, follow_symlinks=yes)"},
{"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=Int32(0o644))"},
{"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"},
{"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path)"},
{"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path)"},
-
- {"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Moment?)"},
- {"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Moment?)"},
- {"changed", "Path$changed", "func(path:Path, follow_symlinks=yes -> Moment?)"},
)},
// RNG must come after Path so we can read bytes from /dev/urandom
{"RNG", RNG_TYPE, "RNG_t", "RNG", TypedArray(ns_entry_t,
diff --git a/stdlib/paths.c b/stdlib/paths.c
index 62908151..813c2004 100644
--- a/stdlib/paths.c
+++ b/stdlib/paths.c
@@ -4,6 +4,8 @@
#include <fcntl.h>
#include <gc.h>
#include <glob.h>
+#include <grp.h>
+#include <pwd.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
@@ -215,6 +217,27 @@ public bool Path$is_symlink(Path_t path)
return (sb.st_mode & S_IFMT) == S_IFLNK;
}
+public bool Path$can_read(Path_t path)
+{
+ path = Path$_expand_home(path);
+ const char *path_str = Path$as_c_string(path);
+ return (euidaccess(path_str, R_OK) == 0);
+}
+
+public bool Path$can_write(Path_t path)
+{
+ path = Path$_expand_home(path);
+ const char *path_str = Path$as_c_string(path);
+ return (euidaccess(path_str, W_OK) == 0);
+}
+
+public bool Path$can_execute(Path_t path)
+{
+ path = Path$_expand_home(path);
+ const char *path_str = Path$as_c_string(path);
+ return (euidaccess(path_str, X_OK) == 0);
+}
+
public OptionalMoment_t Path$modified(Path_t path, bool follow_symlinks)
{
struct stat sb;
@@ -340,6 +363,45 @@ public OptionalText_t Path$read(Path_t path)
return Text$from_bytes(bytes);
}
+public OptionalText_t Path$owner(Path_t path, bool follow_symlinks)
+{
+ struct stat sb;
+ int status = path_stat(path, follow_symlinks, &sb);
+ if (status != 0) return NONE_TEXT;
+ struct passwd *pw = getpwuid(sb.st_uid);
+ return pw ? Text$from_str(pw->pw_name) : NONE_TEXT;
+}
+
+public OptionalText_t Path$group(Path_t path, bool follow_symlinks)
+{
+ struct stat sb;
+ int status = path_stat(path, follow_symlinks, &sb);
+ if (status != 0) return NONE_TEXT;
+ struct group *gr = getgrgid(sb.st_uid);
+ return gr ? Text$from_str(gr->gr_name) : NONE_TEXT;
+}
+
+public void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks)
+{
+ uid_t owner_id = (uid_t)-1;
+ if (owner.length >= 0) {
+ struct passwd *pwd = getpwnam(Text$as_c_string(owner));
+ if (pwd == NULL) fail("Not a valid user: %k", &owner);
+ owner_id = pwd->pw_uid;
+ }
+
+ gid_t group_id = (gid_t)-1;
+ if (group.length >= 0) {
+ struct group *grp = getgrnam(Text$as_c_string(group));
+ if (grp == NULL) fail("Not a valid group: %k", &group);
+ group_id = grp->gr_gid;
+ }
+ const char *path_str = Path$as_c_string(path);
+ int result = follow_symlinks ? chown(path_str, owner_id, group_id) : lchown(path_str, owner_id, group_id);
+ if (result < 0)
+ fail("Could not set owner!");
+}
+
public void Path$remove(Path_t path, bool ignore_missing)
{
path = Path$_expand_home(path);
@@ -515,6 +577,7 @@ public Path_t Path$with_component(Path_t path, Text_t component)
};
ARRAY_INCREF(result.components);
Array$insert(&result.components, &component, I(0), sizeof(Text_t));
+ clean_components(&result.components);
return result;
}
@@ -690,7 +753,7 @@ public Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type)
text = Text$concat(path->components.length > 0 ? Text("~/") : Text("~"), text);
else if (path->type == PATH_ABSOLUTE)
text = Text$concat(Text("/"), text);
- else if (path->type == PATH_RELATIVE && path->components.length > 0 && !Text$equal_values(*(Text_t*)(path->components.data), Text("..")))
+ else if (path->type == PATH_RELATIVE && (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)
diff --git a/stdlib/paths.h b/stdlib/paths.h
index 3efe10da..b971cdf5 100644
--- a/stdlib/paths.h
+++ b/stdlib/paths.h
@@ -24,6 +24,9 @@ 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);
+bool Path$can_read(Path_t path);
+bool Path$can_write(Path_t path);
+bool Path$can_execute(Path_t path);
OptionalMoment_t Path$modified(Path_t path, bool follow_symlinks);
OptionalMoment_t Path$accessed(Path_t path, bool follow_symlinks);
OptionalMoment_t Path$changed(Path_t path, bool follow_symlinks);
@@ -33,6 +36,9 @@ void Path$append(Path_t path, Text_t text, int permissions);
void Path$append_bytes(Path_t path, Array_t bytes, int permissions);
OptionalText_t Path$read(Path_t path);
OptionalArray_t Path$read_bytes(Path_t path, OptionalInt_t limit);
+void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks);
+OptionalText_t Path$owner(Path_t path, bool follow_symlinks);
+OptionalText_t Path$group(Path_t path, bool follow_symlinks);
void Path$remove(Path_t path, bool ignore_missing);
void Path$create_directory(Path_t path, int permissions);
Array_t Path$children(Path_t path, bool include_hidden);