aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-11-15 18:13:44 -0500
committerBruce Hill <bruce@bruce-hill.com>2025-11-15 18:14:09 -0500
commit290c72732f21f1cddb3a0f8ec3213e4ec321da14 (patch)
tree7c6c939a8d563edda5afddefcc2810d550ede0f3
parenta1884f7a85cbee5a67cf48c9e7b088fdea8b8b38 (diff)
Add Path.lines()
-rw-r--r--CHANGES.md1
-rw-r--r--api/api.md71
-rw-r--r--api/paths.md28
-rw-r--r--api/paths.yaml25
-rw-r--r--man/man3/tomo-Path.by_line.310
-rw-r--r--src/environment.c1
-rw-r--r--src/stdlib/paths.c22
-rw-r--r--src/stdlib/paths.h1
8 files changed, 142 insertions, 17 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 1e1e56d7..08fb2396 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,6 +11,7 @@
- Syntax for text literals and inline C code has been simplified somewhat.
- Syntax for tables has changed to use colons (`{k: v}`) instead of equals
(`{k=v}`).
+- Added `Path.lines()`
- Deprecated:
- Sets are no longer a separate type with separate methods.
- Instead of sets, use tables with a value type of `{KeyType:Empty}`.
diff --git a/api/api.md b/api/api.md
index eaf8da17..3af79d37 100644
--- a/api/api.md
+++ b/api/api.md
@@ -342,6 +342,49 @@ assert [x for x in Byte(2).to(5, step=2)] == [Byte(2), Byte(4)]
```
+# CString
+## CString.as_text
+
+```tomo
+CString.as_text : func(str: CString -> Text)
+```
+
+Convert a C string to Text.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+str | `CString` | The C string. | -
+
+**Return:** The C string as a Text.
+
+
+**Example:**
+```tomo
+assert CString("Hello").as_text() == "Hello"
+
+```
+## CString.join
+
+```tomo
+CString.join : func(glue: CString, pieces: [CString] -> CString)
+```
+
+Join a list of C strings together with a separator.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+glue | `CString` | The C joiner used to between elements. | -
+pieces | `[CString]` | A list of C strings to join. | -
+
+**Return:** A C string of the joined together bits.
+
+
+**Example:**
+```tomo
+assert CString(",").join([CString("a"), CString("b")]) == CString("a,b")
+
+```
+
# Int
## Int.abs
@@ -2565,14 +2608,14 @@ path | `Path` | The path of the file. | -
```tomo
# Safely handle file not being readable:
if lines := (./file.txt).by_line()
-for line in lines
-say(line.upper())
+ for line in lines
+ say(line.upper())
else
-say("Couldn't read file!")
+ say("Couldn't read file!")
# Assume the file is readable and error if that's not the case:
for line in (/dev/stdin).by_line()!
-say(line.upper())
+ say(line.upper())
```
## Path.can_execute
@@ -3019,6 +3062,26 @@ path | `Path` | The path to check. | -
assert (./link).is_symlink() == yes
```
+## Path.lines
+
+```tomo
+Path.lines : func(path: Path -> [Text]?)
+```
+
+Returns a list with the lines of text in a file or returns none if the file could not be opened.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+path | `Path` | The path of the file. | -
+
+**Return:** A list of the lines in a file or none if the file couldn't be read.
+
+
+**Example:**
+```tomo
+lines := (./file.txt).lines()!
+
+```
## Path.modified
```tomo
diff --git a/api/paths.md b/api/paths.md
index c69e91d9..07f0560b 100644
--- a/api/paths.md
+++ b/api/paths.md
@@ -108,14 +108,14 @@ path | `Path` | The path of the file. | -
```tomo
# Safely handle file not being readable:
if lines := (./file.txt).by_line()
-for line in lines
-say(line.upper())
+ for line in lines
+ say(line.upper())
else
-say("Couldn't read file!")
+ say("Couldn't read file!")
# Assume the file is readable and error if that's not the case:
for line in (/dev/stdin).by_line()!
-say(line.upper())
+ say(line.upper())
```
## Path.can_execute
@@ -562,6 +562,26 @@ path | `Path` | The path to check. | -
assert (./link).is_symlink() == yes
```
+## Path.lines
+
+```tomo
+Path.lines : func(path: Path -> [Text]?)
+```
+
+Returns a list with the lines of text in a file or returns none if the file could not be opened.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+path | `Path` | The path of the file. | -
+
+**Return:** A list of the lines in a file or none if the file couldn't be read.
+
+
+**Example:**
+```tomo
+lines := (./file.txt).lines()!
+
+```
## Path.modified
```tomo
diff --git a/api/paths.yaml b/api/paths.yaml
index 532d9c71..8fbd18dc 100644
--- a/api/paths.yaml
+++ b/api/paths.yaml
@@ -107,14 +107,31 @@ Path.by_line:
example: |
# Safely handle file not being readable:
if lines := (./file.txt).by_line()
- for line in lines
- say(line.upper())
+ for line in lines
+ say(line.upper())
else
- say("Couldn't read file!")
+ say("Couldn't read file!")
# Assume the file is readable and error if that's not the case:
for line in (/dev/stdin).by_line()!
- say(line.upper())
+ say(line.upper())
+
+Path.lines:
+ short: return the lines in a file
+ description: >
+ Returns a list with the lines of text in a file or returns none if the file
+ could not be opened.
+ return:
+ type: '[Text]?'
+ description: >
+ A list of the lines in a file or none if the file couldn't be read.
+ args:
+ path:
+ type: 'Path'
+ description: >
+ The path of the file.
+ example: |
+ lines := (./file.txt).lines()!
Path.can_execute:
short: check execute permissions
diff --git a/man/man3/tomo-Path.by_line.3 b/man/man3/tomo-Path.by_line.3
index ff7a737c..02a75c5a 100644
--- a/man/man3/tomo-Path.by_line.3
+++ b/man/man3/tomo-Path.by_line.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Path.by_line 3 2025-05-17 "Tomo man-pages"
+.TH Path.by_line 3 2025-11-15 "Tomo man-pages"
.SH NAME
Path.by_line \- iterate by line
.SH LIBRARY
@@ -31,12 +31,12 @@ An iterator that can be used to get lines from a file one at a time or none if t
.EX
# Safely handle file not being readable:
if lines := (./file.txt).by_line()
-for line in lines
-say(line.upper())
+ for line in lines
+ say(line.upper())
else
-say("Couldn't read file!")
+ say("Couldn't read file!")
# Assume the file is readable and error if that's not the case:
for line in (/dev/stdin).by_line()!
-say(line.upper())
+ say(line.upper())
.EE
diff --git a/src/environment.c b/src/environment.c
index 96595ac7..8f49f86e 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -325,6 +325,7 @@ env_t *global_env(bool source_mapping) {
{"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)"}, //
+ {"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)"}, //
diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c
index 810f98b1..704421c2 100644
--- a/src/stdlib/paths.c
+++ b/src/stdlib/paths.c
@@ -704,6 +704,28 @@ OptionalClosure_t Path$by_line(Path_t path) {
}
public
+OptionalList_t Path$lines(Path_t path) {
+ const char *path_str = Path$as_c_string(path);
+ FILE *f = fopen(path_str, "r");
+ if (f == NULL) {
+ if (errno == EMFILE || errno == ENFILE) {
+ // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will
+ // be closed by GC finalizers.
+ GC_gcollect();
+ f = fopen(path_str, "r");
+ }
+ }
+
+ if (f == NULL) return NONE_LIST;
+
+ List_t lines = {};
+ for (OptionalText_t line; (line = _next_line(&f)).tag != TEXT_NONE;) {
+ List$insert(&lines, &line, I(0), sizeof(line));
+ }
+ return lines;
+}
+
+public
List_t Path$glob(Path_t path) {
glob_t glob_result;
int status = glob(Path$as_c_string(path), GLOB_BRACE | GLOB_TILDE, NULL, &glob_result);
diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h
index ce6de1c8..3b1f3ce6 100644
--- a/src/stdlib/paths.h
+++ b/src/stdlib/paths.h
@@ -57,6 +57,7 @@ Path_t Path$sibling(Path_t path, Text_t name);
Path_t Path$with_extension(Path_t path, Text_t extension, bool replace);
Path_t Path$current_dir(void);
Closure_t Path$by_line(Path_t path);
+OptionalList_t Path$lines(Path_t path);
List_t Path$glob(Path_t path);
uint64_t Path$hash(const void *obj, const TypeInfo_t *);