UPdate.
WrenCI / mac (push) Waiting to run
WrenCI / windows (push) Waiting to run
WrenCI / linux (push) Failing after 55s

This commit is contained in:
2026-01-26 05:12:14 +01:00
parent a59dbe1d55
commit fe2f087d9f
143 changed files with 13333 additions and 15112 deletions
@@ -0,0 +1,312 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "Async Programming" %}
{% set breadcrumb = [{"url": "howto/index.html", "title": "How-To Guides"}, {"title": "Async Operations"}] %}
{% set prev_page = {"url": "howto/file-operations.html", "title": "File Operations"} %}
{% set next_page = {"url": "howto/error-handling.html", "title": "Error Handling"} %}
{% block article %}
<h1>Async Programming</h1>
<p>Wren-CLI provides <code>async</code> and <code>await</code> keywords for writing concurrent code. This guide covers the essential patterns for async programming.</p>
<h2>Create an Async Function</h2>
<pre><code>import "scheduler" for Scheduler, Future
var getValue = async { 42 }
var result = await getValue()
System.print(result) // 42</code></pre>
<h2>Async Functions with Parameters</h2>
<pre><code>import "scheduler" for Scheduler, Future
var double = async { |x| x * 2 }
var add = async { |a, b| a + b }
System.print(await double(21)) // 42
System.print(await add(3, 4)) // 7</code></pre>
<h2>Direct Calling vs .call()</h2>
<p>There are two ways to invoke async functions:</p>
<ul>
<li><code>await fn(args)</code> — Direct call, waits immediately (sequential)</li>
<li><code>fn.call(args)</code> — Returns Future, starts without waiting (concurrent)</li>
</ul>
<pre><code>import "scheduler" for Scheduler, Future
var slow = async { |n|
Timer.sleep(100)
return n
}
// SEQUENTIAL: Each call waits before the next starts
var a = await slow(1)
var b = await slow(2)
var c = await slow(3) // Total: ~300ms
// CONCURRENT: All calls start at once, then wait for results
var f1 = slow.call(1)
var f2 = slow.call(2)
var f3 = slow.call(3)
var r1 = await f1
var r2 = await f2
var r3 = await f3 // Total: ~100ms</code></pre>
<h2>Sequential HTTP Requests</h2>
<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"])
}
// Each request waits for the previous one
var user = await fetchJson("https://api.example.com/user/1")
var posts = await fetchJson("https://api.example.com/posts")
var comments = await fetchJson("https://api.example.com/comments")
System.print(user["name"])
System.print(posts.count)
System.print(comments.count)</code></pre>
<h2>Concurrent HTTP Requests</h2>
<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"])
}
// Start all requests at once
var f1 = fetchJson.call("https://api.example.com/user/1")
var f2 = fetchJson.call("https://api.example.com/posts")
var f3 = fetchJson.call("https://api.example.com/comments")
// Wait for results (requests run in parallel)
var user = await f1
var posts = await f2
var comments = await f3
System.print(user["name"])
System.print(posts.count)
System.print(comments.count)</code></pre>
<h2>Batch Processing</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
var urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3",
"https://api.example.com/4",
"https://api.example.com/5"
]
// Start all requests concurrently
var futures = []
for (url in urls) {
futures.add(async { Client.get(url) })
}
// Collect results
var responses = []
for (f in futures) {
responses.add(await f)
}
for (i in 0...urls.count) {
System.print("%(urls[i]): %(responses[i]["status"])")
}</code></pre>
<h2>Reusable Batch Fetcher</h2>
<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"])
}
class BatchFetcher {
static getAll(urls) {
var futures = []
for (url in urls) {
futures.add(fetchJson.call(url))
}
var results = []
for (f in futures) {
results.add(await f)
}
return results
}
}
var urls = [
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments"
]
var results = BatchFetcher.getAll(urls)
for (result in results) {
System.print(result)</code></pre>
<h2>Sleep/Delay</h2>
<pre><code>import "timer" for Timer
System.print("Starting...")
Timer.sleep(1000) // Wait 1 second
System.print("Done!")</code></pre>
<h2>Async with Error Handling</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var safeFetch = async { |url|
var fiber = Fiber.new {
var response = Client.get(url)
return Json.parse(response["body"])
}
var result = fiber.try()
if (fiber.error) {
return {"error": fiber.error}
}
return {"data": result}
}
var result = await safeFetch("https://api.example.com/data")
if (result["error"]) {
System.print("Error: %(result["error"])")
} else {
System.print("Data: %(result["data"])")
}</code></pre>
<h2>Retry with Backoff</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "timer" for Timer
var fetchWithRetry = async { |url, maxRetries|
var attempt = 0
var delay = 1000
while (attempt < maxRetries) {
var fiber = Fiber.new { Client.get(url) }
var result = fiber.try()
if (!fiber.error && result["status"] == 200) {
return result
}
attempt = attempt + 1
System.print("Attempt %(attempt) failed, retrying...")
Timer.sleep(delay)
delay = delay * 2
}
Fiber.abort("All %(maxRetries) attempts failed")
}
var response = await fetchWithRetry("https://api.example.com/data", 3)
System.print("Success: %(response["status"])")</code></pre>
<h2>Polling Pattern</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "timer" for Timer
import "json" for Json
var pollUntilReady = async { |url, maxAttempts|
var attempts = 0
while (attempts < maxAttempts) {
attempts = attempts + 1
System.print("Checking status (attempt %(attempts))...")
var response = Client.get(url)
var data = Json.parse(response["body"])
if (data["status"] == "ready") {
return data
}
Timer.sleep(2000)
}
return null
}
var result = await pollUntilReady("https://api.example.com/job/123", 10)
if (result) {
System.print("Job completed: %(result)")
} else {
System.print("Timed out waiting for job")
}</code></pre>
<h2>Rate Limiting</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "timer" for Timer
var rateLimitedFetch = async { |urls, delayMs|
var results = []
for (url in urls) {
var response = Client.get(url)
results.add(response)
Timer.sleep(delayMs)
}
return results
}
var urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3"
]
var responses = await rateLimitedFetch(urls, 500)
for (r in responses) {
System.print(r["status"])
}</code></pre>
<h2>Graceful Shutdown</h2>
<pre><code>import "signal" for Signal
import "timer" for Timer
var running = true
Signal.handle("SIGINT", Fn.new {
System.print("\nShutting down gracefully...")
running = false
})
System.print("Press Ctrl+C to stop")
while (running) {
System.print("Working...")
Timer.sleep(1000)
}
System.print("Cleanup complete, exiting.")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Always import <code>Scheduler</code> and <code>Future</code> from the scheduler module when using <code>async</code> and <code>await</code>. The syntax requires these classes to be in scope.</p>
</div>
<div class="admonition tip">
<div class="admonition-title">See Also</div>
<p>For more details, see the <a href="../api/scheduler.html">Scheduler API reference</a> and the <a href="../api/web.html">Web module</a> for HTTP client examples.</p>
</div>
{% endblock %}
+378
View File
@@ -0,0 +1,378 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "Error Handling" %}
{% set breadcrumb = [{"url": "howto/index.html", "title": "How-To Guides"}, {"title": "Error Handling"}] %}
{% set prev_page = {"url": "howto/async-operations.html", "title": "Async Operations"} %}
{% set next_page = {"url": "contributing/index.html", "title": "Contributing"} %}
{% block article %}
<h1>Error Handling</h1>
<p>Wren uses fibers for error handling. The <code>fiber.try()</code> method catches errors without crashing your program.</p>
<h2>Basic Try/Catch Pattern</h2>
<pre><code>var fiber = Fiber.new {
Fiber.abort("Something went wrong!")
}
var result = fiber.try()
if (fiber.error) {
System.print("Error: %(fiber.error)")
} else {
System.print("Result: %(result)")
}</code></pre>
<h2>Throw an Error</h2>
<pre><code>Fiber.abort("This is an error message")</code></pre>
<h2>Catch Specific Error Types</h2>
<pre><code>var fiber = Fiber.new {
var x = null
return x.count // Error: null has no method 'count'
}
var result = fiber.try()
if (fiber.error) {
if (fiber.error.contains("null")) {
System.print("Null reference error")
} else {
System.print("Other error: %(fiber.error)")
}
}</code></pre>
<h2>Safe File Read</h2>
<pre><code>import "io" for File
var safeRead = Fn.new { |path|
var fiber = Fiber.new { File.read(path) }
var content = fiber.try()
if (fiber.error) {
return null
}
return content
}
var content = safeRead.call("config.txt")
if (content) {
System.print(content)
} else {
System.print("Could not read file")
}</code></pre>
<h2>Safe JSON Parse</h2>
<pre><code>import "json" for Json
var safeParse = Fn.new { |jsonStr|
var fiber = Fiber.new { Json.parse(jsonStr) }
var data = fiber.try()
if (fiber.error) {
System.print("Invalid JSON: %(fiber.error)")
return null
}
return data
}
var data = safeParse.call('{"valid": true}') // Works
var bad = safeParse.call('not json') // Returns null</code></pre>
<h2>Safe HTTP Request</h2>
<pre><code>import "http" for Http
var safeFetch = Fn.new { |url|
var fiber = Fiber.new { Http.get(url) }
var response = fiber.try()
if (fiber.error) {
return {"error": fiber.error, "ok": false}
}
if (response.statusCode >= 400) {
return {"error": "HTTP %(response.statusCode)", "ok": false}
}
return {"data": response.json, "ok": true}
}
var result = safeFetch.call("https://api.example.com/data")
if (result["ok"]) {
System.print(result["data"])
} else {
System.print("Error: %(result["error"])")
}</code></pre>
<h2>Input Validation</h2>
<pre><code>var validateEmail = Fn.new { |email|
if (email == null) {
Fiber.abort("Email is required")
}
if (!email.contains("@")) {
Fiber.abort("Invalid email format")
}
return true
}
var validate = Fn.new { |email|
var fiber = Fiber.new { validateEmail.call(email) }
fiber.try()
return fiber.error
}
System.print(validate.call(null)) // Email is required
System.print(validate.call("invalid")) // Invalid email format
System.print(validate.call("a@b.com")) // null (no error)</code></pre>
<h2>Result Type Pattern</h2>
<pre><code>class Result {
construct ok(value) {
_value = value
_error = null
}
construct error(message) {
_value = null
_error = message
}
isOk { _error == null }
isError { _error != null }
value { _value }
error { _error }
unwrap {
if (isError) Fiber.abort(_error)
return _value
}
unwrapOr(default) { isOk ? _value : default }
}
var divide = Fn.new { |a, b|
if (b == 0) {
return Result.error("Division by zero")
}
return Result.ok(a / b)
}
var result = divide.call(10, 2)
if (result.isOk) {
System.print("Result: %(result.value)")
}
var bad = divide.call(10, 0)
if (bad.isError) {
System.print("Error: %(bad.error)")
}
System.print(divide.call(10, 5).unwrapOr(0)) // 2
System.print(divide.call(10, 0).unwrapOr(0)) // 0</code></pre>
<h2>Assert Function</h2>
<pre><code>var assert = Fn.new { |condition, message|
if (!condition) {
Fiber.abort("Assertion failed: %(message)")
}
}
var processUser = Fn.new { |user|
assert.call(user != null, "User is required")
assert.call(user["name"] != null, "User name is required")
assert.call(user["age"] >= 0, "Age must be non-negative")
System.print("Processing %(user["name"])...")
}</code></pre>
<h2>Multiple Error Sources</h2>
<pre><code>import "http" for Http
import "json" for Json
var fetchAndParse = Fn.new { |url|
var httpFiber = Fiber.new { Http.get(url) }
var response = httpFiber.try()
if (httpFiber.error) {
return {"error": "Network error: %(httpFiber.error)"}
}
if (response.statusCode != 200) {
return {"error": "HTTP error: %(response.statusCode)"}
}
var jsonFiber = Fiber.new { Json.parse(response.body) }
var data = jsonFiber.try()
if (jsonFiber.error) {
return {"error": "Parse error: %(jsonFiber.error)"}
}
return {"data": data}
}
var result = fetchAndParse.call("https://api.example.com/data")
if (result.containsKey("error")) {
System.print(result["error"])
} else {
System.print(result["data"])
}</code></pre>
<h2>Error Logging</h2>
<pre><code>import "datetime" for DateTime
import "io" for File
class Logger {
construct new(logFile) {
_logFile = logFile
}
log(level, message) {
var timestamp = DateTime.now().toString
var entry = "[%(timestamp)] [%(level)] %(message)\n"
var existing = ""
var fiber = Fiber.new { File.read(_logFile) }
existing = fiber.try() || ""
File.write(_logFile, existing + entry)
if (level == "ERROR") {
System.print("ERROR: %(message)")
}
}
error(message) { log("ERROR", message) }
warn(message) { log("WARN", message) }
info(message) { log("INFO", message) }
}
var logger = Logger.new("app.log")
var safeDivide = Fn.new { |a, b|
if (b == 0) {
logger.error("Division by zero: %(a) / %(b)")
return null
}
return a / b
}
var result = safeDivide.call(10, 0)</code></pre>
<h2>Cleanup with Finally Pattern</h2>
<pre><code>import "sqlite" for Sqlite
var withDatabase = Fn.new { |dbPath, operation|
var db = Sqlite.open(dbPath)
var result = null
var error = null
var fiber = Fiber.new { operation.call(db) }
result = fiber.try()
error = fiber.error
db.close()
if (error) {
Fiber.abort(error)
}
return result
}
var users = withDatabase.call("app.db", Fn.new { |db|
return db.execute("SELECT * FROM users")
})
System.print(users)</code></pre>
<h2>Custom Error Class</h2>
<pre><code>class AppError {
construct new(code, message) {
_code = code
_message = message
}
code { _code }
message { _message }
toString { "[%(code)] %(message)" }
static notFound(resource) {
return AppError.new("NOT_FOUND", "%(resource) not found")
}
static validation(field, reason) {
return AppError.new("VALIDATION", "%(field): %(reason)")
}
static unauthorized() {
return AppError.new("UNAUTHORIZED", "Authentication required")
}
}
var findUser = Fn.new { |id|
if (id == null) {
Fiber.abort(AppError.validation("id", "is required").toString)
}
var user = null
if (user == null) {
Fiber.abort(AppError.notFound("User %(id)").toString)
}
return user
}
var fiber = Fiber.new { findUser.call(null) }
fiber.try()
System.print(fiber.error) // [VALIDATION] id: is required</code></pre>
<h2>Retry on Error</h2>
<pre><code>import "timer" for Timer
var retry = Fn.new { |operation, maxAttempts, delayMs|
var lastError = null
for (i in 1..maxAttempts) {
var fiber = Fiber.new { operation.call() }
var result = fiber.try()
if (!fiber.error) {
return result
}
lastError = fiber.error
System.print("Attempt %(i) failed: %(lastError)")
if (i < maxAttempts) {
Timer.sleep(delayMs)
}
}
Fiber.abort("All %(maxAttempts) attempts failed. Last error: %(lastError)")
}
var result = retry.call(Fn.new {
return "success"
}, 3, 1000)</code></pre>
<div class="admonition tip">
<div class="admonition-title">Best Practices</div>
<ul>
<li>Always wrap external calls (HTTP, file I/O) in fibers</li>
<li>Provide meaningful error messages</li>
<li>Log errors for debugging</li>
<li>Fail fast on programming errors, recover from user errors</li>
<li>Clean up resources (close files, connections) even on error</li>
</ul>
</div>
<div class="admonition note">
<div class="admonition-title">See Also</div>
<p>For more on fibers, see the <a href="../language/fibers.html">Fibers language guide</a>.</p>
</div>
{% endblock %}
+306
View File
@@ -0,0 +1,306 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "File Operations" %}
{% set breadcrumb = [{"url": "howto/index.html", "title": "How-To Guides"}, {"title": "File Operations"}] %}
{% set prev_page = {"url": "howto/regex-patterns.html", "title": "Regex Patterns"} %}
{% set next_page = {"url": "howto/async-operations.html", "title": "Async Operations"} %}
{% block article %}
<h1>File Operations</h1>
<h2>Read Entire File</h2>
<pre><code>import "io" for File
var content = File.read("document.txt")
System.print(content)</code></pre>
<h2>Write to File</h2>
<pre><code>import "io" for File
File.write("output.txt", "Hello, World!")
System.print("File written!")</code></pre>
<h2>Append to File</h2>
<pre><code>import "io" for File
var existing = File.exists("log.txt") ? File.read("log.txt") : ""
File.write("log.txt", existing + "New line\n")</code></pre>
<h2>Check if File Exists</h2>
<pre><code>import "io" for File
if (File.exists("config.txt")) {
System.print("Config found")
var content = File.read("config.txt")
} else {
System.print("Config not found, using defaults")
}</code></pre>
<h2>Get File Size</h2>
<pre><code>import "io" for File
var size = File.size("data.bin")
System.print("File size: %(size) bytes")</code></pre>
<h2>Copy File</h2>
<pre><code>import "io" for File
File.copy("source.txt", "destination.txt")
System.print("File copied!")</code></pre>
<h2>Rename/Move File</h2>
<pre><code>import "io" for File
File.rename("old_name.txt", "new_name.txt")
System.print("File renamed!")
File.rename("file.txt", "subdir/file.txt")
System.print("File moved!")</code></pre>
<h2>Delete File</h2>
<pre><code>import "io" for File
if (File.exists("temp.txt")) {
File.delete("temp.txt")
System.print("File deleted!")
}</code></pre>
<h2>List Directory Contents</h2>
<pre><code>import "io" for Directory
var files = Directory.list(".")
for (file in files) {
System.print(file)
}</code></pre>
<h2>Check if Directory Exists</h2>
<pre><code>import "io" for Directory
if (Directory.exists("data")) {
System.print("Directory found")
} else {
System.print("Directory not found")
}</code></pre>
<h2>Create Directory</h2>
<pre><code>import "io" for Directory
if (!Directory.exists("output")) {
Directory.create("output")
System.print("Directory created!")
}</code></pre>
<h2>Delete Empty Directory</h2>
<pre><code>import "io" for Directory
Directory.delete("empty_folder")
System.print("Directory deleted!")</code></pre>
<h2>Read File Line by Line</h2>
<pre><code>import "io" for File
var content = File.read("data.txt")
var lines = content.split("\n")
for (line in lines) {
if (line.count > 0) {
System.print(line)
}
}</code></pre>
<h2>Process Files in Directory</h2>
<pre><code>import "io" for File, Directory
var files = Directory.list("./data")
for (filename in files) {
if (filename.endsWith(".txt")) {
var path = "./data/%(filename)"
var content = File.read(path)
System.print("%(filename): %(content.count) chars")
}
}</code></pre>
<h2>Recursive Directory Listing</h2>
<pre><code>import "io" for File, Directory
var listRecursive = Fn.new { |path, indent|
var items = Directory.list(path)
for (item in items) {
var fullPath = "%(path)/%(item)"
System.print("%(indent)%(item)")
if (Directory.exists(fullPath)) {
listRecursive.call(fullPath, indent + " ")
}
}
}
listRecursive.call(".", "")</code></pre>
<h2>Find Files by Extension</h2>
<pre><code>import "io" for File, Directory
var findByExtension = Fn.new { |path, ext|
var results = []
var items = Directory.list(path)
for (item in items) {
var fullPath = "%(path)/%(item)"
if (Directory.exists(fullPath)) {
var subResults = findByExtension.call(fullPath, ext)
for (r in subResults) results.add(r)
} else if (item.endsWith(ext)) {
results.add(fullPath)
}
}
return results
}
var wrenFiles = findByExtension.call(".", ".wren")
for (file in wrenFiles) {
System.print(file)
}</code></pre>
<h2>Read JSON Configuration</h2>
<pre><code>import "io" for File
import "json" for Json
var loadConfig = Fn.new { |path, defaults|
if (!File.exists(path)) {
return defaults
}
var content = File.read(path)
var config = Json.parse(content)
for (key in defaults.keys) {
if (!config.containsKey(key)) {
config[key] = defaults[key]
}
}
return config
}
var config = loadConfig.call("config.json", {
"port": 8080,
"debug": false
})
System.print("Port: %(config["port"])")</code></pre>
<h2>Save JSON Configuration</h2>
<pre><code>import "io" for File
import "json" for Json
var config = {
"database": "app.db",
"port": 8080,
"debug": true
}
File.write("config.json", Json.stringify(config, 2))
System.print("Configuration saved!")</code></pre>
<h2>Create Backup Copy</h2>
<pre><code>import "io" for File
import "datetime" for DateTime
var backup = Fn.new { |path|
if (!File.exists(path)) {
System.print("File not found: %(path)")
return null
}
var timestamp = DateTime.now().format("\%Y\%m\%d_\%H\%M\%S")
var backupPath = "%(path).%(timestamp).bak"
File.copy(path, backupPath)
System.print("Backup created: %(backupPath)")
return backupPath
}
backup.call("important.txt")</code></pre>
<h2>Read User Input</h2>
<pre><code>import "io" for Stdin
System.write("Enter your name: ")
var name = Stdin.readLine()
System.print("Hello, %(name)!")</code></pre>
<h2>Interactive Menu</h2>
<pre><code>import "io" for Stdin
System.print("Select an option:")
System.print("1. Option A")
System.print("2. Option B")
System.print("3. Exit")
System.write("Choice: ")
var choice = Stdin.readLine()
if (choice == "1") {
System.print("You selected Option A")
} else if (choice == "2") {
System.print("You selected Option B")
} else if (choice == "3") {
System.print("Goodbye!")
}</code></pre>
<h2>Safe File Operations with Error Handling</h2>
<pre><code>import "io" for File
var safeRead = Fn.new { |path|
var fiber = Fiber.new { File.read(path) }
var result = fiber.try()
if (fiber.error) {
System.print("Error reading %(path): %(fiber.error)")
return null
}
return result
}
var content = safeRead.call("maybe_exists.txt")
if (content) {
System.print("Content: %(content)")
}</code></pre>
<h2>Calculate Directory Size</h2>
<pre><code>import "io" for File, Directory
var dirSize = Fn.new { |path|
var total = 0
var items = Directory.list(path)
for (item in items) {
var fullPath = "%(path)/%(item)"
if (Directory.exists(fullPath)) {
total = total + dirSize.call(fullPath)
} else if (File.exists(fullPath)) {
total = total + File.size(fullPath)
}
}
return total
}
var size = dirSize.call(".")
System.print("Total size: %(size) bytes")</code></pre>
<div class="admonition tip">
<div class="admonition-title">See Also</div>
<p>For full API documentation, see the <a href="../api/io.html">IO module reference</a>. For object-oriented path manipulation with glob, walk, and tree operations, see the <a href="../api/pathlib.html">pathlib module reference</a>.</p>
</div>
{% endblock %}
+234
View File
@@ -0,0 +1,234 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "Making HTTP Requests" %}
{% set breadcrumb = [{"url": "howto/index.html", "title": "How-To Guides"}, {"title": "HTTP Requests"}] %}
{% set prev_page = {"url": "howto/index.html", "title": "How-To List"} %}
{% set next_page = {"url": "howto/json-parsing.html", "title": "JSON Parsing"} %}
{% block article %}
<h1>Making HTTP Requests</h1>
<h2>Basic GET Request</h2>
<pre><code>import "http" for Http
var response = Http.get("https://api.example.com/data")
System.print(response.body)</code></pre>
<h2>GET Request with JSON Response</h2>
<pre><code>import "http" for Http
var response = Http.get("https://jsonplaceholder.typicode.com/posts/1")
var data = response.json
System.print("Title: %(data["title"])")</code></pre>
<h2>GET Request with Headers</h2>
<pre><code>import "http" for Http
var response = Http.get("https://api.example.com/data", {
"Accept": "application/json",
"User-Agent": "Wren-CLI/1.0"
})
System.print(response.body)</code></pre>
<h2>POST Request with JSON Body</h2>
<pre><code>import "http" for Http
import "json" for Json
var data = {
"name": "John Doe",
"email": "john@example.com"
}
var response = Http.post(
"https://api.example.com/users",
Json.stringify(data),
{"Content-Type": "application/json"}
)
System.print("Status: %(response.statusCode)")
System.print("Created: %(response.json)")</code></pre>
<h2>PUT Request</h2>
<pre><code>import "http" for Http
import "json" for Json
var data = {
"id": 1,
"name": "Jane Doe",
"email": "jane@example.com"
}
var response = Http.put(
"https://api.example.com/users/1",
Json.stringify(data),
{"Content-Type": "application/json"}
)
System.print("Updated: %(response.statusCode == 200)")</code></pre>
<h2>DELETE Request</h2>
<pre><code>import "http" for Http
var response = Http.delete("https://api.example.com/users/1")
System.print("Deleted: %(response.statusCode == 204)")</code></pre>
<h2>PATCH Request</h2>
<pre><code>import "http" for Http
import "json" for Json
var response = Http.patch(
"https://api.example.com/users/1",
Json.stringify({"email": "newemail@example.com"}),
{"Content-Type": "application/json"}
)
System.print("Patched: %(response.statusCode)")</code></pre>
<h2>Bearer Token Authentication</h2>
<pre><code>import "http" for Http
var token = "your-api-token"
var response = Http.get("https://api.example.com/protected", {
"Authorization": "Bearer %(token)"
})
System.print(response.json)</code></pre>
<h2>Basic Authentication</h2>
<pre><code>import "http" for Http
import "base64" for Base64
var username = "user"
var password = "pass"
var credentials = Base64.encode("%(username):%(password)")
var response = Http.get("https://api.example.com/protected", {
"Authorization": "Basic %(credentials)"
})
System.print(response.body)</code></pre>
<h2>API Key Authentication</h2>
<pre><code>import "http" for Http
var response = Http.get("https://api.example.com/data", {
"X-API-Key": "your-api-key"
})
System.print(response.body)</code></pre>
<h2>Check Response Status</h2>
<pre><code>import "http" for Http
var response = Http.get("https://api.example.com/data")
if (response.statusCode == 200) {
System.print("Success: %(response.json)")
} else if (response.statusCode == 404) {
System.print("Not found")
} else if (response.statusCode >= 500) {
System.print("Server error: %(response.statusCode)")
} else {
System.print("Error: %(response.statusCode)")
}</code></pre>
<h2>Access Response Headers</h2>
<pre><code>import "http" for Http
var response = Http.get("https://api.example.com/data")
System.print("Content-Type: %(response.headers["content-type"])")
System.print("All headers: %(response.headers)")</code></pre>
<h2>URL Query Parameters</h2>
<pre><code>import "http" for Http
var response = Http.get("https://api.example.com/search?q=wren&limit=10")
System.print(response.json)</code></pre>
<h2>Form URL Encoded POST</h2>
<pre><code>import "http" for Http
var body = "username=john&password=secret"
var response = Http.post(
"https://api.example.com/login",
body,
{"Content-Type": "application/x-www-form-urlencoded"}
)
System.print(response.json)</code></pre>
<h2>Download File</h2>
<pre><code>import "http" for Http
import "io" for File
var response = Http.get("https://example.com/file.txt")
if (response.statusCode == 200) {
File.write("downloaded.txt", response.body)
System.print("File downloaded!")
}</code></pre>
<h2>HTTPS Request</h2>
<pre><code>import "http" for Http
var response = Http.get("https://secure.example.com/api")
System.print(response.body)</code></pre>
<h2>Error Handling</h2>
<pre><code>import "http" for Http
var fiber = Fiber.new {
return Http.get("https://api.example.com/data")
}
var response = fiber.try()
if (fiber.error) {
System.print("Request failed: %(fiber.error)")
} else if (response.statusCode >= 400) {
System.print("HTTP error: %(response.statusCode)")
} else {
System.print("Success: %(response.json)")
}</code></pre>
<h2>Retry on Failure</h2>
<pre><code>import "http" for Http
import "timer" for Timer
var fetchWithRetry = Fn.new { |url, maxRetries|
var attempt = 0
while (attempt < maxRetries) {
var fiber = Fiber.new { Http.get(url) }
var response = fiber.try()
if (!fiber.error && response.statusCode == 200) {
return response
}
attempt = attempt + 1
if (attempt < maxRetries) {
Timer.sleep(1000 * attempt)
}
}
return null
}
var response = fetchWithRetry.call("https://api.example.com/data", 3)
if (response) {
System.print(response.json)
} else {
System.print("Failed after 3 retries")
}</code></pre>
<div class="admonition tip">
<div class="admonition-title">See Also</div>
<p>For a complete API client example, see the <a href="../tutorials/http-client.html">HTTP Client Tutorial</a>. For full API documentation, see the <a href="../api/http.html">HTTP module reference</a>.</p>
</div>
{% endblock %}
+110
View File
@@ -0,0 +1,110 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "How-To Guides" %}
{% set breadcrumb = [{"title": "How-To Guides"}] %}
{% set prev_page = {"url": "tutorials/web-server.html", "title": "Web Server"} %}
{% set next_page = {"url": "howto/http-requests.html", "title": "HTTP Requests"} %}
{% block article %}
<h1>How-To Guides</h1>
<p>Quick, focused guides that show you how to accomplish specific tasks. Each guide provides working code examples you can copy and adapt for your projects.</p>
<div class="card-grid">
<div class="card">
<h3><a href="http-requests.html">Making HTTP Requests</a></h3>
<p>GET, POST, PUT, DELETE requests with headers, authentication, and error handling.</p>
<div class="card-meta">
<span class="tag">http</span>
</div>
</div>
<div class="card">
<h3><a href="json-parsing.html">Working with JSON</a></h3>
<p>Parse JSON strings, access nested data, create JSON output, and handle errors.</p>
<div class="card-meta">
<span class="tag">json</span>
</div>
</div>
<div class="card">
<h3><a href="regex-patterns.html">Using Regular Expressions</a></h3>
<p>Match, search, replace, and split text with regex patterns.</p>
<div class="card-meta">
<span class="tag">regex</span>
</div>
</div>
<div class="card">
<h3><a href="file-operations.html">File Operations</a></h3>
<p>Read, write, copy, and delete files. Work with directories and paths.</p>
<div class="card-meta">
<span class="tag">io</span>
</div>
</div>
<div class="card">
<h3><a href="async-operations.html">Async Programming</a></h3>
<p>Use fibers for concurrent operations, parallel requests, and timeouts.</p>
<div class="card-meta">
<span class="tag">fibers</span>
<span class="tag">scheduler</span>
</div>
</div>
<div class="card">
<h3><a href="error-handling.html">Error Handling</a></h3>
<p>Catch errors with fibers, validate input, and handle edge cases gracefully.</p>
<div class="card-meta">
<span class="tag">fibers</span>
</div>
</div>
</div>
<h2>How-To vs Tutorials</h2>
<p><strong>Tutorials</strong> are learning-oriented. They walk you through building complete applications step by step, introducing concepts gradually.</p>
<p><strong>How-To Guides</strong> are goal-oriented. They assume you know the basics and need to accomplish a specific task quickly. Each guide focuses on one topic with copy-paste examples.</p>
<h2>Quick Reference</h2>
<table>
<tr>
<th>Task</th>
<th>Guide</th>
<th>Key Functions</th>
</tr>
<tr>
<td>Fetch data from API</td>
<td><a href="http-requests.html">HTTP Requests</a></td>
<td><code>Http.get()</code>, <code>response.json</code></td>
</tr>
<tr>
<td>Parse JSON string</td>
<td><a href="json-parsing.html">JSON Parsing</a></td>
<td><code>Json.parse()</code></td>
</tr>
<tr>
<td>Validate email format</td>
<td><a href="regex-patterns.html">Regex Patterns</a></td>
<td><code>Regex.new().test()</code></td>
</tr>
<tr>
<td>Read file contents</td>
<td><a href="file-operations.html">File Operations</a></td>
<td><code>File.read()</code></td>
</tr>
<tr>
<td>Run tasks in parallel</td>
<td><a href="async-operations.html">Async Operations</a></td>
<td><code>Fiber.new { }</code></td>
</tr>
<tr>
<td>Handle runtime errors</td>
<td><a href="error-handling.html">Error Handling</a></td>
<td><code>fiber.try()</code></td>
</tr>
</table>
{% endblock %}
+243
View File
@@ -0,0 +1,243 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "Working with JSON" %}
{% set breadcrumb = [{"url": "howto/index.html", "title": "How-To Guides"}, {"title": "JSON Parsing"}] %}
{% set prev_page = {"url": "howto/http-requests.html", "title": "HTTP Requests"} %}
{% set next_page = {"url": "howto/regex-patterns.html", "title": "Regex Patterns"} %}
{% block article %}
<h1>Working with JSON</h1>
<h2>Parse JSON String</h2>
<pre><code>import "json" for Json
var jsonStr = '{"name": "Alice", "age": 30}'
var data = Json.parse(jsonStr)
System.print(data["name"]) // Alice
System.print(data["age"]) // 30</code></pre>
<h2>Parse JSON Array</h2>
<pre><code>import "json" for Json
var jsonStr = '[1, 2, 3, "four", true, null]'
var items = Json.parse(jsonStr)
for (item in items) {
System.print(item)
}</code></pre>
<h2>Access Nested Objects</h2>
<pre><code>import "json" for Json
var jsonStr = '{"user": {"name": "Bob", "address": {"city": "NYC"}}}'
var data = Json.parse(jsonStr)
System.print(data["user"]["name"]) // Bob
System.print(data["user"]["address"]["city"]) // NYC</code></pre>
<h2>Convert Wren Object to JSON</h2>
<pre><code>import "json" for Json
var data = {
"name": "Charlie",
"age": 25,
"active": true
}
var jsonStr = Json.stringify(data)
System.print(jsonStr) // {"name":"Charlie","age":25,"active":true}</code></pre>
<h2>Pretty Print JSON</h2>
<pre><code>import "json" for Json
var data = {
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]
}
var pretty = Json.stringify(data, 2)
System.print(pretty)</code></pre>
<p>Output:</p>
<pre><code>{
"users": [
{
"name": "Alice",
"age": 30
},
{
"name": "Bob",
"age": 25
}
]
}</code></pre>
<h2>Check if Key Exists</h2>
<pre><code>import "json" for Json
var data = Json.parse('{"name": "Alice"}')
if (data.containsKey("name")) {
System.print("Name: %(data["name"])")
}
if (!data.containsKey("age")) {
System.print("Age not specified")
}</code></pre>
<h2>Provide Default Values</h2>
<pre><code>import "json" for Json
var data = Json.parse('{"name": "Alice"}')
var name = data["name"]
var age = data.containsKey("age") ? data["age"] : 0
var city = data.containsKey("city") ? data["city"] : "Unknown"
System.print("%(name), %(age), %(city)")</code></pre>
<h2>Iterate Over Object Keys</h2>
<pre><code>import "json" for Json
var data = Json.parse('{"a": 1, "b": 2, "c": 3}')
for (key in data.keys) {
System.print("%(key): %(data[key])")
}</code></pre>
<h2>Iterate Over Array</h2>
<pre><code>import "json" for Json
var users = Json.parse('[{"name": "Alice"}, {"name": "Bob"}]')
for (i in 0...users.count) {
System.print("%(i + 1). %(users[i]["name"])")
}</code></pre>
<h2>Modify JSON Data</h2>
<pre><code>import "json" for Json
var data = Json.parse('{"name": "Alice", "age": 30}')
data["age"] = 31
data["email"] = "alice@example.com"
data.remove("name")
System.print(Json.stringify(data))</code></pre>
<h2>Parse JSON from File</h2>
<pre><code>import "json" for Json
import "io" for File
var content = File.read("config.json")
var config = Json.parse(content)
System.print(config["setting"])</code></pre>
<h2>Write JSON to File</h2>
<pre><code>import "json" for Json
import "io" for File
var data = {
"database": "myapp.db",
"port": 8080,
"debug": true
}
File.write("config.json", Json.stringify(data, 2))</code></pre>
<h2>Handle Parse Errors</h2>
<pre><code>import "json" for Json
var jsonStr = "invalid json {"
var fiber = Fiber.new { Json.parse(jsonStr) }
var result = fiber.try()
if (fiber.error) {
System.print("Parse error: %(fiber.error)")
} else {
System.print(result)
}</code></pre>
<h2>Work with Null Values</h2>
<pre><code>import "json" for Json
var data = Json.parse('{"name": "Alice", "address": null}')
if (data["address"] == null) {
System.print("No address provided")
}
var output = {"value": null}
System.print(Json.stringify(output)) // {"value":null}</code></pre>
<h2>Build JSON Array Dynamically</h2>
<pre><code>import "json" for Json
var users = []
users.add({"name": "Alice", "role": "admin"})
users.add({"name": "Bob", "role": "user"})
users.add({"name": "Charlie", "role": "user"})
System.print(Json.stringify(users, 2))</code></pre>
<h2>Filter JSON Array</h2>
<pre><code>import "json" for Json
var users = Json.parse('[
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 17},
{"name": "Charlie", "age": 25}
]')
var adults = []
for (user in users) {
if (user["age"] >= 18) {
adults.add(user)
}
}
System.print("Adults: %(Json.stringify(adults))")</code></pre>
<h2>Transform JSON Data</h2>
<pre><code>import "json" for Json
var users = Json.parse('[
{"firstName": "Alice", "lastName": "Smith"},
{"firstName": "Bob", "lastName": "Jones"}
]')
var names = []
for (user in users) {
names.add("%(user["firstName"]) %(user["lastName"])")
}
System.print(names.join(", "))</code></pre>
<h2>Merge JSON Objects</h2>
<pre><code>import "json" for Json
var defaults = {"theme": "light", "language": "en", "timeout": 30}
var userPrefs = {"theme": "dark"}
var config = {}
for (key in defaults.keys) {
config[key] = defaults[key]
}
for (key in userPrefs.keys) {
config[key] = userPrefs[key]
}
System.print(Json.stringify(config, 2))</code></pre>
<div class="admonition tip">
<div class="admonition-title">See Also</div>
<p>For full API documentation, see the <a href="../api/json.html">JSON module reference</a>.</p>
</div>
{% endblock %}
+237
View File
@@ -0,0 +1,237 @@
{# retoor <retoor@molodetz.nl> #}
{% extends 'page.html' %}
{% set page_title = "Using Regular Expressions" %}
{% set breadcrumb = [{"url": "howto/index.html", "title": "How-To Guides"}, {"title": "Regex Patterns"}] %}
{% set prev_page = {"url": "howto/json-parsing.html", "title": "JSON Parsing"} %}
{% set next_page = {"url": "howto/file-operations.html", "title": "File Operations"} %}
{% block article %}
<h1>Using Regular Expressions</h1>
<h2>Test if String Matches Pattern</h2>
<pre><code>import "regex" for Regex
var pattern = Regex.new("^hello")
System.print(pattern.test("hello world")) // true
System.print(pattern.test("say hello")) // false</code></pre>
<h2>Find First Match</h2>
<pre><code>import "regex" for Regex
var pattern = Regex.new("\\d+")
var match = pattern.match("Order 12345 shipped")
if (match) {
System.print(match.text) // 12345
System.print(match.start) // 6
System.print(match.end) // 11
}</code></pre>
<h2>Find All Matches</h2>
<pre><code>import "regex" for Regex
var pattern = Regex.new("\\d+")
var matches = pattern.matchAll("Items: 10, 20, 30")
for (match in matches) {
System.print(match.text)
}
// 10
// 20
// 30</code></pre>
<h2>Capture Groups</h2>
<pre><code>import "regex" for Regex
var pattern = Regex.new("(\\w+)@(\\w+\\.\\w+)")
var match = pattern.match("Contact: alice@example.com")
if (match) {
System.print(match.group(0)) // alice@example.com
System.print(match.group(1)) // alice
System.print(match.group(2)) // example.com
}</code></pre>
<h2>Replace Matches</h2>
<pre><code>import "regex" for Regex
var pattern = Regex.new("\\bcat\\b")
var result = pattern.replace("The cat sat on the cat mat", "dog")
System.print(result) // The dog sat on the dog mat</code></pre>
<h2>Replace with Callback</h2>
<pre><code>import "regex" for Regex
var pattern = Regex.new("\\d+")
var result = pattern.replace("a1b2c3", Fn.new { |match|
return "[%(match.text)]"
})
System.print(result) // a[1]b[2]c[3]</code></pre>
<h2>Split String</h2>
<pre><code>import "regex" for Regex
var pattern = Regex.new("[,;\\s]+")
var parts = pattern.split("apple, banana; cherry date")
for (part in parts) {
System.print(part)
}
// apple
// banana
// cherry
// date</code></pre>
<h2>Case Insensitive Matching</h2>
<pre><code>import "regex" for Regex
var pattern = Regex.new("hello", "i")
System.print(pattern.test("Hello World")) // true
System.print(pattern.test("HELLO")) // true</code></pre>
<h2>Multiline Matching</h2>
<pre><code>import "regex" for Regex
var text = "Line 1\nLine 2\nLine 3"
var pattern = Regex.new("^Line", "m")
var matches = pattern.matchAll(text)
System.print(matches.count) // 3</code></pre>
<h2>Common Patterns</h2>
<h3>Validate Email</h3>
<pre><code>import "regex" for Regex
var emailPattern = Regex.new("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
System.print(emailPattern.test("user@example.com")) // true
System.print(emailPattern.test("invalid-email")) // false</code></pre>
<h3>Validate URL</h3>
<pre><code>import "regex" for Regex
var urlPattern = Regex.new("^https?://[a-zA-Z0-9.-]+(/.*)?$")
System.print(urlPattern.test("https://example.com")) // true
System.print(urlPattern.test("http://example.com/path")) // true
System.print(urlPattern.test("ftp://invalid")) // false</code></pre>
<h3>Validate Phone Number</h3>
<pre><code>import "regex" for Regex
var phonePattern = Regex.new("^\\+?\\d{1,3}[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$")
System.print(phonePattern.test("+1-555-123-4567")) // true
System.print(phonePattern.test("(555) 123-4567")) // true</code></pre>
<h3>Extract Numbers</h3>
<pre><code>import "regex" for Regex
var numberPattern = Regex.new("-?\\d+\\.?\\d*")
var text = "Temperature: -5.5 to 32.0 degrees"
var matches = numberPattern.matchAll(text)
for (match in matches) {
System.print(match.text)
}
// -5.5
// 32.0</code></pre>
<h3>Extract Hashtags</h3>
<pre><code>import "regex" for Regex
var hashtagPattern = Regex.new("#\\w+")
var text = "Check out #wren and #programming!"
var matches = hashtagPattern.matchAll(text)
for (match in matches) {
System.print(match.text)
}
// #wren
// #programming</code></pre>
<h3>Remove HTML Tags</h3>
<pre><code>import "regex" for Regex
var tagPattern = Regex.new("<[^>]+>")
var html = "<p>Hello <b>World</b>!</p>"
var text = tagPattern.replace(html, "")
System.print(text) // Hello World!</code></pre>
<h3>Validate Password Strength</h3>
<pre><code>import "regex" for Regex
var hasUpper = Regex.new("[A-Z]")
var hasLower = Regex.new("[a-z]")
var hasDigit = Regex.new("\\d")
var hasSpecial = Regex.new("[!@#$%^&*]")
var minLength = 8
var validatePassword = Fn.new { |password|
if (password.count < minLength) return false
if (!hasUpper.test(password)) return false
if (!hasLower.test(password)) return false
if (!hasDigit.test(password)) return false
if (!hasSpecial.test(password)) return false
return true
}
System.print(validatePassword.call("Weak")) // false
System.print(validatePassword.call("Strong@Pass1")) // true</code></pre>
<h3>Parse Log Lines</h3>
<pre><code>import "regex" for Regex
var logPattern = Regex.new("\\[(\\d{4}-\\d{2}-\\d{2})\\]\\s+(\\w+):\\s+(.+)")
var line = "[2024-01-15] ERROR: Connection failed"
var match = logPattern.match(line)
if (match) {
System.print("Date: %(match.group(1))") // 2024-01-15
System.print("Level: %(match.group(2))") // ERROR
System.print("Message: %(match.group(3))") // Connection failed
}</code></pre>
<h3>Validate IP Address</h3>
<pre><code>import "regex" for Regex
var ipPattern = Regex.new("^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$")
System.print(ipPattern.test("192.168.1.1")) // true
System.print(ipPattern.test("256.1.1.1")) // false
System.print(ipPattern.test("10.0.0.255")) // true</code></pre>
<h2>Pattern Syntax Quick Reference</h2>
<table>
<tr><th>Pattern</th><th>Description</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>\\w</code></td><td>Word character (a-z, A-Z, 0-9, _)</td></tr>
<tr><td><code>\\s</code></td><td>Whitespace</td></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>*</code></td><td>Zero or more</td></tr>
<tr><td><code>+</code></td><td>One or more</td></tr>
<tr><td><code>?</code></td><td>Zero or one</td></tr>
<tr><td><code>{n,m}</code></td><td>Between n and m times</td></tr>
<tr><td><code>[abc]</code></td><td>Character class</td></tr>
<tr><td><code>[^abc]</code></td><td>Negated character class</td></tr>
<tr><td><code>(group)</code></td><td>Capture group</td></tr>
<tr><td><code>a|b</code></td><td>Alternation (a or b)</td></tr>
<tr><td><code>\\b</code></td><td>Word boundary</td></tr>
</table>
<div class="admonition tip">
<div class="admonition-title">See Also</div>
<p>For full API documentation, see the <a href="../api/regex.html">Regex module reference</a>.</p>
</div>
{% endblock %}