diff --git a/docs/src/modules/is.md b/docs/src/modules/is.md
index 5e5f8ea..2ce5488 100644
--- a/docs/src/modules/is.md
+++ b/docs/src/modules/is.md
@@ -18,178 +18,344 @@ ok = is.table({}) --> true
> [!NOTE]
>
-> Function names exist in both lowercase and capitalized forms, and `is` is also
-> callable as `is(v, tp)`.
+> Function names are case-insensitive.
>
> ```lua
-> is.table({}) --> true
-> is.Table({}) --> true
-> is("hello", "string") --> true
-> is("hello", "String") --> true
+> is.table({}) --> true
+> is.Table({}) --> true
+> is.tAbLe({}) --> true
> ```
-## Dependencies
+## `is()`
-Dependencies below are lazy-loaded 💤 on first access.
+`is` is also callable as `is(value, type)` to check if a value is of a given
+type.
-- [`lfs`](https://github.com/lunarmodules/luafilesystem) (optional; required
- only for filesystem/path checks)
+```lua
+is("hello", "string") --> true
+is("hello", "String") --> true
+is("hello", "STRING") --> true
+```
## Functions
**Type Checks**:
-| Function | Description |
-| ----------------------- | -------------------------------------- |
-| [`boolean`](#boolean) | Returns `true` when `v` is a boolean. |
-| [`Function`](#function) | Returns `true` when `v` is a function. |
-| [`Nil`](#nil) | Returns `true` when `v` is `nil`. |
-| [`number`](#number) | Returns `true` when `v` is a number. |
-| [`string`](#string) | Returns `true` when `v` is a string. |
-| [`table`](#table) | Returns `true` when `v` is a table. |
-| [`thread`](#thread) | Returns `true` when `v` is a thread. |
-| [`userdata`](#userdata) | Returns `true` when `v` is userdata. |
+| Function | Description |
+| ----------------------------- | -------------------------------------- |
+| [`boolean(v)`](#fn-boolean) | Returns `true` when `v` is a boolean. |
+| [`function(v)`](#fn-function) | Returns `true` when `v` is a function. |
+| [`nil(v)`](#fn-nil) | Returns `true` when `v` is `nil`. |
+| [`number(v)`](#fn-number) | Returns `true` when `v` is a number. |
+| [`string(v)`](#fn-string) | Returns `true` when `v` is a string. |
+| [`table(v)`](#fn-table) | Returns `true` when `v` is a table. |
+| [`thread(v)`](#fn-thread) | Returns `true` when `v` is a thread. |
+| [`userdata(v)`](#fn-userdata) | Returns `true` when `v` is userdata. |
**Value Checks**:
-| Function | Description |
-| ----------------------- | ------------------------------------------- |
-| [`False`](#false) | Returns `true` when `v` is exactly `false`. |
-| [`True`](#true) | Returns `true` when `v` is exactly `true`. |
-| [`falsy`](#falsy) | Returns `true` when `v` is falsy. |
-| [`callable`](#callable) | Returns `true` when `v` is callable. |
-| [`integer`](#integer) | Returns `true` when `v` is an integer. |
-| [`truthy`](#truthy) | Returns `true` when `v` is truthy. |
+| Function | Description |
+| ----------------------------- | ------------------------------------------- |
+| [`false(v)`](#fn-false) | Returns `true` when `v` is exactly `false`. |
+| [`true(v)`](#fn-true) | Returns `true` when `v` is exactly `true`. |
+| [`falsy(v)`](#fn-falsy) | Returns `true` when `v` is falsy. |
+| [`callable(v)`](#fn-callable) | Returns `true` when `v` is callable. |
+| [`integer(v)`](#fn-integer) | Returns `true` when `v` is an integer. |
+| [`truthy(v)`](#fn-truthy) | Returns `true` when `v` is truthy. |
**Path Checks**:
-| Function | Description |
-| ------------------- | ------------------------------------------------------- |
-| [`block`](#block) | Returns `true` when `v` is a block device path. |
-| [`char`](#char) | Returns `true` when `v` is a char device path. |
-| [`device`](#device) | Returns `true` when `v` is a block or char device path. |
-| [`dir`](#dir) | Returns `true` when `v` is a directory path. |
-| [`fifo`](#fifo) | Returns `true` when `v` is a FIFO path. |
-| [`file`](#file) | Returns `true` when `v` is a file path. |
-| [`link`](#link) | Returns `true` when `v` is a symlink path. |
-| [`socket`](#socket) | Returns `true` when `v` is a socket path. |
+| Function | Description |
+| ------------------------- | ------------------------------------------------------- |
+| [`block(v)`](#fn-block) | Returns `true` when `v` is a block device path. |
+| [`char(v)`](#fn-char) | Returns `true` when `v` is a char device path. |
+| [`device(v)`](#fn-device) | Returns `true` when `v` is a block or char device path. |
+| [`dir(v)`](#fn-dir) | Returns `true` when `v` is a directory path. |
+| [`fifo(v)`](#fn-fifo) | Returns `true` when `v` is a FIFO path. |
+| [`file(v)`](#fn-file) | Returns `true` when `v` is a file path. |
+| [`link(v)`](#fn-link) | Returns `true` when `v` is a symlink path. |
+| [`socket(v)`](#fn-socket) | Returns `true` when `v` is a socket path. |
### Type Checks
-Core Lua type checks (`type(v)` family).
+Core Lua type checks (`type(v)` family).
-#### `boolean`
+#### `boolean(v)`
Returns `true` when `v` is a boolean.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.boolean(true)
```
-#### `Function`
+
+
+#### `function(v)`
Returns `true` when `v` is a function.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.Function(function() end)
```
-#### `Nil`
+
+
+#### `nil(v)`
Returns `true` when `v` is `nil`.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.Nil(nil)
```
-#### `number`
+
+
+#### `number(v)`
Returns `true` when `v` is a number.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.number(3.14)
```
-#### `string`
+
+
+#### `string(v)`
Returns `true` when `v` is a string.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.string("hello")
```
-#### `table`
+
+
+#### `table(v)`
Returns `true` when `v` is a table.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.table({})
```
-#### `thread`
+
+
+#### `thread(v)`
Returns `true` when `v` is a thread.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.thread(coroutine.create(function() end))
```
-#### `userdata`
+
+
+#### `userdata(v)`
Returns `true` when `v` is userdata.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.userdata(io.stdout)
```
### Value Checks
-Truthiness, exact-value, and callable checks.
+Truthiness, exact-value, and callable checks.
-#### `False`
+#### `false(v)`
Returns `true` when `v` is exactly `false`.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.False(false)
```
-#### `True`
+
+
+#### `true(v)`
Returns `true` when `v` is exactly `true`.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.True(true)
```
-#### `falsy`
+
+
+#### `falsy(v)`
Returns `true` when `v` is falsy.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.falsy(false)
```
-#### `callable`
+
+
+#### `callable(v)`
Returns `true` when `v` is callable.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.callable(function() end)
```
-#### `integer`
+
+
+#### `integer(v)`
Returns `true` when `v` is an integer.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.integer(42)
```
-#### `truthy`
+
+
+#### `truthy(v)`
Returns `true` when `v` is truthy.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.truthy("non-empty")
```
@@ -202,71 +368,162 @@ Filesystem path kind checks.
>
> Path checks require **LuaFileSystem**
> ([`lfs`](https://github.com/lunarmodules/luafilesystem)) and raise an error it
-> is not installed.
+> is not installed.
-#### `block`
+#### `block(v)`
Returns `true` when `v` is a block device path.
-Raises an error if [`lfs`](https://github.com/lunarmodules/luafilesystem) is not
-installed.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
```lua
is.block("/dev/sda")
```
-#### `char`
+
+
+#### `char(v)`
Returns `true` when `v` is a char device path.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.char("/dev/null")
```
-#### `device`
+
+
+#### `device(v)`
Returns `true` when `v` is a block or char device path.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.device("/dev/null")
```
-#### `dir`
+
+
+#### `dir(v)`
Returns `true` when `v` is a directory path.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.dir("/tmp")
```
-#### `fifo`
+
+
+#### `fifo(v)`
Returns `true` when `v` is a FIFO path.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.fifo("/path/to/fifo")
```
-#### `file`
+
+
+#### `file(v)`
Returns `true` when `v` is a file path.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.file("README.md")
```
-#### `link`
+
+
+#### `link(v)`
Returns `true` when `v` is a symlink path.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.link("/path/to/link")
```
-#### `socket`
+
+
+#### `socket(v)`
Returns `true` when `v` is a socket path.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
+
```lua
is.socket("/path/to/socket")
```
diff --git a/docs/src/modules/keyword.md b/docs/src/modules/keyword.md
index 5131e5c..600eab1 100644
--- a/docs/src/modules/keyword.md
+++ b/docs/src/modules/keyword.md
@@ -15,63 +15,108 @@ kw.iskeyword("local")) --> true
kw.isidentifier("hello_world") --> true
```
-## Dependencies
+## Functions
-Dependencies below are lazy-loaded 💤 on first access.
+| Function | Description |
+| ----------------------------------------------------- | ----------------------------------------------------------------------------------- |
+| [`iskeyword(v)`](#fn-iskeyword) | Return `true` when `v` is a reserved Lua keyword. |
+| [`isidentifier(v)`](#fn-isidentifier) | Return `true` when `v` is a valid non-keyword Lua identifier. |
+| [`kwlist()`](#fn-kwlist) | Return Lua keywords as a [`mods.List`](https://luamod.github.io/mods/modules/list). |
+| [`kwset()`](#fn-kwset) | Return Lua keywords as a [`mods.Set`](https://luamod.github.io/mods/modules/set). |
+| [`normalize_identifier(s)`](#fn-normalize-identifier) | Normalize an input into a safe Lua identifier. |
-- [`mods.Set`](https://luamod.github.io/mods/modules/set)
-- [`mods.List`](https://luamod.github.io/mods/modules/list)
+
-## Functions
+### `iskeyword(v)`
+
+Return `true` when `v` is a reserved Lua keyword.
+
+**Parameters**:
-| Function | Description |
-| ----------------------------------------------- | ----------------------------------------------------------------------------------- |
-| [`iskeyword`](#iskeyword) | Return `true` when `s` is a reserved Lua keyword. |
-| [`isidentifier`](#isidentifier) | Return `true` when `s` is a valid non-keyword Lua identifier. |
-| [`kwlist`](#kwlist) | Return Lua keywords as a [`mods.List`](https://luamod.github.io/mods/modules/list). |
-| [`kwset`](#kwset) | Return Lua keywords as a [`mods.Set`](https://luamod.github.io/mods/modules/set). |
-| [`normalize_identifier`](#normalize-identifier) | Normalize an input into a safe Lua identifier. |
+- `v` (`any`): Value to validate.
-### `iskeyword`
+**Return**:
-Return `true` when `s` is a reserved Lua keyword.
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
```lua
kw.iskeyword("function") --> true
kw.iskeyword("hello") --> false
```
-### `isidentifier`
+
+
+### `isidentifier(v)`
+
+Return `true` when `v` is a valid non-keyword Lua identifier.
-Return `true` when `s` is a valid non-keyword Lua identifier.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+
+**Example**:
```lua
kw.isidentifier("hello_world") --> true
kw.isidentifier("local") --> false
```
-### `kwlist`
+
+
+### `kwlist()`
Return Lua keywords as a
[`mods.List`](https://luamod.github.io/mods/modules/list).
+**Return**:
+
+- `words` (`mods.List`): List of Lua keywords.
+
+**Example**:
+
```lua
kw.kwlist():contains("and") --> true
```
-### `kwset`
+
+
+### `kwset()`
Return Lua keywords as a
[`mods.Set`](https://luamod.github.io/mods/modules/set).
+**Return**:
+
+- `words` (`mods.Set`): Set of Lua keywords.
+
+**Example**:
+
```lua
kw.kwlset():contains("and") --> true
```
-### `normalize_identifier`
+
+
+### `normalize_identifier(s)`
Normalize an input into a safe Lua identifier.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ident` (`string`): Normalized Lua identifier.
+
+**Example**:
+
```lua
kw.normalize_identifier(" 2 bad-name ") --> "_2_bad_name"
```
diff --git a/docs/src/modules/list.md b/docs/src/modules/list.md
index 54c68dc..0071d9c 100644
--- a/docs/src/modules/list.md
+++ b/docs/src/modules/list.md
@@ -1,13 +1,13 @@
---
desc:
- "A Python-style list class providing common operations to create, modify, and
- query sequences of values."
+ "A list class providing common operations to create, modify, and query
+ sequences of values."
---
# `List`
-A Python-style list class providing common operations to create, modify, and
-query sequences of values.
+A list class providing common operations to create, modify, and query sequences
+of values.
## Usage
@@ -16,93 +16,122 @@ List = require "mods.List"
ls = List({ "a" }):append("b")
print(ls:contains("b")) --> true
-print(ls:index("b")) --> 2
+print(ls:index("b")) --> 2
```
-## Dependencies
-
-Dependencies below are lazy-loaded 💤 on first access.
-
-- [`mods.Set`](https://luamod.github.io/mods/modules/set)
+> [!NOTE]
+>
+> `List(t)` wraps `t` with the `List` metatable in place. It does not copy or
+> filter table values. `List(t):copy()` or `List.copy(t)` both copy only `1..#t`
+> and wrap `t` as a List.
## Functions
**Predicates**:
-| Function | Description |
-| ------------- | ----------------------------------------------- |
-| [`all`](#all) | Return true if all values match the predicate. |
-| [`any`](#any) | Return true if any value matches the predicate. |
+| 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. |
+| [`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. |
**Mutation**:
-| Function | Description |
-| --------------------- | -------------------------------------------------------------------- |
-| [`append`](#append) | Append a value to the end of the list. |
-| [`clear`](#clear) | Remove all elements from the list. |
-| [`extend`](#extend) | Extend the list with another list. |
-| [`extract`](#extract) | Extract values matching the predicate and remove them from the list. |
-| [`insert`](#insert) | Insert a value at the given position. |
-| [`insert`](#insert) | Append a value to the end of the list. |
-| [`pop`](#pop) | Remove and return the last element. |
-| [`pop`](#pop) | Remove and return the element at the given position. |
-| [`prepend`](#prepend) | Insert a value at the start of the list. |
-| [`remove`](#remove) | Remove the first matching value. |
-| [`sort`](#sort) | Sort the list in place. |
+| Function | Description |
+| ------------------------------ | -------------------------------------------------------------------- |
+| [`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. |
+| [`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. |
+| [`pop()`](#fn-pop) | Remove and return the last element. |
+| [`pop(pos)`](#fn-pop) | Remove and return the element at the given position. |
+| [`prepend(v)`](#fn-prepend) | Insert a value at the start of the list. |
+| [`remove(v)`](#fn-remove) | Remove the first matching value. |
+| [`sort(comp?)`](#fn-sort) | Sort the list in place. |
**Copying**:
-| Function | Description |
-| --------------- | ---------------------------------- |
-| [`copy`](#copy) | Return a shallow copy of the list. |
+| Function | Description |
+| -------------------- | ---------------------------------- |
+| [`copy()`](#fn-copy) | Return a shallow copy of the list. |
**Query**:
-| Function | Description |
-| ----------------------- | ----------------------------------------------------------- |
-| [`contains`](#contains) | Return true if the list contains the value. |
-| [`count`](#count) | Count how many times a value appears. |
-| [`index`](#index) | Return the index of the first matching value. |
-| [`index_if`](#index-if) | Return the index of the first value matching the predicate. |
-| [`len`](#len) | Return the number of elements in the list. |
+| Function | Description |
+| -------------------------------- | ----------------------------------------------------------- |
+| [`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. |
**Access**:
-| Function | Description |
-| ----------------- | ------------------------------------------- |
-| [`first`](#first) | Return the first element or `nil` if empty. |
-| [`last`](#last) | Return the last element or `nil` if empty. |
+| Function | Description |
+| ---------------------- | ------------------------------------------- |
+| [`first()`](#fn-first) | Return the first element or `nil` if empty. |
+| [`last()`](#fn-last) | Return the last element or `nil` if empty. |
**Transform**:
-| Function | Description |
-| ------------------------------- | ------------------------------------------------------------------ |
-| [`difference`](#difference) | Return a new list with values not in the given list. |
-| [`drop`](#drop) | Return a new list without the first n elements. |
-| [`filter`](#filter) | Return a new list with values matching the predicate. |
-| [`flatten`](#flatten) | Flatten one level of nested lists. |
-| [`foreach`](#foreach) | Apply a function to each element (for side effects). |
-| [`group_by`](#group-by) | Group list values by a computed key. |
-| [`intersection`](#intersection) | Return values that are also present in the given list. |
-| [`invert`](#invert) | Invert values to indices in a new table. |
-| [`join`](#join) | Join list values into a string. |
-| [`map`](#map) | Return a new list by mapping each value. |
-| [`reduce`](#reduce) | Reduce the list to a single value using an accumulator. |
-| [`reverse`](#reverse) | Return a new list with items reversed. |
-| [`toset`](#toset) | Convert the list to a set. |
-| [`slice`](#slice) | Return a new list containing items from i to j (inclusive). |
-| [`take`](#take) | Return the first n elements as a new list. |
-| [`uniq`](#uniq) | Return a new list with duplicates removed (first occurrence kept). |
-| [`zip`](#zip) | Zip two lists into a list of 2-element tables. |
+| 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. |
+
+**Metamethods**:
+
+| Function | Description |
+| ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
+| [`__eq(ls)`](#fn-eq) | Compare two lists using shallow element equality (`==`). |
+| [`__lt(ls)`](#fn-lt) | Compare two lists lexicographically (`<`). |
+| [`__le(ls)`](#fn-le) | Compare two lists lexicographically (`<=`). |
+| [`__mul(n)`](#fn-mul) | Repeat a list `n` times (`*`). |
+| [`__add(ls)`](#fn-add) | Extend the left-hand list in place with right-hand values, then return the same left-hand list reference (`+`). |
+| [`__sub(ls)`](#fn-sub) | Return values from the left list that are not present in the right list (`-`). |
+| [`__tostring()`](#fn-tostring) | Render the list to a string like `{ "a", "b", 1 }`. |
### Predicates
-Boolean checks for list-wide conditions.
+Boolean checks for list-wide conditions.
-#### `all`
+#### `all(pred)`
Return true if all values match the predicate.
+**Parameters**:
+
+- `pred` (`fun(v:any):boolean`): Predicate function.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
```lua
is_even = function(v) return v % 2 == 0 end
ok = List({ 2, 4 }):all(is_even) --> true
@@ -112,109 +141,346 @@ ok = List({ 2, 4 }):all(is_even) --> true
>
> Empty lists return `true`.
-#### `any`
+
+
+#### `any(pred)`
Return true if any value matches the predicate.
+**Parameters**:
+
+- `pred` (`fun(v:any):boolean`): Predicate function.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
```lua
has_len_2 = function(v) return #v == 2 end
ok = List({ "a", "bb" }):any(has_len_2) --> true
```
+
+
+#### `equals(ls)`
+
+Compare two lists using shallow element equality.
+
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
+```lua
+a = List({ "x", "y" })
+b = List({ "x", "y" })
+ok = a:equals(b) --> true
+```
+
+> [!NOTE]
+>
+> - `equals` is also available through the `==` operator when both operands are
+> `List`.
+>
+> ```lua
+> a = List({ "a", 1 })
+> b = List({ "a", 1 })
+> ok = (a == b) --> true
+> ```
+>
+> - Unlike `==`, this method also works when `ls` is a plain array table.
+>
+> ```lua
+> a = List({ "a", 1 })
+> b = { "a", 1 }
+> ok = a:equals(b) --> true
+> ```
+>
+> - `equals` checks only array positions (`1..#list`), so extra non-array keys
+> are ignored:
+>
+> ```lua
+> t = {}
+> a = List({ "a", t })
+> b = { "a", t, a = 1 }
+> ok = a:equals(b) --> true
+> ```
+
+
+
+#### `lt(ls)`
+
+Compare two lists lexicographically.
+
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
+```lua
+ok = List({ 1, 2 }):lt({ 1, 3 }) --> true
+ok = List({ 1, 2 }):lt({ 1, 2, 0 }) --> true
+```
+
+> [!NOTE]
+>
+> `lt` is also available through the `<` operator.
+
+
+
+#### `le(ls)`
+
+Compare two lists lexicographically.
+
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
+```lua
+ok = List({ 1, 2 }):le({ 1, 2 }) --> true
+ok = List({ 1, 2 }):le({ 1, 1 }) --> false
+```
+
+> [!NOTE]
+>
+> `le` is also available through the `<=` operator.
+
### Mutation
-In-place operations that modify the current list.
+In-place operations that modify the current list.
-#### `append`
+#### `append()`
Append a value to the end of the list.
+**Return**:
+
+- `self` (`T`): Current list instance.
+
+**Example**:
+
```lua
ls = List({ "a" }):append("b") --> { "a", "b" }
```
-#### `clear`
+
+
+#### `clear()`
Remove all elements from the list.
+**Return**:
+
+- `self` (`T`): Current list instance.
+
+**Example**:
+
```lua
ls = List({ "a", "b" }):clear() --> { }
```
-#### `extend`
+
+
+#### `extend(ls)`
Extend the list with another list.
+**Parameters**:
+
+- `ls` (`any[]`): List values.
+
+**Return**:
+
+- `self` (`T`): Current list instance.
+
+**Example**:
+
```lua
ls = List({ "a" }):extend({ "b", "c" }) --> { "a", "b", "c" }
```
-#### `extract`
+> [!NOTE]
+>
+> `extend` is also available through the `+` operator.
+
+
+
+#### `extract(pred)`
Extract values matching the predicate and remove them from the list.
+**Parameters**:
+
+- `pred` (`fun(v:any):boolean`): Predicate function.
+
+**Return**:
+
+- `ls` (`mods.List`): Extracted values.
+
+**Example**:
+
```lua
ls = List({ "a", "bb", "c" })
is_len_1 = function(v) return #v == 1 end
ex = ls:extract(is_len_1) --> ex = { "a", "c" }, ls = { "bb" }
```
-#### `insert`
+
+
+#### `insert(pos, v)`
Insert a value at the given position.
+**Parameters**:
+
+- `pos` (`integer`): Insert position.
+- `v` (`any`): Value to insert.
+
+**Return**:
+
+- `self` (`T`): Current list instance.
+
+**Example**:
+
```lua
ls = List({ "a", "c" }):insert(2, "b") --> { "a", "b", "c" }
```
-#### `insert`
+
+
+#### `insert(v)`
Append a value to the end of the list.
+**Parameters**:
+
+- `v` (`any`): Value to append.
+
+**Return**:
+
+- `self` (`T`): Current list instance.
+
+**Example**:
+
```lua
-ls = List({ "a", "b" }):insert("b") --> { "a", "b", "c" }
+ls = List({ "a", "b" }):insert("c") --> { "a", "b", "c" }
```
-#### `pop`
+
+
+#### `pop()`
Remove and return the last element.
+**Return**:
+
+- `value` (`any`): Removed value.
+
+**Example**:
+
```lua
ls = List({ "a", "b" })
v = ls:pop() --> v == "b"; ls is { "a" }
```
-#### `pop`
+
+
+#### `pop(pos)`
Remove and return the element at the given position.
+**Parameters**:
+
+- `pos` (`integer`): Numeric value.
+
+**Return**:
+
+- `value` (`any`): Removed value.
+
+**Example**:
+
```lua
ls = List({ "a", "b", "c" })
v = ls:pop(2) --> v == "b"; ls is { "a", "c" }
```
-#### `prepend`
+
+
+#### `prepend(v)`
Insert a value at the start of the list.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `self` (`T`): Current list instance.
+
+**Example**:
+
```lua
ls = List({ "b", "c" })
ls:prepend("a") --> { "a", "b", "c" }
```
-#### `remove`
+
+
+#### `remove(v)`
Remove the first matching value.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `self` (`T`): Current list instance.
+
+**Example**:
+
```lua
ls = List({ "a", "b", "b" })
ls:remove("b") --> { "a", "b" }
```
-#### `sort`
+
+
+#### `sort(comp?)`
Sort the list in place.
+**Parameters**:
+
+- `comp?` (`fun(a,b):boolean`): Optional comparison function (defaults to
+ `nil`).
+
+**Return**:
+
+- `self` (`T`): Current list instance.
+
+**Example**:
+
```lua
ls = List({ 3, 1, 2 })
ls:sort() --> { 1, 2, 3 }
@@ -222,82 +488,155 @@ ls:sort() --> { 1, 2, 3 }
### Copying
-Operations that return copied list data.
+Operations that return copied list data.
-#### `copy`
+#### `copy()`
Return a shallow copy of the list.
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
c = List({ "a", "b" }):copy() --> { "a", "b" }
```
### Query
-Read-only queries for membership, counts, and indices.
+Read-only queries for membership, counts, and indices.
-#### `contains`
+#### `contains(v)`
Return true if the list contains the value.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
```lua
ok = List({ "a", "b" }):contains("b") --> true
```
-#### `count`
+
+
+#### `count(v)`
Count how many times a value appears.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `res` (`integer`): Result count.
+
+**Example**:
+
```lua
n = List({ "a", "b", "b" }):count("b") --> 2
```
-#### `index`
+
+
+#### `index(v)`
Return the index of the first matching value.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `index` (`integer?`): Result index, or nil when not found.
+
+**Example**:
+
```lua
i = List({ "a", "b", "c", "b" }):index("b") --> 2
```
-#### `index_if`
+
+
+#### `index_if(pred)`
Return the index of the first value matching the predicate.
+**Parameters**:
+
+- `pred` (`fun(v:any):boolean`): Predicate function.
+
+**Return**:
+
+- `index` (`integer?`): Result index, or nil when no value matches.
+
+**Example**:
+
```lua
gt_1 = function(x) return x > 1 end
i = List({ 1, 2, 3 }):index_if(gt_1) --> 2
```
-#### `len`
+
+
+#### `len()`
Return the number of elements in the list.
+**Return**:
+
+- `count` (`integer`): Element count.
+
+**Example**:
+
```lua
n = List({ "a", "b", "c" }):len() --> 3
```
> [!NOTE]
>
-> Uses Lua's `#` operator, so length is reliable for contiguous array-like
-> lists.
+> Uses Lua's `#` operator.
### Access
-Direct element access helpers.
+Direct element access helpers.
-#### `first`
+#### `first()`
Return the first element or `nil` if empty.
+**Return**:
+
+- `value` (`any`): First value, or `nil` if empty.
+
+**Example**:
+
```lua
v = List({ "a", "b" }):first() --> "a"
```
-#### `last`
+
+
+#### `last()`
Return the last element or `nil` if empty.
+**Return**:
+
+- `value` (`any`): Last value, or `nil` if empty.
+
+**Example**:
+
```lua
v = List({ "a", "b" }):last() --> "b"
```
@@ -305,63 +644,146 @@ v = List({ "a", "b" }):last() --> "b"
### Transform
Non-mutating transformations and derived-list operations.
+
-#### `difference`
+#### `difference(ls)`
Return a new list with values not in the given list.
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ls` (`T`): New list.
+
+**Example**:
+
```lua
d = List({ "a", "b", "c" }):difference({ "b" }) --> { "a", "c" }
```
-#### `drop`
+> [!NOTE]
+>
+> `difference` is also available through the `-` operator.
+
+
+
+#### `drop(n)`
Return a new list without the first n elements.
+**Parameters**:
+
+- `n` (`integer`): Numeric value.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
t = List({ "a", "b", "c" }):drop(1) --> { "b", "c" }
```
-#### `filter`
+
+
+#### `filter(pred)`
Return a new list with values matching the predicate.
+**Parameters**:
+
+- `pred` (`fun(v:any):boolean`): Predicate function.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
is_len_1 = function(v) return #v == 1 end
f = List({ "a", "bb", "c" }):filter(is_len_1) --> { "a", "c" }
```
-#### `flatten`
+
+
+#### `flatten()`
Flatten one level of nested lists.
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
f = List({ { "a", "b" }, { "c" } }):flatten() --> { "a", "b", "c" }
```
-#### `foreach`
+
+
+#### `foreach(fn)`
Apply a function to each element (for side effects).
+**Parameters**:
+
+- `fn` (`fun(v:any)`): Callback function.
+
+**Return**:
+
+- `none` (`nil`)
+
+**Example**:
+
```lua
List({ "a", "b" }):foreach(print)
--> prints -> a
--> prints -> b
```
-#### `group_by`
+
+
+#### `group_by(fn)`
Group list values by a computed key.
+**Parameters**:
+
+- `fn` (`fun(v:any):any`): Callback function.
+
+**Return**:
+
+- `groups` (`table`): Groups keyed by the callback result.
+
+**Example**:
+
```lua
words = { "aa", "b", "ccc", "dd" }
g = List(words):group_by(string.len) --> { {"b"}, { "aa", "dd" }, { "ccc" } }
```
-#### `intersection`
+
+
+#### `intersection(ls)`
Return values that are also present in the given list.
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
i = List({ "a", "b", "a", "c" }):intersection({ "a", "c" })
--> { "a", "a", "c" }
@@ -371,35 +793,175 @@ i = List({ "a", "b", "a", "c" }):intersection({ "a", "c" })
>
> Order is preserved from the original list.
-#### `invert`
+
+
+#### `invert()`
Invert values to indices in a new table.
+**Return**:
+
+- `idxByValue` (`table`): Table mapping each value to its last index.
+
+**Example**:
+
```lua
t = List({ "a", "b", "c" }):invert() --> { a = 1, b = 2, c = 3 }
```
-#### `join`
+
+
+#### `concat(sep?, i?, j?)`
+
+Concatenate list values using Lua's native `table.concat` behavior.
+
+**Parameters**:
+
+- `sep?` (`string`): Optional separator value (defaults to `""`).
+- `i?` (`integer`): Optional start index (defaults to `1`).
+- `j?` (`integer`): Optional end index (defaults to `#self`).
+
+**Return**:
+
+- `s` (`string`): Concatenated string.
+
+**Example**:
+
+```lua
+s = List({ "a", "b", "c" }):concat(",") --> "a,b,c"
+```
+
+> [!NOTE]
+>
+> This method forwards to `table.concat` directly and keeps its strict element
+> rules.
+
+
+
+#### `join(sep?, quoted?)`
Join list values into a string.
+**Parameters**:
+
+- `sep?` (`string`): Optional separator value (defaults to `""`).
+- `quoted?` (`boolean`): Optional boolean flag (defaults to `false`).
+
+**Return**:
+
+- `s` (`string`): Joined string.
+
+**Example**:
+
```lua
-s = List({ "a", "b", "c" }):join(",") --> "a,b,c"
+s = List({ "a", "b", "c" }):join(",") --> "a,b,c"
+s = List({ "a", "b", "c" }):join(", ", true) --> '"a", "b", "c"'
```
-#### `map`
+> [!NOTE]
+>
+> Values are converted with `tostring` before joining. Set `quoted = true` to
+> quote string values.
+
+
+
+#### `tostring()`
+
+Render the list to a string via the regular method form.
+
+**Return**:
+
+- `s` (`string`): Rendered list string.
+
+**Example**:
+
+```lua
+s = List({ "a", "b", 1 }):tostring() --> '{ "a", "b", 1 }'
+```
+
+> [!NOTE]
+>
+> `tostring(list)` calls `list:tostring()`.
+
+
+
+#### `keypath()`
+
+Render list items as a table-access key path.
+
+**Return**:
+
+- `s` (`string`): Key-path string.
+
+**Example**:
+
+```lua
+p = List({ "ctx", "users", 1, "name" }):keypath() --> "ctx.users[1].name"
+```
+
+
+
+#### `map(fn)`
Return a new list by mapping each value.
+**Parameters**:
+
+- `fn` (`fun(v):any`): Callback function.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
to_upper = function(v) return v:upper() end
m = List({ "a", "b" }):map(to_upper) --> { "A", "B" }
```
-#### `reduce`
+
+
+#### `mul(n)`
+
+Return a new list repeated `n` times (list multiplication behavior).
+
+**Parameters**:
+
+- `n` (`integer`): Numeric value.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
+```lua
+ls = List({ "a", "b" }):mul(3) --> { "a", "b", "a", "b", "a", "b" }
+```
+
+> [!NOTE]
+>
+> `mul` is also available through the `*` operator.
+
+
+
+#### `reduce(fn, init?)`
Reduce the list to a single value using an accumulator.
+**Parameters**:
+
+- `fn` (`fun(acc:any,`): v:any):any Reducer function.
+- `init?` (`any`): Optional initial accumulator; for non-empty lists, `nil` or
+ omitted uses the first item.
+
+**Return**:
+
+- `res` (`any`): Reduced value.
+
+**Example**:
+
```lua
add = function(acc, v) return acc + v end
sum = List({ 1, 2, 3 }):reduce(add, 0) --> 6
@@ -408,21 +970,36 @@ sum = List({ 1, 2, 3 }):reduce(add, 10) --> 16
> [!NOTE]
>
-> If init is `nil`, the first element is used as the initial value. Empty lists
-> return init (or `nil` if init is `nil`).
+> For empty lists, returns `init` unchanged (or `nil` when omitted).
+
+
-#### `reverse`
+#### `reverse()`
Return a new list with items reversed.
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
r = List({ "a", "b", "c" }):reverse() --> { "c", "b", "a" }
```
-#### `toset`
+
+
+#### `toset()`
Convert the list to a set.
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
```lua
s = List({ "a", "b", "a" }):toset() --> { a = true, b = true }
```
@@ -431,10 +1008,23 @@ s = List({ "a", "b", "a" }):toset() --> { a = true, b = true }
>
> Order is preserved from the original list.
-#### `slice`
+
+
+#### `slice(i?, j?)`
Return a new list containing items from i to j (inclusive).
+**Parameters**:
+
+- `i?` (`integer`): Optional start index (defaults to `1`).
+- `j?` (`integer`): Optional end index (defaults to `#self`).
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
t = List({ "a", "b", "c", "d" }):slice(2, 3) --> { "b", "c" }
```
@@ -443,26 +1033,58 @@ t = List({ "a", "b", "c", "d" }):slice(2, 3) --> { "b", "c" }
>
> Supports negative indices (-1 is last element).
-#### `take`
+
+
+#### `take(n)`
Return the first n elements as a new list.
+**Parameters**:
+
+- `n` (`integer`): Numeric value.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
t = List({ "a", "b", "c" }):take(2) --> { "a", "b" }
```
-#### `uniq`
+
+
+#### `uniq()`
Return a new list with duplicates removed (first occurrence kept).
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
u = List({ "a", "b", "a", "c" }):uniq() --> { "a", "b", "c" }
```
-#### `zip`
+
+
+#### `zip(ls)`
Zip two lists into a list of 2-element tables.
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
```lua
z = List({ "a", "b" }):zip({ 1, 2 }) --> { {"a",1}, {"b",2} }
```
@@ -470,3 +1092,200 @@ z = List({ "a", "b" }):zip({ 1, 2 }) --> { {"a",1}, {"b",2} }
> [!NOTE]
>
> Length is the minimum of both lists.
+
+### Metamethods
+
+
+
+#### `__eq(ls)`
+
+Compare two lists using shallow element equality (`==`).
+
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
+```lua
+a = List({ "a", { 1 } })
+b = List({ "a", { 1 } })
+ok = a == b --> false (different nested table references)
+
+t = { 1 }
+a = List({ "a", t })
+b = List({ "a", t })
+ok = a == b --> true (same nested table reference)
+```
+
+> [!NOTE]
+>
+> - `==` returns `false` for `List` vs plain-table comparisons. Use
+> `:equals(ls)` for `List` vs plain-table comparisons.
+>
+> ```lua
+> t = { "a", 1 }
+> a = List(t)
+> b = { "a", 1 }
+> ok = (a == b) --> false
+> ok = a:equals(b) --> true
+> ```
+>
+> - Like `:equals(ls)`, `==` compares only array positions (`1..#list`), so
+> extra non-array keys are ignored when both operands are `List`.
+>
+> ```lua
+> a = List({ "a", t })
+> b = List({ "a", t, extra = 1 })
+> ok = (a == b) --> true
+> ```
+
+
+
+#### `__lt(ls)`
+
+Compare two lists lexicographically (`<`).
+
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
+```lua
+ok = List({ 1, 2 }) < List({ 1, 3 }) --> true
+```
+
+> [!NOTE]
+>
+> `<` is equivalent to `:lt(ls)`.
+
+
+
+#### `__le(ls)`
+
+Compare two lists lexicographically (`<=`).
+
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the condition is met.
+
+**Example**:
+
+```lua
+ok = List({ 1, 2 }) <= List({ 1, 2 }) --> true
+```
+
+> [!NOTE]
+>
+> `<=` is equivalent to `:le(ls)`.
+
+
+
+#### `__mul(n)`
+
+Repeat a list `n` times (`*`).
+
+**Parameters**:
+
+- `n` (`integer|mods.List`): Right operand.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
+```lua
+l1 = List({ "a", "b" }) * 3 --> { "a", "b", "a", "b", "a", "b" }
+l2 = 3 * List({ "a", "b" }) --> { "a", "b", "a", "b", "a", "b" }
+```
+
+> [!NOTE]
+>
+> `*` is equivalent to `:mul(n)`.
+
+
+
+#### `__add(ls)`
+
+Extend the left-hand list in place with right-hand values, then return the same
+left-hand list reference (`+`).
+
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `self` (`mods.List|any[]`): Current list instance.
+
+**Example**:
+
+```lua
+a = List({ "a", "b" })
+b = { "c", "d" }
+c = a + b --> c and a are the same reference: { "a", "b", "c", "d" }
+```
+
+> [!NOTE]
+>
+> `+` operator is equivalent to `:extend(ls)`.
+
+
+
+#### `__sub(ls)`
+
+Return values from the left list that are not present in the right list (`-`).
+
+**Parameters**:
+
+- `ls` (`mods.List|any[]`): Other list value.
+
+**Return**:
+
+- `ls` (`mods.List`): New list.
+
+**Example**:
+
+```lua
+a = List({ "a", "b", "c" })
+b = { "b" }
+d = a - b --> { "a", "c" }
+```
+
+> [!NOTE]
+>
+> `-` operator is equivalent to `:difference(ls)`.
+
+
+
+#### `__tostring()`
+
+Render the list to a string like `{ "a", "b", 1 }`.
+
+**Return**:
+
+- `s` (`string`): Rendered list string.
+
+**Example**:
+
+```lua
+s = tostring(List({ "a", "b", 1 })) --> '{ "a", "b", 1 }'
+```
+
+> [!NOTE]
+>
+> `tostring(ls)` is equivalent to `:tostring()`.
diff --git a/docs/src/modules/operator.md b/docs/src/modules/operator.md
index 0b07355..a836ca8 100644
--- a/docs/src/modules/operator.md
+++ b/docs/src/modules/operator.md
@@ -18,243 +18,517 @@ print(operator.add(1, 2)) --> 3
**Arithmetic**:
-| Function | Description |
-| --------------- | ------------------------------------------------------------------ |
-| [`add`](#add) | Add two numbers. |
-| [`sub`](#sub) | Subtract `b` from `a`. |
-| [`mul`](#mul) | Multiply two numbers. |
-| [`div`](#div) | Divide `a` by `b` using Lua's floating-point division. |
-| [`idiv`](#idiv) | Divide `a` by `b` and return the integer quotient (`//` behavior). |
-| [`mod`](#mod) | Return the modulo remainder of `a` divided by `b`. |
-| [`pow`](#pow) | Raise `a` to the power of `b`. |
-| [`unm`](#unm) | Negate a number. |
+| Function | Description |
+| ------------------------ | --------------------------------------------------------- |
+| [`add(a, b)`](#fn-add) | Add two numbers. |
+| [`sub(a, b)`](#fn-sub) | Subtract `b` from `a`. |
+| [`mul(a, b)`](#fn-mul) | Multiply two numbers. |
+| [`div(a, b)`](#fn-div) | Divide `a` by `b` using Lua's floating-point division. |
+| [`idiv(a, b)`](#fn-idiv) | Divide `a` by `b` and return the floor-division quotient. |
+| [`mod(a, b)`](#fn-mod) | Return the modulo remainder of `a` divided by `b`. |
+| [`pow(a, b)`](#fn-pow) | Raise `a` to the power of `b`. |
+| [`unm(a)`](#fn-unm) | Negate a number. |
**Comparison**:
-| Function | Description |
-| ------------- | -------------------------------------------------- |
-| [`eq`](#eq) | Check whether two values are equal. |
-| [`neq`](#neq) | Check whether two values are not equal. |
-| [`lt`](#lt) | Check whether `a` is strictly less than `b`. |
-| [`le`](#le) | Check whether `a` is less than or equal to `b`. |
-| [`gt`](#gt) | Check whether `a` is strictly greater than `b`. |
-| [`ge`](#ge) | Check whether `a` is greater than or equal to `b`. |
+| Function | Description |
+| ---------------------- | -------------------------------------------------- |
+| [`eq(a, b)`](#fn-eq) | Check whether two values are equal. |
+| [`neq(a, b)`](#fn-neq) | Check whether two values are not equal. |
+| [`lt(a, b)`](#fn-lt) | Check whether `a` is strictly less than `b`. |
+| [`le(a, b)`](#fn-le) | Check whether `a` is less than or equal to `b`. |
+| [`gt(a, b)`](#fn-gt) | Check whether `a` is strictly greater than `b`. |
+| [`ge(a, b)`](#fn-ge) | Check whether `a` is greater than or equal to `b`. |
**Logical**:
-| Function | Description |
-| --------------- | ---------------------------------------------------- |
-| [`land`](#land) | Evaluate `a and b` with Lua short-circuit semantics. |
-| [`lor`](#lor) | Evaluate `a or b` with Lua short-circuit semantics. |
-| [`lnot`](#lnot) | Return the boolean negation of `a`. |
+| Function | Description |
+| ------------------------ | ---------------------------------------------------- |
+| [`land(a, b)`](#fn-land) | Evaluate `a and b` with Lua short-circuit semantics. |
+| [`lor(a, b)`](#fn-lor) | Evaluate `a or b` with Lua short-circuit semantics. |
+| [`lnot(a)`](#fn-lnot) | Return the boolean negation of `a`. |
**String & Length**:
-| Function | Description |
-| ------------------- | ---------------------------------------------------------------- |
-| [`concat`](#concat) | Concatenate two strings. |
-| [`len`](#len) | Return the length of a string or table using Lua's `#` operator. |
+| Function | Description |
+| ---------------------------- | ---------------------------------------------------------------- |
+| [`concat(a, b)`](#fn-concat) | Concatenate two strings. |
+| [`len(a)`](#fn-len) | Return the length of a string or table using Lua's `#` operator. |
**Tables & Calls**:
-| Function | Description |
-| ----------------------- | -------------------------------------------------------------- |
-| [`index`](#index) | Return the value at key/index `k` in table `t`. |
-| [`setindex`](#setindex) | Set `t[k] = v` and return the assigned value. |
-| [`call`](#call) | Call a function with variadic arguments and return its result. |
+| Function | Description |
+| ----------------------------------- | -------------------------------------------------------------- |
+| [`index(t, k)`](#fn-index) | Return the value at key/index `k` in table `t`. |
+| [`setindex(t, k, v)`](#fn-setindex) | Set `t[k] = v` and return the assigned value. |
+| [`call(f, ...)`](#fn-call) | Call a function with variadic arguments and return its result. |
### Arithmetic
-Numeric arithmetic operators as functions.
+Numeric arithmetic operators as functions.
-#### `add`
+#### `add(a, b)`
Add two numbers.
+**Parameters**:
+
+- `a` (`number`): Left numeric value.
+- `b` (`number`): Right numeric value.
+
+**Return**:
+
+- `sum` (`number`): Sum of `a` and `b`.
+
+**Example**:
+
```lua
add(1, 2) --> 3
```
-#### `sub`
+
+
+#### `sub(a, b)`
Subtract `b` from `a`.
+**Parameters**:
+
+- `a` (`number`): Left numeric value.
+- `b` (`number`): Right numeric value.
+
+**Return**:
+
+- `difference` (`number`): Difference `a - b`.
+
+**Example**:
+
```lua
sub(5, 3) --> 2
```
-#### `mul`
+
+
+#### `mul(a, b)`
Multiply two numbers.
+**Parameters**:
+
+- `a` (`number`): Left numeric value.
+- `b` (`number`): Right numeric value.
+
+**Return**:
+
+- `product` (`number`): Product `a * b`.
+
+**Example**:
+
```lua
mul(3, 4) --> 12
```
-#### `div`
+
+
+#### `div(a, b)`
Divide `a` by `b` using Lua's floating-point division.
+**Parameters**:
+
+- `a` (`number`): Dividend value.
+- `b` (`number`): Divisor value.
+
+**Return**:
+
+- `quotient` (`number`): Quotient `a / b`.
+
+**Example**:
+
```lua
div(10, 4) --> 2.5
```
-#### `idiv`
+
+
+#### `idiv(a, b)`
-Divide `a` by `b` and return the integer quotient (`//` behavior).
+Divide `a` by `b` and return the floor-division quotient.
+
+**Parameters**:
+
+- `a` (`number`): Dividend value.
+- `b` (`number`): Divisor value.
+
+**Return**:
+
+- `quotient` (`integer`): Floor-division result.
+
+**Example**:
```lua
idiv(5, 2) --> 2
```
-#### `mod`
+
+
+#### `mod(a, b)`
Return the modulo remainder of `a` divided by `b`.
+**Parameters**:
+
+- `a` (`number`): Dividend value.
+- `b` (`number`): Divisor value.
+
+**Return**:
+
+- `remainder` (`number`): Remainder of `a % b`.
+
+**Example**:
+
```lua
mod(5, 2) --> 1
```
-#### `pow`
+
+
+#### `pow(a, b)`
Raise `a` to the power of `b`.
+**Parameters**:
+
+- `a` (`number`): Base value.
+- `b` (`number`): Exponent value.
+
+**Return**:
+
+- `power` (`number`): Result of `a ^ b`.
+
+**Example**:
+
```lua
pow(2, 4) --> 16
```
-#### `unm`
+
+
+#### `unm(a)`
Negate a number.
+**Parameters**:
+
+- `a` (`number`): Input numeric value.
+
+**Return**:
+
+- `negated` (`number`): Result of `-a`.
+
+**Example**:
+
```lua
unm(3) --> -3
```
### Comparison
-Equality and ordering comparison operators.
+Equality and ordering comparison operators.
-#### `eq`
+#### `eq(a, b)`
Check whether two values are equal.
+**Parameters**:
+
+- `a` (`any`): Left value.
+- `b` (`any`): Right value.
+
+**Return**:
+
+- `isEqual` (`boolean`): True when `a == b`.
+
+**Example**:
+
```lua
eq(1, 1) --> true
```
-#### `neq`
+
+
+#### `neq(a, b)`
Check whether two values are not equal.
+**Parameters**:
+
+- `a` (`any`): Left value.
+- `b` (`any`): Right value.
+
+**Return**:
+
+- `isNotEqual` (`boolean`): True when `a ~= b`.
+
+**Example**:
+
```lua
neq(1, 2) --> true
```
-#### `lt`
+
+
+#### `lt(a, b)`
Check whether `a` is strictly less than `b`.
+**Parameters**:
+
+- `a` (`number`): Left numeric value.
+- `b` (`number`): Right numeric value.
+
+**Return**:
+
+- `isLess` (`boolean`): True when `a < b`.
+
+**Example**:
+
```lua
lt(1, 2) --> true
```
-#### `le`
+
+
+#### `le(a, b)`
Check whether `a` is less than or equal to `b`.
+**Parameters**:
+
+- `a` (`number`): Left numeric value.
+- `b` (`number`): Right numeric value.
+
+**Return**:
+
+- `isLessOrEqual` (`boolean`): True when `a <= b`.
+
+**Example**:
+
```lua
-lte(2, 2) --> true
+le(2, 2) --> true
```
-#### `gt`
+
+
+#### `gt(a, b)`
Check whether `a` is strictly greater than `b`.
+**Parameters**:
+
+- `a` (`number`): Left numeric value.
+- `b` (`number`): Right numeric value.
+
+**Return**:
+
+- `isGreater` (`boolean`): True when `a > b`.
+
+**Example**:
+
```lua
gt(3, 2) --> true
```
-#### `ge`
+
+
+#### `ge(a, b)`
Check whether `a` is greater than or equal to `b`.
+**Parameters**:
+
+- `a` (`number`): Left numeric value.
+- `b` (`number`): Right numeric value.
+
+**Return**:
+
+- `isGreaterOrEqual` (`boolean`): True when `a >= b`.
+
+**Example**:
+
```lua
-gte(2, 2) --> true
+ge(2, 2) --> true
```
### Logical
-Boolean logic operators with Lua truthiness semantics.
+Boolean logic operators with Lua truthiness semantics.
-#### `land`
+#### `land(a, b)`
Evaluate `a and b` with Lua short-circuit semantics.
+**Parameters**:
+
+- `a` (`T1`): First operand.
+- `b` (`T2`): Second operand.
+
+**Return**:
+
+- `andValue` (`T1|T2`): Result of `a and b`.
+
+**Example**:
+
```lua
land(true, false) --> false
```
-#### `lor`
+
+
+#### `lor(a, b)`
Evaluate `a or b` with Lua short-circuit semantics.
+**Parameters**:
+
+- `a` (`T1`): First operand.
+- `b` (`T2`): Second operand.
+
+**Return**:
+
+- `orValue` (`T1|T2`): Result of `a or b`.
+
+**Example**:
+
```lua
lor(false, true) --> true
```
-#### `lnot`
+
+
+#### `lnot(a)`
Return the boolean negation of `a`.
+**Parameters**:
+
+- `a` (`any`): Input value.
+
+**Return**:
+
+- `isNot` (`boolean`): Result of `not a`.
+
+**Example**:
+
```lua
lnot(true) --> false
```
### String & Length
-String concatenation and length operators.
+String concatenation and length operators.
-#### `concat`
+#### `concat(a, b)`
Concatenate two strings.
+**Parameters**:
+
+- `a` (`string`): Left string.
+- `b` (`string`): Right string.
+
+**Return**:
+
+- `concatenated` (`string`): Concatenated result `a .. b`.
+
+**Example**:
+
```lua
concat("a", "b") --> "ab"
```
-#### `len`
+
+
+#### `len(a)`
Return the length of a string or table using Lua's `#` operator.
+**Parameters**:
+
+- `a` (`string|table`): Value supporting Lua's `#` operator.
+
+**Return**:
+
+- `length` (`integer`): Length computed by `#a`.
+
+**Example**:
+
```lua
len("abc") --> 3
```
### Tables & Calls
-Table indexing helpers and function invocation.
+Table indexing helpers and function invocation.
-#### `index`
+#### `index(t, k)`
Return the value at key/index `k` in table `t`.
+**Parameters**:
+
+- `t` (`table`): Source table.
+- `k` (`T`): Key/index value.
+
+**Return**:
+
+- `value` (`T`): Value stored at `t[k]`.
+
+**Example**:
+
```lua
index({ a = 1 }, "a") --> 1
```
-#### `setindex`
+
+
+#### `setindex(t, k, v)`
Set `t[k] = v` and return the assigned value.
+**Parameters**:
+
+- `t` (`table`): Target table.
+- `k` (`any`): Key/index value.
+- `v` (`T`): Value to set.
+
+**Return**:
+
+- `value` (`T`): Assigned value `v`.
+
+**Example**:
+
```lua
setindex({}, "a", 1) --> 1
```
-#### `call`
+
+
+#### `call(f, ...)`
Call a function with variadic arguments and return its result.
+**Parameters**:
+
+- `f` (`fun(...:T1):T2`): Function to call.
+- `...` (`T1`): Additional arguments.
+
+**Return**:
+
+- `result` (`T2`): Return value(s) from `f(...)`.
+
+**Example**:
+
```lua
call(math.max, 1, 2) --> 2
```
diff --git a/docs/src/modules/repr.md b/docs/src/modules/repr.md
index 2970fe5..2590049 100644
--- a/docs/src/modules/repr.md
+++ b/docs/src/modules/repr.md
@@ -14,14 +14,36 @@ repr = require "mods.repr"
print(repr("Hello world!")) --> "Hello world!"
view = { user = { name = "Ada", tags = { "lua", "docs" } } }
-print(repr(view)) --> {
--- user = {
--- name = "Ada",
--- tags = {
--- [1] = "lua",
--- [2] = "docs"
--- }
--- }
--- }
+print(repr(view))
+--> {
+-- user = {
+-- name = "Ada",
+-- tags = {
+-- [1] = "lua",
+-- [2] = "docs"
+-- }
+-- }
+-- }
+```
+
+
+
+## `repr(v)`
+
+Convert a Lua value to a readable string representation.
+
+**Parameters**:
+- `v` (`any`): Value to render.
+
+**Return**:
+
+- `out` (`string`): Readable string representation.
+
+**Example**:
+
+```lua
+repr("Hello") --> '"Hello"'
+repr({ "a", "b" }) --> '{ "a", "b" }'
+repr() --> "nil"
```
diff --git a/docs/src/modules/runtime.md b/docs/src/modules/runtime.md
new file mode 100644
index 0000000..adfe43c
--- /dev/null
+++ b/docs/src/modules/runtime.md
@@ -0,0 +1,103 @@
+---
+desc: "Exposes Lua runtime metadata and version compatibility flags."
+---
+
+# `runtime`
+
+Exposes Lua runtime metadata and version compatibility flags.
+
+## Usage
+
+```lua
+runtime = require "mods.runtime"
+
+print(runtime.version) --> "Lua 5.x"
+print(runtime.version_num) --> 501 | 502 | 503 | 504
+print(runtime.is_lua54) --> true | false
+```
+
+## Fields
+
+| Field | Description |
+| ----------------------------- | ------------------------------------------------- |
+| [`version`](#version) | Version string reported by the runtime. |
+| [`major`](#major) | Major version number parsed from `version`. |
+| [`minor`](#minor) | Minor version number parsed from `version`. |
+| [`version_num`](#version-num) | Numeric version encoded as `major * 100 + minor`. |
+| [`is_luajit`](#is-luajit) | True when running under LuaJIT. |
+| [`is_lua51`](#is-lua51) | True only on Lua 5.1 runtimes. |
+| [`is_lua52`](#is-lua52) | True only on Lua 5.2 runtimes. |
+| [`is_lua53`](#is-lua53) | True only on Lua 5.3 runtimes. |
+| [`is_lua54`](#is-lua54) | True only on Lua 5.4 runtimes. |
+
+### `version`
+
+Version string reported by the runtime.
+
+```lua
+print(runtime.version) --> "Lua 5.x"
+```
+
+### `major`
+
+Major version number parsed from `version`.
+
+```lua
+print(runtime.major) --> 5
+```
+
+### `minor`
+
+Minor version number parsed from `version`.
+
+```lua
+print(runtime.minor) --> 1 | 2 | 3 | 4
+```
+
+### `version_num`
+
+Numeric version encoded as `major * 100 + minor`.
+
+```lua
+print(runtime.version_num) --> 501 | 502 | 503 | 504
+```
+
+### `is_luajit`
+
+True when running under LuaJIT.
+
+```lua
+print(runtime.is_luajit) --> true | false
+```
+
+### `is_lua51`
+
+True only on Lua 5.1 runtimes.
+
+```lua
+print(runtime.is_lua51) --> true | false
+```
+
+### `is_lua52`
+
+True only on Lua 5.2 runtimes.
+
+```lua
+print(runtime.is_lua52) --> true | false
+```
+
+### `is_lua53`
+
+True only on Lua 5.3 runtimes.
+
+```lua
+print(runtime.is_lua53) --> true | false
+```
+
+### `is_lua54`
+
+True only on Lua 5.4 runtimes.
+
+```lua
+print(runtime.is_lua54) --> true | false
+```
diff --git a/docs/src/modules/set.md b/docs/src/modules/set.md
index b2595b7..1c6768d 100644
--- a/docs/src/modules/set.md
+++ b/docs/src/modules/set.md
@@ -1,13 +1,13 @@
---
desc:
- "A Python-style set class providing common operations to create, modify, and
- query collections of unique values."
+ "A set class providing common operations to create, modify, and query
+ collections of unique values."
---
# `Set`
-A Python-style set class providing common operations to create, modify, and
-query collections of unique values.
+A set class providing common operations to create, modify, and query collections
+of unique values.
## Usage
@@ -18,248 +18,728 @@ s = Set({ "a" })
print(s:contains("a")) --> true
```
-## Dependencies
-
-Dependencies below are lazy-loaded 💤 on first access.
-
-- [`mods.tbl`](https://luamod.github.io/mods/modules/tbl)
-
## Functions
**Mutation**:
-| Function | Description |
-| ------------------------------------------------------------- | ----------------------------------------------------------- |
-| [`add`](#add) | Add an element to the set. |
-| [`clear`](#clear) | Remove all elements from the set. |
-| [`difference_update`](#difference-update) | Remove elements found in another set (in place). |
-| [`intersection_update`](#intersection-update) | Keep only elements common to both sets (in place). |
-| [`pop`](#pop) | Remove and return an arbitrary element. |
-| [`symmetric_difference_update`](#symmetric-difference-update) | Update the set with elements not shared by both (in place). |
-| [`update`](#update) | Add all elements from another set (in place). |
+| Function | Description |
+| --------------------------------------------------------------------- | ----------------------------------------------------------- |
+| [`add(v)`](#fn-add) | Add an element to the set. |
+| [`clear()`](#fn-clear) | Remove all elements from the set. |
+| [`difference_update(set)`](#fn-difference-update) | Remove elements found in another set (in place). |
+| [`intersection_update(set)`](#fn-intersection-update) | Keep only elements common to both sets (in place). |
+| [`pop()`](#fn-pop) | Remove and return an arbitrary element. |
+| [`symmetric_difference_update(set)`](#fn-symmetric-difference-update) | Update the set with elements not shared by both (in place). |
+| [`update(set)`](#fn-update) | Add all elements from another set (in place). |
**Copying**:
-| Function | Description |
-| ----------------------------------------------- | --------------------------------------------------- |
-| [`copy`](#copy) | Return a shallow copy of the set. |
-| [`difference`](#difference) | Return elements in this set but not in another. |
-| [`intersection`](#intersection) | Return elements common to both sets. |
-| [`remove`](#remove) | Remove an element if present, do nothing otherwise. |
-| [`symmetric_difference`](#symmetric-difference) | Return elements not shared by both sets. |
-| [`union`](#union) | Return a new set with all elements from both. |
+| 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. |
**Predicates**:
-| Function | Description |
-| --------------------------- | ---------------------------------------------------------------- |
-| [`isdisjoint`](#isdisjoint) | Return true if sets have no elements in common. |
-| [`isempty`](#isempty) | Return true if the set has no elements. |
-| [`issubset`](#issubset) | Return true if all elements of this set are also in another set. |
-| [`issuperset`](#issuperset) | Return true if this set contains all elements of another set. |
+| 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. |
+| [`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. |
**Query**:
-| Function | Description |
-| ----------------------- | ----------------------------------------- |
-| [`contains`](#contains) | Return true if the set contains `v`. |
-| [`len`](#len) | Return the number of elements in the set. |
+| Function | Description |
+| ----------------------------- | ----------------------------------------- |
+| [`contains(v)`](#fn-contains) | Return true if the set contains `v`. |
+| [`len()`](#fn-len) | Return the number of elements in the set. |
**Transform**:
-| Function | Description |
-| ------------------- | --------------------------------------- |
-| [`map`](#map) | Return a new set by mapping each value. |
-| [`values`](#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. |
+
+**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 `-`. |
### Mutation
-In-place operations that mutate the current set.
+In-place operations that mutate the current set.
-#### `add`
+#### `add(v)`
Add an element to the set.
+**Parameters**:
+
+- `v` (`any`): Value to add.
+
+**Return**:
+
+- `self` (`T`): Current set instance.
+
+**Example**:
+
```lua
s = Set({ "a" }):add("b") --> s contains "a", "b"
```
-#### `clear`
+
+
+#### `clear()`
Remove all elements from the set.
+**Return**:
+
+- `self` (`T`): Current set instance.
+
+**Example**:
+
```lua
s = Set({ "a", "b" }):clear() --> s is empty
```
-#### `difference_update`
+
+
+#### `difference_update(set)`
Remove elements found in another set (in place).
+**Parameters**:
+
+- `set` (`T`): Other set value.
+
+**Return**:
+
+- `self` (`T`): Current set instance.
+
+**Example**:
+
```lua
s = Set({ "a", "b" }):difference_update(Set({ "b" })) --> s contains "a"
```
-#### `intersection_update`
+
+
+#### `intersection_update(set)`
Keep only elements common to both sets (in place).
+**Parameters**:
+
+- `set` (`T`): Other set value.
+
+**Return**:
+
+- `self` (`T`): Current set instance.
+
+**Example**:
+
```lua
s = Set({ "a", "b" }):intersection_update(Set({ "b", "c" }))
--> s contains "b"
```
-#### `pop`
+
+
+#### `pop()`
Remove and return an arbitrary element.
+**Return**:
+
+- `value` (`any`): Removed value, or `nil` when the set is empty.
+
+**Example**:
+
```lua
v = Set({ "a", "b" }):pop() --> v is either "a" or "b"
```
-#### `symmetric_difference_update`
+
+
+#### `symmetric_difference_update(set)`
Update the set with elements not shared by both (in place).
+**Parameters**:
+
+- `set` (`T`): Other set value.
+
+**Return**:
+
+- `self` (`T`): Current set instance.
+
+**Example**:
+
```lua
s = Set({ "a", "b" }):symmetric_difference_update(Set({ "b", "c" }))
--> s contains "a", "c"
```
-#### `update`
+
+
+#### `update(set)`
Add all elements from another set (in place).
+**Parameters**:
+
+- `set` (`T`): Other set value.
+
+**Return**:
+
+- `self` (`T`): Current set instance.
+
+**Example**:
+
```lua
s = Set({ "a" }):update(Set({ "b" })) --> s contains "a", "b"
```
### Copying
-Non-mutating set operations that return new set instances.
+Non-mutating set operations that return new set instances.
-#### `copy`
+#### `copy()`
Return a shallow copy of the set.
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
```lua
c = Set({ "a" }):copy() --> c is a new set with "a"
```
-#### `difference`
+
+
+#### `difference(set)`
Return elements in this set but not in another.
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
```lua
d = Set({ "a", "b" }):difference(Set({ "b" })) --> d contains "a"
```
-#### `intersection`
+> [!NOTE]
+>
+> `difference` is also available as the `__sub` (`-`) operator.
+> `a:difference(b)` is equivalent to `a - b`.
+
+
+
+#### `intersection(set)`
Return elements common to both sets.
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
```lua
i = Set({ "a", "b" }):intersection(Set({ "b", "c" })) --> i contains "b"
```
-#### `remove`
+> [!NOTE]
+>
+> `intersection` is also available as `__band` (`&`) on Lua 5.3+.
+
+
+
+#### `remove(v)`
Remove an element if present, do nothing otherwise.
+**Parameters**:
+
+- `v` (`any`): Value to remove.
+
+**Return**:
+
+- `self` (`T`): Current set instance.
+
+**Example**:
+
```lua
s = Set({ "a", "b" }):remove("b") --> s contains "a"
```
-#### `symmetric_difference`
+
+
+#### `symmetric_difference(set)`
Return elements not shared by both sets.
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
```lua
d = Set({ "a", "b" }):symmetric_difference(Set({ "b", "c" }))
--> d contains "a", "c"
```
-#### `union`
+> [!NOTE]
+>
+> `symmetric_difference` is also available as `__bxor` (`^`) on Lua 5.3+.
+
+
+
+#### `union(set)`
Return a new set with all elements from both.
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
```lua
s = Set({ "a" }):union(Set({ "b" })) --> s contains "a", "b"
```
+> [!NOTE]
+>
+> `union` is available as `__add` (`+`) and `__bor` (`|`) on Lua 5.3+.
+> `a:union(b)` is equivalent to `a + b` and `a | b`.
+
### Predicates
-Boolean checks about set relationships and emptiness.
+Boolean checks about set relationships and emptiness.
-#### `isdisjoint`
+#### `isdisjoint(set)`
Return true if sets have no elements in common.
+**Parameters**:
+
+- `set` (`T`): Other set value.
+
+**Return**:
+
+- `ok` (`boolean`): True when sets have no elements in common.
+
+**Example**:
+
```lua
ok = Set({ "a" }):isdisjoint(Set({ "b" })) --> true
```
-#### `isempty`
+
+
+#### `equals(set)`
+
+Return true when both sets contain exactly the same members.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `ok` (`boolean`): True when both sets contain the same members.
+
+**Example**:
+
+```lua
+a = Set({ "a", "b" })
+b = Set({ "b", "a" })
+ok = a:equals(b) --> true
+```
+
+> [!NOTE]
+>
+> `equals` is also available as the `__eq` (`==`) operator. `a:equals(b)` is
+> equivalent to `a == b`.
+
+
+
+#### `isempty()`
Return true if the set has no elements.
+**Return**:
+
+- `ok` (`boolean`): True when the set has no elements.
+
+**Example**:
+
```lua
empty = Set({}):isempty() --> true
```
-#### `issubset`
+
+
+#### `issubset(set)`
Return true if all elements of this set are also in another set.
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `ok` (`boolean`): True when every element of `self` exists in `set`.
+
+**Example**:
+
```lua
ok = Set({ "a" }):issubset(Set({ "a", "b" })) --> true
```
-#### `issuperset`
+> [!NOTE]
+>
+> `issubset` is also available as the `__le` (`<=`) operator. `a:issubset(b)` is
+> equivalent to `a <= b`.
+
+
+
+#### `issuperset(set)`
Return true if this set contains all elements of another set.
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `ok` (`boolean`): True when `self` contains every element of `set`.
+
+**Example**:
+
```lua
ok = Set({ "a", "b" }):issuperset(Set({ "a" })) --> true
```
### Query
-Read-only queries for membership and size.
+Read-only queries for membership and size.
-#### `contains`
+#### `contains(v)`
Return true if the set contains `v`.
+**Parameters**:
+
+- `v` (`any`): Value to check.
+
+**Return**:
+
+- `ok` (`boolean`): True when `v` is present in the set.
+
+**Example**:
+
```lua
ok = Set({ "a", "b" }):contains("a") --> true
ok = Set({ "a", "b" }):contains("z") --> false
```
-#### `len`
+
+
+#### `len()`
Return the number of elements in the set.
+**Return**:
+
+- `count` (`integer`): Element count.
+
+**Example**:
+
```lua
n = Set({ "a", "b" }):len() --> 2
```
### Transform
-Value-to-value transformations and projection helpers.
+Value-to-value transformations and projection helpers.
-#### `map`
+#### `map(fn)`
Return a new set by mapping each value.
+**Parameters**:
+
+- `fn` (`fun(v:any):any`): Mapping function.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
```lua
s = Set({ 1, 2 }):map(function(v) return v * 10 end) --> s contains 10, 20
```
-#### `values`
+
+
+#### `values()`
Return a list of all values in the set.
+**Return**:
+
+- `values` (`mods.List`): List of set values.
+
+**Example**:
+
```lua
values = Set({ "a", "b" }):values() --> { "a", "b" }
```
+
+### Metamethods
+
+
+
+#### `__add(set)`
+
+Return the union of two sets using `+`.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
+```lua
+a = Set({ "a", "b" })
+b = Set({ "b", "c" })
+u = a + b --> { a = true, b = true, c = true }
+```
+
+> [!NOTE]
+>
+> `__add` is the operator form of `:union(set)`.
+
+
+
+#### `__bor(set)`
+
+Return the union of two sets using `|`.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
+```lua
+a = Set({ "a", "b" })
+b = Set({ "b", "c" })
+u = a | b --> { a = true, b = true, c = true }
+```
+
+> [!NOTE]
+>
+> `__bor` is the operator form of `:union(set)` on Lua 5.3+.
+
+
+
+#### `__band(set)`
+
+Return the intersection of two sets using `&`.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
+```lua
+a = Set({ "a", "b" })
+b = Set({ "b", "c" })
+i = a & b --> { b = true }
+```
+
+> [!NOTE]
+>
+> `__band` is the operator form of `:intersection(set)` on Lua 5.3+.
+
+
+
+#### `__bxor(set)`
+
+Return elements present in exactly one set using `^`.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
+```lua
+a = Set({ "a", "b" })
+b = Set({ "b", "c" })
+d = a ^ b --> { a = true, c = true }
+```
+
+> [!NOTE]
+>
+> `__bxor` is the operator form of `:symmetric_difference(set)` on Lua 5.3+.
+
+
+
+#### `__eq(set)`
+
+Return true if both sets contain exactly the same members using `==`.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `ok` (`boolean`): True when both sets contain the same members.
+
+**Example**:
+
+```lua
+ok = Set({ "a", "b" }) == Set({ "b", "a" }) --> true
+```
+
+> [!NOTE]
+>
+> `__eq` is the operator form of `:equals(set)`.
+
+
+
+#### `__le(set)`
+
+Return true if the left set is a subset of the right set using `<=`.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `ok` (`boolean`): True when `self` is a subset of `set`.
+
+**Example**:
+
+```lua
+a = Set({ "a" })
+b = Set({ "a", "b" })
+ok = a <= b --> true
+```
+
+> [!NOTE]
+>
+> `__le` is the operator form of `:issubset(set)`.
+
+
+
+#### `__lt(set)`
+
+Return true if the left set is a proper subset of the right set using `<`.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `ok` (`boolean`): True when `self` is a proper subset of `set`.
+
+**Example**:
+
+```lua
+a = Set({ "a" })
+b = Set({ "a", "b" })
+ok = a < b --> true
+```
+
+
+
+#### `__sub(set)`
+
+Return the difference of two sets using `-`.
+
+**Parameters**:
+
+- `set` (`mods.Set|table`): Other set value.
+
+**Return**:
+
+- `set` (`mods.Set`): New set.
+
+**Example**:
+
+```lua
+a = Set({ "a", "b" })
+b = Set({ "b", "c" })
+d = a - b --> { a = true }
+```
+
+> [!NOTE]
+>
+> `__sub` is the operator form of `:difference(set)`.
diff --git a/docs/src/modules/str.md b/docs/src/modules/str.md
index 987bbbc..391a811 100644
--- a/docs/src/modules/str.md
+++ b/docs/src/modules/str.md
@@ -14,167 +14,263 @@ str = require "mods.str"
print(str.capitalize("hello world")) --> "Hello world"
```
-## Dependencies
-
-Dependencies below are lazy-loaded 💤 on first access.
-
-- [`mods.keyword`](https://luamod.github.io/mods/modules/keyword)
-- [`mods.List`](https://luamod.github.io/mods/modules/list)
-- [`mods.stringcase`](https://luamod.github.io/mods/modules/stringcase)
-
## Functions
**Formatting**:
-| Function | Description |
-| --------------------------- | --------------------------------------------------------------------- |
-| [`capitalize`](#capitalize) | Return copy with first character capitalized and the rest lowercased. |
-| [`center`](#center) | Center string within width, padded with fill characters. |
-| [`count`](#count) | Count non-overlapping occurrences of a substring. |
-| [`endswith`](#endswith) | Return true if string ends with suffix. |
-| [`expandtabs`](#expandtabs) | Expand tabs to spaces using given tabsize. |
-| [`find`](#find) | Return lowest index of substring or nil if not found. |
-| [`format_map`](#format-map) | Format string with mapping (key-based) replacement. |
+| Function | Description |
+| ---------------------------------------------------- | --------------------------------------------------------------------- |
+| [`capitalize(s)`](#fn-capitalize) | Return copy with first character capitalized and the rest lowercased. |
+| [`center(s, width, fillchar?)`](#fn-center) | Center string within width, padded with fill characters. |
+| [`count(s, sub, start?, stop?)`](#fn-count) | Count non-overlapping occurrences of a substring. |
+| [`endswith(s, suffix, start?, stop?)`](#fn-endswith) | Return true if string ends with suffix. |
+| [`expandtabs(s, tabsize?)`](#fn-expandtabs) | Expand tabs to spaces using given tabsize. |
+| [`find(s, sub, start?, stop?)`](#fn-find) | Return lowest index of substring or nil if not found. |
+| [`format_map(s, mapping)`](#fn-format-map) | Format string with mapping (key-based) replacement. |
**Predicates**:
-| Function | Description |
-| ------------------------------- | -------------------------------------------------------------------------------------------- |
-| [`isalnum`](#isalnum) | Return true if all characters are alphanumeric and string is non-empty. |
-| [`isalpha`](#isalpha) | Return true if all characters are alphabetic and string is non-empty. |
-| [`isascii`](#isascii) | Return true if all characters are ASCII and string is non-empty. |
-| [`isdecimal`](#isdecimal) | Return true if all characters are decimal characters and string is non-empty. |
-| [`isdigit`](#isdigit) | Return true if all characters are digits and string is non-empty. |
-| [`isidentifier`](#isidentifier) | Return true if string is a valid identifier and not a reserved keyword. |
-| [`islower`](#islower) | Return true if all cased characters are lowercase and there is at least one cased character. |
-| [`isnumeric`](#isnumeric) | Return true if all characters are numeric and string is non-empty. |
-| [`isprintable`](#isprintable) | Return true if all characters are printable and string is non-empty. |
-| [`isspace`](#isspace) | Return true if all characters are whitespace and string is non-empty. |
-| [`istitle`](#istitle) | Return true if string is titlecased. |
-| [`isupper`](#isupper) | Return true if all cased characters are uppercase and there is at least one cased character. |
+| Function | Description |
+| ------------------------------------- | -------------------------------------------------------------------------------------------- |
+| [`isalnum(s)`](#fn-isalnum) | Return true if all characters are alphanumeric and string is non-empty. |
+| [`isalpha(s)`](#fn-isalpha) | Return true if all characters are alphabetic and string is non-empty. |
+| [`isascii(s)`](#fn-isascii) | Return true if all characters are ASCII. |
+| [`isdecimal(s)`](#fn-isdecimal) | Return true if all characters are decimal characters and string is non-empty. |
+| [`isdigit(s)`](#fn-isdigit) | Return true if all characters are digits and string is non-empty. |
+| [`isidentifier(s)`](#fn-isidentifier) | Return true if string is a valid identifier and not a reserved keyword. |
+| [`islower(s)`](#fn-islower) | Return true if all cased characters are lowercase and there is at least one cased character. |
+| [`isnumeric(s)`](#fn-isnumeric) | Return true if all characters are numeric and string is non-empty. |
+| [`isprintable(s)`](#fn-isprintable) | Return true if all characters are printable. |
+| [`isspace(s)`](#fn-isspace) | Return true if all characters are whitespace and string is non-empty. |
+| [`istitle(s)`](#fn-istitle) | Return true if string is titlecased. |
+| [`isupper(s)`](#fn-isupper) | Return true if all cased characters are uppercase and there is at least one cased character. |
**Layout**:
-| Function | Description |
-| ------------------- | ------------------------------------------------------------- |
-| [`join`](#join) | Join an iterable of strings using this string as separator. |
-| [`ljust`](#ljust) | Left-justify string in a field of given width. |
-| [`lower`](#lower) | Return lowercased copy. |
-| [`lstrip`](#lstrip) | Remove leading characters (default: whitespace). |
-| [`rstrip`](#rstrip) | Remove trailing characters (default: whitespace). |
-| [`strip`](#strip) | Remove leading and trailing characters (default: whitespace). |
+| Function | Description |
+| ----------------------------------------- | ------------------------------------------------------------------- |
+| [`join(sep, ls)`](#fn-join) | Join an array-like table of strings using this string as separator. |
+| [`ljust(s, width, fillchar?)`](#fn-ljust) | Left-justify string in a field of given width. |
+| [`lower(s)`](#fn-lower) | Return lowercased copy. |
+| [`lstrip(s, chars?)`](#fn-lstrip) | Remove leading characters (default: whitespace). |
+| [`rstrip(s, chars?)`](#fn-rstrip) | Remove trailing characters (default: whitespace). |
+| [`strip(s, chars?)`](#fn-strip) | Remove leading and trailing characters (default: whitespace). |
**Split & Replace**:
-| Function | Description |
-| ------------------------------- | ------------------------------------------------------------------------- |
-| [`partition`](#partition) | Partition string into head, sep, tail from left. |
-| [`removeprefix`](#removeprefix) | Remove prefix if present. |
-| [`removesuffix`](#removesuffix) | Remove suffix if present. |
-| [`replace`](#replace) | Return a copy of the string with all occurrences of a substring replaced. |
-| [`rfind`](#rfind) | Return highest index of substring or nil if not found. |
-| [`rindex`](#rindex) | Like rfind but raises on failure (placeholder). |
-| [`rjust`](#rjust) | Right-justify string in a field of given width. |
-| [`rpartition`](#rpartition) | Partition string into head, sep, tail from right. |
-| [`rsplit`](#rsplit) | Split from the right by separator, up to maxsplit. |
-| [`split`](#split) | Split by separator (or whitespace) up to maxsplit. |
-| [`splitlines`](#splitlines) | Split on line boundaries. |
+| Function | Description |
+| --------------------------------------------- | ------------------------------------------------------------------------- |
+| [`partition(s, sep)`](#fn-partition) | Partition string into head, sep, tail from left. |
+| [`removeprefix(s, prefix)`](#fn-removeprefix) | Remove prefix if present. |
+| [`removesuffix(s, suffix)`](#fn-removesuffix) | Remove suffix if present. |
+| [`replace(s, old, new, count?)`](#fn-replace) | Return a copy of the string with all occurrences of a substring replaced. |
+| [`rfind(s, sub, start?, stop?)`](#fn-rfind) | Return highest index of substring or nil if not found. |
+| [`rindex(s, sub, start?, stop?)`](#fn-rindex) | Like `rfind` but raises an error when the substring is not found. |
+| [`rjust(s, width, fillchar?)`](#fn-rjust) | Right-justify string in a field of given width. |
+| [`rpartition(s, sep)`](#fn-rpartition) | Partition string into head, sep, tail from right. |
+| [`rsplit(s, sep?, maxsplit?)`](#fn-rsplit) | Split from the right by separator, up to maxsplit. |
+| [`split(s, sep?, maxsplit?)`](#fn-split) | Split by separator (or whitespace) up to maxsplit. |
+| [`splitlines(s, keepends?)`](#fn-splitlines) | Split on line boundaries. |
**Casing & Transform**:
-| Function | Description |
-| --------------------------- | --------------------------------------------------------- |
-| [`swapcase`](#swapcase) | Return a copy with case of alphabetic characters swapped. |
-| [`startswith`](#startswith) | Return true if string starts with prefix. |
-| [`title`](#title) | Return titlecased copy. |
-| [`translate`](#translate) | Translate characters using a mapping table. |
-| [`upper`](#upper) | Return uppercased copy. |
-| [`zfill`](#zfill) | Pad numeric string on the left with zeros. |
+| Function | Description |
+| -------------------------------------------------------- | --------------------------------------------------------- |
+| [`swapcase(s)`](#fn-swapcase) | Return a copy with case of alphabetic characters swapped. |
+| [`startswith(s, prefix, start?, stop?)`](#fn-startswith) | Return true if string starts with prefix. |
+| [`title(s)`](#fn-title) | Return titlecased copy. |
+| [`translate(s, table_map)`](#fn-translate) | Translate characters using a mapping table. |
+| [`upper(s)`](#fn-upper) | Return uppercased copy. |
+| [`zfill(s, width)`](#fn-zfill) | Pad numeric string on the left with zeros. |
### Formatting
-#### `capitalize`
+
+
+#### `capitalize(s)`
Return copy with first character capitalized and the rest lowercased.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = capitalize("hello WORLD")
---result: "Hello world"
+s = capitalize("hello WORLD") --> "Hello world"
```
-#### `center`
+
+
+#### `center(s, width, fillchar?)`
Center string within width, padded with fill characters.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `width` (`integer`): Target width.
+- `fillchar?` (`string`): Optional fill character.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = center("hi", 6, "-")
---result: "--hi--"
+s = center("hi", 6, "-") --> "--hi--"
```
-#### `count`
+
+
+#### `count(s, sub, start?, stop?)`
Count non-overlapping occurrences of a substring.
-```lua
-n = count("aaaa", "aa")
---result: 2
+**Parameters**:
-n = count("aaaa", "a", 2, -1)
---result: 2
+- `s` (`string`): Input string.
+- `sub` (`string`): Substring to search.
+- `start?` (`integer`): Optional start index (defaults to `1`).
+- `stop?` (`integer`): Optional exclusive end index (defaults to `#s + 1`).
-n = count("abcd", "")
---result: 5
+**Return**:
+
+- `n` (`integer`): Computed numeric value.
+
+**Example**:
+
+```lua
+n = count("aaaa", "aa") --> 2
+n = count("aaaa", "a", 2, -1) --> 2
+n = count("abcd", "") --> 5
```
-#### `endswith`
+
+
+#### `endswith(s, suffix, start?, stop?)`
Return true if string ends with suffix.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `suffix` (`string|string[]`): Suffix string.
+- `start?` (`integer`): Optional start index (defaults to `1`).
+- `stop?` (`integer`): Optional exclusive end index (defaults to `#s + 1`).
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` ends with `suffix`.
+
+**Example**:
+
+```lua
+ok = endswith("hello.lua", ".lua") --> true
+```
+
> [!NOTE]
>
> If suffix is a list, returns `true` when any suffix matches.
-```lua
-ok = endswith("hello.lua", ".lua")
---result: true
-```
+
-#### `expandtabs`
+#### `expandtabs(s, tabsize?)`
Expand tabs to spaces using given tabsize.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `tabsize?` (`integer`): Optional tab width (defaults to `8`).
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = expandtabs("a\tb", 4)
---result: "a b"
+s = expandtabs("a\tb", 4) --> "a b"
```
-#### `find`
+
+
+#### `find(s, sub, start?, stop?)`
Return lowest index of substring or nil if not found.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `sub` (`string`): Substring to search.
+- `start?` (`integer`): Optional start index (defaults to `1`).
+- `stop?` (`integer`): Optional exclusive end index (defaults to `#s + 1`).
+
+**Return**:
+
+- `index` (`integer?`): First match index, or `nil` when not found.
+
+**Example**:
+
```lua
-i = find("hello", "ll")
---result: 3
+i = find("hello", "ll") --> 3
```
-#### `format_map`
+
+
+#### `format_map(s, mapping)`
Format string with mapping (key-based) replacement.
+**Parameters**:
+
+- `s` (`string`): Template string with `{key}` placeholders.
+- `mapping` (`table`): Values used to replace placeholder keys.
+
+**Return**:
+
+- `s` (`string`): Formatted string with placeholders replaced.
+
+**Example**:
+
```lua
-s = format_map("hi {name}", { name = "bob" })
---result: "hi bob"
+s = format_map("hi {name}", { name = "bob" }) --> "hi bob"
```
+> [!NOTE]
+>
+> `format_map` is a lightweight `{key}` replacement helper. For richer
+> templating, use
+> [`mods.template`](https://luamod.github.io/mods/modules/template).
+
### Predicates
-#### `isalnum`
+
+
+#### `isalnum(s)`
Return true if all characters are alphanumeric and string is non-empty.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` is non-empty and all characters are
+ alphanumeric.
+
+**Example**:
+
```lua
-ok = isalnum("abc123")
---result: true
+ok = isalnum("abc123") --> true
```
> [!NOTE]
@@ -182,16 +278,28 @@ ok = isalnum("abc123")
> Lua letters are ASCII by default, so non-ASCII letters are not alphanumeric.
>
> ```lua
-> isalnum("á1")` --> `false`
+> isalnum("á1") --> false
> ```
-#### `isalpha`
+
+
+#### `isalpha(s)`
Return true if all characters are alphabetic and string is non-empty.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` is non-empty and all characters are
+ alphabetic.
+
+**Example**:
+
```lua
-ok = isalpha("abc")
---result: true
+ok = isalpha("abc") --> true
```
> [!NOTE]
@@ -199,329 +307,727 @@ ok = isalpha("abc")
> Lua letters are ASCII by default, so non-ASCII letters are not alphabetic.
>
> ```lua
-> isalpha("á")` --> `false`
+> isalpha("á") --> false
> ```
-#### `isascii`
+
+
+#### `isascii(s)`
+
+Return true if all characters are ASCII.
+
+**Parameters**:
-Return true if all characters are ASCII and string is non-empty.
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when all bytes in `s` are ASCII.
+
+**Example**:
```lua
-ok = isascii("hello")
---result: true
+ok = isascii("hello") --> true
```
> [!NOTE]
>
> The empty string returns `true`.
-#### `isdecimal`
+
+
+#### `isdecimal(s)`
Return true if all characters are decimal characters and string is non-empty.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` is non-empty and all characters are decimal
+ digits.
+
+**Example**:
+
```lua
-ok = isdecimal("123")
---result: true
+ok = isdecimal("123") --> true
```
-#### `isdigit`
+
+
+#### `isdigit(s)`
Return true if all characters are digits and string is non-empty.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` is non-empty and all characters are digits.
+
+**Example**:
+
```lua
-ok = isdigit("123")
---result: true
+ok = isdigit("123") --> true
```
-#### `isidentifier`
+
+
+#### `isidentifier(s)`
Return true if string is a valid identifier and not a reserved keyword.
-```lua
-ok = isidentifier("foo_bar")
---result: true
+**Parameters**:
-ok = isidentifier("2var")
---result: false
+- `s` (`string`): Input string.
-ok = isidentifier("end")
---result: false (keyword)
+**Return**:
+
+- `ok` (`boolean`): True when `s` is a valid identifier and not a keyword.
+
+**Example**:
+
+```lua
+ok = isidentifier("foo_bar") --> true
+ok = isidentifier("2var") --> false
+ok = isidentifier("end") --> false (keyword)
```
-#### `islower`
+
+
+#### `islower(s)`
Return true if all cased characters are lowercase and there is at least one
cased character.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` has at least one cased character and all are
+ lowercase.
+
+**Example**:
+
```lua
-ok = islower("hello")
---result: true
+ok = islower("hello") --> true
```
-#### `isnumeric`
+
+
+#### `isnumeric(s)`
Return true if all characters are numeric and string is non-empty.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` is non-empty and all characters are numeric.
+
+**Example**:
+
```lua
-ok = isnumeric("123")
---result: true
+ok = isnumeric("123") --> true
```
-#### `isprintable`
+
-Return true if all characters are printable and string is non-empty.
+#### `isprintable(s)`
+
+Return true if all characters are printable.
+
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when all bytes in `s` are printable ASCII.
+
+**Example**:
```lua
-ok = isprintable("abc!")
---result: true
+ok = isprintable("abc!") --> true
```
> [!NOTE]
>
> The empty string returns `true`.
-#### `isspace`
+
+
+#### `isspace(s)`
Return true if all characters are whitespace and string is non-empty.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` is non-empty and all characters are
+ whitespace.
+
+**Example**:
+
```lua
-ok = isspace(" \t")
---result: true
+ok = isspace(" \t") --> true
```
-#### `istitle`
+
+
+#### `istitle(s)`
Return true if string is titlecased.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` is titlecased.
+
+**Example**:
+
```lua
-ok = istitle("Hello World")
---result: true
+ok = istitle("Hello World") --> true
```
-#### `isupper`
+
+
+#### `isupper(s)`
Return true if all cased characters are uppercase and there is at least one
cased character.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` has at least one cased character and all are
+ uppercase.
+
+**Example**:
+
```lua
-ok = isupper("HELLO")
---result: true
+ok = isupper("HELLO") --> true
```
### Layout
-#### `join`
+
+
+#### `join(sep, ls)`
-Join an iterable of strings using this string as separator.
+Join an array-like table of strings using this string as separator.
+
+**Parameters**:
+
+- `sep` (`string`): Separator value.
+- `ls` (`string[]`): Table value.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
```lua
-s = join(",", { "a", "b", "c" })
---result: "a,b,c"
+s = join(",", { "a", "b", "c" }) --> "a,b,c"
```
-#### `ljust`
+
+
+#### `ljust(s, width, fillchar?)`
Left-justify string in a field of given width.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `width` (`integer`): Target width.
+- `fillchar?` (`string`): Optional fill character.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = ljust("hi", 5, ".")
---result: "hi..."
+s = ljust("hi", 5, ".") --> "hi..."
```
-#### `lower`
+
+
+#### `lower(s)`
Return lowercased copy.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = lower("HeLLo")
---result: "hello"
+s = lower("HeLLo") --> "hello"
```
-#### `lstrip`
+
+
+#### `lstrip(s, chars?)`
Remove leading characters (default: whitespace).
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `chars?` (`string`): Optional character set.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = lstrip(" hello")
---result: "hello"
+s = lstrip(" hello") --> "hello"
```
-#### `rstrip`
+
+
+#### `rstrip(s, chars?)`
Remove trailing characters (default: whitespace).
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `chars?` (`string`): Optional character set.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = rstrip("hello ")
---result: "hello"
+s = rstrip("hello ") --> "hello"
```
-#### `strip`
+
+
+#### `strip(s, chars?)`
Remove leading and trailing characters (default: whitespace).
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `chars?` (`string`): Optional character set.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = strip(" hello ")
---result: "hello"
+s = strip(" hello ") --> "hello"
```
### Split & Replace
-#### `partition`
+
+
+#### `partition(s, sep)`
Partition string into head, sep, tail from left.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `sep` (`string`): Separator value.
+
+**Return**:
+
+- `head` (`string`): Part before the separator.
+- `sep_found` (`string`): Matched separator, or empty string when not found.
+- `tail` (`string`): Part after the separator.
+
+**Example**:
+
```lua
-a, b, c = partition("a-b-c", "-")
---result: "a", "-", "b-c"
+a, b, c = partition("a-b-c", "-") --> "a", "-", "b-c"
```
-#### `removeprefix`
+
+
+#### `removeprefix(s, prefix)`
Remove prefix if present.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `prefix` (`string`): Prefix string.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = removeprefix("foobar", "foo")
---result: "bar"
+s = removeprefix("foobar", "foo") --> "bar"
```
-#### `removesuffix`
+
+
+#### `removesuffix(s, suffix)`
Remove suffix if present.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `suffix` (`string`): Suffix string.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = removesuffix("foobar", "bar")
---result: "foo"
+s = removesuffix("foobar", "bar") --> "foo"
```
-#### `replace`
+
+
+#### `replace(s, old, new, count?)`
Return a copy of the string with all occurrences of a substring replaced.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `old` (`string`): Substring to replace.
+- `new` (`string`): Replacement string.
+- `count?` (`integer`): Optional maximum replacement count.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = replace("a-b-c", "-", "_", 1)
---result: "a_b-c"
+s = replace("a-b-c", "-", "_", 1) --> "a_b-c"
```
-#### `rfind`
+
+
+#### `rfind(s, sub, start?, stop?)`
Return highest index of substring or nil if not found.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `sub` (`string`): Substring to search.
+- `start?` (`integer`): Optional start index (defaults to `1`).
+- `stop?` (`integer`): Optional inclusive end index (defaults to `#s`).
+
+**Return**:
+
+- `index` (`integer?`): Last match index, or `nil` when not found.
+
+**Example**:
+
```lua
-i = rfind("ababa", "ba")
---result: 4
+i = rfind("ababa", "ba") --> 4
```
-#### `rindex`
+
+
+#### `rindex(s, sub, start?, stop?)`
+
+Like `rfind` but raises an error when the substring is not found.
-Like rfind but raises on failure (placeholder).
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `sub` (`string`): Substring to search.
+- `start?` (`integer`): Optional start index (defaults to `1`).
+- `stop?` (`integer`): Optional inclusive end index (defaults to `#s`).
+
+**Return**:
+
+- `index` (`integer`): Computed numeric value.
+
+**Example**:
```lua
-i = rindex("ababa", "ba")
---result: 4
+i = rindex("ababa", "ba") --> 4
```
-#### `rjust`
+
+
+#### `rjust(s, width, fillchar?)`
Right-justify string in a field of given width.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `width` (`integer`): Target width.
+- `fillchar?` (`string`): Optional fill character.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = rjust("hi", 5, ".")
---result: "...hi"
+s = rjust("hi", 5, ".") --> "...hi"
```
-#### `rpartition`
+
+
+#### `rpartition(s, sep)`
Partition string into head, sep, tail from right.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `sep` (`string`): Separator value.
+
+**Return**:
+
+- `head` (`string`): Part before the separator.
+- `sep_found` (`string`): Matched separator, or empty string when not found.
+- `tail` (`string`): Part after the separator.
+
+**Example**:
+
```lua
-a, b, c = rpartition("a-b-c", "-")
---result: "a-b", "-", "c"
+a, b, c = rpartition("a-b-c", "-") --> "a-b", "-", "c"
```
-#### `rsplit`
+
+
+#### `rsplit(s, sep?, maxsplit?)`
Split from the right by separator, up to maxsplit.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `sep?` (`string`): Optional separator value.
+- `maxsplit?` (`integer`): Optional maximum number of splits.
+
+**Return**:
+
+- `parts` (`mods.List`): Split parts.
+
+**Example**:
+
```lua
-parts = rsplit("a,b,c", ",", 1)
---result: { "a,b", "c" }
+parts = rsplit("a,b,c", ",", 1) --> { "a,b", "c" }
```
-#### `split`
+
+
+#### `split(s, sep?, maxsplit?)`
Split by separator (or whitespace) up to maxsplit.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `sep?` (`string`): Optional separator value.
+- `maxsplit?` (`integer`): Optional maximum number of splits.
+
+**Return**:
+
+- `parts` (`mods.List`): Split parts.
+
+**Example**:
+
```lua
-parts = split("a,b,c", ",")
---result: { "a", "b", "c" }
+parts = split("a,b,c", ",") --> { "a", "b", "c" }
```
-#### `splitlines`
+
+
+#### `splitlines(s, keepends?)`
Split on line boundaries.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `keepends?` (`boolean`): Optional whether to keep line endings.
+
+**Return**:
+
+- `lines` (`mods.List`): Split lines.
+
+**Example**:
+
```lua
-lines = splitlines("a\nb\r\nc")
---result: { "a", "b", "c" }
+lines = splitlines("a\nb\r\nc") --> { "a", "b", "c" }
```
### Casing & Transform
-#### `swapcase`
+
+
+#### `swapcase(s)`
Return a copy with case of alphabetic characters swapped.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = swapcase("AbC")
---result: "aBc"
+s = swapcase("AbC") --> "aBc"
```
-#### `startswith`
+
+
+#### `startswith(s, prefix, start?, stop?)`
Return true if string starts with prefix.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `prefix` (`string|string[]`): Prefix string.
+- `start?` (`integer`): Optional start index (defaults to `1`).
+- `stop?` (`integer`): Optional exclusive end index (defaults to `#s + 1`).
+
+**Return**:
+
+- `ok` (`boolean`): True when `s` starts with `prefix`.
+
+**Example**:
+
+```lua
+ok = startswith("hello.lua", "he") --> true
+```
+
> [!NOTE]
>
> If prefix is a list, returns `true` when any prefix matches.
-```lua
-ok = startswith("hello.lua", "he")
---result: true
-```
+
-#### `title`
+#### `title(s)`
Return titlecased copy.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = title("hello world")
---result: "Hello World"
+s = title("hello world") --> "Hello World"
```
-#### `translate`
+
+
+#### `translate(s, table_map)`
Translate characters using a mapping table.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `table_map` (`table`): Character translation map.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
map = { [string.byte("a")] = "b", ["c"] = false }
-s = translate("abc", map)
---result: "bb"
+s = translate("abc", map) --> "bb"
```
-#### `upper`
+
+
+#### `upper(s)`
Return uppercased copy.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = upper("Hello")
---result: "HELLO"
+s = upper("Hello") --> "HELLO"
```
-#### `zfill`
+
+
+#### `zfill(s, width)`
Pad numeric string on the left with zeros.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `width` (`integer`): Target width.
+
+**Return**:
+
+- `s` (`string`): Computed string value.
+
+**Example**:
+
```lua
-s = zfill("42", 5)
---result: "00042"
+s = zfill("42", 5) --> "00042"
```
diff --git a/docs/src/modules/stringcase.md b/docs/src/modules/stringcase.md
index aabbfe5..69c02ed 100644
--- a/docs/src/modules/stringcase.md
+++ b/docs/src/modules/stringcase.md
@@ -9,7 +9,7 @@ String case conversion helpers.
## Usage
```lua
-sc = require "mods.stringcase"
+stringcase = require "mods.stringcase"
print(stringcase.snake("FooBar")) --> "foo_bar"
```
@@ -18,50 +18,74 @@ print(stringcase.snake("FooBar")) --> "foo_bar"
**Basic**:
-| Function | Description |
-| ----------------- | -------------------------------- |
-| [`lower`](#lower) | Convert string to all lowercase. |
-| [`upper`](#upper) | Convert string to all uppercase. |
+| Function | Description |
+| ----------------------- | -------------------------------- |
+| [`lower(s)`](#fn-lower) | Convert string to all lowercase. |
+| [`upper(s)`](#fn-upper) | Convert string to all uppercase. |
**Word Case**:
-| Function | Description |
-| ----------------------- | --------------------------------------------------------------------- |
-| [`snake`](#snake) | Convert string to snake_case. |
-| [`camel`](#camel) | Convert string to camelCase. |
-| [`replace`](#replace) | Normalize to snake_case, then replace underscores with a separator. |
-| [`acronym`](#acronym) | Get acronym of words in string (first letters only). |
-| [`title`](#title) | Convert string to Title Case (first letter of each word capitalized). |
-| [`constant`](#constant) | Convert string to CONSTANT_CASE (uppercase snake_case). |
-| [`pascal`](#pascal) | Convert string to PascalCase. |
-| [`kebab`](#kebab) | Convert string to kebab-case. |
-| [`dot`](#dot) | Convert string to dot.case. |
-| [`space`](#space) | Convert string to space case (spaces between words). |
-| [`path`](#path) | Convert string to path/case (slashes between words). |
+| Function | Description |
+| --------------------------------- | --------------------------------------------------------------------- |
+| [`snake(s)`](#fn-snake) | Convert string to snake_case. |
+| [`camel(s)`](#fn-camel) | Convert string to camelCase. |
+| [`replace(s, sep?)`](#fn-replace) | Normalize to snake_case, then replace underscores with a separator. |
+| [`acronym(s)`](#fn-acronym) | Get acronym of words in string (first letters only). |
+| [`title(s)`](#fn-title) | Convert string to Title Case (first letter of each word capitalized). |
+| [`constant(s)`](#fn-constant) | Convert string to CONSTANT_CASE (uppercase snake_case). |
+| [`pascal(s)`](#fn-pascal) | Convert string to PascalCase. |
+| [`kebab(s)`](#fn-kebab) | Convert string to kebab-case. |
+| [`dot(s)`](#fn-dot) | Convert string to dot.case. |
+| [`space(s)`](#fn-space) | Convert string to space case (spaces between words). |
+| [`path(s)`](#fn-path) | Convert string to path/case (slashes between words). |
**Letter Case**:
-| Function | Description |
-| ----------------------- | ------------------------------------------------------------------------- |
-| [`swap`](#swap) | Swap case of each letter. |
-| [`capital`](#capital) | Capitalize the first letter and lowercase the rest. |
-| [`sentence`](#sentence) | Convert string to sentence case (first letter uppercase, rest lowercase). |
+| Function | Description |
+| ----------------------------- | ------------------------------------------------------------------------- |
+| [`swap(s)`](#fn-swap) | Swap case of each letter. |
+| [`capital(s)`](#fn-capital) | Capitalize the first letter and lowercase the rest. |
+| [`sentence(s)`](#fn-sentence) | Convert string to sentence case (first letter uppercase, rest unchanged). |
### Basic
-#### `lower`
+
+
+#### `lower(s)`
Convert string to all lowercase.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Lowercased string.
+
+**Example**:
+
```lua
lower("foo_bar-baz") --> "foo_bar-baz"
lower("FooBar baz") --> "foobar baz"
```
-#### `upper`
+
+
+#### `upper(s)`
Convert string to all uppercase.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Uppercased string.
+
+**Example**:
+
```lua
upper("foo_bar-baz") --> "FOO_BAR-BAZ"
upper("FooBar baz") --> "FOOBAR BAZ"
@@ -69,100 +93,233 @@ upper("FooBar baz") --> "FOOBAR BAZ"
### Word Case
-#### `snake`
+
+
+#### `snake(s)`
Convert string to snake_case.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Snake-cased string.
+
+**Example**:
+
```lua
snake("foo_bar-baz") --> "foo_bar_baz"
snake("FooBar baz") --> "foo_bar_baz"
```
-#### `camel`
+
+
+#### `camel(s)`
Convert string to camelCase.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Camel-cased string.
+
+**Example**:
+
```lua
camel("foo_bar-baz") --> "fooBarBaz"
camel("FooBar baz") --> "fooBarBaz"
```
-#### `replace`
+
+
+#### `replace(s, sep?)`
Normalize to snake_case, then replace underscores with a separator.
+**Parameters**:
+
+- `s` (`string`): Input string.
+- `sep?` (`string`): Optional separator value (defaults to `""`).
+
+**Return**:
+
+- `s` (`string`): String with underscores replaced by `sep`.
+
+**Example**:
+
```lua
replace("foo_bar-baz", "-") --> "foo-bar-baz"
replace("FooBar baz", "-") --> "foo-bar-baz"
```
-#### `acronym`
+
+
+#### `acronym(s)`
Get acronym of words in string (first letters only).
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Acronym string.
+
+**Example**:
+
```lua
acronym("foo_bar-baz") --> "FBB"
acronym("FooBar baz") --> "FBB"
```
-#### `title`
+
+
+#### `title(s)`
Convert string to Title Case (first letter of each word capitalized).
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Title-cased string.
+
+**Example**:
+
```lua
title("foo_bar-baz") --> "Foo Bar Baz"
title("FooBar baz") --> "Foo Bar Baz"
```
-#### `constant`
+
+
+#### `constant(s)`
Convert string to CONSTANT_CASE (uppercase snake_case).
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Constant-cased string.
+
+**Example**:
+
```lua
constant("foo_bar-baz") --> "FOO_BAR_BAZ"
constant("FooBar baz") --> "FOO_BAR_BAZ"
```
-#### `pascal`
+
+
+#### `pascal(s)`
Convert string to PascalCase.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Pascal-cased string.
+
+**Example**:
+
```lua
pascal("foo_bar-baz") --> "FooBarBaz"
pascal("FooBar baz") --> "FooBarBaz"
```
-#### `kebab`
+
+
+#### `kebab(s)`
Convert string to kebab-case.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Kebab-cased string.
+
+**Example**:
+
```lua
kebab("foo_bar-baz") --> "foo-bar-baz"
kebab("FooBar baz") --> "foo-bar-baz"
```
-#### `dot`
+
+
+#### `dot(s)`
Convert string to dot.case.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Dot-cased string.
+
+**Example**:
+
```lua
dot("foo_bar-baz") --> "foo.bar.baz"
dot("FooBar baz") --> "foo.bar.baz"
```
-#### `space`
+
+
+#### `space(s)`
Convert string to space case (spaces between words).
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Space-cased string.
+
+**Example**:
+
```lua
space("foo_bar-baz") --> "foo bar baz"
space("FooBar baz") --> "foo bar baz"
```
-#### `path`
+
+
+#### `path(s)`
Convert string to path/case (slashes between words).
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Path-cased string.
+
+**Example**:
+
```lua
path("foo_bar-baz") --> "foo/bar/baz"
path("FooBar baz") --> "foo/bar/baz"
@@ -170,27 +327,63 @@ path("FooBar baz") --> "foo/bar/baz"
### Letter Case
-#### `swap`
+
+
+#### `swap(s)`
Swap case of each letter.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Swap-cased string.
+
+**Example**:
+
```lua
swap("foo_bar-baz") --> "FOO_BAR-BAZ"
swap("FooBar baz") --> "fOObAR BAZ"
```
-#### `capital`
+
+
+#### `capital(s)`
Capitalize the first letter and lowercase the rest.
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Capitalized string.
+
+**Example**:
+
```lua
capital("foo_bar-baz") --> "Foo_bar-baz"
capital("FooBar baz") --> "Foobar baz"
```
-#### `sentence`
+
+
+#### `sentence(s)`
+
+Convert string to sentence case (first letter uppercase, rest unchanged).
+
+**Parameters**:
+
+- `s` (`string`): Input string.
+
+**Return**:
+
+- `s` (`string`): Sentence-cased string.
-Convert string to sentence case (first letter uppercase, rest lowercase).
+**Example**:
```lua
sentence("foo_bar-baz") --> "Foo_bar-baz"
diff --git a/docs/src/modules/tbl.md b/docs/src/modules/tbl.md
index fb53980..1e8128d 100644
--- a/docs/src/modules/tbl.md
+++ b/docs/src/modules/tbl.md
@@ -18,177 +18,375 @@ print(tbl.count({ a = 1, b = 2 })) --> 2
**Basics**:
-| Function | Description |
-| ----------------- | --------------------------------------- |
-| [`clear`](#clear) | Remove all entries from the table. |
-| [`count`](#count) | Return the number of keys in the table. |
+| Function | Description |
+| ----------------------- | --------------------------------------- |
+| [`clear(t)`](#fn-clear) | Remove all entries from the table. |
+| [`count(t)`](#fn-count) | Return the number of keys in the table. |
**Copying**:
-| Function | Description |
-| ----------------------- | ----------------------------------- |
-| [`copy`](#copy) | Create a shallow copy of the table. |
-| [`deepcopy`](#deepcopy) | Create a deep copy of a value. |
+| Function | Description |
+| ----------------------------- | ----------------------------------- |
+| [`copy(t)`](#fn-copy) | Create a shallow copy of the table. |
+| [`deepcopy(v)`](#fn-deepcopy) | Create a deep copy of a value. |
**Query**:
-| Function | Description |
-| --------------------- | ------------------------------------------------------ |
-| [`filter`](#filter) | Filter entries by a value predicate. |
-| [`find`](#find) | Find the first key whose value equals the given value. |
-| [`find_if`](#find-if) | Find first value and key matching predicate. |
-| [`get`](#get) | Safely get nested value by keys. |
+| Function | Description |
+| --------------------------------- | ---------------------------------------------------------------- |
+| [`filter(t, pred)`](#fn-filter) | Filter entries by a value predicate. |
+| [`find(t, v)`](#fn-find) | Find the first key whose value equals the given value. |
+| [`same(a, b)`](#fn-same) | Return `true` if two tables have the same keys and equal values. |
+| [`find_if(t, pred)`](#fn-find-if) | Find first value and key matching predicate. |
+| [`get(t, ...)`](#fn-get) | Safely get nested value by keys. |
+| [`keypath(...)`](#fn-keypath) | Format a key chain as a Lua-like table access path. |
**Transforms**:
-| Function | Description |
-| --------------------- | ---------------------------------------------------------- |
-| [`invert`](#invert) | Invert keys/values into new table. |
-| [`isempty`](#isempty) | Return true if table has no entries. |
-| [`keys`](#keys) | Return a list of all keys in the table. |
-| [`map`](#map) | Return a new table by mapping each value (keys preserved). |
-| [`pairmap`](#pairmap) | Return a new table by mapping each key-value pair. |
-| [`update`](#update) | Merge entries from `t2` into `t1` and return `t1`. |
-| [`values`](#values) | Return a list of all values in the table. |
+| Function | Description |
+| ------------------------------- | ---------------------------------------------------------- |
+| [`invert(t)`](#fn-invert) | Invert keys/values into new table. |
+| [`isempty(t)`](#fn-isempty) | Return true if table has no entries. |
+| [`keys(t)`](#fn-keys) | Return a list of all keys in the table. |
+| [`map(t, fn)`](#fn-map) | Return a new table by mapping each value (keys preserved). |
+| [`pairmap(t, fn)`](#fn-pairmap) | Return a new table by mapping each key-value pair. |
+| [`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. |
### Basics
-Core table utilities for clearing and counting.
+Core table utilities for clearing and counting.
-#### `clear`
+#### `clear(t)`
Remove all entries from the table.
+**Parameters**:
+
+- `t` (`table`): Target table.
+
+**Return**:
+
+- `none` (`nil`)
+
+**Example**:
+
```lua
t = { a = 1, b = 2 }
clear(t) --> t = {}
```
-#### `count`
+
+
+#### `count(t)`
Return the number of keys in the table.
+**Parameters**:
+
+- `t` (`table`): Input table.
+
+**Return**:
+
+- `count` (`integer`): Number of keys in `t`.
+
+**Example**:
+
```lua
n = count({ a = 1, b = 2 }) --> 2
```
### Copying
-Shallow and deep copy helpers.
+Shallow and deep copy helpers.
-#### `copy`
+#### `copy(t)`
Create a shallow copy of the table.
+**Parameters**:
+
+- `t` (`T`): Source table.
+
+**Return**:
+
+- `copy` (`T`): Shallow-copied table.
+
+**Example**:
+
```lua
t = copy({ a = 1, b = 2 }) --> { a = 1, b = 2 }
```
-#### `deepcopy`
+
+
+#### `deepcopy(v)`
Create a deep copy of a value.
-> [!NOTE]
->
-> If `v` is a table, all nested tables are copied recursively; other types are
-> returned as-is.
+**Parameters**:
+
+- `v` (`T`): Input value.
+
+**Return**:
+
+- `copy` (`T`): Deep-copied value.
+
+**Example**:
```lua
t = deepcopy({ a = { b = 1 } }) --> { a = { b = 1 } }
n = deepcopy(42) --> 42
```
+> [!NOTE]
+>
+> If `v` is a table, all nested tables are copied recursively; other types are
+> returned as-is.
+
### Query
-Read-only lookup and selection helpers.
+Read-only lookup and selection helpers.
-#### `filter`
+#### `filter(t, pred)`
Filter entries by a value predicate.
+**Parameters**:
+
+- `t` (`table`): Input table.
+- `pred` (`fun(v:any):boolean`): Value predicate.
+
+**Return**:
+
+- `filtered` (`table`): Table containing entries where `pred(v)` is true.
+
+**Example**:
+
```lua
even = filter({ a = 1, b = 2, c = 3 }, function(v)
return v % 2 == 0
end) --> { b = 2 }
```
-#### `find`
+
+
+#### `find(t, v)`
Find the first key whose value equals the given value.
+**Parameters**:
+
+- `t` (`{[T1]:T2}`): Input table.
+- `v` (`T2`): Value to find.
+
+**Return**:
+
+- `key` (`T1?`): First matching key, or `nil` when not found.
+
+**Example**:
+
```lua
key = find({ a = 1, b = 2, c = 2 }, 2) --> "b" or "c"
```
-#### `find_if`
+
+
+#### `same(a, b)`
+
+Return `true` if two tables have the same keys and equal values.
+
+**Parameters**:
+
+- `a` (`table`): Left table.
+- `b` (`table`): Right table.
+
+**Return**:
+
+- `ok` (`boolean`): True when both tables have the same keys and values.
+
+**Example**:
+
+```lua
+ok = same({ a = 1, b = 2 }, { b = 2, a = 1 }) --> true
+ok = same({ a = {} }, { a = {} }) --> false
+```
+
+
+
+#### `find_if(t, pred)`
Find first value and key matching predicate.
+**Parameters**:
+
+- `t` (`table`): Input table.
+- `pred` (`fun(v:T1,k:T2):boolean`): Predicate function.
+
+**Return**:
+
+- `v` (`T1?`): First matching value, or `nil` when not found.
+- `k` (`T2?`): Key for the first matching value, or `nil` when not found.
+
+**Example**:
+
```lua
v, k = find_if({ a = 1, b = 2 }, function(v, k)
return k == "b" and v == 2
end) --> 2, "b"
```
-#### `get`
+
+
+#### `get(t, ...)`
Safely get nested value by keys.
+**Parameters**:
+
+- `t` (`table`): Root table.
+- `...` (`any`): Additional arguments.
+
+**Return**:
+
+- `value` (`any`): Nested value, or `nil` when any key is missing.
+
+**Example**:
+
```lua
t = { a = { b = { c = 1 } } }
v1 = get(t, "a", "b", "c") --> 1
-v2 = get(t) --> { a = { b = { c = 1 } } }
+v2 = get(t) --> { a = { b = { c = 1 } } }
```
> [!NOTE]
>
> If no keys are provided, returns the input table.
+
+
+#### `keypath(...)`
+
+Format a key chain as a Lua-like table access path.
+
+**Parameters**:
+
+- `...` (`any`): Additional arguments.
+
+**Return**:
+
+- `path` (`string`): Rendered key path.
+
+**Example**:
+
+```lua
+p1 = keypath("t", "a", "b", "c") --> "t.a.b.c"
+p2 = keypath("ctx", "users", 1, "name") --> "ctx.users[1].name"
+p3 = keypath("ctx", "invalid-key") --> 'ctx["invalid-key"]'
+p4 = keypath() --> ""
+```
+
### Transforms
-Table transformation and conversion utilities.
+Table transformation and conversion utilities.
-#### `invert`
+#### `invert(t)`
Invert keys/values into new table.
+**Parameters**:
+
+- `t` (`{[T1]:T2}`): Input table.
+
+**Return**:
+
+- `inverted` (`{[T2]:T1}`): Inverted table (`value -> key`).
+
+**Example**:
+
```lua
t = invert({ a = 1, b = 2 }) --> { [1] = "a", [2] = "b" }
```
-#### `isempty`
+
+
+#### `isempty(t)`
Return true if table has no entries.
+**Parameters**:
+
+- `t` (`table`): Input table.
+
+**Return**:
+
+- `ok` (`boolean`): True when `t` has no entries.
+
+**Example**:
+
```lua
empty = isempty({}) --> true
```
-#### `keys`
+
+
+#### `keys(t)`
Return a list of all keys in the table.
+**Parameters**:
+
+- `t` (`{[T]:any}`): Input table.
+
+**Return**:
+
+- `keys` (`mods.List`): List of keys in `t`.
+
+**Example**:
+
```lua
keys = keys({ a = 1, b = 2 }) --> { "a", "b" }
```
-#### `map`
+
+
+#### `map(t, fn)`
Return a new table by mapping each value (keys preserved).
+**Parameters**:
+
+- `t` (`{[T1]:T2}`): Input table.
+- `fn` (`fun(v:T2):T3`): Mapping function.
+
+**Return**:
+
+- `mapped` (`{[T1]:T3}`): New table with mapped values.
+
+**Example**:
+
```lua
t = map({ a = 1, b = 2 }, function(v)
return v * 10
end) --> { a = 10, b = 20 }
```
-#### `pairmap`
+
+
+#### `pairmap(t, fn)`
Return a new table by mapping each key-value pair.
-> [!NOTE]
->
-> Output keeps original keys; only values are transformed by `fn`.
+**Parameters**:
+
+- `t` (`{[T1]:T2}`): Input table.
+- `fn` (`fun(k:T1,`): v:T2):T3 Key-value mapping function.
+
+**Return**:
+
+- `mapped` (`{[T1]:T3}`): New table with mapped values.
+
+**Example**:
```lua
t = pairmap({ a = 1, b = 2 }, function(k, v)
@@ -196,19 +394,48 @@ t = pairmap({ a = 1, b = 2 }, function(k, v)
end) --> { a = "a1", b = "b2" }
```
-#### `update`
+> [!NOTE]
+>
+> Output keeps original keys; only values are transformed by `fn`.
+
+
+
+#### `update(t1, t2)`
Merge entries from `t2` into `t1` and return `t1`.
+**Parameters**:
+
+- `t1` (`T`): Target table.
+- `t2` (`table`): Source table.
+
+**Return**:
+
+- `t1` (`T`): Updated `t1` table.
+
+**Example**:
+
```lua
t1 = { a = 1, b = 2 }
update(t1, { b = 3, c = 4 }) --> t1 is { a = 1, b = 3, c = 4 }
```
-#### `values`
+
+
+#### `values(t)`
Return a list of all values in the table.
+**Parameters**:
+
+- `t` (`{[any]:T}`): Input table.
+
+**Return**:
+
+- `values` (`mods.List`): List of values in `t`.
+
+**Example**:
+
```lua
vals = values({ a = 1, b = 2 }) --> { 1, 2 }
```
diff --git a/docs/src/modules/template.md b/docs/src/modules/template.md
index a95234f..407ffb3 100644
--- a/docs/src/modules/template.md
+++ b/docs/src/modules/template.md
@@ -1,13 +1,10 @@
---
-desc:
- "Render lightweight templates with dot-path placeholders and function-aware
- values."
+desc: "Interpolate string placeholders of the form {{."
---
# `template`
-Render lightweight templates with dot-path placeholders and function-aware
-values.
+Interpolate string placeholders of the form {{...}}.
## Usage
@@ -15,76 +12,113 @@ values.
template = require "mods.template"
view = {
- user = { name = "World" },
+ user = { name = "Ada" },
}
-out = template("Hello {{user.name}}!", view) --> "Hello World!"
+out = template("Hello {{user.name}}!", view) --> "Hello Ada!"
```
-## Rules
+
-- Placeholders use {{name}}.
+## `template(tmpl, view)`
-```lua
-template("Hi {{name}}", { name = "Ada" }) --> "Hi Ada"
-```
+Render string templates with {{...}} placeholders.
-- Whitespace inside placeholders is ignored.
+**Parameters**:
-```lua
-template("Hi {{ name }}", { name = "Ada" }) --> "Hi Ada"
-```
+- `tmpl` (`string`): Template string with placeholders.
+- `view` (`table`): Input data used to resolve placeholders.
+
+**Return**:
-- Dot paths are supported.
+- `out` (`string`): Rendered output string.
+
+**Example**:
```lua
-template("Role: {{user.meta.role}}", {
- user = { meta = { role = "Engineer" } },
-}) --> "Role: Engineer"
+view = { subject = "World" }
+template("Hello {{subject}}", view) --> "Hello World"
```
-- {{.}} resolves to the whole `view`.
+> [!NOTE]
+>
+> Whitespace inside placeholders is ignored.
+>
+> ```lua
+> template("Hi {{ name }}", { name = "Ada" }) --> "Hi Ada"
+> ```
+
+## Dot Paths
+
+Use dot notation to access nested values in `view`.
```lua
-template("Value: {{.}}", 123) --> "Value: 123"
+view = { user = { meta = { role = "Engineer" } } }
+template("Role: {{user.meta.role}}", view) --> "Role: Engineer"
```
-- Function values are called and their return value is rendered.
+> [!NOTE]
+>
+> {{.}} renders the entire root `view` table, not a nested
+> field.
+>
+> ```lua
+> template("View: {{.}}", { value = 123 })
+> --> View: {
+> -- value = 123
+> -- }
+> ```
+
+## Function Values
+
+If a placeholder resolves to a function, that function is called and its result
+is rendered.
```lua
-template("Hi {{name_func}}", { name_func = function() return "Ada" end })
---> "Hi Ada"
+view = { name_func = function() return "Ada" end }
+template("Hi {{name_func}}", view) --> "Hi Ada"
```
-- Table values render as first-depth key/value pairs. String values are quoted;
- nested tables/functions are summarized.
+> [!NOTE]
+>
+> If the function returns `nil`, the placeholder renders as an empty string.
+
+## Table Values
+
+Table placeholders are rendered using
+[`mods.repr`](https://luamod.github.io/mods/modules/repr).
```lua
-template("Data: {{data}}", { data = { a = 1, b = true } })
---> {
--- a = 1,
--- b = true
+view = { data = { a = 1, b = true } }
+template("Data: {{data}}", view)
+--> Data: {
+-- a = 1,
+-- b = true
-- }
```
-- Missing keys render as an empty string.
+## Missing and Invalid Placeholders
+
+Missing keys render as an empty string.
```lua
-template("Missing: {{unknown}}", {}) --> "Missing: "
+view = {}
+template("Missing: {{unknown}}", view) --> "Missing: "
```
-- If a tag is not closed ({{name), it is emitted as-is.
+Invalid placeholder names render as an empty string (for example:
+{{..}}, {{.name}},
+{{user.}}, {{user..name}}).
```lua
-template("Hi {{name", { name = "Ada" }) --> "Hi {{name"
+view = { user = { name = "Ada" } }
+template("Bad: {{user..name}}", view) --> "Bad: "
```
-- `template(view)` is shorthand for template("{{.}}", view).
+If a placeholder is not closed ({{unclosed), it is emitted
+as-is.
```lua
-template({ a = 1, b = true })
---> {
--- a = 1,
--- b = true
--- }
+view = { name = "Ada" }
+template("Hi {{name", view) --> "Hi {{name"
```
diff --git a/docs/src/modules/utils.md b/docs/src/modules/utils.md
index 8ff5de7..4fa475d 100644
--- a/docs/src/modules/utils.md
+++ b/docs/src/modules/utils.md
@@ -16,16 +16,30 @@ print(utils.quote('hello "world"')) --> 'hello "world"'
## Functions
-### `quote`
+
+
+### `quote(v)`
Smart-quote a string for readable Lua-like output.
+**Parameters**:
+
+- `v` (`string`)
+
+**Return**:
+
+- `out` (`string`)
+
+**Example**:
+
```lua
print(utils.quote('He said "hi"')) -- 'He said "hi"'
print(utils.quote('say "hi" and \\'bye\\'')) -- "say \"hi\" and 'bye'"
```
-### `repr`
+
+
+### `repr(v)`
Render any Lua value as a string.
@@ -35,6 +49,16 @@ Render any Lua value as a string.
> otherwise falls back to
> [`mods.repr`](https://luamod.github.io/mods/modules/repr).
+**Parameters**:
+
+- `v` (`any`)
+
+**Return**:
+
+- `out` (`string`)
+
+**Example**:
+
```lua
print(utils.repr({ a = 1 })) --> {
-- a = 1
diff --git a/docs/src/modules/validate.md b/docs/src/modules/validate.md
index 33d7f18..d6dbf4a 100644
--- a/docs/src/modules/validate.md
+++ b/docs/src/modules/validate.md
@@ -8,565 +8,668 @@ Validation checks for values and filesystem path types.
## Usage
-```lua [.lua]
-validate= require "mods.validate"
-
-ok, err = validate.is.number("nope")
---> false, "expected number, got string"
+```lua
+local validate = require "mods.validate"
-ok, err = validate.is_not.number(3.14)
---> false, "expected not number"
+ok, err = validate.number("nope") --> false, "expected number, got string"
+ok, err = validate(123, "number") --> true, nil
```
-> [!IMPORTANT]
->
-> Behavior without `tp`:
->
-> - `validate()` is equivalent to `validate(nil, "nil")` (passes)
-> - `validate(1)` is equivalent to `validate(1, "nil")` (fails with
-> `expected nil, got number`)
->
-> Validator access is case-insensitive:
->
-> - `validate.is.number` and `validate.IS.Number` are equivalent.
-> - Top-level aliases are underscore-insensitive: `validate.is_number`,
-> `validate.IS_NUMBER`, and `validate.isnumber`.
-> - Negated validators are available via `is_not`, `isnot`, `isNot`, `not`, and
-> `Not`, including underscore-insensitive top-level aliases (for example,
-> `validate.is_not_number` and `validate.isnotnumber`).
-
-## Dependencies
+## `validate()`
-Dependencies below are lazy-loaded 💤 on first access.
-
-- [`lfs`](https://github.com/lunarmodules/luafilesystem) (optional; required
- only for filesystem/path checks)
-- [`mods.is`](https://luamod.github.io/mods/modules/is)
-
-## Callable Forms
-
-`validate`, `validate.is`, and `validate.is_not` are all callable.
+`validate(v, tp)` dispatches to the registered validator `tp`. If `tp` is
+omitted, it defaults to `"truthy"`.
```lua
-ok, err = validate(1, "number") --> true, nil
-ok, err = validate.is("x", "string") --> true, nil
-ok, err = validate.is_not(1, "number") --> false, "expected not number"
+validate() --> false, "expected truthy value, got no value"
+validate(1) --> true, nil
+validate(1, "nil") --> false, "expected nil, got number"
```
-> [!IMPORTANT]
->
-> When `tp` is omitted, the default check is `"nil"`:
->
-> - `validate()` is equivalent to `validate(nil, "nil")` (passes)
-> - `validate(1)` is equivalent to `validate(1, "nil")` (fails)
->
-> Callable namespace aliases are case-insensitive, and negated aliases are
-> underscore-insensitive:
->
-> - `validate.is`, `validate.IS`
-> - `validate.is_not`, `validate.isnot`, `validate.isNot`, `validate["not"]`,
-> `validate.Not`
->
-> ```lua
-> validate.is(1, "number") --> true
-> validate.IS(1, "number") --> true
-> validate.is_not(1, "number") --> false, "expected not number"
-> validate.Not(1, "number") --> false, "expected not number"
-> ```
-
-## Custom Messages
-
-Customize validator error messages through `validate.messages`.
-
-- `validate.messages.positive.` customizes positive checks
-- `validate.messages.negative.` customizes negated checks
-
-`` is the validator key (for example: `number`, `string`, `truthy`,
-`integer`, `callable`, `file`, `dir`, etc.).
-
-Available placeholders:
-
-- {{expected}}: The check target (for example `number`,
- `string`, `truthy`).
-- {{got}}: The detected failure kind (usually a Lua type;
- path validators use `invalid path`).
-- {{value}}: The passed value, formatted for display (strings
- are quoted).
+## Validator Names
-### Example
+Validator names are case-insensitive for field access.
```lua
-validate.messages.positive.number = "need {{expected}}, got {{got}} (value={{value}})"
-validate.messages.negative.number = "must not be {{expected}} (value={{value}})"
-
-ok, err = validate.is.number("x")
---> false, 'need number, got string (value="x")'
-
-ok, err = validate.is_not.number(42)
---> false, "must not be number (value=42)"
+validate.number(1) --> true, nil
+validate.NumBer(1) --> true, nil
```
-## Default Messages
-
-By default, validate uses built-in templates unless
-`validate.messages.positive.` or `validate.messages.negative.` is
-overridden:
-
-- Positive type/value checks (`validate.is.*`):
- `expected {{expected}}, got {{got}}`
-- Positive path checks (`validate.is.block`, `char`, `device`, `dir`, `fifo`,
- `file`, `link`, `socket`): `{{value}} is not a valid {{expected}} path`
-- Negative checks (`validate.is_not.*`): `expected not {{expected}}`
-
-`integer` uses a more specific default that includes the passed value:
-
-- Positive `integer`: `expected integer, got {{value}}`
-- Negative `integer`: `expected non-integer, got {{value}}`
-
-## On Fail Hook
-
-Set `validate.on_fail` to handle failed validations globally.
-
-- If `on_fail` is set, it is called with the rendered error message.
-- If `on_fail` returns a truthy value, that value is used as the returned error.
-- If `on_fail` returns a falsy value, the default rendered error is returned.
-- If `on_fail` is `nil`, validators return `false, err` as usual.
+`tp` in `validate(v, tp)` is matched as-is (case-sensitive):
```lua
-validate.on_fail = function(errmsg)
- print("validation failed:", errmsg)
- return "custom failure"
-end
-
-ok, err = validate.number("x")
---> prints -> validation failed: expected number, got string
---> false, "custom failure"
+validate(1, "number") --> true, nil
+validate(1, "NuMbEr") --> false, "expected NuMbEr, got number"
```
## Functions
**Type Checks**:
-| Function | Description |
-| ------------------------------- | ---------------------------------------------------------------------------------------------- |
-| [`boolean`](#boolean) | Returns `true` when `v` is a boolean. Otherwise returns `false` and an error message. |
-| [`function`](#function) | Returns `true` when `v` is a function. Otherwise returns `false` and an error message. |
-| [`nil`](#nil) | Returns `true` when `v` is `nil`. Otherwise returns `false` and an error message. |
-| [`number`](#number) | Returns `true` when `v` is a number. Otherwise returns `false` and an error message. |
-| [`string`](#string) | Returns `true` when `v` is a string. Otherwise returns `false` and an error message. |
-| [`table`](#table) | Returns `true` when `v` is a table. Otherwise returns `false` and an error message. |
-| [`thread`](#thread) | Returns `true` when `v` is a thread. Otherwise returns `false` and an error message. |
-| [`userdata`](#userdata) | Returns `true` when `v` is userdata. Otherwise returns `false` and an error message. |
-| [`not_boolean`](#not-boolean) | Returns `true` when `v` is **not** a boolean. Otherwise returns `false` and an error message. |
-| [`not_function`](#not-function) | Returns `true` when `v` is **not** a function. Otherwise returns `false` and an error message. |
-| [`not_nil`](#not-nil) | Returns `true` when `v` is **not** `nil`. Otherwise returns `false` and an error message. |
-| [`not_number`](#not-number) | Returns `true` when `v` is **not** a number. Otherwise returns `false` and an error message. |
-| [`not_string`](#not-string) | Returns `true` when `v` is **not** a string. Otherwise returns `false` and an error message. |
-| [`not_table`](#not-table) | Returns `true` when `v` is **not** a table. Otherwise returns `false` and an error message. |
-| [`not_thread`](#not-thread) | Returns `true` when `v` is **not** a thread. Otherwise returns `false` and an error message. |
-| [`not_userdata`](#not-userdata) | Returns `true` when `v` is **not** userdata. Otherwise returns `false` and an error message. |
+| 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. |
**Value Checks**:
-| Function | Description |
-| ------------------------------- | --------------------------------------------------------------------------------------------------- |
-| [`false`](#false) | Returns `true` when `v` is exactly `false`. Otherwise returns `false` and an error message. |
-| [`true`](#true) | Returns `true` when `v` is exactly `true`. Otherwise returns `false` and an error message. |
-| [`falsy`](#falsy) | Returns `true` when `v` is falsy. Otherwise returns `false` and an error message. |
-| [`callable`](#callable) | Returns `true` when `v` is callable. Otherwise returns `false` and an error message. |
-| [`integer`](#integer) | Returns `true` when `v` is an integer. Otherwise returns `false` and an error message. |
-| [`truthy`](#truthy) | Returns `true` when `v` is truthy. Otherwise returns `false` and an error message. |
-| [`not_false`](#not-false) | Returns `true` when `v` is **not** exactly `false`. Otherwise returns `false` and an error message. |
-| [`not_true`](#not-true) | Returns `true` when `v` is **not** exactly `true`. Otherwise returns `false` and an error message. |
-| [`not_falsy`](#not-falsy) | Returns `true` when `v` is **not** falsy. Otherwise returns `false` and an error message. |
-| [`not_callable`](#not-callable) | Returns `true` when `v` is **not** callable. Otherwise returns `false` and an error message. |
-| [`not_integer`](#not-integer) | Returns `true` when `v` is **not** an integer. Otherwise returns `false` and an error message. |
-| [`not_truthy`](#not-truthy) | Returns `true` when `v` is **not** truthy. Otherwise returns `false` and an error message. |
+| 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. |
**Path Checks**:
-| Function | Description |
-| ------------------- | ------------------------------------------------------------------------------------------------------- |
-| [`block`](#block) | Returns `true` when `v` is a block device path. Otherwise returns `false` and an error message. |
-| [`char`](#char) | Returns `true` when `v` is a char device path. Otherwise returns `false` and an error message. |
-| [`device`](#device) | Returns `true` when `v` is a block or char device path. Otherwise returns `false` and an error message. |
-| [`dir`](#dir) | Returns `true` when `v` is a directory path. Otherwise returns `false` and an error message. |
-| [`fifo`](#fifo) | Returns `true` when `v` is a FIFO path. Otherwise returns `false` and an error message. |
-| [`file`](#file) | Returns `true` when `v` is a file path. Otherwise returns `false` and an error message. |
-| [`link`](#link) | Returns `true` when `v` is a symlink path. Otherwise returns `false` and an error message. |
-| [`socket`](#socket) | Returns `true` when `v` is a socket path. Otherwise returns `false` and an error message. |
+| 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. |
+
+**Validator API**:
+
+| Function | Description |
+| --------------------------------------------- | -------------------------------------------------- |
+| [`register(name, check, msg?)`](#fn-register) | Register or override a validator function by name. |
### Type Checks
-Basic Lua type validators (and their negated variants).
+Basic Lua type validators (and their negated variants).
-#### `boolean`
+#### `boolean(v)`
Returns `true` when `v` is a boolean. Otherwise returns `false` and an error
message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.boolean(true) --> true, nil
-ok, err = validate.is.boolean(1) --> false, "expected boolean, got number"
+ok, err = validate.boolean(true) --> true, nil
+ok, err = validate.boolean(1) --> false, "expected boolean, got number"
```
-#### `function`
+
+
+#### `function(v)`
Returns `true` when `v` is a function. Otherwise returns `false` and an error
message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.Function(function() end) --> true, nil
-ok, err = validate.is.Function(1)
+ok, err = validate.Function(function() end) --> true, nil
+ok, err = validate.Function(1)
--> false, "expected function, got number"
```
-#### `nil`
+
+
+#### `nil(v)`
Returns `true` when `v` is `nil`. Otherwise returns `false` and an error
message.
-```lua
-ok, err = validate.is.Nil(nil) --> true, nil
-ok, err = validate.is.Nil(0) --> false, "expected nil, got number"
-```
-
-#### `number`
+**Parameters**:
-Returns `true` when `v` is a number. Otherwise returns `false` and an error
-message.
+- `v` (`any`): Value to validate.
-```lua
-ok, err = validate.is.number(42) --> true, nil
-ok, err = validate.is.number("x") --> false, "expected number, got string"
-```
+**Return**:
-#### `string`
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
-Returns `true` when `v` is a string. Otherwise returns `false` and an error
-message.
+**Example**:
```lua
-ok, err = validate.is.string("hello") --> true, nil
-ok, err = validate.is.string(1) --> false, "expected string, got number"
+ok, err = validate.Nil(nil) --> true, nil
+ok, err = validate.Nil(0) --> false, "expected nil, got number"
```
-#### `table`
+
-Returns `true` when `v` is a table. Otherwise returns `false` and an error
+#### `number(v)`
+
+Returns `true` when `v` is a number. Otherwise returns `false` and an error
message.
-```lua
-ok, err = validate.is.table({}) --> true, nil
-ok, err = validate.is.table(1) --> false, "expected table, got number"
-```
+**Parameters**:
-#### `thread`
+- `v` (`any`): Value to validate.
-Returns `true` when `v` is a thread. Otherwise returns `false` and an error
-message.
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
```lua
-co = coroutine.create(function() end)
-ok, err = validate.is.thread(co) --> true, nil
-ok, err = validate.is.thread(1) --> false, "expected thread, got number"
+ok, err = validate.number(42) --> true, nil
+ok, err = validate.number("x") --> false, "expected number, got string"
```
-#### `userdata`
+
-Returns `true` when `v` is userdata. Otherwise returns `false` and an error
-message.
+#### `string(v)`
-```lua
-ok, err = validate.is.userdata(io.stdout) --> true, nil
-ok, err = validate.is.userdata(1) --> false, "expected userdata, got number"
-```
+Returns `true` when `v` is a string. Otherwise returns `false` and an error
+message.
-#### `not_boolean`
+**Parameters**:
-Returns `true` when `v` is **not** a boolean. Otherwise returns `false` and an
-error message.
+- `v` (`any`): Value to validate.
-```lua
-ok, err = validate.is_not.boolean(1) --> true, nil
-ok, err = validate.is_not.boolean(true) --> false, "expected not boolean"
-```
+**Return**:
-#### `not_function`
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
-Returns `true` when `v` is **not** a function. Otherwise returns `false` and an
-error message.
+**Example**:
```lua
-ok, err = validate.is_not.Function(1) --> true, nil
-ok, err = validate.is_not.Function(function() end) --> false, "expected not function"
+ok, err = validate.string("hello") --> true, nil
+ok, err = validate.string(1) --> false, "expected string, got number"
```
-#### `not_nil`
+
-Returns `true` when `v` is **not** `nil`. Otherwise returns `false` and an error
+#### `table(v)`
+
+Returns `true` when `v` is a table. Otherwise returns `false` and an error
message.
-```lua
-ok, err = validate.is_not.Nil(0) --> true, nil
-ok, err = validate.is_not.Nil(nil) --> false, "expected not nil"
-```
+**Parameters**:
-#### `not_number`
+- `v` (`any`): Value to validate.
-Returns `true` when `v` is **not** a number. Otherwise returns `false` and an
-error message.
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
```lua
-ok, err = validate.is_not.number("x") --> true, nil
-ok, err = validate.is_not.number(42) --> false, "expected not number"
+ok, err = validate.table({}) --> true, nil
+ok, err = validate.table(1) --> false, "expected table, got number"
```
-#### `not_string`
+
-Returns `true` when `v` is **not** a string. Otherwise returns `false` and an
-error message.
+#### `thread(v)`
-```lua
-ok, err = validate.is_not.string(1) --> true, nil
-ok, err = validate.is_not.string("hello") --> false, "expected not string"
-```
+Returns `true` when `v` is a thread. Otherwise returns `false` and an error
+message.
-#### `not_table`
+**Parameters**:
-Returns `true` when `v` is **not** a table. Otherwise returns `false` and an
-error message.
+- `v` (`any`): Value to validate.
-```lua
-ok, err = validate.is_not.table(1) --> true, nil
-ok, err = validate.is_not.table({}) --> false, "expected not table"
-```
+**Return**:
-#### `not_thread`
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
-Returns `true` when `v` is **not** a thread. Otherwise returns `false` and an
-error message.
+**Example**:
```lua
co = coroutine.create(function() end)
-ok, err = validate.is_not.thread(1) --> true, nil
-ok, err = validate.is_not.thread(co) --> false, "expected not thread"
+ok, err = validate.thread(co) --> true, nil
+ok, err = validate.thread(1) --> false, "expected thread, got number"
```
-#### `not_userdata`
+
+
+#### `userdata(v)`
-Returns `true` when `v` is **not** userdata. Otherwise returns `false` and an
+Returns `true` when `v` is a userdata value. Otherwise returns `false` and an
error message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is_not.userdata(1) --> true, nil
-ok, err = validate.is_not.userdata(io.stdout) --> false, "expected not userdata"
+ok, err = validate.userdata(io.stdout) --> true, nil
+ok, err = validate.userdata(1) --> false, "expected userdata, got number"
```
### Value Checks
Value-state validators (exact true/false, truthy/falsy, callable, integer).
+
-#### `false`
+#### `false(v)`
Returns `true` when `v` is exactly `false`. Otherwise returns `false` and an
error message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.False(false) --> true, nil
-ok, err = validate.is.False(true) --> false, "expected false, got true"
+ok, err = validate.False(false) --> true, nil
+ok, err = validate.False(true) --> false, "expected false, got true"
```
-#### `true`
+
+
+#### `true(v)`
Returns `true` when `v` is exactly `true`. Otherwise returns `false` and an
error message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.True(true) --> true, nil
-ok, err = validate.is.True(false) --> false, "expected true, got false"
+ok, err = validate.True(true) --> true, nil
+ok, err = validate.True(false) --> false, "expected true, got false"
```
-#### `falsy`
+
+
+#### `falsy(v)`
Returns `true` when `v` is falsy. Otherwise returns `false` and an error
message.
-```lua
-ok, err = validate.is.falsy(false) --> true, nil
-ok, err = validate.is.falsy(1) --> false, "expected falsy, got number"
-```
+**Parameters**:
-#### `callable`
+- `v` (`any`): Value to validate.
-Returns `true` when `v` is callable. Otherwise returns `false` and an error
-message.
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
```lua
-ok, err = validate.is.callable(type) --> true, nil
-ok, err = validate.is.callable(1) --> false, "expected callable, got number"
+ok, err = validate.falsy(false) --> true, nil
+ok, err = validate.falsy(1) --> false, "expected falsy, got number"
```
-#### `integer`
+
-Returns `true` when `v` is an integer. Otherwise returns `false` and an error
+#### `callable(v)`
+
+Returns `true` when `v` is callable. Otherwise returns `false` and an error
message.
-```lua
-ok, err = validate.is.integer(1) --> true, nil
-ok, err = validate.is.integer(1.5) --> false, "expected integer, got 1.5"
-```
+**Parameters**:
-#### `truthy`
+- `v` (`any`): Value to validate.
-Returns `true` when `v` is truthy. Otherwise returns `false` and an error
-message.
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
```lua
-ok, err = validate.is.truthy(1) --> true, nil
-ok, err = validate.is.truthy(false) --> false, "expected truthy, got boolean"
+ok, err = validate.callable(type) --> true, nil
+ok, err = validate.callable(1) --> false, "expected callable, got number"
```
-#### `not_false`
+
-Returns `true` when `v` is **not** exactly `false`. Otherwise returns `false`
-and an error message.
+#### `integer(v)`
-```lua
-ok, err = validate.is_not.False(true) --> true, nil
-ok, err = validate.is_not.False(false) --> false, "expected not false"
-```
+Returns `true` when `v` is an integer. Otherwise returns `false` and an error
+message.
-#### `not_true`
+**Parameters**:
-Returns `true` when `v` is **not** exactly `true`. Otherwise returns `false` and
-an error message.
+- `v` (`any`): Value to validate.
-```lua
-ok, err = validate.is_not.True(false) --> true, nil
-ok, err = validate.is_not.True(true) --> false, "expected not true"
-```
+**Return**:
-#### `not_falsy`
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
-Returns `true` when `v` is **not** falsy. Otherwise returns `false` and an error
-message.
+**Example**:
```lua
-ok, err = validate.is_not.falsy(1) --> true, nil
-ok, err = validate.is_not.falsy(false) --> false, "expected not falsy"
+ok, err = validate.integer(1) --> true, nil
+ok, err = validate.integer(1.5) --> false, "expected integer, got 1.5"
```
-#### `not_callable`
+
-Returns `true` when `v` is **not** callable. Otherwise returns `false` and an
-error message.
+#### `truthy(v)`
-```lua
-ok, err = validate.is_not.callable(1) --> true, nil
-ok, err = validate.is_not.callable(function() end) --> false, "expected not callable"
-```
+Returns `true` when `v` is truthy. Otherwise returns `false` and an error
+message.
-#### `not_integer`
+**Parameters**:
-Returns `true` when `v` is **not** an integer. Otherwise returns `false` and an
-error message.
+- `v` (`any`): Value to validate.
-```lua
-ok, err = validate.is_not.integer(1.5) --> true, nil
-ok, err = validate.is_not.integer(1) --> false, "expected non-integer, got 1"
-```
+**Return**:
-#### `not_truthy`
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
-Returns `true` when `v` is **not** truthy. Otherwise returns `false` and an
-error message.
+**Example**:
```lua
-ok, err = validate.is_not.truthy(false) --> true, nil
-ok, err = validate.is_not.truthy(1) --> false, "expected not truthy"
+ok, err = validate.truthy(1) --> true, nil
+ok, err = validate.truthy(false) --> false, "expected truthy, got boolean"
```
### Path Checks
Filesystem path-kind validators backed by LuaFileSystem (`lfs`).
-Filesystem path kind checks.
-
> [!IMPORTANT]
>
> Path checks require **LuaFileSystem**
-> ([`lfs`](https://github.com/lunarmodules/luafilesystem)) and raise an error it
-> is not installed.
+> ([`lfs`](https://github.com/lunarmodules/luafilesystem)) and raise an error if
+> it is not installed.
-#### `block`
+#### `block(v)`
Returns `true` when `v` is a block device path. Otherwise returns `false` and an
error message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.block(".")
+ok, err = validate.block(".")
```
-#### `char`
+
+
+#### `char(v)`
Returns `true` when `v` is a char device path. Otherwise returns `false` and an
error message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.char(".")
+ok, err = validate.char(".")
```
-#### `device`
+
+
+#### `device(v)`
Returns `true` when `v` is a block or char device path. Otherwise returns
`false` and an error message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.device(".")
+ok, err = validate.device(".")
```
-#### `dir`
+
+
+#### `dir(v)`
Returns `true` when `v` is a directory path. Otherwise returns `false` and an
error message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.dir(".")
+ok, err = validate.dir(".")
```
-#### `fifo`
+
+
+#### `fifo(v)`
Returns `true` when `v` is a FIFO path. Otherwise returns `false` and an error
message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.fifo(".")
+ok, err = validate.fifo(".")
```
-#### `file`
+
+
+#### `file(v)`
Returns `true` when `v` is a file path. Otherwise returns `false` and an error
message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.file(".")
+ok, err = validate.file(".")
```
-#### `link`
+
+
+#### `link(v)`
Returns `true` when `v` is a symlink path. Otherwise returns `false` and an
error message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.link(".")
+ok, err = validate.link(".")
```
-#### `socket`
+
+
+#### `socket(v)`
Returns `true` when `v` is a socket path. Otherwise returns `false` and an error
message.
+**Parameters**:
+
+- `v` (`any`): Value to validate.
+
+**Return**:
+
+- `ok` (`boolean`): Whether the check succeeds.
+- `err` (`string?`): Error message when the check fails.
+
+**Example**:
+
```lua
-ok, err = validate.is.socket(".")
+ok, err = validate.socket(".")
```
+
+### Validator API
+
+
+
+#### `register(name, check, msg?)`
+
+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.
+
+**Return**:
+
+- `none` (`nil`)
+
+**Example**:
+
+```lua
+validate.register("odd", function(v)
+ return type(v) == "number" and v % 2 == 1
+end, "{{value}} does not satisfy {{expected}}")
+
+ok, err = validate.odd(3) --> true, nil
+ok, err = validate.odd("x") --> false, '"x" does not satisfy odd'
+ok, err = validate(2, "odd") --> false, "2 does not satisfy odd"
+```
+
+> [!NOTE]
+>
+> - If `msg` is provided, it becomes the default message template for that
+> validator.
+> - If `msg` 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.
+
+```lua
+validate.messages.number = "need {{expected}}, got {{got}}"
+ok, err = validate.number("x") --> false, "need number, got string"
+```
+
+**Placeholders**:
+
+- {{expected}}: The check target (for example `number`,
+ `string`, `truthy`).
+- {{got}}: The detected failure kind (usually a Lua type;
+ path validators use `invalid path`).
+- {{value}}: The passed value, formatted for display (strings
+ are quoted).
+
+> [!NOTE]
+>
+> 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"
+> ```
+>
+> **Default Messages**:
+
+- Type checks: expected {{expected}}, got {{got}}
+- Value checks: expected {{expected}} value, got {{value}}
+- Path checks: {{value}} is not a valid {{expected}} path