aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-06-24 13:37:09 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-06-24 13:37:09 -0400
commit271017ba9970e4220e1bd0dc83ce146afe9222a2 (patch)
tree995233d21141f164aa5f9e1d9b7bab757124e622
parente122e5525ef044c25c2cd888bc267f7a2bd91b4f (diff)
Add Path.has_extension() and update manpages/api docs
-rw-r--r--CHANGES.md3
-rw-r--r--api/api.md77
-rw-r--r--api/paths.md28
-rw-r--r--api/paths.yaml28
-rw-r--r--api/sets.md24
-rw-r--r--api/tables.md27
-rw-r--r--man/man3/tomo-Path.has_extension.341
-rw-r--r--man/man3/tomo-Table.with_fallback.340
-rw-r--r--man/man3/tomo-Table.xor.335
-rw-r--r--src/environment.c1
-rw-r--r--src/stdlib/paths.c16
-rw-r--r--src/stdlib/paths.h1
-rw-r--r--test/paths.tm16
13 files changed, 337 insertions, 0 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 40749904..1d3c2909 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -11,11 +11,14 @@
- Added `tomo --prefix` to print the Tomo install prefix.
- Sets now support infix operations for `and`, `or`, `xor`, and `-`
- Added Path.sibling()
+- Added Path.has_extension()
- Added Table.with_fallback()
- Fixed bugs:
- Negative integers weren't converting to text properly.
- Mutation of a collection during iteration was violating value semantics.
- `extend` statements weren't properly checking that the type name was valid.
+ - Lazy recompilation wasn't happening when `use ./foo.c` was used for local
+ C/assembly files.
## v0.2
diff --git a/api/api.md b/api/api.md
index cbaa7a54..464ec58a 100644
--- a/api/api.md
+++ b/api/api.md
@@ -3010,6 +3010,34 @@ follow_symlinks | `Bool` | Whether to follow symbolic links. | `yes`
= none
```
+## Path.has_extension
+
+```tomo
+Path.has_extension : func(path: Path, extension: Text -> Bool)
+```
+
+Return whether or not a path has a given file extension.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+path | `Path` | A path. | -
+extension | `Text` | A file extension (leading `.` is optional). If empty, the check will test if the file does not have any file extension. | -
+
+**Return:** Whether or not the path has the given extension.
+
+
+**Example:**
+```tomo
+>> (/foo.txt).has_extension("txt")
+= yes
+>> (/foo.txt).has_extension(".txt")
+= yes
+>> (/foo.tar.gz).has_extension("gz")
+= yes
+>> (/foo.tar.gz).has_extension("zip")
+= no
+
+```
## Path.is_directory
```tomo
@@ -3880,6 +3908,55 @@ t.set("C", 3)
= {"A"=1, "B"=2, "C"=3}
```
+## Table.with_fallback
+
+```tomo
+Table.with_fallback : func(t: {K=V}, fallback: {K=V}? -> {K=V})
+```
+
+Return a copy of a table with a different fallback table.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+t | `{K=V}` | The table whose fallback will be replaced. | -
+fallback | `{K=V}?` | The new fallback table value. | -
+
+**Return:** The original table with a different fallback.
+
+
+**Example:**
+```tomo
+t := {"A"=1; fallback={"B"=2}}
+t2 = t.with_fallback({"B"=3"})
+>> t2["B"]
+= 3?
+t3 = t.with_fallback(none)
+>> t2["B"]
+= none
+
+```
+## Table.xor
+
+```tomo
+Table.xor : func(a: |T|, b: |T| -> |T|)
+```
+
+Return set with the elements in one, but not both of the arguments. This is also known as the symmetric difference or disjunctive union.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+a | `|T|` | The first set. | -
+b | `|T|` | The second set. | -
+
+**Return:** A set with the symmetric difference of the arguments.
+
+
+**Example:**
+```tomo
+>> |1, 2, 3|.xor(|2, 3, 4|)
+= |1, 4|
+
+```
# Text
## Text.as_c_string
diff --git a/api/paths.md b/api/paths.md
index ad6b894b..aade9b0f 100644
--- a/api/paths.md
+++ b/api/paths.md
@@ -489,6 +489,34 @@ follow_symlinks | `Bool` | Whether to follow symbolic links. | `yes`
= none
```
+## Path.has_extension
+
+```tomo
+Path.has_extension : func(path: Path, extension: Text -> Bool)
+```
+
+Return whether or not a path has a given file extension.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+path | `Path` | A path. | -
+extension | `Text` | A file extension (leading `.` is optional). If empty, the check will test if the file does not have any file extension. | -
+
+**Return:** Whether or not the path has the given extension.
+
+
+**Example:**
+```tomo
+>> (/foo.txt).has_extension("txt")
+= yes
+>> (/foo.txt).has_extension(".txt")
+= yes
+>> (/foo.tar.gz).has_extension("gz")
+= yes
+>> (/foo.tar.gz).has_extension("zip")
+= no
+
+```
## Path.is_directory
```tomo
diff --git a/api/paths.yaml b/api/paths.yaml
index 40813129..1bfe5d6d 100644
--- a/api/paths.yaml
+++ b/api/paths.yaml
@@ -463,6 +463,34 @@ Path.group:
= "root"
>> (/non/existent/file).group()
= none
+
+Path.has_extension:
+ short: check if a path has a given extension
+ description: >
+ Return whether or not a path has a given file extension.
+ return:
+ type: 'Bool'
+ description: >
+ Whether or not the path has the given extension.
+ args:
+ path:
+ type: 'Path'
+ description: >
+ A path.
+ extension:
+ type: 'Text'
+ description: >
+ A file extension (leading `.` is optional). If empty, the check will
+ test if the file does not have any file extension.
+ example: |
+ >> (/foo.txt).has_extension("txt")
+ = yes
+ >> (/foo.txt).has_extension(".txt")
+ = yes
+ >> (/foo.tar.gz).has_extension("gz")
+ = yes
+ >> (/foo.tar.gz).has_extension("zip")
+ = no
Path.is_directory:
short: check if a path is a directory
diff --git a/api/sets.md b/api/sets.md
index b64439c2..8b07cf08 100644
--- a/api/sets.md
+++ b/api/sets.md
@@ -241,3 +241,27 @@ other | `|T|` | The set of items to remove from the original set. | -
= |1|
```
+
+# Table
+## Table.xor
+
+```tomo
+Table.xor : func(a: |T|, b: |T| -> |T|)
+```
+
+Return set with the elements in one, but not both of the arguments. This is also known as the symmetric difference or disjunctive union.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+a | `|T|` | The first set. | -
+b | `|T|` | The second set. | -
+
+**Return:** A set with the symmetric difference of the arguments.
+
+
+**Example:**
+```tomo
+>> |1, 2, 3|.xor(|2, 3, 4|)
+= |1, 4|
+
+```
diff --git a/api/tables.md b/api/tables.md
index 580488f4..26bfe908 100644
--- a/api/tables.md
+++ b/api/tables.md
@@ -164,3 +164,30 @@ t.set("C", 3)
= {"A"=1, "B"=2, "C"=3}
```
+## Table.with_fallback
+
+```tomo
+Table.with_fallback : func(t: {K=V}, fallback: {K=V}? -> {K=V})
+```
+
+Return a copy of a table with a different fallback table.
+
+Argument | Type | Description | Default
+---------|------|-------------|---------
+t | `{K=V}` | The table whose fallback will be replaced. | -
+fallback | `{K=V}?` | The new fallback table value. | -
+
+**Return:** The original table with a different fallback.
+
+
+**Example:**
+```tomo
+t := {"A"=1; fallback={"B"=2}}
+t2 = t.with_fallback({"B"=3"})
+>> t2["B"]
+= 3?
+t3 = t.with_fallback(none)
+>> t2["B"]
+= none
+
+```
diff --git a/man/man3/tomo-Path.has_extension.3 b/man/man3/tomo-Path.has_extension.3
new file mode 100644
index 00000000..4556c819
--- /dev/null
+++ b/man/man3/tomo-Path.has_extension.3
@@ -0,0 +1,41 @@
+'\" t
+.\" Copyright (c) 2025 Bruce Hill
+.\" All rights reserved.
+.\"
+.TH Path.has_extension 3 2025-06-24 "Tomo man-pages"
+.SH NAME
+Path.has_extension \- check if a path has a given extension
+.SH LIBRARY
+Tomo Standard Library
+.SH SYNOPSIS
+.nf
+.BI Path.has_extension\ :\ func(path:\ Path,\ extension:\ Text\ ->\ Bool)
+.fi
+.SH DESCRIPTION
+Return whether or not a path has a given file extension.
+
+
+.SH ARGUMENTS
+
+.TS
+allbox;
+lb lb lbx lb
+l l l l.
+Name Type Description Default
+path Path A path. -
+extension Text A file extension (leading `.` is optional). If empty, the check will test if the file does not have any file extension. -
+.TE
+.SH RETURN
+Whether or not the path has the given extension.
+
+.SH EXAMPLES
+.EX
+>> (/foo.txt).has_extension("txt")
+= yes
+>> (/foo.txt).has_extension(".txt")
+= yes
+>> (/foo.tar.gz).has_extension("gz")
+= yes
+>> (/foo.tar.gz).has_extension("zip")
+= no
+.EE
diff --git a/man/man3/tomo-Table.with_fallback.3 b/man/man3/tomo-Table.with_fallback.3
new file mode 100644
index 00000000..89c78fe1
--- /dev/null
+++ b/man/man3/tomo-Table.with_fallback.3
@@ -0,0 +1,40 @@
+'\" t
+.\" Copyright (c) 2025 Bruce Hill
+.\" All rights reserved.
+.\"
+.TH Table.with_fallback 3 2025-06-24 "Tomo man-pages"
+.SH NAME
+Table.with_fallback \- return a table with a new fallback
+.SH LIBRARY
+Tomo Standard Library
+.SH SYNOPSIS
+.nf
+.BI Table.with_fallback\ :\ func(t:\ {K=V},\ fallback:\ {K=V}?\ ->\ {K=V})
+.fi
+.SH DESCRIPTION
+Return a copy of a table with a different fallback table.
+
+
+.SH ARGUMENTS
+
+.TS
+allbox;
+lb lb lbx lb
+l l l l.
+Name Type Description Default
+t {K=V} The table whose fallback will be replaced. -
+fallback {K=V}? The new fallback table value. -
+.TE
+.SH RETURN
+The original table with a different fallback.
+
+.SH EXAMPLES
+.EX
+t := {"A"=1; fallback={"B"=2}}
+t2 = t.with_fallback({"B"=3"})
+>> t2["B"]
+= 3?
+t3 = t.with_fallback(none)
+>> t2["B"]
+= none
+.EE
diff --git a/man/man3/tomo-Table.xor.3 b/man/man3/tomo-Table.xor.3
new file mode 100644
index 00000000..8d3cb8f2
--- /dev/null
+++ b/man/man3/tomo-Table.xor.3
@@ -0,0 +1,35 @@
+'\" t
+.\" Copyright (c) 2025 Bruce Hill
+.\" All rights reserved.
+.\"
+.TH Table.xor 3 2025-06-24 "Tomo man-pages"
+.SH NAME
+Table.xor \- symmetric difference
+.SH LIBRARY
+Tomo Standard Library
+.SH SYNOPSIS
+.nf
+.BI Table.xor\ :\ func(a:\ |T|,\ b:\ |T|\ ->\ |T|)
+.fi
+.SH DESCRIPTION
+Return set with the elements in one, but not both of the arguments. This is also known as the symmetric difference or disjunctive union.
+
+
+.SH ARGUMENTS
+
+.TS
+allbox;
+lb lb lbx lb
+l l l l.
+Name Type Description Default
+a |T| The first set. -
+b |T| The second set. -
+.TE
+.SH RETURN
+A set with the symmetric difference of the arguments.
+
+.SH EXAMPLES
+.EX
+>> |1, 2, 3|.xor(|2, 3, 4|)
+= |1, 4|
+.EE
diff --git a/src/environment.c b/src/environment.c
index db17da94..5fb6714a 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -308,6 +308,7 @@ env_t *global_env(bool source_mapping)
{"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?)"},
+ {"has_extension", "Path$has_extension", "func(path:Path, extension:Text -> 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_pipe", "Path$is_pipe", "func(path:Path, follow_symlinks=yes -> Bool)"},
diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c
index 5047d615..772fa1fd 100644
--- a/src/stdlib/paths.c
+++ b/src/stdlib/paths.c
@@ -609,6 +609,22 @@ public Text_t Path$extension(Path_t path, bool full)
return Text$from_str(extension);
}
+public bool Path$has_extension(Path_t path, Text_t extension)
+{
+ if (path.components.length < 2)
+ return extension.length == 0;
+
+ Text_t last = *(Text_t*)(path.components.data + path.components.stride*(path.components.length-1));
+
+ if (extension.length == 0)
+ return !Text$has(Text$from(last, I(2)), Text(".")) || Text$equal_values(last, Text(".."));
+
+ if (!Text$starts_with(extension, Text(".")))
+ extension = Texts(Text("."), extension);
+
+ return Text$ends_with(Text$from(last, I(2)), extension);
+}
+
public Path_t Path$child(Path_t path, Text_t name)
{
if (Text$has(name, Text("/")) || Text$has(name, Text(";")))
diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h
index 4f94d3e4..3a9cdef7 100644
--- a/src/stdlib/paths.h
+++ b/src/stdlib/paths.h
@@ -51,6 +51,7 @@ Path_t Path$write_unique_bytes(Path_t path, List_t bytes);
Path_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);
Path_t Path$child(Path_t path, Text_t name);
Path_t Path$sibling(Path_t path, Text_t name);
Path_t Path$with_extension(Path_t path, Text_t extension, bool replace);
diff --git a/test/paths.tm b/test/paths.tm
index 7685d089..14a15299 100644
--- a/test/paths.tm
+++ b/test/paths.tm
@@ -60,6 +60,22 @@ func main()
= "tar.gz"
>> p.extension(full=no)
= "gz"
+ >> p.has_extension("gz")
+ = yes
+ >> p.has_extension(".gz")
+ = yes
+ >> p.has_extension("tar.gz")
+ = yes
+ >> p.has_extension("txt")
+ = no
+ >> p.has_extension("")
+ = no
+ >> (./foo).has_extension("")
+ = yes
+ >> (..).has_extension("")
+ = yes
+ >> (~/.foo).has_extension("foo")
+ = no
>> (~/.foo).extension()
= ""
>> (~/foo).extension()