feat: add wasm build targets, profile hooks, os_demo example, and restructure manual source

- Add `wasm`, `wasm-clean`, and `install-emscripten` phony targets to Makefile for WebAssembly cross-compilation
- Insert `WREN_PROFILE_ENTER`/`WREN_PROFILE_EXIT` macros in `wren_vm.h` and `wren_vm.c` to support optional runtime profiling via `WREN_PROFILE_ENABLED`
- Create `example/os_demo.wren` demonstrating Platform, Process, and conditional exit usage
- Update `example/regex_demo.wren` to import `Match` and exercise Match object properties, groups, and `matchAll`
- Remove static HTML manual pages (`base64.html`, `dns.html`, `json.html`) and replace with structured `manual_src/` directory containing Jinja2 templates, YAML metadata, and content pages
- Expand `README.md` with build targets table, manual building instructions, source layout, and guide for adding new module documentation
This commit is contained in:
2026-01-26 04:12:14 +00:00
parent 79ff93c9a2
commit 04e467e09b
143 changed files with 13333 additions and 15112 deletions
+300
View File
@@ -0,0 +1,300 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "argparse" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "argparse"}] %}
{% set prev_page = {"url": "api/html.html", "title": "html"} %}
{% set next_page = {"url": "api/wdantic.html", "title": "wdantic"} %}
{% block article %}
<h1>argparse</h1>
<p>The <code>argparse</code> module provides command-line argument parsing with support for positional arguments, optional flags, type conversion, and help generation.</p>
<pre><code>import "argparse" for ArgumentParser</code></pre>
<h2>ArgumentParser Class</h2>
<div class="class-header">
<h3>ArgumentParser</h3>
<p>Command-line argument parser</p>
</div>
<h3>Constructors</h3>
<div class="method-signature">
<span class="method-name">ArgumentParser.new</span>() → <span class="type">ArgumentParser</span>
</div>
<p>Creates a new argument parser with no description.</p>
<div class="method-signature">
<span class="method-name">ArgumentParser.new</span>(<span class="param">description</span>) → <span class="type">ArgumentParser</span>
</div>
<p>Creates a new argument parser with a description shown in help.</p>
<ul class="param-list">
<li><span class="param-name">description</span> <span class="param-type">(String)</span> - Description of the program</li>
</ul>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">prog</span><span class="type">String</span>
</div>
<p>Gets or sets the program name shown in help output.</p>
<div class="method-signature">
<span class="method-name">description</span><span class="type">String</span>
</div>
<p>Gets or sets the program description.</p>
<h3>Instance Methods</h3>
<div class="method-signature">
<span class="method-name">addArgument</span>(<span class="param">name</span>) → <span class="type">ArgumentParser</span>
</div>
<p>Adds an argument with default options. Returns the parser for chaining.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Argument name (positional) or flag (starts with <code>-</code>)</li>
</ul>
<div class="method-signature">
<span class="method-name">addArgument</span>(<span class="param">name</span>, <span class="param">options</span>) → <span class="type">ArgumentParser</span>
</div>
<p>Adds an argument with specified options.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Argument name or flag</li>
<li><span class="param-name">options</span> <span class="param-type">(Map)</span> - Configuration options</li>
</ul>
<div class="method-signature">
<span class="method-name">parseArgs</span>() → <span class="type">Map</span>
</div>
<p>Parses arguments from <code>Process.arguments</code>.</p>
<div class="method-signature">
<span class="method-name">parseArgs</span>(<span class="param">args</span>) → <span class="type">Map</span>
</div>
<p>Parses the provided argument list.</p>
<ul class="param-list">
<li><span class="param-name">args</span> <span class="param-type">(List)</span> - List of argument strings</li>
<li><span class="returns">Returns:</span> Map of argument names to values</li>
</ul>
<div class="method-signature">
<span class="method-name">printHelp</span>()
</div>
<p>Prints formatted help information to stdout.</p>
<h2>Argument Options</h2>
<table>
<tr>
<th>Option</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>long</code></td>
<td>String</td>
<td>Long form of the flag (e.g., <code>--verbose</code>)</td>
</tr>
<tr>
<td><code>type</code></td>
<td>String</td>
<td><code>"string"</code>, <code>"int"</code>, <code>"float"</code>, or <code>"bool"</code></td>
</tr>
<tr>
<td><code>default</code></td>
<td>any</td>
<td>Default value if not provided</td>
</tr>
<tr>
<td><code>required</code></td>
<td>Bool</td>
<td>Whether the argument is required</td>
</tr>
<tr>
<td><code>help</code></td>
<td>String</td>
<td>Help text description</td>
</tr>
<tr>
<td><code>choices</code></td>
<td>List</td>
<td>List of valid values</td>
</tr>
<tr>
<td><code>action</code></td>
<td>String</td>
<td>How to handle the argument</td>
</tr>
<tr>
<td><code>nargs</code></td>
<td>String/Num</td>
<td>Number of values to consume</td>
</tr>
<tr>
<td><code>dest</code></td>
<td>String</td>
<td>Name for the result map key</td>
</tr>
</table>
<h2>Actions</h2>
<table>
<tr>
<th>Action</th>
<th>Description</th>
</tr>
<tr>
<td><code>store</code></td>
<td>Store the value (default)</td>
</tr>
<tr>
<td><code>storeTrue</code></td>
<td>Store <code>true</code> when flag is present</td>
</tr>
<tr>
<td><code>storeFalse</code></td>
<td>Store <code>false</code> when flag is present</td>
</tr>
<tr>
<td><code>count</code></td>
<td>Count occurrences of the flag</td>
</tr>
<tr>
<td><code>append</code></td>
<td>Append values to a list</td>
</tr>
</table>
<h2>Nargs Values</h2>
<table>
<tr>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td><code>*</code></td>
<td>Zero or more values (returns list)</td>
</tr>
<tr>
<td><code>+</code></td>
<td>One or more values (returns list)</td>
</tr>
<tr>
<td><code>N</code> (number)</td>
<td>Exactly N values (returns list)</td>
</tr>
</table>
<h2>Examples</h2>
<h3>Basic Usage</h3>
<pre><code>import "argparse" for ArgumentParser
var parser = ArgumentParser.new("File processing utility")
parser.prog = "myapp"
parser.addArgument("filename", {"help": "Input file to process"})
parser.addArgument("-o", {
"long": "--output",
"help": "Output file path",
"default": "output.txt"
})
var args = parser.parseArgs()
System.print("Input: %(args["filename"])")
System.print("Output: %(args["output"])")</code></pre>
<h3>Boolean Flags</h3>
<pre><code>import "argparse" for ArgumentParser
var parser = ArgumentParser.new()
parser.addArgument("-v", {
"long": "--verbose",
"action": "storeTrue",
"help": "Enable verbose output"
})
parser.addArgument("-q", {
"long": "--quiet",
"action": "storeFalse",
"dest": "verbose",
"help": "Disable verbose output"
})
var args = parser.parseArgs(["-v"])
System.print(args["verbose"]) // true</code></pre>
<h3>Type Conversion</h3>
<pre><code>import "argparse" for ArgumentParser
var parser = ArgumentParser.new()
parser.addArgument("-n", {
"long": "--count",
"type": "int",
"default": 1,
"help": "Number of iterations"
})
parser.addArgument("-t", {
"long": "--threshold",
"type": "float",
"default": 0.5,
"help": "Detection threshold"
})
var args = parser.parseArgs(["-n", "10", "-t", "0.75"])
System.print(args["count"]) // 10 (Num)
System.print(args["threshold"]) // 0.75 (Num)</code></pre>
<h3>Multiple Values</h3>
<pre><code>import "argparse" for ArgumentParser
var parser = ArgumentParser.new()
parser.addArgument("files", {
"nargs": "+",
"help": "Files to process"
})
parser.addArgument("-e", {
"long": "--exclude",
"action": "append",
"help": "Patterns to exclude"
})
var args = parser.parseArgs(["file1.txt", "file2.txt", "-e", "*.tmp", "-e", "*.bak"])
System.print(args["files"]) // ["file1.txt", "file2.txt"]
System.print(args["exclude"]) // ["*.tmp", "*.bak"]</code></pre>
<h3>Choices</h3>
<pre><code>import "argparse" for ArgumentParser
var parser = ArgumentParser.new()
parser.addArgument("-f", {
"long": "--format",
"choices": ["json", "xml", "csv"],
"default": "json",
"help": "Output format"
})
var args = parser.parseArgs(["-f", "csv"])
System.print(args["format"]) // csv</code></pre>
<h3>Verbosity Counter</h3>
<pre><code>import "argparse" for ArgumentParser
var parser = ArgumentParser.new()
parser.addArgument("-v", {
"action": "count",
"help": "Increase verbosity"
})
var args = parser.parseArgs(["-v", "-v", "-v"])
System.print(args["v"]) // 3</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Argument names starting with <code>-</code> are treated as optional flags. Names without a leading dash are positional arguments. Hyphens in argument names are converted to underscores in the result map.</p>
</div>
{% endblock %}
+178
View File
@@ -0,0 +1,178 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "base64" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "base64"}] %}
{% set prev_page = {"url": "api/json.html", "title": "json"} %}
{% set next_page = {"url": "api/regex.html", "title": "regex"} %}
{% block article %}
<h1>base64</h1>
<p>The <code>base64</code> module provides Base64 encoding and decoding functionality, including URL-safe variants.</p>
<pre><code>import "base64" for Base64</code></pre>
<h2>Base64 Class</h2>
<div class="class-header">
<h3>Base64</h3>
<p>Base64 encoding and decoding utilities</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Base64.encode</span>(<span class="param">data</span>) &#8594; <span class="type">String</span>
</div>
<p>Encodes data to standard Base64.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Data to encode</li>
<li><span class="returns">Returns:</span> Base64-encoded string</li>
</ul>
<pre><code>var encoded = Base64.encode("Hello, World!")
System.print(encoded) // SGVsbG8sIFdvcmxkIQ==</code></pre>
<div class="method-signature">
<span class="method-name">Base64.decode</span>(<span class="param">data</span>) &#8594; <span class="type">String</span>
</div>
<p>Decodes a Base64-encoded string.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Base64 string to decode</li>
<li><span class="returns">Returns:</span> Decoded data as a string</li>
</ul>
<pre><code>var decoded = Base64.decode("SGVsbG8sIFdvcmxkIQ==")
System.print(decoded) // Hello, World!</code></pre>
<div class="method-signature">
<span class="method-name">Base64.encodeUrl</span>(<span class="param">data</span>) &#8594; <span class="type">String</span>
</div>
<p>Encodes data to URL-safe Base64. Replaces <code>+</code> with <code>-</code>, <code>/</code> with <code>_</code>, and removes padding.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Data to encode</li>
<li><span class="returns">Returns:</span> URL-safe Base64 string (no padding)</li>
</ul>
<pre><code>var encoded = Base64.encodeUrl("Hello, World!")
System.print(encoded) // SGVsbG8sIFdvcmxkIQ</code></pre>
<div class="method-signature">
<span class="method-name">Base64.decodeUrl</span>(<span class="param">data</span>) &#8594; <span class="type">String</span>
</div>
<p>Decodes a URL-safe Base64 string. Handles strings with or without padding.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - URL-safe Base64 string to decode</li>
<li><span class="returns">Returns:</span> Decoded data as a string</li>
</ul>
<pre><code>var decoded = Base64.decodeUrl("SGVsbG8sIFdvcmxkIQ")
System.print(decoded) // Hello, World!</code></pre>
<h2>Encoding Comparison</h2>
<table>
<tr>
<th>Method</th>
<th>Alphabet</th>
<th>Padding</th>
<th>Use Case</th>
</tr>
<tr>
<td><code>encode</code></td>
<td>A-Z, a-z, 0-9, +, /</td>
<td>Yes (=)</td>
<td>General purpose, email, file storage</td>
</tr>
<tr>
<td><code>encodeUrl</code></td>
<td>A-Z, a-z, 0-9, -, _</td>
<td>No</td>
<td>URLs, filenames, JWT tokens</td>
</tr>
</table>
<h2>Examples</h2>
<h3>Basic Encoding and Decoding</h3>
<pre><code>import "base64" for Base64
var original = "The quick brown fox jumps over the lazy dog"
var encoded = Base64.encode(original)
System.print("Encoded: %(encoded)")
var decoded = Base64.decode(encoded)
System.print("Decoded: %(decoded)")
System.print("Match: %(original == decoded)")</code></pre>
<h3>Binary Data Encoding</h3>
<pre><code>import "base64" for Base64
var binaryData = String.fromCodePoint(0) +
String.fromCodePoint(1) +
String.fromCodePoint(255)
var encoded = Base64.encode(binaryData)
System.print("Encoded binary: %(encoded)")
var decoded = Base64.decode(encoded)
System.print("Byte 0: %(decoded.bytes[0])")
System.print("Byte 1: %(decoded.bytes[1])")
System.print("Byte 2: %(decoded.bytes[2])")</code></pre>
<h3>URL-Safe Encoding for Tokens</h3>
<pre><code>import "base64" for Base64
import "crypto" for Crypto, Hash
var data = "user:12345"
var token = Base64.encodeUrl(data)
System.print("Token: %(token)")
var decodedToken = Base64.decodeUrl(token)
System.print("Decoded: %(decodedToken)")</code></pre>
<h3>HTTP Basic Authentication</h3>
<pre><code>import "base64" for Base64
import "http" for Http
var username = "alice"
var password = "secret123"
var credentials = Base64.encode("%(username):%(password)")
var headers = {
"Authorization": "Basic %(credentials)"
}
var response = Http.get("https://api.example.com/protected", headers)
System.print(response.body)</code></pre>
<h3>Data URI Encoding</h3>
<pre><code>import "base64" for Base64
import "io" for File
var imageData = File.read("image.png")
var encoded = Base64.encode(imageData)
var dataUri = "data:image/png;base64,%(encoded)"
System.print(dataUri)</code></pre>
<h3>JWT-Style Token Parts</h3>
<pre><code>import "base64" for Base64
import "json" for Json
var header = {"alg": "HS256", "typ": "JWT"}
var payload = {"sub": "1234567890", "name": "John Doe"}
var headerB64 = Base64.encodeUrl(Json.stringify(header))
var payloadB64 = Base64.encodeUrl(Json.stringify(payload))
System.print("Header: %(headerB64)")
System.print("Payload: %(payloadB64)")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Base64 encoding increases data size by approximately 33%. A 3-byte input becomes 4 Base64 characters.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Use <code>encodeUrl</code> and <code>decodeUrl</code> when the encoded string will be used in URLs, query parameters, or filenames where <code>+</code>, <code>/</code>, and <code>=</code> characters may cause issues.</p>
</div>
{% endblock %}
+226
View File
@@ -0,0 +1,226 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "crypto" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "crypto"}] %}
{% set prev_page = {"url": "api/jinja.html", "title": "jinja"} %}
{% set next_page = {"url": "api/os.html", "title": "os"} %}
{% block article %}
<h1>crypto</h1>
<p>The <code>crypto</code> module provides cryptographic functions including secure random number generation and hash algorithms.</p>
<pre><code>import "crypto" for Crypto, Hash</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#crypto-class">Crypto Class</a></li>
<li><a href="#hash-class">Hash Class</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="crypto-class">Crypto Class</h2>
<div class="class-header">
<h3>Crypto</h3>
<p>Cryptographic random number generation</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Crypto.randomBytes</span>(<span class="param">length</span>) &#8594; <span class="type">List</span>
</div>
<p>Generates cryptographically secure random bytes.</p>
<ul class="param-list">
<li><span class="param-name">length</span> <span class="param-type">(Num)</span> - Number of bytes to generate (must be non-negative)</li>
<li><span class="returns">Returns:</span> List of random byte values (0-255)</li>
</ul>
<pre><code>var bytes = Crypto.randomBytes(16)
System.print(bytes) // [142, 55, 201, 89, ...]</code></pre>
<div class="method-signature">
<span class="method-name">Crypto.randomInt</span>(<span class="param">min</span>, <span class="param">max</span>) &#8594; <span class="type">Num</span>
</div>
<p>Generates a cryptographically secure random integer in the specified range.</p>
<ul class="param-list">
<li><span class="param-name">min</span> <span class="param-type">(Num)</span> - Minimum value (inclusive)</li>
<li><span class="param-name">max</span> <span class="param-type">(Num)</span> - Maximum value (exclusive)</li>
<li><span class="returns">Returns:</span> Random integer in range [min, max)</li>
</ul>
<pre><code>var roll = Crypto.randomInt(1, 7) // Dice roll: 1-6
System.print(roll)</code></pre>
<h2 id="hash-class">Hash Class</h2>
<div class="class-header">
<h3>Hash</h3>
<p>Cryptographic hash functions</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Hash.md5</span>(<span class="param">data</span>) &#8594; <span class="type">List</span>
</div>
<p>Computes the MD5 hash of the input data.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String|List)</span> - Data to hash (string or list of bytes)</li>
<li><span class="returns">Returns:</span> 16-byte hash as a list of bytes</li>
</ul>
<pre><code>var hash = Hash.md5("Hello, World!")
System.print(Hash.toHex(hash)) // 65a8e27d8879283831b664bd8b7f0ad4</code></pre>
<div class="method-signature">
<span class="method-name">Hash.sha1</span>(<span class="param">data</span>) &#8594; <span class="type">List</span>
</div>
<p>Computes the SHA-1 hash of the input data.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String|List)</span> - Data to hash</li>
<li><span class="returns">Returns:</span> 20-byte hash as a list of bytes</li>
</ul>
<pre><code>var hash = Hash.sha1("Hello, World!")
System.print(Hash.toHex(hash)) // 0a0a9f2a6772942557ab5355d76af442f8f65e01</code></pre>
<div class="method-signature">
<span class="method-name">Hash.sha256</span>(<span class="param">data</span>) &#8594; <span class="type">List</span>
</div>
<p>Computes the SHA-256 hash of the input data.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String|List)</span> - Data to hash</li>
<li><span class="returns">Returns:</span> 32-byte hash as a list of bytes</li>
</ul>
<pre><code>var hash = Hash.sha256("Hello, World!")
System.print(Hash.toHex(hash)) // dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f</code></pre>
<div class="method-signature">
<span class="method-name">Hash.toHex</span>(<span class="param">bytes</span>) &#8594; <span class="type">String</span>
</div>
<p>Converts a list of bytes to a hexadecimal string.</p>
<ul class="param-list">
<li><span class="param-name">bytes</span> <span class="param-type">(List)</span> - List of byte values</li>
<li><span class="returns">Returns:</span> Lowercase hexadecimal string</li>
</ul>
<pre><code>var hex = Hash.toHex([0xDE, 0xAD, 0xBE, 0xEF])
System.print(hex) // deadbeef</code></pre>
<h2>Hash Algorithm Comparison</h2>
<table>
<tr>
<th>Algorithm</th>
<th>Output Size</th>
<th>Security</th>
<th>Use Case</th>
</tr>
<tr>
<td>MD5</td>
<td>128 bits (16 bytes)</td>
<td>Broken</td>
<td>Checksums only (not for security)</td>
</tr>
<tr>
<td>SHA-1</td>
<td>160 bits (20 bytes)</td>
<td>Weak</td>
<td>Legacy compatibility only</td>
</tr>
<tr>
<td>SHA-256</td>
<td>256 bits (32 bytes)</td>
<td>Strong</td>
<td>Recommended for new applications</td>
</tr>
</table>
<h2 id="examples">Examples</h2>
<h3>Generating a Random Token</h3>
<pre><code>import "crypto" for Crypto, Hash
var bytes = Crypto.randomBytes(32)
var token = Hash.toHex(bytes)
System.print("Random token: %(token)")</code></pre>
<h3>Password Hashing</h3>
<pre><code>import "crypto" for Crypto, Hash
var password = "mysecretpassword"
var salt = Crypto.randomBytes(16)
var saltHex = Hash.toHex(salt)
var saltedPassword = saltHex + password
var hash = Hash.sha256(saltedPassword)
var hashHex = Hash.toHex(hash)
System.print("Salt: %(saltHex)")
System.print("Hash: %(hashHex)")</code></pre>
<h3>File Checksum</h3>
<pre><code>import "crypto" for Hash
import "io" for File
var content = File.read("document.txt")
var hash = Hash.sha256(content)
System.print("SHA-256: %(Hash.toHex(hash))")</code></pre>
<h3>Random Selection</h3>
<pre><code>import "crypto" for Crypto
var items = ["apple", "banana", "cherry", "date", "elderberry"]
var index = Crypto.randomInt(0, items.count)
System.print("Selected: %(items[index])")</code></pre>
<h3>Generating Random IDs</h3>
<pre><code>import "crypto" for Crypto, Hash
import "base64" for Base64
var bytes = Crypto.randomBytes(12)
var id = ""
for (b in bytes) {
id = id + String.fromCodePoint(b)
}
var encoded = Base64.encodeUrl(id)
System.print("Random ID: %(encoded)")</code></pre>
<h3>Secure Dice Roll</h3>
<pre><code>import "crypto" for Crypto
var numDice = 5
var results = []
for (i in 0...numDice) {
results.add(Crypto.randomInt(1, 7))
}
System.print("Dice rolls: %(results)")</code></pre>
<h3>Comparing Hashes</h3>
<pre><code>import "crypto" for Hash
var original = "Hello, World!"
var hash1 = Hash.sha256(original)
var hash2 = Hash.sha256(original)
var hash3 = Hash.sha256("Different text")
System.print("Same input, same hash: %(Hash.toHex(hash1) == Hash.toHex(hash2))")
System.print("Different input, different hash: %(Hash.toHex(hash1) != Hash.toHex(hash3))")</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>MD5 and SHA-1 are cryptographically broken and should not be used for security-sensitive applications. Use SHA-256 for new applications requiring secure hashing.</p>
</div>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Hash functions accept both strings and lists of bytes. When a string is provided, it is automatically converted to its byte representation before hashing.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>For password storage, always use a salt (random bytes prepended to the password) before hashing. Store the salt alongside the hash for verification.</p>
</div>
{% endblock %}
+293
View File
@@ -0,0 +1,293 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "dataset" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "dataset"}] %}
{% set prev_page = {"url": "api/wdantic.html", "title": "wdantic"} %}
{% set next_page = {"url": "api/markdown.html", "title": "markdown"} %}
{% block article %}
<h1>dataset</h1>
<p>The <code>dataset</code> module provides a simple ORM-like interface for SQLite databases with automatic schema management. Tables and columns are created automatically as you insert data.</p>
<pre><code>import "dataset" for Dataset, Table</code></pre>
<h2>Dataset Class</h2>
<div class="class-header">
<h3>Dataset</h3>
<p>Database connection and table access</p>
</div>
<h3>Constructors</h3>
<div class="method-signature">
<span class="method-name">Dataset.open</span>(<span class="param">path</span>) → <span class="type">Dataset</span>
</div>
<p>Opens or creates a SQLite database file.</p>
<ul class="param-list">
<li><span class="param-name">path</span> <span class="param-type">(String)</span> - Path to the database file</li>
</ul>
<pre><code>var db = Dataset.open("data.db")</code></pre>
<div class="method-signature">
<span class="method-name">Dataset.memory</span>() → <span class="type">Dataset</span>
</div>
<p>Creates an in-memory database (data is lost when closed).</p>
<pre><code>var db = Dataset.memory()</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">db</span><span class="type">Database</span>
</div>
<p>Access to the underlying SQLite database for raw queries.</p>
<div class="method-signature">
<span class="method-name">tables</span><span class="type">List</span>
</div>
<p>List of table names in the database.</p>
<h3>Subscript Access</h3>
<div class="method-signature">
<span class="method-name">[tableName]</span><span class="type">Table</span>
</div>
<p>Gets a table by name. Creates the table if it does not exist on first insert.</p>
<pre><code>var users = db["users"]</code></pre>
<h3>Instance Methods</h3>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<p>Closes the database connection.</p>
<h2>Table Class</h2>
<div class="class-header">
<h3>Table</h3>
<p>CRUD operations on a database table</p>
</div>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">name</span><span class="type">String</span>
</div>
<p>The table name.</p>
<div class="method-signature">
<span class="method-name">columns</span><span class="type">Map</span>
</div>
<p>Map of column names to SQL types.</p>
<h3>Instance Methods</h3>
<div class="method-signature">
<span class="method-name">insert</span>(<span class="param">record</span>) → <span class="type">Map</span>
</div>
<p>Inserts a record and returns it with generated <code>uid</code> and <code>created_at</code>.</p>
<ul class="param-list">
<li><span class="param-name">record</span> <span class="param-type">(Map)</span> - Data to insert</li>
<li><span class="returns">Returns:</span> Inserted record with uid and created_at</li>
</ul>
<pre><code>var user = db["users"].insert({
"name": "Alice",
"email": "alice@example.com"
})
System.print(user["uid"]) // auto-generated UUID</code></pre>
<div class="method-signature">
<span class="method-name">update</span>(<span class="param">record</span>) → <span class="type">Num</span>
</div>
<p>Updates a record by uid. Returns number of rows affected.</p>
<ul class="param-list">
<li><span class="param-name">record</span> <span class="param-type">(Map)</span> - Must contain <code>uid</code> and fields to update</li>
</ul>
<pre><code>db["users"].update({
"uid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Alice Smith"
})</code></pre>
<div class="method-signature">
<span class="method-name">delete</span>(<span class="param">uid</span>) → <span class="type">Bool</span>
</div>
<p>Soft deletes a record (sets <code>deleted_at</code> timestamp). Returns true if record was deleted.</p>
<pre><code>db["users"].delete("550e8400-e29b-41d4-a716-446655440000")</code></pre>
<div class="method-signature">
<span class="method-name">hardDelete</span>(<span class="param">uid</span>) → <span class="type">Bool</span>
</div>
<p>Permanently deletes a record from the database.</p>
<div class="method-signature">
<span class="method-name">find</span>(<span class="param">conditions</span>) → <span class="type">List</span>
</div>
<p>Finds records matching conditions. Returns list of records (excludes soft-deleted).</p>
<pre><code>var admins = db["users"].find({"role": "admin"})</code></pre>
<div class="method-signature">
<span class="method-name">findOne</span>(<span class="param">conditions</span>) → <span class="type">Map|null</span>
</div>
<p>Finds first record matching conditions or null.</p>
<pre><code>var user = db["users"].findOne({"email": "alice@example.com"})</code></pre>
<div class="method-signature">
<span class="method-name">all</span>() → <span class="type">List</span>
</div>
<p>Returns all non-deleted records.</p>
<div class="method-signature">
<span class="method-name">count</span>() → <span class="type">Num</span>
</div>
<p>Returns count of non-deleted records.</p>
<h2>Query Operators</h2>
<p>Use suffixes in condition keys for comparison operators:</p>
<table>
<tr>
<th>Suffix</th>
<th>SQL Operator</th>
<th>Example</th>
</tr>
<tr>
<td><code>__gt</code></td>
<td>&gt;</td>
<td><code>{"age__gt": 18}</code></td>
</tr>
<tr>
<td><code>__lt</code></td>
<td>&lt;</td>
<td><code>{"price__lt": 100}</code></td>
</tr>
<tr>
<td><code>__gte</code></td>
<td>&gt;=</td>
<td><code>{"score__gte": 90}</code></td>
</tr>
<tr>
<td><code>__lte</code></td>
<td>&lt;=</td>
<td><code>{"score__lte": 100}</code></td>
</tr>
<tr>
<td><code>__ne</code></td>
<td>!=</td>
<td><code>{"status__ne": "deleted"}</code></td>
</tr>
<tr>
<td><code>__like</code></td>
<td>LIKE</td>
<td><code>{"name__like": "A\%"}</code></td>
</tr>
<tr>
<td><code>__in</code></td>
<td>IN</td>
<td><code>{"status__in": ["active", "pending"]}</code></td>
</tr>
<tr>
<td><code>__null</code></td>
<td>IS NULL / IS NOT NULL</td>
<td><code>{"deleted_at__null": true}</code></td>
</tr>
</table>
<h2>Automatic Features</h2>
<h3>Auto-Generated Fields</h3>
<ul>
<li><code>uid</code> - UUID v4 primary key (auto-generated if not provided)</li>
<li><code>created_at</code> - ISO timestamp (auto-generated on insert)</li>
<li><code>deleted_at</code> - ISO timestamp (set by soft delete)</li>
</ul>
<h3>Auto Schema</h3>
<ul>
<li>Tables are created automatically on first insert</li>
<li>Columns are added automatically when new fields appear</li>
<li>Type inference: Num → INTEGER/REAL, Bool → INTEGER, Map/List → TEXT (JSON)</li>
</ul>
<h3>JSON Serialization</h3>
<p>Maps and Lists are automatically serialized to JSON when stored and deserialized when retrieved.</p>
<h2>Examples</h2>
<h3>Basic CRUD</h3>
<pre><code>import "dataset" for Dataset
var db = Dataset.open("app.db")
var users = db["users"]
var user = users.insert({
"name": "Alice",
"email": "alice@example.com",
"settings": {"theme": "dark", "notifications": true}
})
System.print("Created user: %(user["uid"])")
users.update({
"uid": user["uid"],
"name": "Alice Smith"
})
var found = users.findOne({"email": "alice@example.com"})
System.print("Found: %(found["name"])")
users.delete(user["uid"])
db.close()</code></pre>
<h3>Querying with Operators</h3>
<pre><code>import "dataset" for Dataset
var db = Dataset.open("products.db")
var products = db["products"]
var expensive = products.find({"price__gt": 100})
var cheap = products.find({"price__lte": 10})
var search = products.find({"name__like": "\%phone\%"})
var featured = products.find({
"category__in": ["electronics", "gadgets"],
"stock__gt": 0
})
db.close()</code></pre>
<h3>Working with JSON Data</h3>
<pre><code>import "dataset" for Dataset
var db = Dataset.memory()
db["posts"].insert({
"title": "First Post",
"tags": ["wren", "programming", "tutorial"],
"metadata": {
"author": "Alice",
"views": 100,
"featured": true
}
})
var post = db["posts"].findOne({"title": "First Post"})
System.print(post["tags"]) // ["wren", "programming", "tutorial"]
System.print(post["metadata"]) // {"author": "Alice", ...}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Soft delete is the default behavior. Records are not permanently removed but marked with a <code>deleted_at</code> timestamp. Use <code>hardDelete()</code> for permanent removal. All query methods automatically exclude soft-deleted records.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>The <code>uid</code> field is the primary key. Do not use <code>id</code> as a field name. If you provide a <code>uid</code> during insert, it will be used instead of auto-generating one.</p>
</div>
{% endblock %}
+375
View File
@@ -0,0 +1,375 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "datetime" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "datetime"}] %}
{% set prev_page = {"url": "api/sqlite.html", "title": "sqlite"} %}
{% set next_page = {"url": "api/timer.html", "title": "timer"} %}
{% block article %}
<h1>datetime</h1>
<p>The <code>datetime</code> module provides date and time handling with formatting, arithmetic, and duration support.</p>
<pre><code>import "datetime" for DateTime, Duration</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#datetime-class">DateTime Class</a></li>
<li><a href="#duration-class">Duration Class</a></li>
<li><a href="#format-patterns">Format Patterns</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="datetime-class">DateTime Class</h2>
<div class="class-header">
<h3>DateTime</h3>
<p>Date and time representation</p>
</div>
<h3>Constructors</h3>
<div class="method-signature">
<span class="method-name">DateTime.now</span>() → <span class="type">DateTime</span>
</div>
<p>Creates a DateTime representing the current local time.</p>
<pre><code>var now = DateTime.now()
System.print(now) // 2024-01-15T10:30:45</code></pre>
<div class="method-signature">
<span class="method-name">DateTime.fromTimestamp</span>(<span class="param">timestamp</span>) → <span class="type">DateTime</span>
</div>
<p>Creates a DateTime from a Unix timestamp (seconds since epoch).</p>
<pre><code>var dt = DateTime.fromTimestamp(1705312245)
System.print(dt) // 2024-01-15T10:30:45</code></pre>
<h3>Properties</h3>
<table>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>timestamp</code></td>
<td>Num</td>
<td>Unix timestamp (seconds since epoch)</td>
</tr>
<tr>
<td><code>year</code></td>
<td>Num</td>
<td>Year (e.g., 2024)</td>
</tr>
<tr>
<td><code>month</code></td>
<td>Num</td>
<td>Month (1-12)</td>
</tr>
<tr>
<td><code>day</code></td>
<td>Num</td>
<td>Day of month (1-31)</td>
</tr>
<tr>
<td><code>hour</code></td>
<td>Num</td>
<td>Hour (0-23)</td>
</tr>
<tr>
<td><code>minute</code></td>
<td>Num</td>
<td>Minute (0-59)</td>
</tr>
<tr>
<td><code>second</code></td>
<td>Num</td>
<td>Second (0-59)</td>
</tr>
<tr>
<td><code>dayOfWeek</code></td>
<td>Num</td>
<td>Day of week (0=Sunday, 6=Saturday)</td>
</tr>
<tr>
<td><code>dayOfYear</code></td>
<td>Num</td>
<td>Day of year (1-366)</td>
</tr>
<tr>
<td><code>isDst</code></td>
<td>Bool</td>
<td>True if daylight saving time is in effect</td>
</tr>
</table>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">format</span>(<span class="param">pattern</span>) → <span class="type">String</span>
</div>
<p>Formats the date/time using strftime-style patterns.</p>
<pre><code>var now = DateTime.now()
System.print(now.format("\%Y-\%m-\%d")) // 2024-01-15
System.print(now.format("\%H:\%M:\%S")) // 10:30:45
System.print(now.format("\%A, \%B \%d, \%Y")) // Monday, January 15, 2024</code></pre>
<div class="method-signature">
<span class="method-name">toIso8601</span><span class="type">String</span>
</div>
<p>Returns the date/time in ISO 8601 format.</p>
<pre><code>System.print(DateTime.now().toIso8601) // 2024-01-15T10:30:45</code></pre>
<h3>Operators</h3>
<div class="method-signature">
<span class="method-name">+</span>(<span class="param">duration</span>) → <span class="type">DateTime</span>
</div>
<p>Adds a Duration to the DateTime.</p>
<pre><code>var now = DateTime.now()
var later = now + Duration.fromHours(2)
System.print(later)</code></pre>
<div class="method-signature">
<span class="method-name">-</span>(<span class="param">other</span>) → <span class="type">DateTime|Duration</span>
</div>
<p>Subtracts a Duration (returns DateTime) or another DateTime (returns Duration).</p>
<pre><code>var now = DateTime.now()
var earlier = now - Duration.fromDays(1)
var start = DateTime.fromTimestamp(1705312245)
var end = DateTime.now()
var elapsed = end - start // Duration</code></pre>
<div class="method-signature">
<span class="method-name">==</span>, <span class="method-name">&lt;</span>, <span class="method-name">&gt;</span>, <span class="method-name">&lt;=</span>, <span class="method-name">&gt;=</span>
</div>
<p>Comparison operators for comparing two DateTimes.</p>
<h2 id="duration-class">Duration Class</h2>
<div class="class-header">
<h3>Duration</h3>
<p>Time duration representation</p>
</div>
<h3>Constructors</h3>
<div class="method-signature">
<span class="method-name">Duration.fromMilliseconds</span>(<span class="param">ms</span>) → <span class="type">Duration</span>
</div>
<div class="method-signature">
<span class="method-name">Duration.fromSeconds</span>(<span class="param">s</span>) → <span class="type">Duration</span>
</div>
<div class="method-signature">
<span class="method-name">Duration.fromMinutes</span>(<span class="param">m</span>) → <span class="type">Duration</span>
</div>
<div class="method-signature">
<span class="method-name">Duration.fromHours</span>(<span class="param">h</span>) → <span class="type">Duration</span>
</div>
<div class="method-signature">
<span class="method-name">Duration.fromDays</span>(<span class="param">d</span>) → <span class="type">Duration</span>
</div>
<pre><code>var oneHour = Duration.fromHours(1)
var threeMinutes = Duration.fromMinutes(3)
var twoAndHalfDays = Duration.fromDays(2.5)</code></pre>
<h3>Properties</h3>
<table>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>milliseconds</code></td>
<td>Num</td>
<td>Total milliseconds</td>
</tr>
<tr>
<td><code>seconds</code></td>
<td>Num</td>
<td>Total seconds</td>
</tr>
<tr>
<td><code>minutes</code></td>
<td>Num</td>
<td>Total minutes</td>
</tr>
<tr>
<td><code>hours</code></td>
<td>Num</td>
<td>Total hours</td>
</tr>
<tr>
<td><code>days</code></td>
<td>Num</td>
<td>Total days</td>
</tr>
</table>
<h3>Operators</h3>
<div class="method-signature">
<span class="method-name">+</span>, <span class="method-name">-</span>(<span class="param">duration</span>) → <span class="type">Duration</span>
</div>
<p>Add or subtract durations.</p>
<div class="method-signature">
<span class="method-name">*</span>(<span class="param">factor</span>) → <span class="type">Duration</span>
</div>
<p>Multiply duration by a factor.</p>
<pre><code>var d1 = Duration.fromHours(2)
var d2 = Duration.fromMinutes(30)
var total = d1 + d2 // 2.5 hours
var doubled = d1 * 2 // 4 hours</code></pre>
<h2 id="format-patterns">Format Patterns</h2>
<table>
<tr>
<th>Pattern</th>
<th>Description</th>
<th>Example</th>
</tr>
<tr>
<td><code>\%Y</code></td>
<td>4-digit year</td>
<td>2024</td>
</tr>
<tr>
<td><code>\%y</code></td>
<td>2-digit year</td>
<td>24</td>
</tr>
<tr>
<td><code>\%m</code></td>
<td>Month (01-12)</td>
<td>01</td>
</tr>
<tr>
<td><code>\%d</code></td>
<td>Day of month (01-31)</td>
<td>15</td>
</tr>
<tr>
<td><code>\%H</code></td>
<td>Hour 24h (00-23)</td>
<td>14</td>
</tr>
<tr>
<td><code>\%I</code></td>
<td>Hour 12h (01-12)</td>
<td>02</td>
</tr>
<tr>
<td><code>\%M</code></td>
<td>Minute (00-59)</td>
<td>30</td>
</tr>
<tr>
<td><code>\%S</code></td>
<td>Second (00-59)</td>
<td>45</td>
</tr>
<tr>
<td><code>\%p</code></td>
<td>AM/PM</td>
<td>PM</td>
</tr>
<tr>
<td><code>\%A</code></td>
<td>Full weekday name</td>
<td>Monday</td>
</tr>
<tr>
<td><code>\%a</code></td>
<td>Abbreviated weekday</td>
<td>Mon</td>
</tr>
<tr>
<td><code>\%B</code></td>
<td>Full month name</td>
<td>January</td>
</tr>
<tr>
<td><code>\%b</code></td>
<td>Abbreviated month</td>
<td>Jan</td>
</tr>
<tr>
<td><code>\%j</code></td>
<td>Day of year (001-366)</td>
<td>015</td>
</tr>
<tr>
<td><code>\%w</code></td>
<td>Weekday (0-6, Sun=0)</td>
<td>1</td>
</tr>
</table>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>In Wren strings, <code>%</code> starts string interpolation, so use <code>\%</code> for literal percent signs in format patterns.</p>
</div>
<h2 id="examples">Examples</h2>
<h3>Current Date and Time</h3>
<pre><code>import "datetime" for DateTime
var now = DateTime.now()
System.print("Year: %(now.year)")
System.print("Month: %(now.month)")
System.print("Day: %(now.day)")
System.print("Time: %(now.hour):%(now.minute):%(now.second)")</code></pre>
<h3>Formatting Dates</h3>
<pre><code>import "datetime" for DateTime
var now = DateTime.now()
System.print(now.format("\%Y-\%m-\%d")) // 2024-01-15
System.print(now.format("\%B \%d, \%Y")) // January 15, 2024
System.print(now.format("\%I:\%M \%p")) // 02:30 PM</code></pre>
<h3>Date Arithmetic</h3>
<pre><code>import "datetime" for DateTime, Duration
var now = DateTime.now()
var tomorrow = now + Duration.fromDays(1)
var nextWeek = now + Duration.fromDays(7)
var inTwoHours = now + Duration.fromHours(2)
System.print("Tomorrow: %(tomorrow.format("\%Y-\%m-\%d"))")
System.print("Next week: %(nextWeek.format("\%Y-\%m-\%d"))")</code></pre>
<h3>Calculating Time Differences</h3>
<pre><code>import "datetime" for DateTime
var start = DateTime.fromTimestamp(1705312245)
var end = DateTime.now()
var elapsed = end - start
System.print("Elapsed: %(elapsed.days.floor) days, %(elapsed.hours.floor \% 24) hours")</code></pre>
<h3>Comparing Dates</h3>
<pre><code>import "datetime" for DateTime, Duration
var now = DateTime.now()
var deadline = now + Duration.fromDays(7)
if (now < deadline) {
System.print("Still have time!")
}</code></pre>
{% endblock %}
+161
View File
@@ -0,0 +1,161 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "dns" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "dns"}] %}
{% set prev_page = {"url": "api/net.html", "title": "net"} %}
{% set next_page = {"url": "api/json.html", "title": "json"} %}
{% block article %}
<h1>dns</h1>
<p>The <code>dns</code> module provides DNS resolution functionality for looking up hostnames and resolving them to IP addresses.</p>
<pre><code>import "dns" for Dns</code></pre>
<h2>Dns Class</h2>
<div class="class-header">
<h3>Dns</h3>
<p>DNS resolution utilities</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Dns.lookup</span>(<span class="param">hostname</span>) &#8594; <span class="type">String</span>
</div>
<p>Resolves a hostname to an IP address. Uses the system's default address family preference.</p>
<ul class="param-list">
<li><span class="param-name">hostname</span> <span class="param-type">(String)</span> - Hostname to resolve</li>
<li><span class="returns">Returns:</span> IP address as a string</li>
</ul>
<pre><code>var ip = Dns.lookup("example.com")
System.print(ip) // 93.184.216.34</code></pre>
<div class="method-signature">
<span class="method-name">Dns.lookup</span>(<span class="param">hostname</span>, <span class="param">family</span>) &#8594; <span class="type">String</span>
</div>
<p>Resolves a hostname to an IP address with a specific address family.</p>
<ul class="param-list">
<li><span class="param-name">hostname</span> <span class="param-type">(String)</span> - Hostname to resolve</li>
<li><span class="param-name">family</span> <span class="param-type">(Num)</span> - Address family: 0 (any), 4 (IPv4), or 6 (IPv6)</li>
<li><span class="returns">Returns:</span> IP address as a string</li>
</ul>
<pre><code>var ipv4 = Dns.lookup("example.com", 4)
System.print(ipv4) // 93.184.216.34
var ipv6 = Dns.lookup("example.com", 6)
System.print(ipv6) // 2606:2800:220:1:248:1893:25c8:1946</code></pre>
<h3>Address Family Values</h3>
<table>
<tr>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td><code>0</code></td>
<td>Any (system default, typically prefers IPv4)</td>
</tr>
<tr>
<td><code>4</code></td>
<td>IPv4 only</td>
</tr>
<tr>
<td><code>6</code></td>
<td>IPv6 only</td>
</tr>
</table>
<h2>Examples</h2>
<h3>Basic DNS Lookup</h3>
<pre><code>import "dns" for Dns
var domains = ["example.com", "google.com", "github.com"]
for (domain in domains) {
var ip = Dns.lookup(domain)
System.print("%(domain) -> %(ip)")
}</code></pre>
<h3>IPv4 vs IPv6</h3>
<pre><code>import "dns" for Dns
var hostname = "google.com"
var ipv4 = Dns.lookup(hostname, 4)
System.print("IPv4: %(ipv4)")
var fiber = Fiber.new {
return Dns.lookup(hostname, 6)
}
var result = fiber.try()
if (fiber.error != null) {
System.print("IPv6: Not available")
} else {
System.print("IPv6: %(result)")
}</code></pre>
<h3>Using with Socket Connection</h3>
<pre><code>import "dns" for Dns
import "net" for Socket
var hostname = "httpbin.org"
var ip = Dns.lookup(hostname, 4)
System.print("Connecting to %(hostname) (%(ip))")
var socket = Socket.connect(ip, 80)
socket.write("GET /ip HTTP/1.1\r\nHost: %(hostname)\r\nConnection: close\r\n\r\n")
var response = ""
while (true) {
var data = socket.read()
if (data == null) break
response = response + data
}
socket.close()
System.print(response)</code></pre>
<h3>Using with TLS Connection</h3>
<pre><code>import "dns" for Dns
import "tls" for TlsSocket
var hostname = "example.com"
var ip = Dns.lookup(hostname, 4)
var socket = TlsSocket.connect(ip, 443, hostname)
socket.write("GET / HTTP/1.1\r\nHost: %(hostname)\r\nConnection: close\r\n\r\n")
var response = socket.read()
System.print(response)
socket.close()</code></pre>
<h3>Error Handling</h3>
<pre><code>import "dns" for Dns
var fiber = Fiber.new {
return Dns.lookup("nonexistent.invalid.domain")
}
var result = fiber.try()
if (fiber.error != null) {
System.print("DNS lookup failed: %(fiber.error)")
} else {
System.print("Resolved to: %(result)")
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>DNS lookups are asynchronous operations that use libuv's thread pool. The calling fiber will suspend until the lookup completes.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>The hostname must be a string. Passing a non-string value will abort the fiber with an error. Similarly, the family parameter must be 0, 4, or 6.</p>
</div>
{% endblock %}
+208
View File
@@ -0,0 +1,208 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "env" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "env"}] %}
{% set prev_page = {"url": "api/dns.html", "title": "dns"} %}
{% set next_page = {"url": "api/faker.html", "title": "faker"} %}
{% block article %}
<h1>env</h1>
<p>The <code>env</code> module provides access to environment variables for reading, writing, and deleting values.</p>
<pre><code>import "env" for Environment</code></pre>
<h2>Environment Class</h2>
<div class="class-header">
<h3>Environment</h3>
<p>Environment variable access</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Environment.get</span>(<span class="param">name</span>) &#8594; <span class="type">String|null</span>
</div>
<p>Gets the value of an environment variable.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Name of the environment variable</li>
<li><span class="returns">Returns:</span> Value of the variable, or null if not set</li>
</ul>
<pre><code>var home = Environment.get("HOME")
System.print(home) // /home/alice
var missing = Environment.get("UNDEFINED_VAR")
System.print(missing) // null</code></pre>
<div class="method-signature">
<span class="method-name">Environment.set</span>(<span class="param">name</span>, <span class="param">value</span>)
</div>
<p>Sets an environment variable.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Name of the environment variable</li>
<li><span class="param-name">value</span> <span class="param-type">(String)</span> - Value to set</li>
</ul>
<pre><code>Environment.set("MY_APP_DEBUG", "true")
System.print(Environment.get("MY_APP_DEBUG")) // true</code></pre>
<div class="method-signature">
<span class="method-name">Environment.delete</span>(<span class="param">name</span>)
</div>
<p>Deletes an environment variable.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Name of the environment variable to delete</li>
</ul>
<pre><code>Environment.delete("MY_APP_DEBUG")
System.print(Environment.get("MY_APP_DEBUG")) // null</code></pre>
<h3>Static Properties</h3>
<div class="method-signature">
<span class="method-name">Environment.all</span> &#8594; <span class="type">Map</span>
</div>
<p>Returns a map of all environment variables.</p>
<pre><code>var env = Environment.all
for (entry in env) {
System.print("%(entry.key) = %(entry.value)")
}</code></pre>
<h2>Examples</h2>
<h3>Reading Configuration from Environment</h3>
<pre><code>import "env" for Environment
var host = Environment.get("APP_HOST")
if (host == null) host = "localhost"
var port = Environment.get("APP_PORT")
if (port == null) port = "8080"
var debug = Environment.get("APP_DEBUG") == "true"
System.print("Starting server on %(host):%(port)")
System.print("Debug mode: %(debug)")</code></pre>
<h3>Default Values Pattern</h3>
<pre><code>import "env" for Environment
class Config {
static get(name, defaultValue) {
var value = Environment.get(name)
return value != null ? value : defaultValue
}
static getInt(name, defaultValue) {
var value = Environment.get(name)
if (value == null) return defaultValue
return Num.fromString(value)
}
static getBool(name, defaultValue) {
var value = Environment.get(name)
if (value == null) return defaultValue
return value == "true" || value == "1"
}
}
var timeout = Config.getInt("TIMEOUT", 30)
var verbose = Config.getBool("VERBOSE", false)
var logFile = Config.get("LOG_FILE", "/var/log/app.log")
System.print("Timeout: %(timeout)s")
System.print("Verbose: %(verbose)")
System.print("Log file: %(logFile)")</code></pre>
<h3>Setting Environment for Subprocesses</h3>
<pre><code>import "env" for Environment
import "subprocess" for Subprocess
Environment.set("NODE_ENV", "production")
Environment.set("PORT", "3000")
var result = Subprocess.run("node server.js")
System.print(result.stdout)</code></pre>
<h3>Listing All Environment Variables</h3>
<pre><code>import "env" for Environment
System.print("=== Environment Variables ===")
var env = Environment.all
var keys = []
for (entry in env) {
keys.add(entry.key)
}
keys.sort()
for (key in keys) {
System.print("%(key) = %(env[key])")</code></pre>
<h3>Checking Required Environment Variables</h3>
<pre><code>import "env" for Environment
var required = ["DATABASE_URL", "API_KEY", "SECRET_KEY"]
var missing = []
for (name in required) {
if (Environment.get(name) == null) {
missing.add(name)
}
}
if (missing.count > 0) {
System.print("Missing required environment variables:")
for (name in missing) {
System.print(" - %(name)")
}
Fiber.abort("Configuration error")
}
System.print("All required environment variables are set")</code></pre>
<h3>Temporary Environment Modification</h3>
<pre><code>import "env" for Environment
var originalPath = Environment.get("PATH")
Environment.set("PATH", "/custom/bin:" + originalPath)
System.print("Modified PATH for operations...")
Environment.set("PATH", originalPath)
System.print("Restored original PATH")</code></pre>
<h3>Database Connection String from Environment</h3>
<pre><code>import "env" for Environment
var dbUrl = Environment.get("DATABASE_URL")
if (dbUrl != null) {
System.print("Using database: %(dbUrl)")
} else {
var host = Environment.get("DB_HOST")
if (host == null) host = "localhost"
var port = Environment.get("DB_PORT")
if (port == null) port = "5432"
var name = Environment.get("DB_NAME")
if (name == null) name = "myapp"
var user = Environment.get("DB_USER")
if (user == null) user = "postgres"
dbUrl = "postgres://%(user)@%(host):%(port)/%(name)"
System.print("Constructed database URL: %(dbUrl)")
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Environment variables modified with <code>set</code> or <code>delete</code> only affect the current process and any child processes. They do not modify the parent shell's environment.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>For configuration that may vary between environments (development, staging, production), use environment variables. This follows the twelve-factor app methodology.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Never log or print sensitive environment variables like API keys, passwords, or secrets. Use them directly in your application without exposing their values.</p>
</div>
{% endblock %}
+814
View File
@@ -0,0 +1,814 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "faker" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "faker"}] %}
{% set prev_page = {"url": "api/env.html", "title": "env"} %}
{% set next_page = {"url": "api/fswatch.html", "title": "fswatch"} %}
{% block article %}
<h1>faker</h1>
<p>The <code>faker</code> module generates realistic fake data for testing, seeding databases, and prototyping. It provides deterministic output when seeded for reproducible test cases.</p>
<pre><code>import "faker" for Faker</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#seeding">Seeding &amp; Random</a></li>
<li><a href="#format-helpers">Format Helpers</a></li>
<li><a href="#person">Person &amp; Names</a></li>
<li><a href="#address">Address &amp; Location</a></li>
<li><a href="#internet">Internet &amp; Network</a></li>
<li><a href="#datetime">Date &amp; Time</a></li>
<li><a href="#text">Text &amp; Lorem</a></li>
<li><a href="#colors">Colors</a></li>
<li><a href="#company">Company &amp; Job</a></li>
<li><a href="#commerce">Commerce &amp; Products</a></li>
<li><a href="#banking">Banking &amp; Finance</a></li>
<li><a href="#files">Files &amp; Hashes</a></li>
<li><a href="#utilities">Utilities</a></li>
</ul>
</div>
<h2>Faker Class</h2>
<div class="class-header">
<h3>Faker</h3>
<p>Generates realistic fake data across many categories</p>
</div>
<h2 id="seeding">Seeding &amp; Random</h2>
<div class="method-signature">
<span class="method-name">Faker.seed</span>(<span class="param">value</span>)
</div>
<p>Sets the random seed for deterministic output. Use the same seed to generate identical sequences.</p>
<ul class="param-list">
<li><span class="param-name">value</span> <span class="param-type">(Num)</span> - Integer seed value</li>
</ul>
<pre><code>Faker.seed(12345)
System.print(Faker.name()) // Always produces the same name with this seed</code></pre>
<div class="method-signature">
<span class="method-name">Faker.reset</span>()
</div>
<p>Resets to unseeded mode, using cryptographically random values.</p>
<pre><code>Faker.reset()
System.print(Faker.name()) // Now produces random names</code></pre>
<div class="method-signature">
<span class="method-name">Faker.randomElement</span>(<span class="param">list</span>) &rarr; <span class="type">Any</span>
</div>
<p>Returns a random element from a list.</p>
<pre><code>var colors = ["red", "green", "blue"]
System.print(Faker.randomElement(colors))</code></pre>
<div class="method-signature">
<span class="method-name">Faker.randomElements</span>(<span class="param">list</span>, <span class="param">count</span>) &rarr; <span class="type">List</span>
</div>
<p>Returns multiple random elements from a list (with possible duplicates).</p>
<pre><code>var picks = Faker.randomElements(["a", "b", "c", "d"], 3)
System.print(picks) // e.g., ["b", "a", "b"]</code></pre>
<div class="method-signature">
<span class="method-name">Faker.randomInt</span>(<span class="param">min</span>, <span class="param">max</span>) &rarr; <span class="type">Num</span>
</div>
<p>Returns a random integer between min and max (inclusive).</p>
<pre><code>var age = Faker.randomInt(18, 65)
System.print(age) // e.g., 42</code></pre>
<div class="method-signature">
<span class="method-name">Faker.randomFloat</span>(<span class="param">min</span>, <span class="param">max</span>) &rarr; <span class="type">Num</span>
</div>
<p>Returns a random float between min and max.</p>
<pre><code>var temp = Faker.randomFloat(20, 30)
System.print(temp) // e.g., 24.7831...</code></pre>
<div class="method-signature">
<span class="method-name">Faker.randomFloat</span>(<span class="param">min</span>, <span class="param">max</span>, <span class="param">precision</span>) &rarr; <span class="type">Num</span>
</div>
<p>Returns a random float with specified decimal precision.</p>
<pre><code>var price = Faker.randomFloat(10, 100, 2)
System.print(price) // e.g., 47.83</code></pre>
<div class="method-signature">
<span class="method-name">Faker.boolean</span>() &rarr; <span class="type">Bool</span>
</div>
<p>Returns a random boolean. Alias: <code>bool()</code>.</p>
<pre><code>var isActive = Faker.boolean()
System.print(isActive) // true or false</code></pre>
<h2 id="format-helpers">Format Helpers</h2>
<div class="method-signature">
<span class="method-name">Faker.numerify</span>(<span class="param">format</span>) &rarr; <span class="type">String</span>
</div>
<p>Replaces <code>#</code> characters with random digits.</p>
<pre><code>System.print(Faker.numerify("###-###-####")) // e.g., "415-555-2938"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.letterify</span>(<span class="param">format</span>) &rarr; <span class="type">String</span>
</div>
<p>Replaces <code>?</code> characters with random lowercase letters.</p>
<pre><code>System.print(Faker.letterify("???-????")) // e.g., "abc-defg"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.bothify</span>(<span class="param">format</span>) &rarr; <span class="type">String</span>
</div>
<p>Replaces both <code>#</code> with digits and <code>?</code> with letters.</p>
<pre><code>System.print(Faker.bothify("??-###")) // e.g., "ab-123"</code></pre>
<h2 id="person">Person &amp; Names</h2>
<div class="method-signature">
<span class="method-name">Faker.firstName</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random first name. Gender-specific variants: <code>firstNameMale()</code>, <code>firstNameFemale()</code>.</p>
<pre><code>System.print(Faker.firstName()) // e.g., "Emily"
System.print(Faker.firstNameMale()) // e.g., "James"
System.print(Faker.firstNameFemale()) // e.g., "Sarah"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.lastName</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random last name.</p>
<pre><code>System.print(Faker.lastName()) // e.g., "Johnson"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.name</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a full name (first + last). Variants: <code>nameMale()</code>, <code>nameFemale()</code>.</p>
<pre><code>System.print(Faker.name()) // e.g., "Emily Johnson"
System.print(Faker.nameMale()) // e.g., "James Smith"
System.print(Faker.nameFemale()) // e.g., "Sarah Williams"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.prefix</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a name prefix (Mr., Mrs., Ms., Miss, Dr.). Alias: <code>namePrefix()</code>.</p>
<pre><code>System.print(Faker.prefix()) // e.g., "Dr."</code></pre>
<div class="method-signature">
<span class="method-name">Faker.suffix</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a name suffix (Jr., Sr., MD, PhD, etc.). Alias: <code>nameSuffix()</code>.</p>
<pre><code>System.print(Faker.suffix()) // e.g., "Jr."</code></pre>
<div class="method-signature">
<span class="method-name">Faker.gender</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns "Male" or "Female". Alias: <code>sex()</code>.</p>
<pre><code>System.print(Faker.gender()) // "Male" or "Female"</code></pre>
<h2 id="address">Address &amp; Location</h2>
<div class="method-signature">
<span class="method-name">Faker.address</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a full US-style address.</p>
<pre><code>System.print(Faker.address()) // e.g., "1234 Oak Street, Denver, CO 80201"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.streetAddress</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a street address (number + street name).</p>
<pre><code>System.print(Faker.streetAddress()) // e.g., "1234 Oak Street"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.streetName</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a street name with suffix.</p>
<pre><code>System.print(Faker.streetName()) // e.g., "Oak Boulevard"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.buildingNumber</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random building number (1-9999).</p>
<pre><code>System.print(Faker.buildingNumber()) // e.g., "4521"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.city</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a US city name.</p>
<pre><code>System.print(Faker.city()) // e.g., "Portland"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.state</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a US state name. Aliases: <code>stateFull()</code>, <code>stateName()</code>.</p>
<pre><code>System.print(Faker.state()) // e.g., "California"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.stateAbbr</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a US state abbreviation.</p>
<pre><code>System.print(Faker.stateAbbr()) // e.g., "CA"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.country</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a country name. Alias: <code>countryName()</code>.</p>
<pre><code>System.print(Faker.country()) // e.g., "Germany"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.countryCode</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a two-letter country code.</p>
<pre><code>System.print(Faker.countryCode()) // e.g., "DE"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.zipCode</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a 5-digit US ZIP code. Alias: <code>postcode()</code>.</p>
<pre><code>System.print(Faker.zipCode()) // e.g., "90210"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.latitude</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a random latitude (-90 to 90) with 6 decimal places.</p>
<pre><code>System.print(Faker.latitude()) // e.g., 40.712776</code></pre>
<div class="method-signature">
<span class="method-name">Faker.latitude</span>(<span class="param">min</span>, <span class="param">max</span>) &rarr; <span class="type">Num</span>
</div>
<p>Returns a latitude within a specific range.</p>
<pre><code>System.print(Faker.latitude(30, 50)) // e.g., 42.358429</code></pre>
<div class="method-signature">
<span class="method-name">Faker.longitude</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a random longitude (-180 to 180) with 6 decimal places.</p>
<pre><code>System.print(Faker.longitude()) // e.g., -74.005974</code></pre>
<div class="method-signature">
<span class="method-name">Faker.longitude</span>(<span class="param">min</span>, <span class="param">max</span>) &rarr; <span class="type">Num</span>
</div>
<p>Returns a longitude within a specific range.</p>
<pre><code>System.print(Faker.longitude(-125, -65)) // e.g., -87.629799</code></pre>
<h2 id="internet">Internet &amp; Network</h2>
<div class="method-signature">
<span class="method-name">Faker.email</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random email address.</p>
<pre><code>System.print(Faker.email()) // e.g., "john.smith42@gmail.com"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.exampleEmail</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns an email using example.com/org/net domains (safe for testing).</p>
<pre><code>System.print(Faker.exampleEmail()) // e.g., "alice_jones@example.org"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.username</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random username in various formats.</p>
<pre><code>System.print(Faker.username()) // e.g., "john.smith", "emily_42"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.password</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a 12-character random password.</p>
<pre><code>System.print(Faker.password()) // e.g., "aB3$kLm9@pQr"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.password</span>(<span class="param">length</span>) &rarr; <span class="type">String</span>
</div>
<p>Returns a password of specified length.</p>
<pre><code>System.print(Faker.password(20)) // 20-character password</code></pre>
<div class="method-signature">
<span class="method-name">Faker.url</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random URL.</p>
<pre><code>System.print(Faker.url()) // e.g., "https://smith.io"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.domainName</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a domain name.</p>
<pre><code>System.print(Faker.domainName()) // e.g., "johnson.com"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.domainWord</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a domain word (without TLD).</p>
<pre><code>System.print(Faker.domainWord()) // e.g., "smith"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.tld</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a top-level domain. Alias: <code>topLevelDomain()</code>.</p>
<pre><code>System.print(Faker.tld()) // e.g., "com", "io", "org"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.protocol</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns "http" or "https".</p>
<pre><code>System.print(Faker.protocol()) // "http" or "https"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.ipv4</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random IPv4 address.</p>
<pre><code>System.print(Faker.ipv4()) // e.g., "192.168.45.123"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.ipv6</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random IPv6 address.</p>
<pre><code>System.print(Faker.ipv6()) // e.g., "2001:0db8:85a3:0000:0000:8a2e:0370:7334"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.macAddress</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random MAC address.</p>
<pre><code>System.print(Faker.macAddress()) // e.g., "00:1a:2b:3c:4d:5e"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.port</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a random port number (1-65535).</p>
<pre><code>System.print(Faker.port()) // e.g., 8080</code></pre>
<div class="method-signature">
<span class="method-name">Faker.hostname</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a hostname with subdomain.</p>
<pre><code>System.print(Faker.hostname()) // e.g., "server-42.company.com"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.httpMethod</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random HTTP method.</p>
<pre><code>System.print(Faker.httpMethod()) // "GET", "POST", "PUT", etc.</code></pre>
<div class="method-signature">
<span class="method-name">Faker.httpStatusCode</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a common HTTP status code.</p>
<pre><code>System.print(Faker.httpStatusCode()) // 200, 404, 500, etc.</code></pre>
<div class="method-signature">
<span class="method-name">Faker.userAgent</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a realistic browser user agent string.</p>
<pre><code>System.print(Faker.userAgent()) // e.g., "Mozilla/5.0 (Windows NT 10.0..."</code></pre>
<h2 id="datetime">Date &amp; Time</h2>
<div class="method-signature">
<span class="method-name">Faker.date</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random date within the past 10 years in YYYY-MM-DD format.</p>
<pre><code>System.print(Faker.date()) // e.g., "2021-07-15"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.dateTime</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random datetime within the past 10 years in ISO 8601 format.</p>
<pre><code>System.print(Faker.dateTime()) // e.g., "2022-03-14T09:26:53Z"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.pastDate</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a date within the past year. Accepts optional years parameter.</p>
<pre><code>System.print(Faker.pastDate()) // Within past year
System.print(Faker.pastDate(5)) // Within past 5 years</code></pre>
<div class="method-signature">
<span class="method-name">Faker.futureDate</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a date within the next year. Accepts optional years parameter.</p>
<pre><code>System.print(Faker.futureDate()) // Within next year
System.print(Faker.futureDate(2)) // Within next 2 years</code></pre>
<div class="method-signature">
<span class="method-name">Faker.dateBetween</span>(<span class="param">start</span>, <span class="param">end</span>) &rarr; <span class="type">String</span>
</div>
<p>Returns a random date between two DateTime objects.</p>
<pre><code>import "datetime" for DateTime, Duration
var start = DateTime.now() - Duration.fromDays(30)
var end = DateTime.now()
System.print(Faker.dateBetween(start, end))</code></pre>
<div class="method-signature">
<span class="method-name">Faker.dateOfBirth</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a birth date for someone aged 18-80. Accepts optional min/max age.</p>
<pre><code>System.print(Faker.dateOfBirth()) // Age 18-80
System.print(Faker.dateOfBirth(21, 35)) // Age 21-35</code></pre>
<div class="method-signature">
<span class="method-name">Faker.year</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a random year (1950-2025).</p>
<pre><code>System.print(Faker.year()) // e.g., 1987</code></pre>
<div class="method-signature">
<span class="method-name">Faker.month</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a random month number (1-12).</p>
<pre><code>System.print(Faker.month()) // e.g., 7</code></pre>
<div class="method-signature">
<span class="method-name">Faker.monthName</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a month name.</p>
<pre><code>System.print(Faker.monthName()) // e.g., "July"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.dayOfWeek</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a day of the week.</p>
<pre><code>System.print(Faker.dayOfWeek()) // e.g., "Wednesday"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.dayOfMonth</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a random day (1-28).</p>
<pre><code>System.print(Faker.dayOfMonth()) // e.g., 15</code></pre>
<div class="method-signature">
<span class="method-name">Faker.time</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random time in HH:MM:SS format.</p>
<pre><code>System.print(Faker.time()) // e.g., "14:32:07"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.hour</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a random hour (0-23). Related: <code>minute()</code>, <code>second()</code>, <code>amPm()</code>.</p>
<pre><code>System.print(Faker.hour()) // e.g., 14
System.print(Faker.minute()) // e.g., 32
System.print(Faker.second()) // e.g., 45
System.print(Faker.amPm()) // "AM" or "PM"</code></pre>
<h2 id="text">Text &amp; Lorem</h2>
<div class="method-signature">
<span class="method-name">Faker.word</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random lorem ipsum word.</p>
<pre><code>System.print(Faker.word()) // e.g., "consectetur"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.words</span>(<span class="param">count</span>) &rarr; <span class="type">List</span>
</div>
<p>Returns a list of random words.</p>
<pre><code>System.print(Faker.words(5)) // ["lorem", "ipsum", "dolor", "sit", "amet"]</code></pre>
<div class="method-signature">
<span class="method-name">Faker.sentence</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a sentence (5-12 words). Accepts optional word count.</p>
<pre><code>System.print(Faker.sentence()) // Random length
System.print(Faker.sentence(8)) // Exactly 8 words</code></pre>
<div class="method-signature">
<span class="method-name">Faker.sentences</span>(<span class="param">count</span>) &rarr; <span class="type">String</span>
</div>
<p>Returns multiple sentences joined with spaces.</p>
<pre><code>System.print(Faker.sentences(3)) // Three sentences</code></pre>
<div class="method-signature">
<span class="method-name">Faker.paragraph</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a paragraph (3-6 sentences). Accepts optional sentence count.</p>
<pre><code>System.print(Faker.paragraph()) // Random length
System.print(Faker.paragraph(5)) // Exactly 5 sentences</code></pre>
<div class="method-signature">
<span class="method-name">Faker.paragraphs</span>(<span class="param">count</span>) &rarr; <span class="type">String</span>
</div>
<p>Returns multiple paragraphs separated by double newlines.</p>
<pre><code>System.print(Faker.paragraphs(3)) // Three paragraphs</code></pre>
<div class="method-signature">
<span class="method-name">Faker.text</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns approximately 200 characters of text. Accepts optional max length.</p>
<pre><code>System.print(Faker.text()) // ~200 characters
System.print(Faker.text(500)) // ~500 characters</code></pre>
<div class="method-signature">
<span class="method-name">Faker.slug</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a URL-friendly slug (3 words). Accepts optional word count.</p>
<pre><code>System.print(Faker.slug()) // e.g., "lorem-ipsum-dolor"
System.print(Faker.slug(5)) // e.g., "lorem-ipsum-dolor-sit-amet"</code></pre>
<h2 id="colors">Colors</h2>
<div class="method-signature">
<span class="method-name">Faker.colorName</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a color name.</p>
<pre><code>System.print(Faker.colorName()) // e.g., "Crimson"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.hexColor</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a hex color code.</p>
<pre><code>System.print(Faker.hexColor()) // e.g., "#a3c2f0"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.rgbColor</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns an RGB color string. Alias: <code>rgb()</code>.</p>
<pre><code>System.print(Faker.rgbColor()) // e.g., "rgb(163, 194, 240)"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.rgbaCssColor</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns an RGBA color string with random alpha.</p>
<pre><code>System.print(Faker.rgbaCssColor()) // e.g., "rgba(163, 194, 240, 0.75)"</code></pre>
<h2 id="company">Company &amp; Job</h2>
<div class="method-signature">
<span class="method-name">Faker.company</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a company name. Alias: <code>companyName()</code>.</p>
<pre><code>System.print(Faker.company()) // e.g., "Smith Industries"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.companySuffix</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a company suffix.</p>
<pre><code>System.print(Faker.companySuffix()) // e.g., "LLC", "Inc.", "Corp."</code></pre>
<div class="method-signature">
<span class="method-name">Faker.job</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a job title. Alias: <code>jobTitle()</code>.</p>
<pre><code>System.print(Faker.job()) // e.g., "Software Engineer"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.jobDescriptor</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a job level descriptor.</p>
<pre><code>System.print(Faker.jobDescriptor()) // e.g., "Senior", "Lead", "Principal"</code></pre>
<h2 id="commerce">Commerce &amp; Products</h2>
<div class="method-signature">
<span class="method-name">Faker.product</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a product name. Alias: <code>productName()</code>.</p>
<pre><code>System.print(Faker.product()) // e.g., "Elegant Steel Chair"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.productCategory</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a product category.</p>
<pre><code>System.print(Faker.productCategory()) // e.g., "Electronics"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.price</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a price (1-1000) with 2 decimal places. Accepts optional min/max.</p>
<pre><code>System.print(Faker.price()) // e.g., 49.99
System.print(Faker.price(10, 50)) // e.g., 24.95</code></pre>
<div class="method-signature">
<span class="method-name">Faker.currency</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a currency code.</p>
<pre><code>System.print(Faker.currency()) // e.g., "USD", "EUR", "GBP"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.currencyName</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a currency name.</p>
<pre><code>System.print(Faker.currencyName()) // e.g., "US Dollar"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.currencySymbol</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a currency symbol.</p>
<pre><code>System.print(Faker.currencySymbol()) // e.g., "$", "€", "£"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.creditCardType</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a credit card type.</p>
<pre><code>System.print(Faker.creditCardType()) // e.g., "Visa", "Mastercard"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.creditCardNumber</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a formatted credit card number.</p>
<pre><code>System.print(Faker.creditCardNumber()) // e.g., "4123-4567-8901-234"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.creditCardCVV</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a 3-digit CVV code.</p>
<pre><code>System.print(Faker.creditCardCVV()) // e.g., "123"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.creditCardExpiryDate</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns an expiry date in MM/YY format.</p>
<pre><code>System.print(Faker.creditCardExpiryDate()) // e.g., "09/27"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.phoneNumber</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a US-format phone number. Alias: <code>phone()</code>.</p>
<pre><code>System.print(Faker.phoneNumber()) // e.g., "(415) 555-1234"</code></pre>
<h2 id="banking">Banking &amp; Finance</h2>
<div class="method-signature">
<span class="method-name">Faker.iban</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns an IBAN (International Bank Account Number).</p>
<pre><code>System.print(Faker.iban()) // e.g., "DE89370400440532013000"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.accountNumber</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a 10-digit account number. Accepts optional length.</p>
<pre><code>System.print(Faker.accountNumber()) // 10 digits
System.print(Faker.accountNumber(12)) // 12 digits</code></pre>
<div class="method-signature">
<span class="method-name">Faker.routingNumber</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a 9-digit routing number.</p>
<pre><code>System.print(Faker.routingNumber()) // e.g., "123456789"</code></pre>
<h2 id="files">Files &amp; Hashes</h2>
<div class="method-signature">
<span class="method-name">Faker.fileName</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a file name with extension.</p>
<pre><code>System.print(Faker.fileName()) // e.g., "document.pdf"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.fileExtension</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a file extension.</p>
<pre><code>System.print(Faker.fileExtension()) // e.g., "pdf", "jpg", "txt"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.mimeType</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a MIME type.</p>
<pre><code>System.print(Faker.mimeType()) // e.g., "application/json"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.semver</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a semantic version string.</p>
<pre><code>System.print(Faker.semver()) // e.g., "2.14.3"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.uuid</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a UUID v4.</p>
<pre><code>System.print(Faker.uuid()) // e.g., "550e8400-e29b-41d4-a716-446655440000"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.md5</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns an MD5 hash string.</p>
<pre><code>System.print(Faker.md5()) // 32-character hex string</code></pre>
<div class="method-signature">
<span class="method-name">Faker.sha1</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a SHA-1 hash string.</p>
<pre><code>System.print(Faker.sha1()) // 40-character hex string</code></pre>
<div class="method-signature">
<span class="method-name">Faker.sha256</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a SHA-256 hash string.</p>
<pre><code>System.print(Faker.sha256()) // 64-character hex string</code></pre>
<h2 id="utilities">Utilities</h2>
<div class="method-signature">
<span class="method-name">Faker.digit</span>() &rarr; <span class="type">Num</span>
</div>
<p>Returns a random digit (0-9). Alias: <code>randomDigit()</code>.</p>
<pre><code>System.print(Faker.digit()) // 0-9</code></pre>
<div class="method-signature">
<span class="method-name">Faker.digits</span>(<span class="param">count</span>) &rarr; <span class="type">List</span>
</div>
<p>Returns a list of random digits.</p>
<pre><code>System.print(Faker.digits(4)) // e.g., [1, 4, 7, 2]</code></pre>
<div class="method-signature">
<span class="method-name">Faker.letter</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns a random lowercase letter.</p>
<pre><code>System.print(Faker.letter()) // e.g., "k"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.letters</span>(<span class="param">count</span>) &rarr; <span class="type">String</span>
</div>
<p>Returns a string of random lowercase letters.</p>
<pre><code>System.print(Faker.letters(6)) // e.g., "xkjmvq"</code></pre>
<div class="method-signature">
<span class="method-name">Faker.shuffle</span>(<span class="param">list</span>) &rarr; <span class="type">List</span>
</div>
<p>Returns a shuffled copy of the list.</p>
<pre><code>var nums = [1, 2, 3, 4, 5]
System.print(Faker.shuffle(nums)) // e.g., [3, 1, 5, 2, 4]</code></pre>
<div class="method-signature">
<span class="method-name">Faker.profile</span>() &rarr; <span class="type">Map</span>
</div>
<p>Returns a complete user profile with username, name, email, address, phone, job, company, and birthdate.</p>
<pre><code>var user = Faker.profile()
System.print(user["name"])
System.print(user["email"])
System.print(user["company"])</code></pre>
<div class="method-signature">
<span class="method-name">Faker.simpleProfile</span>() &rarr; <span class="type">Map</span>
</div>
<p>Returns a basic profile with username, name, email, and address.</p>
<pre><code>var user = Faker.simpleProfile()
System.print(user["name"])
System.print(user["email"])</code></pre>
<div class="method-signature">
<span class="method-name">Faker.locale</span>() &rarr; <span class="type">String</span>
</div>
<p>Returns the current locale (always "en_US").</p>
<pre><code>System.print(Faker.locale()) // "en_US"</code></pre>
<h2>Examples</h2>
<h3>Seeding for Reproducible Tests</h3>
<pre><code>import "faker" for Faker
Faker.seed(42)
var user1 = Faker.name()
var user2 = Faker.name()
Faker.seed(42)
System.print(Faker.name() == user1) // true
System.print(Faker.name() == user2) // true</code></pre>
<h3>Generating Test Users</h3>
<pre><code>import "faker" for Faker
import "json" for Json
var users = []
for (i in 0...5) {
users.add({
"id": Faker.uuid(),
"name": Faker.name(),
"email": Faker.email(),
"age": Faker.randomInt(18, 65),
"active": Faker.boolean()
})
}
System.print(Json.stringify(users, 2))</code></pre>
<h3>Creating Product Catalog</h3>
<pre><code>import "faker" for Faker
for (i in 0...3) {
System.print("Product: %(Faker.product())")
System.print("Category: %(Faker.productCategory())")
System.print("Price: %(Faker.currencySymbol())%(Faker.price())")
System.print("---")
}</code></pre>
<h3>Generating Addresses</h3>
<pre><code>import "faker" for Faker
for (i in 0...3) {
System.print(Faker.address())
System.print(" Lat: %(Faker.latitude())")
System.print(" Lng: %(Faker.longitude())")
System.print("")
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>When unseeded, Faker uses cryptographically secure random numbers from the <code>crypto</code> module. When seeded, it uses a deterministic linear congruential generator for reproducibility.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Use seeding in tests to ensure consistent, reproducible test data. Call <code>Faker.reset()</code> to return to random mode for production use.</p>
</div>
{% endblock %}
+255
View File
@@ -0,0 +1,255 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "fswatch" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "fswatch"}] %}
{% set prev_page = {"url": "api/faker.html", "title": "faker"} %}
{% set next_page = {"url": "api/html.html", "title": "html"} %}
{% block article %}
<h1>fswatch</h1>
<p>The <code>fswatch</code> module provides file system watching capabilities, allowing scripts to monitor files and directories for changes in real-time. Built on libuv's filesystem events.</p>
<pre><code>import "fswatch" for FileWatcher, FsEvent</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#filewatcher-class">FileWatcher Class</a></li>
<li><a href="#fsevent-class">FsEvent Class</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="filewatcher-class">FileWatcher Class</h2>
<div class="class-header">
<h3>FileWatcher</h3>
<p>Monitors a file or directory for changes</p>
</div>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">FileWatcher.new</span>(<span class="param">path</span>) &#8594; <span class="type">FileWatcher</span>
</div>
<p>Creates a new FileWatcher for the specified path. The path can be a file or directory.</p>
<ul class="param-list">
<li><span class="param-name">path</span> <span class="param-type">(String)</span> - Path to the file or directory to watch</li>
</ul>
<pre><code>var watcher = FileWatcher.new("./config.json")
var dirWatcher = FileWatcher.new("./src")</code></pre>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">start</span>(<span class="param">callback</span>)
</div>
<p>Starts watching for changes. The callback function is invoked with an FsEvent whenever a change is detected.</p>
<ul class="param-list">
<li><span class="param-name">callback</span> <span class="param-type">(Fn)</span> - Function that receives an FsEvent parameter</li>
</ul>
<pre><code>watcher.start { |event|
System.print("Changed: %(event.filename)")
}</code></pre>
<div class="method-signature">
<span class="method-name">stop</span>()
</div>
<p>Stops watching for changes.</p>
<pre><code>watcher.stop()</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">isActive</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns true if the watcher is currently active.</p>
<pre><code>if (watcher.isActive) {
System.print("Watcher is running")
}</code></pre>
<h2 id="fsevent-class">FsEvent Class</h2>
<div class="class-header">
<h3>FsEvent</h3>
<p>Represents a file system change event</p>
</div>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">filename</span> &#8594; <span class="type">String</span>
</div>
<p>The name of the file that changed. For directory watchers, this is the relative filename within the directory.</p>
<pre><code>System.print("File changed: %(event.filename)")</code></pre>
<div class="method-signature">
<span class="method-name">isRename</span> &#8594; <span class="type">Bool</span>
</div>
<p>True if the event is a rename/move operation. This includes file creation and deletion.</p>
<pre><code>if (event.isRename) {
System.print("File renamed/created/deleted")
}</code></pre>
<div class="method-signature">
<span class="method-name">isChange</span> &#8594; <span class="type">Bool</span>
</div>
<p>True if the event is a content change.</p>
<pre><code>if (event.isChange) {
System.print("File content modified")
}</code></pre>
<h2 id="examples">Examples</h2>
<h3>Watch a Single File</h3>
<pre><code>import "fswatch" for FileWatcher
var watcher = FileWatcher.new("./config.json")
watcher.start { |event|
System.print("Config changed!")
System.print(" Rename: %(event.isRename)")
System.print(" Change: %(event.isChange)")
}
System.print("Watching config.json for changes...")
System.print("Press Ctrl+C to stop")</code></pre>
<h3>Watch a Directory</h3>
<pre><code>import "fswatch" for FileWatcher
var watcher = FileWatcher.new("./src")
watcher.start { |event|
System.print("[%(event.filename)]")
if (event.isRename) {
System.print(" Created/Deleted/Renamed")
}
if (event.isChange) {
System.print(" Modified")
}
}
System.print("Watching ./src directory...")
System.print("Press Ctrl+C to stop")</code></pre>
<h3>Auto-Reload Configuration</h3>
<pre><code>import "fswatch" for FileWatcher
import "io" for File
import "json" for Json
var config = {}
var loadConfig = Fn.new {
var content = File.read("config.json")
config = Json.parse(content)
System.print("Config loaded: %(config)")
}
loadConfig.call()
var watcher = FileWatcher.new("./config.json")
watcher.start { |event|
System.print("Config file changed, reloading...")
loadConfig.call()
}
System.print("Running with auto-reload...")</code></pre>
<h3>Build on Change</h3>
<pre><code>import "fswatch" for FileWatcher
import "subprocess" for Subprocess
var watcher = FileWatcher.new("./src")
var build = Fn.new {
System.print("Building...")
var result = Subprocess.run("make", ["build"])
if (result.exitCode == 0) {
System.print("Build successful!")
} else {
System.print("Build failed!")
System.print(result.stderr)
}
}
watcher.start { |event|
if (event.filename.endsWith(".c") || event.filename.endsWith(".h")) {
System.print("%(event.filename) changed")
build.call()
}
}
System.print("Watching for source changes...")</code></pre>
<h3>Multiple Watchers</h3>
<pre><code>import "fswatch" for FileWatcher
var srcWatcher = FileWatcher.new("./src")
var testWatcher = FileWatcher.new("./test")
srcWatcher.start { |event|
System.print("[SRC] %(event.filename)")
}
testWatcher.start { |event|
System.print("[TEST] %(event.filename)")
}
System.print("Watching src/ and test/ directories...")</code></pre>
<h3>Debounced Watcher</h3>
<pre><code>import "fswatch" for FileWatcher
import "timer" for Timer
import "datetime" for DateTime
var lastChange = null
var debounceMs = 500
var watcher = FileWatcher.new("./src")
watcher.start { |event|
var now = DateTime.now()
if (lastChange == null || (now - lastChange).milliseconds > debounceMs) {
lastChange = now
System.print("Change detected: %(event.filename)")
}
}
System.print("Watching with %(debounceMs)ms debounce...")</code></pre>
<h3>Graceful Shutdown</h3>
<pre><code>import "fswatch" for FileWatcher
import "signal" for Signal
var watcher = FileWatcher.new("./data")
watcher.start { |event|
System.print("%(event.filename) changed")
}
Signal.trap(Signal.SIGINT) {
System.print("\nStopping watcher...")
watcher.stop()
System.print("Watcher stopped: %(watcher.isActive)")
}
System.print("Watching ./data (Ctrl+C to stop)")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>File system event behavior varies by operating system. On some platforms, multiple events may be delivered for a single change, or event types may overlap (both <code>isRename</code> and <code>isChange</code> true).</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>When watching directories, the <code>filename</code> property contains the relative path of the changed file within the watched directory, not the full path.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Watching a large directory tree may consume significant system resources. Consider watching specific subdirectories when possible.</p>
</div>
{% endblock %}
+185
View File
@@ -0,0 +1,185 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "html" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "html"}] %}
{% set prev_page = {"url": "api/fswatch.html", "title": "fswatch"} %}
{% set next_page = {"url": "api/http.html", "title": "http"} %}
{% block article %}
<h1>html</h1>
<p>The <code>html</code> module provides utilities for HTML and URL encoding/decoding, slug generation, and query string handling.</p>
<pre><code>import "html" for Html</code></pre>
<h2>Html Class</h2>
<div class="class-header">
<h3>Html</h3>
<p>HTML and URL encoding utilities</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Html.urlencode</span>(<span class="param">string</span>) → <span class="type">String</span>
</div>
<p>URL-encodes a string for use in URLs and query parameters. Spaces become <code>+</code>, special characters become percent-encoded.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to encode</li>
<li><span class="returns">Returns:</span> URL-encoded string</li>
</ul>
<pre><code>System.print(Html.urlencode("hello world")) // hello+world
System.print(Html.urlencode("a=b&c=d")) // a\%3Db\%26c\%3Dd
System.print(Html.urlencode("café")) // caf\%C3\%A9</code></pre>
<div class="method-signature">
<span class="method-name">Html.urldecode</span>(<span class="param">string</span>) → <span class="type">String</span>
</div>
<p>Decodes a URL-encoded string. Converts <code>+</code> to space and decodes percent-encoded characters.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The URL-encoded string</li>
<li><span class="returns">Returns:</span> Decoded string</li>
</ul>
<pre><code>System.print(Html.urldecode("hello+world")) // hello world
System.print(Html.urldecode("caf\%C3\%A9")) // café</code></pre>
<div class="method-signature">
<span class="method-name">Html.slugify</span>(<span class="param">string</span>) → <span class="type">String</span>
</div>
<p>Converts a string to a URL-friendly slug. Lowercase, alphanumeric characters with hyphens.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to slugify</li>
<li><span class="returns">Returns:</span> URL-friendly slug</li>
</ul>
<pre><code>System.print(Html.slugify("Hello World")) // hello-world
System.print(Html.slugify("My Blog Post!")) // my-blog-post
System.print(Html.slugify(" Multiple Spaces ")) // multiple-spaces</code></pre>
<div class="method-signature">
<span class="method-name">Html.quote</span>(<span class="param">string</span>) → <span class="type">String</span>
</div>
<p>Escapes HTML special characters to prevent XSS attacks.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to escape</li>
<li><span class="returns">Returns:</span> HTML-escaped string</li>
</ul>
<pre><code>System.print(Html.quote("&lt;script&gt;alert('xss')&lt;/script&gt;"))
// &amp;lt;script&amp;gt;alert(&amp;#39;xss&amp;#39;)&amp;lt;/script&amp;gt;
System.print(Html.quote("A &amp; B")) // A &amp;amp; B
System.print(Html.quote("\"quoted\"")) // &amp;quot;quoted&amp;quot;</code></pre>
<div class="method-signature">
<span class="method-name">Html.unquote</span>(<span class="param">string</span>) → <span class="type">String</span>
</div>
<p>Unescapes HTML entities back to their original characters.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The HTML-escaped string</li>
<li><span class="returns">Returns:</span> Unescaped string</li>
</ul>
<pre><code>System.print(Html.unquote("&amp;lt;div&amp;gt;")) // &lt;div&gt;
System.print(Html.unquote("A &amp;amp; B")) // A &amp; B</code></pre>
<div class="method-signature">
<span class="method-name">Html.encodeParams</span>(<span class="param">params</span>) → <span class="type">String</span>
</div>
<p>Encodes a map of key-value pairs into a URL query string.</p>
<ul class="param-list">
<li><span class="param-name">params</span> <span class="param-type">(Map)</span> - Map of parameters to encode</li>
<li><span class="returns">Returns:</span> URL-encoded query string</li>
</ul>
<pre><code>var params = {"name": "John Doe", "age": 30}
System.print(Html.encodeParams(params)) // name=John+Doe&amp;age=30
var search = {"q": "wren lang", "page": 1}
System.print(Html.encodeParams(search)) // q=wren+lang&amp;page=1</code></pre>
<div class="method-signature">
<span class="method-name">Html.decodeParams</span>(<span class="param">string</span>) → <span class="type">Map</span>
</div>
<p>Decodes a URL query string into a map of key-value pairs.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - URL-encoded query string</li>
<li><span class="returns">Returns:</span> Map of decoded parameters</li>
</ul>
<pre><code>var params = Html.decodeParams("name=John+Doe&amp;age=30")
System.print(params["name"]) // John Doe
System.print(params["age"]) // 30</code></pre>
<h2>Entity Reference</h2>
<table>
<tr>
<th>Character</th>
<th>Entity</th>
</tr>
<tr>
<td>&amp;</td>
<td>&amp;amp;</td>
</tr>
<tr>
<td>&lt;</td>
<td>&amp;lt;</td>
</tr>
<tr>
<td>&gt;</td>
<td>&amp;gt;</td>
</tr>
<tr>
<td>"</td>
<td>&amp;quot;</td>
</tr>
<tr>
<td>'</td>
<td>&amp;#39;</td>
</tr>
</table>
<h2>Examples</h2>
<h3>Building URLs</h3>
<pre><code>import "html" for Html
var baseUrl = "https://api.example.com/search"
var params = {
"query": "wren programming",
"limit": 10,
"offset": 0
}
var url = baseUrl + "?" + Html.encodeParams(params)
System.print(url)
// https://api.example.com/search?query=wren+programming&amp;limit=10&amp;offset=0</code></pre>
<h3>Safe HTML Output</h3>
<pre><code>import "html" for Html
var userInput = "&lt;script&gt;alert('xss')&lt;/script&gt;"
var safeHtml = "&lt;div class=\"comment\"&gt;" + Html.quote(userInput) + "&lt;/div&gt;"
System.print(safeHtml)</code></pre>
<h3>Generating Slugs for URLs</h3>
<pre><code>import "html" for Html
var title = "How to Build Web Apps with Wren!"
var slug = Html.slugify(title)
var url = "/blog/" + slug
System.print(url) // /blog/how-to-build-web-apps-with-wren</code></pre>
<h3>Parsing Query Strings</h3>
<pre><code>import "html" for Html
var queryString = "category=books&amp;sort=price&amp;order=asc"
var params = Html.decodeParams(queryString)
for (key in params.keys) {
System.print("%(key): %(params[key])")
}</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Always use <code>Html.quote()</code> when inserting user-provided content into HTML to prevent cross-site scripting (XSS) attacks.</p>
</div>
{% endblock %}
+329
View File
@@ -0,0 +1,329 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "http" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "http"}] %}
{% set prev_page = {"url": "api/index.html", "title": "API Overview"} %}
{% set next_page = {"url": "api/websocket.html", "title": "websocket"} %}
{% block article %}
<h1>http</h1>
<p>The <code>http</code> module provides an HTTP client for making requests to web servers. It supports both HTTP and HTTPS, all common HTTP methods, custom headers, and JSON handling.</p>
<pre><code>import "http" for Http, HttpResponse, Url</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#http-class">Http Class</a></li>
<li><a href="#httpresponse-class">HttpResponse Class</a></li>
<li><a href="#url-class">Url Class</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="http-class">Http Class</h2>
<p>The main class for making HTTP requests. All methods are static.</p>
<div class="class-header">
<h3>Http</h3>
<p>Static class for making HTTP requests</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Http.get</span>(<span class="param">url</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP GET request.</p>
<ul class="param-list">
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to request</li>
<li><span class="returns">Returns:</span> HttpResponse object</li>
</ul>
<pre><code>var response = Http.get("https://api.example.com/users")
System.print(response.body)</code></pre>
<div class="method-signature">
<span class="method-name">Http.get</span>(<span class="param">url</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP GET request with custom headers.</p>
<ul class="param-list">
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to request</li>
<li><span class="param-name">headers</span> <span class="param-type">(Map)</span> - Custom headers to include</li>
<li><span class="returns">Returns:</span> HttpResponse object</li>
</ul>
<pre><code>var headers = {"Authorization": "Bearer token123"}
var response = Http.get("https://api.example.com/me", headers)</code></pre>
<div class="method-signature">
<span class="method-name">Http.post</span>(<span class="param">url</span>, <span class="param">body</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP POST request with a body.</p>
<ul class="param-list">
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to post to</li>
<li><span class="param-name">body</span> <span class="param-type">(String|Map|List)</span> - Request body. Maps and Lists are JSON-encoded automatically.</li>
<li><span class="returns">Returns:</span> HttpResponse object</li>
</ul>
<pre><code>var data = {"name": "Alice", "email": "alice@example.com"}
var response = Http.post("https://api.example.com/users", data)</code></pre>
<div class="method-signature">
<span class="method-name">Http.post</span>(<span class="param">url</span>, <span class="param">body</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP POST request with a body and custom headers.</p>
<ul class="param-list">
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to post to</li>
<li><span class="param-name">body</span> <span class="param-type">(String|Map|List)</span> - Request body</li>
<li><span class="param-name">headers</span> <span class="param-type">(Map)</span> - Custom headers</li>
<li><span class="returns">Returns:</span> HttpResponse object</li>
</ul>
<div class="method-signature">
<span class="method-name">Http.put</span>(<span class="param">url</span>, <span class="param">body</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP PUT request.</p>
<div class="method-signature">
<span class="method-name">Http.put</span>(<span class="param">url</span>, <span class="param">body</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP PUT request with custom headers.</p>
<div class="method-signature">
<span class="method-name">Http.delete</span>(<span class="param">url</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP DELETE request.</p>
<div class="method-signature">
<span class="method-name">Http.delete</span>(<span class="param">url</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP DELETE request with custom headers.</p>
<div class="method-signature">
<span class="method-name">Http.patch</span>(<span class="param">url</span>, <span class="param">body</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP PATCH request.</p>
<div class="method-signature">
<span class="method-name">Http.patch</span>(<span class="param">url</span>, <span class="param">body</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs an HTTP PATCH request with custom headers.</p>
<div class="method-signature">
<span class="method-name">Http.request</span>(<span class="param">url</span>, <span class="param">method</span>, <span class="param">body</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
</div>
<p>Performs a custom HTTP request with full control over method, body, and headers.</p>
<ul class="param-list">
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to request</li>
<li><span class="param-name">method</span> <span class="param-type">(String)</span> - HTTP method (GET, POST, PUT, DELETE, PATCH, etc.)</li>
<li><span class="param-name">body</span> <span class="param-type">(String|Map|List|null)</span> - Request body or null</li>
<li><span class="param-name">headers</span> <span class="param-type">(Map)</span> - Custom headers</li>
<li><span class="returns">Returns:</span> HttpResponse object</li>
</ul>
<pre><code>var response = Http.request(
"https://api.example.com/users/1",
"OPTIONS",
null,
{}
)</code></pre>
<h2 id="httpresponse-class">HttpResponse Class</h2>
<p>Represents an HTTP response returned from a request.</p>
<div class="class-header">
<h3>HttpResponse</h3>
<p>HTTP response object with status, headers, and body</p>
</div>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">statusCode</span><span class="type">Num</span>
</div>
<p>The HTTP status code (e.g., 200, 404, 500).</p>
<div class="method-signature">
<span class="method-name">statusText</span><span class="type">String</span>
</div>
<p>The HTTP status text (e.g., "OK", "Not Found").</p>
<div class="method-signature">
<span class="method-name">headers</span><span class="type">Map</span>
</div>
<p>A map of response headers (header name to value).</p>
<div class="method-signature">
<span class="method-name">body</span><span class="type">String</span>
</div>
<p>The response body as a string.</p>
<div class="method-signature">
<span class="method-name">ok</span><span class="type">Bool</span>
</div>
<p>True if the status code is in the 200-299 range.</p>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">header</span>(<span class="param">name</span>) → <span class="type">String|null</span>
</div>
<p>Gets a header value by name (case-insensitive).</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Header name</li>
<li><span class="returns">Returns:</span> Header value or null if not found</li>
</ul>
<pre><code>var contentType = response.header("Content-Type")</code></pre>
<div class="method-signature">
<span class="method-name">json</span><span class="type">Map|List|null</span>
</div>
<p>Parses the response body as JSON and returns the result.</p>
<pre><code>var data = response.json
System.print(data["name"])</code></pre>
<h2 id="url-class">Url Class</h2>
<p>Utility class for parsing URLs into their components.</p>
<div class="class-header">
<h3>Url</h3>
<p>URL parser</p>
</div>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">Url.parse</span>(<span class="param">url</span>) → <span class="type">Url</span>
</div>
<p>Parses a URL string into its components.</p>
<pre><code>var url = Url.parse("https://example.com:8080/path?query=value")
System.print(url.scheme) // https
System.print(url.host) // example.com
System.print(url.port) // 8080
System.print(url.path) // /path
System.print(url.query) // query=value</code></pre>
<h3>Properties</h3>
<table>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>scheme</code></td>
<td>String</td>
<td>URL scheme (http, https)</td>
</tr>
<tr>
<td><code>host</code></td>
<td>String</td>
<td>Hostname</td>
</tr>
<tr>
<td><code>port</code></td>
<td>Num</td>
<td>Port number (80 for http, 443 for https by default)</td>
</tr>
<tr>
<td><code>path</code></td>
<td>String</td>
<td>URL path</td>
</tr>
<tr>
<td><code>query</code></td>
<td>String</td>
<td>Query string (without leading ?)</td>
</tr>
<tr>
<td><code>fullPath</code></td>
<td>String</td>
<td>Path with query string</td>
</tr>
</table>
<h2 id="examples">Examples</h2>
<h3>Simple GET Request</h3>
<pre><code>import "http" for Http
var response = Http.get("https://httpbin.org/get")
System.print("Status: %(response.statusCode)")
System.print("Body: %(response.body)")</code></pre>
<h3>POST with JSON Body</h3>
<pre><code>import "http" for Http
var data = {
"username": "alice",
"email": "alice@example.com"
}
var response = Http.post("https://httpbin.org/post", data)
if (response.ok) {
System.print("User created!")
System.print(response.json)
} else {
System.print("Error: %(response.statusCode)")
}</code></pre>
<h3>Custom Headers with Authentication</h3>
<pre><code>import "http" for Http
var headers = {
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs...",
"Accept": "application/json"
}
var response = Http.get("https://api.example.com/me", headers)
var user = response.json
System.print("Hello, %(user["name"])!")</code></pre>
<h3>Error Handling</h3>
<pre><code>import "http" for Http
var fiber = Fiber.new {
var response = Http.get("https://invalid-domain.example")
return response
}
var result = fiber.try()
if (fiber.error) {
System.print("Request failed: %(fiber.error)")
} else {
System.print("Got response: %(result.statusCode)")
}</code></pre>
<h3>Sending Form Data</h3>
<pre><code>import "http" for Http
var formData = "username=alice&password=secret"
var headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
var response = Http.post("https://example.com/login", formData, headers)</code></pre>
<h3>Checking Response Headers</h3>
<pre><code>import "http" for Http
var response = Http.get("https://example.com")
System.print("Content-Type: %(response.header("Content-Type"))")
System.print("Server: %(response.header("Server"))")
for (entry in response.headers) {
System.print("%(entry.key): %(entry.value)")
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>The HTTP module automatically handles HTTPS by using the TLS module. No additional configuration is needed for HTTPS URLs.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>When posting a Map or List, the module automatically sets <code>Content-Type: application/json</code> and JSON-encodes the body. For other content types, pass a string body and set the Content-Type header explicitly.</p>
</div>
{% endblock %}
+349
View File
@@ -0,0 +1,349 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "API Reference" %}
{% set breadcrumb = [{"title": "API Reference"}] %}
{% set prev_page = {"url": "language/modules.html", "title": "Modules"} %}
{% set next_page = {"url": "api/http.html", "title": "http"} %}
{% block article %}
<h1>API Reference</h1>
<p>Wren-CLI provides 32 built-in modules covering networking, file I/O, data processing, system operations, and more. All modules are imported using the <code>import</code> statement.</p>
<pre><code>import "http" for Http
import "json" for Json
import "io" for File</code></pre>
<h2>Core Types</h2>
<p>Extended methods on built-in types, available without imports.</p>
<div class="card-grid">
<div class="card">
<h3><a href="string.html">String</a></h3>
<p>Case conversion, character testing, padding, splitting, reversing, and more on all strings.</p>
</div>
<div class="card">
<h3><a href="number.html">Num</a></h3>
<p>Query predicates, hyperbolic functions, base conversion, formatting, GCD/LCM, and more on all numbers.</p>
</div>
</div>
<h2>Networking</h2>
<p>Modules for HTTP, WebSocket, and low-level network operations.</p>
<div class="card-grid">
<div class="card">
<h3><a href="http.html">http</a></h3>
<p>HTTP client for making GET, POST, PUT, DELETE, and PATCH requests. Supports HTTPS.</p>
</div>
<div class="card">
<h3><a href="websocket.html">websocket</a></h3>
<p>WebSocket client and server implementation with full protocol support.</p>
</div>
<div class="card">
<h3><a href="tls.html">tls</a></h3>
<p>TLS/SSL socket wrapper for encrypted connections using OpenSSL.</p>
</div>
<div class="card">
<h3><a href="net.html">net</a></h3>
<p>Low-level TCP sockets and servers for custom network protocols.</p>
</div>
<div class="card">
<h3><a href="udp.html">udp</a></h3>
<p>UDP datagram sockets for connectionless networking.</p>
</div>
<div class="card">
<h3><a href="dns.html">dns</a></h3>
<p>DNS resolution for hostname to IP address lookups.</p>
</div>
</div>
<h2>Data Processing</h2>
<p>Modules for parsing, encoding, and transforming data.</p>
<div class="card-grid">
<div class="card">
<h3><a href="json.html">json</a></h3>
<p>Parse and stringify JSON data with pretty-printing support.</p>
</div>
<div class="card">
<h3><a href="base64.html">base64</a></h3>
<p>Base64 encoding and decoding for binary-to-text conversion.</p>
</div>
<div class="card">
<h3><a href="regex.html">regex</a></h3>
<p>Regular expression matching, replacement, and splitting.</p>
</div>
<div class="card">
<h3><a href="jinja.html">jinja</a></h3>
<p>Jinja2-compatible template engine with filters, inheritance, and macros.</p>
</div>
<div class="card">
<h3><a href="crypto.html">crypto</a></h3>
<p>Cryptographic hashing (MD5, SHA-1, SHA-256) and random byte generation.</p>
</div>
<div class="card">
<h3><a href="uuid.html">uuid</a></h3>
<p>UUID generation and validation with v4 support.</p>
</div>
<div class="card">
<h3><a href="html.html">html</a></h3>
<p>HTML/URL encoding, decoding, slug generation, and query string handling.</p>
</div>
<div class="card">
<h3><a href="markdown.html">markdown</a></h3>
<p>Convert Markdown text to HTML with safe mode support.</p>
</div>
</div>
<h2>System</h2>
<p>Modules for interacting with the operating system and environment.</p>
<div class="card-grid">
<div class="card">
<h3><a href="os.html">os</a></h3>
<p>Platform information, process details, and command-line arguments.</p>
</div>
<div class="card">
<h3><a href="env.html">env</a></h3>
<p>Read and write environment variables.</p>
</div>
<div class="card">
<h3><a href="signal.html">signal</a></h3>
<p>Handle Unix signals like SIGINT, SIGTERM, and SIGHUP.</p>
</div>
<div class="card">
<h3><a href="subprocess.html">subprocess</a></h3>
<p>Run external commands and capture their output.</p>
</div>
<div class="card">
<h3><a href="io.html">io</a></h3>
<p>File and directory operations, stdin/stdout handling.</p>
</div>
<div class="card">
<h3><a href="pathlib.html">pathlib</a></h3>
<p>Object-oriented filesystem paths with glob, walk, and tree operations.</p>
</div>
<div class="card">
<h3><a href="sysinfo.html">sysinfo</a></h3>
<p>System information: CPU, memory, uptime, and network interfaces.</p>
</div>
<div class="card">
<h3><a href="fswatch.html">fswatch</a></h3>
<p>File system watching for monitoring file and directory changes.</p>
</div>
</div>
<h2>Data & Time</h2>
<p>Modules for databases, time, and scheduling.</p>
<div class="card-grid">
<div class="card">
<h3><a href="sqlite.html">sqlite</a></h3>
<p>SQLite database for persistent data storage with SQL queries.</p>
</div>
<div class="card">
<h3><a href="datetime.html">datetime</a></h3>
<p>Date and time manipulation with formatting and arithmetic.</p>
</div>
<div class="card">
<h3><a href="timer.html">timer</a></h3>
<p>Sleep, timeouts, and interval timers.</p>
</div>
<div class="card">
<h3><a href="math.html">math</a></h3>
<p>Mathematical functions like sqrt, sin, cos, and constants like PI.</p>
</div>
<div class="card">
<h3><a href="scheduler.html">scheduler</a></h3>
<p>Async fiber scheduling for non-blocking I/O operations.</p>
</div>
<div class="card">
<h3><a href="dataset.html">dataset</a></h3>
<p>Simple ORM for SQLite with automatic schema management.</p>
</div>
</div>
<h2>Application Development</h2>
<p>Modules for building web applications and command-line tools.</p>
<div class="card-grid">
<div class="card">
<h3><a href="web.html">web</a></h3>
<p>Web framework with routing, middleware, sessions, and HTTP client.</p>
</div>
<div class="card">
<h3><a href="argparse.html">argparse</a></h3>
<p>Command-line argument parsing with type conversion and help generation.</p>
</div>
<div class="card">
<h3><a href="wdantic.html">wdantic</a></h3>
<p>Data validation with schema definitions and built-in validators.</p>
</div>
</div>
<h2>Module Summary</h2>
<table>
<tr>
<th>Module</th>
<th>Main Classes</th>
<th>Description</th>
</tr>
<tr>
<td><a href="http.html">http</a></td>
<td>Http, HttpResponse, Url</td>
<td>HTTP/HTTPS client</td>
</tr>
<tr>
<td><a href="websocket.html">websocket</a></td>
<td>WebSocket, WebSocketServer, WebSocketMessage</td>
<td>WebSocket protocol</td>
</tr>
<tr>
<td><a href="tls.html">tls</a></td>
<td>TlsSocket</td>
<td>TLS/SSL sockets</td>
</tr>
<tr>
<td><a href="net.html">net</a></td>
<td>Socket, Server</td>
<td>TCP networking</td>
</tr>
<tr>
<td><a href="udp.html">udp</a></td>
<td>UdpSocket, UdpMessage</td>
<td>UDP networking</td>
</tr>
<tr>
<td><a href="dns.html">dns</a></td>
<td>Dns</td>
<td>DNS resolution</td>
</tr>
<tr>
<td><a href="json.html">json</a></td>
<td>Json</td>
<td>JSON parsing</td>
</tr>
<tr>
<td><a href="base64.html">base64</a></td>
<td>Base64</td>
<td>Base64 encoding</td>
</tr>
<tr>
<td><a href="regex.html">regex</a></td>
<td>Regex, Match</td>
<td>Regular expressions</td>
</tr>
<tr>
<td><a href="jinja.html">jinja</a></td>
<td>Environment, Template, DictLoader, FileSystemLoader</td>
<td>Template engine</td>
</tr>
<tr>
<td><a href="crypto.html">crypto</a></td>
<td>Crypto, Hash</td>
<td>Cryptography</td>
</tr>
<tr>
<td><a href="os.html">os</a></td>
<td>Process, Platform</td>
<td>OS information</td>
</tr>
<tr>
<td><a href="env.html">env</a></td>
<td>Env</td>
<td>Environment variables</td>
</tr>
<tr>
<td><a href="fswatch.html">fswatch</a></td>
<td>FileWatcher, FsEvent</td>
<td>File system watching</td>
</tr>
<tr>
<td><a href="signal.html">signal</a></td>
<td>Signal</td>
<td>Unix signals</td>
</tr>
<tr>
<td><a href="subprocess.html">subprocess</a></td>
<td>Subprocess</td>
<td>External processes</td>
</tr>
<tr>
<td><a href="sysinfo.html">sysinfo</a></td>
<td>SysInfo</td>
<td>System information</td>
</tr>
<tr>
<td><a href="sqlite.html">sqlite</a></td>
<td>Sqlite</td>
<td>SQLite database</td>
</tr>
<tr>
<td><a href="datetime.html">datetime</a></td>
<td>DateTime, Duration</td>
<td>Date/time handling</td>
</tr>
<tr>
<td><a href="timer.html">timer</a></td>
<td>Timer, TimerHandle</td>
<td>Timers, delays, and intervals</td>
</tr>
<tr>
<td><a href="io.html">io</a></td>
<td>File, Directory, Stdin, Stdout</td>
<td>File I/O</td>
</tr>
<tr>
<td><a href="pathlib.html">pathlib</a></td>
<td>Path, PurePath</td>
<td>Filesystem paths</td>
</tr>
<tr>
<td><a href="scheduler.html">scheduler</a></td>
<td>Scheduler</td>
<td>Async scheduling</td>
</tr>
<tr>
<td><a href="math.html">math</a></td>
<td>Math</td>
<td>Math functions</td>
</tr>
<tr>
<td><a href="uuid.html">uuid</a></td>
<td>Uuid</td>
<td>UUID generation</td>
</tr>
<tr>
<td><a href="html.html">html</a></td>
<td>Html</td>
<td>HTML/URL encoding</td>
</tr>
<tr>
<td><a href="argparse.html">argparse</a></td>
<td>ArgumentParser</td>
<td>CLI arguments</td>
</tr>
<tr>
<td><a href="wdantic.html">wdantic</a></td>
<td>Validator, Field, Schema, ValidationResult</td>
<td>Data validation</td>
</tr>
<tr>
<td><a href="dataset.html">dataset</a></td>
<td>Dataset, Table</td>
<td>Simple ORM</td>
</tr>
<tr>
<td><a href="markdown.html">markdown</a></td>
<td>Markdown</td>
<td>Markdown to HTML</td>
</tr>
<tr>
<td><a href="web.html">web</a></td>
<td>Application, Router, Request, Response, View, Session, Client</td>
<td>Web framework</td>
</tr>
</table>
{% endblock %}
+290
View File
@@ -0,0 +1,290 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "io" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "io"}] %}
{% set prev_page = {"url": "api/timer.html", "title": "timer"} %}
{% set next_page = {"url": "api/pathlib.html", "title": "pathlib"} %}
{% block article %}
<h1>io</h1>
<p>The <code>io</code> module provides file and directory operations, as well as stdin/stdout handling.</p>
<pre><code>import "io" for File, Directory, Stdin, Stdout, Stat</code></pre>
<h2>File Class</h2>
<div class="class-header">
<h3>File</h3>
<p>File operations</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">File.read</span>(<span class="param">path</span>) → <span class="type">String</span>
</div>
<p>Reads the entire contents of a file.</p>
<pre><code>var content = File.read("config.txt")
System.print(content)</code></pre>
<div class="method-signature">
<span class="method-name">File.write</span>(<span class="param">path</span>, <span class="param">content</span>)
</div>
<p>Writes content to a file (creates or overwrites).</p>
<pre><code>File.write("output.txt", "Hello, World!")</code></pre>
<div class="method-signature">
<span class="method-name">File.exists</span>(<span class="param">path</span>) → <span class="type">Bool</span>
</div>
<p>Returns true if the file exists.</p>
<pre><code>if (File.exists("config.txt")) {
System.print("Config found")
}</code></pre>
<div class="method-signature">
<span class="method-name">File.delete</span>(<span class="param">path</span>)
</div>
<p>Deletes a file.</p>
<div class="method-signature">
<span class="method-name">File.size</span>(<span class="param">path</span>) → <span class="type">Num</span>
</div>
<p>Returns the size of a file in bytes.</p>
<div class="method-signature">
<span class="method-name">File.copy</span>(<span class="param">source</span>, <span class="param">dest</span>)
</div>
<p>Copies a file from source to destination.</p>
<div class="method-signature">
<span class="method-name">File.rename</span>(<span class="param">oldPath</span>, <span class="param">newPath</span>)
</div>
<p>Renames a file within the same filesystem.</p>
<div class="method-signature">
<span class="method-name">File.move</span>(<span class="param">source</span>, <span class="param">dest</span>)
</div>
<p>Moves a file from source to destination. Uses libuv's async rename operation.</p>
<pre><code>File.move("old/location/file.txt", "new/location/file.txt")</code></pre>
<h2>Directory Class</h2>
<div class="class-header">
<h3>Directory</h3>
<p>Directory operations</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Directory.list</span>(<span class="param">path</span>) → <span class="type">List</span>
</div>
<p>Lists the contents of a directory.</p>
<pre><code>var files = Directory.list(".")
for (file in files) {
System.print(file)
}</code></pre>
<div class="method-signature">
<span class="method-name">Directory.exists</span>(<span class="param">path</span>) → <span class="type">Bool</span>
</div>
<p>Returns true if the directory exists.</p>
<div class="method-signature">
<span class="method-name">Directory.create</span>(<span class="param">path</span>)
</div>
<p>Creates a directory.</p>
<div class="method-signature">
<span class="method-name">Directory.delete</span>(<span class="param">path</span>)
</div>
<p>Deletes an empty directory.</p>
<h2>Stdin Class</h2>
<div class="class-header">
<h3>Stdin</h3>
<p>Standard input</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Stdin.readLine</span>() → <span class="type">String</span>
</div>
<p>Reads a line from standard input.</p>
<pre><code>System.write("Enter your name: ")
var name = Stdin.readLine()
System.print("Hello, %(name)!")</code></pre>
<div class="method-signature">
<span class="method-name">Stdin.read</span>() → <span class="type">String</span>
</div>
<p>Reads all available data from stdin.</p>
<h2>Stdout Class</h2>
<div class="class-header">
<h3>Stdout</h3>
<p>Standard output</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Stdout.flush</span>()
</div>
<p>Flushes the stdout buffer.</p>
<h2>Stat Class</h2>
<div class="class-header">
<h3>Stat</h3>
<p>File metadata and statistics</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Stat.path</span>(<span class="param">path</span>) &#8594; <span class="type">Stat</span>
</div>
<p>Returns a Stat object for the specified path.</p>
<pre><code>var stat = Stat.path("/etc/hosts")
System.print("Size: %(stat.size) bytes")</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">size</span> &#8594; <span class="type">Num</span>
</div>
<p>The size of the file in bytes.</p>
<div class="method-signature">
<span class="method-name">isFile</span> &#8594; <span class="type">Bool</span>
</div>
<p>True if the path is a regular file.</p>
<div class="method-signature">
<span class="method-name">isDirectory</span> &#8594; <span class="type">Bool</span>
</div>
<p>True if the path is a directory.</p>
<div class="method-signature">
<span class="method-name">mtime</span> &#8594; <span class="type">Num</span>
</div>
<p>The modification time as a Unix timestamp (seconds since epoch).</p>
<pre><code>var stat = Stat.path("file.txt")
System.print("Modified: %(stat.mtime)")</code></pre>
<div class="method-signature">
<span class="method-name">atime</span> &#8594; <span class="type">Num</span>
</div>
<p>The access time as a Unix timestamp.</p>
<div class="method-signature">
<span class="method-name">ctime</span> &#8594; <span class="type">Num</span>
</div>
<p>The change time (inode change) as a Unix timestamp.</p>
<div class="method-signature">
<span class="method-name">mode</span> &#8594; <span class="type">Num</span>
</div>
<p>The file permission mode.</p>
<div class="method-signature">
<span class="method-name">inode</span> &#8594; <span class="type">Num</span>
</div>
<p>The inode number.</p>
<div class="method-signature">
<span class="method-name">device</span> &#8594; <span class="type">Num</span>
</div>
<p>The device ID containing the file.</p>
<div class="method-signature">
<span class="method-name">linkCount</span> &#8594; <span class="type">Num</span>
</div>
<p>The number of hard links to the file.</p>
<div class="method-signature">
<span class="method-name">user</span> &#8594; <span class="type">Num</span>
</div>
<p>The user ID of the file owner.</p>
<div class="method-signature">
<span class="method-name">group</span> &#8594; <span class="type">Num</span>
</div>
<p>The group ID of the file.</p>
<div class="method-signature">
<span class="method-name">blockSize</span> &#8594; <span class="type">Num</span>
</div>
<p>The preferred block size for I/O operations.</p>
<div class="method-signature">
<span class="method-name">blockCount</span> &#8594; <span class="type">Num</span>
</div>
<p>The number of blocks allocated for the file.</p>
<h2>Examples</h2>
<h3>Reading and Writing Files</h3>
<pre><code>import "io" for File
var content = File.read("input.txt")
var processed = content.replace("old", "new")
File.write("output.txt", processed)</code></pre>
<h3>Working with JSON Files</h3>
<pre><code>import "io" for File
import "json" for Json
var config = Json.parse(File.read("config.json"))
config["updated"] = true
File.write("config.json", Json.stringify(config, 2))</code></pre>
<h3>Processing Directory Contents</h3>
<pre><code>import "io" for File, Directory
var files = Directory.list("./data")
for (file in files) {
if (file.endsWith(".txt")) {
var path = "./data/%(file)"
var size = File.size(path)
System.print("%(file): %(size) bytes")
}
}</code></pre>
<h3>Interactive Input</h3>
<pre><code>import "io" for Stdin
System.write("Username: ")
var username = Stdin.readLine()
System.write("Age: ")
var age = Num.fromString(Stdin.readLine())
System.print("Hello %(username), you are %(age) years old")</code></pre>
<h3>File Backup</h3>
<pre><code>import "io" for File
import "datetime" for DateTime
var backup = Fn.new { |path|
if (!File.exists(path)) return
var timestamp = DateTime.now().format("\%Y\%m\%d_\%H\%M\%S")
var backupPath = "%(path).%(timestamp).bak"
File.copy(path, backupPath)
System.print("Backed up to %(backupPath)")
}
backup.call("important.txt")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>File operations are synchronous but use libuv internally for async I/O. The fiber suspends during I/O operations.</p>
</div>
{% endblock %}
File diff suppressed because it is too large Load Diff
+166
View File
@@ -0,0 +1,166 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "json" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "json"}] %}
{% set prev_page = {"url": "api/dns.html", "title": "dns"} %}
{% set next_page = {"url": "api/base64.html", "title": "base64"} %}
{% block article %}
<h1>json</h1>
<p>The <code>json</code> module provides JSON parsing and stringification. It uses the cJSON library for parsing and implements stringify in Wren.</p>
<pre><code>import "json" for Json</code></pre>
<h2>Json Class</h2>
<div class="class-header">
<h3>Json</h3>
<p>JSON parsing and stringification</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Json.parse</span>(<span class="param">string</span>) → <span class="type">Map|List|String|Num|Bool|null</span>
</div>
<p>Parses a JSON string and returns the corresponding Wren value.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - JSON string to parse</li>
<li><span class="returns">Returns:</span> Parsed value (Map, List, String, Num, Bool, or null)</li>
</ul>
<pre><code>var data = Json.parse('{"name": "Alice", "age": 30}')
System.print(data["name"]) // Alice
System.print(data["age"]) // 30
var list = Json.parse('[1, 2, 3]')
System.print(list[0]) // 1</code></pre>
<div class="method-signature">
<span class="method-name">Json.stringify</span>(<span class="param">value</span>) → <span class="type">String</span>
</div>
<p>Converts a Wren value to a JSON string (compact, no whitespace).</p>
<ul class="param-list">
<li><span class="param-name">value</span> <span class="param-type">(any)</span> - Value to stringify</li>
<li><span class="returns">Returns:</span> JSON string</li>
</ul>
<pre><code>var json = Json.stringify({"name": "Alice", "age": 30})
System.print(json) // {"name":"Alice","age":30}</code></pre>
<div class="method-signature">
<span class="method-name">Json.stringify</span>(<span class="param">value</span>, <span class="param">indent</span>) → <span class="type">String</span>
</div>
<p>Converts a Wren value to a formatted JSON string with indentation.</p>
<ul class="param-list">
<li><span class="param-name">value</span> <span class="param-type">(any)</span> - Value to stringify</li>
<li><span class="param-name">indent</span> <span class="param-type">(Num|String)</span> - Number of spaces or indent string</li>
<li><span class="returns">Returns:</span> Formatted JSON string</li>
</ul>
<pre><code>var json = Json.stringify({"name": "Alice"}, 2)
System.print(json)
// {
// "name": "Alice"
// }
var json2 = Json.stringify({"name": "Bob"}, "\t")
// Uses tab for indentation</code></pre>
<h2>Type Mapping</h2>
<table>
<tr>
<th>JSON Type</th>
<th>Wren Type</th>
</tr>
<tr>
<td>object</td>
<td>Map</td>
</tr>
<tr>
<td>array</td>
<td>List</td>
</tr>
<tr>
<td>string</td>
<td>String</td>
</tr>
<tr>
<td>number</td>
<td>Num</td>
</tr>
<tr>
<td>true/false</td>
<td>Bool</td>
</tr>
<tr>
<td>null</td>
<td>null</td>
</tr>
</table>
<h2>Examples</h2>
<h3>Parsing JSON</h3>
<pre><code>import "json" for Json
var jsonString = '{"users": [{"name": "Alice"}, {"name": "Bob"}]}'
var data = Json.parse(jsonString)
for (user in data["users"]) {
System.print("User: %(user["name"])")
}</code></pre>
<h3>Building and Stringifying</h3>
<pre><code>import "json" for Json
var data = {
"name": "Product",
"price": 29.99,
"tags": ["electronics", "sale"],
"inStock": true,
"metadata": null
}
System.print(Json.stringify(data, 2))</code></pre>
<h3>Nested Structures</h3>
<pre><code>import "json" for Json
var config = {
"server": {
"host": "localhost",
"port": 8080
},
"database": {
"url": "sqlite://data.db"
}
}
var json = Json.stringify(config)
System.print(json)
var parsed = Json.parse(json)
System.print(parsed["server"]["port"]) // 8080</code></pre>
<h3>Special Values</h3>
<pre><code>import "json" for Json
var special = {
"infinity": 1/0,
"nan": 0/0
}
System.print(Json.stringify(special))
// {"infinity":null,"nan":null}
// Infinity and NaN are converted to null</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Map keys are converted to strings in JSON output. Non-string keys will have their <code>toString</code> method called.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Invalid JSON strings will cause a runtime error. Use <code>Fiber.try()</code> to catch parsing errors.</p>
</div>
{% endblock %}
+334
View File
@@ -0,0 +1,334 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "markdown" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "markdown"}] %}
{% set prev_page = {"url": "api/dataset.html", "title": "dataset"} %}
{% set next_page = {"url": "api/web.html", "title": "web"} %}
{% block article %}
<h1>markdown</h1>
<p>The <code>markdown</code> module provides bidirectional conversion between Markdown and HTML. It supports common Markdown syntax including headings, emphasis, code blocks, lists, links, and images.</p>
<pre><code>import "markdown" for Markdown</code></pre>
<h2>Markdown Class</h2>
<div class="class-header">
<h3>Markdown</h3>
<p>Markdown to HTML converter</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Markdown.toHtml</span>(<span class="param">text</span>) → <span class="type">String</span>
</div>
<p>Converts Markdown text to HTML.</p>
<ul class="param-list">
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Markdown text</li>
<li><span class="returns">Returns:</span> HTML string</li>
</ul>
<pre><code>var html = Markdown.toHtml("# Hello World")
System.print(html) // &lt;h1&gt;Hello World&lt;/h1&gt;</code></pre>
<div class="method-signature">
<span class="method-name">Markdown.toHtml</span>(<span class="param">text</span>, <span class="param">options</span>) → <span class="type">String</span>
</div>
<p>Converts Markdown text to HTML with options.</p>
<ul class="param-list">
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Markdown text</li>
<li><span class="param-name">options</span> <span class="param-type">(Map)</span> - Conversion options</li>
<li><span class="returns">Returns:</span> HTML string</li>
</ul>
<div class="method-signature">
<span class="method-name">Markdown.fromHtml</span>(<span class="param">html</span>) → <span class="type">String</span>
</div>
<p>Converts HTML to Markdown text.</p>
<ul class="param-list">
<li><span class="param-name">html</span> <span class="param-type">(String)</span> - HTML string</li>
<li><span class="returns">Returns:</span> Markdown text</li>
</ul>
<pre><code>var md = Markdown.fromHtml("&lt;h1&gt;Hello World&lt;/h1&gt;")
System.print(md) // # Hello World</code></pre>
<div class="method-signature">
<span class="method-name">Markdown.fromHtml</span>(<span class="param">html</span>, <span class="param">options</span>) → <span class="type">String</span>
</div>
<p>Converts HTML to Markdown text with options.</p>
<ul class="param-list">
<li><span class="param-name">html</span> <span class="param-type">(String)</span> - HTML string</li>
<li><span class="param-name">options</span> <span class="param-type">(Map)</span> - Conversion options</li>
<li><span class="returns">Returns:</span> Markdown text</li>
</ul>
<h2>Options</h2>
<h3>toHtml Options</h3>
<table>
<tr>
<th>Option</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td><code>safeMode</code></td>
<td>Bool</td>
<td><code>false</code></td>
<td>Escape HTML in input to prevent XSS</td>
</tr>
</table>
<h3>fromHtml Options</h3>
<table>
<tr>
<th>Option</th>
<th>Type</th>
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td><code>stripUnknown</code></td>
<td>Bool</td>
<td><code>true</code></td>
<td>Strip unknown HTML tags, keeping their content</td>
</tr>
</table>
<h2>Supported Syntax</h2>
<h3>Headings</h3>
<pre><code># Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6</code></pre>
<p>Converts to <code>&lt;h1&gt;</code> through <code>&lt;h6&gt;</code> tags.</p>
<h3>Emphasis</h3>
<pre><code>*italic* or _italic_
**bold** or __bold__
~~strikethrough~~</code></pre>
<p>Converts to <code>&lt;em&gt;</code>, <code>&lt;strong&gt;</code>, and <code>&lt;del&gt;</code> tags.</p>
<h3>Code</h3>
<pre><code>Inline `code` here
```
Code block
Multiple lines
```</code></pre>
<p>Inline code uses <code>&lt;code&gt;</code>, blocks use <code>&lt;pre&gt;&lt;code&gt;</code>.</p>
<h3>Lists</h3>
<pre><code>Unordered:
- Item 1
- Item 2
* Also works
+ And this
Ordered:
1. First
2. Second
3. Third</code></pre>
<p>Creates <code>&lt;ul&gt;</code> and <code>&lt;ol&gt;</code> with <code>&lt;li&gt;</code> items.</p>
<h3>Links and Images</h3>
<pre><code>[Link text](https://example.com)
![Alt text](image.png)</code></pre>
<p>Creates <code>&lt;a href="..."&gt;</code> and <code>&lt;img src="..." alt="..."&gt;</code>.</p>
<h3>Blockquotes</h3>
<pre><code>&gt; This is a quote
&gt; Multiple lines</code></pre>
<p>Creates <code>&lt;blockquote&gt;</code> with <code>&lt;p&gt;</code> content.</p>
<h3>Horizontal Rule</h3>
<pre><code>---
***
___</code></pre>
<p>Creates <code>&lt;hr&gt;</code> tag.</p>
<h3>Paragraphs</h3>
<p>Text separated by blank lines becomes <code>&lt;p&gt;</code> elements.</p>
<h2>HTML to Markdown Conversions</h2>
<p>The <code>fromHtml</code> method converts the following HTML elements to Markdown:</p>
<table>
<tr>
<th>HTML</th>
<th>Markdown</th>
</tr>
<tr>
<td><code>&lt;h1&gt;</code> to <code>&lt;h6&gt;</code></td>
<td><code>#</code> to <code>######</code></td>
</tr>
<tr>
<td><code>&lt;strong&gt;</code>, <code>&lt;b&gt;</code></td>
<td><code>**text**</code></td>
</tr>
<tr>
<td><code>&lt;em&gt;</code>, <code>&lt;i&gt;</code></td>
<td><code>*text*</code></td>
</tr>
<tr>
<td><code>&lt;code&gt;</code></td>
<td><code>`text`</code></td>
</tr>
<tr>
<td><code>&lt;pre&gt;&lt;code&gt;</code></td>
<td>Fenced code block</td>
</tr>
<tr>
<td><code>&lt;a href="url"&gt;</code></td>
<td><code>[text](url)</code></td>
</tr>
<tr>
<td><code>&lt;img src="url" alt=""&gt;</code></td>
<td><code>![alt](url)</code></td>
</tr>
<tr>
<td><code>&lt;ul&gt;&lt;li&gt;</code></td>
<td><code>- item</code></td>
</tr>
<tr>
<td><code>&lt;ol&gt;&lt;li&gt;</code></td>
<td><code>1. item</code></td>
</tr>
<tr>
<td><code>&lt;blockquote&gt;</code></td>
<td><code>&gt; text</code></td>
</tr>
<tr>
<td><code>&lt;hr&gt;</code></td>
<td><code>---</code></td>
</tr>
<tr>
<td><code>&lt;del&gt;</code>, <code>&lt;s&gt;</code></td>
<td><code>~~text~~</code></td>
</tr>
</table>
<p>Container tags (<code>&lt;div&gt;</code>, <code>&lt;span&gt;</code>, <code>&lt;section&gt;</code>, etc.) are stripped but their content is preserved. Script and style tags are removed entirely.</p>
<h2>Examples</h2>
<h3>Basic Conversion</h3>
<pre><code>import "markdown" for Markdown
var md = "
# Welcome
This is a **Markdown** document with:
- Lists
- *Emphasis*
- `Code`
Visit [Wren](https://wren.io) for more.
"
var html = Markdown.toHtml(md)
System.print(html)</code></pre>
<h3>Safe Mode for User Content</h3>
<pre><code>import "markdown" for Markdown
var userInput = "# Title\n&lt;script&gt;alert('xss')&lt;/script&gt;\nContent here."
var safeHtml = Markdown.toHtml(userInput, {"safeMode": true})
System.print(safeHtml)</code></pre>
<h3>Rendering a Blog Post</h3>
<pre><code>import "markdown" for Markdown
import "io" for File
var postContent = File.read("post.md")
var html = Markdown.toHtml(postContent)
var page = "&lt;html&gt;
&lt;head&gt;&lt;title&gt;Blog&lt;/title&gt;&lt;/head&gt;
&lt;body&gt;
&lt;article&gt;
%(html)
&lt;/article&gt;
&lt;/body&gt;
&lt;/html&gt;"
File.write("post.html", page)</code></pre>
<h3>Code Blocks with Language Hints</h3>
<pre><code>import "markdown" for Markdown
var md = "
```wren
System.print(\"Hello, World!\")
```
"
var html = Markdown.toHtml(md)
System.print(html)
// &lt;pre&gt;&lt;code&gt;System.print("Hello, World!")&lt;/code&gt;&lt;/pre&gt;</code></pre>
<h3>Combining with Templates</h3>
<pre><code>import "markdown" for Markdown
import "jinja" for Environment, DictLoader
var env = Environment.new(DictLoader.new({
"base": "&lt;html&gt;&lt;body&gt;{{ content }}&lt;/body&gt;&lt;/html&gt;"
}))
var md = "# Hello\n\nThis is **Markdown**."
var content = Markdown.toHtml(md)
var html = env.getTemplate("base").render({"content": content})
System.print(html)</code></pre>
<h3>Converting HTML to Markdown</h3>
<pre><code>import "markdown" for Markdown
var html = "
&lt;html&gt;
&lt;body&gt;
&lt;h1&gt;Article Title&lt;/h1&gt;
&lt;p&gt;This is &lt;strong&gt;important&lt;/strong&gt; content.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First item&lt;/li&gt;
&lt;li&gt;Second item&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;
&lt;/html&gt;
"
var md = Markdown.fromHtml(html)
System.print(md)
// # Article Title
//
// This is **important** content.
//
// - First item
// - Second item</code></pre>
<h3>Cleaning HTML for Plain Text</h3>
<pre><code>import "markdown" for Markdown
var webContent = "&lt;div&gt;&lt;script&gt;alert('xss')&lt;/script&gt;&lt;p&gt;Safe &lt;b&gt;content&lt;/b&gt;&lt;/p&gt;&lt;/div&gt;"
var clean = Markdown.fromHtml(webContent)
System.print(clean) // Safe **content**</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>When rendering user-provided Markdown, always use <code>safeMode: true</code> to prevent cross-site scripting (XSS) attacks. Safe mode escapes HTML entities in the input text.</p>
</div>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Language identifiers after code fences (e.g., <code>```wren</code>) are currently ignored. The code is rendered without syntax highlighting. Consider using a client-side syntax highlighter like Prism.js or highlight.js for the resulting HTML.</p>
</div>
{% endblock %}
+200
View File
@@ -0,0 +1,200 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "math" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "math"}] %}
{% set prev_page = {"url": "api/scheduler.html", "title": "scheduler"} %}
{% set next_page = {"url": "tutorials/index.html", "title": "Tutorials"} %}
{% block article %}
<h1>math</h1>
<p>The <code>math</code> module provides mathematical functions and constants.</p>
<pre><code>import "math" for Math</code></pre>
<h2>Math Class</h2>
<div class="class-header">
<h3>Math</h3>
<p>Mathematical functions and constants</p>
</div>
<h3>Constants</h3>
<table>
<tr>
<th>Constant</th>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td><code>Math.pi</code></td>
<td>3.14159...</td>
<td>Pi</td>
</tr>
<tr>
<td><code>Math.e</code></td>
<td>2.71828...</td>
<td>Euler's number</td>
</tr>
<tr>
<td><code>Math.tau</code></td>
<td>6.28318...</td>
<td>Tau (2 * Pi)</td>
</tr>
</table>
<h3>Basic Functions</h3>
<div class="method-signature">
<span class="method-name">Math.abs</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the absolute value.</p>
<pre><code>Math.abs(-5) // 5
Math.abs(3.14) // 3.14</code></pre>
<div class="method-signature">
<span class="method-name">Math.min</span>(<span class="param">a</span>, <span class="param">b</span>) → <span class="type">Num</span>
</div>
<p>Returns the smaller of two values.</p>
<div class="method-signature">
<span class="method-name">Math.max</span>(<span class="param">a</span>, <span class="param">b</span>) → <span class="type">Num</span>
</div>
<p>Returns the larger of two values.</p>
<div class="method-signature">
<span class="method-name">Math.clamp</span>(<span class="param">value</span>, <span class="param">min</span>, <span class="param">max</span>) → <span class="type">Num</span>
</div>
<p>Clamps a value to a range.</p>
<pre><code>Math.clamp(15, 0, 10) // 10
Math.clamp(5, 0, 10) // 5
Math.clamp(-5, 0, 10) // 0</code></pre>
<h3>Rounding</h3>
<div class="method-signature">
<span class="method-name">Math.floor</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Rounds down to the nearest integer.</p>
<div class="method-signature">
<span class="method-name">Math.ceil</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Rounds up to the nearest integer.</p>
<div class="method-signature">
<span class="method-name">Math.round</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Rounds to the nearest integer.</p>
<pre><code>Math.floor(3.7) // 3
Math.ceil(3.2) // 4
Math.round(3.5) // 4</code></pre>
<h3>Powers and Roots</h3>
<div class="method-signature">
<span class="method-name">Math.sqrt</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the square root.</p>
<div class="method-signature">
<span class="method-name">Math.pow</span>(<span class="param">base</span>, <span class="param">exp</span>) → <span class="type">Num</span>
</div>
<p>Returns base raised to the power of exp.</p>
<div class="method-signature">
<span class="method-name">Math.exp</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns e raised to the power of x.</p>
<pre><code>Math.sqrt(16) // 4
Math.pow(2, 10) // 1024
Math.exp(1) // 2.71828...</code></pre>
<h3>Logarithms</h3>
<div class="method-signature">
<span class="method-name">Math.log</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the natural logarithm (base e).</p>
<div class="method-signature">
<span class="method-name">Math.log10</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the base-10 logarithm.</p>
<div class="method-signature">
<span class="method-name">Math.log2</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the base-2 logarithm.</p>
<h3>Trigonometry</h3>
<div class="method-signature">
<span class="method-name">Math.sin</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the sine (x in radians).</p>
<div class="method-signature">
<span class="method-name">Math.cos</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the cosine (x in radians).</p>
<div class="method-signature">
<span class="method-name">Math.tan</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the tangent (x in radians).</p>
<div class="method-signature">
<span class="method-name">Math.asin</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the arcsine in radians.</p>
<div class="method-signature">
<span class="method-name">Math.acos</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the arccosine in radians.</p>
<div class="method-signature">
<span class="method-name">Math.atan</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the arctangent in radians.</p>
<div class="method-signature">
<span class="method-name">Math.atan2</span>(<span class="param">y</span>, <span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Returns the angle in radians between the positive x-axis and the point (x, y).</p>
<h2>Examples</h2>
<h3>Distance Calculation</h3>
<pre><code>import "math" for Math
var distance = Fn.new { |x1, y1, x2, y2|
var dx = x2 - x1
var dy = y2 - y1
return Math.sqrt(dx * dx + dy * dy)
}
System.print(distance.call(0, 0, 3, 4)) // 5</code></pre>
<h3>Degrees to Radians</h3>
<pre><code>import "math" for Math
var toRadians = Fn.new { |degrees| degrees * Math.pi / 180 }
var toDegrees = Fn.new { |radians| radians * 180 / Math.pi }
System.print(Math.sin(toRadians.call(90))) // 1</code></pre>
<h3>Circle Area</h3>
<pre><code>import "math" for Math
var circleArea = Fn.new { |radius|
return Math.pi * radius * radius
}
System.print(circleArea.call(5)) // 78.539...</code></pre>
{% endblock %}
+240
View File
@@ -0,0 +1,240 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "net" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "net"}] %}
{% set prev_page = {"url": "api/tls.html", "title": "tls"} %}
{% set next_page = {"url": "api/dns.html", "title": "dns"} %}
{% block article %}
<h1>net</h1>
<p>The <code>net</code> module provides low-level TCP socket and server functionality for network programming. All operations are asynchronous using libuv.</p>
<pre><code>import "net" for Socket, Server</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#socket-class">Socket Class</a></li>
<li><a href="#server-class">Server Class</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="socket-class">Socket Class</h2>
<div class="class-header">
<h3>Socket</h3>
<p>TCP socket for client connections</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Socket.connect</span>(<span class="param">host</span>, <span class="param">port</span>) &#8594; <span class="type">Socket</span>
</div>
<p>Establishes a TCP connection to a remote server.</p>
<ul class="param-list">
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - Hostname or IP address to connect to</li>
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number</li>
<li><span class="returns">Returns:</span> Connected Socket instance</li>
</ul>
<pre><code>var socket = Socket.connect("127.0.0.1", 8080)</code></pre>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">write</span>(<span class="param">data</span>) &#8594; <span class="type">Num</span>
</div>
<p>Writes data to the socket. Blocks until the data is sent.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Data to send</li>
<li><span class="returns">Returns:</span> Number of bytes written</li>
</ul>
<pre><code>socket.write("Hello, server!")</code></pre>
<div class="method-signature">
<span class="method-name">read</span>() &#8594; <span class="type">String|null</span>
</div>
<p>Reads data from the socket. Blocks until data is available. Returns null when the connection is closed by the remote end.</p>
<ul class="param-list">
<li><span class="returns">Returns:</span> Data received as a string, or null if connection closed</li>
</ul>
<pre><code>var data = socket.read()
if (data != null) {
System.print("Received: %(data)")
}</code></pre>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<p>Closes the socket connection.</p>
<pre><code>socket.close()</code></pre>
<h2 id="server-class">Server Class</h2>
<div class="class-header">
<h3>Server</h3>
<p>TCP server for accepting client connections</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Server.bind</span>(<span class="param">host</span>, <span class="param">port</span>) &#8594; <span class="type">Server</span>
</div>
<p>Creates a TCP server listening on the specified host and port.</p>
<ul class="param-list">
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - Host to bind to (e.g., "0.0.0.0" for all interfaces, "127.0.0.1" for localhost only)</li>
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number to listen on</li>
<li><span class="returns">Returns:</span> Bound Server instance</li>
</ul>
<pre><code>var server = Server.bind("0.0.0.0", 8080)</code></pre>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">accept</span>() &#8594; <span class="type">Socket|null</span>
</div>
<p>Accepts an incoming connection. Blocks until a client connects. Returns a Socket instance for the connected client.</p>
<ul class="param-list">
<li><span class="returns">Returns:</span> Socket for the accepted connection</li>
</ul>
<pre><code>var client = server.accept()
System.print("Client connected!")</code></pre>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<p>Stops the server and closes the listening socket.</p>
<pre><code>server.close()</code></pre>
<h2 id="examples">Examples</h2>
<h3>Simple TCP Client</h3>
<pre><code>import "net" for Socket
var socket = Socket.connect("example.com", 80)
socket.write("GET / HTTP/1.1\r\n")
socket.write("Host: example.com\r\n")
socket.write("Connection: close\r\n")
socket.write("\r\n")
var response = ""
while (true) {
var chunk = socket.read()
if (chunk == null) break
response = response + chunk
}
socket.close()
System.print(response)</code></pre>
<h3>Echo Server</h3>
<pre><code>import "net" for Server
var server = Server.bind("0.0.0.0", 8080)
System.print("Echo server listening on port 8080")
while (true) {
var client = server.accept()
System.print("Client connected")
while (true) {
var data = client.read()
if (data == null) {
System.print("Client disconnected")
break
}
System.print("Received: %(data)")
client.write(data)
}
client.close()
}</code></pre>
<h3>Line-Based Protocol Server</h3>
<pre><code>import "net" for Server
var server = Server.bind("127.0.0.1", 9000)
System.print("Chat server on port 9000")
while (true) {
var client = server.accept()
client.write("Welcome! Type messages and press Enter.\n")
var buffer = ""
while (true) {
var data = client.read()
if (data == null) break
buffer = buffer + data
while (buffer.contains("\n")) {
var lineEnd = buffer.indexOf("\n")
var line = buffer[0...lineEnd]
buffer = buffer[lineEnd + 1..-1]
if (line == "quit") {
client.write("Goodbye!\n")
client.close()
break
}
client.write("You said: %(line)\n")
}
}
}</code></pre>
<h3>Connecting with DNS Resolution</h3>
<pre><code>import "net" for Socket
import "dns" for Dns
var hostname = "httpbin.org"
var ip = Dns.lookup(hostname)
System.print("Resolved %(hostname) to %(ip)")
var socket = Socket.connect(ip, 80)
socket.write("GET /ip HTTP/1.1\r\nHost: %(hostname)\r\nConnection: close\r\n\r\n")
var response = ""
while (true) {
var data = socket.read()
if (data == null) break
response = response + data
}
socket.close()
System.print(response)</code></pre>
<h3>Simple Request-Response Client</h3>
<pre><code>import "net" for Socket
var socket = Socket.connect("127.0.0.1", 8080)
socket.write("PING\n")
var response = socket.read()
System.print("Server responded: %(response)")
socket.write("ECHO Hello World\n")
response = socket.read()
System.print("Server responded: %(response)")
socket.close()</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>The Socket class uses TCP, which is a stream-based protocol. Data may arrive in chunks that do not correspond to message boundaries. For protocols that require message framing, implement appropriate buffering and parsing logic.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>For secure connections, use the <code>tls</code> module's TlsSocket class instead of Socket. For HTTP/HTTPS requests, the higher-level <code>http</code> module handles protocol details automatically.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Both host and port arguments are validated. Providing a non-string host or non-number port will abort the fiber with an error message.</p>
</div>
{% endblock %}
+401
View File
@@ -0,0 +1,401 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "Num" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "Num"}] %}
{% set prev_page = {"url": "api/string.html", "title": "String"} %}
{% set next_page = {"url": "api/http.html", "title": "http"} %}
{% block article %}
<h1>Num</h1>
<p>The <code>Num</code> class is the core numeric type in Wren. All numbers are double-precision floating point. The class provides arithmetic, trigonometric, bitwise, rounding, query, conversion, and formatting methods.</p>
<h2>Static Constants</h2>
<div class="method-signature">
<span class="method-name">Num.pi</span><span class="type">Num</span>
</div>
<p>The ratio of a circle's circumference to its diameter (3.14159...).</p>
<div class="method-signature">
<span class="method-name">Num.tau</span><span class="type">Num</span>
</div>
<p>Two times pi (6.28318...).</p>
<div class="method-signature">
<span class="method-name">Num.e</span><span class="type">Num</span>
</div>
<p>Euler's number (2.71828...).</p>
<div class="method-signature">
<span class="method-name">Num.infinity</span><span class="type">Num</span>
</div>
<p>Positive infinity.</p>
<div class="method-signature">
<span class="method-name">Num.nan</span><span class="type">Num</span>
</div>
<p>Not-a-number value.</p>
<div class="method-signature">
<span class="method-name">Num.largest</span><span class="type">Num</span>
</div>
<p>The largest representable double value.</p>
<div class="method-signature">
<span class="method-name">Num.smallest</span><span class="type">Num</span>
</div>
<p>The smallest positive representable double value.</p>
<div class="method-signature">
<span class="method-name">Num.maxSafeInteger</span><span class="type">Num</span>
</div>
<p>The largest integer representable without loss of precision (9007199254740991).</p>
<div class="method-signature">
<span class="method-name">Num.minSafeInteger</span><span class="type">Num</span>
</div>
<p>The smallest integer representable without loss of precision (-9007199254740991).</p>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Num.fromString</span>(<span class="param">string</span>) → <span class="type">Num|null</span>
</div>
<p>Parses a string as a number. Returns <code>null</code> if the string is not a valid number.</p>
<pre><code>Num.fromString("42") // 42
Num.fromString("3.14") // 3.14
Num.fromString("nope") // null</code></pre>
<h2>Query Methods</h2>
<div class="method-signature">
<span class="method-name">isInteger</span><span class="type">Bool</span>
</div>
<p>Whether the number is an integer (has no fractional part).</p>
<div class="method-signature">
<span class="method-name">isNan</span><span class="type">Bool</span>
</div>
<p>Whether the number is NaN.</p>
<div class="method-signature">
<span class="method-name">isInfinity</span><span class="type">Bool</span>
</div>
<p>Whether the number is positive or negative infinity.</p>
<div class="method-signature">
<span class="method-name">isFinite</span><span class="type">Bool</span>
</div>
<p>Whether the number is finite (not infinity and not NaN).</p>
<pre><code>42.isFinite // true
(1/0).isFinite // false
(0/0).isFinite // false</code></pre>
<div class="method-signature">
<span class="method-name">isZero</span><span class="type">Bool</span>
</div>
<p>Whether the number equals zero.</p>
<div class="method-signature">
<span class="method-name">isPositive</span><span class="type">Bool</span>
</div>
<p>Whether the number is strictly greater than zero.</p>
<div class="method-signature">
<span class="method-name">isNegative</span><span class="type">Bool</span>
</div>
<p>Whether the number is strictly less than zero.</p>
<div class="method-signature">
<span class="method-name">isEven</span><span class="type">Bool</span>
</div>
<p>Whether the number is an even integer.</p>
<div class="method-signature">
<span class="method-name">isOdd</span><span class="type">Bool</span>
</div>
<p>Whether the number is an odd integer.</p>
<div class="method-signature">
<span class="method-name">isBetween</span>(<span class="param">min</span>, <span class="param">max</span>) → <span class="type">Bool</span>
</div>
<p>Whether the number is within the inclusive range [min, max].</p>
<pre><code>5.isBetween(1, 10) // true
0.isBetween(1, 10) // false
10.isBetween(1, 10) // true</code></pre>
<div class="method-signature">
<span class="method-name">sign</span><span class="type">Num</span>
</div>
<p>Returns 1 for positive, -1 for negative, or 0 for zero.</p>
<h2>Arithmetic</h2>
<div class="method-signature">
<span class="method-name">abs</span><span class="type">Num</span>
</div>
<p>Absolute value.</p>
<div class="method-signature">
<span class="method-name">min</span>(<span class="param">other</span>) → <span class="type">Num</span>
</div>
<p>Returns the smaller of this number and <code>other</code>.</p>
<div class="method-signature">
<span class="method-name">max</span>(<span class="param">other</span>) → <span class="type">Num</span>
</div>
<p>Returns the larger of this number and <code>other</code>.</p>
<div class="method-signature">
<span class="method-name">clamp</span>(<span class="param">min</span>, <span class="param">max</span>) → <span class="type">Num</span>
</div>
<p>Constrains this number to the range [min, max].</p>
<pre><code>15.clamp(0, 10) // 10
5.clamp(0, 10) // 5
(-3).clamp(0, 10) // 0</code></pre>
<div class="method-signature">
<span class="method-name">pow</span>(<span class="param">exponent</span>) → <span class="type">Num</span>
</div>
<p>Raises this number to the given power.</p>
<h2>Rounding</h2>
<div class="method-signature">
<span class="method-name">ceil</span><span class="type">Num</span>
</div>
<p>Rounds up to the nearest integer.</p>
<div class="method-signature">
<span class="method-name">floor</span><span class="type">Num</span>
</div>
<p>Rounds down to the nearest integer.</p>
<div class="method-signature">
<span class="method-name">round</span><span class="type">Num</span>
</div>
<p>Rounds to the nearest integer.</p>
<div class="method-signature">
<span class="method-name">truncate</span><span class="type">Num</span>
</div>
<p>Removes the fractional part, rounding toward zero.</p>
<div class="method-signature">
<span class="method-name">fraction</span><span class="type">Num</span>
</div>
<p>Returns the fractional part of the number.</p>
<h2>Roots &amp; Exponents</h2>
<div class="method-signature">
<span class="method-name">sqrt</span><span class="type">Num</span>
</div>
<p>Square root.</p>
<div class="method-signature">
<span class="method-name">cbrt</span><span class="type">Num</span>
</div>
<p>Cube root.</p>
<div class="method-signature">
<span class="method-name">exp</span><span class="type">Num</span>
</div>
<p>Returns e raised to this power.</p>
<h2>Logarithms</h2>
<div class="method-signature">
<span class="method-name">log</span><span class="type">Num</span>
</div>
<p>Natural logarithm (base e).</p>
<div class="method-signature">
<span class="method-name">log2</span><span class="type">Num</span>
</div>
<p>Base-2 logarithm.</p>
<div class="method-signature">
<span class="method-name">log10</span><span class="type">Num</span>
</div>
<p>Base-10 logarithm.</p>
<pre><code>100.log10 // 2
1000.log10 // 3
1.log10 // 0</code></pre>
<h2>Trigonometry</h2>
<div class="method-signature">
<span class="method-name">sin</span><span class="type">Num</span>
</div>
<p>Sine (argument in radians).</p>
<div class="method-signature">
<span class="method-name">cos</span><span class="type">Num</span>
</div>
<p>Cosine (argument in radians).</p>
<div class="method-signature">
<span class="method-name">tan</span><span class="type">Num</span>
</div>
<p>Tangent (argument in radians).</p>
<div class="method-signature">
<span class="method-name">asin</span><span class="type">Num</span>
</div>
<p>Arc sine, returns radians.</p>
<div class="method-signature">
<span class="method-name">acos</span><span class="type">Num</span>
</div>
<p>Arc cosine, returns radians.</p>
<div class="method-signature">
<span class="method-name">atan</span><span class="type">Num</span>
</div>
<p>Arc tangent, returns radians.</p>
<div class="method-signature">
<span class="method-name">atan</span>(<span class="param">x</span>) → <span class="type">Num</span>
</div>
<p>Two-argument arc tangent (atan2). Returns the angle between the positive x-axis and the point (x, this).</p>
<h3>Hyperbolic Functions</h3>
<div class="method-signature">
<span class="method-name">sinh</span><span class="type">Num</span>
</div>
<p>Hyperbolic sine.</p>
<div class="method-signature">
<span class="method-name">cosh</span><span class="type">Num</span>
</div>
<p>Hyperbolic cosine.</p>
<div class="method-signature">
<span class="method-name">tanh</span><span class="type">Num</span>
</div>
<p>Hyperbolic tangent.</p>
<pre><code>0.sinh // 0
0.cosh // 1
0.tanh // 0</code></pre>
<h2>Angle Conversion</h2>
<div class="method-signature">
<span class="method-name">toDegrees</span><span class="type">Num</span>
</div>
<p>Converts radians to degrees.</p>
<div class="method-signature">
<span class="method-name">toRadians</span><span class="type">Num</span>
</div>
<p>Converts degrees to radians.</p>
<pre><code>Num.pi.toDegrees // 180
180.toRadians // 3.14159...</code></pre>
<h2>Conversion</h2>
<div class="method-signature">
<span class="method-name">toString</span><span class="type">String</span>
</div>
<p>Converts the number to its string representation.</p>
<div class="method-signature">
<span class="method-name">toChar</span><span class="type">String</span>
</div>
<p>Returns the Unicode character for this code point.</p>
<pre><code>65.toChar // "A"
97.toChar // "a"</code></pre>
<div class="method-signature">
<span class="method-name">toBase</span>(<span class="param">radix</span>) → <span class="type">String</span>
</div>
<p>Converts the integer part to a string in the given base (2-36).</p>
<pre><code>255.toBase(16) // "ff"
255.toBase(2) // "11111111"
255.toBase(8) // "377"</code></pre>
<div class="method-signature">
<span class="method-name">toHex</span><span class="type">String</span>
</div>
<p>Shorthand for <code>toBase(16)</code>.</p>
<div class="method-signature">
<span class="method-name">toBinary</span><span class="type">String</span>
</div>
<p>Shorthand for <code>toBase(2)</code>.</p>
<div class="method-signature">
<span class="method-name">toOctal</span><span class="type">String</span>
</div>
<p>Shorthand for <code>toBase(8)</code>.</p>
<h2>Formatting</h2>
<div class="method-signature">
<span class="method-name">format</span>(<span class="param">decimals</span>) → <span class="type">String</span>
</div>
<p>Formats the number with a fixed number of decimal places (0-20).</p>
<pre><code>3.14159.format(2) // "3.14"
42.format(3) // "42.000"
(-1.5).format(1) // "-1.5"</code></pre>
<h2>Integer Operations</h2>
<div class="method-signature">
<span class="method-name">gcd</span>(<span class="param">other</span>) → <span class="type">Num</span>
</div>
<p>Greatest common divisor using the Euclidean algorithm. Operates on absolute values.</p>
<pre><code>12.gcd(8) // 4
54.gcd(24) // 6
7.gcd(13) // 1</code></pre>
<div class="method-signature">
<span class="method-name">lcm</span>(<span class="param">other</span>) → <span class="type">Num</span>
</div>
<p>Least common multiple.</p>
<pre><code>4.lcm(6) // 12
3.lcm(5) // 15</code></pre>
<div class="method-signature">
<span class="method-name">digits</span><span class="type">List</span>
</div>
<p>Returns a list of the individual digits of the integer's absolute value. Aborts if the number is not an integer.</p>
<pre><code>123.digits // [1, 2, 3]
0.digits // [0]
(-456).digits // [4, 5, 6]</code></pre>
<h2>Bitwise Operations</h2>
<p>Bitwise operators work on 32-bit unsigned integers.</p>
<table>
<tr>
<th>Operator</th>
<th>Description</th>
</tr>
<tr><td><code>&amp;(other)</code></td><td>Bitwise AND</td></tr>
<tr><td><code>|(other)</code></td><td>Bitwise OR</td></tr>
<tr><td><code>^(other)</code></td><td>Bitwise XOR</td></tr>
<tr><td><code>&lt;&lt;(other)</code></td><td>Left shift</td></tr>
<tr><td><code>&gt;&gt;(other)</code></td><td>Right shift</td></tr>
<tr><td><code>~</code></td><td>Bitwise NOT</td></tr>
</table>
<h2>Ranges</h2>
<div class="method-signature">
<span class="method-name">..</span>(<span class="param">to</span>) → <span class="type">Range</span>
</div>
<p>Creates an inclusive range from this number to <code>to</code>.</p>
<div class="method-signature">
<span class="method-name">...</span>(<span class="param">to</span>) → <span class="type">Range</span>
</div>
<p>Creates an exclusive range from this number to <code>to</code> (excludes the end).</p>
<pre><code>for (i in 1..5) System.print(i) // 1 2 3 4 5
for (i in 1...5) System.print(i) // 1 2 3 4</code></pre>
{% endblock %}
+226
View File
@@ -0,0 +1,226 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "os" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "os"}] %}
{% set prev_page = {"url": "api/crypto.html", "title": "crypto"} %}
{% set next_page = {"url": "api/env.html", "title": "env"} %}
{% block article %}
<h1>os</h1>
<p>The <code>os</code> module provides information about the operating system, platform, and current process.</p>
<pre><code>import "os" for Platform, Process</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#platform-class">Platform Class</a></li>
<li><a href="#process-class">Process Class</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="platform-class">Platform Class</h2>
<div class="class-header">
<h3>Platform</h3>
<p>Operating system and platform information</p>
</div>
<h3>Static Properties</h3>
<div class="method-signature">
<span class="method-name">Platform.name</span> &#8594; <span class="type">String</span>
</div>
<p>The name of the operating system (e.g., "Linux", "macOS", "Windows", "FreeBSD").</p>
<pre><code>System.print(Platform.name) // Linux</code></pre>
<div class="method-signature">
<span class="method-name">Platform.isPosix</span> &#8594; <span class="type">Bool</span>
</div>
<p>True if running on a POSIX-compatible system (Linux, macOS, BSD, etc.).</p>
<pre><code>if (Platform.isPosix) {
System.print("Running on a POSIX system")
}</code></pre>
<div class="method-signature">
<span class="method-name">Platform.isWindows</span> &#8594; <span class="type">Bool</span>
</div>
<p>True if running on Windows.</p>
<pre><code>if (Platform.isWindows) {
System.print("Running on Windows")
}</code></pre>
<div class="method-signature">
<span class="method-name">Platform.homePath</span> &#8594; <span class="type">String</span>
</div>
<p>The current user's home directory path.</p>
<pre><code>System.print(Platform.homePath) // /home/alice (Linux) or C:\Users\alice (Windows)</code></pre>
<h2 id="process-class">Process Class</h2>
<div class="class-header">
<h3>Process</h3>
<p>Current process information and arguments</p>
</div>
<h3>Static Properties</h3>
<div class="method-signature">
<span class="method-name">Process.arguments</span> &#8594; <span class="type">List</span>
</div>
<p>Command-line arguments passed to the script (excludes the interpreter and script path).</p>
<pre><code>// Running: wren_cli script.wren arg1 arg2
System.print(Process.arguments) // [arg1, arg2]</code></pre>
<div class="method-signature">
<span class="method-name">Process.allArguments</span> &#8594; <span class="type">List</span>
</div>
<p>All command-line arguments including the interpreter path and script path.</p>
<pre><code>// Running: wren_cli script.wren arg1 arg2
System.print(Process.allArguments) // [wren_cli, script.wren, arg1, arg2]</code></pre>
<div class="method-signature">
<span class="method-name">Process.cwd</span> &#8594; <span class="type">String</span>
</div>
<p>The current working directory.</p>
<pre><code>System.print(Process.cwd) // /home/alice/projects</code></pre>
<div class="method-signature">
<span class="method-name">Process.pid</span> &#8594; <span class="type">Num</span>
</div>
<p>The process ID of the current process.</p>
<pre><code>System.print(Process.pid) // 12345</code></pre>
<div class="method-signature">
<span class="method-name">Process.ppid</span> &#8594; <span class="type">Num</span>
</div>
<p>The parent process ID.</p>
<pre><code>System.print(Process.ppid) // 12300</code></pre>
<div class="method-signature">
<span class="method-name">Process.version</span> &#8594; <span class="type">String</span>
</div>
<p>The version string of the Wren-CLI runtime.</p>
<pre><code>System.print(Process.version) // 0.4.0</code></pre>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Process.exit</span>(<span class="param">code</span>)
</div>
<p>Terminates the process with the specified exit code.</p>
<ul class="param-list">
<li><span class="param-name">code</span> <span class="param-type">(Num)</span> - Exit code (0 for success, non-zero for error)</li>
</ul>
<pre><code>if (hasError) {
Process.exit(1)
}
Process.exit(0)</code></pre>
<h2 id="examples">Examples</h2>
<h3>Platform-Specific Behavior</h3>
<pre><code>import "os" for Platform
var configPath
if (Platform.isWindows) {
configPath = Platform.homePath + "\\AppData\\Local\\myapp\\config.json"
} else {
configPath = Platform.homePath + "/.config/myapp/config.json"
}
System.print("Config path: %(configPath)")</code></pre>
<h3>Processing Command-Line Arguments</h3>
<pre><code>import "os" for Process
var args = Process.arguments
if (args.count == 0) {
System.print("Usage: script.wren <command> [options]")
} else {
var command = args[0]
if (command == "help") {
System.print("Available commands: help, version, run")
} else if (command == "version") {
System.print("Version 1.0.0")
} else if (command == "run") {
if (args.count > 1) {
System.print("Running: %(args[1])")
} else {
System.print("Error: run requires a file argument")
}
} else {
System.print("Unknown command: %(command)")
}
}</code></pre>
<h3>Script Information</h3>
<pre><code>import "os" for Platform, Process
System.print("=== System Information ===")
System.print("Platform: %(Platform.name)")
System.print("POSIX: %(Platform.isPosix)")
System.print("Home: %(Platform.homePath)")
System.print("")
System.print("=== Process Information ===")
System.print("PID: %(Process.pid)")
System.print("Parent PID: %(Process.ppid)")
System.print("Working Directory: %(Process.cwd)")
System.print("Wren Version: %(Process.version)")
System.print("")
System.print("=== Arguments ===")
System.print("Arguments: %(Process.arguments)")</code></pre>
<h3>Building File Paths</h3>
<pre><code>import "os" for Platform, Process
var separator = Platform.isWindows ? "\\" : "/"
var dataDir = Process.cwd + separator + "data"
var configFile = Platform.homePath + separator + ".myapprc"
System.print("Data directory: %(dataDir)")
System.print("Config file: %(configFile)")</code></pre>
<h3>Argument Parsing with Flags</h3>
<pre><code>import "os" for Process
var verbose = false
var outputFile = "output.txt"
var inputFiles = []
var i = 0
var args = Process.arguments
while (i < args.count) {
var arg = args[i]
if (arg == "-v" || arg == "--verbose") {
verbose = true
} else if (arg == "-o" || arg == "--output") {
i = i + 1
if (i < args.count) {
outputFile = args[i]
}
} else if (!arg.startsWith("-")) {
inputFiles.add(arg)
}
i = i + 1
}
System.print("Verbose: %(verbose)")
System.print("Output: %(outputFile)")
System.print("Input files: %(inputFiles)")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p><code>Process.arguments</code> returns only the user's arguments (after the script path), while <code>Process.allArguments</code> includes the full command line including the interpreter.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Use <code>Platform.isPosix</code> to write cross-platform code that handles path separators, shell commands, and other platform-specific differences.</p>
</div>
{% endblock %}
+573
View File
@@ -0,0 +1,573 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "pathlib" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "pathlib"}] %}
{% set prev_page = {"url": "api/io.html", "title": "io"} %}
{% set next_page = {"url": "api/scheduler.html", "title": "scheduler"} %}
{% block article %}
<h1>pathlib</h1>
<p>The <code>pathlib</code> module provides object-oriented filesystem path operations. Inspired by Python's <code>pathlib</code>, it offers immutable <code>Path</code> objects that combine path manipulation with filesystem I/O in a single interface.</p>
<pre><code>import "pathlib" for Path</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#purepath-class">PurePath Class</a></li>
<li><a href="#path-class">Path Class</a></li>
<li><a href="#path-properties">Path Properties</a></li>
<li><a href="#path-manipulation">Path Manipulation</a></li>
<li><a href="#filesystem-operations">Filesystem Operations</a></li>
<li><a href="#directory-operations">Directory Operations</a></li>
<li><a href="#symlink-operations">Symlink Operations</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="purepath-class">PurePath Class</h2>
<div class="class-header">
<h3>PurePath</h3>
<p>String-only path operations without filesystem access. Base class for <code>Path</code>.</p>
</div>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">PurePath.new</span>(<span class="param">path</span>) &#8594; <span class="type">PurePath</span>
</div>
<p>Creates a new PurePath from a string or another PurePath.</p>
<pre><code>var p = PurePath.new("/home/user/file.txt")</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">parts</span> &#8594; <span class="type">List</span>
</div>
<p>The individual components of the path.</p>
<pre><code>var p = Path.new("/home/user/file.txt")
System.print(p.parts) // [/, home, user, file.txt]</code></pre>
<div class="method-signature">
<span class="method-name">name</span> &#8594; <span class="type">String</span>
</div>
<p>The final component of the path (filename with extension).</p>
<pre><code>var p = Path.new("/home/user/file.tar.gz")
System.print(p.name) // file.tar.gz</code></pre>
<div class="method-signature">
<span class="method-name">stem</span> &#8594; <span class="type">String</span>
</div>
<p>The filename without the last extension.</p>
<pre><code>var p = Path.new("/home/user/file.tar.gz")
System.print(p.stem) // file.tar</code></pre>
<div class="method-signature">
<span class="method-name">suffix</span> &#8594; <span class="type">String</span>
</div>
<p>The last file extension, including the leading dot.</p>
<pre><code>var p = Path.new("/home/user/file.tar.gz")
System.print(p.suffix) // .gz</code></pre>
<div class="method-signature">
<span class="method-name">suffixes</span> &#8594; <span class="type">List</span>
</div>
<p>All file extensions as a list.</p>
<pre><code>var p = Path.new("/home/user/file.tar.gz")
System.print(p.suffixes) // [.tar, .gz]</code></pre>
<div class="method-signature">
<span class="method-name">parent</span> &#8594; <span class="type">Path</span>
</div>
<p>The logical parent of the path.</p>
<pre><code>var p = Path.new("/home/user/file.txt")
System.print(p.parent) // /home/user</code></pre>
<div class="method-signature">
<span class="method-name">parents</span> &#8594; <span class="type">List</span>
</div>
<p>A list of all ancestor paths, from immediate parent to root.</p>
<pre><code>var p = Path.new("/home/user/docs/file.txt")
for (ancestor in p.parents) {
System.print(ancestor)
}
// /home/user/docs
// /home/user
// /home
// /</code></pre>
<div class="method-signature">
<span class="method-name">drive</span> &#8594; <span class="type">String</span>
</div>
<p>The drive letter (Windows only). Returns empty string on POSIX.</p>
<div class="method-signature">
<span class="method-name">root</span> &#8594; <span class="type">String</span>
</div>
<p>The root of the path. Returns <code>"/"</code> for absolute paths, empty string for relative.</p>
<div class="method-signature">
<span class="method-name">anchor</span> &#8594; <span class="type">String</span>
</div>
<p>The concatenation of drive and root.</p>
<div class="method-signature">
<span class="method-name">isAbsolute</span> &#8594; <span class="type">Bool</span>
</div>
<p>True if the path is absolute (has a root).</p>
<pre><code>System.print(Path.new("/etc/hosts").isAbsolute) // true
System.print(Path.new("relative/path").isAbsolute) // false</code></pre>
<h2 id="path-manipulation">Path Manipulation</h2>
<div class="method-signature">
<span class="method-name">path</span> / <span class="param">other</span> &#8594; <span class="type">Path</span>
</div>
<p>Joins two paths using the <code>/</code> operator. If <code>other</code> is absolute, it replaces the current path.</p>
<pre><code>var base = Path.new("/home/user")
var full = base / "documents" / "file.txt"
System.print(full) // /home/user/documents/file.txt</code></pre>
<div class="method-signature">
<span class="method-name">joinpath</span>(<span class="param">other</span>) &#8594; <span class="type">Path</span>
</div>
<p>Joins paths. Accepts a string, Path, or List of components.</p>
<pre><code>var p = Path.new("/home").joinpath("user").joinpath("file.txt")
System.print(p) // /home/user/file.txt
var q = Path.new("/home").joinpath(["user", "docs", "file.txt"])
System.print(q) // /home/user/docs/file.txt</code></pre>
<div class="method-signature">
<span class="method-name">withName</span>(<span class="param">newName</span>) &#8594; <span class="type">Path</span>
</div>
<p>Returns a new path with the filename replaced.</p>
<pre><code>var p = Path.new("/home/user/old.txt")
System.print(p.withName("new.txt")) // /home/user/new.txt</code></pre>
<div class="method-signature">
<span class="method-name">withStem</span>(<span class="param">newStem</span>) &#8594; <span class="type">Path</span>
</div>
<p>Returns a new path with the stem replaced, keeping the extension.</p>
<pre><code>var p = Path.new("/data/archive.tar.gz")
System.print(p.withStem("backup")) // /data/backup.gz</code></pre>
<div class="method-signature">
<span class="method-name">withSuffix</span>(<span class="param">newSuffix</span>) &#8594; <span class="type">Path</span>
</div>
<p>Returns a new path with the extension replaced.</p>
<pre><code>var p = Path.new("/data/file.txt")
System.print(p.withSuffix(".md")) // /data/file.md</code></pre>
<div class="method-signature">
<span class="method-name">relativeTo</span>(<span class="param">base</span>) &#8594; <span class="type">Path</span>
</div>
<p>Returns a relative path from <code>base</code> to this path. Aborts if the path is not relative to <code>base</code>.</p>
<pre><code>var p = Path.new("/home/user/docs/file.txt")
System.print(p.relativeTo("/home/user")) // docs/file.txt</code></pre>
<div class="method-signature">
<span class="method-name">match</span>(<span class="param">pattern</span>) &#8594; <span class="type">Bool</span>
</div>
<p>Matches the filename against a glob pattern. Supports <code>*</code> and <code>?</code> wildcards.</p>
<pre><code>var p = Path.new("/home/user/notes.txt")
System.print(p.match("*.txt")) // true
System.print(p.match("*.md")) // false</code></pre>
<div class="method-signature">
<span class="method-name">asPosix</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the path string with forward slashes (converts backslashes on Windows).</p>
<div class="method-signature">
<span class="method-name">expanduser</span>() &#8594; <span class="type">Path</span>
</div>
<p>Expands a leading <code>~</code> to the user's home directory.</p>
<pre><code>var p = Path.new("~/.config/app")
System.print(p.expanduser()) // /home/alice/.config/app</code></pre>
<h3>Operators</h3>
<div class="method-signature">
<span class="method-name">==</span>(<span class="param">other</span>) &#8594; <span class="type">Bool</span>
</div>
<p>Compares two paths for equality by their string representation.</p>
<div class="method-signature">
<span class="method-name">!=</span>(<span class="param">other</span>) &#8594; <span class="type">Bool</span>
</div>
<p>Compares two paths for inequality.</p>
<div class="method-signature">
<span class="method-name">toString</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the path as a string.</p>
<h2 id="path-class">Path Class</h2>
<div class="class-header">
<h3>Path</h3>
<p>Extends PurePath with filesystem operations. This is the primary class for working with paths.</p>
</div>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">Path.new</span>(<span class="param">path</span>) &#8594; <span class="type">Path</span>
</div>
<p>Creates a new Path from a string or another Path.</p>
<pre><code>var p = Path.new("/home/user/file.txt")</code></pre>
<h3>Class Properties</h3>
<div class="method-signature">
<span class="method-name">Path.cwd</span> &#8594; <span class="type">Path</span>
</div>
<p>The current working directory as a Path.</p>
<pre><code>System.print(Path.cwd) // /home/user/projects</code></pre>
<div class="method-signature">
<span class="method-name">Path.home</span> &#8594; <span class="type">Path</span>
</div>
<p>The current user's home directory as a Path.</p>
<pre><code>System.print(Path.home) // /home/alice</code></pre>
<h2 id="filesystem-operations">Filesystem Operations</h2>
<div class="method-signature">
<span class="method-name">exists</span>() &#8594; <span class="type">Bool</span>
</div>
<p>Returns true if the path points to an existing file or directory.</p>
<pre><code>if (Path.new("/etc/hosts").exists()) {
System.print("File exists")
}</code></pre>
<div class="method-signature">
<span class="method-name">isFile</span>() &#8594; <span class="type">Bool</span>
</div>
<p>Returns true if the path points to a regular file.</p>
<div class="method-signature">
<span class="method-name">isDir</span>() &#8594; <span class="type">Bool</span>
</div>
<p>Returns true if the path points to a directory.</p>
<div class="method-signature">
<span class="method-name">isSymlink</span>() &#8594; <span class="type">Bool</span>
</div>
<p>Returns true if the path is a symbolic link.</p>
<div class="method-signature">
<span class="method-name">stat</span>() &#8594; <span class="type">Stat</span>
</div>
<p>Returns a Stat object with file metadata (size, mode, timestamps).</p>
<pre><code>var s = Path.new("/etc/hosts").stat()
System.print(s.size) // 234</code></pre>
<div class="method-signature">
<span class="method-name">lstat</span>() &#8594; <span class="type">Stat</span>
</div>
<p>Like <code>stat()</code> but does not follow symbolic links.</p>
<div class="method-signature">
<span class="method-name">readText</span>() &#8594; <span class="type">String</span>
</div>
<p>Reads the entire file contents as a string.</p>
<pre><code>var content = Path.new("config.json").readText()
System.print(content)</code></pre>
<div class="method-signature">
<span class="method-name">readBytes</span>() &#8594; <span class="type">String</span>
</div>
<p>Reads the entire file contents as bytes.</p>
<div class="method-signature">
<span class="method-name">writeText</span>(<span class="param">content</span>)
</div>
<p>Writes a string to the file, creating or overwriting it.</p>
<pre><code>Path.new("output.txt").writeText("Hello, World!")</code></pre>
<div class="method-signature">
<span class="method-name">writeBytes</span>(<span class="param">content</span>)
</div>
<p>Writes bytes to the file, creating or overwriting it.</p>
<div class="method-signature">
<span class="method-name">touch</span>()
</div>
<p>Creates an empty file if it does not exist, or updates its modification time if it does.</p>
<pre><code>Path.new("marker.txt").touch()</code></pre>
<div class="method-signature">
<span class="method-name">unlink</span>()
</div>
<p>Deletes the file.</p>
<div class="method-signature">
<span class="method-name">rename</span>(<span class="param">target</span>) &#8594; <span class="type">Path</span>
</div>
<p>Moves or renames the file to <code>target</code>. Returns the new Path.</p>
<pre><code>var old = Path.new("draft.txt")
var published = old.rename("article.txt")
System.print(published) // article.txt</code></pre>
<div class="method-signature">
<span class="method-name">replace</span>(<span class="param">target</span>) &#8594; <span class="type">Path</span>
</div>
<p>Alias for <code>rename()</code>. Replaces the target if it exists.</p>
<div class="method-signature">
<span class="method-name">copyfile</span>(<span class="param">dest</span>)
</div>
<p>Copies this file to <code>dest</code>.</p>
<pre><code>Path.new("original.txt").copyfile("backup.txt")</code></pre>
<div class="method-signature">
<span class="method-name">size</span>() &#8594; <span class="type">Num</span>
</div>
<p>Returns the size of the file in bytes.</p>
<pre><code>var bytes = Path.new("data.txt").size()
System.print("File size: %(bytes) bytes")</code></pre>
<div class="method-signature">
<span class="method-name">mtime</span>() &#8594; <span class="type">Num</span>
</div>
<p>Returns the modification time as a Unix timestamp (seconds since epoch).</p>
<pre><code>var modified = Path.new("file.txt").mtime()
System.print("Last modified: %(modified)")</code></pre>
<div class="method-signature">
<span class="method-name">atime</span>() &#8594; <span class="type">Num</span>
</div>
<p>Returns the access time as a Unix timestamp.</p>
<div class="method-signature">
<span class="method-name">ctime</span>() &#8594; <span class="type">Num</span>
</div>
<p>Returns the change time (inode change) as a Unix timestamp.</p>
<div class="method-signature">
<span class="method-name">chmod</span>(<span class="param">mode</span>)
</div>
<p>Changes the file permissions. Mode is an octal number.</p>
<pre><code>Path.new("script.sh").chmod(0x1ed) // 0755 in octal</code></pre>
<div class="method-signature">
<span class="method-name">resolve</span>() &#8594; <span class="type">Path</span>
</div>
<p>Returns the absolute real path, resolving any symlinks.</p>
<pre><code>var real = Path.new("./relative/../file.txt").resolve()
System.print(real) // /home/user/file.txt</code></pre>
<div class="method-signature">
<span class="method-name">samefile</span>(<span class="param">other</span>) &#8594; <span class="type">Bool</span>
</div>
<p>Returns true if this path and <code>other</code> refer to the same file (compares inodes).</p>
<div class="method-signature">
<span class="method-name">owner</span>() &#8594; <span class="type">String</span>
</div>
<p>Returns the username of the file owner (POSIX only).</p>
<div class="method-signature">
<span class="method-name">group</span>() &#8594; <span class="type">String</span>
</div>
<p>Returns the group name of the file (POSIX only).</p>
<h2 id="directory-operations">Directory Operations</h2>
<div class="method-signature">
<span class="method-name">mkdir</span>()
</div>
<p>Creates the directory.</p>
<div class="method-signature">
<span class="method-name">mkdir</span>(<span class="param">parents</span>)
</div>
<p>Creates the directory. If <code>parents</code> is true, creates all intermediate directories.</p>
<pre><code>Path.new("/tmp/a/b/c").mkdir(true)</code></pre>
<div class="method-signature">
<span class="method-name">rmdir</span>()
</div>
<p>Removes an empty directory.</p>
<div class="method-signature">
<span class="method-name">rmtree</span>()
</div>
<p>Recursively deletes the directory and all its contents.</p>
<pre><code>Path.new("/tmp/build_output").rmtree()</code></pre>
<div class="method-signature">
<span class="method-name">iterdir</span>() &#8594; <span class="type">List</span>
</div>
<p>Returns a list of Path objects for each entry in the directory.</p>
<pre><code>for (entry in Path.new(".").iterdir()) {
System.print(entry.name)
}</code></pre>
<div class="method-signature">
<span class="method-name">glob</span>(<span class="param">pattern</span>) &#8594; <span class="type">List</span>
</div>
<p>Returns paths matching the glob pattern in this directory. Supports <code>*</code> and <code>?</code> wildcards.</p>
<pre><code>var txtFiles = Path.new(".").glob("*.txt")
for (f in txtFiles) {
System.print(f.name)
}</code></pre>
<div class="method-signature">
<span class="method-name">rglob</span>(<span class="param">pattern</span>) &#8594; <span class="type">List</span>
</div>
<p>Recursively matches files in this directory and all subdirectories.</p>
<pre><code>var allWren = Path.cwd.rglob("*.wren")
System.print("Found %(allWren.count) Wren files")</code></pre>
<div class="method-signature">
<span class="method-name">walk</span>() &#8594; <span class="type">List</span>
</div>
<p>Recursively walks the directory tree top-down. Returns a list of <code>[dirPath, dirNames, fileNames]</code> tuples.</p>
<pre><code>for (entry in Path.new("src").walk()) {
var dir = entry[0]
var dirs = entry[1]
var files = entry[2]
for (f in files) {
System.print(dir / f)
}
}</code></pre>
<div class="method-signature">
<span class="method-name">walk</span>(<span class="param">topDown</span>) &#8594; <span class="type">List</span>
</div>
<p>Walks the directory tree. If <code>topDown</code> is false, visits child directories before parents.</p>
<h2 id="symlink-operations">Symlink Operations</h2>
<div class="method-signature">
<span class="method-name">symlinkTo</span>(<span class="param">target</span>)
</div>
<p>Creates a symbolic link at this path pointing to <code>target</code>.</p>
<pre><code>Path.new("/tmp/link").symlinkTo("/etc/hosts")</code></pre>
<div class="method-signature">
<span class="method-name">hardlinkTo</span>(<span class="param">target</span>)
</div>
<p>Creates a hard link at this path pointing to <code>target</code>.</p>
<div class="method-signature">
<span class="method-name">readlink</span>() &#8594; <span class="type">String</span>
</div>
<p>Returns the target of the symbolic link.</p>
<h2 id="examples">Examples</h2>
<h3>Configuration File Management</h3>
<pre><code>import "pathlib" for Path
import "json" for Json
var configDir = Path.home / ".config" / "myapp"
if (!configDir.exists()) {
configDir.mkdir(true)
}
var configFile = configDir / "settings.json"
if (!configFile.exists()) {
configFile.writeText(Json.stringify({
"theme": "dark",
"language": "en"
}, 2))
}
var settings = Json.parse(configFile.readText())
System.print("Theme: %(settings["theme"])")</code></pre>
<h3>Batch File Processing</h3>
<pre><code>import "pathlib" for Path
var dataDir = Path.new("./data")
for (file in dataDir.glob("*.csv")) {
var size = file.stat().size
System.print("%(file.name): %(size) bytes")
}
var total = 0
for (wren in Path.cwd.rglob("*.wren")) {
total = total + 1
}
System.print("Found %(total) Wren source files")</code></pre>
<h3>Directory Tree Traversal</h3>
<pre><code>import "pathlib" for Path
var src = Path.new("src")
for (entry in src.walk()) {
var dir = entry[0]
var files = entry[2]
if (files.count > 0) {
System.print("%(dir.relativeTo(src)):")
for (f in files) {
System.print(" %(f)")
}
}
}</code></pre>
<h3>Path Manipulation</h3>
<pre><code>import "pathlib" for Path
var p = Path.new("/home/user/documents/report.tar.gz")
System.print(p.name) // report.tar.gz
System.print(p.stem) // report.tar
System.print(p.suffix) // .gz
System.print(p.suffixes) // [.tar, .gz]
System.print(p.parent) // /home/user/documents
var backup = p.withSuffix(".bak")
System.print(backup) // /home/user/documents/report.tar.bak
var renamed = p.withName("summary.pdf")
System.print(renamed) // /home/user/documents/summary.pdf</code></pre>
<h3>Temporary File Cleanup</h3>
<pre><code>import "pathlib" for Path
var tmpDir = Path.new("/tmp/build_artifacts")
if (tmpDir.exists()) {
tmpDir.rmtree()
}
tmpDir.mkdir(true)
(tmpDir / "output.o").touch()
(tmpDir / "output.bin").touch()
var count = tmpDir.iterdir().count
System.print("%(count) files in build directory")
tmpDir.rmtree()</code></pre>
<h3>Home Directory Expansion</h3>
<pre><code>import "pathlib" for Path
var shortcuts = [
Path.new("~/.bashrc"),
Path.new("~/.config"),
Path.new("~/Documents")
]
for (p in shortcuts) {
var expanded = p.expanduser()
System.print("%(p) -> %(expanded) (exists: %(expanded.exists()))")
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Path objects are immutable. Methods like <code>withName()</code>, <code>withSuffix()</code>, and <code>joinpath()</code> return new Path objects rather than modifying the original.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Use the <code>/</code> operator for readable path construction: <code>Path.home / ".config" / "myapp" / "settings.json"</code> reads naturally and handles separator insertion automatically.</p>
</div>
{% endblock %}
+269
View File
@@ -0,0 +1,269 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "regex" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "regex"}] %}
{% set prev_page = {"url": "api/base64.html", "title": "base64"} %}
{% set next_page = {"url": "api/jinja.html", "title": "jinja"} %}
{% block article %}
<h1>regex</h1>
<p>The <code>regex</code> module provides regular expression matching, replacement, and splitting using PCRE-compatible patterns.</p>
<pre><code>import "regex" for Regex, Match</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#regex-class">Regex Class</a></li>
<li><a href="#match-class">Match Class</a></li>
<li><a href="#pattern-syntax">Pattern Syntax</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="regex-class">Regex Class</h2>
<div class="class-header">
<h3>Regex</h3>
<p>Regular expression pattern</p>
</div>
<h3>Constructors</h3>
<div class="method-signature">
<span class="method-name">Regex.new</span>(<span class="param">pattern</span>) → <span class="type">Regex</span>
</div>
<p>Creates a new regex from a pattern string.</p>
<pre><code>var re = Regex.new("\\d+") // Match digits</code></pre>
<div class="method-signature">
<span class="method-name">Regex.new</span>(<span class="param">pattern</span>, <span class="param">flags</span>) → <span class="type">Regex</span>
</div>
<p>Creates a regex with flags.</p>
<ul class="param-list">
<li><span class="param-name">pattern</span> <span class="param-type">(String)</span> - Regex pattern</li>
<li><span class="param-name">flags</span> <span class="param-type">(String)</span> - Flags: "i" (case-insensitive), "m" (multiline), "s" (dotall)</li>
</ul>
<pre><code>var re = Regex.new("hello", "i") // Case-insensitive</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">pattern</span><span class="type">String</span>
</div>
<p>The pattern string.</p>
<div class="method-signature">
<span class="method-name">flags</span><span class="type">String</span>
</div>
<p>The flags string.</p>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">test</span>(<span class="param">string</span>) → <span class="type">Bool</span>
</div>
<p>Tests if the pattern matches anywhere in the string.</p>
<pre><code>var re = Regex.new("\\d+")
System.print(re.test("abc123")) // true
System.print(re.test("abc")) // false</code></pre>
<div class="method-signature">
<span class="method-name">match</span>(<span class="param">string</span>) → <span class="type">Match|null</span>
</div>
<p>Finds the first match in the string.</p>
<pre><code>var re = Regex.new("(\\w+)@(\\w+\\.\\w+)")
var m = re.match("email: alice@example.com")
if (m != null) {
System.print(m.text) // alice@example.com
System.print(m.group(1)) // alice
System.print(m.group(2)) // example.com
}</code></pre>
<div class="method-signature">
<span class="method-name">matchAll</span>(<span class="param">string</span>) → <span class="type">List</span>
</div>
<p>Finds all matches in the string.</p>
<pre><code>var re = Regex.new("\\d+")
var matches = re.matchAll("a1 b22 c333")
for (m in matches) {
System.print(m.text) // 1, 22, 333
}</code></pre>
<div class="method-signature">
<span class="method-name">replace</span>(<span class="param">string</span>, <span class="param">replacement</span>) → <span class="type">String</span>
</div>
<p>Replaces the first match with the replacement string.</p>
<pre><code>var re = Regex.new("\\d+")
System.print(re.replace("a1b2c3", "X")) // aXb2c3</code></pre>
<div class="method-signature">
<span class="method-name">replaceAll</span>(<span class="param">string</span>, <span class="param">replacement</span>) → <span class="type">String</span>
</div>
<p>Replaces all matches with the replacement string.</p>
<pre><code>var re = Regex.new("\\d+")
System.print(re.replaceAll("a1b2c3", "X")) // aXbXcX</code></pre>
<div class="method-signature">
<span class="method-name">split</span>(<span class="param">string</span>) → <span class="type">List</span>
</div>
<p>Splits the string by the pattern.</p>
<pre><code>var re = Regex.new("[,;\\s]+")
var parts = re.split("a, b; c d")
System.print(parts) // [a, b, c, d]</code></pre>
<h2 id="match-class">Match Class</h2>
<div class="class-header">
<h3>Match</h3>
<p>Regex match result</p>
</div>
<h3>Properties</h3>
<table>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>text</code></td>
<td>String</td>
<td>The matched text</td>
</tr>
<tr>
<td><code>start</code></td>
<td>Num</td>
<td>Start index in the original string</td>
</tr>
<tr>
<td><code>end</code></td>
<td>Num</td>
<td>End index in the original string</td>
</tr>
<tr>
<td><code>groups</code></td>
<td>List</td>
<td>List where index 0 is the entire match, index 1+ are capture groups</td>
</tr>
</table>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">group</span>(<span class="param">index</span>) → <span class="type">String|null</span>
</div>
<p>Gets a captured group by index. Group 0 is the entire match.</p>
<pre><code>var re = Regex.new("(\\w+)-(\\d+)")
var m = re.match("item-42")
System.print(m.group(0)) // item-42
System.print(m.group(1)) // item
System.print(m.group(2)) // 42</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>The <code>groups</code> list uses 0-based indexing where <code>groups[0]</code> is the entire match (same as <code>text</code>), and <code>groups[1]</code>, <code>groups[2]</code>, etc. are the captured groups. This is consistent with <code>group(0)</code>, <code>group(1)</code>, etc.</p>
</div>
<h2 id="pattern-syntax">Pattern Syntax</h2>
<h3>Character Classes</h3>
<table>
<tr><th>Pattern</th><th>Matches</th></tr>
<tr><td><code>.</code></td><td>Any character (except newline)</td></tr>
<tr><td><code>\d</code></td><td>Digit (0-9)</td></tr>
<tr><td><code>\D</code></td><td>Non-digit</td></tr>
<tr><td><code>\w</code></td><td>Word character (a-z, A-Z, 0-9, _)</td></tr>
<tr><td><code>\W</code></td><td>Non-word character</td></tr>
<tr><td><code>\s</code></td><td>Whitespace</td></tr>
<tr><td><code>\S</code></td><td>Non-whitespace</td></tr>
<tr><td><code>[abc]</code></td><td>Any of a, b, or c</td></tr>
<tr><td><code>[^abc]</code></td><td>Not a, b, or c</td></tr>
<tr><td><code>[a-z]</code></td><td>Range a through z</td></tr>
</table>
<h3>Quantifiers</h3>
<table>
<tr><th>Pattern</th><th>Matches</th></tr>
<tr><td><code>*</code></td><td>0 or more</td></tr>
<tr><td><code>+</code></td><td>1 or more</td></tr>
<tr><td><code>?</code></td><td>0 or 1</td></tr>
<tr><td><code>{n}</code></td><td>Exactly n times</td></tr>
<tr><td><code>{n,}</code></td><td>n or more times</td></tr>
<tr><td><code>{n,m}</code></td><td>Between n and m times</td></tr>
</table>
<h3>Anchors</h3>
<table>
<tr><th>Pattern</th><th>Matches</th></tr>
<tr><td><code>^</code></td><td>Start of string/line</td></tr>
<tr><td><code>$</code></td><td>End of string/line</td></tr>
<tr><td><code>\b</code></td><td>Word boundary</td></tr>
<tr><td><code>\B</code></td><td>Non-word boundary</td></tr>
</table>
<h3>Groups</h3>
<table>
<tr><th>Pattern</th><th>Description</th></tr>
<tr><td><code>(abc)</code></td><td>Capturing group</td></tr>
<tr><td><code>(?:abc)</code></td><td>Non-capturing group</td></tr>
<tr><td><code>a|b</code></td><td>Alternation (a or b)</td></tr>
</table>
<h2 id="examples">Examples</h2>
<h3>Email Validation</h3>
<pre><code>import "regex" for Regex
var emailRe = Regex.new("^[\\w.+-]+@[\\w-]+\\.[\\w.-]+$")
var emails = ["alice@example.com", "invalid", "bob@test.org"]
for (email in emails) {
if (emailRe.test(email)) {
System.print("%(email) is valid")
} else {
System.print("%(email) is invalid")
}
}</code></pre>
<h3>Extracting Data</h3>
<pre><code>import "regex" for Regex
var logRe = Regex.new("(\\d{4}-\\d{2}-\\d{2}) (\\w+): (.+)")
var log = "2024-01-15 ERROR: Connection failed"
var m = logRe.match(log)
if (m != null) {
System.print("Date: %(m.group(1))") // 2024-01-15
System.print("Level: %(m.group(2))") // ERROR
System.print("Message: %(m.group(3))") // Connection failed
}</code></pre>
<h3>Find and Replace</h3>
<pre><code>import "regex" for Regex
var text = "Call 555-1234 or 555-5678"
var phoneRe = Regex.new("\\d{3}-\\d{4}")
var redacted = phoneRe.replaceAll(text, "XXX-XXXX")
System.print(redacted) // Call XXX-XXXX or XXX-XXXX</code></pre>
<h3>Parsing URLs</h3>
<pre><code>import "regex" for Regex
var urlRe = Regex.new("(https?)://([^/]+)(/.*)?")
var url = "https://example.com/path/to/page"
var m = urlRe.match(url)
System.print("Scheme: %(m.group(1))") // https
System.print("Host: %(m.group(2))") // example.com
System.print("Path: %(m.group(3))") // /path/to/page</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Remember to escape backslashes in Wren strings. Use <code>\\d</code> instead of <code>\d</code>.</p>
</div>
{% endblock %}
+255
View File
@@ -0,0 +1,255 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "scheduler" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "scheduler"}] %}
{% set prev_page = {"url": "api/pathlib.html", "title": "pathlib"} %}
{% set next_page = {"url": "api/math.html", "title": "math"} %}
{% block article %}
<h1>scheduler</h1>
<p>The <code>scheduler</code> module manages the async event loop and fiber scheduling. It is the foundation for all async operations in Wren-CLI.</p>
<pre><code>import "scheduler" for Scheduler, Future</code></pre>
<div class="admonition note">
<div class="admonition-title">Quick Reference</div>
<p>The scheduler module enables async programming with <code>async</code> and <code>await</code>:</p>
<ul>
<li><code>async { code }</code> — Create an async function</li>
<li><code>await fn()</code> — Call async function and wait for result</li>
<li><code>fn.call()</code> — Start async function, return Future (for concurrent execution)</li>
</ul>
</div>
<h2>Scheduler Class</h2>
<div class="class-header">
<h3>Scheduler</h3>
<p>Async fiber scheduler</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Scheduler.await_</span>(<span class="param">block</span>) → <span class="type">any</span>
</div>
<p>Suspends the current fiber until an async operation completes. Used internally by modules to implement async operations.</p>
<ul class="param-list">
<li><span class="param-name">block</span> <span class="param-type">(Fn)</span> - Block that initiates the async operation</li>
<li><span class="returns">Returns:</span> Result of the async operation</li>
</ul>
<pre><code>var result = Scheduler.await_ {
Timer.sleep_(1000, Fiber.current)
}</code></pre>
<div class="method-signature">
<span class="method-name">Scheduler.resume_</span>(<span class="param">fiber</span>)
</div>
<p>Resumes a suspended fiber. Used by native code to wake up fibers when async operations complete.</p>
<div class="method-signature">
<span class="method-name">Scheduler.resume_</span>(<span class="param">fiber</span>, <span class="param">value</span>)
</div>
<p>Resumes a fiber with a result value.</p>
<h2 id="await-syntax">Await Syntax</h2>
<p>Wren-CLI provides a convenient syntax for awaiting async functions. Instead of using <code>.call()</code>, you can call async functions directly after <code>await</code>:</p>
<h3>Before and After</h3>
<pre><code>// Old syntax (still works)
var result = await asyncFn.call(arg1, arg2)
// New syntax (recommended)
var result = await asyncFn(arg1, arg2)</code></pre>
<p>The compiler automatically translates <code>await fn(args)</code> to <code>await fn.call(args)</code>.</p>
<h3>Supported Patterns</h3>
<table class="params-table">
<thead>
<tr><th>Pattern</th><th>Example</th></tr>
</thead>
<tbody>
<tr><td>No arguments</td><td><code>await getValue()</code></td></tr>
<tr><td>Single argument</td><td><code>await double(21)</code></td></tr>
<tr><td>Multiple arguments</td><td><code>await add(3, 4, 5)</code></td></tr>
<tr><td>Nested awaits</td><td><code>await outer(await inner(x))</code></td></tr>
<tr><td>In expressions</td><td><code>var x = 1 + await fn(2)</code></td></tr>
<tr><td>In conditions</td><td><code>if (await check(x)) { }</code></td></tr>
<tr><td>Block argument</td><td><code>await map(list) { |x| x * 2 }</code></td></tr>
</tbody>
</table>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Method chaining after <code>await fn(args)</code> requires assigning the result first:</p>
<pre><code>var list = await getList(5)
System.print(list.count)</code></pre>
</div>
<h3>Examples</h3>
<pre><code>import "scheduler" for Scheduler, Future
var double = async { |x| x * 2 }
var add = async { |a, b| a + b }
// Basic usage
System.print(await double(21)) // 42
System.print(await add(3, 4)) // 7
// Nested awaits
var result = await double(await add(5, 5)) // 20
// Method chaining
var getList = async { |n| [1, 2, 3, n] }
System.print(await getList(4).count) // 4
// In expressions
var total = await add(10, 20) + await double(5) // 40</code></pre>
<h3>Class-Based Async Patterns</h3>
<p>Static getters that return async functions must first be assigned to a variable:</p>
<pre><code>import "scheduler" for Scheduler, Future
class Calculator {
static add { async { |a, b| a + b } }
static multiply { async { |a, b| a * b } }
static square { async { |x| x * x } }
static compute(a, b) {
var addFn = Calculator.add
var multiplyFn = Calculator.multiply
var sum = await addFn(a, b)
var product = await multiplyFn(a, b)
return [sum, product]
}
}
// Assign getter to variable first, then await
var add = Calculator.add
System.print(await add(3, 4)) // 7
var multiply = Calculator.multiply
System.print(await multiply(5, 6)) // 30
var square = Calculator.square
System.print(await square(8)) // 64</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>The <code>fn(args)</code> syntax only works directly after <code>await</code>. Outside of <code>await</code>, use <code>fn.call(args)</code> to invoke async functions.</p>
</div>
<h2>Future Class</h2>
<div class="class-header">
<h3>Future</h3>
<p>Represents a pending async result</p>
</div>
<p>A <code>Future</code> is returned when you call an async function with <code>.call()</code> instead of using <code>await</code> directly. This enables concurrent execution by starting multiple operations without waiting.</p>
<h3>Creating Futures</h3>
<pre><code>import "scheduler" for Scheduler, Future
var double = async { |x| x * 2 }
// Direct call with await - waits immediately
var result = await double(21)
// Using .call() returns a Future - does not wait
var future = double.call(21)
// Later, await the future to get the result
var result = await future</code></pre>
<h3>Concurrent Execution Pattern</h3>
<pre><code>import "scheduler" for Scheduler, Future
import "web" for Client
var fetch = async { |url| Client.get(url) }
// Start all requests at once (returns Futures)
var f1 = fetch.call("https://api.example.com/users")
var f2 = fetch.call("https://api.example.com/posts")
var f3 = fetch.call("https://api.example.com/comments")
// All three requests are now running concurrently
// Wait for each result
var users = await f1
var posts = await f2
var comments = await f3</code></pre>
<h2>How Async Works</h2>
<p>Wren-CLI uses an event loop (libuv) for non-blocking I/O. Here is how async operations work:</p>
<ol>
<li>A Wren fiber calls an async method (e.g., <code>Http.get</code>)</li>
<li>The method starts a native async operation and suspends the fiber</li>
<li>The event loop continues processing other events</li>
<li>When the operation completes, the fiber is resumed with the result</li>
</ol>
<h3>Under the Hood</h3>
<pre><code>// How Timer.sleep works internally
class Timer {
static sleep(ms) {
return Scheduler.await_ {
Timer.sleep_(ms, Fiber.current)
}
}
foreign static sleep_(ms, fiber)
}</code></pre>
<h2>Examples</h2>
<h3>Sequential vs Concurrent Execution</h3>
<p>Using <code>await fn(args)</code> executes operations sequentially:</p>
<pre><code>import "scheduler" for Scheduler, Future
import "web" for Client
var fetch = async { |url| Client.get(url) }
// Sequential: each request waits for the previous one
var r1 = await fetch("https://api.example.com/1")
var r2 = await fetch("https://api.example.com/2")
var r3 = await fetch("https://api.example.com/3")</code></pre>
<p>Using <code>.call()</code> enables concurrent execution:</p>
<pre><code>import "scheduler" for Scheduler, Future
import "web" for Client
var fetch = async { |url| Client.get(url) }
// Start all requests at once (concurrent)
var f1 = fetch.call("https://api.example.com/1")
var f2 = fetch.call("https://api.example.com/2")
var f3 = fetch.call("https://api.example.com/3")
// All three requests are running in parallel
// Now wait for results
var r1 = await f1
var r2 = await f2
var r3 = await f3</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>The scheduler is used internally by modules. Most user code does not need to interact with it directly. Use higher-level modules like <code>timer</code>, <code>http</code>, and <code>io</code> instead.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Async operations in Wren-CLI are cooperative, not preemptive. A fiber runs until it explicitly yields or calls an async operation.</p>
</div>
{% endblock %}
+260
View File
@@ -0,0 +1,260 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "signal" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "signal"}] %}
{% set prev_page = {"url": "api/scheduler.html", "title": "scheduler"} %}
{% set next_page = {"url": "api/sqlite.html", "title": "sqlite"} %}
{% block article %}
<h1>signal</h1>
<p>The <code>signal</code> module provides Unix signal handling capabilities, allowing scripts to trap, ignore, or reset signal handlers.</p>
<pre><code>import "signal" for Signal</code></pre>
<h2>Signal Class</h2>
<div class="class-header">
<h3>Signal</h3>
<p>Unix signal handling</p>
</div>
<h3>Signal Constants</h3>
<table>
<tr>
<th>Constant</th>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td><code>Signal.SIGHUP</code></td>
<td>1</td>
<td>Hangup (terminal closed or controlling process ended)</td>
</tr>
<tr>
<td><code>Signal.SIGINT</code></td>
<td>2</td>
<td>Interrupt (Ctrl+C)</td>
</tr>
<tr>
<td><code>Signal.SIGQUIT</code></td>
<td>3</td>
<td>Quit (Ctrl+\)</td>
</tr>
<tr>
<td><code>Signal.SIGTERM</code></td>
<td>15</td>
<td>Termination request</td>
</tr>
<tr>
<td><code>Signal.SIGUSR1</code></td>
<td>10</td>
<td>User-defined signal 1</td>
</tr>
<tr>
<td><code>Signal.SIGUSR2</code></td>
<td>12</td>
<td>User-defined signal 2</td>
</tr>
</table>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Signal.trap</span>(<span class="param">signum</span>, <span class="param">fn</span>)
</div>
<p>Registers a handler function to be called when the signal is received. The handler will be called each time the signal is received.</p>
<ul class="param-list">
<li><span class="param-name">signum</span> <span class="param-type">(Num)</span> - Signal number or constant</li>
<li><span class="param-name">fn</span> <span class="param-type">(Fn)</span> - Handler function (no arguments)</li>
</ul>
<pre><code>Signal.trap(Signal.SIGINT) {
System.print("Caught Ctrl+C!")
}</code></pre>
<div class="method-signature">
<span class="method-name">Signal.ignore</span>(<span class="param">signum</span>)
</div>
<p>Ignores the specified signal. The signal will be delivered but have no effect.</p>
<ul class="param-list">
<li><span class="param-name">signum</span> <span class="param-type">(Num)</span> - Signal number to ignore</li>
</ul>
<pre><code>Signal.ignore(Signal.SIGHUP)</code></pre>
<div class="method-signature">
<span class="method-name">Signal.reset</span>(<span class="param">signum</span>)
</div>
<p>Resets the signal handler to the default behavior.</p>
<ul class="param-list">
<li><span class="param-name">signum</span> <span class="param-type">(Num)</span> - Signal number to reset</li>
</ul>
<pre><code>Signal.reset(Signal.SIGINT)</code></pre>
<h2>Examples</h2>
<h3>Graceful Shutdown</h3>
<pre><code>import "signal" for Signal
import "timer" for Timer
var running = true
Signal.trap(Signal.SIGINT) {
System.print("\nShutting down gracefully...")
running = false
}
Signal.trap(Signal.SIGTERM) {
System.print("\nReceived SIGTERM, shutting down...")
running = false
}
System.print("Server running. Press Ctrl+C to stop.")
while (running) {
Timer.sleep(1000)
}
System.print("Cleanup complete. Goodbye!")</code></pre>
<h3>Configuration Reload on SIGHUP</h3>
<pre><code>import "signal" for Signal
import "timer" for Timer
import "io" for File
var config = {}
var loadConfig = Fn.new {
System.print("Loading configuration...")
if (File.exists("config.json")) {
var content = File.read("config.json")
System.print("Config loaded: %(content)")
}
}
loadConfig.call()
Signal.trap(Signal.SIGHUP) {
System.print("Received SIGHUP, reloading config...")
loadConfig.call()
}
System.print("Running. Send SIGHUP to reload config.")
while (true) {
Timer.sleep(1000)
}</code></pre>
<h3>Custom Signal for Debug Dump</h3>
<pre><code>import "signal" for Signal
import "timer" for Timer
import "datetime" for DateTime
var requestCount = 0
var startTime = DateTime.now()
Signal.trap(Signal.SIGUSR1) {
System.print("=== Debug Dump ===")
System.print("Requests handled: %(requestCount)")
var uptime = DateTime.now() - startTime
System.print("Uptime: %(uptime.hours) hours")
System.print("==================")
}
System.print("Send SIGUSR1 for debug info (kill -USR1 pid)")
while (true) {
requestCount = requestCount + 1
Timer.sleep(100)
}</code></pre>
<h3>Ignoring Signals</h3>
<pre><code>import "signal" for Signal
import "timer" for Timer
Signal.ignore(Signal.SIGINT)
System.print("SIGINT is now ignored. Ctrl+C will not stop this script.")
System.print("Use 'kill -9 pid' to force stop.")
for (i in 1..10) {
System.print("Still running... %(i)")
Timer.sleep(1000)
}
Signal.reset(Signal.SIGINT)
System.print("SIGINT handler restored. Ctrl+C works again.")</code></pre>
<h3>Multiple Signal Handlers</h3>
<pre><code>import "signal" for Signal
import "timer" for Timer
var shutdownRequested = false
var forceShutdown = false
Signal.trap(Signal.SIGINT) {
if (shutdownRequested) {
System.print("\nForce shutdown!")
forceShutdown = true
} else {
System.print("\nGraceful shutdown requested. Press Ctrl+C again to force.")
shutdownRequested = true
}
}
Signal.trap(Signal.SIGTERM) {
System.print("\nSIGTERM received, shutting down immediately.")
forceShutdown = true
}
System.print("Running... Press Ctrl+C to stop.")
while (!forceShutdown) {
if (shutdownRequested) {
System.print("Cleaning up...")
Timer.sleep(500)
break
}
Timer.sleep(100)
}
System.print("Exited.")</code></pre>
<h3>Worker Process Control</h3>
<pre><code>import "signal" for Signal
import "timer" for Timer
var paused = false
Signal.trap(Signal.SIGUSR1) {
paused = true
System.print("Worker paused")
}
Signal.trap(Signal.SIGUSR2) {
paused = false
System.print("Worker resumed")
}
System.print("Worker running. SIGUSR1 to pause, SIGUSR2 to resume.")
var count = 0
while (true) {
if (!paused) {
count = count + 1
System.print("Working... %(count)")
}
Timer.sleep(1000)
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Signal handling is only available on POSIX systems (Linux, macOS, BSD). On Windows, signal support is limited.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Some signals like SIGKILL (9) and SIGSTOP (19) cannot be trapped or ignored. Attempting to do so will have no effect.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Always implement graceful shutdown handlers for long-running services. Use SIGTERM for clean shutdowns and reserve SIGINT for interactive termination.</p>
</div>
{% endblock %}
+167
View File
@@ -0,0 +1,167 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "sqlite" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "sqlite"}] %}
{% set prev_page = {"url": "api/signal.html", "title": "signal"} %}
{% set next_page = {"url": "api/subprocess.html", "title": "subprocess"} %}
{% block article %}
<h1>sqlite</h1>
<p>The <code>sqlite</code> module provides SQLite database functionality for persistent data storage.</p>
<pre><code>import "sqlite" for Sqlite</code></pre>
<h2>Sqlite Class</h2>
<div class="class-header">
<h3>Sqlite</h3>
<p>SQLite database connection</p>
</div>
<h3>Constructors</h3>
<div class="method-signature">
<span class="method-name">Sqlite.open</span>(<span class="param">path</span>) → <span class="type">Sqlite</span>
</div>
<p>Opens or creates an SQLite database file.</p>
<ul class="param-list">
<li><span class="param-name">path</span> <span class="param-type">(String)</span> - Path to the database file</li>
<li><span class="returns">Returns:</span> Database connection</li>
</ul>
<pre><code>var db = Sqlite.open("data.db")</code></pre>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">execute</span>(<span class="param">sql</span>) → <span class="type">List</span>
</div>
<p>Executes an SQL statement and returns the results.</p>
<ul class="param-list">
<li><span class="param-name">sql</span> <span class="param-type">(String)</span> - SQL statement</li>
<li><span class="returns">Returns:</span> List of result rows (each row is a Map)</li>
</ul>
<pre><code>var rows = db.execute("SELECT * FROM users")
for (row in rows) {
System.print(row["name"])
}</code></pre>
<div class="method-signature">
<span class="method-name">execute</span>(<span class="param">sql</span>, <span class="param">params</span>) → <span class="type">List</span>
</div>
<p>Executes a parameterized SQL statement (prevents SQL injection).</p>
<ul class="param-list">
<li><span class="param-name">sql</span> <span class="param-type">(String)</span> - SQL with ? placeholders</li>
<li><span class="param-name">params</span> <span class="param-type">(List)</span> - Parameter values</li>
</ul>
<pre><code>var rows = db.execute("SELECT * FROM users WHERE age > ?", [18])</code></pre>
<div class="method-signature">
<span class="method-name">lastInsertId</span><span class="type">Num</span>
</div>
<p>Returns the row ID of the last INSERT operation.</p>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<p>Closes the database connection.</p>
<h2>Examples</h2>
<h3>Creating Tables</h3>
<pre><code>import "sqlite" for Sqlite
var db = Sqlite.open("app.db")
db.execute("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
")
db.close()</code></pre>
<h3>Inserting Data</h3>
<pre><code>import "sqlite" for Sqlite
var db = Sqlite.open("app.db")
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Alice", "alice@example.com"])
System.print("Inserted user with ID: %(db.lastInsertId)")
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Bob", "bob@example.com"])
db.close()</code></pre>
<h3>Querying Data</h3>
<pre><code>import "sqlite" for Sqlite
var db = Sqlite.open("app.db")
var users = db.execute("SELECT * FROM users ORDER BY name")
for (user in users) {
System.print("%(user["id"]): %(user["name"]) <%(user["email"])>")
}
var user = db.execute("SELECT * FROM users WHERE id = ?", [1])
if (user.count > 0) {
System.print("Found: %(user[0]["name"])")
}
db.close()</code></pre>
<h3>Updating Data</h3>
<pre><code>import "sqlite" for Sqlite
var db = Sqlite.open("app.db")
db.execute("UPDATE users SET email = ? WHERE id = ?", ["newemail@example.com", 1])
db.close()</code></pre>
<h3>Deleting Data</h3>
<pre><code>import "sqlite" for Sqlite
var db = Sqlite.open("app.db")
db.execute("DELETE FROM users WHERE id = ?", [2])
db.close()</code></pre>
<h3>Transactions</h3>
<pre><code>import "sqlite" for Sqlite
var db = Sqlite.open("app.db")
db.execute("BEGIN TRANSACTION")
var fiber = Fiber.new {
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Charlie", "charlie@example.com"])
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Diana", "diana@example.com"])
}
var error = fiber.try()
if (error) {
db.execute("ROLLBACK")
System.print("Error: %(error)")
} else {
db.execute("COMMIT")
System.print("Transaction committed")
}
db.close()</code></pre>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Always use parameterized queries with <code>?</code> placeholders to prevent SQL injection attacks. Never concatenate user input directly into SQL strings.</p>
</div>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Use <code>":memory:"</code> as the path for an in-memory database that does not persist to disk.</p>
</div>
{% endblock %}
+345
View File
@@ -0,0 +1,345 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "String" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "String"}] %}
{% set prev_page = {"url": "api/index.html", "title": "Overview"} %}
{% set next_page = {"url": "api/http.html", "title": "http"} %}
{% block article %}
<h1>String</h1>
<p>The <code>String</code> class is a core type available globally without imports. Strings in Wren are immutable sequences of bytes, typically representing UTF-8 encoded text. All methods return new strings rather than modifying the original.</p>
<pre><code>var s = "Hello World"
System.print(s.lower) // hello world
System.print(s.upper) // HELLO WORLD
System.print(s.title) // Hello World
System.print(s.reverse) // dlroW olleH</code></pre>
<h2>Case Conversion</h2>
<div class="method-signature">
<span class="method-name">lower</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the string with all ASCII uppercase characters converted to lowercase.</p>
<pre><code>"Hello World".lower // "hello world"
"ABC 123".lower // "abc 123"</code></pre>
<div class="method-signature">
<span class="method-name">upper</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the string with all ASCII lowercase characters converted to uppercase.</p>
<pre><code>"Hello World".upper // "HELLO WORLD"
"abc 123".upper // "ABC 123"</code></pre>
<div class="method-signature">
<span class="method-name">capitalize</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the string with the first character uppercased and the rest lowercased.</p>
<pre><code>"hello world".capitalize // "Hello world"
"HELLO".capitalize // "Hello"</code></pre>
<div class="method-signature">
<span class="method-name">title</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the string with the first character of each word capitalized and the rest lowercased. Words are separated by whitespace.</p>
<pre><code>"hello world".title // "Hello World"
"HELLO WORLD".title // "Hello World"</code></pre>
<div class="method-signature">
<span class="method-name">swapCase</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the string with uppercase characters converted to lowercase and vice versa.</p>
<pre><code>"Hello World".swapCase // "hELLO wORLD"</code></pre>
<h2>Character Testing</h2>
<div class="method-signature">
<span class="method-name">isLower</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if the string contains at least one alphabetic character and all alphabetic characters are lowercase.</p>
<pre><code>"hello".isLower // true
"hello1".isLower // true
"Hello".isLower // false
"123".isLower // false</code></pre>
<div class="method-signature">
<span class="method-name">isUpper</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if the string contains at least one alphabetic character and all alphabetic characters are uppercase.</p>
<pre><code>"HELLO".isUpper // true
"HELLO1".isUpper // true
"Hello".isUpper // false</code></pre>
<div class="method-signature">
<span class="method-name">isDigit</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if the string is non-empty and all bytes are ASCII digits (0-9).</p>
<pre><code>"12345".isDigit // true
"123a5".isDigit // false
"".isDigit // false</code></pre>
<div class="method-signature">
<span class="method-name">isAlpha</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if the string is non-empty and all bytes are ASCII letters (a-z, A-Z).</p>
<pre><code>"hello".isAlpha // true
"hello1".isAlpha // false</code></pre>
<div class="method-signature">
<span class="method-name">isAlphaNumeric</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if the string is non-empty and all bytes are ASCII letters or digits.</p>
<pre><code>"hello123".isAlphaNumeric // true
"hello!".isAlphaNumeric // false</code></pre>
<div class="method-signature">
<span class="method-name">isSpace</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if the string is non-empty and all bytes are ASCII whitespace (space, tab, newline, carriage return, form feed, vertical tab).</p>
<pre><code>" ".isSpace // true
" \t\n".isSpace // true
"hello".isSpace // false</code></pre>
<div class="method-signature">
<span class="method-name">isAscii</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if all bytes in the string are less than 128. Returns <code>true</code> for empty strings.</p>
<pre><code>"hello".isAscii // true
"".isAscii // true</code></pre>
<h2>Search</h2>
<div class="method-signature">
<span class="method-name">lastIndexOf</span>(<span class="param">search</span>) &#8594; <span class="type">Num</span>
</div>
<p>Returns the byte index of the last occurrence of <code>search</code> in the string, or <code>-1</code> if not found.</p>
<ul class="param-list">
<li><span class="param-name">search</span> <span class="param-type">(String)</span> - The substring to search for</li>
<li><span class="returns">Returns:</span> Byte index or -1</li>
</ul>
<pre><code>"hello world hello".lastIndexOf("hello") // 12
"hello".lastIndexOf("x") // -1</code></pre>
<div class="method-signature">
<span class="method-name">lastIndexOf</span>(<span class="param">search</span>, <span class="param">start</span>) &#8594; <span class="type">Num</span>
</div>
<p>Returns the byte index of the last occurrence of <code>search</code> at or before <code>start</code>, or <code>-1</code> if not found.</p>
<ul class="param-list">
<li><span class="param-name">search</span> <span class="param-type">(String)</span> - The substring to search for</li>
<li><span class="param-name">start</span> <span class="param-type">(Num)</span> - Maximum byte index to search from</li>
<li><span class="returns">Returns:</span> Byte index or -1</li>
</ul>
<pre><code>"hello world hello".lastIndexOf("hello", 11) // 0
"hello world hello".lastIndexOf("hello", 12) // 12</code></pre>
<h2>Transformation</h2>
<div class="method-signature">
<span class="method-name">reverse</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the string with characters in reverse order. Code point aware for UTF-8 strings.</p>
<pre><code>"hello".reverse // "olleh"
"abcde".reverse // "edcba"</code></pre>
<div class="method-signature">
<span class="method-name">center</span>(<span class="param">width</span>) &#8594; <span class="type">String</span>
</div>
<p>Centers the string in a field of <code>width</code> characters, padded with spaces.</p>
<pre><code>"hi".center(10) // " hi "</code></pre>
<div class="method-signature">
<span class="method-name">center</span>(<span class="param">width</span>, <span class="param">char</span>) &#8594; <span class="type">String</span>
</div>
<p>Centers the string in a field of <code>width</code> characters, padded with the given fill character.</p>
<pre><code>"hi".center(10, "-") // "----hi----"</code></pre>
<div class="method-signature">
<span class="method-name">lpad</span>(<span class="param">width</span>, <span class="param">char</span>) &#8594; <span class="type">String</span>
</div>
<p>Left-pads the string to <code>width</code> characters using the given fill character.</p>
<pre><code>"42".lpad(5, "0") // "00042"</code></pre>
<div class="method-signature">
<span class="method-name">rpad</span>(<span class="param">width</span>, <span class="param">char</span>) &#8594; <span class="type">String</span>
</div>
<p>Right-pads the string to <code>width</code> characters using the given fill character.</p>
<pre><code>"hi".rpad(5, ".") // "hi..."</code></pre>
<div class="method-signature">
<span class="method-name">zfill</span>(<span class="param">width</span>) &#8594; <span class="type">String</span>
</div>
<p>Pads the string with leading zeros to <code>width</code> characters. Preserves a leading <code>-</code> or <code>+</code> sign.</p>
<pre><code>"42".zfill(5) // "00042"
"-42".zfill(6) // "-00042"
"+42".zfill(6) // "+00042"</code></pre>
<div class="method-signature">
<span class="method-name">removePrefix</span>(<span class="param">prefix</span>) &#8594; <span class="type">String</span>
</div>
<p>If the string starts with <code>prefix</code>, returns the string with the prefix removed. Otherwise returns the original string.</p>
<pre><code>"HelloWorld".removePrefix("Hello") // "World"
"HelloWorld".removePrefix("World") // "HelloWorld"</code></pre>
<div class="method-signature">
<span class="method-name">removeSuffix</span>(<span class="param">suffix</span>) &#8594; <span class="type">String</span>
</div>
<p>If the string ends with <code>suffix</code>, returns the string with the suffix removed. Otherwise returns the original string.</p>
<pre><code>"HelloWorld".removeSuffix("World") // "Hello"
"HelloWorld".removeSuffix("Hello") // "HelloWorld"</code></pre>
<h2>Splitting &amp; Conversion</h2>
<div class="method-signature">
<span class="method-name">splitLines</span> &#8594; <span class="type">List</span>
</div>
<p>Splits the string by line endings (<code>\n</code>, <code>\r\n</code>, <code>\r</code>) and returns a list of strings.</p>
<pre><code>"line1\nline2\nline3".splitLines // ["line1", "line2", "line3"]
"a\r\nb\r\nc".splitLines // ["a", "b", "c"]</code></pre>
<div class="method-signature">
<span class="method-name">chars</span> &#8594; <span class="type">List</span>
</div>
<p>Returns a list of individual characters (code points) in the string.</p>
<pre><code>"abc".chars // ["a", "b", "c"]
"".chars // []</code></pre>
<div class="method-signature">
<span class="method-name">toNum</span> &#8594; <span class="type">Num</span> | <span class="type">Null</span>
</div>
<p>Parses the string as a number. Returns <code>null</code> if the string cannot be parsed.</p>
<pre><code>"42".toNum // 42
"3.14".toNum // 3.14
"-7".toNum // -7
"abc".toNum // null</code></pre>
<h2>Comparison</h2>
<p>Strings support lexicographic comparison using the standard comparison operators. Comparison is byte-by-byte using the raw byte values, which produces correct results for ASCII and UTF-8 encoded text.</p>
<div class="method-signature">
<span class="method-name">&lt;</span>(<span class="param">other</span>) &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if this string is lexicographically less than <code>other</code>.</p>
<ul class="param-list">
<li><span class="param-name">other</span> <span class="param-type">(String)</span> - The string to compare against</li>
</ul>
<pre><code>"apple" < "banana" // true
"abc" < "abd" // true
"abc" < "abcd" // true (prefix is less than longer string)</code></pre>
<div class="method-signature">
<span class="method-name">&gt;</span>(<span class="param">other</span>) &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if this string is lexicographically greater than <code>other</code>.</p>
<ul class="param-list">
<li><span class="param-name">other</span> <span class="param-type">(String)</span> - The string to compare against</li>
</ul>
<pre><code>"banana" > "apple" // true
"abd" > "abc" // true
"abcd" > "abc" // true</code></pre>
<div class="method-signature">
<span class="method-name">&lt;=</span>(<span class="param">other</span>) &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if this string is lexicographically less than or equal to <code>other</code>.</p>
<ul class="param-list">
<li><span class="param-name">other</span> <span class="param-type">(String)</span> - The string to compare against</li>
</ul>
<pre><code>"abc" <= "abc" // true
"abc" <= "abd" // true
"abd" <= "abc" // false</code></pre>
<div class="method-signature">
<span class="method-name">&gt;=</span>(<span class="param">other</span>) &#8594; <span class="type">Bool</span>
</div>
<p>Returns <code>true</code> if this string is lexicographically greater than or equal to <code>other</code>.</p>
<ul class="param-list">
<li><span class="param-name">other</span> <span class="param-type">(String)</span> - The string to compare against</li>
</ul>
<pre><code>"abc" >= "abc" // true
"abd" >= "abc" // true
"abc" >= "abd" // false</code></pre>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>String comparison is byte-based, so uppercase letters sort before lowercase letters in ASCII order (e.g., <code>"Z" &lt; "a"</code> is <code>true</code>). If you need case-insensitive comparison, convert both strings to the same case first using <code>lower</code> or <code>upper</code>.</p>
</div>
<h2>Existing Methods</h2>
<p>The following methods are also available on all strings as part of the core VM.</p>
<table>
<tr>
<th>Method</th>
<th>Description</th>
</tr>
<tr>
<td><code>contains(s)</code></td>
<td>Returns true if the string contains <code>s</code></td>
</tr>
<tr>
<td><code>startsWith(s)</code></td>
<td>Returns true if the string starts with <code>s</code></td>
</tr>
<tr>
<td><code>endsWith(s)</code></td>
<td>Returns true if the string ends with <code>s</code></td>
</tr>
<tr>
<td><code>indexOf(s)</code></td>
<td>Returns the byte index of <code>s</code>, or -1</td>
</tr>
<tr>
<td><code>indexOf(s, start)</code></td>
<td>Returns the byte index of <code>s</code> starting from <code>start</code></td>
</tr>
<tr>
<td><code>split(delim)</code></td>
<td>Splits by delimiter, returns a list</td>
</tr>
<tr>
<td><code>replace(from, to)</code></td>
<td>Replaces all occurrences of <code>from</code> with <code>to</code></td>
</tr>
<tr>
<td><code>trim()</code></td>
<td>Removes leading and trailing whitespace</td>
</tr>
<tr>
<td><code>trimStart()</code></td>
<td>Removes leading whitespace</td>
</tr>
<tr>
<td><code>trimEnd()</code></td>
<td>Removes trailing whitespace</td>
</tr>
<tr>
<td><code>bytes</code></td>
<td>Returns a sequence of byte values</td>
</tr>
<tr>
<td><code>codePoints</code></td>
<td>Returns a sequence of code point values</td>
</tr>
<tr>
<td><code>count</code></td>
<td>Number of code points in the string</td>
</tr>
<tr>
<td><code>isEmpty</code></td>
<td>Returns true if the string has no characters</td>
</tr>
<tr>
<td><code>*(n)</code></td>
<td>Repeats the string <code>n</code> times</td>
</tr>
</table>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>String is a core type available globally. No <code>import</code> statement is needed. All string methods operate on ASCII characters. Multi-byte UTF-8 characters are preserved but case conversion applies only to ASCII letters (a-z, A-Z).</p>
</div>
{% endblock %}
+90
View File
@@ -0,0 +1,90 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "subprocess" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "subprocess"}] %}
{% set prev_page = {"url": "api/sqlite.html", "title": "sqlite"} %}
{% set next_page = {"url": "api/sysinfo.html", "title": "sysinfo"} %}
{% block article %}
<h1>subprocess</h1>
<p>The <code>subprocess</code> module allows running external commands and capturing their output.</p>
<pre><code>import "subprocess" for Subprocess</code></pre>
<h2>Subprocess Class</h2>
<div class="class-header">
<h3>Subprocess</h3>
<p>External process execution</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Subprocess.run</span>(<span class="param">command</span>) → <span class="type">Map</span>
</div>
<p>Runs a command and waits for it to complete.</p>
<ul class="param-list">
<li><span class="param-name">command</span> <span class="param-type">(List)</span> - Command and arguments as a list</li>
<li><span class="returns">Returns:</span> Map with "stdout", "stderr", and "exitCode"</li>
</ul>
<pre><code>var result = Subprocess.run(["ls", "-la"])
System.print("Exit code: %(result["exitCode"])")
System.print("Output: %(result["stdout"])")</code></pre>
<div class="method-signature">
<span class="method-name">Subprocess.run</span>(<span class="param">command</span>, <span class="param">input</span>) → <span class="type">Map</span>
</div>
<p>Runs a command with input data provided to stdin.</p>
<ul class="param-list">
<li><span class="param-name">command</span> <span class="param-type">(List)</span> - Command and arguments</li>
<li><span class="param-name">input</span> <span class="param-type">(String)</span> - Input to send to stdin</li>
</ul>
<pre><code>var result = Subprocess.run(["cat"], "Hello, World!")
System.print(result["stdout"]) // Hello, World!</code></pre>
<h2>Examples</h2>
<h3>Running Commands</h3>
<pre><code>import "subprocess" for Subprocess
var result = Subprocess.run(["echo", "Hello"])
System.print(result["stdout"]) // Hello
var files = Subprocess.run(["ls", "-la", "/tmp"])
System.print(files["stdout"])</code></pre>
<h3>Checking Exit Codes</h3>
<pre><code>import "subprocess" for Subprocess
var result = Subprocess.run(["grep", "pattern", "file.txt"])
if (result["exitCode"] == 0) {
System.print("Found: %(result["stdout"])")
} else {
System.print("Not found or error")
}</code></pre>
<h3>Processing Data</h3>
<pre><code>import "subprocess" for Subprocess
import "json" for Json
var result = Subprocess.run(["curl", "-s", "https://api.example.com/data"])
if (result["exitCode"] == 0) {
var data = Json.parse(result["stdout"])
System.print(data)
}</code></pre>
<h3>Piping Data</h3>
<pre><code>import "subprocess" for Subprocess
var input = "line1\nline2\nline3"
var result = Subprocess.run(["wc", "-l"], input)
System.print("Lines: %(result["stdout"].trim())")</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Commands are not executed through a shell. Use explicit command and argument lists, not shell command strings.</p>
</div>
{% endblock %}
+244
View File
@@ -0,0 +1,244 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "sysinfo" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "sysinfo"}] %}
{% set prev_page = {"url": "api/subprocess.html", "title": "subprocess"} %}
{% set next_page = {"url": "api/tempfile.html", "title": "tempfile"} %}
{% block article %}
<h1>sysinfo</h1>
<p>The <code>sysinfo</code> module provides system information including CPU details, memory usage, uptime, and network interfaces. All values are retrieved from libuv.</p>
<pre><code>import "sysinfo" for SysInfo</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#sysinfo-class">SysInfo Class</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="sysinfo-class">SysInfo Class</h2>
<div class="class-header">
<h3>SysInfo</h3>
<p>System information properties</p>
</div>
<h3>Static Properties</h3>
<div class="method-signature">
<span class="method-name">SysInfo.cpuInfo</span> &#8594; <span class="type">List</span>
</div>
<p>Returns a list of maps containing CPU information. Each map includes <code>model</code>, <code>speed</code>, and <code>times</code> (with <code>user</code>, <code>nice</code>, <code>sys</code>, <code>idle</code>, <code>irq</code>).</p>
<pre><code>var cpus = SysInfo.cpuInfo
for (cpu in cpus) {
System.print("Model: %(cpu["model"])")
System.print("Speed: %(cpu["speed"]) MHz")
}</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.cpuCount</span> &#8594; <span class="type">Num</span>
</div>
<p>Returns the number of CPUs/cores available on the system.</p>
<pre><code>System.print("CPU count: %(SysInfo.cpuCount)")</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.loadAverage</span> &#8594; <span class="type">List</span>
</div>
<p>Returns the system load averages as a list of three numbers: 1-minute, 5-minute, and 15-minute averages. On Windows, returns <code>[0, 0, 0]</code>.</p>
<pre><code>var load = SysInfo.loadAverage
System.print("Load: %(load[0]) %(load[1]) %(load[2])")</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.totalMemory</span> &#8594; <span class="type">Num</span>
</div>
<p>Returns the total system memory in bytes.</p>
<pre><code>var totalMB = SysInfo.totalMemory / 1024 / 1024
System.print("Total memory: %(totalMB) MB")</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.freeMemory</span> &#8594; <span class="type">Num</span>
</div>
<p>Returns the free system memory in bytes.</p>
<pre><code>var freeMB = SysInfo.freeMemory / 1024 / 1024
System.print("Free memory: %(freeMB) MB")</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.uptime</span> &#8594; <span class="type">Num</span>
</div>
<p>Returns the system uptime in seconds.</p>
<pre><code>var hours = (SysInfo.uptime / 3600).floor
System.print("Uptime: %(hours) hours")</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.hostname</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the system hostname.</p>
<pre><code>System.print("Hostname: %(SysInfo.hostname)")</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.networkInterfaces</span> &#8594; <span class="type">Map</span>
</div>
<p>Returns a map of network interfaces. Each key is an interface name, and each value is a list of address maps containing <code>address</code>, <code>netmask</code>, <code>family</code> (IPv4 or IPv6), <code>internal</code>, and <code>mac</code>.</p>
<pre><code>var interfaces = SysInfo.networkInterfaces
for (name in interfaces.keys) {
System.print("Interface: %(name)")
for (addr in interfaces[name]) {
System.print(" %(addr["family"]): %(addr["address"])")
}
}</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.hrtime</span> &#8594; <span class="type">Num</span>
</div>
<p>Returns a high-resolution time value in nanoseconds. Useful for precise timing measurements.</p>
<pre><code>var start = SysInfo.hrtime
// ... do some work ...
var elapsed = SysInfo.hrtime - start
System.print("Elapsed: %(elapsed / 1000000) ms")</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.residentMemory</span> &#8594; <span class="type">Num</span>
</div>
<p>Returns the resident set size (RSS) of the current process in bytes.</p>
<pre><code>var rssMB = SysInfo.residentMemory / 1024 / 1024
System.print("Process RSS: %(rssMB) MB")</code></pre>
<div class="method-signature">
<span class="method-name">SysInfo.constrainedMemory</span> &#8594; <span class="type">Num</span>
</div>
<p>Returns the memory limit for the process in bytes (e.g., cgroup limit). Returns 0 if no limit is set.</p>
<pre><code>var limit = SysInfo.constrainedMemory
if (limit > 0) {
System.print("Memory limit: %(limit / 1024 / 1024) MB")
} else {
System.print("No memory limit set")
}</code></pre>
<h2 id="examples">Examples</h2>
<h3>System Overview</h3>
<pre><code>import "sysinfo" for SysInfo
System.print("=== System Information ===")
System.print("Hostname: %(SysInfo.hostname)")
System.print("CPUs: %(SysInfo.cpuCount)")
System.print("")
System.print("=== Memory ===")
var totalGB = (SysInfo.totalMemory / 1024 / 1024 / 1024 * 100).floor / 100
var freeGB = (SysInfo.freeMemory / 1024 / 1024 / 1024 * 100).floor / 100
var usedPercent = ((1 - SysInfo.freeMemory / SysInfo.totalMemory) * 100).floor
System.print("Total: %(totalGB) GB")
System.print("Free: %(freeGB) GB")
System.print("Used: %(usedPercent)\%")
System.print("")
System.print("=== Load Average ===")
var load = SysInfo.loadAverage
System.print("1 min: %(load[0])")
System.print("5 min: %(load[1])")
System.print("15 min: %(load[2])")
System.print("")
System.print("=== Uptime ===")
var uptime = SysInfo.uptime
var days = (uptime / 86400).floor
var hours = ((uptime % 86400) / 3600).floor
var minutes = ((uptime % 3600) / 60).floor
System.print("%(days) days, %(hours) hours, %(minutes) minutes")</code></pre>
<h3>CPU Information</h3>
<pre><code>import "sysinfo" for SysInfo
var cpus = SysInfo.cpuInfo
System.print("CPU Information:")
System.print("================")
var i = 0
for (cpu in cpus) {
System.print("CPU %(i): %(cpu["model"])")
System.print(" Speed: %(cpu["speed"]) MHz")
var times = cpu["times"]
var total = times["user"] + times["nice"] + times["sys"] + times["idle"] + times["irq"]
var idle = (times["idle"] / total * 100).floor
System.print(" Idle: %(idle)\%")
i = i + 1
}</code></pre>
<h3>Network Interfaces</h3>
<pre><code>import "sysinfo" for SysInfo
var interfaces = SysInfo.networkInterfaces
System.print("Network Interfaces:")
System.print("===================")
for (name in interfaces.keys) {
var addrs = interfaces[name]
System.print("%(name):")
for (addr in addrs) {
if (!addr["internal"]) {
System.print(" %(addr["family"]): %(addr["address"])")
if (addr["mac"] != "00:00:00:00:00:00") {
System.print(" MAC: %(addr["mac"])")
}
}
}
}</code></pre>
<h3>Performance Timing</h3>
<pre><code>import "sysinfo" for SysInfo
var measure = Fn.new { |name, fn|
var start = SysInfo.hrtime
fn.call()
var elapsed = SysInfo.hrtime - start
System.print("%(name): %(elapsed / 1000000) ms")
}
measure.call("String concatenation") {
var s = ""
for (i in 0...1000) {
s = s + "x"
}
}
measure.call("List operations") {
var list = []
for (i in 0...10000) {
list.add(i)
}
}</code></pre>
<h3>Memory Monitoring</h3>
<pre><code>import "sysinfo" for SysInfo
import "timer" for Timer
System.print("Memory Monitor (press Ctrl+C to stop)")
System.print("=====================================")
while (true) {
var totalMB = (SysInfo.totalMemory / 1024 / 1024).floor
var freeMB = (SysInfo.freeMemory / 1024 / 1024).floor
var rssMB = (SysInfo.residentMemory / 1024 / 1024).floor
var usedPercent = ((1 - SysInfo.freeMemory / SysInfo.totalMemory) * 100).floor
System.print("System: %(freeMB)/%(totalMB) MB free (%(usedPercent)\% used) | Process RSS: %(rssMB) MB")
Timer.sleep(1000)
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Load average values are not available on Windows and will return <code>[0, 0, 0]</code>.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Use <code>SysInfo.hrtime</code> for precise performance measurements. It provides nanosecond resolution and is not affected by system clock adjustments.</p>
</div>
{% endblock %}
+270
View File
@@ -0,0 +1,270 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "tempfile" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "tempfile"}] %}
{% set prev_page = {"url": "api/sysinfo.html", "title": "sysinfo"} %}
{% set next_page = {"url": "api/timer.html", "title": "timer"} %}
{% block article %}
<h1>tempfile</h1>
<p>The <code>tempfile</code> module provides utilities for creating temporary files and directories. It mirrors Python's <code>tempfile</code> API with three classes: <code>TempFile</code> for low-level operations, <code>NamedTemporaryFile</code> for temporary files with automatic cleanup, and <code>TemporaryDirectory</code> for temporary directories with recursive cleanup.</p>
<pre><code>import "tempfile" for TempFile, NamedTemporaryFile, TemporaryDirectory</code></pre>
<h2>TempFile Class</h2>
<div class="class-header">
<h3>TempFile</h3>
<p>Low-level temporary file and directory utilities</p>
</div>
<h3>Static Properties</h3>
<div class="method-signature">
<span class="method-name">TempFile.tempdir</span><span class="type">String|Null</span>
</div>
<p>Gets or sets a global override for the temporary directory. When set, <code>gettempdir()</code> returns this value instead of checking environment variables.</p>
<pre><code>TempFile.tempdir = "/my/custom/tmp"
System.print(TempFile.gettempdir()) // /my/custom/tmp
TempFile.tempdir = null // Reset to default</code></pre>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">TempFile.gettempdir</span>() → <span class="type">String</span>
</div>
<p>Returns the resolved temporary directory path. Checks in order: the <code>tempdir</code> override, then <code>TMPDIR</code>, <code>TEMP</code>, <code>TMP</code> environment variables, and falls back to <code>/tmp</code> on POSIX or <code>C:\Temp</code> on Windows.</p>
<div class="method-signature">
<span class="method-name">TempFile.gettempprefix</span>() → <span class="type">String</span>
</div>
<p>Returns the default prefix for temporary file names: <code>"tmp"</code>.</p>
<div class="method-signature">
<span class="method-name">TempFile.mktemp</span>() → <span class="type">String</span><br>
<span class="method-name">TempFile.mktemp</span>(<span class="param">suffix</span>) → <span class="type">String</span><br>
<span class="method-name">TempFile.mktemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">String</span><br>
<span class="method-name">TempFile.mktemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">String</span>
</div>
<p>Generates a unique temporary file path without creating any file. The generated name consists of the prefix, 8 random hexadecimal characters, and the suffix.</p>
<ul class="param-list">
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the filename (default: <code>""</code>)</li>
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the filename (default: <code>"tmp"</code>)</li>
<li><span class="param-name">dir</span> <span class="param-type">(String)</span> - Directory for the path (default: <code>gettempdir()</code>)</li>
<li><span class="returns">Returns:</span> A file path string (file is not created)</li>
</ul>
<pre><code>var name = TempFile.mktemp(".txt", "data_")
System.print(name) // e.g., /tmp/data_a1b2c3d4.txt</code></pre>
<div class="method-signature">
<span class="method-name">TempFile.mkstemp</span>() → <span class="type">String</span><br>
<span class="method-name">TempFile.mkstemp</span>(<span class="param">suffix</span>) → <span class="type">String</span><br>
<span class="method-name">TempFile.mkstemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">String</span><br>
<span class="method-name">TempFile.mkstemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">String</span>
</div>
<p>Creates a temporary file atomically using exclusive creation flags (<code>O_EXCL</code>), preventing race conditions. Retries up to 100 times on name collisions.</p>
<ul class="param-list">
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the filename (default: <code>""</code>)</li>
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the filename (default: <code>"tmp"</code>)</li>
<li><span class="param-name">dir</span> <span class="param-type">(String)</span> - Directory for the file (default: <code>gettempdir()</code>)</li>
<li><span class="returns">Returns:</span> The path of the newly created file</li>
</ul>
<pre><code>import "io" for File
var path = TempFile.mkstemp(".dat", "work_")
System.print(path) // e.g., /tmp/work_e5f6a7b8.dat
File.delete(path) // Caller must clean up</code></pre>
<div class="method-signature">
<span class="method-name">TempFile.mkdtemp</span>() → <span class="type">String</span><br>
<span class="method-name">TempFile.mkdtemp</span>(<span class="param">suffix</span>) → <span class="type">String</span><br>
<span class="method-name">TempFile.mkdtemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">String</span><br>
<span class="method-name">TempFile.mkdtemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">String</span>
</div>
<p>Creates a temporary directory. Retries up to 100 times on name collisions.</p>
<ul class="param-list">
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the directory name (default: <code>""</code>)</li>
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the directory name (default: <code>"tmp"</code>)</li>
<li><span class="param-name">dir</span> <span class="param-type">(String)</span> - Parent directory (default: <code>gettempdir()</code>)</li>
<li><span class="returns">Returns:</span> The path of the newly created directory</li>
</ul>
<pre><code>import "io" for Directory
var dir = TempFile.mkdtemp("_work")
System.print(dir) // e.g., /tmp/tmp9c0d1e2f_work
Directory.delete(dir) // Caller must clean up</code></pre>
<h2>NamedTemporaryFile Class</h2>
<div class="class-header">
<h3>NamedTemporaryFile</h3>
<p>A temporary file with a visible name that is optionally deleted on close</p>
</div>
<h3>Constructors</h3>
<div class="method-signature">
<span class="method-name">NamedTemporaryFile.new</span>() → <span class="type">NamedTemporaryFile</span><br>
<span class="method-name">NamedTemporaryFile.new</span>(<span class="param">suffix</span>) → <span class="type">NamedTemporaryFile</span><br>
<span class="method-name">NamedTemporaryFile.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">NamedTemporaryFile</span><br>
<span class="method-name">NamedTemporaryFile.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">NamedTemporaryFile</span><br>
<span class="method-name">NamedTemporaryFile.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>, <span class="param">delete</span>) → <span class="type">NamedTemporaryFile</span>
</div>
<ul class="param-list">
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the filename (default: <code>""</code>)</li>
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the filename (default: <code>"tmp"</code>)</li>
<li><span class="param-name">dir</span> <span class="param-type">(String|Null)</span> - Directory for the file (default: <code>null</code>, uses <code>TempFile.gettempdir()</code>)</li>
<li><span class="param-name">delete</span> <span class="param-type">(Bool)</span> - Whether to delete the file on close (default: <code>true</code>)</li>
</ul>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">.name</span><span class="type">String</span>
</div>
<p>The full path of the temporary file.</p>
<div class="method-signature">
<span class="method-name">.path</span><span class="type">Path</span>
</div>
<p>A <code>Path</code> object for the temporary file.</p>
<div class="method-signature">
<span class="method-name">.delete</span><span class="type">Bool</span>
</div>
<p>Whether the file will be deleted on close. Can be set.</p>
<div class="method-signature">
<span class="method-name">.closed</span><span class="type">Bool</span>
</div>
<p>Whether the file has been closed.</p>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">.write</span>(<span class="param">content</span>)
</div>
<p>Writes string content to the file, replacing any existing content.</p>
<div class="method-signature">
<span class="method-name">.read</span>() → <span class="type">String</span>
</div>
<p>Reads and returns the file content as a string.</p>
<div class="method-signature">
<span class="method-name">.close</span>()
</div>
<p>Closes the file. If <code>delete</code> is <code>true</code>, the file is deleted from disk.</p>
<div class="method-signature">
<span class="method-name">.use</span>(<span class="param">fn</span>) → <span class="type">*</span>
</div>
<p>Executes <code>fn</code> with the file as argument and guarantees cleanup afterwards, even if an error occurs. Equivalent to Python's <code>with</code> statement.</p>
<pre><code>NamedTemporaryFile.new(".txt").use {|f|
f.write("temporary data")
System.print(f.read())
}
// File is automatically deleted here</code></pre>
<h2>TemporaryDirectory Class</h2>
<div class="class-header">
<h3>TemporaryDirectory</h3>
<p>A temporary directory with recursive cleanup support</p>
</div>
<h3>Constructors</h3>
<div class="method-signature">
<span class="method-name">TemporaryDirectory.new</span>() → <span class="type">TemporaryDirectory</span><br>
<span class="method-name">TemporaryDirectory.new</span>(<span class="param">suffix</span>) → <span class="type">TemporaryDirectory</span><br>
<span class="method-name">TemporaryDirectory.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">TemporaryDirectory</span><br>
<span class="method-name">TemporaryDirectory.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">TemporaryDirectory</span><br>
<span class="method-name">TemporaryDirectory.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>, <span class="param">delete</span>) → <span class="type">TemporaryDirectory</span>
</div>
<ul class="param-list">
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the directory name (default: <code>""</code>)</li>
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the directory name (default: <code>"tmp"</code>)</li>
<li><span class="param-name">dir</span> <span class="param-type">(String|Null)</span> - Parent directory (default: <code>null</code>, uses <code>TempFile.gettempdir()</code>)</li>
<li><span class="param-name">delete</span> <span class="param-type">(Bool)</span> - Whether to delete the directory on cleanup (default: <code>true</code>)</li>
</ul>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">.name</span><span class="type">String</span>
</div>
<p>The full path of the temporary directory.</p>
<div class="method-signature">
<span class="method-name">.path</span><span class="type">Path</span>
</div>
<p>A <code>Path</code> object for the temporary directory.</p>
<div class="method-signature">
<span class="method-name">.delete</span><span class="type">Bool</span>
</div>
<p>Whether the directory will be deleted on cleanup. Can be set.</p>
<div class="method-signature">
<span class="method-name">.closed</span><span class="type">Bool</span>
</div>
<p>Whether the directory has been cleaned up.</p>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">.cleanup</span>()
</div>
<p>Removes the directory and all its contents recursively (using <code>Path.rmtree()</code>). If <code>delete</code> is <code>false</code>, the directory is not removed.</p>
<div class="method-signature">
<span class="method-name">.use</span>(<span class="param">fn</span>) → <span class="type">*</span>
</div>
<p>Executes <code>fn</code> with the directory as argument and guarantees cleanup afterwards, even if an error occurs.</p>
<pre><code>TemporaryDirectory.new().use {|d|
var path = d.name + "/data.txt"
// Create files, do work...
}
// Directory and all contents automatically removed</code></pre>
<h2>Examples</h2>
<h3>Processing Files Safely</h3>
<pre><code>import "tempfile" for NamedTemporaryFile
import "pathlib" for Path
NamedTemporaryFile.new(".csv", "report_").use {|tmp|
tmp.write("name,value\nalice,42\nbob,17")
var data = tmp.read()
System.print("Processing %(tmp.name)")
System.print(data)
}</code></pre>
<h3>Working Directory for Build Output</h3>
<pre><code>import "tempfile" for TemporaryDirectory
import "pathlib" for Path
TemporaryDirectory.new("_build").use {|build|
var src = Path.new(build.name) / "output.txt"
src.writeText("Build artifact")
System.print("Build dir: %(build.name)")
}
// Build directory cleaned up automatically</code></pre>
<h3>Persistent Temporary Files</h3>
<pre><code>import "tempfile" for NamedTemporaryFile
var tmp = NamedTemporaryFile.new(".log", "app_", null, false)
tmp.write("Application started")
tmp.close()
// File persists at tmp.name after close
System.print("Log file: %(tmp.name)")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>File creation via <code>mkstemp</code> uses exclusive creation flags (<code>O_EXCL</code>) for atomic file creation, preventing race conditions between checking for existence and creating the file. Random suffixes are generated using <code>Crypto.randomBytes</code> for cryptographic quality randomness.</p>
</div>
{% endblock %}
+227
View File
@@ -0,0 +1,227 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "timer" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "timer"}] %}
{% set prev_page = {"url": "api/tempfile.html", "title": "tempfile"} %}
{% set next_page = {"url": "api/tls.html", "title": "tls"} %}
{% block article %}
<h1>timer</h1>
<p>The <code>timer</code> module provides timing functionality for delays, intervals, and immediate execution.</p>
<pre><code>import "timer" for Timer, TimerHandle</code></pre>
<h2>Timer Class</h2>
<div class="class-header">
<h3>Timer</h3>
<p>Timer and delay functionality</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Timer.sleep</span>(<span class="param">milliseconds</span>)
</div>
<p>Pauses execution for the specified duration. The fiber suspends and other operations can proceed.</p>
<ul class="param-list">
<li><span class="param-name">milliseconds</span> <span class="param-type">(Num)</span> - Duration to sleep in milliseconds</li>
</ul>
<pre><code>System.print("Starting...")
Timer.sleep(1000) // Wait 1 second
System.print("Done!")</code></pre>
<div class="method-signature">
<span class="method-name">Timer.interval</span>(<span class="param">milliseconds</span>, <span class="param">fn</span>) &#8594; <span class="type">TimerHandle</span>
</div>
<p>Creates a repeating timer that calls the callback function at the specified interval. Returns a TimerHandle that can be used to stop the interval.</p>
<ul class="param-list">
<li><span class="param-name">milliseconds</span> <span class="param-type">(Num)</span> - Interval between calls in milliseconds</li>
<li><span class="param-name">fn</span> <span class="param-type">(Fn)</span> - Callback function to execute (no arguments)</li>
<li><span class="returns">Returns:</span> TimerHandle for controlling the interval</li>
</ul>
<pre><code>var count = 0
var handle = Timer.interval(1000) {
count = count + 1
System.print("Tick %(count)")
if (count >= 5) {
handle.stop()
}
}</code></pre>
<div class="method-signature">
<span class="method-name">Timer.immediate</span>(<span class="param">fn</span>)
</div>
<p>Schedules a function to run on the next iteration of the event loop. Useful for deferring execution without blocking.</p>
<ul class="param-list">
<li><span class="param-name">fn</span> <span class="param-type">(Fn)</span> - Callback function to execute (no arguments)</li>
</ul>
<pre><code>System.print("Before")
Timer.immediate {
System.print("Deferred execution")
}
System.print("After")
// Output: Before, After, Deferred execution</code></pre>
<h2>TimerHandle Class</h2>
<div class="class-header">
<h3>TimerHandle</h3>
<p>Handle for controlling interval timers</p>
</div>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">stop</span>()
</div>
<p>Stops the interval timer. After calling stop, the callback will no longer be invoked.</p>
<pre><code>handle.stop()</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">isActive</span> &#8594; <span class="type">Bool</span>
</div>
<p>Returns true if the interval timer is still active.</p>
<pre><code>if (handle.isActive) {
System.print("Timer is running")
}</code></pre>
<h2>Examples</h2>
<h3>Basic Delay</h3>
<pre><code>import "timer" for Timer
System.print("Starting...")
Timer.sleep(2000)
System.print("2 seconds later...")</code></pre>
<h3>Polling Loop</h3>
<pre><code>import "timer" for Timer
import "http" for Http
var checkStatus = Fn.new {
var response = Http.get("https://api.example.com/status")
return response.json["ready"]
}
while (!checkStatus.call()) {
System.print("Waiting...")
Timer.sleep(5000) // Check every 5 seconds
}
System.print("Ready!")</code></pre>
<h3>Rate Limiting</h3>
<pre><code>import "timer" for Timer
import "http" for Http
var urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3"
]
for (url in urls) {
var response = Http.get(url)
System.print("%(url): %(response.statusCode)")
Timer.sleep(1000) // 1 second between requests
}</code></pre>
<h3>Timeout Pattern</h3>
<pre><code>import "timer" for Timer
import "datetime" for DateTime, Duration
var start = DateTime.now()
var timeout = Duration.fromSeconds(30)
while (true) {
var elapsed = DateTime.now() - start
if (elapsed.seconds >= timeout.seconds) {
System.print("Timeout!")
break
}
// Do work...
Timer.sleep(100)
}</code></pre>
<h3>Animation/Progress</h3>
<pre><code>import "timer" for Timer
var frames = ["-", "\\", "|", "/"]
var i = 0
for (step in 1..20) {
System.write("\rProcessing %(frames[i]) ")
i = (i + 1) % 4
Timer.sleep(100)
}
System.print("\rDone! ")</code></pre>
<h3>Interval Timer</h3>
<pre><code>import "timer" for Timer, TimerHandle
var seconds = 0
var handle = Timer.interval(1000) {
seconds = seconds + 1
System.print("Elapsed: %(seconds) seconds")
}
Timer.sleep(5000)
handle.stop()
System.print("Timer stopped")</code></pre>
<h3>Periodic Status Updates</h3>
<pre><code>import "timer" for Timer, TimerHandle
import "sysinfo" for SysInfo
var handle = Timer.interval(2000) {
var freeMB = (SysInfo.freeMemory / 1024 / 1024).floor
var load = SysInfo.loadAverage[0]
System.print("Memory: %(freeMB) MB free | Load: %(load)")
}
System.print("Monitoring system (runs for 10 seconds)...")
Timer.sleep(10000)
handle.stop()</code></pre>
<h3>Immediate Execution</h3>
<pre><code>import "timer" for Timer
System.print("1. Synchronous")
Timer.immediate {
System.print("3. Immediate callback")
}
System.print("2. Still synchronous")
Timer.sleep(100)</code></pre>
<h3>Self-Stopping Interval</h3>
<pre><code>import "timer" for Timer, TimerHandle
var count = 0
var handle = Timer.interval(500) {
count = count + 1
System.print("Count: %(count)")
if (count >= 10) {
System.print("Stopping at %(count)")
handle.stop()
}
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Timer.sleep is non-blocking at the event loop level. The current fiber suspends, but other scheduled operations can continue.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>Use <code>Timer.interval</code> for recurring tasks like heartbeats, polling, or animations. The returned handle allows you to stop the interval from within the callback itself.</p>
</div>
{% endblock %}
+153
View File
@@ -0,0 +1,153 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "tls" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "tls"}] %}
{% set prev_page = {"url": "api/timer.html", "title": "timer"} %}
{% set next_page = {"url": "api/udp.html", "title": "udp"} %}
{% block article %}
<h1>tls</h1>
<p>The <code>tls</code> module provides SSL/TLS socket support for secure network connections. It uses OpenSSL for encryption and is used internally by the <code>http</code> module for HTTPS connections.</p>
<pre><code>import "tls" for TlsSocket</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#tlssocket-class">TlsSocket Class</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="tlssocket-class">TlsSocket Class</h2>
<div class="class-header">
<h3>TlsSocket</h3>
<p>SSL/TLS encrypted socket connection</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">TlsSocket.connect</span>(<span class="param">host</span>, <span class="param">port</span>, <span class="param">hostname</span>) &#8594; <span class="type">TlsSocket</span>
</div>
<p>Establishes a TLS connection to a remote server.</p>
<ul class="param-list">
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - IP address to connect to</li>
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number (typically 443 for HTTPS)</li>
<li><span class="param-name">hostname</span> <span class="param-type">(String)</span> - Server hostname for SNI (Server Name Indication)</li>
<li><span class="returns">Returns:</span> Connected TlsSocket instance</li>
</ul>
<pre><code>var socket = TlsSocket.connect("93.184.216.34", 443, "example.com")</code></pre>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">write</span>(<span class="param">text</span>)
</div>
<p>Writes data to the TLS connection. This is an async operation that blocks until the data is sent.</p>
<ul class="param-list">
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Data to send</li>
</ul>
<pre><code>socket.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")</code></pre>
<div class="method-signature">
<span class="method-name">read</span>() &#8594; <span class="type">String|null</span>
</div>
<p>Reads data from the TLS connection. Blocks until data is available. Returns null when the connection is closed.</p>
<ul class="param-list">
<li><span class="returns">Returns:</span> Data received as a string, or null if connection closed</li>
</ul>
<pre><code>var data = socket.read()
if (data != null) {
System.print(data)
}</code></pre>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<p>Closes the TLS connection and releases resources.</p>
<pre><code>socket.close()</code></pre>
<h2 id="examples">Examples</h2>
<h3>Basic HTTPS Request</h3>
<pre><code>import "tls" for TlsSocket
import "dns" for Dns
var host = "example.com"
var ip = Dns.lookup(host)
var socket = TlsSocket.connect(ip, 443, host)
socket.write("GET / HTTP/1.1\r\n")
socket.write("Host: %(host)\r\n")
socket.write("Connection: close\r\n")
socket.write("\r\n")
var response = ""
while (true) {
var chunk = socket.read()
if (chunk == null) break
response = response + chunk
}
socket.close()
System.print(response)</code></pre>
<h3>Reading Until Complete</h3>
<pre><code>import "tls" for TlsSocket
import "dns" for Dns
var host = "api.example.com"
var ip = Dns.lookup(host)
var socket = TlsSocket.connect(ip, 443, host)
socket.write("GET /data HTTP/1.1\r\n")
socket.write("Host: %(host)\r\n")
socket.write("Accept: application/json\r\n")
socket.write("Connection: close\r\n\r\n")
var buffer = ""
while (true) {
var data = socket.read()
if (data == null) break
buffer = buffer + data
}
socket.close()
var bodyStart = buffer.indexOf("\r\n\r\n")
if (bodyStart != -1) {
var body = buffer[bodyStart + 4..-1]
System.print("Response body: %(body)")
}</code></pre>
<h3>Using with DNS Resolution</h3>
<pre><code>import "tls" for TlsSocket
import "dns" for Dns
var hostname = "secure.example.com"
var ip = Dns.lookup(hostname, 4)
System.print("Connecting to %(ip)")
var socket = TlsSocket.connect(ip, 443, hostname)
socket.write("GET /secure-endpoint HTTP/1.1\r\nHost: %(hostname)\r\n\r\n")
var response = socket.read()
System.print(response)
socket.close()</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>The <code>hostname</code> parameter is used for SNI (Server Name Indication), which is required when connecting to servers that host multiple domains on the same IP address. It should match the domain name in the server's certificate.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>For most use cases, consider using the higher-level <code>http</code> module which handles TLS connections, HTTP protocol details, and response parsing automatically.</p>
</div>
{% endblock %}
+318
View File
@@ -0,0 +1,318 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "udp" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "udp"}] %}
{% set prev_page = {"url": "api/tls.html", "title": "tls"} %}
{% set next_page = {"url": "api/uuid.html", "title": "uuid"} %}
{% block article %}
<h1>udp</h1>
<p>The <code>udp</code> module provides UDP (User Datagram Protocol) socket functionality for connectionless networking. UDP is useful for applications requiring fast, lightweight communication without the overhead of TCP connection management.</p>
<pre><code>import "udp" for UdpSocket, UdpMessage</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#udpsocket-class">UdpSocket Class</a></li>
<li><a href="#udpmessage-class">UdpMessage Class</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="udpsocket-class">UdpSocket Class</h2>
<div class="class-header">
<h3>UdpSocket</h3>
<p>UDP datagram socket for sending and receiving messages</p>
</div>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">UdpSocket.new</span>() &#8594; <span class="type">UdpSocket</span>
</div>
<p>Creates a new UDP socket.</p>
<pre><code>var socket = UdpSocket.new()</code></pre>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">bind</span>(<span class="param">host</span>, <span class="param">port</span>)
</div>
<p>Binds the socket to a local address and port. Required before receiving messages.</p>
<ul class="param-list">
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - Local address to bind (e.g., "0.0.0.0" for all interfaces)</li>
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number to bind</li>
</ul>
<pre><code>socket.bind("0.0.0.0", 8080)</code></pre>
<div class="method-signature">
<span class="method-name">send</span>(<span class="param">data</span>, <span class="param">host</span>, <span class="param">port</span>)
</div>
<p>Sends a datagram to the specified address. Async operation.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Data to send</li>
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - Destination hostname or IP</li>
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Destination port</li>
</ul>
<pre><code>socket.send("Hello!", "192.168.1.100", 8080)</code></pre>
<div class="method-signature">
<span class="method-name">receive</span>() &#8594; <span class="type">UdpMessage</span>
</div>
<p>Receives a datagram. Blocks until data arrives. Returns a UdpMessage containing the data and sender information.</p>
<pre><code>var msg = socket.receive()
System.print("From %(msg.address):%(msg.port): %(msg.data)")</code></pre>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<p>Closes the socket.</p>
<pre><code>socket.close()</code></pre>
<div class="method-signature">
<span class="method-name">setBroadcast</span>(<span class="param">enabled</span>)
</div>
<p>Enables or disables broadcast mode. When enabled, the socket can send to broadcast addresses.</p>
<ul class="param-list">
<li><span class="param-name">enabled</span> <span class="param-type">(Bool)</span> - True to enable broadcast</li>
</ul>
<pre><code>socket.setBroadcast(true)
socket.send("Broadcast message", "255.255.255.255", 8080)</code></pre>
<div class="method-signature">
<span class="method-name">setMulticastTTL</span>(<span class="param">ttl</span>)
</div>
<p>Sets the time-to-live for multicast packets.</p>
<ul class="param-list">
<li><span class="param-name">ttl</span> <span class="param-type">(Num)</span> - TTL value (1-255)</li>
</ul>
<pre><code>socket.setMulticastTTL(64)</code></pre>
<div class="method-signature">
<span class="method-name">setMulticastLoopback</span>(<span class="param">enabled</span>)
</div>
<p>Enables or disables multicast loopback. When enabled, the socket receives its own multicast messages.</p>
<ul class="param-list">
<li><span class="param-name">enabled</span> <span class="param-type">(Bool)</span> - True to enable loopback</li>
</ul>
<pre><code>socket.setMulticastLoopback(false)</code></pre>
<div class="method-signature">
<span class="method-name">joinMulticast</span>(<span class="param">group</span>)
</div>
<p>Joins a multicast group on the default interface.</p>
<ul class="param-list">
<li><span class="param-name">group</span> <span class="param-type">(String)</span> - Multicast group address (e.g., "239.255.0.1")</li>
</ul>
<pre><code>socket.joinMulticast("239.255.0.1")</code></pre>
<div class="method-signature">
<span class="method-name">joinMulticast</span>(<span class="param">group</span>, <span class="param">iface</span>)
</div>
<p>Joins a multicast group on a specific interface.</p>
<ul class="param-list">
<li><span class="param-name">group</span> <span class="param-type">(String)</span> - Multicast group address</li>
<li><span class="param-name">iface</span> <span class="param-type">(String)</span> - Local interface address to join from</li>
</ul>
<pre><code>socket.joinMulticast("239.255.0.1", "192.168.1.100")</code></pre>
<div class="method-signature">
<span class="method-name">leaveMulticast</span>(<span class="param">group</span>)
</div>
<p>Leaves a multicast group on the default interface.</p>
<ul class="param-list">
<li><span class="param-name">group</span> <span class="param-type">(String)</span> - Multicast group address</li>
</ul>
<pre><code>socket.leaveMulticast("239.255.0.1")</code></pre>
<div class="method-signature">
<span class="method-name">leaveMulticast</span>(<span class="param">group</span>, <span class="param">iface</span>)
</div>
<p>Leaves a multicast group on a specific interface.</p>
<ul class="param-list">
<li><span class="param-name">group</span> <span class="param-type">(String)</span> - Multicast group address</li>
<li><span class="param-name">iface</span> <span class="param-type">(String)</span> - Local interface address</li>
</ul>
<pre><code>socket.leaveMulticast("239.255.0.1", "192.168.1.100")</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">localAddress</span> &#8594; <span class="type">String</span>
</div>
<p>Returns the local address the socket is bound to.</p>
<pre><code>System.print("Bound to: %(socket.localAddress)")</code></pre>
<div class="method-signature">
<span class="method-name">localPort</span> &#8594; <span class="type">Num</span>
</div>
<p>Returns the local port the socket is bound to.</p>
<pre><code>System.print("Port: %(socket.localPort)")</code></pre>
<h2 id="udpmessage-class">UdpMessage Class</h2>
<div class="class-header">
<h3>UdpMessage</h3>
<p>Represents a received UDP datagram</p>
</div>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">data</span> &#8594; <span class="type">String</span>
</div>
<p>The data contained in the message.</p>
<div class="method-signature">
<span class="method-name">address</span> &#8594; <span class="type">String</span>
</div>
<p>The IP address of the sender.</p>
<div class="method-signature">
<span class="method-name">port</span> &#8594; <span class="type">Num</span>
</div>
<p>The port number of the sender.</p>
<pre><code>var msg = socket.receive()
System.print("Data: %(msg.data)")
System.print("From: %(msg.address):%(msg.port)")</code></pre>
<h2 id="examples">Examples</h2>
<h3>Simple UDP Server</h3>
<pre><code>import "udp" for UdpSocket
var socket = UdpSocket.new()
socket.bind("0.0.0.0", 8080)
System.print("UDP server listening on port 8080")
while (true) {
var msg = socket.receive()
System.print("Received from %(msg.address):%(msg.port): %(msg.data)")
socket.send("Echo: %(msg.data)", msg.address, msg.port)
}</code></pre>
<h3>Simple UDP Client</h3>
<pre><code>import "udp" for UdpSocket
var socket = UdpSocket.new()
socket.bind("0.0.0.0", 0)
socket.send("Hello, server!", "127.0.0.1", 8080)
var response = socket.receive()
System.print("Server responded: %(response.data)")
socket.close()</code></pre>
<h3>Broadcast Discovery</h3>
<pre><code>import "udp" for UdpSocket
import "timer" for Timer
var socket = UdpSocket.new()
socket.bind("0.0.0.0", 0)
socket.setBroadcast(true)
System.print("Sending discovery broadcast...")
socket.send("DISCOVER", "255.255.255.255", 9999)
System.print("Waiting for responses...")
var timeout = false
var timeoutFiber = Fiber.new {
Timer.sleep(3000)
timeout = true
}
timeoutFiber.call()
while (!timeout) {
var msg = socket.receive()
System.print("Found device at %(msg.address): %(msg.data)")
}
socket.close()</code></pre>
<h3>Multicast Group</h3>
<pre><code>import "udp" for UdpSocket
var MULTICAST_GROUP = "239.255.0.1"
var MULTICAST_PORT = 5000
var socket = UdpSocket.new()
socket.bind("0.0.0.0", MULTICAST_PORT)
socket.joinMulticast(MULTICAST_GROUP)
socket.setMulticastLoopback(true)
System.print("Joined multicast group %(MULTICAST_GROUP)")
socket.send("Hello multicast!", MULTICAST_GROUP, MULTICAST_PORT)
var msg = socket.receive()
System.print("Received: %(msg.data) from %(msg.address)")
socket.leaveMulticast(MULTICAST_GROUP)
socket.close()</code></pre>
<h3>DNS-Style Query/Response</h3>
<pre><code>import "udp" for UdpSocket
import "json" for Json
var socket = UdpSocket.new()
socket.bind("0.0.0.0", 0)
var query = Json.stringify({
"type": "lookup",
"name": "example.local"
})
socket.send(query, "192.168.1.1", 5353)
var response = socket.receive()
var result = Json.parse(response.data)
System.print("Lookup result: %(result)")
socket.close()</code></pre>
<h3>UDP Ping</h3>
<pre><code>import "udp" for UdpSocket
import "sysinfo" for SysInfo
var socket = UdpSocket.new()
socket.bind("0.0.0.0", 0)
var target = "127.0.0.1"
var port = 8080
for (i in 1..5) {
var start = SysInfo.hrtime
socket.send("PING %(i)", target, port)
var msg = socket.receive()
var elapsed = (SysInfo.hrtime - start) / 1000000
System.print("Reply from %(msg.address): %(elapsed) ms")
}
socket.close()</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>UDP is connectionless and does not guarantee delivery. Messages may be lost, duplicated, or arrive out of order. Use TCP (<code>net</code> module) when reliability is required.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">Tip</div>
<p>When binding to port 0, the system automatically assigns an available port. Use <code>localPort</code> to retrieve the assigned port number.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Multicast group addresses must be in the range 224.0.0.0 to 239.255.255.255. Using addresses outside this range will cause errors.</p>
</div>
{% endblock %}
+127
View File
@@ -0,0 +1,127 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "uuid" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "uuid"}] %}
{% set prev_page = {"url": "api/udp.html", "title": "udp"} %}
{% set next_page = {"url": "api/wdantic.html", "title": "wdantic"} %}
{% block article %}
<h1>uuid</h1>
<p>The <code>uuid</code> module provides UUID (Universally Unique Identifier) generation and validation. It supports version 4 UUIDs which are randomly generated.</p>
<pre><code>import "uuid" for Uuid</code></pre>
<h2>Uuid Class</h2>
<div class="class-header">
<h3>Uuid</h3>
<p>UUID generation and validation</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Uuid.v4</span>() → <span class="type">String</span>
</div>
<p>Generates a new random version 4 UUID.</p>
<ul class="param-list">
<li><span class="returns">Returns:</span> A 36-character UUID string in the format <code>xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx</code></li>
</ul>
<pre><code>var id = Uuid.v4()
System.print(id) // e.g., "550e8400-e29b-41d4-a716-446655440000"</code></pre>
<div class="method-signature">
<span class="method-name">Uuid.isValid</span>(<span class="param">string</span>) → <span class="type">Bool</span>
</div>
<p>Checks if a string is a valid UUID format (any version).</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to validate</li>
<li><span class="returns">Returns:</span> <code>true</code> if the string is a valid UUID format, <code>false</code> otherwise</li>
</ul>
<pre><code>System.print(Uuid.isValid("550e8400-e29b-41d4-a716-446655440000")) // true
System.print(Uuid.isValid("not-a-uuid")) // false
System.print(Uuid.isValid("550e8400e29b41d4a716446655440000")) // false (missing hyphens)</code></pre>
<div class="method-signature">
<span class="method-name">Uuid.isV4</span>(<span class="param">string</span>) → <span class="type">Bool</span>
</div>
<p>Checks if a string is a valid version 4 UUID.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to validate</li>
<li><span class="returns">Returns:</span> <code>true</code> if the string is a valid v4 UUID, <code>false</code> otherwise</li>
</ul>
<pre><code>var id = Uuid.v4()
System.print(Uuid.isV4(id)) // true
System.print(Uuid.isV4("550e8400-e29b-11d4-a716-446655440000")) // false (version 1)</code></pre>
<h2>UUID Format</h2>
<p>UUIDs are 128-bit identifiers represented as 32 hexadecimal characters separated by hyphens into five groups:</p>
<pre><code>xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
8 4 4 4 12 = 32 hex chars + 4 hyphens = 36 chars</code></pre>
<table>
<tr>
<th>Position</th>
<th>Description</th>
</tr>
<tr>
<td>M</td>
<td>Version number (4 for v4 UUIDs)</td>
</tr>
<tr>
<td>N</td>
<td>Variant (8, 9, a, or b for RFC 4122 UUIDs)</td>
</tr>
</table>
<h2>Examples</h2>
<h3>Generating Unique Identifiers</h3>
<pre><code>import "uuid" for Uuid
var userId = Uuid.v4()
var sessionId = Uuid.v4()
System.print("User ID: %(userId)")
System.print("Session ID: %(sessionId)")</code></pre>
<h3>Validating User Input</h3>
<pre><code>import "uuid" for Uuid
var input = "550e8400-e29b-41d4-a716-446655440000"
if (Uuid.isValid(input)) {
System.print("Valid UUID")
if (Uuid.isV4(input)) {
System.print("This is a v4 UUID")
}
} else {
System.print("Invalid UUID format")
}</code></pre>
<h3>Using with Database Records</h3>
<pre><code>import "uuid" for Uuid
class User {
construct new(name) {
_id = Uuid.v4()
_name = name
}
id { _id }
name { _name }
}
var user = User.new("Alice")
System.print("Created user %(user.name) with ID %(user.id)")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Version 4 UUIDs are generated using cryptographically secure random bytes from the <code>crypto</code> module. The probability of generating duplicate UUIDs is astronomically low.</p>
</div>
{% endblock %}
+314
View File
@@ -0,0 +1,314 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "wdantic" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "wdantic"}] %}
{% set prev_page = {"url": "api/argparse.html", "title": "argparse"} %}
{% set next_page = {"url": "api/dataset.html", "title": "dataset"} %}
{% block article %}
<h1>wdantic</h1>
<p>The <code>wdantic</code> module provides data validation similar to Python's Pydantic. It includes standalone validators and a schema-based validation system for structured data.</p>
<pre><code>import "wdantic" for Validator, Field, Schema, ValidationResult</code></pre>
<h2>Validator Class</h2>
<div class="class-header">
<h3>Validator</h3>
<p>Standalone validation functions</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Validator.email</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Validates email address format.</p>
<pre><code>Validator.email("user@example.com") // true
Validator.email("invalid") // false</code></pre>
<div class="method-signature">
<span class="method-name">Validator.domain</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Validates domain name format.</p>
<pre><code>Validator.domain("example.com") // true
Validator.domain("localhost") // false</code></pre>
<div class="method-signature">
<span class="method-name">Validator.url</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Validates HTTP/HTTPS URL format.</p>
<pre><code>Validator.url("https://example.com/path") // true
Validator.url("ftp://example.com") // false</code></pre>
<div class="method-signature">
<span class="method-name">Validator.uuid</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Validates UUID format.</p>
<pre><code>Validator.uuid("550e8400-e29b-41d4-a716-446655440000") // true</code></pre>
<div class="method-signature">
<span class="method-name">Validator.safeStr</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Checks if string contains only printable ASCII characters (32-126).</p>
<pre><code>Validator.safeStr("Hello World") // true
Validator.safeStr("Hello\x00") // false</code></pre>
<div class="method-signature">
<span class="method-name">Validator.base64</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Validates Base64 encoding format.</p>
<pre><code>Validator.base64("SGVsbG8=") // true
Validator.base64("invalid!") // false</code></pre>
<div class="method-signature">
<span class="method-name">Validator.json</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Checks if string is valid JSON.</p>
<pre><code>Validator.json("{\"key\": \"value\"}") // true
Validator.json("{invalid}") // false</code></pre>
<div class="method-signature">
<span class="method-name">Validator.ipv4</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Validates IPv4 address format.</p>
<pre><code>Validator.ipv4("192.168.1.1") // true
Validator.ipv4("256.0.0.1") // false</code></pre>
<div class="method-signature">
<span class="method-name">Validator.minLength</span>(<span class="param">value</span>, <span class="param">min</span>) → <span class="type">Bool</span>
</div>
<p>Checks minimum length of string or list.</p>
<div class="method-signature">
<span class="method-name">Validator.maxLength</span>(<span class="param">value</span>, <span class="param">max</span>) → <span class="type">Bool</span>
</div>
<p>Checks maximum length of string or list.</p>
<div class="method-signature">
<span class="method-name">Validator.range</span>(<span class="param">value</span>, <span class="param">min</span>, <span class="param">max</span>) → <span class="type">Bool</span>
</div>
<p>Checks if number is within range (inclusive).</p>
<pre><code>Validator.range(5, 1, 10) // true
Validator.range(15, 1, 10) // false</code></pre>
<div class="method-signature">
<span class="method-name">Validator.positive</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Checks if number is positive (greater than 0).</p>
<div class="method-signature">
<span class="method-name">Validator.negative</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Checks if number is negative (less than 0).</p>
<div class="method-signature">
<span class="method-name">Validator.integer</span>(<span class="param">value</span>) → <span class="type">Bool</span>
</div>
<p>Checks if number is an integer (no decimal part).</p>
<div class="method-signature">
<span class="method-name">Validator.regex</span>(<span class="param">value</span>, <span class="param">pattern</span>) → <span class="type">Bool</span>
</div>
<p>Tests if value matches a regular expression pattern.</p>
<pre><code>Validator.regex("abc123", "^[a-z]+[0-9]+$") // true</code></pre>
<h2>Field Class</h2>
<div class="class-header">
<h3>Field</h3>
<p>Factory for creating field definitions</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Field.string</span>() → <span class="type">StringField</span>
</div>
<div class="method-signature">
<span class="method-name">Field.string</span>(<span class="param">options</span>) → <span class="type">StringField</span>
</div>
<p>Creates a string field. Options: <code>minLength</code>, <code>maxLength</code>, <code>pattern</code>, <code>required</code>, <code>default</code>.</p>
<div class="method-signature">
<span class="method-name">Field.integer</span>() → <span class="type">IntegerField</span>
</div>
<div class="method-signature">
<span class="method-name">Field.integer</span>(<span class="param">options</span>) → <span class="type">IntegerField</span>
</div>
<p>Creates an integer field. Options: <code>min</code>, <code>max</code>, <code>required</code>, <code>default</code>.</p>
<div class="method-signature">
<span class="method-name">Field.number</span>() → <span class="type">NumberField</span>
</div>
<div class="method-signature">
<span class="method-name">Field.number</span>(<span class="param">options</span>) → <span class="type">NumberField</span>
</div>
<p>Creates a number field (integer or float). Options: <code>min</code>, <code>max</code>, <code>required</code>, <code>default</code>.</p>
<div class="method-signature">
<span class="method-name">Field.email</span>() → <span class="type">EmailField</span>
</div>
<p>Creates a field that validates email format.</p>
<div class="method-signature">
<span class="method-name">Field.boolean</span>() → <span class="type">BooleanField</span>
</div>
<p>Creates a boolean field.</p>
<div class="method-signature">
<span class="method-name">Field.list</span>(<span class="param">itemType</span>) → <span class="type">ListField</span>
</div>
<p>Creates a list field with typed items.</p>
<div class="method-signature">
<span class="method-name">Field.map</span>() → <span class="type">MapField</span>
</div>
<p>Creates a map/object field.</p>
<div class="method-signature">
<span class="method-name">Field.optional</span>(<span class="param">fieldDef</span>) → <span class="type">OptionalField</span>
</div>
<p>Makes any field optional (not required).</p>
<h2>Schema Class</h2>
<div class="class-header">
<h3>Schema</h3>
<p>Validates data against a field definition map</p>
</div>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">Schema.new</span>(<span class="param">definition</span>) → <span class="type">Schema</span>
</div>
<p>Creates a schema from a map of field names to field definitions.</p>
<h3>Instance Methods</h3>
<div class="method-signature">
<span class="method-name">validate</span>(<span class="param">data</span>) → <span class="type">ValidationResult</span>
</div>
<p>Validates data against the schema.</p>
<div class="method-signature">
<span class="method-name">validateOrAbort</span>(<span class="param">data</span>) → <span class="type">Map</span>
</div>
<p>Validates data and aborts on failure. Returns validated data on success.</p>
<h2>ValidationResult Class</h2>
<div class="class-header">
<h3>ValidationResult</h3>
<p>Result of schema validation</p>
</div>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">isValid</span><span class="type">Bool</span>
</div>
<p>Whether validation passed.</p>
<div class="method-signature">
<span class="method-name">errors</span><span class="type">List</span>
</div>
<p>List of ValidationError objects.</p>
<div class="method-signature">
<span class="method-name">data</span><span class="type">Map</span>
</div>
<p>Validated and coerced data (null if invalid).</p>
<h2>Examples</h2>
<h3>Basic Schema Validation</h3>
<pre><code>import "wdantic" for Field, Schema
var userSchema = Schema.new({
"name": Field.string({"minLength": 1, "maxLength": 100}),
"email": Field.email(),
"age": Field.integer({"min": 0, "max": 150})
})
var result = userSchema.validate({
"name": "Alice",
"email": "alice@example.com",
"age": 30
})
if (result.isValid) {
System.print("Valid: %(result.data)")
} else {
for (error in result.errors) {
System.print("Error: %(error)")
}
}</code></pre>
<h3>Optional Fields</h3>
<pre><code>import "wdantic" for Field, Schema
var schema = Schema.new({
"title": Field.string(),
"description": Field.optional(Field.string()),
"tags": Field.optional(Field.list(Field.string()))
})
var result = schema.validate({
"title": "My Post"
})
System.print(result.isValid) // true</code></pre>
<h3>Nested Lists</h3>
<pre><code>import "wdantic" for Field, Schema
var schema = Schema.new({
"numbers": Field.list(Field.integer({"min": 0}))
})
var result = schema.validate({
"numbers": [1, 2, 3, -1] // -1 will fail
})
System.print(result.isValid) // false</code></pre>
<h3>Using validateOrAbort</h3>
<pre><code>import "wdantic" for Field, Schema
var schema = Schema.new({
"username": Field.string({"minLength": 3}),
"password": Field.string({"minLength": 8})
})
var data = schema.validateOrAbort({
"username": "admin",
"password": "secret123"
})</code></pre>
<h3>Standalone Validators</h3>
<pre><code>import "wdantic" for Validator
var email = "user@example.com"
if (Validator.email(email)) {
System.print("Valid email")
}
var ip = "192.168.1.1"
if (Validator.ipv4(ip)) {
System.print("Valid IP address")
}
var jsonStr = "{\"key\": 123}"
if (Validator.json(jsonStr)) {
System.print("Valid JSON")
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Field types automatically coerce values when possible. For example, <code>IntegerField</code> will convert the string <code>"42"</code> to the number <code>42</code>.</p>
</div>
{% endblock %}
+999
View File
@@ -0,0 +1,999 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "web" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "web"}] %}
{% set prev_page = {"url": "api/wdantic.html", "title": "wdantic"} %}
{% set next_page = {"url": "api/websocket.html", "title": "websocket"} %}
{% block article %}
<h1>web</h1>
<p>The <code>web</code> module provides a web framework for building HTTP servers and a client for making HTTP requests. It includes routing, middleware, sessions, static file serving, and class-based views.</p>
<pre><code>import "web" for Application, Router, Request, Response, View, Session, Client, WebSocketResponse</code></pre>
<h2>Application Class</h2>
<div class="class-header">
<h3>Application</h3>
<p>HTTP server with routing and middleware</p>
</div>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">Application.new</span>() → <span class="type">Application</span>
</div>
<p>Creates a new web application.</p>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">router</span><span class="type">Router</span>
</div>
<p>Access to the underlying router.</p>
<div class="method-signature">
<span class="method-name">sessionStore</span><span class="type">SessionStore</span>
</div>
<p>Access to the session storage.</p>
<h3>Routing Methods</h3>
<div class="method-signature">
<span class="method-name">get</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
</div>
<div class="method-signature">
<span class="method-name">post</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
</div>
<div class="method-signature">
<span class="method-name">put</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
</div>
<div class="method-signature">
<span class="method-name">delete</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
</div>
<div class="method-signature">
<span class="method-name">patch</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
</div>
<p>Register route handlers. Handler receives <code>Request</code> and returns <code>Response</code>.</p>
<ul class="param-list">
<li><span class="param-name">path</span> <span class="param-type">(String)</span> - URL path with optional parameters (<code>:param</code>) or wildcards (<code>*</code>)</li>
<li><span class="param-name">handler</span> <span class="param-type">(Fn)</span> - Function receiving Request, returning Response</li>
</ul>
<div class="method-signature">
<span class="method-name">addView</span>(<span class="param">path</span>, <span class="param">viewClass</span>) → <span class="type">Application</span>
</div>
<p>Registers a class-based view for all HTTP methods.</p>
<div class="method-signature">
<span class="method-name">static_</span>(<span class="param">prefix</span>, <span class="param">directory</span>) → <span class="type">Application</span>
</div>
<p>Serves static files from a directory.</p>
<pre><code>app.static_("/assets", "./public")</code></pre>
<div class="method-signature">
<span class="method-name">websocket</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
</div>
<p>Registers a WebSocket handler for the given path. Handler receives Request and should use WebSocketResponse to handle the connection.</p>
<pre><code>app.websocket("/ws", Fn.new { |req|
var ws = WebSocketResponse.new()
ws.prepare(req)
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null || msg.isClose) break
if (msg.isText) ws.send("Echo: " + msg.text)
}
return ws
})</code></pre>
<div class="method-signature">
<span class="method-name">use</span>(<span class="param">middleware</span>) → <span class="type">Application</span>
</div>
<p>Adds middleware function. Middleware receives Request, returns Response or null to continue.</p>
<div class="method-signature">
<span class="method-name">run</span>(<span class="param">host</span>, <span class="param">port</span>)
</div>
<p>Starts the server (blocking).</p>
<h2>Request Class</h2>
<div class="class-header">
<h3>Request</h3>
<p>Incoming HTTP request</p>
</div>
<h3>Properties</h3>
<table>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>method</code></td>
<td>String</td>
<td>HTTP method (GET, POST, etc.)</td>
</tr>
<tr>
<td><code>path</code></td>
<td>String</td>
<td>Request path without query string</td>
</tr>
<tr>
<td><code>query</code></td>
<td>Map</td>
<td>Parsed query parameters</td>
</tr>
<tr>
<td><code>headers</code></td>
<td>Map</td>
<td>Request headers</td>
</tr>
<tr>
<td><code>body</code></td>
<td>String</td>
<td>Raw request body</td>
</tr>
<tr>
<td><code>params</code></td>
<td>Map</td>
<td>Route parameters from URL</td>
</tr>
<tr>
<td><code>cookies</code></td>
<td>Map</td>
<td>Parsed cookies</td>
</tr>
<tr>
<td><code>session</code></td>
<td>Session</td>
<td>Session data</td>
</tr>
</table>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">header</span>(<span class="param">name</span>) → <span class="type">String|null</span>
</div>
<p>Gets header value (case-insensitive).</p>
<div class="method-signature">
<span class="method-name">json</span><span class="type">Map|List</span>
</div>
<p>Parses body as JSON (cached).</p>
<div class="method-signature">
<span class="method-name">form</span><span class="type">Map</span>
</div>
<p>Parses body as form data (cached).</p>
<h2>Response Class</h2>
<div class="class-header">
<h3>Response</h3>
<p>HTTP response builder</p>
</div>
<h3>Static Factory Methods</h3>
<div class="method-signature">
<span class="method-name">Response.text</span>(<span class="param">content</span>) → <span class="type">Response</span>
</div>
<p>Creates plain text response.</p>
<div class="method-signature">
<span class="method-name">Response.html</span>(<span class="param">content</span>) → <span class="type">Response</span>
</div>
<p>Creates HTML response.</p>
<div class="method-signature">
<span class="method-name">Response.json</span>(<span class="param">data</span>) → <span class="type">Response</span>
</div>
<p>Creates JSON response (automatically stringifies).</p>
<div class="method-signature">
<span class="method-name">Response.redirect</span>(<span class="param">url</span>) → <span class="type">Response</span>
</div>
<div class="method-signature">
<span class="method-name">Response.redirect</span>(<span class="param">url</span>, <span class="param">status</span>) → <span class="type">Response</span>
</div>
<p>Creates redirect response (default 302).</p>
<div class="method-signature">
<span class="method-name">Response.file</span>(<span class="param">path</span>) → <span class="type">Response</span>
</div>
<div class="method-signature">
<span class="method-name">Response.file</span>(<span class="param">path</span>, <span class="param">contentType</span>) → <span class="type">Response</span>
</div>
<p>Serves a file. Auto-detects content type if not specified.</p>
<h3>Instance Methods</h3>
<div class="method-signature">
<span class="method-name">header</span>(<span class="param">name</span>, <span class="param">value</span>) → <span class="type">Response</span>
</div>
<p>Sets a response header.</p>
<div class="method-signature">
<span class="method-name">cookie</span>(<span class="param">name</span>, <span class="param">value</span>) → <span class="type">Response</span>
</div>
<div class="method-signature">
<span class="method-name">cookie</span>(<span class="param">name</span>, <span class="param">value</span>, <span class="param">options</span>) → <span class="type">Response</span>
</div>
<p>Sets a cookie. Options: <code>path</code>, <code>maxAge</code>, <code>httpOnly</code>, <code>secure</code>.</p>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">status</span><span class="type">Num</span>
</div>
<p>Gets or sets HTTP status code.</p>
<div class="method-signature">
<span class="method-name">body</span><span class="type">String</span>
</div>
<p>Gets or sets response body.</p>
<h2>View Class</h2>
<div class="class-header">
<h3>View</h3>
<p>Base class for class-based views</p>
</div>
<p>Subclass and override methods for each HTTP method:</p>
<pre><code>class UserView is View {
get(request) {
return Response.json({"users": []})
}
post(request) {
var data = request.json
return Response.json({"created": data})
}
websocket(request) {
var ws = WebSocketResponse.new()
ws.prepare(request)
ws.iterate(Fn.new { |msg|
if (msg.isText) ws.send("Echo: " + msg.text)
})
return ws
}
}</code></pre>
<h2>WebSocketResponse Class</h2>
<div class="class-header">
<h3>WebSocketResponse</h3>
<p>Server-side WebSocket connection handler (aiohttp-style API)</p>
</div>
<p>The <code>WebSocketResponse</code> class provides a server-side WebSocket handler following the aiohttp-style API pattern. It handles the WebSocket handshake, frame encoding/decoding, and provides convenient methods for sending and receiving messages.</p>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">WebSocketResponse.new</span>() → <span class="type">WebSocketResponse</span>
</div>
<p>Creates a new WebSocket response handler. Call <code>prepare(request)</code> to complete the handshake before sending or receiving messages.</p>
<h3>Properties</h3>
<table>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>isPrepared</code></td>
<td>Bool</td>
<td>True if the WebSocket handshake has been performed</td>
</tr>
<tr>
<td><code>isOpen</code></td>
<td>Bool</td>
<td>True if the connection is open and ready for messages</td>
</tr>
</table>
<h3>Connection Methods</h3>
<div class="method-signature">
<span class="method-name">prepare</span>(<span class="param">request</span>) → <span class="type">WebSocketResponse</span>
</div>
<p>Performs the WebSocket handshake using the request's socket. Must be called before sending or receiving messages. Returns <code>this</code> for method chaining.</p>
<ul class="param-list">
<li><span class="param-name">request</span> <span class="param-type">(Request)</span> - The HTTP request containing the WebSocket upgrade headers</li>
</ul>
<pre><code>var ws = WebSocketResponse.new()
ws.prepare(request)</code></pre>
<h3>Sending Methods</h3>
<div class="method-signature">
<span class="method-name">send</span>(<span class="param">text</span>)
</div>
<div class="method-signature">
<span class="method-name">sendText</span>(<span class="param">text</span>)
</div>
<p>Sends a text message to the client. Both methods are equivalent.</p>
<ul class="param-list">
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Text message to send</li>
</ul>
<pre><code>ws.send("Hello, client!")
ws.sendText("Another message")</code></pre>
<div class="method-signature">
<span class="method-name">sendJson</span>(<span class="param">data</span>)
</div>
<p>Serializes the data to JSON and sends it as a text message. Convenience method for JSON-based protocols.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(Map|List)</span> - Data to serialize and send</li>
</ul>
<pre><code>ws.sendJson({"type": "update", "value": 42})
ws.sendJson(["item1", "item2", "item3"])</code></pre>
<div class="method-signature">
<span class="method-name">sendBinary</span>(<span class="param">bytes</span>)
</div>
<p>Sends a binary message to the client.</p>
<ul class="param-list">
<li><span class="param-name">bytes</span> <span class="param-type">(List)</span> - List of bytes to send</li>
</ul>
<pre><code>ws.sendBinary([0x01, 0x02, 0x03, 0x04])
var imageData = File.readBytes("image.png")
ws.sendBinary(imageData)</code></pre>
<h3>Receiving Methods</h3>
<div class="method-signature">
<span class="method-name">receive</span>() → <span class="type">WebSocketMessage|null</span>
</div>
<p>Receives the next message from the client. Blocks until a message arrives. Returns a <code>WebSocketMessage</code> object, or <code>null</code> if the connection closed unexpectedly. Ping frames are automatically answered with pong.</p>
<pre><code>var msg = ws.receive()
if (msg != null && msg.isText) {
System.print("Got: %(msg.text)")
}</code></pre>
<div class="method-signature">
<span class="method-name">receiveJson</span>() → <span class="type">Map|List|WebSocketMessage|null</span>
</div>
<p>Receives a text message and parses it as JSON. Returns the parsed JSON data, the close frame if the connection was closed, or <code>null</code> if the connection closed unexpectedly. Aborts if a binary frame is received.</p>
<pre><code>var data = ws.receiveJson()
if (data is Map && data.containsKey("type")) {
if (data["type"] == "message") {
System.print("Message: %(data["text"])")
}
}</code></pre>
<div class="method-signature">
<span class="method-name">receiveText</span>() → <span class="type">String|null</span>
</div>
<p>Receives a message and returns only the text content. Returns <code>null</code> if the message is not a text frame, if the connection was closed, or if the connection closed unexpectedly.</p>
<pre><code>var text = ws.receiveText()
if (text != null) {
System.print("Received: %(text)")
}</code></pre>
<div class="method-signature">
<span class="method-name">receiveBinary</span>() → <span class="type">List|null</span>
</div>
<p>Receives a message and returns only the binary content. Returns <code>null</code> if the message is not a binary frame, if the connection was closed, or if the connection closed unexpectedly.</p>
<pre><code>var bytes = ws.receiveBinary()
if (bytes != null) {
System.print("Received %(bytes.count) bytes")
}</code></pre>
<div class="method-signature">
<span class="method-name">iterate</span>(<span class="param">callback</span>)
</div>
<p>Helper method that loops while the connection is open, calling the callback for each received message (excluding close frames). Simplifies the common receive loop pattern.</p>
<ul class="param-list">
<li><span class="param-name">callback</span> <span class="param-type">(Fn)</span> - Function to call with each WebSocketMessage</li>
</ul>
<pre><code>ws.iterate(Fn.new { |msg|
if (msg.isText) {
ws.send("Echo: " + msg.text)
}
})</code></pre>
<h3>Control Methods</h3>
<div class="method-signature">
<span class="method-name">ping</span>()
</div>
<div class="method-signature">
<span class="method-name">ping</span>(<span class="param">data</span>)
</div>
<p>Sends a ping frame to check if the client is still connected. Payload must be 125 bytes or less.</p>
<ul class="param-list">
<li><span class="param-name">data</span> <span class="param-type">(String|List)</span> - Optional payload data</li>
</ul>
<div class="method-signature">
<span class="method-name">pong</span>(<span class="param">data</span>)
</div>
<p>Sends a pong frame. Usually automatic in response to ping, but can be sent manually.</p>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<div class="method-signature">
<span class="method-name">close</span>(<span class="param">code</span>, <span class="param">reason</span>)
</div>
<p>Closes the WebSocket connection. Default code is 1000 (normal closure).</p>
<ul class="param-list">
<li><span class="param-name">code</span> <span class="param-type">(Num)</span> - Close status code (1000-4999)</li>
<li><span class="param-name">reason</span> <span class="param-type">(String)</span> - Human-readable close reason</li>
</ul>
<pre><code>ws.close()
ws.close(1000, "Goodbye")</code></pre>
<h3>WebSocketMessage Reference</h3>
<p>The <code>receive()</code> method returns a <code>WebSocketMessage</code> object with the following properties:</p>
<table>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>opcode</code></td>
<td>Num</td>
<td>Frame opcode (1=text, 2=binary, 8=close, 9=ping, 10=pong)</td>
</tr>
<tr>
<td><code>fin</code></td>
<td>Bool</td>
<td>True if this is the final fragment</td>
</tr>
<tr>
<td><code>isText</code></td>
<td>Bool</td>
<td>True if this is a text frame</td>
</tr>
<tr>
<td><code>isBinary</code></td>
<td>Bool</td>
<td>True if this is a binary frame</td>
</tr>
<tr>
<td><code>isClose</code></td>
<td>Bool</td>
<td>True if this is a close frame</td>
</tr>
<tr>
<td><code>isPing</code></td>
<td>Bool</td>
<td>True if this is a ping frame</td>
</tr>
<tr>
<td><code>isPong</code></td>
<td>Bool</td>
<td>True if this is a pong frame</td>
</tr>
<tr>
<td><code>text</code></td>
<td>String</td>
<td>Payload as string (for text frames)</td>
</tr>
<tr>
<td><code>bytes</code></td>
<td>List</td>
<td>Payload as byte list</td>
</tr>
<tr>
<td><code>closeCode</code></td>
<td>Num</td>
<td>Close status code (for close frames)</td>
</tr>
<tr>
<td><code>closeReason</code></td>
<td>String</td>
<td>Close reason text (for close frames)</td>
</tr>
</table>
<h3>Close Codes Reference</h3>
<table>
<tr>
<th>Code</th>
<th>Meaning</th>
</tr>
<tr>
<td>1000</td>
<td>Normal closure</td>
</tr>
<tr>
<td>1001</td>
<td>Going away (server shutdown)</td>
</tr>
<tr>
<td>1002</td>
<td>Protocol error</td>
</tr>
<tr>
<td>1003</td>
<td>Unsupported data type</td>
</tr>
<tr>
<td>1005</td>
<td>No status received</td>
</tr>
<tr>
<td>1006</td>
<td>Abnormal closure</td>
</tr>
<tr>
<td>1011</td>
<td>Server error</td>
</tr>
<tr>
<td>4000-4999</td>
<td>Application-specific codes</td>
</tr>
</table>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Ping frames received from clients are automatically answered with pong frames. You typically do not need to handle ping/pong manually unless implementing custom keepalive logic.</p>
</div>
<h2>Session Class</h2>
<div class="class-header">
<h3>Session</h3>
<p>Session data storage</p>
</div>
<h3>Properties and Methods</h3>
<div class="method-signature">
<span class="method-name">id</span><span class="type">String</span>
</div>
<p>The session ID.</p>
<div class="method-signature">
<span class="method-name">[key]</span><span class="type">any</span>
</div>
<p>Gets session value.</p>
<div class="method-signature">
<span class="method-name">[key]=</span>(<span class="param">value</span>)
</div>
<p>Sets session value.</p>
<div class="method-signature">
<span class="method-name">remove</span>(<span class="param">key</span>)
</div>
<p>Removes a session key.</p>
<h2>Client Class</h2>
<div class="class-header">
<h3>Client</h3>
<p>HTTP client for making requests</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Client.get</span>(<span class="param">url</span>) → <span class="type">Map</span>
</div>
<div class="method-signature">
<span class="method-name">Client.get</span>(<span class="param">url</span>, <span class="param">options</span>) → <span class="type">Map</span>
</div>
<p>Makes GET request.</p>
<div class="method-signature">
<span class="method-name">Client.post</span>(<span class="param">url</span>) → <span class="type">Map</span>
</div>
<div class="method-signature">
<span class="method-name">Client.post</span>(<span class="param">url</span>, <span class="param">options</span>) → <span class="type">Map</span>
</div>
<p>Makes POST request.</p>
<div class="method-signature">
<span class="method-name">Client.put</span>(<span class="param">url</span>, <span class="param">options</span>) → <span class="type">Map</span>
</div>
<p>Makes PUT request.</p>
<div class="method-signature">
<span class="method-name">Client.delete</span>(<span class="param">url</span>, <span class="param">options</span>) → <span class="type">Map</span>
</div>
<p>Makes DELETE request.</p>
<h3>Client Options</h3>
<table>
<tr>
<th>Option</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>headers</code></td>
<td>Map</td>
<td>Request headers</td>
</tr>
<tr>
<td><code>body</code></td>
<td>String</td>
<td>Request body</td>
</tr>
<tr>
<td><code>json</code></td>
<td>Map/List</td>
<td>JSON body (auto-stringifies and sets Content-Type)</td>
</tr>
</table>
<h3>Response Format</h3>
<pre><code>{
"status": 200,
"headers": {"Content-Type": "application/json"},
"body": "{...}"
}</code></pre>
<h2>Examples</h2>
<h3>Basic Server</h3>
<pre><code>import "web" for Application, Response
var app = Application.new()
app.get("/", Fn.new { |req|
return Response.html("&lt;h1&gt;Hello World&lt;/h1&gt;")
})
app.get("/api/users", Fn.new { |req|
return Response.json({"users": ["Alice", "Bob"]})
})
app.run("0.0.0.0", 8080)</code></pre>
<h3>Route Parameters</h3>
<pre><code>import "web" for Application, Response
var app = Application.new()
app.get("/users/:id", Fn.new { |req|
var userId = req.params["id"]
return Response.json({"id": userId})
})
app.get("/files/*", Fn.new { |req|
return Response.text("Wildcard route")
})
app.run("0.0.0.0", 8080)</code></pre>
<h3>Class-Based Views</h3>
<pre><code>import "web" for Application, Response, View
class ArticleView is View {
get(request) {
return Response.json({"articles": []})
}
post(request) {
var data = request.json
return Response.json({"created": data})
}
}
var app = Application.new()
app.addView("/articles", ArticleView)
app.run("0.0.0.0", 8080)</code></pre>
<h3>Sessions</h3>
<pre><code>import "web" for Application, Response
var app = Application.new()
app.get("/login", Fn.new { |req|
req.session["user"] = "Alice"
return Response.text("Logged in")
})
app.get("/profile", Fn.new { |req|
var user = req.session["user"]
if (user == null) {
return Response.redirect("/login")
}
return Response.text("Hello, %(user)")
})
app.run("0.0.0.0", 8080)</code></pre>
<h3>Middleware</h3>
<pre><code>import "web" for Application, Response
var app = Application.new()
app.use(Fn.new { |req|
System.print("%(req.method) %(req.path)")
return null
})
app.use(Fn.new { |req|
if (req.path.startsWith("/admin") &amp;&amp; !req.session["isAdmin"]) {
return Response.redirect("/login")
}
return null
})
app.get("/", Fn.new { |req|
return Response.text("Home")
})
app.run("0.0.0.0", 8080)</code></pre>
<h3>HTTP Client</h3>
<pre><code>import "web" for Client
import "json" for Json
var response = Client.get("https://api.example.com/data")
System.print("Status: %(response["status"])")
System.print("Body: %(response["body"])")
var postResponse = Client.post("https://api.example.com/users", {
"json": {"name": "Alice", "email": "alice@example.com"}
})
var data = Json.parse(postResponse["body"])</code></pre>
<h3>Concurrent HTTP Requests</h3>
<p>Use the <code>async</code> keyword to create futures and <code>await</code> to wait for results. Each request runs in its own fiber and executes in parallel while waiting for I/O.</p>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
var urls = [
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments"
]
var futures = []
for (url in urls) {
futures.add(async { Client.get(url) })
}
var responses = []
for (future in futures) {
responses.add(await future)
}
for (i in 0...urls.count) {
System.print("%(urls[i]): %(responses[i]["status"])")
}</code></pre>
<h3>Parallel Batch Requests</h3>
<p>For batch operations, create all futures first, then await them. The requests execute concurrently.</p>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchUrl = async { |url| Client.get(url) }
class BatchClient {
static getAll(urls) {
var futures = []
for (url in urls) {
futures.add(async { Client.get(url) })
}
var results = []
for (f in futures) {
results.add(await f)
}
return results
}
static postAll(requests) {
var futures = []
for (req in requests) {
futures.add(async { Client.post(req["url"], req["options"]) })
}
var results = []
for (f in futures) {
results.add(await f)
}
return results
}
}
var urls = [
"https://api.example.com/endpoint1",
"https://api.example.com/endpoint2",
"https://api.example.com/endpoint3"
]
var results = BatchClient.getAll(urls)
for (result in results) {
System.print("Status: %(result["status"])")
}</code></pre>
<h3>Parameterized Async Functions</h3>
<p>Create reusable async functions with parameters using <code>async { |args| ... }</code>. There are two ways to invoke these functions:</p>
<ul>
<li><strong><code>await fn(args)</code></strong> — Direct call, waits immediately (sequential execution)</li>
<li><strong><code>fn.call(args)</code></strong> — Returns a Future, starts execution without waiting (concurrent execution)</li>
</ul>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchJson = async { |url|
var response = Client.get(url)
return Json.parse(response["body"])
}
var postJson = async { |url, data|
var response = Client.post(url, {"json": data})
return Json.parse(response["body"])
}
// DIRECT CALLING (preferred for sequential operations)
// Waits for each request to complete before continuing
var user = await fetchJson("https://api.example.com/user/1")
System.print(user["name"])
// CONCURRENT EXECUTION (use .call() to start without waiting)
// Both requests run at the same time
var f1 = fetchJson.call("https://api.example.com/posts")
var f2 = fetchJson.call("https://api.example.com/comments")
// Now wait for results
var posts = await f1
var comments = await f2</code></pre>
<div class="admonition tip">
<div class="admonition-title">Direct Calling vs .call()</div>
<p>Use <code>await fn(args)</code> when you want sequential execution. Use <code>fn.call(args)</code> when you want to start multiple operations concurrently, then <code>await</code> their results later.</p>
</div>
<h3>WebSocket Echo Server</h3>
<pre><code>import "web" for Application, Response, WebSocketResponse
var app = Application.new()
app.get("/", Fn.new { |req|
return Response.html("&lt;script&gt;var ws=new WebSocket('ws://localhost:8080/ws');ws.onmessage=e=&gt;console.log(e.data);ws.onopen=()=&gt;ws.send('hello');&lt;/script&gt;")
})
app.websocket("/ws", Fn.new { |req|
var ws = WebSocketResponse.new()
ws.prepare(req)
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null || msg.isClose) break
if (msg.isText) {
ws.send("Echo: " + msg.text)
}
}
ws.close()
return ws
})
app.run("0.0.0.0", 8080)</code></pre>
<h3>WebSocket JSON API</h3>
<pre><code>import "web" for Application, Response, WebSocketResponse
var app = Application.new()
app.websocket("/api", Fn.new { |req|
var ws = WebSocketResponse.new()
ws.prepare(req)
ws.sendJson({"type": "welcome", "message": "Connected to API"})
while (ws.isOpen) {
var data = ws.receiveJson()
if (data == null) break
if (data is Map &amp;&amp; data["type"] == "ping") {
ws.sendJson({"type": "pong", "timestamp": data["timestamp"]})
} else if (data is Map &amp;&amp; data["type"] == "echo") {
ws.sendJson({"type": "echo", "data": data["data"]})
}
}
return ws
})
app.run("0.0.0.0", 8080)</code></pre>
<h3>WebSocket Binary Data Transfer</h3>
<pre><code>import "web" for Application, Response, WebSocketResponse
import "io" for File
var app = Application.new()
app.websocket("/binary", Fn.new { |req|
var ws = WebSocketResponse.new()
ws.prepare(req)
ws.send("Ready to receive binary data")
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null || msg.isClose) break
if (msg.isBinary) {
System.print("Received %(msg.bytes.count) bytes")
ws.sendJson({"received": msg.bytes.count, "status": "ok"})
} else if (msg.isText) {
if (msg.text == "send-file") {
var data = File.readBytes("example.bin")
ws.sendBinary(data)
}
}
}
return ws
})
app.run("0.0.0.0", 8080)</code></pre>
<h3>WebSocket in Class-Based View</h3>
<pre><code>import "web" for Application, Response, View, WebSocketResponse
class ChatView is View {
get(request) {
return Response.html("&lt;h1&gt;Chat Room&lt;/h1&gt;&lt;p&gt;Connect via WebSocket&lt;/p&gt;")
}
websocket(request) {
var ws = WebSocketResponse.new()
ws.prepare(request)
ws.sendJson({"type": "connected", "room": "general"})
ws.iterate(Fn.new { |msg|
if (msg.isText) {
var data = Json.parse(msg.text)
if (data["type"] == "message") {
ws.sendJson({
"type": "broadcast",
"from": data["from"],
"text": data["text"]
})
}
}
})
return ws
}
}
var app = Application.new()
app.addView("/chat", ChatView)
app.run("0.0.0.0", 8080)</code></pre>
<div class="admonition warning">
<div class="admonition-title">WebSocket Error Handling</div>
<p>Always check for <code>null</code> or close frames when receiving messages. The connection can close at any time due to network issues or client disconnection. Wrap WebSocket handling in proper error handling to prevent server crashes.</p>
</div>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Sessions are stored in memory by default. Data is lost when the server restarts. For production, consider persisting session data to a database.</p>
</div>
{% endblock %}
+408
View File
@@ -0,0 +1,408 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "websocket" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "websocket"}] %}
{% set prev_page = {"url": "api/http.html", "title": "http"} %}
{% set next_page = {"url": "api/yaml.html", "title": "yaml"} %}
{% block article %}
<h1>websocket</h1>
<p>The <code>websocket</code> module provides WebSocket client and server functionality implementing the RFC 6455 protocol. It supports both <code>ws://</code> and <code>wss://</code> (secure) connections.</p>
<pre><code>import "websocket" for WebSocket, WebSocketServer, WebSocketMessage</code></pre>
<div class="toc">
<h4>On This Page</h4>
<ul>
<li><a href="#websocket-class">WebSocket Class</a></li>
<li><a href="#websocketserver-class">WebSocketServer Class</a></li>
<li><a href="#websocketmessage-class">WebSocketMessage Class</a></li>
<li><a href="#opcodes">Frame Opcodes</a></li>
<li><a href="#examples">Examples</a></li>
</ul>
</div>
<h2 id="websocket-class">WebSocket Class</h2>
<p>WebSocket client for connecting to WebSocket servers.</p>
<div class="class-header">
<h3>WebSocket</h3>
<p>WebSocket client connection</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">WebSocket.connect</span>(<span class="param">url</span>) → <span class="type">WebSocket</span>
</div>
<p>Connects to a WebSocket server.</p>
<ul class="param-list">
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - WebSocket URL (ws:// or wss://)</li>
<li><span class="returns">Returns:</span> Connected WebSocket instance</li>
</ul>
<pre><code>var ws = WebSocket.connect("ws://echo.websocket.org")</code></pre>
<div class="method-signature">
<span class="method-name">WebSocket.connect</span>(<span class="param">url</span>, <span class="param">headers</span>) → <span class="type">WebSocket</span>
</div>
<p>Connects with custom headers (useful for authentication).</p>
<ul class="param-list">
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - WebSocket URL</li>
<li><span class="param-name">headers</span> <span class="param-type">(Map)</span> - Custom HTTP headers for handshake</li>
</ul>
<pre><code>var headers = {"Authorization": "Bearer token123"}
var ws = WebSocket.connect("wss://api.example.com/ws", headers)</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">url</span><span class="type">String</span>
</div>
<p>The URL this WebSocket is connected to.</p>
<div class="method-signature">
<span class="method-name">isOpen</span><span class="type">Bool</span>
</div>
<p>True if the connection is still open.</p>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">send</span>(<span class="param">text</span>)
</div>
<p>Sends a text message to the server.</p>
<ul class="param-list">
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Text message to send</li>
</ul>
<pre><code>ws.send("Hello, server!")</code></pre>
<div class="method-signature">
<span class="method-name">sendBinary</span>(<span class="param">bytes</span>)
</div>
<p>Sends a binary message to the server.</p>
<ul class="param-list">
<li><span class="param-name">bytes</span> <span class="param-type">(List)</span> - List of bytes to send</li>
</ul>
<pre><code>ws.sendBinary([0x01, 0x02, 0x03, 0x04])</code></pre>
<div class="method-signature">
<span class="method-name">receive</span>() → <span class="type">WebSocketMessage|null</span>
</div>
<p>Receives the next message from the server. Blocks until a message arrives. Returns null if the connection is closed.</p>
<pre><code>var msg = ws.receive()
if (msg != null && msg.isText) {
System.print("Got: %(msg.text)")
}</code></pre>
<div class="method-signature">
<span class="method-name">ping</span>()
</div>
<p>Sends a ping frame to the server.</p>
<div class="method-signature">
<span class="method-name">ping</span>(<span class="param">data</span>)
</div>
<p>Sends a ping frame with payload data (max 125 bytes).</p>
<div class="method-signature">
<span class="method-name">pong</span>(<span class="param">data</span>)
</div>
<p>Sends a pong frame (usually automatic in response to ping).</p>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<p>Closes the connection with status code 1000 (normal closure).</p>
<div class="method-signature">
<span class="method-name">close</span>(<span class="param">code</span>, <span class="param">reason</span>)
</div>
<p>Closes the connection with a custom status code and reason.</p>
<ul class="param-list">
<li><span class="param-name">code</span> <span class="param-type">(Num)</span> - Close status code (1000-4999)</li>
<li><span class="param-name">reason</span> <span class="param-type">(String)</span> - Human-readable close reason</li>
</ul>
<pre><code>ws.close(1000, "Goodbye")</code></pre>
<h2 id="websocketserver-class">WebSocketServer Class</h2>
<p>WebSocket server for accepting client connections.</p>
<div class="class-header">
<h3>WebSocketServer</h3>
<p>WebSocket server</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">WebSocketServer.bind</span>(<span class="param">host</span>, <span class="param">port</span>) → <span class="type">WebSocketServer</span>
</div>
<p>Creates a WebSocket server listening on the specified host and port.</p>
<ul class="param-list">
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - Host to bind to (e.g., "0.0.0.0", "127.0.0.1")</li>
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number</li>
</ul>
<pre><code>var server = WebSocketServer.bind("0.0.0.0", 8080)</code></pre>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">accept</span>() → <span class="type">WebSocket|null</span>
</div>
<p>Accepts and upgrades an incoming connection. Blocks until a client connects. Returns a WebSocket instance for the connected client.</p>
<pre><code>var client = server.accept()
if (client != null) {
System.print("Client connected!")
}</code></pre>
<div class="method-signature">
<span class="method-name">close</span>()
</div>
<p>Stops the server and closes the listening socket.</p>
<h2 id="websocketmessage-class">WebSocketMessage Class</h2>
<p>Represents a WebSocket frame received from a connection.</p>
<div class="class-header">
<h3>WebSocketMessage</h3>
<p>WebSocket message/frame</p>
</div>
<h3>Properties</h3>
<table>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td><code>opcode</code></td>
<td>Num</td>
<td>Frame opcode (1=text, 2=binary, 8=close, 9=ping, 10=pong)</td>
</tr>
<tr>
<td><code>payload</code></td>
<td>List</td>
<td>Raw payload bytes</td>
</tr>
<tr>
<td><code>fin</code></td>
<td>Bool</td>
<td>True if this is the final fragment</td>
</tr>
<tr>
<td><code>isText</code></td>
<td>Bool</td>
<td>True if this is a text frame</td>
</tr>
<tr>
<td><code>isBinary</code></td>
<td>Bool</td>
<td>True if this is a binary frame</td>
</tr>
<tr>
<td><code>isClose</code></td>
<td>Bool</td>
<td>True if this is a close frame</td>
</tr>
<tr>
<td><code>isPing</code></td>
<td>Bool</td>
<td>True if this is a ping frame</td>
</tr>
<tr>
<td><code>isPong</code></td>
<td>Bool</td>
<td>True if this is a pong frame</td>
</tr>
<tr>
<td><code>text</code></td>
<td>String</td>
<td>Payload as string (only for text frames)</td>
</tr>
<tr>
<td><code>bytes</code></td>
<td>List</td>
<td>Payload as byte list</td>
</tr>
<tr>
<td><code>closeCode</code></td>
<td>Num</td>
<td>Close status code (only for close frames)</td>
</tr>
<tr>
<td><code>closeReason</code></td>
<td>String</td>
<td>Close reason text (only for close frames)</td>
</tr>
</table>
<h2 id="opcodes">Frame Opcodes</h2>
<table>
<tr>
<th>Opcode</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>0</td>
<td>Continuation</td>
<td>Continuation of fragmented message</td>
</tr>
<tr>
<td>1</td>
<td>Text</td>
<td>UTF-8 text data</td>
</tr>
<tr>
<td>2</td>
<td>Binary</td>
<td>Binary data</td>
</tr>
<tr>
<td>8</td>
<td>Close</td>
<td>Connection close</td>
</tr>
<tr>
<td>9</td>
<td>Ping</td>
<td>Ping (keepalive)</td>
</tr>
<tr>
<td>10</td>
<td>Pong</td>
<td>Pong response</td>
</tr>
</table>
<h3>Common Close Codes</h3>
<table>
<tr>
<th>Code</th>
<th>Meaning</th>
</tr>
<tr>
<td>1000</td>
<td>Normal closure</td>
</tr>
<tr>
<td>1001</td>
<td>Going away (server shutdown)</td>
</tr>
<tr>
<td>1002</td>
<td>Protocol error</td>
</tr>
<tr>
<td>1003</td>
<td>Unsupported data type</td>
</tr>
<tr>
<td>1005</td>
<td>No status received</td>
</tr>
<tr>
<td>1006</td>
<td>Abnormal closure</td>
</tr>
<tr>
<td>1011</td>
<td>Server error</td>
</tr>
</table>
<h2 id="examples">Examples</h2>
<h3>Echo Client</h3>
<pre><code>import "websocket" for WebSocket
var ws = WebSocket.connect("ws://echo.websocket.org")
ws.send("Hello, WebSocket!")
var msg = ws.receive()
if (msg != null && msg.isText) {
System.print("Echo: %(msg.text)")
}
ws.close()</code></pre>
<h3>Chat Client</h3>
<pre><code>import "websocket" for WebSocket
import "json" for Json
var ws = WebSocket.connect("wss://chat.example.com/socket")
ws.send(Json.stringify({
"type": "join",
"room": "general",
"username": "alice"
}))
while (ws.isOpen) {
var msg = ws.receive()
if (msg == null) break
if (msg.isClose) {
System.print("Server closed: %(msg.closeCode) %(msg.closeReason)")
break
}
if (msg.isText) {
var data = Json.parse(msg.text)
if (data["type"] == "message") {
System.print("%(data["from"]): %(data["text"])")
}
}
}
ws.close()</code></pre>
<h3>WebSocket Server</h3>
<pre><code>import "websocket" for WebSocketServer
var server = WebSocketServer.bind("0.0.0.0", 8080)
System.print("WebSocket server listening on port 8080")
while (true) {
var client = server.accept()
if (client == null) continue
System.print("Client connected")
while (client.isOpen) {
var msg = client.receive()
if (msg == null) break
if (msg.isClose) {
System.print("Client disconnected")
break
}
if (msg.isText) {
System.print("Received: %(msg.text)")
client.send("Echo: %(msg.text)")
}
}
}</code></pre>
<h3>Secure WebSocket (WSS)</h3>
<pre><code>import "websocket" for WebSocket
var ws = WebSocket.connect("wss://secure.example.com/socket")
ws.send("Secure message")
var response = ws.receive()
System.print(response.text)
ws.close()</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Ping frames are automatically responded to with pong frames. You typically do not need to handle ping/pong manually unless implementing custom keepalive logic.</p>
</div>
{% endblock %}
+218
View File
@@ -0,0 +1,218 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "yaml" %}
{% set breadcrumb = [{"url": "api/index.html", "title": "API Reference"}, {"title": "yaml"}] %}
{% set prev_page = {"url": "api/websocket.html", "title": "websocket"} %}
{% set next_page = {"url": "tutorials/index.html", "title": "Tutorial List"} %}
{% block article %}
<h1>yaml</h1>
<p>The <code>yaml</code> module provides YAML parsing and stringification. It implements a line-based parser in pure Wren that supports common YAML features including nested structures, lists, and scalar types.</p>
<pre><code>import "yaml" for Yaml</code></pre>
<h2>Yaml Class</h2>
<div class="class-header">
<h3>Yaml</h3>
<p>YAML parsing and stringification</p>
</div>
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Yaml.parse</span>(<span class="param">text</span>) &rarr; <span class="type">Map|List|String|Num|Bool|null</span>
</div>
<p>Parses a YAML string and returns the corresponding Wren value.</p>
<ul class="param-list">
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - YAML string to parse</li>
<li><span class="returns">Returns:</span> Parsed value (Map, List, String, Num, Bool, or null)</li>
</ul>
<pre><code>var data = Yaml.parse("name: Alice\nage: 30")
System.print(data["name"]) // Alice
System.print(data["age"]) // 30
var list = Yaml.parse("- one\n- two\n- three")
System.print(list[0]) // one</code></pre>
<div class="method-signature">
<span class="method-name">Yaml.stringify</span>(<span class="param">value</span>) &rarr; <span class="type">String</span>
</div>
<p>Converts a Wren value to a YAML string with default indentation (2 spaces).</p>
<ul class="param-list">
<li><span class="param-name">value</span> <span class="param-type">(any)</span> - Value to stringify</li>
<li><span class="returns">Returns:</span> YAML string</li>
</ul>
<pre><code>var yaml = Yaml.stringify({"name": "Alice", "age": 30})
System.print(yaml)
// name: Alice
// age: 30</code></pre>
<div class="method-signature">
<span class="method-name">Yaml.stringify</span>(<span class="param">value</span>, <span class="param">indent</span>) &rarr; <span class="type">String</span>
</div>
<p>Converts a Wren value to a YAML string with custom indentation.</p>
<ul class="param-list">
<li><span class="param-name">value</span> <span class="param-type">(any)</span> - Value to stringify</li>
<li><span class="param-name">indent</span> <span class="param-type">(Num)</span> - Number of spaces for indentation</li>
<li><span class="returns">Returns:</span> YAML string</li>
</ul>
<pre><code>var yaml = Yaml.stringify({"server": {"host": "localhost"}}, 4)
System.print(yaml)
// server:
// host: localhost</code></pre>
<h2>Supported Features</h2>
<h3>Key-Value Pairs</h3>
<pre><code>var data = Yaml.parse("
name: Alice
age: 30
email: alice@example.com
")
System.print(data["name"]) // Alice</code></pre>
<h3>Nested Maps</h3>
<pre><code>var config = Yaml.parse("
database:
host: localhost
port: 5432
credentials:
user: admin
password: secret
")
System.print(config["database"]["host"]) // localhost
System.print(config["database"]["credentials"]["user"]) // admin</code></pre>
<h3>Lists</h3>
<pre><code>var data = Yaml.parse("
languages:
- Wren
- C
- Python
")
for (lang in data["languages"]) {
System.print(lang)
}</code></pre>
<h3>Lists of Maps</h3>
<pre><code>var nav = Yaml.parse("
pages:
- file: index
title: Home
- file: about
title: About Us
")
for (page in nav["pages"]) {
System.print(page["title"])
}</code></pre>
<h3>Comments</h3>
<pre><code>var data = Yaml.parse("
# This is a comment
name: test
# Another comment
value: 123
")
System.print(data["name"]) // test</code></pre>
<h3>Data Types</h3>
<pre><code>var types = Yaml.parse("
string: hello world
number: 42
float: 3.14
bool_true: true
bool_false: false
null_value: null
tilde_null: ~
quoted: \"with:special chars\"
")
System.print(types["string"]) // hello world (String)
System.print(types["number"]) // 42 (Num)
System.print(types["bool_true"]) // true (Bool)
System.print(types["null_value"]) // null</code></pre>
<h2>Type Mapping</h2>
<table>
<tr>
<th>YAML Type</th>
<th>Wren Type</th>
</tr>
<tr>
<td>mapping</td>
<td>Map</td>
</tr>
<tr>
<td>sequence</td>
<td>List</td>
</tr>
<tr>
<td>string</td>
<td>String</td>
</tr>
<tr>
<td>integer/float</td>
<td>Num</td>
</tr>
<tr>
<td>true/false</td>
<td>Bool</td>
</tr>
<tr>
<td>null/~</td>
<td>null</td>
</tr>
</table>
<h2>Examples</h2>
<h3>Configuration File</h3>
<pre><code>import "yaml" for Yaml
import "io" for File
var config = Yaml.parse(File.read("config.yaml"))
System.print("Server: %(config["server"]["host"]):%(config["server"]["port"])")
System.print("Database: %(config["database"]["url"])")</code></pre>
<h3>Building and Stringifying</h3>
<pre><code>import "yaml" for Yaml
var data = {
"name": "Project",
"version": "1.0.0",
"dependencies": ["wren", "libuv"],
"settings": {
"debug": true,
"timeout": 30
}
}
System.print(Yaml.stringify(data))</code></pre>
<h3>Round-Trip</h3>
<pre><code>import "yaml" for Yaml
var original = "
title: Test
items:
- first
- second
"
var parsed = Yaml.parse(original)
var reserialized = Yaml.stringify(parsed)
var reparsed = Yaml.parse(reserialized)
System.print(parsed["title"] == reparsed["title"]) // true</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>This module implements a subset of YAML 1.2 suitable for configuration files. Advanced features like anchors, aliases, multi-line strings with <code>|</code> or <code>&gt;</code>, and tags are not supported.</p>
</div>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Strings containing special characters (<code>:</code>, <code>#</code>, quotes) should be quoted in YAML input. The stringify method handles this automatically.</p>
</div>
{% endblock %}