aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md11
-rw-r--r--api/api.md41
-rw-r--r--api/paths.md41
-rw-r--r--api/paths.yaml41
-rw-r--r--man/man3/tomo-Path.318
-rw-r--r--man/man3/tomo-Path.append.38
-rw-r--r--man/man3/tomo-Path.append_bytes.38
-rw-r--r--man/man3/tomo-Path.create_directory.36
-rw-r--r--man/man3/tomo-Path.parent.36
-rw-r--r--man/man3/tomo-Path.relative_to.37
-rw-r--r--man/man3/tomo-Path.remove.36
-rw-r--r--man/man3/tomo-Path.set_owner.36
-rw-r--r--man/man3/tomo-Path.write.36
-rw-r--r--man/man3/tomo-Path.write_bytes.36
-rw-r--r--src/environment.c27
-rw-r--r--src/environment.h1
-rw-r--r--src/stdlib/datatypes.h35
-rw-r--r--src/stdlib/optionals.h2
-rw-r--r--src/stdlib/paths.c111
-rw-r--r--src/stdlib/paths.h22
-rw-r--r--src/stdlib/result.c65
-rw-r--r--src/stdlib/result.h9
-rw-r--r--src/stdlib/tomo.h1
-rw-r--r--test/paths.tm10
24 files changed, 317 insertions, 177 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 1baafc87..51c4dcb9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -10,6 +10,17 @@
have tags with member fields and those without.
- Rename `Empty()` to `Present()` for set-like tables.
- Paths are now an `enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))`
+- Added `enum Result(Success, Failure(message:Text))` type for indicating
+ success or failure.
+- Some path methods now use `Result` return types instead of failing:
+ - `Path.append()`
+ - `Path.append_bytes()`
+ - `Path.create_directory()`
+ - `Path.remove()`
+ - `Path.set_owner()`
+ - `Path.write()`
+ - `Path.write_bytes()`
+- `Path.parent()` returns `none` if path is `(/)` (file root)
## v2025-11-30
diff --git a/api/api.md b/api/api.md
index 6a7d218b..2be5e4e9 100644
--- a/api/api.md
+++ b/api/api.md
@@ -2556,7 +2556,7 @@ assert (./not-a-file).accessed() == none
## Path.append
```tomo
-Path.append : func(path: Path, text: Text, permissions: Int32 = Int32(0o644) -> Void)
+Path.append : func(path: Path, text: Text, permissions: Int32 = Int32(0o644) -> Result)
```
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.
@@ -2567,18 +2567,18 @@ path | `Path` | The path of the file to append to. | -
text | `Text` | The text to append to the file. | -
permissions | `Int32` | The permissions to set on the file if it is being created. | `Int32(0o644)`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
```tomo
-(./log.txt).append("extra line$(\n)")
+(./log.txt).append("extra line\n")!
```
## Path.append_bytes
```tomo
-Path.append_bytes : func(path: Path, bytes: [Byte], permissions: Int32 = Int32(0o644) -> Void)
+Path.append_bytes : func(path: Path, bytes: [Byte], permissions: Int32 = Int32(0o644) -> Result)
```
Appends the given bytes 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.
@@ -2589,12 +2589,12 @@ path | `Path` | The path of the file to append to. | -
bytes | `[Byte]` | The bytes to append to the file. | -
permissions | `Int32` | The permissions to set on the file if it is being created. | `Int32(0o644)`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
```tomo
-(./log.txt).append_bytes([104, 105])
+(./log.txt).append_bytes([104, 105])!
```
## Path.base_name
@@ -2781,7 +2781,7 @@ assert (./directory).children(include_hidden=yes) == [".git", "foo.txt"]
## Path.create_directory
```tomo
-Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Void)
+Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Result)
```
Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed.
@@ -2793,7 +2793,7 @@ path | `Path` | The path of the directory to create. | -
permissions | `` | The permissions to set on the new directory. | `Int32(0o755)`
recursive | `` | If set to `yes`, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to `yes`, this function behaves like `mkdir -p`. | `yes`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -3159,7 +3159,7 @@ assert (/non/existent/file).owner() == none
## Path.parent
```tomo
-Path.parent : func(path: Path -> Path)
+Path.parent : func(path: Path -> Path?)
```
Returns the parent directory of the file or directory at the specified path.
@@ -3168,7 +3168,7 @@ Argument | Type | Description | Default
---------|------|-------------|---------
path | `Path` | The path of the file or directory. | -
-**Return:** The path of the parent directory.
+**Return:** The path of the parent directory or `none` if the path is `(/)` (the file root).
**Example:**
@@ -3232,18 +3232,19 @@ Argument | Type | Description | Default
path | `Path` | The path to convert. | -
relative_to | `` | The base path for the relative path. | `(./)`
-**Return:** The relative path.
+**Return:** A relative path from the reference point to the given path.
**Example:**
```tomo
-assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt)
+assert (./path/to/file.txt).relative_to((./path)) == (./to/file.txt)
+assert (/tmp/foo).relative_to((/tmp)) == (./foo)
```
## Path.remove
```tomo
-Path.remove : func(path: Path, ignore_missing = no -> Void)
+Path.remove : func(path: Path, ignore_missing = no -> Result)
```
Removes the file or directory at the specified path. A runtime error is raised if something goes wrong.
@@ -3253,7 +3254,7 @@ Argument | Type | Description | Default
path | `Path` | The path to remove. | -
ignore_missing | `` | Whether to ignore errors if the file or directory does not exist. | `no`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -3286,7 +3287,7 @@ assert (./path/to/file.txt).resolved(relative_to=(/foo)) == (/foo/path/to/file.t
## Path.set_owner
```tomo
-Path.set_owner : func(path: Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes -> Void)
+Path.set_owner : func(path: Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes -> Result)
```
Set the owning user and/or group for a path.
@@ -3298,7 +3299,7 @@ owner | `Text?` | If non-none, the new user to assign to be the owner of the fil
group | `Text?` | If non-none, the new group to assign to be the owner of the file. | `none`
follow_symlinks | `Bool` | Whether to follow symbolic links. | `yes`
-**Return:** Nothing. If a path does not exist, a failure will be raised.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -3374,7 +3375,7 @@ created.remove()
## Path.write
```tomo
-Path.write : func(path: Path, text: Text, permissions = Int32(0o644) -> Void)
+Path.write : func(path: Path, text: Text, permissions = Int32(0o644) -> Result)
```
Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised.
@@ -3385,7 +3386,7 @@ path | `Path` | The path of the file to write to. | -
text | `Text` | The text to write to the file. | -
permissions | `` | The permissions to set on the file if it is created. | `Int32(0o644)`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -3396,7 +3397,7 @@ permissions | `` | The permissions to set on the file if it is created. | `Int3
## Path.write_bytes
```tomo
-Path.write_bytes : func(path: Path, bytes: [Byte], permissions = Int32(0o644) -> Void)
+Path.write_bytes : func(path: Path, bytes: [Byte], permissions = Int32(0o644) -> Result)
```
Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised.
@@ -3407,7 +3408,7 @@ path | `Path` | The path of the file to write to. | -
bytes | `[Byte]` | A list of bytes to write to the file. | -
permissions | `` | The permissions to set on the file if it is created. | `Int32(0o644)`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
diff --git a/api/paths.md b/api/paths.md
index 4beabdc2..435932e3 100644
--- a/api/paths.md
+++ b/api/paths.md
@@ -28,7 +28,7 @@ assert (./not-a-file).accessed() == none
## Path.append
```tomo
-Path.append : func(path: Path, text: Text, permissions: Int32 = Int32(0o644) -> Void)
+Path.append : func(path: Path, text: Text, permissions: Int32 = Int32(0o644) -> Result)
```
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.
@@ -39,18 +39,18 @@ path | `Path` | The path of the file to append to. | -
text | `Text` | The text to append to the file. | -
permissions | `Int32` | The permissions to set on the file if it is being created. | `Int32(0o644)`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
```tomo
-(./log.txt).append("extra line$(\n)")
+(./log.txt).append("extra line\n")!
```
## Path.append_bytes
```tomo
-Path.append_bytes : func(path: Path, bytes: [Byte], permissions: Int32 = Int32(0o644) -> Void)
+Path.append_bytes : func(path: Path, bytes: [Byte], permissions: Int32 = Int32(0o644) -> Result)
```
Appends the given bytes 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.
@@ -61,12 +61,12 @@ path | `Path` | The path of the file to append to. | -
bytes | `[Byte]` | The bytes to append to the file. | -
permissions | `Int32` | The permissions to set on the file if it is being created. | `Int32(0o644)`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
```tomo
-(./log.txt).append_bytes([104, 105])
+(./log.txt).append_bytes([104, 105])!
```
## Path.base_name
@@ -253,7 +253,7 @@ assert (./directory).children(include_hidden=yes) == [".git", "foo.txt"]
## Path.create_directory
```tomo
-Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Void)
+Path.create_directory : func(path: Path, permissions = Int32(0o755), recursive = yes -> Result)
```
Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed.
@@ -265,7 +265,7 @@ path | `Path` | The path of the directory to create. | -
permissions | `` | The permissions to set on the new directory. | `Int32(0o755)`
recursive | `` | If set to `yes`, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to `yes`, this function behaves like `mkdir -p`. | `yes`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -631,7 +631,7 @@ assert (/non/existent/file).owner() == none
## Path.parent
```tomo
-Path.parent : func(path: Path -> Path)
+Path.parent : func(path: Path -> Path?)
```
Returns the parent directory of the file or directory at the specified path.
@@ -640,7 +640,7 @@ Argument | Type | Description | Default
---------|------|-------------|---------
path | `Path` | The path of the file or directory. | -
-**Return:** The path of the parent directory.
+**Return:** The path of the parent directory or `none` if the path is `(/)` (the file root).
**Example:**
@@ -704,18 +704,19 @@ Argument | Type | Description | Default
path | `Path` | The path to convert. | -
relative_to | `` | The base path for the relative path. | `(./)`
-**Return:** The relative path.
+**Return:** A relative path from the reference point to the given path.
**Example:**
```tomo
-assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt)
+assert (./path/to/file.txt).relative_to((./path)) == (./to/file.txt)
+assert (/tmp/foo).relative_to((/tmp)) == (./foo)
```
## Path.remove
```tomo
-Path.remove : func(path: Path, ignore_missing = no -> Void)
+Path.remove : func(path: Path, ignore_missing = no -> Result)
```
Removes the file or directory at the specified path. A runtime error is raised if something goes wrong.
@@ -725,7 +726,7 @@ Argument | Type | Description | Default
path | `Path` | The path to remove. | -
ignore_missing | `` | Whether to ignore errors if the file or directory does not exist. | `no`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -758,7 +759,7 @@ assert (./path/to/file.txt).resolved(relative_to=(/foo)) == (/foo/path/to/file.t
## Path.set_owner
```tomo
-Path.set_owner : func(path: Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes -> Void)
+Path.set_owner : func(path: Path, owner: Text? = none, group: Text? = none, follow_symlinks: Bool = yes -> Result)
```
Set the owning user and/or group for a path.
@@ -770,7 +771,7 @@ owner | `Text?` | If non-none, the new user to assign to be the owner of the fil
group | `Text?` | If non-none, the new group to assign to be the owner of the file. | `none`
follow_symlinks | `Bool` | Whether to follow symbolic links. | `yes`
-**Return:** Nothing. If a path does not exist, a failure will be raised.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -846,7 +847,7 @@ created.remove()
## Path.write
```tomo
-Path.write : func(path: Path, text: Text, permissions = Int32(0o644) -> Void)
+Path.write : func(path: Path, text: Text, permissions = Int32(0o644) -> Result)
```
Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised.
@@ -857,7 +858,7 @@ path | `Path` | The path of the file to write to. | -
text | `Text` | The text to write to the file. | -
permissions | `` | The permissions to set on the file if it is created. | `Int32(0o644)`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
@@ -868,7 +869,7 @@ permissions | `` | The permissions to set on the file if it is created. | `Int3
## Path.write_bytes
```tomo
-Path.write_bytes : func(path: Path, bytes: [Byte], permissions = Int32(0o644) -> Void)
+Path.write_bytes : func(path: Path, bytes: [Byte], permissions = Int32(0o644) -> Result)
```
Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised.
@@ -879,7 +880,7 @@ path | `Path` | The path of the file to write to. | -
bytes | `[Byte]` | A list of bytes to write to the file. | -
permissions | `` | The permissions to set on the file if it is created. | `Int32(0o644)`
-**Return:** Nothing.
+**Return:** Either `Success` or `Failure(reason)`.
**Example:**
diff --git a/api/paths.yaml b/api/paths.yaml
index 65d63671..02b8fbe8 100644
--- a/api/paths.yaml
+++ b/api/paths.yaml
@@ -27,9 +27,9 @@ Path.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.
return:
- type: 'Void'
+ type: 'Result'
description: >
- Nothing.
+ Either `Success` or `Failure(reason)`.
args:
path:
type: 'Path'
@@ -45,7 +45,7 @@ Path.append:
description: >
The permissions to set on the file if it is being created.
example: |
- (./log.txt).append("extra line$(\n)")
+ (./log.txt).append("extra line\n")!
Path.append_bytes:
short: append bytes to a file
@@ -53,9 +53,9 @@ Path.append_bytes:
Appends the given bytes 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.
return:
- type: 'Void'
+ type: 'Result'
description: >
- Nothing.
+ Either `Success` or `Failure(reason)`.
args:
path:
type: 'Path'
@@ -71,7 +71,7 @@ Path.append_bytes:
description: >
The permissions to set on the file if it is being created.
example: |
- (./log.txt).append_bytes([104, 105])
+ (./log.txt).append_bytes([104, 105])!
Path.base_name:
short: base name of a file
@@ -260,9 +260,9 @@ Path.create_directory:
any of the parent directories do not exist, they will be created as needed.
note: >
return:
- type: 'Void'
+ type: 'Result'
description: >
- Nothing.
+ Either `Success` or `Failure(reason)`.
args:
path:
type: 'Path'
@@ -604,9 +604,9 @@ Path.parent:
description: >
Returns the parent directory of the file or directory at the specified path.
return:
- type: 'Path'
+ type: 'Path?'
description: >
- The path of the parent directory.
+ The path of the parent directory or `none` if the path is `(/)` (the file root).
args:
path:
type: 'Path'
@@ -666,7 +666,7 @@ Path.relative_to:
return:
type: 'Path'
description: >
- The relative path.
+ A relative path from the reference point to the given path.
args:
path:
type: 'Path'
@@ -677,16 +677,17 @@ Path.relative_to:
description: >
The base path for the relative path.
example: |
- assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt)
+ assert (./path/to/file.txt).relative_to((./path)) == (./to/file.txt)
+ assert (/tmp/foo).relative_to((/tmp)) == (./foo)
Path.remove:
short: remove a file or directory
description: >
Removes the file or directory at the specified path. A runtime error is raised if something goes wrong.
return:
- type: 'Void'
+ type: 'Result'
description: >
- Nothing.
+ Either `Success` or `Failure(reason)`.
args:
path:
type: 'Path'
@@ -725,9 +726,9 @@ Path.set_owner:
description: >
Set the owning user and/or group for a path.
return:
- type: 'Void'
+ type: 'Result'
description: >
- Nothing. If a path does not exist, a failure will be raised.
+ Either `Success` or `Failure(reason)`.
args:
path:
type: 'Path'
@@ -818,9 +819,9 @@ Path.write:
it doesn't already exist. Sets the file permissions as specified. If the file
writing cannot be successfully completed, a runtime error is raised.
return:
- type: 'Void'
+ type: 'Result'
description: >
- Nothing.
+ Either `Success` or `Failure(reason)`.
args:
path:
type: 'Path'
@@ -844,9 +845,9 @@ Path.write_bytes:
it doesn't already exist. Sets the file permissions as specified. If the file
writing cannot be successfully completed, a runtime error is raised.
return:
- type: 'Void'
+ type: 'Result'
description: >
- Nothing.
+ Either `Success` or `Failure(reason)`.
args:
path:
type: 'Path'
diff --git a/man/man3/tomo-Path.3 b/man/man3/tomo-Path.3
index f383196e..ae9b6d51 100644
--- a/man/man3/tomo-Path.3
+++ b/man/man3/tomo-Path.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path 3 2025-11-29 "Tomo man-pages"
+.TH Path 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path \- a Tomo type
.SH LIBRARY
@@ -19,7 +19,7 @@ For more, see:
.TP
-.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void)
+.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Result)
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.
For more, see:
@@ -27,7 +27,7 @@ For more, see:
.TP
-.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void)
+.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Result)
Appends the given bytes 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.
For more, see:
@@ -99,7 +99,7 @@ For more, see:
.TP
-.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Void)
+.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Result)
Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed.
For more, see:
@@ -235,7 +235,7 @@ For more, see:
.TP
-.BI Path.parent\ :\ func(path:\ Path\ ->\ Path)
+.BI Path.parent\ :\ func(path:\ Path\ ->\ Path?)
Returns the parent directory of the file or directory at the specified path.
For more, see:
@@ -267,7 +267,7 @@ For more, see:
.TP
-.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Void)
+.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Result)
Removes the file or directory at the specified path. A runtime error is raised if something goes wrong.
For more, see:
@@ -283,7 +283,7 @@ For more, see:
.TP
-.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Void)
+.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Result)
Set the owning user and/or group for a path.
For more, see:
@@ -315,7 +315,7 @@ For more, see:
.TP
-.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Void)
+.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Result)
Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised.
For more, see:
@@ -323,7 +323,7 @@ For more, see:
.TP
-.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Void)
+.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Result)
Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised.
For more, see:
diff --git a/man/man3/tomo-Path.append.3 b/man/man3/tomo-Path.append.3
index b065d55b..d11169b0 100644
--- a/man/man3/tomo-Path.append.3
+++ b/man/man3/tomo-Path.append.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.append 3 2025-11-29 "Tomo man-pages"
+.TH Path.append 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.append \- append to a file
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void)
+.BI Path.append\ :\ func(path:\ Path,\ text:\ Text,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Result)
.fi
.SH DESCRIPTION
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.
@@ -27,11 +27,11 @@ text Text The text to append to the file. -
permissions Int32 The permissions to set on the file if it is being created. Int32(0o644)
.TE
.SH RETURN
-Nothing.
+Either `Success` or `Failure(reason)`.
.SH EXAMPLES
.EX
-(./log.txt).append("extra line$(\[rs]n)")
+(./log.txt).append("extra line\[rs]n")!
.EE
.SH SEE ALSO
.BR Tomo-Path (3)
diff --git a/man/man3/tomo-Path.append_bytes.3 b/man/man3/tomo-Path.append_bytes.3
index 48f795c3..c89cf08c 100644
--- a/man/man3/tomo-Path.append_bytes.3
+++ b/man/man3/tomo-Path.append_bytes.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.append_bytes 3 2025-11-29 "Tomo man-pages"
+.TH Path.append_bytes 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.append_bytes \- append bytes to a file
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Void)
+.BI Path.append_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ Result)
.fi
.SH DESCRIPTION
Appends the given bytes 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.
@@ -27,11 +27,11 @@ bytes [Byte] The bytes to append to the file. -
permissions Int32 The permissions to set on the file if it is being created. Int32(0o644)
.TE
.SH RETURN
-Nothing.
+Either `Success` or `Failure(reason)`.
.SH EXAMPLES
.EX
-(./log.txt).append_bytes([104, 105])
+(./log.txt).append_bytes([104, 105])!
.EE
.SH SEE ALSO
.BR Tomo-Path (3)
diff --git a/man/man3/tomo-Path.create_directory.3 b/man/man3/tomo-Path.create_directory.3
index 539aae36..639e3f4a 100644
--- a/man/man3/tomo-Path.create_directory.3
+++ b/man/man3/tomo-Path.create_directory.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.create_directory 3 2025-11-29 "Tomo man-pages"
+.TH Path.create_directory 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.create_directory \- make a directory
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Void)
+.BI Path.create_directory\ :\ func(path:\ Path,\ permissions\ =\ Int32(0o755),\ recursive\ =\ yes\ ->\ Result)
.fi
.SH DESCRIPTION
Creates a new directory at the specified path with the given permissions. If any of the parent directories do not exist, they will be created as needed.
@@ -27,7 +27,7 @@ permissions The permissions to set on the new directory. Int32(0o755)
recursive If set to \fByes\fR, then recursively create any parent directories if they don't exist, otherwise fail if the parent directory does not exist. When set to \fByes\fR, this function behaves like \fBmkdir -p\fR. yes
.TE
.SH RETURN
-Nothing.
+Either `Success` or `Failure(reason)`.
.SH NOTES
diff --git a/man/man3/tomo-Path.parent.3 b/man/man3/tomo-Path.parent.3
index 7af4f98f..57c4a4a5 100644
--- a/man/man3/tomo-Path.parent.3
+++ b/man/man3/tomo-Path.parent.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.parent 3 2025-11-29 "Tomo man-pages"
+.TH Path.parent 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.parent \- get parent directory
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Path.parent\ :\ func(path:\ Path\ ->\ Path)
+.BI Path.parent\ :\ func(path:\ Path\ ->\ Path?)
.fi
.SH DESCRIPTION
Returns the parent directory of the file or directory at the specified path.
@@ -25,7 +25,7 @@ Name Type Description
path Path The path of the file or directory.
.TE
.SH RETURN
-The path of the parent directory.
+The path of the parent directory or `none` if the path is `(/)` (the file root).
.SH EXAMPLES
.EX
diff --git a/man/man3/tomo-Path.relative_to.3 b/man/man3/tomo-Path.relative_to.3
index a3bf2136..d7f547fb 100644
--- a/man/man3/tomo-Path.relative_to.3
+++ b/man/man3/tomo-Path.relative_to.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.relative_to 3 2025-11-29 "Tomo man-pages"
+.TH Path.relative_to 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.relative_to \- apply a relative path to another
.SH LIBRARY
@@ -26,11 +26,12 @@ path Path The path to convert. -
relative_to The base path for the relative path. (./)
.TE
.SH RETURN
-The relative path.
+A relative path from the reference point to the given path.
.SH EXAMPLES
.EX
-assert (./path/to/file.txt).relative(relative_to=(./path)) == (./to/file.txt)
+assert (./path/to/file.txt).relative_to((./path)) == (./to/file.txt)
+assert (/tmp/foo).relative_to((/tmp)) == (./foo)
.EE
.SH SEE ALSO
.BR Tomo-Path (3)
diff --git a/man/man3/tomo-Path.remove.3 b/man/man3/tomo-Path.remove.3
index a98b4483..70ef4a56 100644
--- a/man/man3/tomo-Path.remove.3
+++ b/man/man3/tomo-Path.remove.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.remove 3 2025-11-29 "Tomo man-pages"
+.TH Path.remove 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.remove \- remove a file or directory
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Void)
+.BI Path.remove\ :\ func(path:\ Path,\ ignore_missing\ =\ no\ ->\ Result)
.fi
.SH DESCRIPTION
Removes the file or directory at the specified path. A runtime error is raised if something goes wrong.
@@ -26,7 +26,7 @@ path Path The path to remove. -
ignore_missing Whether to ignore errors if the file or directory does not exist. no
.TE
.SH RETURN
-Nothing.
+Either `Success` or `Failure(reason)`.
.SH EXAMPLES
.EX
diff --git a/man/man3/tomo-Path.set_owner.3 b/man/man3/tomo-Path.set_owner.3
index b0b24e71..1cba764a 100644
--- a/man/man3/tomo-Path.set_owner.3
+++ b/man/man3/tomo-Path.set_owner.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.set_owner 3 2025-11-29 "Tomo man-pages"
+.TH Path.set_owner 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.set_owner \- set the owner
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Void)
+.BI Path.set_owner\ :\ func(path:\ Path,\ owner:\ Text?\ =\ none,\ group:\ Text?\ =\ none,\ follow_symlinks:\ Bool\ =\ yes\ ->\ Result)
.fi
.SH DESCRIPTION
Set the owning user and/or group for a path.
@@ -28,7 +28,7 @@ group Text? If non-none, the new group to assign to be the owner of the file. n
follow_symlinks Bool Whether to follow symbolic links. yes
.TE
.SH RETURN
-Nothing. If a path does not exist, a failure will be raised.
+Either `Success` or `Failure(reason)`.
.SH EXAMPLES
.EX
diff --git a/man/man3/tomo-Path.write.3 b/man/man3/tomo-Path.write.3
index 6d9e1043..565935c5 100644
--- a/man/man3/tomo-Path.write.3
+++ b/man/man3/tomo-Path.write.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.write 3 2025-11-29 "Tomo man-pages"
+.TH Path.write 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.write \- write to a file
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Void)
+.BI Path.write\ :\ func(path:\ Path,\ text:\ Text,\ permissions\ =\ Int32(0o644)\ ->\ Result)
.fi
.SH DESCRIPTION
Writes the given text to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised.
@@ -27,7 +27,7 @@ text Text The text to write to the file. -
permissions The permissions to set on the file if it is created. Int32(0o644)
.TE
.SH RETURN
-Nothing.
+Either `Success` or `Failure(reason)`.
.SH EXAMPLES
.EX
diff --git a/man/man3/tomo-Path.write_bytes.3 b/man/man3/tomo-Path.write_bytes.3
index 6534d18e..c11e97ed 100644
--- a/man/man3/tomo-Path.write_bytes.3
+++ b/man/man3/tomo-Path.write_bytes.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.write_bytes 3 2025-11-29 "Tomo man-pages"
+.TH Path.write_bytes 3 2025-12-07 "Tomo man-pages"
.SH NAME
Path.write_bytes \- write bytes to a file
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Void)
+.BI Path.write_bytes\ :\ func(path:\ Path,\ bytes:\ [Byte],\ permissions\ =\ Int32(0o644)\ ->\ Result)
.fi
.SH DESCRIPTION
Writes the given bytes to the file at the specified path, creating the file if it doesn't already exist. Sets the file permissions as specified. If the file writing cannot be successfully completed, a runtime error is raised.
@@ -27,7 +27,7 @@ bytes [Byte] A list of bytes to write to the file. -
permissions The permissions to set on the file if it is created. Int32(0o644)
.TE
.SH RETURN
-Nothing.
+Either `Success` or `Failure(reason)`.
.SH EXAMPLES
.EX
diff --git a/src/environment.c b/src/environment.c
index bdbc8a4f..f84b6697 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -18,6 +18,8 @@ public
type_t *PATH_TYPE = NULL;
public
type_t *PRESENT_TYPE = NULL;
+public
+type_t *RESULT_TYPE = NULL;
static type_t *declare_type(env_t *env, const char *def_str) {
ast_t *ast = parse_file_str(def_str);
@@ -68,6 +70,7 @@ env_t *global_env(bool source_mapping) {
PATH_TYPE = declare_type(
env,
"enum Path(AbsolutePath(components:[Text]), RelativePath(components:[Text]), HomePath(components:[Text]))");
+ RESULT_TYPE = declare_type(env, "enum Result(Success, Failure(reason:Text))");
PRESENT_TYPE = declare_type(env, "struct Present()");
@@ -90,6 +93,7 @@ env_t *global_env(bool source_mapping) {
MAKE_TYPE("Abort", Type(AbortType), Text("void"), Text("Abort$info")),
MAKE_TYPE("Memory", Type(MemoryType), Text("void"), Text("Memory$info")),
MAKE_TYPE("Present", PRESENT_TYPE, Text("Present$$type"), Text("Present$$info")),
+ MAKE_TYPE("Result", RESULT_TYPE, Text("Result_t"), Text("Result$$info")),
MAKE_TYPE( //
"Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"),
{"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}),
@@ -293,8 +297,9 @@ env_t *global_env(bool source_mapping) {
MAKE_TYPE( //
"Path", PATH_TYPE, Text("Path_t"), Text("Path$info"), //
{"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes -> Int64?)"}, //
- {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644))"}, //
- {"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644))"}, //
+ {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, //
+ {"append_bytes", "Path$append_bytes",
+ "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, //
{"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)"}, //
@@ -306,7 +311,7 @@ env_t *global_env(bool source_mapping) {
{"concatenated_with", "Path$concat", "func(a,b:Path -> Path)"}, //
{"components", "Path$components", "func(path:Path -> [Text])"}, //
{"create_directory", "Path$create_directory",
- "func(path:Path, permissions=Int32(0o755), recursive=yes)"}, //
+ "func(path:Path, permissions=Int32(0o755), recursive=yes -> Result)"}, //
{"current_dir", "Path$current_dir", "func(->Path)"}, //
{"exists", "Path$exists", "func(path:Path -> Bool)"}, //
{"expand_home", "Path$expand_home", "func(path:Path -> Path)"}, //
@@ -324,21 +329,21 @@ env_t *global_env(bool source_mapping) {
{"lines", "Path$lines", "func(path:Path -> [Text]?)"}, //
{"modified", "Path$modified", "func(path:Path, follow_symlinks=yes -> Int64?)"}, //
{"owner", "Path$owner", "func(path:Path, follow_symlinks=yes -> Text?)"}, //
- {"parent", "Path$parent", "func(path:Path -> Path)"}, //
+ {"parent", "Path$parent", "func(path:Path -> Path?)"}, //
{"read", "Path$read", "func(path:Path -> Text?)"}, //
{"read_bytes", "Path$read_bytes", "func(path:Path, limit:Int?=none -> [Byte]?)"}, //
{"relative_to", "Path$relative_to", "func(path:Path, relative_to:Path -> Path)"}, //
- {"remove", "Path$remove", "func(path:Path, ignore_missing=no)"}, //
+ {"remove", "Path$remove", "func(path:Path, ignore_missing=no -> Result)"}, //
{"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"}, //
- {"set_owner", "Path$set_owner", //
- "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes)"}, //
+ {"set_owner", "Path$set_owner",
+ "func(path:Path, owner:Text?=none, group:Text?=none, follow_symlinks=yes -> Result)"}, //
{"sibling", "Path$sibling", "func(path:Path, name:Text -> 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=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)"}),
+ {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, //
+ {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, //
+ {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path?)"}, //
+ {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path?)"}),
MAKE_TYPE( //
"Text", TEXT_TYPE, Text("Text_t"), Text("Text$info"), //
{"as_c_string", "Text$as_c_string", "func(text:Text -> CString)"}, //
diff --git a/src/environment.h b/src/environment.h
index 88d36d42..ba036f2e 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -87,3 +87,4 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name);
extern type_t *TEXT_TYPE;
extern type_t *PATH_TYPE;
extern type_t *PRESENT_TYPE;
+extern type_t *RESULT_TYPE;
diff --git a/src/stdlib/datatypes.h b/src/stdlib/datatypes.h
index 49c4d835..3cd99f38 100644
--- a/src/stdlib/datatypes.h
+++ b/src/stdlib/datatypes.h
@@ -152,6 +152,41 @@ typedef struct {
};
} Path_t;
+#define $OptionalPath$$type Path_t
+#define OptionalPath_t Path_t
+
+typedef struct Result$Success$$struct {
+} Result$Success$$type;
+
+typedef struct {
+ Result$Success$$type value;
+ bool has_value;
+} $OptionalResult$Success$$type;
+
+typedef struct Result$Failure$$struct {
+ Text_t reason;
+} Result$Failure$$type;
+
+typedef struct {
+ Result$Failure$$type value;
+ bool has_value;
+} $OptionalResult$Failure$$type;
+
+#define Result$Success ((Result$$type){.$tag = Result$tag$Success})
+#define SuccessResult Result$Success
+#define Result$tagged$Failure(msg) ((Result$$type){.$tag = Result$tag$Failure, .Failure.reason = msg})
+#define FailureResult(...) Result$tagged$Failure(Texts(__VA_ARGS__))
+
+typedef struct Result$$struct {
+ enum { Result$tag$none, Result$tag$Success, Result$tag$Failure } $tag;
+ union {
+ Result$Success$$type Success;
+ Result$Failure$$type Failure;
+ };
+} Result$$type;
+
+#define Result_t Result$$type
+
#define OptionalBool_t uint8_t
#define OptionalList_t List_t
#define OptionalTable_t Table_t
diff --git a/src/stdlib/optionals.h b/src/stdlib/optionals.h
index 985d6611..700a4ada 100644
--- a/src/stdlib/optionals.h
+++ b/src/stdlib/optionals.h
@@ -15,7 +15,7 @@
#define NONE_TABLE ((OptionalTable_t){.entries.data = NULL})
#define NONE_CLOSURE ((OptionalClosure_t){.fn = NULL})
#define NONE_TEXT ((OptionalText_t){.tag = TEXT_NONE})
-#define NONE_PATH ((Path_t){.$tag = Path$tag$none})
+#define NONE_PATH ((OptionalPath_t){.$tag = Path$tag$none})
PUREFUNC bool is_none(const void *obj, const TypeInfo_t *non_optional_type);
PUREFUNC uint64_t Optional$hash(const void *obj, const TypeInfo_t *type);
diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c
index 9fb37ee3..ed8383fd 100644
--- a/src/stdlib/paths.c
+++ b/src/stdlib/paths.c
@@ -128,8 +128,10 @@ Path_t Path$_concat(int n, Path_t items[n]) {
public
Path_t Path$resolved(Path_t path, Path_t relative_to) {
- if (path.$tag == Path$tag$RelativePath
- && !(relative_to.$tag == Path$tag$RelativePath && relative_to.components.length == 0)) {
+ if (path.$tag == Path$tag$HomePath) {
+ return Path$expand_home(path);
+ } else if (path.$tag == Path$tag$RelativePath
+ && !(relative_to.$tag == Path$tag$RelativePath && relative_to.components.length == 0)) {
Path_t result = {
.$tag = relative_to.$tag,
.components = relative_to.components,
@@ -144,16 +146,18 @@ Path_t Path$resolved(Path_t path, Path_t relative_to) {
public
Path_t Path$relative_to(Path_t path, Path_t relative_to) {
- if (path.$tag != relative_to.$tag)
- fail("Cannot create a path relative to a different path with a mismatching type: (", path, ") relative to (",
- relative_to, ")");
+ if (path.$tag != relative_to.$tag) {
+ path = Path$resolved(path, Path$current_dir());
+ relative_to = Path$resolved(relative_to, Path$current_dir());
+ }
Path_t result = Path$tagged$RelativePath(EMPTY_LIST);
int64_t shared = 0;
- for (; shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length; shared++) {
+ while (shared < (int64_t)path.components.length && shared < (int64_t)relative_to.components.length) {
Text_t *p = (Text_t *)(path.components.data + shared * path.components.stride);
Text_t *r = (Text_t *)(relative_to.components.data + shared * relative_to.components.stride);
if (!Text$equal_values(*p, *r)) break;
+ shared += 1;
}
for (int64_t i = shared; i < (int64_t)relative_to.components.length; i++)
@@ -163,7 +167,6 @@ Path_t Path$relative_to(Path_t path, Path_t relative_to) {
Text_t *p = (Text_t *)(path.components.data + i * path.components.stride);
List$insert(&result.components, p, I(0), sizeof(Text_t));
}
- // clean_components(&result.components);
return result;
}
@@ -277,7 +280,7 @@ OptionalInt64_t Path$changed(Path_t path, bool follow_symlinks) {
return (OptionalInt64_t){.value = (int64_t)sb.st_ctime};
}
-static void _write(Path_t path, List_t bytes, int mode, int permissions) {
+static Result_t _write(Path_t path, List_t bytes, int mode, int permissions) {
path = Path$expand_home(path);
const char *path_str = Path$as_c_string(path);
int fd = open(path_str, mode, permissions);
@@ -287,36 +290,38 @@ static void _write(Path_t path, List_t bytes, int mode, int permissions) {
// be closed by GC finalizers.
GC_gcollect();
fd = open(path_str, mode, permissions);
- if (fd == -1) fail("Could not write to file: ", path_str, "\n", strerror(errno));
}
+ if (fd == -1) return FailureResult("Could not write to file: ", path_str, " (", strerror(errno), ")");
}
if (bytes.stride != 1) List$compact(&bytes, 1);
ssize_t written = write(fd, bytes.data, (size_t)bytes.length);
- if (written != (ssize_t)bytes.length) fail("Could not write to file: ", path_str, "\n", strerror(errno));
+ if (written != (ssize_t)bytes.length)
+ return FailureResult("Could not write to file: ", path_str, " (", strerror(errno), ")");
close(fd);
+ return SuccessResult;
}
public
-void Path$write(Path_t path, Text_t text, int permissions) {
+Result_t Path$write(Path_t path, Text_t text, int permissions) {
List_t bytes = Text$utf8(text);
- _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions);
+ return _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions);
}
public
-void Path$write_bytes(Path_t path, List_t bytes, int permissions) {
- _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions);
+Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions) {
+ return _write(path, bytes, O_WRONLY | O_CREAT | O_TRUNC, permissions);
}
public
-void Path$append(Path_t path, Text_t text, int permissions) {
+Result_t Path$append(Path_t path, Text_t text, int permissions) {
List_t bytes = Text$utf8(text);
- _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
+ return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
}
public
-void Path$append_bytes(Path_t path, List_t bytes, int permissions) {
- _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
+Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions) {
+ return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions);
}
public
@@ -347,8 +352,7 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) {
memcpy(content, mem, (size_t)sb.st_size);
content[sb.st_size] = '\0';
close(fd);
- if (count.small && (int64_t)sb.st_size < target_count)
- fail("Could not read ", target_count, " bytes from ", path, " (only got ", (uint64_t)sb.st_size, ")");
+ if (count.small && (int64_t)sb.st_size < target_count) return NONE_LIST;
int64_t len = count.small ? target_count : (int64_t)sb.st_size;
return (List_t){.data = content, .atomic = 1, .stride = 1, .length = (uint64_t)len};
} else {
@@ -376,8 +380,7 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) {
len += (size_t)just_read;
}
close(fd);
- if (count.small != 0 && (int64_t)len < target_count)
- fail("Could not read ", target_count, " bytes from ", path, " (only got ", (uint64_t)len, ")");
+ if (count.small != 0 && (int64_t)len < target_count) return NONE_LIST;
return (List_t){.data = content, .atomic = 1, .stride = 1, .length = (uint64_t)len};
}
}
@@ -408,23 +411,24 @@ OptionalText_t Path$group(Path_t path, bool follow_symlinks) {
}
public
-void Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) {
+Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks) {
uid_t owner_id = (uid_t)-1;
if (owner.tag == TEXT_NONE) {
struct passwd *pwd = getpwnam(Text$as_c_string(owner));
- if (pwd == NULL) fail("Not a valid user: ", owner);
+ if (pwd == NULL) return FailureResult("Not a valid user: ", owner);
owner_id = pwd->pw_uid;
}
gid_t group_id = (gid_t)-1;
if (group.tag == TEXT_NONE) {
struct group *grp = getgrnam(Text$as_c_string(group));
- if (grp == NULL) fail("Not a valid group: ", group);
+ if (grp == NULL) return FailureResult("Not a valid group: ", 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!");
+ if (result < 0) return FailureResult("Could not set owner!");
+ return SuccessResult;
}
static int _remove_files(const char *path, const struct stat *sbuf, int type, struct FTW *ftwb) {
@@ -446,29 +450,30 @@ static int _remove_files(const char *path, const struct stat *sbuf, int type, st
}
public
-void Path$remove(Path_t path, bool ignore_missing) {
+Result_t Path$remove(Path_t path, bool ignore_missing) {
path = Path$expand_home(path);
const char *path_str = Path$as_c_string(path);
struct stat sb;
if (lstat(path_str, &sb) != 0) {
- if (!ignore_missing) fail("Could not remove file: ", path_str, " (", strerror(errno), ")");
- return;
+ if (!ignore_missing) return FailureResult("Could not remove file: ", path_str, " (", strerror(errno), ")");
+ return SuccessResult;
}
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: ", path_str, " (", strerror(errno), ")");
+ return FailureResult("Could not remove file: ", path_str, " (", strerror(errno), ")");
} else if ((sb.st_mode & S_IFMT) == S_IFDIR) {
const int num_open_fd = 10;
if (nftw(path_str, _remove_files, num_open_fd, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < 0)
- fail("Could not remove directory: %s (%s)", path_str, strerror(errno));
+ return FailureResult("Could not remove directory: ", path_str, " (", strerror(errno), ")");
} else {
- fail("Could not remove path: ", path_str, " (not a file or directory)");
+ return FailureResult("Could not remove path: ", path_str, " (not a file or directory)");
}
+ return SuccessResult;
}
public
-void Path$create_directory(Path_t path, int permissions, bool recursive) {
+Result_t Path$create_directory(Path_t path, int permissions, bool recursive) {
retry:
path = Path$expand_home(path);
const char *c_path = Path$as_c_string(path);
@@ -478,19 +483,20 @@ retry:
Path$create_directory(Path$parent(path), permissions, recursive);
goto retry;
} else if (errno != EEXIST) {
- fail("Could not create directory: ", c_path, " (", strerror(errno), ")");
+ return FailureResult("Could not create directory: ", c_path, " (", strerror(errno), ")");
}
}
+ return SuccessResult;
}
-static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) {
+static OptionalList_t _filtered_children(Path_t path, bool include_hidden, mode_t filter) {
path = Path$expand_home(path);
struct dirent *dir;
List_t children = EMPTY_LIST;
const char *path_str = Path$as_c_string(path);
size_t path_len = strlen(path_str);
DIR *d = opendir(path_str);
- if (!d) fail("Could not open directory: ", path, " (", strerror(errno), ")");
+ if (!d) return NONE_LIST;
if (path_str[path_len - 1] == '/') --path_len;
@@ -511,18 +517,22 @@ static List_t _filtered_children(Path_t path, bool include_hidden, mode_t filter
}
public
-List_t Path$children(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, (mode_t)-1); }
+OptionalList_t Path$children(Path_t path, bool include_hidden) {
+ return _filtered_children(path, include_hidden, (mode_t)-1);
+}
public
-List_t Path$files(Path_t path, bool include_hidden) { return _filtered_children(path, include_hidden, S_IFREG); }
+OptionalList_t Path$files(Path_t path, bool include_hidden) {
+ return _filtered_children(path, include_hidden, S_IFREG);
+}
public
-List_t Path$subdirectories(Path_t path, bool include_hidden) {
+OptionalList_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) {
+OptionalPath_t Path$unique_directory(Path_t path) {
path = Path$expand_home(path);
const char *path_str = Path$as_c_string(path);
size_t len = strlen(path_str);
@@ -532,12 +542,12 @@ Path_t Path$unique_directory(Path_t path) {
buf[len] = '\0';
if (buf[len - 1] == '/') buf[--len] = '\0';
char *created = mkdtemp(buf);
- if (!created) fail("Failed to create temporary directory: ", path_str, " (", strerror(errno), ")");
+ if (!created) return NONE_PATH;
return Path$from_str(created);
}
public
-Path_t Path$write_unique_bytes(Path_t path, List_t bytes) {
+OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes) {
path = Path$expand_home(path);
const char *path_str = Path$as_c_string(path);
size_t len = strlen(path_str);
@@ -553,23 +563,23 @@ Path_t Path$write_unique_bytes(Path_t path, List_t bytes) {
++suffixlen;
int fd = mkstemps(buf, suffixlen);
- if (fd == -1) fail("Could not write to unique file: ", buf, "\n", strerror(errno));
+ if (fd == -1) return NONE_PATH;
if (bytes.stride != 1) List$compact(&bytes, 1);
ssize_t written = write(fd, bytes.data, (size_t)bytes.length);
- if (written != (ssize_t)bytes.length) fail("Could not write to file: ", buf, "\n", strerror(errno));
+ if (written != (ssize_t)bytes.length) fail("Could not write to file: ", buf, " (", strerror(errno), ")");
close(fd);
return Path$from_str(buf);
}
public
-Path_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); }
+OptionalPath_t Path$write_unique(Path_t path, Text_t text) { return Path$write_unique_bytes(path, Text$utf8(text)); }
public
-Path_t Path$parent(Path_t path) {
+OptionalPath_t Path$parent(Path_t path) {
if (path.$tag == Path$tag$AbsolutePath && path.components.length == 0) {
- return path;
+ return NONE_PATH;
} else if (path.components.length > 0
&& !Text$equal_values(
*(Text_t *)(path.components.data + path.components.stride * ((int64_t)path.components.length - 1)),
@@ -631,11 +641,10 @@ public
Path_t Path$sibling(Path_t path, Text_t name) { return Path$child(Path$parent(path), name); }
public
-Path_t Path$with_extension(Path_t path, Text_t extension, bool replace) {
- if (path.components.length == 0) fail("A path with no components can't have an extension!");
+OptionalPath_t Path$with_extension(Path_t path, Text_t extension, bool replace) {
+ if (path.components.length == 0) return NONE_PATH;
- if (Text$has(extension, Text("/")) || Text$has(extension, Text(";")))
- fail("Path extension has invalid characters: ", extension);
+ if (Text$has(extension, Text("/")) || Text$has(extension, Text(";"))) return NONE_PATH;
Path_t result = {
.$tag = path.$tag,
diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h
index 7a175099..4e52866e 100644
--- a/src/stdlib/paths.h
+++ b/src/stdlib/paths.h
@@ -34,24 +34,24 @@ bool Path$can_execute(Path_t path);
OptionalInt64_t Path$modified(Path_t path, bool follow_symlinks);
OptionalInt64_t Path$accessed(Path_t path, bool follow_symlinks);
OptionalInt64_t Path$changed(Path_t path, bool follow_symlinks);
-void Path$write(Path_t path, Text_t text, int permissions);
-void Path$write_bytes(Path_t path, List_t bytes, int permissions);
-void Path$append(Path_t path, Text_t text, int permissions);
-void Path$append_bytes(Path_t path, List_t bytes, int permissions);
+Result_t Path$write(Path_t path, Text_t text, int permissions);
+Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions);
+Result_t Path$append(Path_t path, Text_t text, int permissions);
+Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions);
OptionalText_t Path$read(Path_t path);
OptionalList_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);
+Result_t 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, bool recursive);
+Result_t Path$remove(Path_t path, bool ignore_missing);
+Result_t Path$create_directory(Path_t path, int permissions, bool recursive);
List_t Path$children(Path_t path, bool include_hidden);
List_t Path$files(Path_t path, bool include_hidden);
List_t Path$subdirectories(Path_t path, bool include_hidden);
-Path_t Path$unique_directory(Path_t path);
-Path_t Path$write_unique(Path_t path, Text_t text);
-Path_t Path$write_unique_bytes(Path_t path, List_t bytes);
-Path_t Path$parent(Path_t path);
+OptionalPath_t Path$unique_directory(Path_t path);
+OptionalPath_t Path$write_unique(Path_t path, Text_t text);
+OptionalPath_t Path$write_unique_bytes(Path_t path, List_t bytes);
+OptionalPath_t Path$parent(Path_t path);
Text_t Path$base_name(Path_t path);
Text_t Path$extension(Path_t path, bool full);
bool Path$has_extension(Path_t path, Text_t extension);
diff --git a/src/stdlib/result.c b/src/stdlib/result.c
new file mode 100644
index 00000000..8fd2ca1e
--- /dev/null
+++ b/src/stdlib/result.c
@@ -0,0 +1,65 @@
+// Result (Success/Failure) type info
+#include <err.h>
+#include <gc.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/param.h>
+
+#include "enums.h"
+#include "structs.h"
+#include "text.h"
+#include "util.h"
+
+public
+const TypeInfo_t Result$Success$$info = {
+ .size = sizeof(Result$Success$$type),
+ .align = __alignof__(Result$Success$$type),
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "Success",
+ .num_fields = 0,
+ },
+ .metamethods = Struct$metamethods,
+};
+
+public
+const TypeInfo_t Result$Failure$$info = {
+ .size = sizeof(Result$Failure$$type),
+ .align = __alignof__(Result$Failure$$type),
+ .tag = StructInfo,
+ .StructInfo =
+ {
+ .name = "Failure",
+ .num_fields = 1,
+ .fields =
+ (NamedType_t[1]){
+ {.name = "reason", .type = &Text$info},
+ },
+ },
+ .metamethods = Struct$metamethods,
+};
+
+public
+const TypeInfo_t Result$$info = {
+ .size = sizeof(Result_t),
+ .align = __alignof__(Result_t),
+ .tag = EnumInfo,
+ .EnumInfo =
+ {
+ .name = "Result",
+ .num_tags = 2,
+ .tags =
+ (NamedType_t[2]){
+ {
+ .name = "Success",
+ .type = &Result$Success$$info,
+ },
+ {
+ .name = "Failure",
+ .type = &Result$Failure$$info,
+ },
+ },
+ },
+ .metamethods = Enum$metamethods,
+};
diff --git a/src/stdlib/result.h b/src/stdlib/result.h
new file mode 100644
index 00000000..328480e7
--- /dev/null
+++ b/src/stdlib/result.h
@@ -0,0 +1,9 @@
+#pragma once
+
+// Result type for Success/Failure
+
+#include "types.h"
+
+extern const TypeInfo_t Result$Success$$info;
+extern const TypeInfo_t Result$Failure$$info;
+extern const TypeInfo_t Result$$info;
diff --git a/src/stdlib/tomo.h b/src/stdlib/tomo.h
index 1ff065b9..ff8f90a2 100644
--- a/src/stdlib/tomo.h
+++ b/src/stdlib/tomo.h
@@ -25,6 +25,7 @@
#include "paths.h" // IWYU pragma: export
#include "pointers.h" // IWYU pragma: export
#include "print.h" // IWYU pragma: export
+#include "result.h" // IWYU pragma: export
#include "siphash.h" // IWYU pragma: export
#include "stacktrace.h" // IWYU pragma: export
#include "structs.h" // IWYU pragma: export
diff --git a/test/paths.tm b/test/paths.tm
index d7630fc3..a0ae4384 100644
--- a/test/paths.tm
+++ b/test/paths.tm
@@ -17,8 +17,8 @@ func main()
assert optional_path == (./foo)
>> tmpfile := (tmpdir++(./one.txt))
- >> tmpfile.write("Hello world")
- >> tmpfile.append("!")
+ >> tmpfile.write("Hello world")!
+ >> tmpfile.append("!")!
assert tmpfile.read() == "Hello world!"
assert tmpfile.read_bytes()! == [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21]
assert tmpdir.files().has(tmpfile)
@@ -35,11 +35,11 @@ func main()
else
pass
- >> tmpfile.remove()
+ >> tmpfile.remove()!
assert tmpdir.files().has(tmpfile) == no
- >> tmpdir.remove()
+ >> tmpdir.remove()!
>> p := (/foo/baz.x/qux.tar.gz)
assert p.base_name() == "qux.tar.gz"
@@ -59,12 +59,12 @@ func main()
assert (~/.foo.baz.qux).extension() == "baz.qux"
- assert (/).parent() == (/)
assert (~/x/.).parent() == (~)
assert (~/x).parent() == (~)
assert (.).parent() == (..)
assert (..).parent() == (../..)
assert (../foo).parent() == (..)
+ assert (/).parent() == none
# Concatenation tests:
say("Basic relative path concatenation:")