From 271017ba9970e4220e1bd0dc83ce146afe9222a2 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 24 Jun 2025 13:37:09 -0400 Subject: Add Path.has_extension() and update manpages/api docs --- CHANGES.md | 3 ++ api/api.md | 77 +++++++++++++++++++++++++++++++++++++ api/paths.md | 28 ++++++++++++++ api/paths.yaml | 28 ++++++++++++++ api/sets.md | 24 ++++++++++++ api/tables.md | 27 +++++++++++++ man/man3/tomo-Path.has_extension.3 | 41 ++++++++++++++++++++ man/man3/tomo-Table.with_fallback.3 | 40 +++++++++++++++++++ man/man3/tomo-Table.xor.3 | 35 +++++++++++++++++ src/environment.c | 1 + src/stdlib/paths.c | 16 ++++++++ src/stdlib/paths.h | 1 + test/paths.tm | 16 ++++++++ 13 files changed, 337 insertions(+) create mode 100644 man/man3/tomo-Path.has_extension.3 create mode 100644 man/man3/tomo-Table.with_fallback.3 create mode 100644 man/man3/tomo-Table.xor.3 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 @@ -3009,6 +3009,34 @@ follow_symlinks | `Bool` | Whether to follow symbolic links. | `yes` >> (/non/existent/file).group() = 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 @@ -3879,6 +3907,55 @@ t.set("C", 3) >> t = {"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 diff --git a/api/paths.md b/api/paths.md index ad6b894b..aade9b0f 100644 --- a/api/paths.md +++ b/api/paths.md @@ -488,6 +488,34 @@ follow_symlinks | `Bool` | Whether to follow symbolic links. | `yes` >> (/non/existent/file).group() = 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 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() -- cgit v1.2.3