diff --git a/docs/src/modules/fs.md b/docs/src/modules/fs.md
index 68facb7..e5cd6c0 100644
--- a/docs/src/modules/fs.md
+++ b/docs/src/modules/fs.md
@@ -1,10 +1,10 @@
---
-description: "Filesystem I/O and metadata operations."
+description: "Filesystem I/O, metadata, and filesystem path operations."
---
# `fs`
-Filesystem I/O and metadata operations.
+Filesystem I/O, metadata, and filesystem path operations.
## Usage
@@ -12,21 +12,520 @@ Filesystem I/O and metadata operations.
fs = require "mods.fs"
fs.mkdir("tmp/cache/app", true)
+fs.write_text("tmp/cache/app/data.txt", "hello")
+print(fs.read_text("tmp/cache/app/data.txt")) --> "hello"
```
## Functions
-| Function | Description |
-| ---------- | -------------------------------------------------- |
-| `getcwd` | Alias of `lfs.currentdir` |
-| `isblock` | Alias of [`mods.is.block`](/modules/is#fn-block) |
-| `ischar` | Alias of [`mods.is.char`](/modules/is#fn-char) |
-| `isdevice` | Alias of [`mods.is.device`](/modules/is#fn-device) |
-| `isdir` | Alias of [`mods.is.dir`](/modules/is#fn-dir) |
-| `isfifo` | Alias of [`mods.is.fifo`](/modules/is#fn-fifo) |
-| `isfile` | Alias of [`mods.is.file`](/modules/is#fn-file) |
-| `islink` | Alias of [`mods.is.link`](/modules/is#fn-link) |
-| `issocket` | Alias of [`mods.is.socket`](/modules/is#fn-socket) |
-| `lstat` | Alias of `lfs.symlinkattributes` |
-| `rmdir` | Alias of `lfs.rmdir` |
-| `stat` | Alias of `lfs.attributes` |
+**Reading**:
+
+| Function | Description |
+| ------------------------------------- | -------------------------------------- |
+| [`read_bytes(path)`](#fn-read-bytes) | Read full file in binary mode. |
+| [`read_text(path)`](#fn-read-text) | Read full file in text mode. |
+| [`dir(path, opts?)`](#fn-dir) | Iterator over items in `path`. |
+| [`listdir(path, opts?)`](#fn-listdir) | Return direct children of a directory. |
+
+**Writing**:
+
+| Function | Description |
+| -------------------------------------------- | ----------------------------------------------------------------------------- |
+| [`write_bytes(path, data)`](#fn-write-bytes) | Write full file in binary mode. |
+| [`write_text(path, data)`](#fn-write-text) | Write full file in text mode. |
+| [`touch(path)`](#fn-touch) | Create file if missing without truncating, or update timestamps if it exists. |
+| [`rename(oldname, newname)`](#fn-rename) | Rename or move a filesystem entry. |
+| [`rm(path, recursive?)`](#fn-rm) | Remove a filesystem entry, or a directory tree when `recursive` is `true`. |
+| [`mkdir(path, parents?)`](#fn-mkdir) | Create a directory. |
+| [`cp(src, dst)`](#fn-cp) | Copy a file or directory tree. |
+
+**Metadata**:
+
+| Function | Description |
+| ------------------------------------------ | ---------------------------------------------------------------------------------- |
+| [`getsize(path)`](#fn-getsize) | Return file size in bytes. |
+| [`getatime(path)`](#fn-getatime) | Return last access time. |
+| [`getmtime(path)`](#fn-getmtime) | Return last modification time. |
+| [`getctime(path)`](#fn-getctime) | Return metadata change time. |
+| [`lstat(path)`](#fn-lstat) | Return symlink-aware file attributes. |
+| [`stat(path)`](#fn-stat) | Return file attributes. |
+| [`samefile(path_a, path_b)`](#fn-samefile) | Return whether two paths refer to the same file, or `nil` and an error on failure. |
+
+**Existence Checks**:
+
+| Function | Description |
+| ------------------------------ | ------------------------------------------------------------ |
+| [`exists(path)`](#fn-exists) | Return `true` when a path exists. |
+| [`lexists(path)`](#fn-lexists) | Return `true` when a path exists without following symlinks. |
+
+### Reading
+
+
+
+#### `read_bytes(path)`
+
+Read full file in binary mode.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `body` (`string?`): File contents read in binary mode, or `nil` on failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.read_bytes("README.md")
+```
+
+
+
+#### `read_text(path)`
+
+Read full file in text mode.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `body` (`string?`): File contents read in text mode, or `nil` on failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.read_text("README.md")
+```
+
+
+
+#### `dir(path, opts?)`
+
+Iterator over items in `path`.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `opts?` (`{hidden?:boolean,`): recursive?:boolean, follow_links?:boolean,
+ type?:string} Optional traversal options.
+
+**Return**:
+
+- `prev?:string):basename:string?,` (`(fun(state:table,`):
+ type:"file"|"directory"|"link"|"fifo"|"socket"|"char"|"block"|"unknown"?)?
+ iterator Iterator, or `nil` on failure.
+- `state` (`table|string`): Iterator state on success, or error message on
+ failure.
+
+**Example**:
+
+```lua
+for name, type in fs.dir(path.cwd(), { recursive = true }) do
+ print(name, type)
+end
+```
+
+
+
+#### `listdir(path, opts?)`
+
+Return direct children of a directory.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `opts?` (`{hidden?:boolean,`): recursive?:boolean, follow_links?:boolean,
+ type?:string} Optional traversal options.
+
+**Return**:
+
+- `paths` (`mods.List?`): Direct child paths.
+- `err` (`string?`): Error message when traversal setup fails.
+
+**Example**:
+
+```lua
+fs.listdir("src")
+```
+
+### Writing
+
+
+
+#### `write_bytes(path, data)`
+
+Write full file in binary mode.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `data` (`string`): Input data.
+
+**Return**:
+
+- `written` (`true?`): `true` when writing succeeds, or `nil` on failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.write_bytes("tmp.bin", "abc") --> true, nil
+```
+
+
+
+#### `write_text(path, data)`
+
+Write full file in text mode.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `data` (`string`): Input data.
+
+**Return**:
+
+- `written` (`true?`): `true` when writing succeeds, or `nil` on failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.write_text("tmp.txt", "abc") --> true, nil
+```
+
+
+
+#### `touch(path)`
+
+Create file if missing without truncating, or update timestamps if it exists.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `touched` (`true?`): `true` when the file exists after touch, or `nil` on
+ failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.touch("tmp.txt") --> true, nil
+```
+
+
+
+#### `rename(oldname, newname)`
+
+Rename or move a filesystem entry.
+
+**Parameters**:
+
+- `oldname` (`string`): Existing path.
+- `newname` (`string`): Replacement path.
+
+**Return**:
+
+- `renamed` (`true?`): `true` when the rename succeeds, or `nil` on failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.rename("old.txt", "new.txt")
+```
+
+> [!NOTE] This is an alias for `os.rename`.
+
+
+
+#### `rm(path, recursive?)`
+
+Remove a filesystem entry, or a directory tree when `recursive` is `true`.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `recursive?` (`boolean`): Remove a directory tree recursively when `true`.
+
+**Return**:
+
+- `removed` (`true?`): `true` when removal succeeds, or `nil` on failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.rm("tmp.txt") --> true, nil
+fs.rm("tmp/cache", true) --> true, nil
+```
+
+
+
+#### `mkdir(path, parents?)`
+
+Create a directory.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `parents?` (`boolean`): Create missing parent directories when `true`.
+
+**Return**:
+
+- `created` (`true?`): `true` when directory creation succeeds, or `nil` on
+ failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.mkdir("tmp/a/b", true)
+```
+
+
+
+#### `cp(src, dst)`
+
+Copy a file or directory tree.
+
+**Parameters**:
+
+- `src` (`string`): Source path.
+- `dst` (`string`): Destination path.
+
+**Return**:
+
+- `copied` (`true?`): `true` when copying succeeds, or `nil` on failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.cp("a.txt", "b.txt")
+fs.cp("src", "backup/src")
+```
+
+### Metadata
+
+
+
+#### `getsize(path)`
+
+Return file size in bytes.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `size` (`integer?`): File size in bytes.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.getsize("README.md") --> 1234
+```
+
+
+
+#### `getatime(path)`
+
+Return last access time.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `timestamp` (`number?`): Access time (seconds since epoch).
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.getatime("README.md") --> 1712345678
+```
+
+
+
+#### `getmtime(path)`
+
+Return last modification time.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `timestamp` (`number?`): Modification time (seconds since epoch).
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.getmtime("README.md") --> 1712345678
+```
+
+
+
+#### `getctime(path)`
+
+Return metadata change time.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `timestamp` (`number?`): Change time (seconds since epoch).
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.getctime("README.md") --> 1712345678
+```
+
+
+
+#### `lstat(path)`
+
+Return symlink-aware file attributes.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `attrs` (`LuaFileSystem.Attributes?`): Symlink-aware attributes, or `nil` on
+ failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.lstat("README.md")
+```
+
+
+
+#### `stat(path)`
+
+Return file attributes.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `attrs`
+ (`string|integer|LuaFileSystem.AttributeMode|LuaFileSystem.Attributes?`): File
+ attributes, or `nil` on failure.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.stat("README.md")
+```
+
+
+
+#### `samefile(path_a, path_b)`
+
+Return whether two paths refer to the same file, or `nil` and an error on
+failure.
+
+**Parameters**:
+
+- `path_a` (`string`): Input path.
+- `path_b` (`string`): Input path.
+
+**Return**:
+
+- `isSameFile` (`boolean?`): True when both paths refer to the same file.
+- `errmsg` (`string?`): Error message when the check fails.
+- `errcode` (`integer?`): OS error code when available.
+
+**Example**:
+
+```lua
+fs.samefile("README.md", "README.md") --> true
+```
+
+### Existence Checks
+
+
+
+#### `exists(path)`
+
+Return `true` when a path exists.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `exists` (`boolean`): True when the path exists.
+
+**Example**:
+
+```lua
+fs.exists("README.md") --> true
+```
+
+> [!NOTE] Broken symlinks return `false`.
+
+
+
+#### `lexists(path)`
+
+Return `true` when a path exists without following symlinks.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `exists` (`boolean`): True when the path or symlink entry exists.
+
+**Example**:
+
+```lua
+fs.lexists("README.md") --> true
+```
+
+> [!NOTE] Broken symlinks return `true`.
diff --git a/docs/src/modules/is.md b/docs/src/modules/is.md
index 8a3fa47..7c89073 100644
--- a/docs/src/modules/is.md
+++ b/docs/src/modules/is.md
@@ -67,6 +67,7 @@ is("hello", "STRING") --> true
| Function | Description |
| ------------------------- | ------------------------------------------------------------ |
+| [`path(v)`](#fn-path) | Returns `true` when `v` is a valid filesystem path. |
| [`block(v)`](#fn-block) | Returns `true` when `v` is a block device path. |
| [`char(v)`](#fn-char) | Returns `true` when `v` is a character device path. |
| [`device(v)`](#fn-device) | Returns `true` when `v` is a block or character device path. |
@@ -368,7 +369,29 @@ Filesystem path type checks.
>
> Path checks require **LuaFileSystem**
> ([`lfs`](https://github.com/lunarmodules/luafilesystem)) and raise an error if
-> it is not installed.
+> it is not installed.
+
+#### `path(v)`
+
+Returns `true` when `v` is a valid filesystem path.
+
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `isPath` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
+```lua
+is.path("README.md")
+```
+
+> [!NOTE] Returns `true` for broken symlinks.
+
+
#### `block(v)`
diff --git a/docs/src/modules/keyword.md b/docs/src/modules/keyword.md
index 94b5a11..97a0aaf 100644
--- a/docs/src/modules/keyword.md
+++ b/docs/src/modules/keyword.md
@@ -1,10 +1,10 @@
---
-description: "Lua keyword helpers."
+description: "Helpers for Lua keywords and identifiers."
---
# `keyword`
-Lua keyword helpers.
+Helpers for Lua keywords and identifiers.
## Usage
diff --git a/docs/src/modules/list.md b/docs/src/modules/list.md
index 1ec6f76..3ef666d 100644
--- a/docs/src/modules/list.md
+++ b/docs/src/modules/list.md
@@ -1,13 +1,11 @@
---
description:
- "A list class providing common operations to create, modify, and query
- sequences of values."
+ "A list class for creating, transforming, and querying sequences of values."
---
# `List`
-A list class providing common operations to create, modify, and query sequences
-of values.
+A list class for creating, transforming, and querying sequences of values.
## Usage
@@ -31,8 +29,8 @@ print(ls:index("b")) --> 2
| Function | Description |
| -------------------------- | ------------------------------------------------- |
-| [`all(pred)`](#fn-all) | Return true if all values match the predicate. |
-| [`any(pred)`](#fn-any) | Return true if any value matches the predicate. |
+| [`all(pred)`](#fn-all) | Return `true` if all values match the predicate. |
+| [`any(pred)`](#fn-any) | Return `true` if any value matches the predicate. |
| [`equals(ls)`](#fn-equals) | Compare two lists using shallow element equality. |
| [`lt(ls)`](#fn-lt) | Compare two lists lexicographically. |
| [`le(ls)`](#fn-le) | Compare two lists lexicographically. |
@@ -43,7 +41,7 @@ print(ls:index("b")) --> 2
| ------------------------------ | -------------------------------------------------------------------- |
| [`append()`](#fn-append) | Append a value to the end of the list. |
| [`clear()`](#fn-clear) | Remove all elements from the list. |
-| [`extend(ls)`](#fn-extend) | Extend the list with another list. |
+| [`extend(t)`](#fn-extend) | Extend the list with another list or set. |
| [`extract(pred)`](#fn-extract) | Extract values matching the predicate and remove them from the list. |
| [`insert(pos, v)`](#fn-insert) | Insert a value at the given position. |
| [`insert(v)`](#fn-insert) | Append a value to the end of the list. |
@@ -63,11 +61,12 @@ print(ls:index("b")) --> 2
| Function | Description |
| -------------------------------- | ----------------------------------------------------------- |
-| [`contains(v)`](#fn-contains) | Return true if the list contains the value. |
+| [`contains(v)`](#fn-contains) | Return `true` if the list contains the value. |
| [`count(v)`](#fn-count) | Count how many times a value appears. |
| [`index(v)`](#fn-index) | Return the index of the first matching value. |
| [`index_if(pred)`](#fn-index-if) | Return the index of the first value matching the predicate. |
| [`len()`](#fn-len) | Return the number of elements in the list. |
+| [`isempty()`](#fn-isempty) | Return whether the list has no elements. |
**Access**:
@@ -78,29 +77,29 @@ print(ls:index("b")) --> 2
**Transform**:
-| Function | Description |
-| -------------------------------------- | -------------------------------------------------------------------- |
-| [`difference(ls)`](#fn-difference) | Return a new list with values not in the given list. |
-| [`drop(n)`](#fn-drop) | Return a new list without the first n elements. |
-| [`filter(pred)`](#fn-filter) | Return a new list with values matching the predicate. |
-| [`flatten()`](#fn-flatten) | Flatten one level of nested lists. |
-| [`foreach(fn)`](#fn-foreach) | Apply a function to each element (for side effects). |
-| [`group_by(fn)`](#fn-group-by) | Group list values by a computed key. |
-| [`intersection(ls)`](#fn-intersection) | Return values that are also present in the given list. |
-| [`invert()`](#fn-invert) | Invert values to indices in a new table. |
-| [`concat(sep?, i?, j?)`](#fn-concat) | Concatenate list values using Lua's native `table.concat` behavior. |
-| [`join(sep?, quoted?)`](#fn-join) | Join list values into a string. |
-| [`tostring()`](#fn-tostring) | Render the list to a string via the regular method form. |
-| [`keypath()`](#fn-keypath) | Render list items as a table-access key path. |
-| [`map(fn)`](#fn-map) | Return a new list by mapping each value. |
-| [`mul(n)`](#fn-mul) | Return a new list repeated `n` times (list multiplication behavior). |
-| [`reduce(fn, init?)`](#fn-reduce) | Reduce the list to a single value using an accumulator. |
-| [`reverse()`](#fn-reverse) | Return a new list with items reversed. |
-| [`toset()`](#fn-toset) | Convert the list to a set. |
-| [`slice(i?, j?)`](#fn-slice) | Return a new list containing items from i to j (inclusive). |
-| [`take(n)`](#fn-take) | Return the first n elements as a new list. |
-| [`uniq()`](#fn-uniq) | Return a new list with duplicates removed (first occurrence kept). |
-| [`zip(ls)`](#fn-zip) | Zip two lists into a list of 2-element tables. |
+| Function | Description |
+| ------------------------------------- | -------------------------------------------------------------------- |
+| [`difference(t)`](#fn-difference) | Return a new list with values not in the given list or set. |
+| [`drop(n)`](#fn-drop) | Return a new list without the first n elements. |
+| [`filter(pred)`](#fn-filter) | Return a new list with values matching the predicate. |
+| [`flatten()`](#fn-flatten) | Flatten one level of nested lists. |
+| [`foreach(fn)`](#fn-foreach) | Apply a function to each element (for side effects). |
+| [`group_by(fn)`](#fn-group-by) | Group list values by a computed key. |
+| [`intersection(t)`](#fn-intersection) | Return values that are also present in the given list or set. |
+| [`invert()`](#fn-invert) | Invert values to indices in a new table. |
+| [`concat(sep?, i?, j?)`](#fn-concat) | Concatenate list values using Lua's native `table.concat` behavior. |
+| [`join(sep?, quoted?)`](#fn-join) | Join list values into a string. |
+| [`tostring()`](#fn-tostring) | Render the list to a string via the regular method form. |
+| [`keypath()`](#fn-keypath) | Render list items as a table-access key path. |
+| [`map(fn)`](#fn-map) | Return a new list by mapping each value. |
+| [`mul(n)`](#fn-mul) | Return a new list repeated `n` times (list multiplication behavior). |
+| [`reduce(fn, init?)`](#fn-reduce) | Reduce the list to a single value using an accumulator. |
+| [`reverse()`](#fn-reverse) | Reverse the list in place. |
+| [`toset()`](#fn-toset) | Convert the list to a set. |
+| [`slice(i?, j?)`](#fn-slice) | Return a new list containing items from i to j (inclusive). |
+| [`take(n)`](#fn-take) | Return the first n elements as a new list. |
+| [`uniq()`](#fn-uniq) | Return a new list with duplicates removed (first occurrence kept). |
+| [`zip(t)`](#fn-zip) | Zip two collections into a list of 2-element tables. |
**Metamethods**:
@@ -120,7 +119,7 @@ Boolean checks for list-wide conditions.
#### `all(pred)`
-Return true if all values match the predicate.
+Return `true` if all values match the predicate.
**Parameters**:
@@ -145,7 +144,7 @@ ok = List({ 2, 4 }):all(is_even) --> true
#### `any(pred)`
-Return true if any value matches the predicate.
+Return `true` if any value matches the predicate.
**Parameters**:
@@ -273,7 +272,7 @@ Append a value to the end of the list.
**Return**:
-- `self` (`T`): Current list instance.
+- `self` (`T`): Current list.
**Example**:
@@ -289,7 +288,7 @@ Remove all elements from the list.
**Return**:
-- `self` (`T`): Current list instance.
+- `self` (`T`): Current list.
**Example**:
@@ -299,22 +298,23 @@ ls = List({ "a", "b" }):clear() --> { }
-#### `extend(ls)`
+#### `extend(t)`
-Extend the list with another list.
+Extend the list with another list or set.
**Parameters**:
-- `ls` (`any[]`): List values.
+- `t` (`mods.List|mods.Set|any[]`): Values to append.
**Return**:
-- `self` (`T`): Current list instance.
+- `self` (`T`): Current list.
**Example**:
```lua
-ls = List({ "a" }):extend({ "b", "c" }) --> { "a", "b", "c" }
+ls = List({ "a" }):extend({ "b", "c" }) --> { "a", "b", "c" }
+ls = List({ "a" }):extend(Set({ "b", "c" })) --> { "a", "b", "c" }
```
> [!NOTE]
@@ -356,7 +356,7 @@ Insert a value at the given position.
**Return**:
-- `self` (`T`): Current list instance.
+- `self` (`T`): Current list.
**Example**:
@@ -376,7 +376,7 @@ Append a value to the end of the list.
**Return**:
-- `self` (`T`): Current list instance.
+- `self` (`T`): Current list.
**Example**:
@@ -434,7 +434,7 @@ Insert a value at the start of the list.
**Return**:
-- `self` (`T`): Current list instance.
+- `self` (`T`): Current list.
**Example**:
@@ -455,7 +455,7 @@ Remove the first matching value.
**Return**:
-- `self` (`T`): Current list instance.
+- `self` (`T`): Current list.
**Example**:
@@ -472,18 +472,23 @@ Sort the list in place.
**Parameters**:
-- `comp?` (`fun(a,b):boolean`): Optional comparison function (defaults to
- `nil`).
+- `comp?` (`fun(a:any,`): b:any):boolean Optional comparison function (defaults
+ to `nil`).
**Return**:
-- `self` (`T`): Current list instance.
+- `self` (`T`): Current list.
**Example**:
```lua
ls = List({ 3, 1, 2 })
ls:sort() --> { 1, 2, 3 }
+
+words = List({ "ccc", "a", "bb" })
+words:sort(function(a, b)
+ return #a < #b
+end) --> { "a", "bb", "ccc" }
```
### Copying
@@ -510,7 +515,7 @@ Read-only queries for membership, counts, and indices.
#### `contains(v)`
-Return true if the list contains the value.
+Return `true` if the list contains the value.
**Parameters**:
@@ -607,6 +612,22 @@ n = List({ "a", "b", "c" }):len() --> 3
>
> Uses Lua's `#` operator.
+
+
+#### `isempty()`
+
+Return whether the list has no elements.
+
+**Return**:
+
+- `empty` (`boolean`): `true` when the list has no elements.
+
+**Example**:
+
+```lua
+ok = List():isempty() --> true
+```
+
### Access
Direct element access helpers.
@@ -646,13 +667,13 @@ v = List({ "a", "b" }):last() --> "b"
Non-mutating transformations and derived-list operations.
-#### `difference(ls)`
+#### `difference(t)`
-Return a new list with values not in the given list.
+Return a new list with values not in the given list or set.
**Parameters**:
-- `ls` (`mods.List|any[]`): Other list value.
+- `t` (`mods.List|mods.Set|any[]`): Values to remove.
**Return**:
@@ -770,13 +791,13 @@ g = List(words):group_by(string.len) --> { {"b"}, { "aa", "dd" }, { "ccc" } }
-#### `intersection(ls)`
+#### `intersection(t)`
-Return values that are also present in the given list.
+Return values that are also present in the given list or set.
**Parameters**:
-- `ls` (`mods.List|any[]`): Other list value.
+- `t` (`mods.List|mods.Set|any[]`): Other list/set.
**Return**:
@@ -801,7 +822,7 @@ Invert values to indices in a new table.
**Return**:
-- `idxByValue` (`table`): Table mapping each value to its last index.
+- `idxByValue` (`table`): Table mapping each value to its last index.
**Example**:
@@ -879,10 +900,6 @@ Render the list to a string via the regular method form.
s = List({ "a", "b", 1 }):tostring() --> '{ "a", "b", 1 }'
```
-> [!NOTE]
->
-> `tostring(list)` calls `list:tostring()`.
-
#### `keypath()`
@@ -907,7 +924,7 @@ Return a new list by mapping each value.
**Parameters**:
-- `fn` (`fun(v):any`): Callback function.
+- `fn` (`fun(value:T):any`): Callback function.
**Return**:
@@ -976,11 +993,11 @@ sum = List({ 1, 2, 3 }):reduce(add, 10) --> 16
#### `reverse()`
-Return a new list with items reversed.
+Reverse the list in place.
**Return**:
-- `ls` (`mods.List`): New list.
+- `ls` (`mods.List`): Same list, reversed in place.
**Example**:
@@ -1071,13 +1088,13 @@ u = List({ "a", "b", "a", "c" }):uniq() --> { "a", "b", "c" }
-#### `zip(ls)`
+#### `zip(t)`
-Zip two lists into a list of 2-element tables.
+Zip two collections into a list of 2-element tables.
**Parameters**:
-- `ls` (`mods.List|any[]`): Other list value.
+- `t` (`mods.List|mods.Set|any[]`): Values to pair with.
**Return**:
@@ -1086,12 +1103,13 @@ Zip two lists into a list of 2-element tables.
**Example**:
```lua
-z = List({ "a", "b" }):zip({ 1, 2 }) --> { {"a",1}, {"b",2} }
+z = List({ "a", "b" }):zip({ 1, 2 }) --> { {"a",1}, {"b",2} }
+z = List({ "a", "b" }):zip(Set({ 1, 2 })) --> { {"a",1}, {"b",2} }
```
> [!NOTE]
>
-> Length is the minimum of both lists.
+> Length is the minimum of both tables' lengths.
### Metamethods
@@ -1230,7 +1248,7 @@ left-hand list reference (`+`).
**Return**:
-- `self` (`mods.List|any[]`): Current list instance.
+- `self` (`mods.List|any[]`): Current list.
**Example**:
@@ -1285,7 +1303,3 @@ Render the list to a string like `{ "a", "b", 1 }`.
```lua
s = tostring(List({ "a", "b", 1 })) --> '{ "a", "b", 1 }'
```
-
-> [!NOTE]
->
-> `tostring(ls)` is equivalent to `:tostring()`.
diff --git a/docs/src/modules/ntpath.md b/docs/src/modules/ntpath.md
index a7c59d2..119a590 100644
--- a/docs/src/modules/ntpath.md
+++ b/docs/src/modules/ntpath.md
@@ -1,10 +1,10 @@
---
-description: "Path operations for Windows/NT-style paths."
+description: "Windows/NT-style path operations."
---
# `ntpath`
-Path operations for Windows/NT-style paths.
+Windows/NT-style path operations.
> 💡Python `ntpath`-style behavior, ported to Lua.
diff --git a/docs/src/modules/operator.md b/docs/src/modules/operator.md
index 3e8aa8a..51951c7 100644
--- a/docs/src/modules/operator.md
+++ b/docs/src/modules/operator.md
@@ -1,10 +1,10 @@
---
-description: "Operator helpers as functions."
+description: "Lua operators exposed as functions."
---
# `operator`
-Operator helpers as functions.
+Lua operators exposed as functions.
## Usage
diff --git a/docs/src/modules/path.md b/docs/src/modules/path.md
index 2e866fa..fe5f7a5 100644
--- a/docs/src/modules/path.md
+++ b/docs/src/modules/path.md
@@ -1,10 +1,10 @@
---
-description: "Generic cross-platform path API."
+description: "Cross-platform path operations with host-platform semantics."
---
# `path`
-Generic cross-platform path API.
+Cross-platform path operations with host-platform semantics.
## Usage
@@ -18,7 +18,7 @@ print(path.splitext("archive.tar.gz")) --> "archive.tar", ".gz"
## Functions
-**Normalization & Predicates**:
+**Normalization**:
| Function | Description |
| -------------------------------- | ---------------------------------------------------- |
@@ -27,7 +27,7 @@ print(path.splitext("archive.tar.gz")) --> "archive.tar", ".gz"
| [`normpath(path)`](#fn-normpath) | Normalize separators and dot segments. |
| [`isabs(path)`](#fn-isabs) | Return `true` when `path` is absolute. |
-**Path Decomposition**:
+**Decomposition**:
| Function | Description |
| ------------------------------------ | -------------------------------------------------- |
@@ -38,22 +38,61 @@ print(path.splitext("archive.tar.gz")) --> "archive.tar", ".gz"
| [`basename(path)`](#fn-basename) | Return final path component. |
| [`dirname(path)`](#fn-dirname) | Return directory portion of a path. |
-**Environment Expand**:
+**Environment**:
-| Function | Description |
-| ------------------------------------ | ---------------------------------------------- |
-| [`expanduser(path)`](#fn-expanduser) | Expand `~` home segment when available. |
-| [`home()`](#fn-home) | Return the current user's home directory path. |
+| Function | Description |
+| ------------------------------------ | ----------------------------------------------------------------------- |
+| [`expanduser(path)`](#fn-expanduser) | Expand `~` home segment when available. |
+| [`expandvars(path)`](#fn-expandvars) | Expand vars in a path (`$VAR`/`${VAR}` everywhere, `%VAR%` on Windows). |
+| [`home()`](#fn-home) | Return the current user's home directory path. |
+| [`cwd()`](#fn-cwd) | Return the current working directory path. |
-**Derived Paths**:
+**Derived**:
-| Function | Description |
-| -------------------------------------- | ------------------------------------------------ |
-| [`abspath(path)`](#fn-abspath) | Return normalized absolute path. |
-| [`relpath(path, start?)`](#fn-relpath) | Return `path` relative to optional `start` path. |
-| [`commonpath(paths)`](#fn-commonpath) | Return longest common sub-path from a path list. |
+| Function | Description |
+| ----------------------------------------- | ------------------------------------------------ |
+| [`abspath(path)`](#fn-abspath) | Return normalized absolute path. |
+| [`relpath(path, start?)`](#fn-relpath) | Return `path` relative to optional `start` path. |
+| [`commonpath(paths)`](#fn-commonpath) | Return longest common sub-path from a path list. |
+| [`commonprefix(paths)`](#fn-commonprefix) | Return longest common leading string prefix. |
-### Normalization & Predicates
+**Anchors**:
+
+| Function | Description |
+| ---------------------------- | ------------------------------------------- |
+| [`drive(path)`](#fn-drive) | Return drive prefix when present. |
+| [`root(path)`](#fn-root) | Return root separator segment when present. |
+| [`anchor(path)`](#fn-anchor) | Return drive and root combined. |
+
+**Components**:
+
+| Function | Description |
+| -------------------------------- | ------------------------------------------------------------- |
+| [`parts(path)`](#fn-parts) | Split path into logical parts, including anchor when present. |
+| [`stem(path)`](#fn-stem) | Return filename without its final suffix. |
+| [`suffixes(path)`](#fn-suffixes) | Return all filename suffixes in order. |
+| [`parents(path)`](#fn-parents) | Return logical parent paths from nearest to farthest. |
+
+**Relations**:
+
+| Function | Description |
+| ------------------------------------------------------- | --------------------------------------------------------------------------------------- |
+| [`relative_to(path, other, walk_up?)`](#fn-relative-to) | Return `path` relative to `other`, or `nil` with an error when it is not under `other`. |
+| [`is_relative_to(path, other)`](#fn-is-relative-to) | Return `true` when `path` is under `other`. |
+| [`with_name(path, name)`](#fn-with-name) | Return a path with the final filename replaced. |
+| [`with_stem(path, stem)`](#fn-with-stem) | Return a path with the final filename stem replaced. |
+| [`with_suffix(path, suffix)`](#fn-with-suffix) | Return a path with the final filename suffix replaced. |
+
+**Conversions**:
+
+| Function | Description |
+| ---------------------------------------------------- | --------------------------------------------------------------------------- |
+| [`as_posix(path)`](#fn-as-posix) | Convert backslashes (`\`) to forward slashes (`/`). |
+| [`as_uri(path)`](#fn-as-uri) | Convert a local path to a `file://` URI. |
+| [`match(path, pattern, case_sensitive?)`](#fn-match) | Match a path against a glob-style pattern using only `*` and `?` wildcards. |
+| [`from_uri(uri)`](#fn-from-uri) | Convert a `file://` URI to a local absolute path. |
+
+### Normalization
@@ -67,17 +106,20 @@ Normalize path case using the active path semantics.
**Return**:
-- `value` (`string`): Path after case normalization.
+- `normalizedPath` (`string`): Path after case normalization.
**Example**:
```lua
-path.normcase("/A/B") --> "/A/B"
+path.normcase("ABC") --> "abc"
+path.normcase("/A/B") --> "\\a\\b"
```
> [!NOTE]
>
-> On POSIX semantics this returns the input unchanged.
+> On POSIX semantics this returns the input unchanged. Use
+> [`mods.ntpath`](/modules/ntpath) to force Windows-style case folding and
+> separator normalization.
@@ -92,13 +134,13 @@ Join path components.
**Return**:
-- `value` (`string`): Joined path.
+- `joinedPath` (`string`): Joined path.
**Example**:
```lua
path.join("/usr", "bin") --> "/usr/bin"
-path.join([[C:\a]], [[b]]) --> [[C:\a\b]]
+path.join([[C:/a]], [[b]]) --> [[C:/a\b]]
```
> [!NOTE]
@@ -117,7 +159,7 @@ Normalize separators and dot segments.
**Return**:
-- `value` (`string`): Normalized path.
+- `normalizedPath` (`string`): Normalized path.
**Example**:
@@ -138,7 +180,7 @@ Return `true` when `path` is absolute.
**Return**:
-- `value` (`boolean`): True when `path` is absolute.
+- `isAbsolute` (`boolean`): True when `path` is absolute.
**Example**:
@@ -146,7 +188,7 @@ Return `true` when `path` is absolute.
path.isabs("/a/b") --> true
```
-### Path Decomposition
+### Decomposition
@@ -250,7 +292,7 @@ Return final path component.
**Return**:
-- `value` (`string`): Final path component.
+- `basename` (`string`): Final path component.
**Example**:
@@ -271,7 +313,7 @@ Return directory portion of a path.
**Return**:
-- `value` (`string`): Parent directory path.
+- `dirname` (`string`): Parent directory path.
**Example**:
@@ -280,7 +322,7 @@ path.dirname("/a/b.txt") --> "/a"
path.dirname([[C:\a\b.txt]]) --> [[C:\a]]
```
-### Environment Expand
+### Environment
@@ -294,7 +336,8 @@ Expand `~` home segment when available.
**Return**:
-- `value` (`string?`): Path with the home segment expanded when available.
+- `expandedPath` (`string?`): Path with the home segment expanded when
+ available.
- `err` (`string?`): Error message when `~` expansion cannot be resolved.
**Example**:
@@ -304,6 +347,29 @@ path.expanduser("~/tmp") --> "/tmp" (when HOME is set)
path.expanduser([[x\y]]) --> [[x\y]]
```
+
+
+#### `expandvars(path)`
+
+Expand vars in a path (`$VAR`/`${VAR}` everywhere, `%VAR%` on Windows).
+
+**Parameters**:
+
+- `path` (`string`): Path containing variable placeholders.
+
+**Return**:
+
+- `expandedPath` (`string`): Path with variable values substituted.
+
+**Example**:
+
+```lua
+path.expandvars("$HOME/bin") --> "/home/me/bin"
+path.expandvars("${XDG_CONFIG_HOME}/nvim") --> "/home/me/.config/nvim"
+path.expandvars("%USERPROFILE%\\bin") --> "C:\\Users\\me\\bin"
+path.expandvars("$UNKNOWN/bin") --> "$UNKNOWN/bin"
+```
+
#### `home()`
@@ -312,7 +378,7 @@ Return the current user's home directory path.
**Return**:
-- `value` (`string?`): Home directory path when available.
+- `homePath` (`string?`): Home directory path when available.
- `err` (`string?`): Error message when the home directory cannot be resolved.
**Example**:
@@ -321,7 +387,24 @@ Return the current user's home directory path.
path.home()
```
-### Derived Paths
+
+
+#### `cwd()`
+
+Return the current working directory path.
+
+**Return**:
+
+- `cwd` (`string?`): Current working directory path.
+- `err` (`string?`): Error message when the cwd cannot be resolved.
+
+**Example**:
+
+```lua
+path.cwd()
+```
+
+### Derived
@@ -335,7 +418,7 @@ Return normalized absolute path.
**Return**:
-- `value` (`string`): Absolute normalized path.
+- `absolutePath` (`string`): Absolute normalized path.
**Example**:
@@ -357,7 +440,8 @@ Return `path` relative to optional `start` path.
**Return**:
-- `value` (`string`): Relative path from `start` to `path`.
+- `relativePath` (`string?`): Relative path from `start` to `path`.
+- `err` (`string?`): Error message when the path cannot be made relative.
**Example**:
@@ -378,7 +462,8 @@ Return longest common sub-path from a path list.
**Return**:
-- `value` (`string`): Longest common sub-path.
+- `commonPath` (`string?`): Longest common sub-path.
+- `err` (`string?`): Error message when inputs are incompatible.
**Example**:
@@ -387,7 +472,396 @@ path.commonpath({ "/a/b/c", "/a/b/d" }) --> "/a/b"
path.commonpath({ [[C:\a\b\c]], [[c:/a/b/d]] }) --> [[C:\a\b]]
```
-> [!NOTE]
->
-> All inputs must use compatible drive/root semantics. Mixing absolute and
-> relative paths may raise an error.
+
+
+#### `commonprefix(paths)`
+
+Return longest common leading string prefix.
+
+**Parameters**:
+
+- `paths` (`string[]`): List of paths.
+
+**Return**:
+
+- `commonPrefix` (`string`): Longest common string prefix.
+
+**Example**:
+
+```lua
+path.commonprefix({"abc", "abd"}) --> "ab"
+path.commonprefix({"/home/swen/spam", "/home/swen/eggs"}) --> "/home/swen/"
+path.commonprefix({"abc", "xyz"}) --> ""
+```
+
+### Anchors
+
+
+
+#### `drive(path)`
+
+Return drive prefix when present.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `drivePrefix` (`string`): Drive prefix.
+
+**Example**:
+
+```lua
+path.drive("c:a/b") --> "c:"
+path.drive("a/b") --> ""
+```
+
+
+
+#### `root(path)`
+
+Return root separator segment when present.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `rootSeparator` (`string`): Root separator segment.
+
+**Example**:
+
+```lua
+path.root("/tmp/a.txt") --> "/"
+path.root("c:/") --> "\\"
+path.root("a/b") --> ""
+```
+
+
+
+#### `anchor(path)`
+
+Return drive and root combined.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `anchor` (`string`): Drive and root anchor.
+
+**Example**:
+
+```lua
+path.anchor("c:\\") --> "c:\\"
+```
+
+### Components
+
+
+
+#### `parts(path)`
+
+Split path into logical parts, including anchor when present.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `paths` (`mods.List`): Path parts including anchor when present.
+
+**Example**:
+
+```lua
+path.parts("a/b.txt") --> {"a", "b.txt"}
+path.parts("/a/b") --> {"/", "a", "b"}
+path.parts("c:a\\b") --> {"c:", "a", "b"}
+```
+
+
+
+#### `stem(path)`
+
+Return filename without its final suffix.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `stem` (`string`): Filename stem.
+
+**Example**:
+
+```lua
+path.stem("archive.tar.gz") --> "archive.tar"
+path.stem("c:a/b") --> "b"
+```
+
+
+
+#### `suffixes(path)`
+
+Return all filename suffixes in order.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `suffixes` (`mods.List`): Filename suffixes.
+
+**Example**:
+
+```lua
+path.suffixes("archive.tar.gz") --> {".tar", ".gz"}
+path.suffixes("a/b") --> {}
+```
+
+
+
+#### `parents(path)`
+
+Return logical parent paths from nearest to farthest.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `parents` (`mods.List`): Ancestor paths from nearest to farthest.
+
+**Example**:
+
+```lua
+path.parents("a/b/c") --> {"a/b", "a", "."}
+path.parents("c:a/b") --> {"c:a", "c:"}
+```
+
+### Relations
+
+
+
+#### `relative_to(path, other, walk_up?)`
+
+Return `path` relative to `other`, or `nil` with an error when it is not under
+`other`.
+
+When `walk_up` is `true`, allow `..` segments to walk up to a shared prefix.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `other` (`string`): Reference path.
+- `walk_up?` (`boolean`): Allow walking up to a shared prefix.
+
+**Return**:
+
+- `relativePath` (`string?`): Path relative to `other`, or `nil` on error.
+- `err` (`string?`): Error message when the path cannot be made relative.
+
+**Example**:
+
+```lua
+path.relative_to("/a/b/c.txt", "/a") --> "b/c.txt"
+path.relative_to("/a/b", "/a/c", true) --> "../b"
+path.relative_to("/a/b", "/a/x") --> nil, "'/a/b' is not in the subpath of '/a/x'"
+```
+
+
+
+#### `is_relative_to(path, other)`
+
+Return `true` when `path` is under `other`.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `other` (`string`): Reference path.
+
+**Return**:
+
+- `isRelative` (`boolean`): True when `path` is under `other`.
+
+**Example**:
+
+```lua
+path.is_relative_to("a/b/c", "a/b") --> true
+path.is_relative_to("C:A/B", "c:a") --> true
+path.is_relative_to("a/b", "a/b/c") --> false
+```
+
+
+
+#### `with_name(path, name)`
+
+Return a path with the final filename replaced.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `name` (`string`): Replacement filename.
+
+**Return**:
+
+- `updatedPath` (`string?`): Path with replaced filename, or `nil` on error.
+- `err` (`string?`): Error message when replacement fails.
+
+**Example**:
+
+```lua
+path.with_name("a/b", "c.txt") --> "a/c.txt"
+path.with_name("a/b.txt", "c.lua") --> "a/c.lua"
+path.with_name("a/b", "c/d") --> nil, "invalid name 'c/d'"
+path.with_name("/", "d.xml") --> nil, "'/' has an empty name"
+```
+
+
+
+#### `with_stem(path, stem)`
+
+Return a path with the final filename stem replaced.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `stem` (`string`): Replacement filename stem.
+
+**Return**:
+
+- `updatedPath` (`string?`): Path with replaced filename stem, or `nil` on
+ error.
+- `err` (`string?`): Error message when replacement fails.
+
+**Example**:
+
+```lua
+path.with_stem("a/b", "d") --> "/a/d"
+path.with_stem("a/b.lua", "d") --> "/a/d.lua"
+path.with_stem("/", "d") --> "'/' has an empty name"
+path.with_stem("a/b", "d") --> "invalid name ''."
+```
+
+
+
+#### `with_suffix(path, suffix)`
+
+Return a path with the final filename suffix replaced.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `suffix` (`string`): Replacement suffix.
+
+**Return**:
+
+- `updatedPath` (`string?`): Path with replaced suffix, or `nil` on error.
+- `err` (`string?`): Error message when replacement fails.
+
+**Example**:
+
+```lua
+path.with_suffix("a/b", ".gz") --> "a/b/.gz"
+path.with_suffix("a/b.gz", ".lua") --> "a/b/.lua"
+path.with_suffix("a/b", "gz") --> nil, "invalid suffix 'gz'"
+path.with_suffix("//a/b", "gz") --> nil, "'//a/b' has an empty name"
+```
+
+### Conversions
+
+
+
+#### `as_posix(path)`
+
+Convert backslashes (`\`) to forward slashes (`/`).
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `posixPath` (`string`): POSIX-style path.
+
+**Example**:
+
+```lua
+path.as_posix("a\\b\\c") --> "a/b/c"
+```
+
+
+
+#### `as_uri(path)`
+
+Convert a local path to a `file://` URI.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+
+**Return**:
+
+- `fileUri` (`string?`): File URI.
+- `err` (`string?`): Error message when conversion fails.
+
+**Example**:
+
+```lua
+path.as_uri("/home/user/report.txt") --> "file:///home/user/report.txt"
+path.as_uri("c:/a/b.c") --> "file:///c:/a/b.c"
+path.as_uri("/a/b%#c") --> "file:///a/b%25%23c"
+```
+
+
+
+#### `match(path, pattern, case_sensitive?)`
+
+Match a path against a glob-style pattern using only `*` and `?` wildcards.
+
+**Parameters**:
+
+- `path` (`string`): Input path.
+- `pattern` (`string`): Pattern to match.
+- `case_sensitive?` (`boolean`): Override platform-default case matching.
+
+**Return**:
+
+- `matchesPattern` (`boolean`): True when the path matches.
+
+**Example**:
+
+```lua
+path.match("a/b.lua", "*.lua") --> true
+path.match("A.lua", "a.LUA", false) --> true
+path.match("notes.txt", "n?tes.*") --> true
+path.match("a/b/c.lua", "a/*/c.lua") --> true
+```
+
+
+
+#### `from_uri(uri)`
+
+Convert a `file://` URI to a local absolute path.
+
+**Parameters**:
+
+- `uri` (`string`): URI value.
+
+**Return**:
+
+- `path` (`string?`): Resolved absolute path.
+- `err` (`string?`): Error message when conversion fails.
+
+**Example**:
+
+```lua
+path.from_uri("file://localhost/tmp/a.txt") --> "/tmp/a.txt"
+```
diff --git a/docs/src/modules/posixpath.md b/docs/src/modules/posixpath.md
index c0db09c..48e81d1 100644
--- a/docs/src/modules/posixpath.md
+++ b/docs/src/modules/posixpath.md
@@ -1,10 +1,10 @@
---
-description: "Path operations for POSIX-style paths."
+description: "POSIX-style path operations."
---
# `posixpath`
-Path operations for POSIX-style paths.
+POSIX-style path operations.
> 💡 Python `posixpath`-style behavior, ported to Lua.
diff --git a/docs/src/modules/repr.md b/docs/src/modules/repr.md
index e0ffceb..640edc9 100644
--- a/docs/src/modules/repr.md
+++ b/docs/src/modules/repr.md
@@ -1,10 +1,10 @@
---
-description: "Render any Lua value as a readable string."
+description: "Readable string rendering for Lua values."
---
# `repr`
-Render any Lua value as a readable string.
+Readable string rendering for Lua values.
## Usage
diff --git a/docs/src/modules/runtime.md b/docs/src/modules/runtime.md
index e8c2b99..6c3e236 100644
--- a/docs/src/modules/runtime.md
+++ b/docs/src/modules/runtime.md
@@ -1,10 +1,10 @@
---
-description: "Exposes Lua runtime metadata and version compatibility flags."
+description: "Lua runtime metadata and version compatibility flags."
---
# `runtime`
-Exposes Lua runtime metadata and version compatibility flags.
+Lua runtime metadata and version compatibility flags.
## Usage
diff --git a/docs/src/modules/set.md b/docs/src/modules/set.md
index ee20a2b..cad87fb 100644
--- a/docs/src/modules/set.md
+++ b/docs/src/modules/set.md
@@ -1,13 +1,10 @@
---
-description:
- "A set class providing common operations to create, modify, and query
- collections of unique values."
+description: "A set class for creating, combining, and querying unique values."
---
# `Set`
-A set class providing common operations to create, modify, and query collections
-of unique values.
+A set class for creating, combining, and querying unique values.
## Usage
@@ -34,24 +31,24 @@ print(s:contains("a")) --> true
**Copying**:
-| Function | Description |
-| ------------------------------------------------------- | --------------------------------------------------- |
-| [`copy()`](#fn-copy) | Return a shallow copy of the set. |
-| [`difference(set)`](#fn-difference) | Return elements in this set but not in another. |
-| [`intersection(set)`](#fn-intersection) | Return elements common to both sets. |
-| [`remove(v)`](#fn-remove) | Remove an element if present, do nothing otherwise. |
-| [`symmetric_difference(set)`](#fn-symmetric-difference) | Return elements not shared by both sets. |
-| [`union(set)`](#fn-union) | Return a new set with all elements from both. |
+| Function | Description |
+| ----------------------------------------------------- | --------------------------------------------------- |
+| [`copy()`](#fn-copy) | Return a shallow copy of the set. |
+| [`difference(t)`](#fn-difference) | Return elements in this set but not in another. |
+| [`intersection(t)`](#fn-intersection) | Return elements common to both sets. |
+| [`remove(v)`](#fn-remove) | Remove an element if present, do nothing otherwise. |
+| [`symmetric_difference(t)`](#fn-symmetric-difference) | Return elements not shared by both sets. |
+| [`union(t)`](#fn-union) | Return a new set with all elements from both. |
**Predicates**:
| Function | Description |
| ----------------------------------- | ---------------------------------------------------------------- |
| [`isdisjoint(set)`](#fn-isdisjoint) | Return true if sets have no elements in common. |
-| [`equals(set)`](#fn-equals) | Return true when both sets contain exactly the same members. |
+| [`equals(t)`](#fn-equals) | Return true when both sets contain exactly the same members. |
| [`isempty()`](#fn-isempty) | Return true if the set has no elements. |
-| [`issubset(set)`](#fn-issubset) | Return true if all elements of this set are also in another set. |
-| [`issuperset(set)`](#fn-issuperset) | Return true if this set contains all elements of another set. |
+| [`issubset(t)`](#fn-issubset) | Return true if all elements of this set are also in another set. |
+| [`issuperset(t)`](#fn-issuperset) | Return true if this set contains all elements of another set. |
**Query**:
@@ -62,23 +59,26 @@ print(s:contains("a")) --> true
**Transform**:
-| Function | Description |
-| ------------------------ | --------------------------------------- |
-| [`map(fn)`](#fn-map) | Return a new set by mapping each value. |
-| [`values()`](#fn-values) | Return a list of all values in the set. |
+| Function | Description |
+| --------------------------------- | --------------------------------------- |
+| [`map(fn)`](#fn-map) | Return a new set by mapping each value. |
+| [`values()`](#fn-values) | Return a list of all values in the set. |
+| [`tostring()`](#fn-tostring) | Render the set as a string. |
+| [`join(sep?, quoted?)`](#fn-join) | Join set values into a string. |
**Metamethods**:
-| Function | Description |
-| ------------------------- | -------------------------------------------------------------------------- |
-| [`__add(set)`](#fn-add) | Return the union of two sets using `+`. |
-| [`__bor(set)`](#fn-bor) | Return the union of two sets using `\|`. |
-| [`__band(set)`](#fn-band) | Return the intersection of two sets using `&`. |
-| [`__bxor(set)`](#fn-bxor) | Return elements present in exactly one set using `^`. |
-| [`__eq(set)`](#fn-eq) | Return true if both sets contain exactly the same members using `==`. |
-| [`__le(set)`](#fn-le) | Return true if the left set is a subset of the right set using `<=`. |
-| [`__lt(set)`](#fn-lt) | Return true if the left set is a proper subset of the right set using `<`. |
-| [`__sub(set)`](#fn-sub) | Return the difference of two sets using `-`. |
+| Function | Description |
+| ------------------------------ | -------------------------------------------------------------------------- |
+| [`__add(t)`](#fn-add) | Return the union of two sets using `+`. |
+| [`__bor(t)`](#fn-bor) | Return the union of two sets using `\|`. |
+| [`__band(t)`](#fn-band) | Return the intersection of two sets using `&`. |
+| [`__bxor(t)`](#fn-bxor) | Return elements present in exactly one set using `^`. |
+| [`__eq(t)`](#fn-eq) | Return true if both sets contain exactly the same members using `==`. |
+| [`__le(t)`](#fn-le) | Return true if the left set is a subset of the right set using `<=`. |
+| [`__lt(set)`](#fn-lt) | Return true if the left set is a proper subset of the right set using `<`. |
+| [`__sub(set)`](#fn-sub) | Return the difference of two sets using `-`. |
+| [`__tostring()`](#fn-tostring) | Render the set via `tostring(set)`. |
### Mutation
@@ -94,7 +94,7 @@ Add an element to the set.
**Return**:
-- `self` (`T`): Current set instance.
+- `self` (`T`): Current set.
**Example**:
@@ -110,7 +110,7 @@ Remove all elements from the set.
**Return**:
-- `self` (`T`): Current set instance.
+- `self` (`T`): Current set.
**Example**:
@@ -126,11 +126,11 @@ Remove elements found in another set (in place).
**Parameters**:
-- `set` (`T`): Other set value.
+- `set` (`T|mods.List`): Other set/list.
**Return**:
-- `self` (`T`): Current set instance.
+- `self` (`T`): Current set.
**Example**:
@@ -146,11 +146,11 @@ Keep only elements common to both sets (in place).
**Parameters**:
-- `set` (`T`): Other set value.
+- `set` (`T|mods.List`): Other set/list.
**Return**:
-- `self` (`T`): Current set instance.
+- `self` (`T`): Current set.
**Example**:
@@ -183,11 +183,11 @@ Update the set with elements not shared by both (in place).
**Parameters**:
-- `set` (`T`): Other set value.
+- `set` (`T|mods.List`): Other set/list.
**Return**:
-- `self` (`T`): Current set instance.
+- `self` (`T`): Current set.
**Example**:
@@ -204,11 +204,11 @@ Add all elements from another set (in place).
**Parameters**:
-- `set` (`T`): Other set value.
+- `set` (`T|mods.List`): Other set/list.
**Return**:
-- `self` (`T`): Current set instance.
+- `self` (`T`): Current set.
**Example**:
@@ -236,13 +236,13 @@ c = Set({ "a" }):copy() --> c is a new set with "a"
-#### `difference(set)`
+#### `difference(t)`
Return elements in this set but not in another.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -261,13 +261,13 @@ d = Set({ "a", "b" }):difference(Set({ "b" })) --> d contains "a"
-#### `intersection(set)`
+#### `intersection(t)`
Return elements common to both sets.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -295,7 +295,7 @@ Remove an element if present, do nothing otherwise.
**Return**:
-- `self` (`T`): Current set instance.
+- `self` (`T`): Current set.
**Example**:
@@ -305,13 +305,13 @@ s = Set({ "a", "b" }):remove("b") --> s contains "a"
-#### `symmetric_difference(set)`
+#### `symmetric_difference(t)`
Return elements not shared by both sets.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -330,13 +330,13 @@ d = Set({ "a", "b" }):symmetric_difference(Set({ "b", "c" }))
-#### `union(set)`
+#### `union(t)`
Return a new set with all elements from both.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -363,7 +363,7 @@ Return true if sets have no elements in common.
**Parameters**:
-- `set` (`T`): Other set value.
+- `set` (`T|mods.List`): Other set/list.
**Return**:
@@ -377,13 +377,13 @@ ok = Set({ "a" }):isdisjoint(Set({ "b" })) --> true
-#### `equals(set)`
+#### `equals(t)`
Return true when both sets contain exactly the same members.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -420,13 +420,13 @@ empty = Set({}):isempty() --> true
-#### `issubset(set)`
+#### `issubset(t)`
Return true if all elements of this set are also in another set.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -445,13 +445,13 @@ ok = Set({ "a" }):issubset(Set({ "a", "b" })) --> true
-#### `issuperset(set)`
+#### `issuperset(t)`
Return true if this set contains all elements of another set.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -540,17 +540,59 @@ Return a list of all values in the set.
values = Set({ "a", "b" }):values() --> { "a", "b" }
```
+
+
+#### `tostring()`
+
+Render the set as a string.
+
+**Return**:
+
+- `renderedSet` (`string`): Rendered set string.
+
+**Example**:
+
+```lua
+s = Set({ "b", "a", 1 }):tostring() --> '{ 1, "a", "b" }'
+```
+
+
+
+#### `join(sep?, quoted?)`
+
+Join set values into a string.
+
+**Parameters**:
+
+- `sep?` (`string`): Optional separator value (defaults to `""`).
+- `quoted?` (`boolean`): Optional boolean flag (defaults to `false`).
+
+**Return**:
+
+- `joined` (`string`): Joined string.
+
+**Example**:
+
+```lua
+s = Set({ "b", "a" }):join(", ") --> "a, b"
+s = Set({ "b", "a" }):join(", ", true) --> '"a", "b"'
+```
+
+> [!NOTE]
+>
+> Join order is not guaranteed.
+
### Metamethods
-#### `__add(set)`
+#### `__add(t)`
Return the union of two sets using `+`.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -570,13 +612,13 @@ u = a + b --> { a = true, b = true, c = true }
-#### `__bor(set)`
+#### `__bor(t)`
Return the union of two sets using `|`.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -596,13 +638,13 @@ u = a | b --> { a = true, b = true, c = true }
-#### `__band(set)`
+#### `__band(t)`
Return the intersection of two sets using `&`.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -622,13 +664,13 @@ i = a & b --> { b = true }
-#### `__bxor(set)`
+#### `__bxor(t)`
Return elements present in exactly one set using `^`.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -648,13 +690,13 @@ d = a ^ b --> { a = true, c = true }
-#### `__eq(set)`
+#### `__eq(t)`
Return true if both sets contain exactly the same members using `==`.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -672,13 +714,13 @@ ok = Set({ "a", "b" }) == Set({ "b", "a" }) --> true
-#### `__le(set)`
+#### `__le(t)`
Return true if the left set is a subset of the right set using `<=`.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `t` (`mods.Set|mods.List|table`): Other set/list.
**Return**:
@@ -704,7 +746,7 @@ Return true if the left set is a proper subset of the right set using `<`.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `set` (`mods.Set|table`): Other set.
**Return**:
@@ -726,7 +768,7 @@ Return the difference of two sets using `-`.
**Parameters**:
-- `set` (`mods.Set|table`): Other set value.
+- `set` (`mods.Set|table`): Other set.
**Return**:
@@ -743,3 +785,19 @@ d = a - b --> { a = true }
> [!NOTE]
>
> `__sub` is the operator form of `:difference(set)`.
+
+
+
+#### `__tostring()`
+
+Render the set via `tostring(set)`.
+
+**Return**:
+
+- `renderedSet` (`string`): Rendered set string.
+
+**Example**:
+
+```lua
+s = tostring(Set({ "b", "a", 1 })) --> '{ 1, "a", "b" }'
+```
diff --git a/docs/src/modules/str.md b/docs/src/modules/str.md
index f449cb1..fa3074c 100644
--- a/docs/src/modules/str.md
+++ b/docs/src/modules/str.md
@@ -1,10 +1,11 @@
---
-description: "String utility helpers modeled after Python's `str`."
+description:
+ "String operations for searching, splitting, trimming, and formatting text."
---
# `str`
-String utility helpers modeled after Python's `str`.
+String operations for searching, splitting, trimming, and formatting text.
## Usage
diff --git a/docs/src/modules/stringcase.md b/docs/src/modules/stringcase.md
index a0a2036..68aa650 100644
--- a/docs/src/modules/stringcase.md
+++ b/docs/src/modules/stringcase.md
@@ -1,10 +1,10 @@
---
-description: "String case conversion helpers."
+description: "String case conversion and word splitting."
---
# `stringcase`
-String case conversion helpers.
+String case conversion and word splitting.
## Usage
diff --git a/docs/src/modules/tbl.md b/docs/src/modules/tbl.md
index f089a1d..432fc0a 100644
--- a/docs/src/modules/tbl.md
+++ b/docs/src/modules/tbl.md
@@ -1,10 +1,11 @@
---
-description: "Utility functions for working with Lua tables."
+description:
+ "Table operations for querying, copying, merging, and transforming tables."
---
# `tbl`
-Utility functions for working with Lua tables.
+Table operations for querying, copying, merging, and transforming tables.
## Usage
@@ -52,6 +53,13 @@ print(tbl.count({ a = 1, b = 2 })) --> 2
| [`update(t1, t2)`](#fn-update) | Merge entries from `t2` into `t1` and return `t1`. |
| [`values(t)`](#fn-values) | Return a list of all values in the table. |
+**Iteration**:
+
+| Function | Description |
+| ------------------------------- | -------------------------------------------- |
+| [`foreach(t, fn)`](#fn-foreach) | Call a function for each value in the table. |
+| [`spairs(t)`](#fn-spairs) | Iterate key-value pairs in sorted key order. |
+
### Basics
Core table utilities for clearing and counting.
@@ -153,8 +161,8 @@ Filter entries by a value predicate.
**Parameters**:
-- `t` (`table`): Input table.
-- `pred` (`fun(v:any):boolean`): Value predicate.
+- `t` (`table`): Input table.
+- `pred` (`fun(value:V):boolean`): Value predicate.
**Return**:
@@ -176,12 +184,12 @@ Find the first key whose value equals the given value.
**Parameters**:
-- `t` (`{[T1]:T2}`): Input table.
-- `v` (`T2`): Value to find.
+- `t` (`table`): Input table.
+- `v` (`V`): Value to find.
**Return**:
-- `key` (`T1?`): First matching key, or `nil` when not found.
+- `key` (`K?`): First matching key, or `nil` when not found.
**Example**:
@@ -220,12 +228,12 @@ Find first value and key matching predicate.
**Parameters**:
- `t` (`table`): Input table.
-- `pred` (`fun(v:T1,k:T2):boolean`): Predicate function.
+- `pred` (`fun(key:K,value:V):boolean`): Predicate function.
**Return**:
-- `matchedValue` (`T1?`): First matching value, or `nil` when not found.
-- `k` (`T2?`): Key for the first matching value, or `nil` when not found.
+- `value` (`V?`): First matching value, or `nil` when not found.
+- `key` (`K?`): Key for the first matching value, or `nil` when not found.
**Example**:
@@ -272,11 +280,11 @@ Invert keys/values into new table.
**Parameters**:
-- `t` (`{[T1]:T2}`): Input table.
+- `t` (`table`): Input table.
**Return**:
-- `inverted` (`{[T2]:T1}`): Inverted table (`value -> key`).
+- `inverted` (`table`): Inverted table (`value -> key`).
**Example**:
@@ -312,11 +320,11 @@ Return a list of all keys in the table.
**Parameters**:
-- `t` (`{[T]:any}`): Input table.
+- `t` (`table`): Input table.
**Return**:
-- `keys` (`mods.List`): List of keys in `t`.
+- `keys` (`mods.List`): List of keys in `t`.
**Example**:
@@ -332,12 +340,12 @@ Return a new table by mapping each value (keys preserved).
**Parameters**:
-- `t` (`{[T1]:T2}`): Input table.
-- `fn` (`fun(v:T2):T3`): Mapping function.
+- `t` (`table`): Input table.
+- `fn` (`fun(value:V):T`): Mapping function.
**Return**:
-- `mapped` (`{[T1]:T3}`): New table with mapped values.
+- `mapped` (`table`): New table with mapped values.
**Example**:
@@ -355,12 +363,12 @@ Return a new table by mapping each key-value pair.
**Parameters**:
-- `t` (`{[T1]:T2}`): Input table.
-- `fn` (`fun(k:T1,`): v:T2):T3 Key-value mapping function.
+- `t` (`table`): Input table.
+- `fn` (`fun(key:K,`): value:V):T Key-value mapping function.
**Return**:
-- `mapped` (`{[T1]:T3}`): New table with mapped values.
+- `mapped` (`table`): New table with mapped values.
**Example**:
@@ -387,7 +395,7 @@ Merge entries from `t2` into `t1` and return `t1`.
**Return**:
-- `t1` (`T`): Updated `t1` table.
+- `table` (`T`): Updated `t1` table.
**Example**:
@@ -404,14 +412,63 @@ Return a list of all values in the table.
**Parameters**:
-- `t` (`{[any]:T}`): Input table.
+- `t` (`table`): Input table.
**Return**:
-- `values` (`mods.List`): List of values in `t`.
+- `values` (`mods.List`): List of values in `t`.
**Example**:
```lua
vals = values({ a = 1, b = 2 }) --> { 1, 2 }
```
+
+### Iteration
+
+Iterators and ordered traversal helpers.
+
+#### `foreach(t, fn)`
+
+Call a function for each value in the table.
+
+**Parameters**:
+
+- `t` (`table`): Input table.
+- `fn` (`fun(value:V,`): key:K) Function invoked for each entry.
+
+**Return**:
+
+- `none` (`nil`)
+
+**Example**:
+
+```lua
+foreach({ a = 1, b = 2 }, function(v, k)
+ print(k, v)
+end)
+```
+
+
+
+#### `spairs(t)`
+
+Iterate key-value pairs in sorted key order.
+
+**Parameters**:
+
+- `t` (`T`): Input table.
+
+**Return**:
+
+- `table, index?: K):(K, V) iterator Sorted pairs
+ iterator.
+- **value** (`T`)
+
+**Example**:
+
+```lua
+for k, v in spairs({ b = 2, a = 1 }) do
+ print(k, v)
+end
+```
diff --git a/docs/src/modules/template.md b/docs/src/modules/template.md
index 6488c2d..292521d 100644
--- a/docs/src/modules/template.md
+++ b/docs/src/modules/template.md
@@ -1,10 +1,10 @@
---
-description: "Interpolate string placeholders of the form {{."
+description: "String template rendering with {{."
---
# `template`
-Interpolate string placeholders of the form {{...}}.
+String template rendering with {{...}} placeholders.
## Usage
diff --git a/docs/src/modules/utils.md b/docs/src/modules/utils.md
index d3a40a8..e7fa93e 100644
--- a/docs/src/modules/utils.md
+++ b/docs/src/modules/utils.md
@@ -1,10 +1,10 @@
---
-description: "Small shared utility helpers used by modules in this library."
+description: "Shared utility helpers used across the Mods library."
---
# `utils`
-Small shared utility helpers used by modules in this library.
+Shared utility helpers used across the Mods library.
## Usage
@@ -16,6 +16,16 @@ print(utils.quote('hello "world"')) --> 'hello "world"'
## Functions
+| Function | Description |
+| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
+| [`quote(v)`](#fn-quote) | Smart-quote a string for readable Lua-like output. |
+| [`keypath(...)`](#fn-keypath) | Format a key chain as a Lua-like table access path. |
+| [`args_repr(v)`](#fn-args-repr) | Format a list-like table as a comma-separated argument string. |
+| [`assert_arg(argn, v, validator?, optional?, msg?)`](#fn-assert-arg) | Assert argument value using [`mods.validate`](/modules/validate) and raise a Lua error on failure. |
+| [`validate(name, v, validator?, optional?, msg?)`](#fn-validate) | Validate a value using [`mods.validate`](/modules/validate) and raise a Lua error on failure. |
+| [`validate(path, v, validator?, optional?, msg?)`](#fn-validate) | Validate a value using [`mods.validate`](/modules/validate) and raise a Lua error on failure. |
+| [`lazy_module(name, err?)`](#fn-lazy-module) | Return a lazy proxy for a module. |
+
### `quote(v)`
@@ -60,9 +70,33 @@ p3 = utils.keypath("ctx", "invalid-key") --> 'ctx["invalid-key"]'
p4 = utils.keypath() --> ""
```
+
+
+### `args_repr(v)`
+
+Format a list-like table as a comma-separated argument string.
+
+**Parameters**:
+
+- `v` (`table|any`): Value to format.
+
+**Return**:
+
+- `out` (`string`): Argument list string.
+
+**Example**:
+
+```lua
+utils.args_repr({ "a", 1, true }) --> '"a", 1, true'
+```
+
+> [!NOTE]
+>
+> Requires [`inspect`](https://github.com/kikito/inspect.lua)
+
-### `assert_arg(argn, v, tp?, level?, msg?)`
+### `assert_arg(argn, v, validator?, optional?, msg?)`
Assert argument value using [`mods.validate`](/modules/validate) and raise a Lua
error on failure.
@@ -71,22 +105,23 @@ error on failure.
- `argn` (`integer`): Argument index for error context.
- `v` (`T`): Value to check.
-- `tp?` (`modsIsType`): Validator name (defaults to `"truthy"`).
-- `level?` (`integer`): Optional error level for `error(...)` (defaults to `2`).
+- `validator?` (`modsValidatorName`): Validator name (defaults to `"truthy"`).
+- `optional?` (`boolean`): Skip errors when `v` is `nil` (defaults to `false`).
- `msg?` (`string`): Optional override template passed to
[`mods.validate`](/modules/validate).
**Return**:
-- `validatedValue` (`T`): Same input value on success.
+- `validatedValue` (`T`): Same input value on success, or `nil` when optional.
**Example**:
```lua
utils.assert_arg(1, "ok", "string") --> "ok"
+utils.assert_arg(2, nil, "string", true) --> nil
utils.assert_arg(2, 123, "string")
--> raises: bad argument #2 (expected string, got number)
-utils.assert_arg(3, "x", "number", 2, "need {{expected}}, got {{got}}")
+utils.assert_arg(3, "x", "number", false, "need {{expected}}, got {{got}}")
--> raises: bad argument #3 (need number, got string)
```
@@ -94,3 +129,97 @@ utils.assert_arg(3, "x", "number", 2, "need {{expected}}, got {{got}}")
>
> When the caller function name is available, error text includes
> `to ''` (Lua-style bad argument context).
+
+
+
+### `validate(name, v, validator?, optional?, msg?)`
+
+Validate a value using [`mods.validate`](/modules/validate) and raise a Lua
+error on failure.
+
+**Parameters**:
+
+- `name` (`string`): Name for the error prefix.
+- `v` (`any`): Value to validate.
+- `validator?` (`modsValidatorName`): Validator name (defaults to `"truthy"`).
+- `optional?` (`boolean`): Skip errors when `v` is `nil` (defaults to `false`).
+- `msg?` (`string`): Optional override template passed to
+ [`mods.validate`](/modules/validate).
+
+**Return**:
+
+- `none` (`nil`)
+
+**Example**:
+
+```lua
+utils.validate("path", "ok", "string")
+utils.validate("name", nil, "string", true)
+utils.validate("count", "x", "number")
+--> raises: count: expected number, got string
+```
+
+
+
+### `validate(path, v, validator?, optional?, msg?)`
+
+Validate a value using [`mods.validate`](/modules/validate) and raise a Lua
+error on failure.
+
+**Parameters**:
+
+- `path` (`table`): Path parts for the error name.
+- `v` (`any`): Value to validate.
+- `validator?` (`modsValidatorName`): Validator name (defaults to `"truthy"`).
+- `optional?` (`boolean`): Skip errors when `v` is `nil` (defaults to `false`).
+- `msg?` (`string`): Optional override template passed to
+ [`mods.validate`](/modules/validate).
+
+**Return**:
+
+- `none` (`nil`)
+
+**Example**:
+
+```lua
+utils.validate({ "ctx", "users", 1, "name" }, nil, "string", true)
+utils.validate({ "ctx", "users", 1, "name" }, 123, "string")
+--> raises: ctx.users[1].name: expected string, got number
+```
+
+> [!NOTE]
+>
+> On failure, `path` is rendered with
+> [`mods.utils.keypath`](/modules/utils#fn-keypath).
+
+
+
+### `lazy_module(name, err?)`
+
+Return a lazy proxy for a module.
+
+The proxy rewrites its metamethods after first access while keeping the proxy
+table itself free of cached fields.
+
+**Parameters**:
+
+- `name` (`string`): Module name passed to `require`.
+- `err?` (`string`): Optional error message raised when loading fails.
+
+**Return**:
+
+- `module` (`{}`): Lazy proxy for the loaded module.
+
+**Example**:
+
+```lua
+local fs = utils.lazy_module("mods.fs")
+print(fs.exists("README.md"))
+
+local repr = utils.lazy_module("mods.repr")
+print(repr({ a = 1 }))
+```
+
+> [!NOTE]
+>
+> Supports both table-returning modules and function-returning modules.
diff --git a/docs/src/modules/validate.md b/docs/src/modules/validate.md
index 4605e2d..f904c2d 100644
--- a/docs/src/modules/validate.md
+++ b/docs/src/modules/validate.md
@@ -1,29 +1,29 @@
---
-description: "Validation checks for values and filesystem path types."
+description: "Validation helpers for Lua values and filesystem path types."
---
# `validate`
-Validation checks for values and filesystem path types.
+Validation helpers for Lua values and filesystem path types.
## Usage
```lua
local validate = require "mods.validate"
-ok, err = validate.number("nope") --> false, "expected number, got string"
+ok, err = validate.number("nope") --> false, "number expected, got string"
ok, err = validate(123, "number") --> true, nil
```
## `validate()`
-`validate(v, tp)` dispatches to the registered validator `tp`. If `tp` is
-omitted, it defaults to `"truthy"`.
+`validate(v, validator)` dispatches to the registered validator. If `validator`
+is omitted, it defaults to `"truthy"`.
```lua
-validate() --> false, "expected truthy value, got no value"
+validate() --> false, "truthy value expected, got no value"
validate(1) --> true, nil
-validate(1, "nil") --> false, "expected nil, got number"
+validate(1, "nil") --> false, "nil expected, got number"
```
## Validator Names
@@ -35,63 +35,77 @@ validate.number(1) --> true, nil
validate.NumBer(1) --> true, nil
```
-`tp` in `validate(v, tp)` is matched as-is (case-sensitive):
+`validator` in `validate(v, validator)` is matched as-is (case-sensitive):
```lua
validate(1, "number") --> true, nil
-validate(1, "NuMbEr") --> false, "expected NuMbEr, got number"
+validate(1, "NuMbEr") --> false, "NuMbEr expected, got number"
+```
+
+## Custom Messages
+
+Validator functions accept an optional template override as the second argument:
+validate.number(v, "need {{expected}}, got {{got}}")`.
+
+You can also set `validate.messages.` to define default templates per
+validator.
+
+```lua
+validate.string(123, "want {{expected}}, got {{got}}")
+--> false, "want string, got number"
```
## Functions
**Type Checks**:
-| Function | Description |
-| ----------------------------- | -------------------------------------------------------------------------------------------- |
-| [`boolean(v)`](#fn-boolean) | Returns `true` when `v` is a boolean. Otherwise returns `false` and an error message. |
-| [`function(v)`](#fn-function) | Returns `true` when `v` is a function. Otherwise returns `false` and an error message. |
-| [`nil(v)`](#fn-nil) | Returns `true` when `v` is `nil`. Otherwise returns `false` and an error message. |
-| [`number(v)`](#fn-number) | Returns `true` when `v` is a number. Otherwise returns `false` and an error message. |
-| [`string(v)`](#fn-string) | Returns `true` when `v` is a string. Otherwise returns `false` and an error message. |
-| [`table(v)`](#fn-table) | Returns `true` when `v` is a table. Otherwise returns `false` and an error message. |
-| [`thread(v)`](#fn-thread) | Returns `true` when `v` is a thread. Otherwise returns `false` and an error message. |
-| [`userdata(v)`](#fn-userdata) | Returns `true` when `v` is a userdata value. Otherwise returns `false` and an error message. |
+| Function | Description |
+| ----------------------------------- | -------------------------------------------------------------------------------------------- |
+| [`boolean(v, msg?)`](#fn-boolean) | Returns `true` when `v` is a boolean. Otherwise returns `false` and an error message. |
+| [`function(v, msg?)`](#fn-function) | Returns `true` when `v` is a function. Otherwise returns `false` and an error message. |
+| [`nil(v, msg?)`](#fn-nil) | Returns `true` when `v` is `nil`. Otherwise returns `false` and an error message. |
+| [`number(v, msg?)`](#fn-number) | Returns `true` when `v` is a number. Otherwise returns `false` and an error message. |
+| [`string(v, msg?)`](#fn-string) | Returns `true` when `v` is a string. Otherwise returns `false` and an error message. |
+| [`table(v, msg?)`](#fn-table) | Returns `true` when `v` is a table. Otherwise returns `false` and an error message. |
+| [`thread(v, msg?)`](#fn-thread) | Returns `true` when `v` is a thread. Otherwise returns `false` and an error message. |
+| [`userdata(v, msg?)`](#fn-userdata) | Returns `true` when `v` is a userdata value. Otherwise returns `false` and an error message. |
**Value Checks**:
-| Function | Description |
-| ----------------------------- | ------------------------------------------------------------------------------------------- |
-| [`false(v)`](#fn-false) | Returns `true` when `v` is exactly `false`. Otherwise returns `false` and an error message. |
-| [`true(v)`](#fn-true) | Returns `true` when `v` is exactly `true`. Otherwise returns `false` and an error message. |
-| [`falsy(v)`](#fn-falsy) | Returns `true` when `v` is falsy. Otherwise returns `false` and an error message. |
-| [`callable(v)`](#fn-callable) | Returns `true` when `v` is callable. Otherwise returns `false` and an error message. |
-| [`integer(v)`](#fn-integer) | Returns `true` when `v` is an integer. Otherwise returns `false` and an error message. |
-| [`truthy(v)`](#fn-truthy) | Returns `true` when `v` is truthy. Otherwise returns `false` and an error message. |
+| Function | Description |
+| ----------------------------------- | ------------------------------------------------------------------------------------------- |
+| [`false(v, msg?)`](#fn-false) | Returns `true` when `v` is exactly `false`. Otherwise returns `false` and an error message. |
+| [`true(v, msg?)`](#fn-true) | Returns `true` when `v` is exactly `true`. Otherwise returns `false` and an error message. |
+| [`falsy(v, msg?)`](#fn-falsy) | Returns `true` when `v` is falsy. Otherwise returns `false` and an error message. |
+| [`callable(v, msg?)`](#fn-callable) | Returns `true` when `v` is callable. Otherwise returns `false` and an error message. |
+| [`integer(v, msg?)`](#fn-integer) | Returns `true` when `v` is an integer. Otherwise returns `false` and an error message. |
+| [`truthy(v, msg?)`](#fn-truthy) | Returns `true` when `v` is truthy. Otherwise returns `false` and an error message. |
**Path Checks**:
-| Function | Description |
-| ------------------------- | ------------------------------------------------------------------------------------------------------- |
-| [`block(v)`](#fn-block) | Returns `true` when `v` is a block device path. Otherwise returns `false` and an error message. |
-| [`char(v)`](#fn-char) | Returns `true` when `v` is a char device path. Otherwise returns `false` and an error message. |
-| [`device(v)`](#fn-device) | Returns `true` when `v` is a block or char device path. Otherwise returns `false` and an error message. |
-| [`dir(v)`](#fn-dir) | Returns `true` when `v` is a directory path. Otherwise returns `false` and an error message. |
-| [`fifo(v)`](#fn-fifo) | Returns `true` when `v` is a FIFO path. Otherwise returns `false` and an error message. |
-| [`file(v)`](#fn-file) | Returns `true` when `v` is a file path. Otherwise returns `false` and an error message. |
-| [`link(v)`](#fn-link) | Returns `true` when `v` is a symlink path. Otherwise returns `false` and an error message. |
-| [`socket(v)`](#fn-socket) | Returns `true` when `v` is a socket path. Otherwise returns `false` and an error message. |
+| Function | Description |
+| ------------------------------- | ------------------------------------------------------------------------------------------------------- |
+| [`path(v, msg?)`](#fn-path) | Returns `true` when `v` is a valid filesystem path. Otherwise returns `false` and an error message. |
+| [`block(v, msg?)`](#fn-block) | Returns `true` when `v` is a block device path. Otherwise returns `false` and an error message. |
+| [`char(v, msg?)`](#fn-char) | Returns `true` when `v` is a char device path. Otherwise returns `false` and an error message. |
+| [`device(v, msg?)`](#fn-device) | Returns `true` when `v` is a block or char device path. Otherwise returns `false` and an error message. |
+| [`dir(v, msg?)`](#fn-dir) | Returns `true` when `v` is a directory path. Otherwise returns `false` and an error message. |
+| [`fifo(v, msg?)`](#fn-fifo) | Returns `true` when `v` is a FIFO path. Otherwise returns `false` and an error message. |
+| [`file(v, msg?)`](#fn-file) | Returns `true` when `v` is a file path. Otherwise returns `false` and an error message. |
+| [`link(v, msg?)`](#fn-link) | Returns `true` when `v` is a symlink path. Otherwise returns `false` and an error message. |
+| [`socket(v, msg?)`](#fn-socket) | Returns `true` when `v` is a socket path. Otherwise returns `false` and an error message. |
**Validator API**:
-| Function | Description |
-| --------------------------------------------- | -------------------------------------------------- |
-| [`register(name, check, msg?)`](#fn-register) | Register or override a validator function by name. |
+| Function | Description |
+| ------------------------------------------------------ | -------------------------------------------------- |
+| [`register(name, validator, template?)`](#fn-register) | Register or override a validator function by name. |
### Type Checks
Basic Lua type validators (and their negated variants).
-#### `boolean(v)`
+#### `boolean(v, msg?)`
Returns `true` when `v` is a boolean. Otherwise returns `false` and an error
message.
@@ -99,6 +113,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -109,12 +124,12 @@ message.
```lua
ok, err = validate.boolean(true) --> true, nil
-ok, err = validate.boolean(1) --> false, "expected boolean, got number"
+ok, err = validate.boolean(1) --> false, "boolean expected, got number"
```
-#### `function(v)`
+#### `function(v, msg?)`
Returns `true` when `v` is a function. Otherwise returns `false` and an error
message.
@@ -122,6 +137,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -133,12 +149,12 @@ message.
```lua
ok, err = validate.Function(function() end) --> true, nil
ok, err = validate.Function(1)
---> false, "expected function, got number"
+--> false, "function expected, got number"
```
-#### `nil(v)`
+#### `nil(v, msg?)`
Returns `true` when `v` is `nil`. Otherwise returns `false` and an error
message.
@@ -146,6 +162,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -156,12 +173,12 @@ message.
```lua
ok, err = validate.Nil(nil) --> true, nil
-ok, err = validate.Nil(0) --> false, "expected nil, got number"
+ok, err = validate.Nil(0) --> false, "nil expected, got number"
```
-#### `number(v)`
+#### `number(v, msg?)`
Returns `true` when `v` is a number. Otherwise returns `false` and an error
message.
@@ -169,6 +186,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -179,12 +197,12 @@ message.
```lua
ok, err = validate.number(42) --> true, nil
-ok, err = validate.number("x") --> false, "expected number, got string"
+ok, err = validate.number("x") --> false, "number expected, got string"
```
-#### `string(v)`
+#### `string(v, msg?)`
Returns `true` when `v` is a string. Otherwise returns `false` and an error
message.
@@ -192,6 +210,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -202,12 +221,12 @@ message.
```lua
ok, err = validate.string("hello") --> true, nil
-ok, err = validate.string(1) --> false, "expected string, got number"
+ok, err = validate.string(1) --> false, "string expected, got number"
```
-#### `table(v)`
+#### `table(v, msg?)`
Returns `true` when `v` is a table. Otherwise returns `false` and an error
message.
@@ -215,6 +234,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -225,12 +245,12 @@ message.
```lua
ok, err = validate.table({}) --> true, nil
-ok, err = validate.table(1) --> false, "expected table, got number"
+ok, err = validate.table(1) --> false, "table expected, got number"
```
-#### `thread(v)`
+#### `thread(v, msg?)`
Returns `true` when `v` is a thread. Otherwise returns `false` and an error
message.
@@ -238,6 +258,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -249,12 +270,12 @@ message.
```lua
co = coroutine.create(function() end)
ok, err = validate.thread(co) --> true, nil
-ok, err = validate.thread(1) --> false, "expected thread, got number"
+ok, err = validate.thread(1) --> false, "thread expected, got number"
```
-#### `userdata(v)`
+#### `userdata(v, msg?)`
Returns `true` when `v` is a userdata value. Otherwise returns `false` and an
error message.
@@ -262,6 +283,7 @@ error message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -272,7 +294,7 @@ error message.
```lua
ok, err = validate.userdata(io.stdout) --> true, nil
-ok, err = validate.userdata(1) --> false, "expected userdata, got number"
+ok, err = validate.userdata(1) --> false, "userdata expected, got number"
```
### Value Checks
@@ -280,7 +302,7 @@ ok, err = validate.userdata(1) --> false, "expected userdata, got number
Value-state validators (exact true/false, truthy/falsy, callable, integer).
-#### `false(v)`
+#### `false(v, msg?)`
Returns `true` when `v` is exactly `false`. Otherwise returns `false` and an
error message.
@@ -288,6 +310,7 @@ error message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -298,12 +321,12 @@ error message.
```lua
ok, err = validate.False(false) --> true, nil
-ok, err = validate.False(true) --> false, "expected false, got true"
+ok, err = validate.False(true) --> false, "false value expected, got true"
```
-#### `true(v)`
+#### `true(v, msg?)`
Returns `true` when `v` is exactly `true`. Otherwise returns `false` and an
error message.
@@ -311,6 +334,7 @@ error message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -321,12 +345,12 @@ error message.
```lua
ok, err = validate.True(true) --> true, nil
-ok, err = validate.True(false) --> false, "expected true, got false"
+ok, err = validate.True(false) --> false, "true value expected, got false"
```
-#### `falsy(v)`
+#### `falsy(v, msg?)`
Returns `true` when `v` is falsy. Otherwise returns `false` and an error
message.
@@ -334,6 +358,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -344,12 +369,12 @@ message.
```lua
ok, err = validate.falsy(false) --> true, nil
-ok, err = validate.falsy(1) --> false, "expected falsy, got number"
+ok, err = validate.falsy(1) --> false, "falsy value expected, got 1"
```
-#### `callable(v)`
+#### `callable(v, msg?)`
Returns `true` when `v` is callable. Otherwise returns `false` and an error
message.
@@ -357,6 +382,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -367,12 +393,12 @@ message.
```lua
ok, err = validate.callable(type) --> true, nil
-ok, err = validate.callable(1) --> false, "expected callable, got number"
+ok, err = validate.callable(1) --> false, "callable value expected, got 1"
```
-#### `integer(v)`
+#### `integer(v, msg?)`
Returns `true` when `v` is an integer. Otherwise returns `false` and an error
message.
@@ -380,6 +406,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -390,12 +417,12 @@ message.
```lua
ok, err = validate.integer(1) --> true, nil
-ok, err = validate.integer(1.5) --> false, "expected integer, got 1.5"
+ok, err = validate.integer(1.5) --> false, "integer value expected, got 1.5"
```
-#### `truthy(v)`
+#### `truthy(v, msg?)`
Returns `true` when `v` is truthy. Otherwise returns `false` and an error
message.
@@ -403,6 +430,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -413,7 +441,7 @@ message.
```lua
ok, err = validate.truthy(1) --> true, nil
-ok, err = validate.truthy(false) --> false, "expected truthy, got boolean"
+ok, err = validate.truthy(false) --> false, "truthy value expected, got false"
```
### Path Checks
@@ -424,9 +452,32 @@ Filesystem path-kind validators backed by LuaFileSystem (`lfs`).
>
> Path checks require **LuaFileSystem**
> ([`lfs`](https://github.com/lunarmodules/luafilesystem)) and raise an error if
-> it is not installed.
+> it is not installed.
+
+#### `path(v, msg?)`
+
+Returns `true` when `v` is a valid filesystem path. Otherwise returns `false`
+and an error message.
+
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
+
+**Return**:
-#### `block(v)`
+- `isValid` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
+```lua
+ok, err = validate.path("README.md")
+```
+
+
+
+#### `block(v, msg?)`
Returns `true` when `v` is a block device path. Otherwise returns `false` and an
error message.
@@ -434,6 +485,7 @@ error message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -448,7 +500,7 @@ ok, err = validate.block(".")
-#### `char(v)`
+#### `char(v, msg?)`
Returns `true` when `v` is a char device path. Otherwise returns `false` and an
error message.
@@ -456,6 +508,7 @@ error message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -470,7 +523,7 @@ ok, err = validate.char(".")
-#### `device(v)`
+#### `device(v, msg?)`
Returns `true` when `v` is a block or char device path. Otherwise returns
`false` and an error message.
@@ -478,6 +531,7 @@ Returns `true` when `v` is a block or char device path. Otherwise returns
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -492,7 +546,7 @@ ok, err = validate.device(".")
-#### `dir(v)`
+#### `dir(v, msg?)`
Returns `true` when `v` is a directory path. Otherwise returns `false` and an
error message.
@@ -500,6 +554,7 @@ error message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -514,7 +569,7 @@ ok, err = validate.dir(".")
-#### `fifo(v)`
+#### `fifo(v, msg?)`
Returns `true` when `v` is a FIFO path. Otherwise returns `false` and an error
message.
@@ -522,6 +577,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -536,7 +592,7 @@ ok, err = validate.fifo(".")
-#### `file(v)`
+#### `file(v, msg?)`
Returns `true` when `v` is a file path. Otherwise returns `false` and an error
message.
@@ -544,6 +600,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -558,7 +615,7 @@ ok, err = validate.file(".")
-#### `link(v)`
+#### `link(v, msg?)`
Returns `true` when `v` is a symlink path. Otherwise returns `false` and an
error message.
@@ -566,6 +623,7 @@ error message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -580,7 +638,7 @@ ok, err = validate.link(".")
-#### `socket(v)`
+#### `socket(v, msg?)`
Returns `true` when `v` is a socket path. Otherwise returns `false` and an error
message.
@@ -588,6 +646,7 @@ message.
**Parameters**:
- `v` (`any`): Value to validate.
+- `msg?` (`string`): Optional override template.
**Return**:
@@ -604,15 +663,15 @@ ok, err = validate.socket(".")
-#### `register(name, check, msg?)`
+#### `register(name, validator, template?)`
Register or override a validator function by name.
**Parameters**:
- `name` (`string`): Validator name.
-- `check` (`fun(v:any):(ok:boolean)`): Validator function.
-- `msg?` (`string`): Optional default message template.
+- `validator` (`fun(v:any):(ok:boolean)`): Validator function.
+- `template?` (`string`): Optional default message template.
**Return**:
@@ -632,18 +691,22 @@ ok, err = validate(2, "odd") --> false, "2 does not satisfy odd"
> [!NOTE]
>
-> - If `msg` is provided, it becomes the default message template for that
+> - If `template` is provided, it becomes the default message template for that
> validator.
-> - If `msg` is omitted, failures use: `expected {{expected}}, got {{got}}`.
+> - If `template` is omitted, failures use:
+> `{{expected}} expected, got {{got}}`.
## Fields
### `messages`
-Custom error-message templates for validator failures. Set
-`validate.messages.`, where `` is a validator name (for example:
-`number`, `truthy`, `file`). The template is used only when validation fails and
-an error message is returned.
+Custom error-message templates for validator failures.
+
+Set `validate.messages.`, where `` is a validator name (for example:
+`number`, `truthy`, `file`).
+
+The error-message template is used only when validation fails and an error
+message is returned.
```lua
validate.messages.number = "need {{expected}}, got {{got}}"
@@ -664,12 +727,18 @@ ok, err = validate.number("x") --> false, "need number, got string"
> When the passed value is `nil`, rendered value text uses `no value`.
>
> ```lua
-> validate.messages.truthy = "expected {{expected}} value, got {{value}}"
-> validate.truthy(nil) --> false, "expected truthy value, got no value"
+> validate.messages.truthy = "{{expected}} value expected, got {{value}}"
+> validate.truthy(nil) --> false, "truthy value expected, got no value"
> ```
**Default Messages**:
-- Type checks: expected {{expected}}, got {{got}}
-- Value checks: expected {{expected}} value, got {{value}}
+- Type checks: {{expected}} expected, got {{got}}
+- Value checks: {{expected}} value expected, got {{value}}
- Path checks: {{value}} is not a valid {{expected}} path
+ (for `path`: {{value}} is not a valid path)
+
+> [!NOTE]
+>
+> For path checks, if the value is not a `string`, the message falls back to
+> `messages.string` (as if `validate.string` was called).