This commit is contained in:
retoor 2026-01-25 19:02:02 +01:00
parent a830238d11
commit 1ee28a1644
92 changed files with 10955 additions and 621 deletions

View File

@ -1,6 +1,6 @@
# retoor <retoor@molodetz.nl>
.PHONY: build tests clean debug
.PHONY: build tests clean debug sync-sidebar buildmanual
build:
cd projects/make && $(MAKE) -f wren_cli.make -j $$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
@ -25,3 +25,6 @@ install:
clean:
cd projects/make && $(MAKE) -f wren_cli.make clean
buildmanual:
python3 util/build_manual.py

117
example/faker_demo.wren vendored Normal file
View File

@ -0,0 +1,117 @@
// retoor <retoor@molodetz.nl>
import "faker" for Faker
System.print("=== Faker Module Demo ===\n")
System.print("--- Person Information ---")
System.print("Name: %(Faker.name())")
System.print("First Name (Male): %(Faker.firstNameMale())")
System.print("First Name (Female): %(Faker.firstNameFemale())")
System.print("Username: %(Faker.username())")
System.print("Email: %(Faker.email())")
System.print("Phone: %(Faker.phoneNumber())")
System.print("Gender: %(Faker.gender())")
System.print("\n--- Address Information ---")
System.print("Street Address: %(Faker.streetAddress())")
System.print("City: %(Faker.city())")
System.print("State: %(Faker.state()) (%(Faker.stateAbbr()))")
System.print("ZIP Code: %(Faker.zipCode())")
System.print("Country: %(Faker.country()) (%(Faker.countryCode()))")
System.print("Full Address: %(Faker.address())")
System.print("Latitude: %(Faker.latitude())")
System.print("Longitude: %(Faker.longitude())")
System.print("\n--- Internet Data ---")
System.print("IPv4: %(Faker.ipv4())")
System.print("IPv6: %(Faker.ipv6())")
System.print("MAC Address: %(Faker.macAddress())")
System.print("Domain: %(Faker.domainName())")
System.print("URL: %(Faker.url())")
System.print("Password: %(Faker.password())")
System.print("UUID: %(Faker.uuid())")
System.print("User Agent: %(Faker.userAgent())")
System.print("\n--- Company and Job ---")
System.print("Company: %(Faker.company())")
System.print("Job Title: %(Faker.jobTitle())")
System.print("Job Descriptor: %(Faker.jobDescriptor())")
System.print("\n--- Product and Commerce ---")
System.print("Product: %(Faker.product())")
System.print("Category: %(Faker.productCategory())")
System.print("Price: $%(Faker.price())")
System.print("Currency: %(Faker.currency()) (%(Faker.currencySymbol()))")
System.print("Credit Card: %(Faker.creditCardType()) %(Faker.creditCardNumber())")
System.print("CVV: %(Faker.creditCardCVV())")
System.print("Expiry: %(Faker.creditCardExpiryDate())")
System.print("\n--- Date and Time ---")
System.print("Date: %(Faker.date())")
System.print("Past Date: %(Faker.pastDate())")
System.print("Future Date: %(Faker.futureDate())")
System.print("Date of Birth: %(Faker.dateOfBirth())")
System.print("Month: %(Faker.monthName())")
System.print("Day of Week: %(Faker.dayOfWeek())")
System.print("Time: %(Faker.time())")
System.print("\n--- Text Generation ---")
System.print("Word: %(Faker.word())")
System.print("Words: %(Faker.words(5).join(" "))")
System.print("Sentence: %(Faker.sentence())")
System.print("Paragraph: %(Faker.paragraph())")
System.print("Slug: %(Faker.slug())")
System.print("\n--- Colors ---")
System.print("Color Name: %(Faker.colorName())")
System.print("Hex Color: %(Faker.hexColor())")
System.print("RGB Color: %(Faker.rgbColor())")
System.print("\n--- File and Tech ---")
System.print("Filename: %(Faker.fileName())")
System.print("Extension: %(Faker.fileExtension())")
System.print("MIME Type: %(Faker.mimeType())")
System.print("Semver: %(Faker.semver())")
System.print("\n--- Banking ---")
System.print("IBAN: %(Faker.iban())")
System.print("Account Number: %(Faker.accountNumber())")
System.print("Routing Number: %(Faker.routingNumber())")
System.print("\n--- Cryptographic ---")
System.print("MD5: %(Faker.md5())")
System.print("SHA1: %(Faker.sha1())")
System.print("SHA256: %(Faker.sha256())")
System.print("\n--- Format Helpers ---")
System.print("Numerify ###-###-####: %(Faker.numerify("###-###-####"))")
System.print("Letterify ???-???: %(Faker.letterify("???-???"))")
System.print("Bothify ??-###: %(Faker.bothify("??-###"))")
System.print("\n--- Seeding for Reproducibility ---")
Faker.seed(42)
System.print("Seeded name 1: %(Faker.name())")
Faker.seed(42)
System.print("Seeded name 2: %(Faker.name())")
Faker.reset()
System.print("\n--- Profile Generation ---")
var profile = Faker.profile()
System.print("Profile:")
System.print(" Username: %(profile["username"])")
System.print(" Name: %(profile["name"])")
System.print(" Email: %(profile["email"])")
System.print(" Address: %(profile["address"])")
System.print(" Phone: %(profile["phone"])")
System.print(" Job: %(profile["job"])")
System.print(" Company: %(profile["company"])")
System.print(" Birthdate: %(profile["birthdate"])")
System.print("\n--- Bulk Generation Example ---")
System.print("Generating 5 users:")
for (i in 1..5) {
System.print(" %(i). %(Faker.name()) <%(Faker.email())>")
}
System.print("\n=== Demo Complete ===")

58
example/subprocess_async_demo.wren vendored Normal file
View File

@ -0,0 +1,58 @@
// retoor <retoor@molodetz.nl>
import "subprocess" for Popen
System.print("=== Concurrent Ping Demo (Parallel Streaming) ===\n")
class PingTask {
construct new(host, label) {
_host = host
_label = label
_done = false
_proc = Popen.new(["ping", "-c", "3", host])
}
tick() {
if (_done) return
var chunk = _proc.stdout.read()
if (chunk == "") {
_done = true
} else {
for (line in chunk.split("\n")) {
if (line.trim() != "") {
System.print("[%(_label)] %(line)")
}
}
}
}
isDone { _done }
label { _label }
host { _host }
}
var tasks = [
PingTask.new("127.0.0.1", "LOCAL"),
PingTask.new("8.8.8.8", "GOOGLE"),
PingTask.new("1.1.1.1", "CLOUDFLARE"),
PingTask.new("9.9.9.9", "QUAD9"),
PingTask.new("208.67.222.222", "OPENDNS")
]
System.print("Starting 5 concurrent pings...")
for (t in tasks) System.print(" %(t.label) -> %(t.host)")
System.print("")
while (true) {
var allDone = true
for (task in tasks) {
if (!task.isDone) {
allDone = false
task.tick()
}
}
if (allDone) break
}
System.print("")
System.print("All 5 pings completed concurrently.")

86
example/subprocess_concurrent_demo.wren vendored Normal file
View File

@ -0,0 +1,86 @@
// retoor <retoor@molodetz.nl>
import "subprocess" for Popen
System.print("=== Concurrent Ping Demo - 5 Processes ===")
System.print("")
var p1 = Popen.new(["ping", "-c", "3", "127.0.0.1"])
var p2 = Popen.new(["ping", "-c", "3", "8.8.8.8"])
var p3 = Popen.new(["ping", "-c", "3", "1.1.1.1"])
var p4 = Popen.new(["ping", "-c", "3", "9.9.9.9"])
var p5 = Popen.new(["ping", "-c", "3", "208.67.222.222"])
System.print("Started 5 ping processes simultaneously:")
System.print(" PID %(p1.pid) -> 127.0.0.1 (localhost)")
System.print(" PID %(p2.pid) -> 8.8.8.8 (Google DNS)")
System.print(" PID %(p3.pid) -> 1.1.1.1 (Cloudflare)")
System.print(" PID %(p4.pid) -> 9.9.9.9 (Quad9)")
System.print(" PID %(p5.pid) -> 208.67.222.222 (OpenDNS)")
System.print("")
var done1 = false
var done2 = false
var done3 = false
var done4 = false
var done5 = false
while (!done1 || !done2 || !done3 || !done4 || !done5) {
if (!done1) {
var chunk = p1.stdout.read()
if (chunk == "") {
done1 = true
} else {
for (line in chunk.split("\n")) {
if (line.trim() != "") System.print("[LOCAL ] %(line)")
}
}
}
if (!done2) {
var chunk = p2.stdout.read()
if (chunk == "") {
done2 = true
} else {
for (line in chunk.split("\n")) {
if (line.trim() != "") System.print("[GOOGLE ] %(line)")
}
}
}
if (!done3) {
var chunk = p3.stdout.read()
if (chunk == "") {
done3 = true
} else {
for (line in chunk.split("\n")) {
if (line.trim() != "") System.print("[CLOUDFLR] %(line)")
}
}
}
if (!done4) {
var chunk = p4.stdout.read()
if (chunk == "") {
done4 = true
} else {
for (line in chunk.split("\n")) {
if (line.trim() != "") System.print("[QUAD9 ] %(line)")
}
}
}
if (!done5) {
var chunk = p5.stdout.read()
if (chunk == "") {
done5 = true
} else {
for (line in chunk.split("\n")) {
if (line.trim() != "") System.print("[OPENDNS ] %(line)")
}
}
}
}
System.print("")
System.print("All 5 ping processes completed concurrently!")

74
example/subprocess_fiber_demo.wren vendored Normal file
View File

@ -0,0 +1,74 @@
// retoor <retoor@molodetz.nl>
import "subprocess" for Popen
import "scheduler" for Scheduler
class AsyncPing {
construct new(host, label) {
_host = host
_label = label
_output = []
_done = false
}
start() {
_proc = Popen.new(["ping", "-c", "3", _host])
_fiber = Fiber.new {
while (true) {
var chunk = _proc.stdout.read()
if (chunk == "") {
_done = true
Fiber.yield()
return
}
for (line in chunk.split("\n")) {
if (line.trim() != "") _output.add(line)
}
Fiber.yield()
}
}
return this
}
tick() {
if (!_done && !_fiber.isDone) _fiber.call()
}
isDone { _done || _fiber.isDone }
label { _label }
output { _output }
host { _host }
}
System.print("=== Async Fiber Demo - 5 Concurrent Pings ===")
System.print("")
var tasks = [
AsyncPing.new("127.0.0.1", "LOCAL ").start(),
AsyncPing.new("8.8.8.8", "GOOGLE ").start(),
AsyncPing.new("1.1.1.1", "CLOUDFLR").start(),
AsyncPing.new("9.9.9.9", "QUAD9 ").start(),
AsyncPing.new("208.67.222.222", "OPENDNS ").start()
]
System.print("Started 5 async ping tasks:")
for (t in tasks) System.print(" [%(t.label)] -> %(t.host)")
System.print("")
while (true) {
var allDone = true
for (task in tasks) {
if (!task.isDone) {
allDone = false
var before = task.output.count
task.tick()
for (i in before...task.output.count) {
System.print("[%(task.label)] %(task.output[i])")
}
}
}
if (allDone) break
}
System.print("")
System.print("All 5 async tasks completed!")

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
@ -115,7 +129,17 @@
<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</code></pre>
<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>
@ -241,6 +265,48 @@ System.print(await square(8)) // 64</code></pre>
<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>
@ -266,28 +332,34 @@ class Timer {
<h2>Examples</h2>
<h3>Sequential vs Parallel</h3>
<p>Operations are sequential by default:</p>
<pre><code>import "http" for Http
<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
// These run one after another (sequential)
var r1 = Http.get("https://api.example.com/1")
var r2 = Http.get("https://api.example.com/2")
var r3 = Http.get("https://api.example.com/3")</code></pre>
var fetch = async { |url| Client.get(url) }
<p>For parallel operations, use multiple fibers:</p>
<pre><code>import "http" for Http
// 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>
var fibers = [
Fiber.new { Http.get("https://api.example.com/1") },
Fiber.new { Http.get("https://api.example.com/2") },
Fiber.new { Http.get("https://api.example.com/3") }
]
<p>Using <code>.call()</code> enables concurrent execution:</p>
<pre><code>import "scheduler" for Scheduler, Future
import "web" for Client
// Start all fibers
for (f in fibers) f.call()
var fetch = async { |url| Client.get(url) }
// Results are already available when fibers complete</code></pre>
// 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>

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
@ -115,7 +129,7 @@
<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</code></pre>
<pre><code>import "web" for Application, Router, Request, Response, View, Session, Client, WebSocketResponse</code></pre>
<h2>Application Class</h2>
@ -177,6 +191,21 @@
<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>
@ -345,8 +374,300 @@
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">
@ -551,6 +872,244 @@ var postResponse = Client.post("https://api.example.com/users", {
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>
@ -558,8 +1117,8 @@ var data = Json.parse(postResponse["body"])</code></pre>
</article>
<footer class="page-footer">
<a href="markdown.html" class="prev">markdown</a>
<a href="index.html" class="next">Overview</a>
<a href="wdantic.html" class="prev">wdantic</a>
<a href="websocket.html" class="next">websocket</a>
</footer>
</main>
</div>

View File

@ -85,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -0,0 +1,548 @@
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Async Patterns - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="index.html">Overview</a></li>
<li><a href="module-overview.html">Module Architecture</a></li>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="c-backed-module.html">C-Backed Modules</a></li>
<li><a href="foreign-classes.html">Foreign Classes</a></li>
<li><a href="async-patterns.html" class="active">Async Patterns</a></li>
<li><a href="testing.html">Writing Tests</a></li>
<li><a href="documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<a href="index.html">Contributing</a>
<span class="separator">/</span>
<span>Async Patterns</span>
</nav>
<article>
<h1>Async Patterns</h1>
<p>Wren-CLI uses libuv for non-blocking I/O. Wren fibers suspend during I/O operations and resume when complete. This section explains how to implement async operations in C-backed modules.</p>
<h2>The Scheduler/Fiber Pattern</h2>
<p>The standard pattern for async operations involves:</p>
<ol>
<li>A public Wren method that users call</li>
<li>An internal foreign method (ending with <code>_</code>) that takes a fiber handle</li>
<li>C code that stores the fiber handle and schedules async work</li>
<li>A libuv callback that resumes the fiber with the result</li>
</ol>
<h2>Basic Example</h2>
<h3>Wren Interface</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "scheduler" for Scheduler
class AsyncFile {
foreign static read_(path, fiber)
static read(path) {
return Scheduler.await_ { read_(path, Fiber.current) }
}
}</code></pre>
<p>The public <code>read</code> method wraps the internal <code>read_</code> method. <code>Scheduler.await_</code> suspends the current fiber until the async operation completes.</p>
<h3>Naming Convention</h3>
<ul>
<li>Internal methods end with <code>_</code> (e.g., <code>read_</code>, <code>write_</code>)</li>
<li>Public methods have clean names (e.g., <code>read</code>, <code>write</code>)</li>
<li>Internal methods take a fiber as the last argument</li>
</ul>
<h2>C Implementation Structure</h2>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;uv.h&gt;
#include "asyncfile.h"
#include "wren.h"
#include "vm.h"
typedef struct {
WrenVM* vm;
WrenHandle* fiber;
uv_fs_t req;
char* path;
uv_buf_t buffer;
} ReadRequest;
void asyncFileRead(WrenVM* vm) {
const char* path = wrenGetSlotString(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
ReadRequest* request = (ReadRequest*)malloc(sizeof(ReadRequest));
request->vm = vm;
request->fiber = fiber;
request->path = strdup(path);
request->req.data = request;
uv_loop_t* loop = getLoop();
uv_fs_open(loop, &request->req, path, UV_FS_O_RDONLY, 0, onFileOpened);
}
void onFileOpened(uv_fs_t* req) {
ReadRequest* request = (ReadRequest*)req->data;
uv_fs_req_cleanup(req);
if (req->result < 0) {
resumeWithError(request, "Failed to open file.");
return;
}
int fd = (int)req->result;
uv_fs_fstat(getLoop(), &request->req, fd, onFileStat);
}
// ... additional callbacks for fstat, read, close ...</code></pre>
<h2>Fiber Handle Management</h2>
<h3>Capturing the Fiber</h3>
<pre><code>WrenHandle* fiber = wrenGetSlotHandle(vm, 2);</code></pre>
<p>The fiber handle is obtained from the slot where it was passed. This handle must be stored for later use.</p>
<h3>Resuming the Fiber</h3>
<p>Use the scheduler's resume mechanism:</p>
<pre><code>void resumeWithResult(ReadRequest* request, const char* result) {
WrenVM* vm = request->vm;
schedulerResume(request->fiber, true);
wrenReleaseHandle(vm, request->fiber);
wrenEnsureSlots(vm, 1);
wrenSetSlotString(vm, 0, result);
free(request->path);
free(request);
}
void resumeWithError(ReadRequest* request, const char* error) {
WrenVM* vm = request->vm;
schedulerResume(request->fiber, false);
wrenReleaseHandle(vm, request->fiber);
wrenEnsureSlots(vm, 1);
wrenSetSlotString(vm, 0, error);
free(request->path);
free(request);
}</code></pre>
<h3>schedulerResume</h3>
<pre><code>void schedulerResume(WrenHandle* fiber, bool success);</code></pre>
<ul>
<li><code>fiber</code>: The fiber handle to resume</li>
<li><code>success</code>: true if the operation succeeded, false for error</li>
</ul>
<p>After calling <code>schedulerResume</code>, the value in slot 0 becomes the return value (for success) or error message (for failure).</p>
<h2>libuv Integration</h2>
<h3>Getting the Event Loop</h3>
<pre><code>uv_loop_t* loop = getLoop();</code></pre>
<p>The <code>getLoop()</code> function returns the global libuv event loop used by Wren-CLI.</p>
<h3>Common libuv Operations</h3>
<table>
<tr>
<th>Operation</th>
<th>libuv Function</th>
</tr>
<tr>
<td>File read</td>
<td><code>uv_fs_read</code></td>
</tr>
<tr>
<td>File write</td>
<td><code>uv_fs_write</code></td>
</tr>
<tr>
<td>TCP connect</td>
<td><code>uv_tcp_connect</code></td>
</tr>
<tr>
<td>DNS lookup</td>
<td><code>uv_getaddrinfo</code></td>
</tr>
<tr>
<td>Timer</td>
<td><code>uv_timer_start</code></td>
</tr>
<tr>
<td>Process spawn</td>
<td><code>uv_spawn</code></td>
</tr>
</table>
<h3>Request Data Pattern</h3>
<p>Store context in the <code>data</code> field of libuv requests:</p>
<pre><code>typedef struct {
WrenVM* vm;
WrenHandle* fiber;
// ... operation-specific data ...
} MyRequest;
MyRequest* req = malloc(sizeof(MyRequest));
req->vm = vm;
req->fiber = fiber;
uvReq.data = req;</code></pre>
<p>In callbacks, retrieve the context:</p>
<pre><code>void onComplete(uv_xxx_t* uvReq) {
MyRequest* req = (MyRequest*)uvReq->data;
// ...
}</code></pre>
<h2>Complete Async File Read Example</h2>
<h3>asyncfile.wren</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "scheduler" for Scheduler
class AsyncFile {
foreign static read_(path, fiber)
foreign static write_(path, content, fiber)
static read(path) {
return Scheduler.await_ { read_(path, Fiber.current) }
}
static write(path, content) {
return Scheduler.await_ { write_(path, content, Fiber.current) }
}
}</code></pre>
<h3>asyncfile.c (simplified)</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;uv.h&gt;
#include "asyncfile.h"
#include "wren.h"
#include "vm.h"
#include "scheduler.h"
typedef struct {
WrenVM* vm;
WrenHandle* fiber;
uv_fs_t req;
uv_file fd;
char* buffer;
size_t size;
} FileRequest;
static void cleanupRequest(FileRequest* request) {
if (request->buffer) free(request->buffer);
wrenReleaseHandle(request->vm, request->fiber);
free(request);
}
static void onReadComplete(uv_fs_t* req) {
FileRequest* request = (FileRequest*)req->data;
uv_fs_req_cleanup(req);
if (req->result < 0) {
schedulerResume(request->fiber, false);
wrenEnsureSlots(request->vm, 1);
wrenSetSlotString(request->vm, 0, "Read failed.");
} else {
request->buffer[req->result] = '\0';
schedulerResume(request->fiber, true);
wrenEnsureSlots(request->vm, 1);
wrenSetSlotString(request->vm, 0, request->buffer);
}
uv_fs_close(getLoop(), req, request->fd, NULL);
cleanupRequest(request);
}
static void onFileStatComplete(uv_fs_t* req) {
FileRequest* request = (FileRequest*)req->data;
uv_fs_req_cleanup(req);
if (req->result < 0) {
schedulerResume(request->fiber, false);
wrenEnsureSlots(request->vm, 1);
wrenSetSlotString(request->vm, 0, "Stat failed.");
uv_fs_close(getLoop(), req, request->fd, NULL);
cleanupRequest(request);
return;
}
request->size = req->statbuf.st_size;
request->buffer = (char*)malloc(request->size + 1);
uv_buf_t buf = uv_buf_init(request->buffer, request->size);
uv_fs_read(getLoop(), &request->req, request->fd, &buf, 1, 0, onReadComplete);
}
static void onFileOpenComplete(uv_fs_t* req) {
FileRequest* request = (FileRequest*)req->data;
uv_fs_req_cleanup(req);
if (req->result < 0) {
schedulerResume(request->fiber, false);
wrenEnsureSlots(request->vm, 1);
wrenSetSlotString(request->vm, 0, "Open failed.");
cleanupRequest(request);
return;
}
request->fd = (uv_file)req->result;
uv_fs_fstat(getLoop(), &request->req, request->fd, onFileStatComplete);
}
void asyncFileRead(WrenVM* vm) {
const char* path = wrenGetSlotString(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
FileRequest* request = (FileRequest*)malloc(sizeof(FileRequest));
request->vm = vm;
request->fiber = fiber;
request->buffer = NULL;
request->req.data = request;
uv_fs_open(getLoop(), &request->req, path, UV_FS_O_RDONLY, 0, onFileOpenComplete);
}</code></pre>
<h2>Error Handling in Async Operations</h2>
<h3>libuv Errors</h3>
<p>Check <code>req->result</code> for negative values:</p>
<pre><code>if (req->result < 0) {
const char* msg = uv_strerror((int)req->result);
schedulerResume(request->fiber, false);
wrenSetSlotString(request->vm, 0, msg);
return;
}</code></pre>
<h3>Propagating to Wren</h3>
<p>Use <code>schedulerResume(fiber, false)</code> for errors. The value in slot 0 becomes the error that <code>Scheduler.await_</code> throws as a runtime error.</p>
<h2>Timer Example</h2>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
typedef struct {
WrenVM* vm;
WrenHandle* fiber;
uv_timer_t timer;
} TimerRequest;
static void onTimerComplete(uv_timer_t* timer) {
TimerRequest* request = (TimerRequest*)timer->data;
schedulerResume(request->fiber, true);
wrenReleaseHandle(request->vm, request->fiber);
wrenEnsureSlots(request->vm, 1);
wrenSetSlotNull(request->vm, 0);
uv_close((uv_handle_t*)timer, NULL);
free(request);
}
void timerSleep(WrenVM* vm) {
double ms = wrenGetSlotDouble(vm, 1);
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
TimerRequest* request = (TimerRequest*)malloc(sizeof(TimerRequest));
request->vm = vm;
request->fiber = fiber;
request->timer.data = request;
uv_timer_init(getLoop(), &request->timer);
uv_timer_start(&request->timer, onTimerComplete, (uint64_t)ms, 0);
}</code></pre>
<h2>Multiple Concurrent Operations</h2>
<p>Each async operation gets its own request structure and fiber. Multiple operations can run concurrently:</p>
<pre><code>import "asyncfile" for AsyncFile
var fiber1 = Fiber.new {
var a = AsyncFile.read("a.txt")
System.print("A: %(a.count) bytes")
}
var fiber2 = Fiber.new {
var b = AsyncFile.read("b.txt")
System.print("B: %(b.count) bytes")
}
fiber1.call()
fiber2.call()</code></pre>
<h2>Best Practices</h2>
<ul>
<li><strong>Always release fiber handles</strong>: Call <code>wrenReleaseHandle</code> after resuming</li>
<li><strong>Cleanup on all paths</strong>: Free resources in both success and error cases</li>
<li><strong>Use uv_fs_req_cleanup</strong>: Required after filesystem operations</li>
<li><strong>Close handles properly</strong>: Use <code>uv_close</code> for handles like timers and sockets</li>
<li><strong>Check for VM validity</strong>: The VM pointer should remain valid during callbacks</li>
</ul>
<h2>Debugging Tips</h2>
<ul>
<li>Add logging in callbacks to trace execution flow</li>
<li>Verify libuv error codes with <code>uv_strerror</code></li>
<li>Use <code>make debug</code> build for symbols</li>
<li>Check for memory leaks with valgrind</li>
</ul>
<h2>Next Steps</h2>
<p>See <a href="testing.html">Writing Tests</a> for testing async operations, including the <code>// skip:</code> annotation for tests that require network access.</p>
</article>
<footer class="page-footer">
<a href="foreign-classes.html" class="prev">Foreign Classes</a>
<a href="testing.html" class="next">Writing Tests</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,540 @@
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>C-Backed Modules - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="index.html">Overview</a></li>
<li><a href="module-overview.html">Module Architecture</a></li>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="c-backed-module.html" class="active">C-Backed Modules</a></li>
<li><a href="foreign-classes.html">Foreign Classes</a></li>
<li><a href="async-patterns.html">Async Patterns</a></li>
<li><a href="testing.html">Writing Tests</a></li>
<li><a href="documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<a href="index.html">Contributing</a>
<span class="separator">/</span>
<span>C-Backed Modules</span>
</nav>
<article>
<h1>C-Backed Modules</h1>
<p>C-backed modules implement foreign methods in C, providing access to system libraries, native performance, or functionality not available in pure Wren. Examples: json, io, net, crypto, tls, sqlite, base64.</p>
<h2>Step 1: Create the Wren Interface</h2>
<p>Create <code>src/module/&lt;name&gt;.wren</code> with foreign method declarations:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
class Counter {
foreign static create()
foreign static increment(handle)
foreign static getValue(handle)
foreign static destroy(handle)
static use(fn) {
var handle = Counter.create()
var result = fn.call(handle)
Counter.destroy(handle)
return result
}
}</code></pre>
<p>The <code>foreign</code> keyword indicates the method is implemented in C. Non-foreign methods can provide higher-level Wren wrappers around the foreign primitives.</p>
<h2>Step 2: Generate the .wren.inc</h2>
<pre><code>python3 util/wren_to_c_string.py src/module/counter.wren.inc src/module/counter.wren</code></pre>
<h2>Step 3: Create the C Implementation</h2>
<p>Create <code>src/module/counter.c</code>:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
#include &lt;stdlib.h&gt;
#include "counter.h"
#include "wren.h"
typedef struct {
int value;
} Counter;
void counterCreate(WrenVM* vm) {
Counter* counter = (Counter*)malloc(sizeof(Counter));
if (!counter) {
wrenSetSlotNull(vm, 0);
return;
}
counter->value = 0;
wrenSetSlotDouble(vm, 0, (double)(uintptr_t)counter);
}
void counterIncrement(WrenVM* vm) {
double handle = wrenGetSlotDouble(vm, 1);
Counter* counter = (Counter*)(uintptr_t)handle;
counter->value++;
}
void counterGetValue(WrenVM* vm) {
double handle = wrenGetSlotDouble(vm, 1);
Counter* counter = (Counter*)(uintptr_t)handle;
wrenSetSlotDouble(vm, 0, counter->value);
}
void counterDestroy(WrenVM* vm) {
double handle = wrenGetSlotDouble(vm, 1);
Counter* counter = (Counter*)(uintptr_t)handle;
free(counter);
}</code></pre>
<h2>Step 4: Create the C Header</h2>
<p>Create <code>src/module/counter.h</code>:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
#ifndef counter_h
#define counter_h
#include "wren.h"
void counterCreate(WrenVM* vm);
void counterIncrement(WrenVM* vm);
void counterGetValue(WrenVM* vm);
void counterDestroy(WrenVM* vm);
#endif</code></pre>
<h2>Step 5: Register in modules.c</h2>
<p>Edit <code>src/cli/modules.c</code>:</p>
<h3>Add the Include</h3>
<pre><code>#include "counter.wren.inc"</code></pre>
<h3>Add Extern Declarations</h3>
<pre><code>extern void counterCreate(WrenVM* vm);
extern void counterIncrement(WrenVM* vm);
extern void counterGetValue(WrenVM* vm);
extern void counterDestroy(WrenVM* vm);</code></pre>
<h3>Add the Module Entry</h3>
<pre><code>MODULE(counter)
CLASS(Counter)
STATIC_METHOD("create()", counterCreate)
STATIC_METHOD("increment(_)", counterIncrement)
STATIC_METHOD("getValue(_)", counterGetValue)
STATIC_METHOD("destroy(_)", counterDestroy)
END_CLASS
END_MODULE</code></pre>
<h2>Step 6: Update the Makefile</h2>
<p>Edit <code>projects/make/wren_cli.make</code>:</p>
<h3>Add to OBJECTS</h3>
<pre><code>OBJECTS += $(OBJDIR)/counter.o</code></pre>
<h3>Add Compilation Rule</h3>
<pre><code>$(OBJDIR)/counter.o: ../../src/module/counter.c
@echo $(notdir $&lt;)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$&lt;"</code></pre>
<h2>Step 7: Build and Test</h2>
<pre><code>make clean && make build
python3 util/test.py counter</code></pre>
<h2>Wren/C Data Exchange</h2>
<h3>Getting Values from Wren</h3>
<pre><code>const char* str = wrenGetSlotString(vm, 1);
double num = wrenGetSlotDouble(vm, 1);
bool b = wrenGetSlotBool(vm, 1);
void* foreign = wrenGetSlotForeign(vm, 0);
WrenHandle* handle = wrenGetSlotHandle(vm, 1);
int count = wrenGetSlotCount(vm);
WrenType type = wrenGetSlotType(vm, 1);</code></pre>
<h3>Setting Return Values</h3>
<pre><code>wrenSetSlotString(vm, 0, "result");
wrenSetSlotDouble(vm, 0, 42.0);
wrenSetSlotBool(vm, 0, true);
wrenSetSlotNull(vm, 0);
wrenSetSlotNewList(vm, 0);</code></pre>
<h3>Working with Lists</h3>
<pre><code>wrenSetSlotNewList(vm, 0);
wrenSetSlotString(vm, 1, "item");
wrenInsertInList(vm, 0, -1, 1);
int count = wrenGetListCount(vm, 0);
wrenGetListElement(vm, 0, index, 1);</code></pre>
<h3>Working with Maps</h3>
<pre><code>wrenSetSlotNewMap(vm, 0);
wrenSetSlotString(vm, 1, "key");
wrenSetSlotDouble(vm, 2, 123);
wrenSetMapValue(vm, 0, 1, 2);</code></pre>
<h3>Ensuring Slots</h3>
<pre><code>wrenEnsureSlots(vm, 5);</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Slot 0 is used for the return value and (for instance methods) the receiver. Arguments start at slot 1.</p>
</div>
<h2>Error Handling</h2>
<p>Use <code>wrenAbortFiber</code> to report errors:</p>
<pre><code>void myMethod(WrenVM* vm) {
const char* path = wrenGetSlotString(vm, 1);
FILE* file = fopen(path, "r");
if (!file) {
wrenSetSlotString(vm, 0, "Failed to open file.");
wrenAbortFiber(vm, 0);
return;
}
// ... process file ...
}</code></pre>
<p>The error message is set in slot 0, then <code>wrenAbortFiber</code> is called with the slot containing the message.</p>
<h2>Type Checking</h2>
<p>Verify argument types before using them:</p>
<pre><code>void myMethod(WrenVM* vm) {
if (wrenGetSlotType(vm, 1) != WREN_TYPE_STRING) {
wrenSetSlotString(vm, 0, "Argument must be a string.");
wrenAbortFiber(vm, 0);
return;
}
const char* str = wrenGetSlotString(vm, 1);
// ...
}</code></pre>
<p>WrenType values: <code>WREN_TYPE_BOOL</code>, <code>WREN_TYPE_NUM</code>, <code>WREN_TYPE_FOREIGN</code>, <code>WREN_TYPE_LIST</code>, <code>WREN_TYPE_MAP</code>, <code>WREN_TYPE_NULL</code>, <code>WREN_TYPE_STRING</code>, <code>WREN_TYPE_UNKNOWN</code>.</p>
<h2>Method Signature Rules</h2>
<table>
<tr>
<th>Wren Declaration</th>
<th>Signature String</th>
</tr>
<tr>
<td><code>foreign static foo()</code></td>
<td><code>"foo()"</code></td>
</tr>
<tr>
<td><code>foreign static foo(a)</code></td>
<td><code>"foo(_)"</code></td>
</tr>
<tr>
<td><code>foreign static foo(a, b)</code></td>
<td><code>"foo(_,_)"</code></td>
</tr>
<tr>
<td><code>foreign foo()</code></td>
<td><code>"foo()"</code> with <code>METHOD</code></td>
</tr>
<tr>
<td><code>foreign name</code> (getter)</td>
<td><code>"name"</code></td>
</tr>
<tr>
<td><code>foreign name=(v)</code> (setter)</td>
<td><code>"name=(_)"</code></td>
</tr>
</table>
<h2>Complete Example</h2>
<p>A more realistic example parsing hexadecimal strings:</p>
<h3>hex.wren</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
class Hex {
foreign static encode(bytes)
foreign static decode(str)
static isValid(str) {
for (c in str) {
var code = c.bytes[0]
var valid = (code >= 48 && code <= 57) ||
(code >= 65 && code <= 70) ||
(code >= 97 && code <= 102)
if (!valid) return false
}
return str.count \% 2 == 0
}
}</code></pre>
<h3>hex.c</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include "hex.h"
#include "wren.h"
static const char HEX_CHARS[] = "0123456789abcdef";
void hexEncode(WrenVM* vm) {
const char* input = wrenGetSlotString(vm, 1);
size_t len = strlen(input);
char* output = (char*)malloc(len * 2 + 1);
if (!output) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)input[i];
output[i * 2] = HEX_CHARS[(c >> 4) & 0xF];
output[i * 2 + 1] = HEX_CHARS[c & 0xF];
}
output[len * 2] = '\0';
wrenSetSlotString(vm, 0, output);
free(output);
}
static int hexCharToInt(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return -1;
}
void hexDecode(WrenVM* vm) {
const char* input = wrenGetSlotString(vm, 1);
size_t len = strlen(input);
if (len \% 2 != 0) {
wrenSetSlotString(vm, 0, "Hex string must have even length.");
wrenAbortFiber(vm, 0);
return;
}
char* output = (char*)malloc(len / 2 + 1);
if (!output) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
for (size_t i = 0; i < len; i += 2) {
int high = hexCharToInt(input[i]);
int low = hexCharToInt(input[i + 1]);
if (high < 0 || low < 0) {
free(output);
wrenSetSlotString(vm, 0, "Invalid hex character.");
wrenAbortFiber(vm, 0);
return;
}
output[i / 2] = (char)((high << 4) | low);
}
output[len / 2] = '\0';
wrenSetSlotString(vm, 0, output);
free(output);
}</code></pre>
<h3>hex.h</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
#ifndef hex_h
#define hex_h
#include "wren.h"
void hexEncode(WrenVM* vm);
void hexDecode(WrenVM* vm);
#endif</code></pre>
<h3>modules.c entries</h3>
<pre><code>#include "hex.wren.inc"
extern void hexEncode(WrenVM* vm);
extern void hexDecode(WrenVM* vm);
MODULE(hex)
CLASS(Hex)
STATIC_METHOD("encode(_)", hexEncode)
STATIC_METHOD("decode(_)", hexDecode)
END_CLASS
END_MODULE</code></pre>
<h2>Common Pitfalls</h2>
<ul>
<li><strong>Stale .wren.inc</strong>: Always regenerate after editing the <code>.wren</code> file</li>
<li><strong>Signature mismatch</strong>: The string in <code>STATIC_METHOD("name(_)", fn)</code> must exactly match the Wren declaration's arity</li>
<li><strong>Slot management</strong>: Always call <code>wrenEnsureSlots(vm, n)</code> before using high-numbered slots</li>
<li><strong>Memory leaks</strong>: Free allocated memory before returning</li>
<li><strong>String lifetime</strong>: Strings from <code>wrenGetSlotString</code> are valid only until the next Wren API call</li>
<li><strong>make clean</strong>: Required after adding new object files</li>
</ul>
<h2>Checklist</h2>
<ul>
<li>Created <code>src/module/&lt;name&gt;.wren</code> with foreign declarations</li>
<li>Generated <code>.wren.inc</code></li>
<li>Created <code>src/module/&lt;name&gt;.c</code></li>
<li>Created <code>src/module/&lt;name&gt;.h</code></li>
<li>Added <code>#include</code> for <code>.wren.inc</code> in modules.c</li>
<li>Added extern declarations in modules.c</li>
<li>Added <code>MODULE</code>/<code>CLASS</code>/<code>METHOD</code> block in modules.c</li>
<li>Added <code>OBJECTS</code> entry in Makefile</li>
<li>Added compilation rule in Makefile</li>
<li>Built with <code>make clean && make build</code></li>
<li>Created tests and example</li>
<li>Created documentation page</li>
</ul>
<h2>Next Steps</h2>
<ul>
<li><a href="foreign-classes.html">Foreign Classes</a> - for native resource management</li>
<li><a href="async-patterns.html">Async Patterns</a> - for non-blocking I/O operations</li>
</ul>
</article>
<footer class="page-footer">
<a href="pure-wren-module.html" class="prev">Pure-Wren Modules</a>
<a href="foreign-classes.html" class="next">Foreign Classes</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,498 @@
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documentation - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="index.html">Overview</a></li>
<li><a href="module-overview.html">Module Architecture</a></li>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="c-backed-module.html">C-Backed Modules</a></li>
<li><a href="foreign-classes.html">Foreign Classes</a></li>
<li><a href="async-patterns.html">Async Patterns</a></li>
<li><a href="testing.html">Writing Tests</a></li>
<li><a href="documentation.html" class="active">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<a href="index.html">Contributing</a>
<span class="separator">/</span>
<span>Documentation</span>
</nav>
<article>
<h1>Documentation</h1>
<p>The Wren-CLI manual is hand-written HTML in <code>manual/</code>. There is no generation step. Every page is a standalone HTML file sharing common CSS and JavaScript.</p>
<h2>HTML Template</h2>
<p>Every page follows this structure:</p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;!-- retoor &lt;retoor@molodetz.nl&gt; --&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;[Page Title] - Wren-CLI Manual&lt;/title&gt;
&lt;link rel="stylesheet" href="../css/style.css"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;button class="mobile-menu-toggle"&gt;Menu&lt;/button&gt;
&lt;div class="container"&gt;
&lt;aside class="sidebar"&gt;
&lt;div class="sidebar-header"&gt;
&lt;h1&gt;&lt;a href="../index.html"&gt;Wren-CLI&lt;/a&gt;&lt;/h1&gt;
&lt;div class="version"&gt;v0.4.0&lt;/div&gt;
&lt;/div&gt;
&lt;nav class="sidebar-nav"&gt;
&lt;!-- Auto-generated by sync_sidebar.py --&gt;
&lt;/nav&gt;
&lt;/aside&gt;
&lt;main class="content"&gt;
&lt;nav class="breadcrumb"&gt;
&lt;a href="../index.html"&gt;Home&lt;/a&gt;
&lt;span class="separator"&gt;/&lt;/span&gt;
&lt;a href="index.html"&gt;API Reference&lt;/a&gt;
&lt;span class="separator"&gt;/&lt;/span&gt;
&lt;span&gt;[Current Page]&lt;/span&gt;
&lt;/nav&gt;
&lt;article&gt;
&lt;h1&gt;[Page Title]&lt;/h1&gt;
&lt;!-- Content --&gt;
&lt;/article&gt;
&lt;footer class="page-footer"&gt;
&lt;a href="[prev].html" class="prev"&gt;[Previous]&lt;/a&gt;
&lt;a href="[next].html" class="next"&gt;[Next]&lt;/a&gt;
&lt;/footer&gt;
&lt;/main&gt;
&lt;/div&gt;
&lt;script src="../js/main.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h2>Sidebar Navigation</h2>
<p>The sidebar is automatically synchronized across all pages by <code>util/sync_sidebar.py</code>. Never edit the sidebar manually.</p>
<h3>Adding a New Module to the Sidebar</h3>
<ol>
<li>Create the module's HTML page in <code>manual/api/</code></li>
<li>Run <code>make sync-manual</code></li>
</ol>
<p>The sync script automatically discovers new API pages and adds them to the sidebar in alphabetical order.</p>
<h3>Adding a New Section</h3>
<p>To add a new top-level section (like "Contributing"), edit <code>util/sync_sidebar.py</code> and add an entry to the <code>SECTIONS</code> list:</p>
<pre><code>{
"title": "New Section",
"directory": "new-section",
"pages": [
("index.html", "Overview"),
("page1.html", "Page One"),
("page2.html", "Page Two"),
]
},</code></pre>
<h2>API Page Structure</h2>
<p>API documentation pages follow a consistent structure:</p>
<pre><code>&lt;h1&gt;modulename&lt;/h1&gt;
&lt;p&gt;Description paragraph.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import "modulename" for ClassName&lt;/code&gt;&lt;/pre&gt;
&lt;div class="class-header"&gt;&lt;h2&gt;Class: ClassName&lt;/h2&gt;&lt;/div&gt;
&lt;h3&gt;Static Methods&lt;/h3&gt;
&lt;div class="method-signature"&gt;
&lt;span class="method-name"&gt;ClassName.methodName&lt;/span&gt;(&lt;span class="param"&gt;arg&lt;/span&gt;)
&amp;rarr; &lt;span class="type"&gt;ReturnType&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Method description.&lt;/p&gt;
&lt;h3&gt;Examples&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import "modulename" for ClassName
var result = ClassName.methodName("hello")
System.print(result)&lt;/code&gt;&lt;/pre&gt;</code></pre>
<h2>CSS Classes</h2>
<table>
<tr>
<th>Class</th>
<th>Usage</th>
</tr>
<tr>
<td><code>.method-signature</code></td>
<td>Method signature box</td>
</tr>
<tr>
<td><code>.method-name</code></td>
<td>Method name within signature</td>
</tr>
<tr>
<td><code>.param</code></td>
<td>Parameter name</td>
</tr>
<tr>
<td><code>.type</code></td>
<td>Return type</td>
</tr>
<tr>
<td><code>.class-header</code></td>
<td>Class section header</td>
</tr>
<tr>
<td><code>.param-list</code></td>
<td>Parameter description list</td>
</tr>
<tr>
<td><code>.toc</code></td>
<td>Table of contents box</td>
</tr>
<tr>
<td><code>.admonition</code></td>
<td>Note/warning/tip box</td>
</tr>
<tr>
<td><code>.example-output</code></td>
<td>Expected output display</td>
</tr>
</table>
<h2>Method Signature Format</h2>
<p>Static method:</p>
<pre><code>&lt;div class="method-signature"&gt;
&lt;span class="method-name"&gt;ClassName.methodName&lt;/span&gt;(&lt;span class="param"&gt;arg1&lt;/span&gt;, &lt;span class="param"&gt;arg2&lt;/span&gt;)
&amp;rarr; &lt;span class="type"&gt;String&lt;/span&gt;
&lt;/div&gt;</code></pre>
<p>Instance method:</p>
<pre><code>&lt;div class="method-signature"&gt;
&lt;span class="method-name"&gt;instance.methodName&lt;/span&gt;(&lt;span class="param"&gt;arg&lt;/span&gt;)
&amp;rarr; &lt;span class="type"&gt;Bool&lt;/span&gt;
&lt;/div&gt;</code></pre>
<p>Property (getter):</p>
<pre><code>&lt;div class="method-signature"&gt;
&lt;span class="method-name"&gt;instance.propertyName&lt;/span&gt;
&amp;rarr; &lt;span class="type"&gt;Num&lt;/span&gt;
&lt;/div&gt;</code></pre>
<h2>Admonition Blocks</h2>
<p>Use admonitions for notes, warnings, and tips:</p>
<h3>Note</h3>
<pre><code>&lt;div class="admonition note"&gt;
&lt;div class="admonition-title"&gt;Note&lt;/div&gt;
&lt;p&gt;Additional information.&lt;/p&gt;
&lt;/div&gt;</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Additional information.</p>
</div>
<h3>Warning</h3>
<pre><code>&lt;div class="admonition warning"&gt;
&lt;div class="admonition-title"&gt;Warning&lt;/div&gt;
&lt;p&gt;Important caution.&lt;/p&gt;
&lt;/div&gt;</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Important caution.</p>
</div>
<h3>Tip</h3>
<pre><code>&lt;div class="admonition tip"&gt;
&lt;div class="admonition-title"&gt;Tip&lt;/div&gt;
&lt;p&gt;Helpful suggestion.&lt;/p&gt;
&lt;/div&gt;</code></pre>
<h2>Parameter Lists</h2>
<p>For methods with complex parameters:</p>
<pre><code>&lt;div class="param-list"&gt;
&lt;div class="param-item"&gt;
&lt;span class="param-name"&gt;path&lt;/span&gt;
&lt;span class="param-type"&gt;String&lt;/span&gt;
&lt;p&gt;The file path to read.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="param-item"&gt;
&lt;span class="param-name"&gt;encoding&lt;/span&gt;
&lt;span class="param-type"&gt;String&lt;/span&gt;
&lt;p&gt;The character encoding (default: "utf-8").&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</code></pre>
<h2>Tables</h2>
<p>Use tables for options, type mappings, or comparisons:</p>
<pre><code>&lt;table&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"r"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"w"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;</code></pre>
<h2>Footer Navigation</h2>
<p>Every page has previous/next navigation:</p>
<pre><code>&lt;footer class="page-footer"&gt;
&lt;a href="previous.html" class="prev"&gt;Previous Page&lt;/a&gt;
&lt;a href="next.html" class="next"&gt;Next Page&lt;/a&gt;
&lt;/footer&gt;</code></pre>
<p>Update the footer on adjacent pages when adding new pages.</p>
<h2>Breadcrumb Navigation</h2>
<p>Shows the page hierarchy:</p>
<pre><code>&lt;nav class="breadcrumb"&gt;
&lt;a href="../index.html"&gt;Home&lt;/a&gt;
&lt;span class="separator"&gt;/&lt;/span&gt;
&lt;a href="index.html"&gt;API Reference&lt;/a&gt;
&lt;span class="separator"&gt;/&lt;/span&gt;
&lt;span&gt;io&lt;/span&gt;
&lt;/nav&gt;</code></pre>
<h2>Syncing the Sidebar</h2>
<pre><code>make sync-manual</code></pre>
<p>This runs <code>util/sync_sidebar.py</code> which:</p>
<ol>
<li>Discovers all API module pages</li>
<li>Builds the complete sidebar HTML</li>
<li>Updates the <code>&lt;nav class="sidebar-nav"&gt;</code> in every HTML file</li>
<li>Sets the <code>active</code> class on the current page's link</li>
</ol>
<p>The script is idempotent - running it twice produces the same result.</p>
<h2>Adding a New API Page</h2>
<ol>
<li>Create <code>manual/api/&lt;name&gt;.html</code></li>
<li>Use the template structure above</li>
<li>Run <code>make sync-manual</code></li>
<li>Update footer prev/next on adjacent pages</li>
<li>Add entry to <code>manual/api/index.html</code></li>
</ol>
<h2>Example: Minimal API Page</h2>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;!-- retoor &lt;retoor@molodetz.nl&gt; --&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;mymodule - Wren-CLI Manual&lt;/title&gt;
&lt;link rel="stylesheet" href="../css/style.css"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;button class="mobile-menu-toggle"&gt;Menu&lt;/button&gt;
&lt;div class="container"&gt;
&lt;aside class="sidebar"&gt;
&lt;div class="sidebar-header"&gt;
&lt;h1&gt;&lt;a href="../index.html"&gt;Wren-CLI&lt;/a&gt;&lt;/h1&gt;
&lt;div class="version"&gt;v0.4.0&lt;/div&gt;
&lt;/div&gt;
&lt;nav class="sidebar-nav"&gt;
&lt;/nav&gt;
&lt;/aside&gt;
&lt;main class="content"&gt;
&lt;nav class="breadcrumb"&gt;
&lt;a href="../index.html"&gt;Home&lt;/a&gt;
&lt;span class="separator"&gt;/&lt;/span&gt;
&lt;a href="index.html"&gt;API Reference&lt;/a&gt;
&lt;span class="separator"&gt;/&lt;/span&gt;
&lt;span&gt;mymodule&lt;/span&gt;
&lt;/nav&gt;
&lt;article&gt;
&lt;h1&gt;mymodule&lt;/h1&gt;
&lt;p&gt;The &lt;code&gt;mymodule&lt;/code&gt; module provides...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import "mymodule" for MyClass&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;MyClass&lt;/h2&gt;
&lt;h3&gt;Static Methods&lt;/h3&gt;
&lt;div class="method-signature"&gt;
&lt;span class="method-name"&gt;MyClass.process&lt;/span&gt;(&lt;span class="param"&gt;input&lt;/span&gt;)
&amp;rarr; &lt;span class="type"&gt;String&lt;/span&gt;
&lt;/div&gt;
&lt;p&gt;Processes the input and returns a result.&lt;/p&gt;
&lt;h3&gt;Examples&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import "mymodule" for MyClass
var result = MyClass.process("hello")
System.print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;/article&gt;
&lt;footer class="page-footer"&gt;
&lt;a href="math.html" class="prev"&gt;math&lt;/a&gt;
&lt;a href="net.html" class="next"&gt;net&lt;/a&gt;
&lt;/footer&gt;
&lt;/main&gt;
&lt;/div&gt;
&lt;script src="../js/main.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h2>Checklist for New Documentation</h2>
<ul>
<li>Author comment on line 2</li>
<li>Correct title in <code>&lt;title&gt;</code> and <code>&lt;h1&gt;</code></li>
<li>Proper breadcrumb navigation</li>
<li>Import statement example</li>
<li>All methods documented with signatures</li>
<li>Working code examples</li>
<li>Footer with prev/next links</li>
<li>Ran <code>make sync-manual</code></li>
<li>Updated adjacent page footers</li>
<li>Added to index page if applicable</li>
</ul>
</article>
<footer class="page-footer">
<a href="testing.html" class="prev">Writing Tests</a>
<a href="../api/index.html" class="next">API Reference</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,511 @@
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Foreign Classes - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="index.html">Overview</a></li>
<li><a href="module-overview.html">Module Architecture</a></li>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="c-backed-module.html">C-Backed Modules</a></li>
<li><a href="foreign-classes.html" class="active">Foreign Classes</a></li>
<li><a href="async-patterns.html">Async Patterns</a></li>
<li><a href="testing.html">Writing Tests</a></li>
<li><a href="documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<a href="index.html">Contributing</a>
<span class="separator">/</span>
<span>Foreign Classes</span>
</nav>
<article>
<h1>Foreign Classes</h1>
<p>Foreign classes allow Wren objects to hold native C data. This is used when a Wren object needs to manage resources like file handles, network sockets, database connections, or any native data structure.</p>
<h2>When to Use Foreign Classes</h2>
<ul>
<li>Wrapping system resources (files, sockets, processes)</li>
<li>Managing native library objects</li>
<li>Storing data structures more complex than Wren's built-in types</li>
<li>Resources requiring explicit cleanup</li>
</ul>
<h2>Architecture</h2>
<p>A foreign class has three components:</p>
<ol>
<li><strong>Allocate function</strong>: Called when an instance is created via <code>construct new()</code></li>
<li><strong>Finalize function</strong>: Called when the garbage collector frees the instance</li>
<li><strong>Instance methods</strong>: Operate on the foreign data</li>
</ol>
<h2>Basic Pattern</h2>
<h3>Wren Interface</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
class Buffer {
foreign construct new(size)
foreign write(data)
foreign read()
foreign size
foreign clear()
}</code></pre>
<p>The constructor uses <code>foreign construct</code> to trigger allocation.</p>
<h3>C Implementation</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include "buffer.h"
#include "wren.h"
typedef struct {
char* data;
size_t size;
size_t capacity;
} Buffer;
void bufferAllocate(WrenVM* vm) {
Buffer* buffer = (Buffer*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(Buffer));
double capacity = wrenGetSlotDouble(vm, 1);
buffer->capacity = (size_t)capacity;
buffer->size = 0;
buffer->data = (char*)malloc(buffer->capacity);
if (!buffer->data) {
buffer->capacity = 0;
}
}
void bufferFinalize(void* data) {
Buffer* buffer = (Buffer*)data;
if (buffer->data) {
free(buffer->data);
buffer->data = NULL;
}
}
void bufferWrite(WrenVM* vm) {
Buffer* buffer = (Buffer*)wrenGetSlotForeign(vm, 0);
const char* str = wrenGetSlotString(vm, 1);
size_t len = strlen(str);
if (buffer->size + len > buffer->capacity) {
wrenSetSlotString(vm, 0, "Buffer overflow.");
wrenAbortFiber(vm, 0);
return;
}
memcpy(buffer->data + buffer->size, str, len);
buffer->size += len;
}
void bufferRead(WrenVM* vm) {
Buffer* buffer = (Buffer*)wrenGetSlotForeign(vm, 0);
char* copy = (char*)malloc(buffer->size + 1);
if (!copy) {
wrenSetSlotNull(vm, 0);
return;
}
memcpy(copy, buffer->data, buffer->size);
copy[buffer->size] = '\0';
wrenSetSlotString(vm, 0, copy);
free(copy);
}
void bufferSize(WrenVM* vm) {
Buffer* buffer = (Buffer*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)buffer->size);
}
void bufferClear(WrenVM* vm) {
Buffer* buffer = (Buffer*)wrenGetSlotForeign(vm, 0);
buffer->size = 0;
}</code></pre>
<h3>Registration</h3>
<pre><code>MODULE(buffer)
CLASS(Buffer)
ALLOCATE(bufferAllocate)
FINALIZE(bufferFinalize)
METHOD("write(_)", bufferWrite)
METHOD("read()", bufferRead)
METHOD("size", bufferSize)
METHOD("clear()", bufferClear)
END_CLASS
END_MODULE</code></pre>
<h2>Memory Management</h2>
<h3>wrenSetSlotNewForeign</h3>
<pre><code>void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size);</code></pre>
<ul>
<li><code>slot</code>: Where to place the new instance (usually 0)</li>
<li><code>classSlot</code>: Slot containing the class (usually 0 for the current class)</li>
<li><code>size</code>: Size of the native data structure</li>
</ul>
<p>Returns a pointer to the allocated memory. This memory is managed by Wren's garbage collector.</p>
<h3>Finalize Function Signature</h3>
<pre><code>void myFinalize(void* data);</code></pre>
<p>The finalize function receives only a pointer to the foreign data, not the VM. This means:</p>
<ul>
<li>No Wren API calls in finalize</li>
<li>Cannot throw errors</li>
<li>Must be fast (GC is running)</li>
<li>Free any resources allocated in allocate or methods</li>
</ul>
<h3>Accessing Foreign Data</h3>
<p>In instance methods, use <code>wrenGetSlotForeign</code> on slot 0:</p>
<pre><code>void bufferMethod(WrenVM* vm) {
Buffer* buffer = (Buffer*)wrenGetSlotForeign(vm, 0);
// buffer points to the struct created in bufferAllocate
}</code></pre>
<h2>File Handle Example</h2>
<p>A practical example wrapping a file handle:</p>
<h3>filehandle.wren</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
class FileHandle {
foreign construct open(path, mode)
foreign read()
foreign write(data)
foreign close()
foreign isOpen
}</code></pre>
<h3>filehandle.c</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include "filehandle.h"
#include "wren.h"
typedef struct {
FILE* file;
char* path;
} FileHandle;
void fileHandleAllocate(WrenVM* vm) {
FileHandle* handle = (FileHandle*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(FileHandle));
const char* path = wrenGetSlotString(vm, 1);
const char* mode = wrenGetSlotString(vm, 2);
handle->path = strdup(path);
handle->file = fopen(path, mode);
if (!handle->file) {
wrenSetSlotString(vm, 0, "Failed to open file.");
wrenAbortFiber(vm, 0);
}
}
void fileHandleFinalize(void* data) {
FileHandle* handle = (FileHandle*)data;
if (handle->file) {
fclose(handle->file);
handle->file = NULL;
}
if (handle->path) {
free(handle->path);
handle->path = NULL;
}
}
void fileHandleRead(WrenVM* vm) {
FileHandle* handle = (FileHandle*)wrenGetSlotForeign(vm, 0);
if (!handle->file) {
wrenSetSlotString(vm, 0, "File not open.");
wrenAbortFiber(vm, 0);
return;
}
fseek(handle->file, 0, SEEK_END);
long size = ftell(handle->file);
fseek(handle->file, 0, SEEK_SET);
char* content = (char*)malloc(size + 1);
if (!content) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
fread(content, 1, size, handle->file);
content[size] = '\0';
wrenSetSlotString(vm, 0, content);
free(content);
}
void fileHandleWrite(WrenVM* vm) {
FileHandle* handle = (FileHandle*)wrenGetSlotForeign(vm, 0);
const char* data = wrenGetSlotString(vm, 1);
if (!handle->file) {
wrenSetSlotString(vm, 0, "File not open.");
wrenAbortFiber(vm, 0);
return;
}
size_t written = fwrite(data, 1, strlen(data), handle->file);
wrenSetSlotDouble(vm, 0, (double)written);
}
void fileHandleClose(WrenVM* vm) {
FileHandle* handle = (FileHandle*)wrenGetSlotForeign(vm, 0);
if (handle->file) {
fclose(handle->file);
handle->file = NULL;
}
}
void fileHandleIsOpen(WrenVM* vm) {
FileHandle* handle = (FileHandle*)wrenGetSlotForeign(vm, 0);
wrenSetSlotBool(vm, 0, handle->file != NULL);
}</code></pre>
<h3>Usage</h3>
<pre><code>import "filehandle" for FileHandle
var file = FileHandle.open("test.txt", "w")
file.write("Hello, World!")
file.close()
file = FileHandle.open("test.txt", "r")
System.print(file.read())
file.close()</code></pre>
<h2>Multiple Foreign Classes</h2>
<p>A module can have multiple foreign classes:</p>
<pre><code>MODULE(database)
CLASS(Connection)
ALLOCATE(connectionAllocate)
FINALIZE(connectionFinalize)
METHOD("query(_)", connectionQuery)
METHOD("close()", connectionClose)
END_CLASS
CLASS(Statement)
ALLOCATE(statementAllocate)
FINALIZE(statementFinalize)
METHOD("bind(_,_)", statementBind)
METHOD("execute()", statementExecute)
END_CLASS
END_MODULE</code></pre>
<h2>Resource Safety Patterns</h2>
<h3>Early Close</h3>
<p>Always check if resource is still valid:</p>
<pre><code>void handleMethod(WrenVM* vm) {
Handle* h = (Handle*)wrenGetSlotForeign(vm, 0);
if (!h->resource) {
wrenSetSlotString(vm, 0, "Handle already closed.");
wrenAbortFiber(vm, 0);
return;
}
// ...
}</code></pre>
<h3>Double-Free Prevention</h3>
<p>Set pointers to NULL after freeing:</p>
<pre><code>void handleClose(WrenVM* vm) {
Handle* h = (Handle*)wrenGetSlotForeign(vm, 0);
if (h->resource) {
resource_free(h->resource);
h->resource = NULL;
}
}</code></pre>
<h3>Defensive Finalize</h3>
<p>Always handle partially constructed objects:</p>
<pre><code>void handleFinalize(void* data) {
Handle* h = (Handle*)data;
if (h->resource) {
resource_free(h->resource);
}
if (h->name) {
free(h->name);
}
}</code></pre>
<h2>Common Pitfalls</h2>
<ul>
<li><strong>Forgetting FINALIZE</strong>: Memory leaks for any allocated resources</li>
<li><strong>Using VM in finalize</strong>: Causes undefined behavior</li>
<li><strong>Wrong slot for foreign data</strong>: Instance methods get <code>this</code> in slot 0</li>
<li><strong>Static methods on foreign class</strong>: Use <code>STATIC_METHOD</code> macro, but note that <code>wrenGetSlotForeign</code> is not available (no instance)</li>
</ul>
<h2>Checklist</h2>
<ul>
<li><code>foreign construct</code> in Wren class</li>
<li>Allocate function uses <code>wrenSetSlotNewForeign</code></li>
<li>Finalize function frees all resources</li>
<li><code>ALLOCATE</code> and <code>FINALIZE</code> in registration</li>
<li>Instance methods use <code>METHOD</code> (not <code>STATIC_METHOD</code>)</li>
<li>Methods access foreign data via slot 0</li>
<li>All methods check resource validity</li>
</ul>
<h2>Next Steps</h2>
<p>For I/O-bound foreign classes, see <a href="async-patterns.html">Async Patterns</a> to integrate with the libuv event loop.</p>
</article>
<footer class="page-footer">
<a href="c-backed-module.html" class="prev">C-Backed Modules</a>
<a href="async-patterns.html" class="next">Async Patterns</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,254 @@
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contributing - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="index.html" class="active">Overview</a></li>
<li><a href="module-overview.html">Module Architecture</a></li>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="c-backed-module.html">C-Backed Modules</a></li>
<li><a href="foreign-classes.html">Foreign Classes</a></li>
<li><a href="async-patterns.html">Async Patterns</a></li>
<li><a href="testing.html">Writing Tests</a></li>
<li><a href="documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<span>Contributing</span>
</nav>
<article>
<h1>Contributing</h1>
<p>This guide explains how to contribute new modules to Wren-CLI, write tests, and add documentation. Whether you are adding a pure-Wren module or implementing C-backed foreign methods, this section provides step-by-step instructions.</p>
<div class="toc">
<h4>In This Section</h4>
<ul>
<li><a href="module-overview.html">Module Architecture</a> - Project structure and artifact matrix</li>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a> - Step-by-step for Wren-only modules</li>
<li><a href="c-backed-module.html">C-Backed Modules</a> - Implementing foreign methods in C</li>
<li><a href="foreign-classes.html">Foreign Classes</a> - Native resource management</li>
<li><a href="async-patterns.html">Async Patterns</a> - Scheduler/Fiber and libuv integration</li>
<li><a href="testing.html">Writing Tests</a> - Test structure and annotations</li>
<li><a href="documentation.html">Documentation</a> - Writing manual pages</li>
</ul>
</div>
<h2>Prerequisites</h2>
<p>Before contributing, ensure you have:</p>
<ul>
<li>A working build environment (see <a href="../getting-started/installation.html">Installation</a>)</li>
<li>Python 3 (for utility scripts)</li>
<li>Basic understanding of <a href="../language/index.html">Wren syntax</a></li>
<li>For C-backed modules: familiarity with C and the Wren embedding API</li>
</ul>
<h2>Module Types</h2>
<p>Wren-CLI supports two types of modules:</p>
<table>
<tr>
<th>Type</th>
<th>Description</th>
<th>Files Required</th>
</tr>
<tr>
<td>Pure-Wren</td>
<td>Modules written entirely in Wren. Examples: argparse, html, jinja, http, markdown, dataset, web, websocket, wdantic, uuid, tempfile</td>
<td><code>.wren</code>, <code>.wren.inc</code>, modules.c entry</td>
</tr>
<tr>
<td>C-Backed</td>
<td>Modules with foreign methods implemented in C. Examples: json, io, net, crypto, tls, sqlite, base64</td>
<td><code>.wren</code>, <code>.wren.inc</code>, <code>.c</code>, <code>.h</code>, modules.c entry, Makefile entry</td>
</tr>
</table>
<h2>Workflow Checklist</h2>
<h3>Pure-Wren Module</h3>
<ol>
<li>Create <code>src/module/&lt;name&gt;.wren</code></li>
<li>Generate <code>.wren.inc</code> with <code>python3 util/wren_to_c_string.py</code></li>
<li>Add <code>#include</code> and <code>MODULE</code> block in <code>src/cli/modules.c</code></li>
<li>Build with <code>make clean && make build</code></li>
<li>Create tests in <code>test/&lt;name&gt;/</code></li>
<li>Create example in <code>example/&lt;name&gt;_demo.wren</code></li>
<li>Create <code>manual/api/&lt;name&gt;.html</code></li>
<li>Run <code>make sync-manual</code></li>
<li>Verify with <code>python3 util/test.py &lt;name&gt;</code></li>
</ol>
<h3>C-Backed Module</h3>
<ol>
<li>All pure-Wren steps, plus:</li>
<li>Create <code>src/module/&lt;name&gt;.c</code> with foreign method implementations</li>
<li>Create <code>src/module/&lt;name&gt;.h</code> with function declarations</li>
<li>Add extern declarations in <code>modules.c</code></li>
<li>Add <code>CLASS</code>/<code>METHOD</code> registrations with correct signatures</li>
<li>Add <code>OBJECTS</code> and compilation rule to <code>projects/make/wren_cli.make</code></li>
</ol>
<h2>Build Commands</h2>
<pre><code>make build # Release build
make debug # Debug build
make clean # Clean artifacts
make tests # Build and run all tests
make sync-manual # Sync sidebar across manual pages</code></pre>
<h2>Directory Structure</h2>
<pre><code>src/
cli/
modules.c # Foreign function registry
vm.c # VM and module loading
module/
&lt;name&gt;.wren # Wren interface source
&lt;name&gt;.wren.inc # Generated C string literal
&lt;name&gt;.c # C implementation (if foreign methods)
&lt;name&gt;.h # C header (if foreign methods)
test/
&lt;name&gt;/
&lt;feature&gt;.wren # Test files with annotations
example/
&lt;name&gt;_demo.wren # Usage demonstrations
manual/
api/&lt;name&gt;.html # API documentation</code></pre>
<h2>Getting Help</h2>
<p>If you encounter issues while contributing:</p>
<ul>
<li>Review existing modules as reference implementations</li>
<li>Check the test files for expected behavior patterns</li>
<li>Consult the <a href="module-overview.html">Module Architecture</a> section for detailed artifact requirements</li>
</ul>
<h2>Next Steps</h2>
<p>Start with the <a href="module-overview.html">Module Architecture</a> section to understand the project structure, then proceed to the appropriate module guide based on your needs.</p>
</article>
<footer class="page-footer">
<a href="../howto/error-handling.html" class="prev">Error Handling</a>
<a href="module-overview.html" class="next">Module Architecture</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,349 @@
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Module Architecture - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="index.html">Overview</a></li>
<li><a href="module-overview.html" class="active">Module Architecture</a></li>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="c-backed-module.html">C-Backed Modules</a></li>
<li><a href="foreign-classes.html">Foreign Classes</a></li>
<li><a href="async-patterns.html">Async Patterns</a></li>
<li><a href="testing.html">Writing Tests</a></li>
<li><a href="documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<a href="index.html">Contributing</a>
<span class="separator">/</span>
<span>Module Architecture</span>
</nav>
<article>
<h1>Module Architecture</h1>
<p>This section explains how Wren-CLI modules are structured, what files each module type requires, and how the build system processes them.</p>
<h2>Project Structure</h2>
<pre><code>src/
cli/
main.c # Entry point
vm.c # VM initialization, libuv event loop, module loading
modules.c # Foreign function registry, module registration
modules.h # Public interface for module loading/binding
path.c # Cross-platform path manipulation
module/
&lt;name&gt;.wren # Wren interface source
&lt;name&gt;.wren.inc # Generated C string literal (do not edit)
&lt;name&gt;.c # C implementation (only for foreign methods)
&lt;name&gt;.h # C header (only for foreign methods)
test/
&lt;modulename&gt;/
&lt;testname&gt;.wren # Test files with inline annotations
example/
&lt;modulename&gt;_demo.wren # Comprehensive usage demonstrations
manual/
api/ # One HTML file per module
deps/
wren/ # Wren language VM
libuv/ # Async I/O library
cjson/ # JSON parsing library
sqlite/ # SQLite database library</code></pre>
<h2>Module Artifact Matrix</h2>
<p>Every built-in module has up to six artifacts:</p>
<table>
<tr>
<th>Artifact</th>
<th>Path</th>
<th>Required?</th>
</tr>
<tr>
<td>Wren source</td>
<td><code>src/module/&lt;name&gt;.wren</code></td>
<td>Always</td>
</tr>
<tr>
<td>Generated C string</td>
<td><code>src/module/&lt;name&gt;.wren.inc</code></td>
<td>Always</td>
</tr>
<tr>
<td>C implementation</td>
<td><code>src/module/&lt;name&gt;.c</code></td>
<td>Only if foreign methods</td>
</tr>
<tr>
<td>C header</td>
<td><code>src/module/&lt;name&gt;.h</code></td>
<td>Only if .c exists</td>
</tr>
<tr>
<td>Registration in modules.c</td>
<td><code>src/cli/modules.c</code></td>
<td>Always</td>
</tr>
<tr>
<td>Makefile object entry</td>
<td><code>projects/make/wren_cli.make</code></td>
<td>Only if .c exists</td>
</tr>
</table>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Pure-Wren modules (argparse, html, jinja, http, markdown, dataset, web, websocket, wdantic, uuid, tempfile, repl) skip the C files and Makefile entry. C-backed modules (json, io, net, crypto, tls, sqlite, base64, etc.) need all six.</p>
</div>
<h2>The .wren to .wren.inc Pipeline</h2>
<p>Module source code is embedded directly into the compiled binary as C string literals. The script <code>util/wren_to_c_string.py</code> performs this conversion.</p>
<h3>What It Does</h3>
<ol>
<li>Reads the <code>.wren</code> file line by line</li>
<li>Escapes <code>\</code> to <code>\\</code> and <code>"</code> to <code>\"</code></li>
<li>Wraps each line in C string literal quotes with <code>\n</code> appended</li>
<li>Outputs a <code>.wren.inc</code> file with a <code>static const char*</code> variable</li>
</ol>
<h3>Variable Naming</h3>
<p>The variable name is derived from the filename: <code>foo.wren</code> becomes <code>fooModuleSource</code>. Prefixes <code>opt_</code> and <code>wren_</code> are stripped automatically.</p>
<h3>Usage</h3>
<pre><code>python3 util/wren_to_c_string.py src/module/foo.wren.inc src/module/foo.wren</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Every time a <code>.wren</code> file is edited, its <code>.wren.inc</code> must be regenerated. If forgotten, the binary will contain stale module source.</p>
</div>
<h2>Module Registration in modules.c</h2>
<p><code>src/cli/modules.c</code> has three sections to update:</p>
<h3>Section 1: Include the .wren.inc</h3>
<p>Add at the top of the file with other includes:</p>
<pre><code>#include "mymodule.wren.inc"</code></pre>
<h3>Section 2: Extern Declarations</h3>
<p>Only required for C-backed modules:</p>
<pre><code>extern void mymoduleDoSomething(WrenVM* vm);
extern void mymoduleComplexOp(WrenVM* vm);</code></pre>
<h3>Section 3: Module Array Entry</h3>
<p>For pure-Wren modules:</p>
<pre><code>MODULE(mymodule)
END_MODULE</code></pre>
<p>For C-backed modules with static methods:</p>
<pre><code>MODULE(mymodule)
CLASS(MyClass)
STATIC_METHOD("doSomething(_)", mymoduleDoSomething)
STATIC_METHOD("complexOp_(_,_,_)", mymoduleComplexOp)
END_CLASS
END_MODULE</code></pre>
<p>For foreign classes with allocation/finalization:</p>
<pre><code>MODULE(mymodule)
CLASS(MyForeignClass)
ALLOCATE(myClassAllocate)
FINALIZE(myClassFinalize)
METHOD("doThing(_)", myClassDoThing)
END_CLASS
END_MODULE</code></pre>
<h3>Registration Macros Reference</h3>
<table>
<tr>
<th>Macro</th>
<th>Usage</th>
</tr>
<tr>
<td><code>MODULE(name)</code> / <code>END_MODULE</code></td>
<td>Module boundary, name must match import string</td>
</tr>
<tr>
<td><code>CLASS(name)</code> / <code>END_CLASS</code></td>
<td>Class boundary, name must match Wren class name</td>
</tr>
<tr>
<td><code>STATIC_METHOD("sig", fn)</code></td>
<td>Bind static foreign method</td>
</tr>
<tr>
<td><code>METHOD("sig", fn)</code></td>
<td>Bind instance foreign method</td>
</tr>
<tr>
<td><code>ALLOCATE(fn)</code></td>
<td>Foreign class constructor</td>
</tr>
<tr>
<td><code>FINALIZE(fn)</code></td>
<td>Foreign class destructor</td>
</tr>
</table>
<h3>Method Signature Format</h3>
<p>The signature string must exactly match what Wren expects:</p>
<ul>
<li><code>"methodName(_)"</code> - one argument</li>
<li><code>"methodName(_,_)"</code> - two arguments</li>
<li><code>"propertyName"</code> - getter (no parentheses)</li>
<li><code>"propertyName=(_)"</code> - setter</li>
</ul>
<h2>Core Components</h2>
<h3>vm.c</h3>
<p>VM initialization, libuv event loop integration, module resolution. The <code>loadModule()</code> function first checks <code>wren_modules/</code> on disk, then falls back to <code>loadBuiltInModule()</code> which serves embedded <code>.wren.inc</code> strings. The <code>resolveModule()</code> function handles simple imports (bare names to built-in) and relative imports (<code>./</code>, <code>../</code> to file path resolution).</p>
<h3>modules.c</h3>
<p>Central registry of all built-in modules. Contains the <code>modules[]</code> array with module/class/method metadata, the <code>.wren.inc</code> includes, extern declarations for C functions, and lookup functions (<code>findModule</code>, <code>findClass</code>, <code>findMethod</code>).</p>
<h2>Event Loop</h2>
<p>All I/O is async via libuv. Wren fibers suspend during I/O operations and resume when complete. The event loop runs after script interpretation via <code>uv_run(loop, UV_RUN_DEFAULT)</code>.</p>
<h2>System Dependencies</h2>
<ul>
<li>OpenSSL (<code>libssl</code>, <code>libcrypto</code>) - required for TLS/HTTPS support</li>
<li>pthreads, dl, m - linked automatically</li>
</ul>
<h2>Next Steps</h2>
<p>Now that you understand the architecture, proceed to:</p>
<ul>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a> - for modules without C code</li>
<li><a href="c-backed-module.html">C-Backed Modules</a> - for modules with foreign methods</li>
</ul>
</article>
<footer class="page-footer">
<a href="index.html" class="prev">Overview</a>
<a href="pure-wren-module.html" class="next">Pure-Wren Modules</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,375 @@
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pure-Wren Modules - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="index.html">Overview</a></li>
<li><a href="module-overview.html">Module Architecture</a></li>
<li><a href="pure-wren-module.html" class="active">Pure-Wren Modules</a></li>
<li><a href="c-backed-module.html">C-Backed Modules</a></li>
<li><a href="foreign-classes.html">Foreign Classes</a></li>
<li><a href="async-patterns.html">Async Patterns</a></li>
<li><a href="testing.html">Writing Tests</a></li>
<li><a href="documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<a href="index.html">Contributing</a>
<span class="separator">/</span>
<span>Pure-Wren Modules</span>
</nav>
<article>
<h1>Pure-Wren Modules</h1>
<p>Pure-Wren modules are written entirely in Wren, with no C code required. They may depend on other modules (both pure-Wren and C-backed) but do not implement any foreign methods themselves.</p>
<p>Examples of pure-Wren modules: argparse, html, jinja, http, markdown, dataset, web, websocket, wdantic, uuid, tempfile.</p>
<h2>Step 1: Create the Wren Source</h2>
<p>Create <code>src/module/&lt;name&gt;.wren</code> with your module implementation:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
class Utils {
static capitalize(str) {
if (str.count == 0) return str
return str[0].upcase + str[1..-1]
}
static reverse(str) {
var result = ""
for (i in (str.count - 1)..0) {
result = result + str[i]
}
return result
}
static repeat(str, times) {
var result = ""
for (i in 0...times) {
result = result + str
}
return result
}
static words(str) {
return str.split(" ").where {|w| w.count > 0 }.toList
}
}</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Always include the author comment on the first line. Class names should be capitalized and descriptive.</p>
</div>
<h2>Step 2: Generate the .wren.inc</h2>
<p>Convert the Wren source to a C string literal:</p>
<pre><code>python3 util/wren_to_c_string.py src/module/utils.wren.inc src/module/utils.wren</code></pre>
<p>This creates <code>src/module/utils.wren.inc</code> containing:</p>
<pre><code>static const char* utilsModuleSource =
"// retoor &lt;retoor@molodetz.nl&gt;\n"
"\n"
"class Utils {\n"
" static capitalize(str) {\n"
// ... rest of the source
;</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Never edit <code>.wren.inc</code> files directly. Always edit the <code>.wren</code> source and regenerate.</p>
</div>
<h2>Step 3: Register in modules.c</h2>
<p>Edit <code>src/cli/modules.c</code> to register the new module.</p>
<h3>Add the Include</h3>
<p>Near the top of the file with other includes:</p>
<pre><code>#include "utils.wren.inc"</code></pre>
<h3>Add the Module Entry</h3>
<p>In the <code>modules[]</code> array:</p>
<pre><code>MODULE(utils)
END_MODULE</code></pre>
<p>For pure-Wren modules, the block is empty. No class or method registrations are needed because there are no foreign bindings.</p>
<h2>Step 4: Build</h2>
<pre><code>make clean && make build</code></pre>
<p>The clean build ensures the new module is properly included.</p>
<h2>Step 5: Test</h2>
<p>Create the test directory:</p>
<pre><code>mkdir -p test/utils</code></pre>
<p>Create test files with inline annotations. For example, <code>test/utils/capitalize.wren</code>:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "utils" for Utils
System.print(Utils.capitalize("hello")) // expect: Hello
System.print(Utils.capitalize("WORLD")) // expect: WORLD
System.print(Utils.capitalize("")) // expect:</code></pre>
<p>Run the tests:</p>
<pre><code>python3 util/test.py utils</code></pre>
<h2>Step 6: Create Example</h2>
<p>Create <code>example/utils_demo.wren</code>:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "utils" for Utils
System.print("=== Utils Demo ===\n")
System.print("--- Capitalize ---")
System.print(Utils.capitalize("hello"))
System.print(Utils.capitalize("wren"))
System.print("\n--- Reverse ---")
System.print(Utils.reverse("hello"))
System.print(Utils.reverse("12345"))
System.print("\n--- Repeat ---")
System.print(Utils.repeat("ab", 3))
System.print(Utils.repeat("-", 10))
System.print("\n--- Words ---")
var sentence = "The quick brown fox"
var wordList = Utils.words(sentence)
for (word in wordList) {
System.print(" - %(word)")
}</code></pre>
<h2>Step 7: Add Documentation</h2>
<p>Create <code>manual/api/utils.html</code> following the API page template. See the <a href="documentation.html">Documentation</a> section for details.</p>
<h2>Step 8: Sync the Sidebar</h2>
<pre><code>make sync-manual</code></pre>
<p>This updates the sidebar navigation across all manual pages to include the new module.</p>
<h2>Step 9: Verify</h2>
<pre><code>python3 util/test.py utils
bin/wren_cli example/utils_demo.wren</code></pre>
<h2>Complete Example</h2>
<p>Here is a more realistic pure-Wren module that builds on existing modules:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "json" for Json
import "io" for File
class Config {
static load(path) {
var content = File.read(path)
return Json.parse(content)
}
static save(path, data) {
var content = Json.stringify(data, 2)
File.write(path, content)
}
static get(path, key) {
var config = Config.load(path)
return config[key]
}
static set(path, key, value) {
var config = {}
if (File.exists(path)) {
config = Config.load(path)
}
config[key] = value
Config.save(path, config)
}
}</code></pre>
<p>This module depends on <code>json</code> and <code>io</code>, demonstrating how pure-Wren modules compose functionality from other modules.</p>
<h2>Common Patterns</h2>
<h3>Static Utility Classes</h3>
<p>Most pure-Wren modules use static methods for stateless utilities:</p>
<pre><code>class StringUtils {
static trim(s) { ... }
static pad(s, width) { ... }
}</code></pre>
<h3>Factory Classes</h3>
<p>For stateful objects, use instance methods:</p>
<pre><code>class Builder {
construct new() {
_parts = []
}
add(part) {
_parts.add(part)
return this
}
build() { _parts.join("") }
}</code></pre>
<h3>Wrapping Async Operations</h3>
<p>Pure-Wren modules can wrap async operations from C-backed modules:</p>
<pre><code>import "http" for Http
class Api {
static get(endpoint) {
return Http.get("https://api.example.com" + endpoint)
}
}</code></pre>
<h2>Checklist</h2>
<ul>
<li>Created <code>src/module/&lt;name&gt;.wren</code> with author comment</li>
<li>Generated <code>.wren.inc</code> with util script</li>
<li>Added <code>#include</code> in modules.c</li>
<li>Added <code>MODULE</code>/<code>END_MODULE</code> block in modules.c</li>
<li>Built with <code>make clean && make build</code></li>
<li>Created tests in <code>test/&lt;name&gt;/</code></li>
<li>Created example in <code>example/&lt;name&gt;_demo.wren</code></li>
<li>Created documentation page</li>
<li>Ran <code>make sync-manual</code></li>
<li>All tests pass</li>
</ul>
<h2>Next Steps</h2>
<p>If your module requires native functionality not available through existing modules, see <a href="c-backed-module.html">C-Backed Modules</a>.</p>
</article>
<footer class="page-footer">
<a href="module-overview.html" class="prev">Module Architecture</a>
<a href="c-backed-module.html" class="next">C-Backed Modules</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,442 @@
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Writing Tests - Wren-CLI Manual</title>
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<button class="mobile-menu-toggle">Menu</button>
<div class="container">
<aside class="sidebar">
<div class="sidebar-header">
<h1><a href="../index.html">Wren-CLI</a></h1>
<div class="version">v0.4.0</div>
</div>
<nav class="sidebar-nav">
<div class="section">
<span class="section-title">Getting Started</span>
<ul>
<li><a href="../getting-started/index.html">Overview</a></li>
<li><a href="../getting-started/installation.html">Installation</a></li>
<li><a href="../getting-started/first-script.html">First Script</a></li>
<li><a href="../getting-started/repl.html">Using the REPL</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Language</span>
<ul>
<li><a href="../language/index.html">Syntax Overview</a></li>
<li><a href="../language/classes.html">Classes</a></li>
<li><a href="../language/methods.html">Methods</a></li>
<li><a href="../language/control-flow.html">Control Flow</a></li>
<li><a href="../language/fibers.html">Fibers</a></li>
<li><a href="../language/modules.html">Modules</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">API Reference</span>
<ul>
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Tutorials</span>
<ul>
<li><a href="../tutorials/index.html">Tutorial List</a></li>
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="index.html">Overview</a></li>
<li><a href="module-overview.html">Module Architecture</a></li>
<li><a href="pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="c-backed-module.html">C-Backed Modules</a></li>
<li><a href="foreign-classes.html">Foreign Classes</a></li>
<li><a href="async-patterns.html">Async Patterns</a></li>
<li><a href="testing.html" class="active">Writing Tests</a></li>
<li><a href="documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
<nav class="breadcrumb">
<a href="../index.html">Home</a>
<span class="separator">/</span>
<a href="index.html">Contributing</a>
<span class="separator">/</span>
<span>Writing Tests</span>
</nav>
<article>
<h1>Writing Tests</h1>
<p>Wren-CLI uses inline annotations in test files to specify expected behavior. The test runner <code>util/test.py</code> parses these annotations and verifies the output.</p>
<h2>Test Directory Structure</h2>
<pre><code>test/
&lt;modulename&gt;/
&lt;feature&gt;.wren # Functional tests
error_&lt;scenario&gt;.wren # Error case tests (one runtime error each)</code></pre>
<p>Each module has its own directory under <code>test/</code>. Test files are named descriptively based on what they test.</p>
<h2>Running Tests</h2>
<pre><code># Build and run all tests
make tests
# Run tests for a specific module
python3 util/test.py json
# Run a specific test file (prefix match)
python3 util/test.py json/parse
# Run with debug binary
python3 util/test.py --suffix=_d</code></pre>
<h2>Test Annotations</h2>
<table>
<tr>
<th>Annotation</th>
<th>Purpose</th>
<th>Expected Exit Code</th>
</tr>
<tr>
<td><code>// expect: output</code></td>
<td>Assert stdout line (matched in order)</td>
<td>0</td>
</tr>
<tr>
<td><code>// expect error</code></td>
<td>Assert compile error on this line</td>
<td>65</td>
</tr>
<tr>
<td><code>// expect error line N</code></td>
<td>Assert compile error on line N</td>
<td>65</td>
</tr>
<tr>
<td><code>// expect runtime error: msg</code></td>
<td>Assert runtime error with exact message</td>
<td>70</td>
</tr>
<tr>
<td><code>// expect handled runtime error: msg</code></td>
<td>Assert caught runtime error</td>
<td>0</td>
</tr>
<tr>
<td><code>// stdin: text</code></td>
<td>Feed text to stdin (repeatable)</td>
<td>-</td>
</tr>
<tr>
<td><code>// skip: reason</code></td>
<td>Skip this test file</td>
<td>-</td>
</tr>
<tr>
<td><code>// nontest</code></td>
<td>Ignore this file</td>
<td>-</td>
</tr>
</table>
<h2>Basic Test Example</h2>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "json" for Json
var obj = {"name": "test", "value": 42}
var str = Json.stringify(obj)
var parsed = Json.parse(str)
System.print(parsed["name"]) // expect: test
System.print(parsed["value"]) // expect: 42</code></pre>
<p>Each <code>// expect:</code> annotation asserts that the corresponding line of output matches exactly.</p>
<h2>Multiple Expect Annotations</h2>
<p>Multiple expectations are matched in order:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "utils" for Utils
System.print(Utils.capitalize("hello")) // expect: Hello
System.print(Utils.capitalize("world")) // expect: World
System.print(Utils.capitalize("UPPER")) // expect: UPPER
System.print(Utils.capitalize("")) // expect:</code></pre>
<p>The empty <code>// expect:</code> matches an empty line.</p>
<h2>Runtime Error Tests</h2>
<p>For testing error conditions, create a separate file per error case:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "json" for Json
Json.parse("invalid json") // expect runtime error: Invalid JSON.</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Only one runtime error per test file. The test runner tracks a single expected error. Split multiple error cases into separate files.</p>
</div>
<h3>Error Line Matching</h3>
<p>The runtime error annotation must be on the line that calls the aborting code. The runner checks the stack trace for a <code>test/...</code> path and matches the line number.</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "io" for File
var content = File.read("/nonexistent/path") // expect runtime error: Cannot open file.</code></pre>
<h2>Compile Error Tests</h2>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
class Test {
foo( // expect error
}</code></pre>
<p>Or specify a different line number:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
// This is valid
var x = 1
class Broken {
// expect error line 7
foo(</code></pre>
<h2>Stdin Input</h2>
<p>Use <code>// stdin:</code> to provide input:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "io" for Stdin
// stdin: hello
// stdin: world
var line1 = Stdin.readLine()
var line2 = Stdin.readLine()
System.print(line1) // expect: hello
System.print(line2) // expect: world</code></pre>
<p>Multiple <code>// stdin:</code> lines are concatenated with newlines.</p>
<h2>Skipping Tests</h2>
<p>Use <code>// skip:</code> for tests that cannot run in all environments:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
// skip: Requires network access
import "http" for Http
var response = Http.get("https://example.com")
System.print(response.status) // expect: 200</code></pre>
<h2>Non-Test Files</h2>
<p>Use <code>// nontest</code> for helper files that should not be run as tests:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
// nontest
class TestHelper {
static setup() { ... }
}</code></pre>
<h2>Test File Organization</h2>
<h3>Feature Tests</h3>
<p>Test each feature in a dedicated file:</p>
<pre><code>test/json/
parse.wren # Basic parsing
parse_nested.wren # Nested objects/arrays
stringify.wren # JSON serialization
stringify_pretty.wren # Pretty printing
types.wren # Type handling</code></pre>
<h3>Error Tests</h3>
<p>One error per file, named with <code>error_</code> prefix:</p>
<pre><code>test/json/
error_invalid_syntax.wren
error_unexpected_eof.wren
error_invalid_escape.wren</code></pre>
<h2>Handled Runtime Error</h2>
<p>For testing error handling where errors are caught:</p>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "json" for Json
var result = Fiber.new {
Json.parse("bad")
}.try()
if (result.error) {
System.print("Caught error") // expect: Caught error
} // expect handled runtime error: Invalid JSON.</code></pre>
<h2>Test Timeout</h2>
<p>Each test file has a 15-second timeout. If a test hangs (e.g., waiting for input that never comes), it will be killed.</p>
<h2>Test Discovery</h2>
<p>The test runner discovers tests by:</p>
<ol>
<li>Walking the <code>test/</code> directory recursively</li>
<li>Filtering by <code>.wren</code> extension</li>
<li>Converting paths to relative paths from <code>test/</code></li>
<li>Checking if path starts with the filter argument</li>
</ol>
<p>This means:</p>
<ul>
<li><code>python3 util/test.py json</code> runs everything in <code>test/json/</code></li>
<li><code>python3 util/test.py io/file</code> runs everything in <code>test/io/file/</code></li>
<li>Subdirectories are supported for organizing large test suites</li>
</ul>
<h2>Example Test Suite</h2>
<pre><code>test/mymodule/
basic.wren
advanced.wren
edge_cases.wren
error_null_input.wren
error_invalid_type.wren
error_overflow.wren</code></pre>
<h3>basic.wren</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "mymodule" for MyClass
System.print(MyClass.process("hello")) // expect: HELLO
System.print(MyClass.process("world")) // expect: WORLD
System.print(MyClass.length("test")) // expect: 4</code></pre>
<h3>error_null_input.wren</h3>
<pre><code>// retoor &lt;retoor@molodetz.nl&gt;
import "mymodule" for MyClass
MyClass.process(null) // expect runtime error: Input cannot be null.</code></pre>
<h2>Debugging Failing Tests</h2>
<ol>
<li>Run the test manually: <code>bin/wren_cli test/mymodule/failing.wren</code></li>
<li>Check the actual output versus expected</li>
<li>Verify annotations are on correct lines</li>
<li>For runtime errors, ensure annotation is on the calling line</li>
</ol>
<h2>Best Practices</h2>
<ul>
<li>Test one concept per file when possible</li>
<li>Use descriptive file names</li>
<li>Always include the author comment</li>
<li>Test edge cases (empty strings, null, large values)</li>
<li>Test error conditions in separate files</li>
<li>Keep tests simple and focused</li>
</ul>
<h2>Next Steps</h2>
<p>After writing tests, add documentation in <a href="documentation.html">Documentation</a>.</p>
</article>
<footer class="page-footer">
<a href="async-patterns.html" class="prev">Async Patterns</a>
<a href="documentation.html" class="next">Documentation</a>
</footer>
</main>
</div>
<script src="../js/main.js"></script>
</body>
</html>

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,8 +83,9 @@
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">
@ -102,22 +127,155 @@
<article>
<h1>Async Programming</h1>
<p>Wren-CLI uses fibers for concurrent operations. Understanding fibers is key to writing efficient async code.</p>
<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 a Fiber</h2>
<pre><code>var fiber = Fiber.new {
System.print("Hello from fiber!")
<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
}
fiber.call()</code></pre>
// 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
<h2>Fibers with Return Values</h2>
<pre><code>var fiber = Fiber.new {
return 42
// 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"])
}
var result = fiber.call()
System.print("Result: %(result)") // 42</code></pre>
// 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
@ -126,133 +284,77 @@ System.print("Starting...")
Timer.sleep(1000) // Wait 1 second
System.print("Done!")</code></pre>
<h2>Sequential HTTP Requests</h2>
<pre><code>import "http" for Http
<h2>Async with Error Handling</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "json" for Json
var r1 = Http.get("https://api.example.com/1")
var r2 = Http.get("https://api.example.com/2")
var r3 = Http.get("https://api.example.com/3")
System.print(r1.json)
System.print(r2.json)
System.print(r3.json)</code></pre>
<h2>Parallel HTTP Requests</h2>
<pre><code>import "http" for Http
var urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3"
]
var fibers = []
for (url in urls) {
fibers.add(Fiber.new { Http.get(url) })
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}
}
for (fiber in fibers) {
fiber.call()
}
for (fiber in fibers) {
System.print(fiber.value)
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>Run Task in Background</h2>
<pre><code>import "timer" for Timer
<h2>Retry with Backoff</h2>
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "timer" for Timer
var backgroundTask = Fiber.new {
for (i in 1..5) {
System.print("Background: %(i)")
Timer.sleep(500)
}
}
var fetchWithRetry = async { |url, maxRetries|
var attempt = 0
var delay = 1000
backgroundTask.call()
while (attempt < maxRetries) {
var fiber = Fiber.new { Client.get(url) }
var result = fiber.try()
System.print("Main thread continues...")
Timer.sleep(3000)</code></pre>
<h2>Wait for Multiple Operations</h2>
<pre><code>import "http" for Http
var fetchAll = Fn.new { |urls|
var results = []
var fibers = []
for (url in urls) {
fibers.add(Fiber.new { Http.get(url) })
}
for (fiber in fibers) {
fiber.call()
}
for (fiber in fibers) {
results.add(fiber.value)
}
return results
}
var responses = fetchAll.call([
"https://api.example.com/a",
"https://api.example.com/b"
])
for (r in responses) {
System.print(r.statusCode)
}</code></pre>
<h2>Timeout Pattern</h2>
<pre><code>import "timer" for Timer
import "datetime" for DateTime
var withTimeout = Fn.new { |operation, timeoutMs|
var start = DateTime.now()
var result = null
var done = false
var workFiber = Fiber.new {
result = operation.call()
done = true
}
workFiber.call()
while (!done) {
var elapsed = (DateTime.now() - start).milliseconds
if (elapsed >= timeoutMs) {
Fiber.abort("Operation timed out")
if (!fiber.error && result["status"] == 200) {
return result
}
Timer.sleep(10)
attempt = attempt + 1
System.print("Attempt %(attempt) failed, retrying...")
Timer.sleep(delay)
delay = delay * 2
}
return result
Fiber.abort("All %(maxRetries) attempts failed")
}
var result = withTimeout.call(Fn.new {
Timer.sleep(500)
return "completed"
}, 1000)
var response = await fetchWithRetry("https://api.example.com/data", 3)
System.print("Success: %(response["status"])")</code></pre>
System.print(result)</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
<h2>Polling Loop</h2>
<pre><code>import "timer" for Timer
import "http" for Http
var pollUntilReady = Fn.new { |url, maxAttempts|
var pollUntilReady = async { |url, maxAttempts|
var attempts = 0
while (attempts < maxAttempts) {
attempts = attempts + 1
System.print("Attempt %(attempts)...")
System.print("Checking status (attempt %(attempts))...")
var response = Http.get(url)
if (response.json["ready"]) {
return response.json
var response = Client.get(url)
var data = Json.parse(response["body"])
if (data["status"] == "ready") {
return data
}
Timer.sleep(2000)
@ -261,22 +363,23 @@ var pollUntilReady = Fn.new { |url, maxAttempts|
return null
}
var result = pollUntilReady.call("https://api.example.com/status", 10)
var result = await pollUntilReady("https://api.example.com/job/123", 10)
if (result) {
System.print("Ready: %(result)")
System.print("Job completed: %(result)")
} else {
System.print("Timed out waiting for ready state")
System.print("Timed out waiting for job")
}</code></pre>
<h2>Rate Limiting</h2>
<pre><code>import "timer" for Timer
import "http" for Http
<pre><code>import "web" for Client
import "scheduler" for Scheduler, Future
import "timer" for Timer
var rateLimitedFetch = Fn.new { |urls, delayMs|
var rateLimitedFetch = async { |urls, delayMs|
var results = []
for (url in urls) {
var response = Http.get(url)
var response = Client.get(url)
results.add(response)
Timer.sleep(delayMs)
}
@ -284,104 +387,17 @@ var rateLimitedFetch = Fn.new { |urls, delayMs|
return results
}
var responses = rateLimitedFetch.call([
var urls = [
"https://api.example.com/1",
"https://api.example.com/2",
"https://api.example.com/3"
], 1000)
]
var responses = await rateLimitedFetch(urls, 500)
for (r in responses) {
System.print(r.statusCode)
System.print(r["status"])
}</code></pre>
<h2>Producer/Consumer Pattern</h2>
<pre><code>import "timer" for Timer
var queue = []
var running = true
var producer = Fiber.new {
for (i in 1..10) {
queue.add(i)
System.print("Produced: %(i)")
Timer.sleep(200)
}
running = false
}
var consumer = Fiber.new {
while (running || queue.count > 0) {
if (queue.count > 0) {
var item = queue.removeAt(0)
System.print("Consumed: %(item)")
}
Timer.sleep(100)
}
}
producer.call()
consumer.call()
System.print("Done!")</code></pre>
<h2>Retry with Exponential Backoff</h2>
<pre><code>import "timer" for Timer
import "http" for Http
var retryWithBackoff = Fn.new { |operation, maxRetries|
var attempt = 0
var delay = 1000
while (attempt < maxRetries) {
var fiber = Fiber.new { operation.call() }
var result = fiber.try()
if (!fiber.error) {
return result
}
attempt = attempt + 1
System.print("Attempt %(attempt) failed, retrying in %(delay)ms...")
Timer.sleep(delay)
delay = delay * 2
}
Fiber.abort("All %(maxRetries) attempts failed")
}
var response = retryWithBackoff.call(Fn.new {
return Http.get("https://api.example.com/data")
}, 3)
System.print(response.json)</code></pre>
<h2>Concurrent WebSocket Handling</h2>
<pre><code>import "websocket" for WebSocket, WebSocketMessage
import "timer" for Timer
var ws = WebSocket.connect("ws://localhost:8080")
var receiver = Fiber.new {
while (true) {
var message = ws.receive()
if (message == null) break
if (message.opcode == WebSocketMessage.TEXT) {
System.print("Received: %(message.payload)")
}
}
}
var sender = Fiber.new {
for (i in 1..5) {
ws.send("Message %(i)")
Timer.sleep(1000)
}
ws.close()
}
receiver.call()
sender.call()</code></pre>
<h2>Graceful Shutdown</h2>
<pre><code>import "signal" for Signal
import "timer" for Timer
@ -404,12 +420,12 @@ System.print("Cleanup complete, exiting.")</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>Wren fibers are cooperative, not preemptive. A fiber runs until it explicitly yields, calls an async operation, or completes. Long-running computations should periodically yield to allow other fibers to run.</p>
<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 on fibers, see the <a href="../language/fibers.html">Fibers language guide</a> and the <a href="../api/scheduler.html">Scheduler module reference</a>.</p>
<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>
</article>

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,8 +83,9 @@
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="error-handling.html" class="active">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,8 +83,9 @@
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,8 +83,9 @@
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,8 +83,9 @@
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,8 +83,9 @@
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,8 +83,9 @@
<li><a href="../tutorials/http-client.html">HTTP Client</a></li>
<li><a href="../tutorials/websocket-chat.html">WebSocket Chat</a></li>
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Template Rendering</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -85,6 +85,7 @@
<li><a href="tutorials/database-app.html">Database App</a></li>
<li><a href="tutorials/template-rendering.html">Templates</a></li>
<li><a href="tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -99,6 +100,19 @@
<li><a href="howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="contributing/index.html">Overview</a></li>
<li><a href="contributing/module-overview.html">Module Architecture</a></li>
<li><a href="contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="contributing/testing.html">Writing Tests</a></li>
<li><a href="contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -74,6 +85,7 @@
<li><a href="../tutorials/database-app.html">Database App</a></li>
<li><a href="../tutorials/template-rendering.html">Templates</a></li>
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
<li><a href="../tutorials/web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
@ -88,6 +100,19 @@
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
</aside>
<main class="content">

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,14 +83,34 @@
<li><a href="http-client.html">HTTP Client</a></li>
<li><a href="websocket-chat.html">WebSocket Chat</a></li>
<li><a href="database-app.html">Database App</a></li>
<li><a href="template-rendering.html">Template Rendering</a></li>
<li><a href="template-rendering.html">Templates</a></li>
<li><a href="cli-tool.html" class="active">CLI Tool</a></li>
<li><a href="web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
@ -733,7 +764,7 @@ SHA256: a3f2e8b9c4d5...</code></pre>
<footer class="page-footer">
<a href="template-rendering.html" class="prev">Template Rendering</a>
<a href="../howto/index.html" class="next">How-To Guides</a>
<a href="web-server.html" class="next">Web Server</a>
</footer>
</main>
</div>

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,14 +83,34 @@
<li><a href="http-client.html">HTTP Client</a></li>
<li><a href="websocket-chat.html">WebSocket Chat</a></li>
<li><a href="database-app.html" class="active">Database App</a></li>
<li><a href="template-rendering.html">Template Rendering</a></li>
<li><a href="template-rendering.html">Templates</a></li>
<li><a href="cli-tool.html">CLI Tool</a></li>
<li><a href="web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,14 +83,34 @@
<li><a href="http-client.html" class="active">HTTP Client</a></li>
<li><a href="websocket-chat.html">WebSocket Chat</a></li>
<li><a href="database-app.html">Database App</a></li>
<li><a href="template-rendering.html">Template Rendering</a></li>
<li><a href="template-rendering.html">Templates</a></li>
<li><a href="cli-tool.html">CLI Tool</a></li>
<li><a href="web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
@ -398,6 +429,92 @@ if (result["error"]) {
System.print("Success: %(result["data"]["title"])")
}</code></pre>
<h2>Step 9: Concurrent HTTP Requests</h2>
<p>When you need to fetch data from multiple endpoints, sequential requests can be slow. Use <code>async</code> and <code>await</code> to run requests concurrently:</p>
<pre><code>import "http" for Http
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchJson = async { |url|
var response = Http.get(url)
return response.json
}
// SEQUENTIAL: Each request waits for the previous one
System.print("--- Sequential requests ---")
var user = await fetchJson("https://jsonplaceholder.typicode.com/users/1")
var posts = await fetchJson("https://jsonplaceholder.typicode.com/posts?userId=1")
var todos = await fetchJson("https://jsonplaceholder.typicode.com/todos?userId=1")
System.print("User: %(user["name"])")
System.print("Posts: %(posts.count)")
System.print("Todos: %(todos.count)")</code></pre>
<p>For concurrent execution, use <code>.call()</code> to start requests without waiting, then <code>await</code> the results:</p>
<pre><code>import "http" for Http
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchJson = async { |url|
var response = Http.get(url)
return response.json
}
// CONCURRENT: All requests start at once
System.print("--- Concurrent requests ---")
var f1 = fetchJson.call("https://jsonplaceholder.typicode.com/users/1")
var f2 = fetchJson.call("https://jsonplaceholder.typicode.com/posts?userId=1")
var f3 = fetchJson.call("https://jsonplaceholder.typicode.com/todos?userId=1")
// Wait for results (requests run in parallel)
var user = await f1
var posts = await f2
var todos = await f3
System.print("User: %(user["name"])")
System.print("Posts: %(posts.count)")
System.print("Todos: %(todos.count)")</code></pre>
<h3>Batch Fetching with Concurrent Requests</h3>
<p>For fetching multiple URLs dynamically, create futures in a loop:</p>
<pre><code>import "http" for Http
import "scheduler" for Scheduler, Future
import "json" for Json
var fetchJson = async { |url|
var response = Http.get(url)
return response.json
}
var userIds = [1, 2, 3, 4, 5]
// Start all requests concurrently
var futures = []
for (id in userIds) {
futures.add(fetchJson.call("https://jsonplaceholder.typicode.com/users/%(id)"))
}
// Collect results
var users = []
for (f in futures) {
users.add(await f)
}
System.print("Fetched %(users.count) users:")
for (user in users) {
System.print(" - %(user["name"]) (%(user["email"]))")
}</code></pre>
<div class="admonition tip">
<div class="admonition-title">Direct Calling vs .call()</div>
<p>Use <code>await fn(args)</code> for sequential execution (waits immediately). Use <code>fn.call(args)</code> to start without waiting, enabling concurrent execution.</p>
</div>
<h2>Complete Example</h2>
<p>Here is a complete, production-ready API client:</p>

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,14 +83,34 @@
<li><a href="http-client.html">HTTP Client</a></li>
<li><a href="websocket-chat.html">WebSocket Chat</a></li>
<li><a href="database-app.html">Database App</a></li>
<li><a href="template-rendering.html">Template Rendering</a></li>
<li><a href="template-rendering.html">Templates</a></li>
<li><a href="cli-tool.html">CLI Tool</a></li>
<li><a href="web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>
@ -141,6 +172,15 @@
<span class="tag">subprocess</span>
</div>
</div>
<div class="card">
<h3><a href="web-server.html">Building a Web Server</a></h3>
<p>Build HTTP servers with routing, sessions, middleware, and REST APIs using the web module.</p>
<div class="card-meta">
<span class="tag">web</span>
<span class="tag">http</span>
</div>
</div>
</div>
<h2>Learning Path</h2>
@ -153,6 +193,7 @@
<li><strong>Template Rendering</strong> - Learn the Jinja template system</li>
<li><strong>WebSocket Chat</strong> - Advanced async patterns with fibers</li>
<li><strong>CLI Tool</strong> - Bringing it all together in a real application</li>
<li><strong>Web Server</strong> - Build complete web applications with the web module</li>
</ol>
<h2>Prerequisites</h2>

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,14 +83,34 @@
<li><a href="http-client.html">HTTP Client</a></li>
<li><a href="websocket-chat.html">WebSocket Chat</a></li>
<li><a href="database-app.html">Database App</a></li>
<li><a href="template-rendering.html" class="active">Template Rendering</a></li>
<li><a href="template-rendering.html" class="active">Templates</a></li>
<li><a href="cli-tool.html">CLI Tool</a></li>
<li><a href="web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>

File diff suppressed because it is too large Load Diff

View File

@ -42,27 +42,38 @@
<li><a href="../api/index.html">Overview</a></li>
<li><a href="../api/string.html">String</a></li>
<li><a href="../api/number.html">Num</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/argparse.html">argparse</a></li>
<li><a href="../api/base64.html">base64</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/crypto.html">crypto</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/dataset.html">dataset</a></li>
<li><a href="../api/datetime.html">datetime</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/dns.html">dns</a></li>
<li><a href="../api/env.html">env</a></li>
<li><a href="../api/fswatch.html">fswatch</a></li>
<li><a href="../api/html.html">html</a></li>
<li><a href="../api/http.html">http</a></li>
<li><a href="../api/io.html">io</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/jinja.html">jinja</a></li>
<li><a href="../api/json.html">json</a></li>
<li><a href="../api/markdown.html">markdown</a></li>
<li><a href="../api/math.html">math</a></li>
<li><a href="../api/net.html">net</a></li>
<li><a href="../api/os.html">os</a></li>
<li><a href="../api/pathlib.html">pathlib</a></li>
<li><a href="../api/regex.html">regex</a></li>
<li><a href="../api/scheduler.html">scheduler</a></li>
<li><a href="../api/signal.html">signal</a></li>
<li><a href="../api/sqlite.html">sqlite</a></li>
<li><a href="../api/subprocess.html">subprocess</a></li>
<li><a href="../api/sysinfo.html">sysinfo</a></li>
<li><a href="../api/tempfile.html">tempfile</a></li>
<li><a href="../api/timer.html">timer</a></li>
<li><a href="../api/tls.html">tls</a></li>
<li><a href="../api/udp.html">udp</a></li>
<li><a href="../api/uuid.html">uuid</a></li>
<li><a href="../api/wdantic.html">wdantic</a></li>
<li><a href="../api/web.html">web</a></li>
<li><a href="../api/websocket.html">websocket</a></li>
</ul>
</div>
<div class="section">
@ -72,14 +83,34 @@
<li><a href="http-client.html">HTTP Client</a></li>
<li><a href="websocket-chat.html" class="active">WebSocket Chat</a></li>
<li><a href="database-app.html">Database App</a></li>
<li><a href="template-rendering.html">Template Rendering</a></li>
<li><a href="template-rendering.html">Templates</a></li>
<li><a href="cli-tool.html">CLI Tool</a></li>
<li><a href="web-server.html">Web Server</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">How-To Guides</span>
<ul>
<li><a href="../howto/index.html">How-To List</a></li>
<li><a href="../howto/http-requests.html">HTTP Requests</a></li>
<li><a href="../howto/json-parsing.html">JSON Parsing</a></li>
<li><a href="../howto/regex-patterns.html">Regex Patterns</a></li>
<li><a href="../howto/file-operations.html">File Operations</a></li>
<li><a href="../howto/async-operations.html">Async Operations</a></li>
<li><a href="../howto/error-handling.html">Error Handling</a></li>
</ul>
</div>
<div class="section">
<span class="section-title">Contributing</span>
<ul>
<li><a href="../contributing/index.html">Overview</a></li>
<li><a href="../contributing/module-overview.html">Module Architecture</a></li>
<li><a href="../contributing/pure-wren-module.html">Pure-Wren Modules</a></li>
<li><a href="../contributing/c-backed-module.html">C-Backed Modules</a></li>
<li><a href="../contributing/foreign-classes.html">Foreign Classes</a></li>
<li><a href="../contributing/async-patterns.html">Async Patterns</a></li>
<li><a href="../contributing/testing.html">Writing Tests</a></li>
<li><a href="../contributing/documentation.html">Documentation</a></li>
</ul>
</div>
</nav>

View File

@ -12,6 +12,7 @@
#include "datetime.wren.inc"
#include "dns.wren.inc"
#include "env.wren.inc"
#include "faker.wren.inc"
#include "fswatch.wren.inc"
#include "html.wren.inc"
#include "http.wren.inc"
@ -179,6 +180,16 @@ extern void sqliteClose(WrenVM* vm);
extern void sqliteLastInsertId(WrenVM* vm);
extern void sqliteChanges(WrenVM* vm);
extern void subprocessRun(WrenVM* vm);
extern void popenAllocate(WrenVM* vm);
extern void popenFinalize(void* data);
extern void popenPid(WrenVM* vm);
extern void popenIsRunning(WrenVM* vm);
extern void popenWait(WrenVM* vm);
extern void popenKill(WrenVM* vm);
extern void popenReadStream(WrenVM* vm);
extern void popenWriteStream(WrenVM* vm);
extern void popenCloseStream(WrenVM* vm);
extern void popenIsStreamOpen(WrenVM* vm);
extern void regexAllocate(WrenVM* vm);
extern void regexFinalize(void* data);
extern void regexTest(WrenVM* vm);
@ -351,6 +362,8 @@ static ModuleRegistry modules[] =
STATIC_METHOD("all", envAll)
END_CLASS
END_MODULE
MODULE(faker)
END_MODULE
MODULE(fswatch)
CLASS(FileWatcher)
ALLOCATE(fswatchAllocate)
@ -576,6 +589,18 @@ static ModuleRegistry modules[] =
CLASS(Subprocess)
STATIC_METHOD("run_(_,_)", subprocessRun)
END_CLASS
CLASS(Popen)
ALLOCATE(popenAllocate)
FINALIZE(popenFinalize)
METHOD("pid", popenPid)
METHOD("isRunning", popenIsRunning)
METHOD("wait_(_)", popenWait)
METHOD("kill_(_)", popenKill)
METHOD("readStream_(_,_)", popenReadStream)
METHOD("writeStream_(_,_,_)", popenWriteStream)
METHOD("closeStream_(_)", popenCloseStream)
METHOD("isStreamOpen_(_)", popenIsStreamOpen)
END_CLASS
END_MODULE
MODULE(tempfile)
END_MODULE

890
src/module/faker.wren vendored Normal file
View File

@ -0,0 +1,890 @@
// retoor <retoor@molodetz.nl>
import "crypto" for Crypto, Hash
import "uuid" for Uuid
import "datetime" for DateTime, Duration
import "strutil" for Str
class FakerRandom {
static seed_ { __seed }
static state_ { __state }
static seed(value) {
__seed = value
__state = value
}
static reset() {
__seed = null
__state = null
}
static next(min, max) {
if (__seed == null) {
return Crypto.randomInt(min, max + 1)
}
if (__state == null) __state = __seed
__state = ((__state * 1103515245) + 12345) % 2147483648
var range = max - min + 1
return min + (__state % range)
}
static nextFloat() {
if (__seed == null) {
var bytes = Crypto.randomBytes(4)
var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]
if (value < 0) value = -value
return value / 2147483648
}
if (__state == null) __state = __seed
__state = ((__state * 1103515245) + 12345) % 2147483648
return __state / 2147483648
}
}
class FakerData {
static firstNamesMale {
return [
"James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Charles",
"Christopher", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven", "Paul", "Andrew", "Joshua",
"Kenneth", "Kevin", "Brian", "George", "Timothy", "Ronald", "Edward", "Jason", "Jeffrey", "Ryan",
"Jacob", "Gary", "Nicholas", "Eric", "Jonathan", "Stephen", "Larry", "Justin", "Scott", "Brandon",
"Benjamin", "Samuel", "Raymond", "Gregory", "Frank", "Alexander", "Patrick", "Jack", "Dennis", "Jerry",
"Tyler", "Aaron", "Jose", "Adam", "Nathan", "Henry", "Douglas", "Zachary", "Peter", "Kyle",
"Noah", "Ethan", "Jeremy", "Walter", "Christian", "Keith", "Roger", "Terry", "Austin", "Sean",
"Gerald", "Carl", "Harold", "Dylan", "Arthur", "Lawrence", "Jordan", "Jesse", "Bryan", "Billy",
"Bruce", "Gabriel", "Joe", "Logan", "Albert", "Willie", "Alan", "Vincent", "Eugene", "Russell",
"Elijah", "Randy", "Philip", "Harry", "Howard", "Roy", "Louis", "Russell", "Bobby", "Johnny"
]
}
static firstNamesFemale {
return [
"Mary", "Patricia", "Jennifer", "Linda", "Barbara", "Elizabeth", "Susan", "Jessica", "Sarah", "Karen",
"Lisa", "Nancy", "Betty", "Margaret", "Sandra", "Ashley", "Kimberly", "Emily", "Donna", "Michelle",
"Dorothy", "Carol", "Amanda", "Melissa", "Deborah", "Stephanie", "Rebecca", "Sharon", "Laura", "Cynthia",
"Kathleen", "Amy", "Angela", "Shirley", "Anna", "Brenda", "Pamela", "Emma", "Nicole", "Helen",
"Samantha", "Katherine", "Christine", "Debra", "Rachel", "Carolyn", "Janet", "Catherine", "Maria", "Heather",
"Diane", "Ruth", "Julie", "Olivia", "Joyce", "Virginia", "Victoria", "Kelly", "Lauren", "Christina",
"Joan", "Evelyn", "Judith", "Megan", "Andrea", "Cheryl", "Hannah", "Jacqueline", "Martha", "Gloria",
"Teresa", "Ann", "Sara", "Madison", "Frances", "Kathryn", "Janice", "Jean", "Abigail", "Alice",
"Judy", "Sophia", "Grace", "Denise", "Amber", "Doris", "Marilyn", "Danielle", "Beverly", "Isabella",
"Theresa", "Diana", "Natalie", "Brittany", "Charlotte", "Marie", "Kayla", "Alexis", "Lori", "Jane"
]
}
static firstNames {
return firstNamesMale + firstNamesFemale
}
static lastNames {
return [
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez",
"Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
"Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson",
"Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores",
"Green", "Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell", "Carter", "Roberts",
"Gomez", "Phillips", "Evans", "Turner", "Diaz", "Parker", "Cruz", "Edwards", "Collins", "Reyes",
"Stewart", "Morris", "Morales", "Murphy", "Cook", "Rogers", "Gutierrez", "Ortiz", "Morgan", "Cooper",
"Peterson", "Bailey", "Reed", "Kelly", "Howard", "Ramos", "Kim", "Cox", "Ward", "Richardson",
"Watson", "Brooks", "Chavez", "Wood", "James", "Bennett", "Gray", "Mendoza", "Ruiz", "Hughes",
"Price", "Alvarez", "Castillo", "Sanders", "Patel", "Myers", "Long", "Ross", "Foster", "Jimenez",
"Powell", "Jenkins", "Perry", "Russell", "Sullivan", "Bell", "Coleman", "Butler", "Henderson", "Barnes",
"Gonzales", "Fisher", "Vasquez", "Simmons", "Graham", "Patterson", "Jordan", "Reynolds", "Hamilton", "Graham",
"Wallace", "Cole", "West", "Stone", "Holmes", "Meyer", "Boyd", "Mills", "Warren", "Fox",
"Rose", "Rice", "Moreno", "Schmidt", "Patel", "Ferguson", "Nichols", "Herrera", "Medina", "Ryan",
"Fernandez", "Weaver", "Daniels", "Stephens", "Knight", "Hudson", "Spencer", "Elliott", "Bishop", "Craig",
"Hunter", "Hart", "Armstrong", "Carpenter", "Harvey", "Hawkins", "Harrison", "Fields", "Webb", "Tucker",
"Mason", "Porter", "Burns", "Gibson", "Ellis", "Gordon", "Kennedy", "Willis", "Chapman", "Palmer",
"Arnold", "Lane", "Dean", "Matthews", "Wagner", "Austin", "Murray", "Stanley", "Fowler", "Grant",
"Kelley", "Black", "Lynch", "Barnes", "Dunn", "Shaw", "Olson", "Simpson", "George", "Marshall",
"Watkins", "Owens", "Hunt", "Hicks", "Freeman", "Pierce", "Snyder", "Franklin", "Soto", "Newman"
]
}
static emailDomains {
return [
"gmail.com", "yahoo.com", "hotmail.com", "outlook.com", "icloud.com",
"mail.com", "protonmail.com", "aol.com", "zoho.com", "yandex.com",
"gmx.com", "fastmail.com", "tutanota.com", "inbox.com", "live.com",
"example.com", "example.org", "example.net", "test.com", "demo.com",
"company.com", "corp.com", "business.com", "enterprise.com", "work.com",
"office.com", "professional.com", "service.com", "support.com", "info.com"
]
}
static loremWords {
return [
"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "sed", "do",
"eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua", "enim",
"ad", "minim", "veniam", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "aliquip",
"ex", "ea", "commodo", "consequat", "duis", "aute", "irure", "in", "reprehenderit", "voluptate",
"velit", "esse", "cillum", "fugiat", "nulla", "pariatur", "excepteur", "sint", "occaecat", "cupidatat",
"non", "proident", "sunt", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id",
"est", "laborum", "at", "vero", "eos", "accusamus", "iusto", "odio", "dignissimos", "ducimus",
"blanditiis", "praesentium", "voluptatum", "deleniti", "atque", "corrupti", "quos", "quas", "molestias",
"excepturi", "occaecati", "cupiditate", "provident", "similique", "mollitia", "animi", "quasi", "architecto",
"beatae", "vitae", "dicta", "explicabo", "nemo", "ipsam", "quia", "voluptas", "aspernatur", "aut",
"odit", "fugit", "consequuntur", "magni", "dolores", "ratione", "sequi", "nesciunt", "neque", "porro",
"quisquam", "dolorem", "adipisci", "numquam", "eius", "modi", "tempora", "magnam", "quaerat", "rem",
"aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "quasi", "perspiciatis",
"unde", "omnis", "iste", "natus", "error", "accusantium", "doloremque", "laudantium", "totam", "recusandae",
"saepe", "eveniet", "voluptatem", "alias", "autem", "minima", "eligendi", "optio", "cumque", "nihil",
"impedit", "quo", "minus", "quod", "maxime", "placeat", "facere", "possimus", "assumenda", "repellendus",
"temporibus", "debitis", "rerum", "hic", "tenetur", "sapiente", "delectus", "reiciendis", "perferendis",
"doloribus", "asperiores", "repellat", "nam", "libero", "soluta", "nobis", "cum", "corporis", "suscipit",
"officiis", "laboriosam", "harum", "necessitatibus", "praesentium", "voluptates", "repudiandae", "fuga", "sint"
]
}
static cities {
return [
"New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego",
"Dallas", "San Jose", "Austin", "Jacksonville", "Fort Worth", "Columbus", "Charlotte", "San Francisco",
"Indianapolis", "Seattle", "Denver", "Washington", "Boston", "El Paso", "Nashville", "Detroit", "Portland",
"Memphis", "Oklahoma City", "Las Vegas", "Louisville", "Baltimore", "Milwaukee", "Albuquerque", "Tucson",
"Fresno", "Sacramento", "Kansas City", "Mesa", "Atlanta", "Omaha", "Colorado Springs", "Raleigh", "Miami",
"Long Beach", "Virginia Beach", "Oakland", "Minneapolis", "Tulsa", "Tampa", "Arlington", "New Orleans",
"Wichita", "Cleveland", "Bakersfield", "Aurora", "Anaheim", "Honolulu", "Santa Ana", "Riverside", "Corpus Christi",
"Lexington", "Henderson", "Stockton", "Saint Paul", "Cincinnati", "St. Louis", "Pittsburgh", "Greensboro",
"Lincoln", "Anchorage", "Plano", "Orlando", "Irvine", "Newark", "Durham", "Chula Vista", "Toledo", "Fort Wayne",
"St. Petersburg", "Laredo", "Jersey City", "Chandler", "Madison", "Lubbock", "Scottsdale", "Reno", "Buffalo",
"Gilbert", "Glendale", "North Las Vegas", "Winston-Salem", "Chesapeake", "Norfolk", "Fremont", "Garland",
"Irving", "Hialeah", "Richmond", "Boise", "Spokane", "Baton Rouge", "Tacoma", "San Bernardino", "Modesto",
"Fontana", "Des Moines", "Moreno Valley", "Santa Clarita", "Fayetteville", "Birmingham", "Oxnard", "Rochester",
"Port St. Lucie", "Grand Rapids", "Huntsville", "Salt Lake City", "Frisco", "Yonkers", "Amarillo", "Glendale",
"Huntington Beach", "McKinney", "Montgomery", "Augusta", "Aurora", "Akron", "Little Rock", "Tempe", "Columbus",
"Overland Park", "Grand Prairie", "Tallahassee", "Cape Coral", "Mobile", "Knoxville", "Shreveport", "Worcester",
"Ontario", "Vancouver", "Sioux Falls", "Chattanooga", "Brownsville", "Fort Lauderdale", "Providence", "Newport News",
"Rancho Cucamonga", "Santa Rosa", "Peoria", "Oceanside", "Elk Grove", "Salem", "Pembroke Pines", "Eugene", "Garden Grove",
"Cary", "Fort Collins", "Corona", "Springfield", "Jackson", "Alexandria", "Hayward", "Lancaster", "Lakewood",
"Clarksville", "Palmdale", "Salinas", "Springfield", "Hollywood", "Pasadena", "Sunnyvale", "Macon", "Kansas City",
"Pomona", "Escondido", "Killeen", "Naperville", "Joliet", "Bellevue", "Rockford", "Savannah", "Paterson", "Torrance",
"Bridgeport", "McAllen", "Mesquite", "Syracuse", "Midland", "Pasadena", "Murfreesboro", "Miramar", "Dayton", "Fullerton"
]
}
static states {
return [
["Alabama", "AL"], ["Alaska", "AK"], ["Arizona", "AZ"], ["Arkansas", "AR"], ["California", "CA"],
["Colorado", "CO"], ["Connecticut", "CT"], ["Delaware", "DE"], ["Florida", "FL"], ["Georgia", "GA"],
["Hawaii", "HI"], ["Idaho", "ID"], ["Illinois", "IL"], ["Indiana", "IN"], ["Iowa", "IA"],
["Kansas", "KS"], ["Kentucky", "KY"], ["Louisiana", "LA"], ["Maine", "ME"], ["Maryland", "MD"],
["Massachusetts", "MA"], ["Michigan", "MI"], ["Minnesota", "MN"], ["Mississippi", "MS"], ["Missouri", "MO"],
["Montana", "MT"], ["Nebraska", "NE"], ["Nevada", "NV"], ["New Hampshire", "NH"], ["New Jersey", "NJ"],
["New Mexico", "NM"], ["New York", "NY"], ["North Carolina", "NC"], ["North Dakota", "ND"], ["Ohio", "OH"],
["Oklahoma", "OK"], ["Oregon", "OR"], ["Pennsylvania", "PA"], ["Rhode Island", "RI"], ["South Carolina", "SC"],
["South Dakota", "SD"], ["Tennessee", "TN"], ["Texas", "TX"], ["Utah", "UT"], ["Vermont", "VT"],
["Virginia", "VA"], ["Washington", "WA"], ["West Virginia", "WV"], ["Wisconsin", "WI"], ["Wyoming", "WY"]
]
}
static countries {
return [
["United States", "US"], ["United Kingdom", "GB"], ["Canada", "CA"], ["Australia", "AU"], ["Germany", "DE"],
["France", "FR"], ["Italy", "IT"], ["Spain", "ES"], ["Netherlands", "NL"], ["Belgium", "BE"],
["Switzerland", "CH"], ["Austria", "AT"], ["Sweden", "SE"], ["Norway", "NO"], ["Denmark", "DK"],
["Finland", "FI"], ["Ireland", "IE"], ["Portugal", "PT"], ["Greece", "GR"], ["Poland", "PL"],
["Czech Republic", "CZ"], ["Hungary", "HU"], ["Romania", "RO"], ["Bulgaria", "BG"], ["Croatia", "HR"],
["Slovakia", "SK"], ["Slovenia", "SI"], ["Estonia", "EE"], ["Latvia", "LV"], ["Lithuania", "LT"],
["Japan", "JP"], ["China", "CN"], ["South Korea", "KR"], ["India", "IN"], ["Singapore", "SG"],
["Malaysia", "MY"], ["Thailand", "TH"], ["Indonesia", "ID"], ["Philippines", "PH"], ["Vietnam", "VN"],
["Brazil", "BR"], ["Mexico", "MX"], ["Argentina", "AR"], ["Chile", "CL"], ["Colombia", "CO"],
["Peru", "PE"], ["Venezuela", "VE"], ["Ecuador", "EC"], ["Uruguay", "UY"], ["Paraguay", "PY"],
["South Africa", "ZA"], ["Egypt", "EG"], ["Nigeria", "NG"], ["Kenya", "KE"], ["Morocco", "MA"],
["Israel", "IL"], ["Turkey", "TR"], ["Saudi Arabia", "SA"], ["United Arab Emirates", "AE"], ["Russia", "RU"],
["Ukraine", "UA"], ["New Zealand", "NZ"], ["Iceland", "IS"], ["Luxembourg", "LU"], ["Malta", "MT"]
]
}
static streetSuffixes {
return [
"Street", "Avenue", "Boulevard", "Drive", "Lane", "Road", "Way", "Place", "Court", "Circle",
"Trail", "Parkway", "Terrace", "Plaza", "Commons", "Alley", "Path", "Run", "View", "Ridge"
]
}
static streetNames {
return [
"Main", "Oak", "Maple", "Cedar", "Pine", "Elm", "Washington", "Lake", "Hill", "Park",
"River", "Spring", "Valley", "Forest", "Sunset", "Highland", "Meadow", "Church", "Mill", "Union",
"Market", "Water", "Bridge", "School", "North", "South", "East", "West", "Center", "High",
"Front", "Broad", "College", "Prospect", "Lincoln", "Franklin", "Jefferson", "Adams", "Madison", "Jackson",
"Monroe", "Wilson", "Harrison", "Cleveland", "Grant", "Lee", "King", "Queen", "Duke", "Prince",
"Cherry", "Walnut", "Chestnut", "Birch", "Willow", "Poplar", "Spruce", "Hickory", "Ash", "Beech",
"Summit", "Ridge", "Crest", "Mount", "Valley", "Canyon", "Creek", "Brook", "Falls", "Grove",
"Orchard", "Garden", "Field", "Woods", "Acres", "Estate", "Manor", "Villa", "Terrace", "Glen",
"Bay", "Harbor", "Shore", "Beach", "Ocean", "Sea", "Coral", "Palm", "Sunset", "Sunrise",
"Colonial", "Liberty", "Independence", "Victory", "Heritage", "Pioneer", "Frontier", "Commerce", "Industrial", "Technology"
]
}
static companyPrefixes {
return [
"Global", "United", "International", "National", "American", "Pacific", "Atlantic", "Northern", "Southern",
"Western", "Eastern", "Central", "Premier", "Prime", "Elite", "Apex", "Summit", "Peak", "Pinnacle",
"First", "Advanced", "Modern", "Future", "Next", "New", "Innovative", "Dynamic", "Strategic", "Smart",
"Digital", "Tech", "Cyber", "Data", "Cloud", "Net", "Web", "Quantum", "Fusion", "Synergy"
]
}
static companySuffixes {
return [
"Inc.", "LLC", "Corp.", "Ltd.", "Co.", "Group", "Holdings", "Industries", "Enterprises", "Solutions",
"Services", "Systems", "Technologies", "Partners", "Associates", "Consulting", "International", "Global", "Worldwide"
]
}
static companyNouns {
return [
"Tech", "Systems", "Solutions", "Data", "Software", "Digital", "Media", "Networks", "Communications", "Industries",
"Dynamics", "Logic", "Works", "Labs", "Innovations", "Ventures", "Capital", "Resources", "Management", "Consulting"
]
}
static jobTitles {
return [
"Software Engineer", "Product Manager", "Data Scientist", "Marketing Manager", "Sales Representative",
"Financial Analyst", "Human Resources Manager", "Operations Manager", "Project Manager", "Business Analyst",
"Accountant", "Graphic Designer", "Content Writer", "Customer Service Representative", "Administrative Assistant",
"Executive Assistant", "Office Manager", "Quality Assurance Engineer", "DevOps Engineer", "System Administrator",
"Network Engineer", "Database Administrator", "Security Analyst", "UX Designer", "UI Developer",
"Full Stack Developer", "Frontend Developer", "Backend Developer", "Mobile Developer", "Cloud Architect",
"Solutions Architect", "Technical Lead", "Engineering Manager", "Director of Engineering", "VP of Engineering",
"Chief Technology Officer", "Chief Executive Officer", "Chief Financial Officer", "Chief Operating Officer", "Chief Marketing Officer"
]
}
static jobDescriptors {
return ["Lead", "Senior", "Junior", "Associate", "Principal", "Staff", "Chief", "Head", "Director", "Manager"]
}
static productCategories {
return [
"Electronics", "Clothing", "Home & Garden", "Sports & Outdoors", "Books", "Toys & Games",
"Health & Beauty", "Automotive", "Food & Grocery", "Office Supplies", "Pet Supplies", "Jewelry",
"Music", "Movies", "Software", "Tools & Hardware", "Baby Products", "Furniture", "Art & Crafts", "Industrial"
]
}
static productAdjectives {
return [
"Incredible", "Fantastic", "Amazing", "Gorgeous", "Practical", "Sleek", "Elegant", "Rustic", "Modern",
"Handmade", "Refined", "Premium", "Ergonomic", "Intelligent", "Smart", "Licensed", "Recycled", "Unbranded", "Generic", "Tasty"
]
}
static productMaterials {
return [
"Steel", "Wooden", "Concrete", "Plastic", "Cotton", "Granite", "Rubber", "Metal", "Soft", "Fresh",
"Frozen", "Bronze", "Silver", "Gold", "Copper", "Marble", "Leather", "Silk", "Wool", "Linen"
]
}
static productNouns {
return [
"Chair", "Car", "Computer", "Keyboard", "Mouse", "Bike", "Ball", "Gloves", "Pants", "Shirt",
"Table", "Shoes", "Hat", "Towels", "Soap", "Tuna", "Chicken", "Fish", "Cheese", "Bacon",
"Pizza", "Salad", "Sausages", "Chips", "Watch", "Phone", "Tablet", "Camera", "Lamp", "Clock"
]
}
static creditCardTypes {
return [
["Visa", "4"],
["Mastercard", "5"],
["American Express", "3"],
["Discover", "6"]
]
}
static currencies {
return [
["USD", "US Dollar", "$"], ["EUR", "Euro", "€"], ["GBP", "British Pound", "£"],
["JPY", "Japanese Yen", "¥"], ["AUD", "Australian Dollar", "A$"], ["CAD", "Canadian Dollar", "C$"],
["CHF", "Swiss Franc", "CHF"], ["CNY", "Chinese Yuan", "¥"], ["INR", "Indian Rupee", "₹"],
["MXN", "Mexican Peso", "$"], ["BRL", "Brazilian Real", "R$"], ["RUB", "Russian Ruble", "₽"]
]
}
static colorNames {
return [
"Red", "Blue", "Green", "Yellow", "Orange", "Purple", "Pink", "Brown", "Black", "White",
"Gray", "Cyan", "Magenta", "Lime", "Maroon", "Navy", "Olive", "Teal", "Aqua", "Silver",
"Crimson", "Coral", "Salmon", "Tomato", "Gold", "Khaki", "Indigo", "Violet", "Plum", "Orchid",
"Tan", "Beige", "Ivory", "Lavender", "Mint", "Turquoise", "Azure", "Slate", "Charcoal", "Pearl"
]
}
static months {
return ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
}
static daysOfWeek {
return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
}
static topLevelDomains {
return ["com", "org", "net", "edu", "gov", "io", "co", "biz", "info", "me", "us", "uk", "de", "fr", "jp"]
}
static httpMethods {
return ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
}
static protocols {
return ["http", "https"]
}
static userAgents {
return [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1"
]
}
}
class Faker {
static seed(value) { FakerRandom.seed(value) }
static reset() { FakerRandom.reset() }
static randomElement(list) {
if (list.count == 0) return null
return list[FakerRandom.next(0, list.count - 1)]
}
static randomElements(list, count) {
var result = []
for (i in 0...count) {
result.add(randomElement(list))
}
return result
}
static randomInt(min, max) { FakerRandom.next(min, max) }
static randomFloat(min, max) {
var f = FakerRandom.nextFloat()
return min + (f * (max - min))
}
static randomFloat(min, max, precision) {
var f = randomFloat(min, max)
var mult = 1
for (i in 0...precision) mult = mult * 10
return (f * mult).round / mult
}
static boolean() { FakerRandom.next(0, 1) == 1 }
static bool() { boolean() }
static numerify(format) {
var result = ""
for (char in format) {
if (char == "#") {
result = result + FakerRandom.next(0, 9).toString
} else {
result = result + char
}
}
return result
}
static letterify(format) {
var letters = "abcdefghijklmnopqrstuvwxyz"
var result = ""
for (char in format) {
if (char == "?") {
result = result + letters[FakerRandom.next(0, 25)]
} else {
result = result + char
}
}
return result
}
static bothify(format) { letterify(numerify(format)) }
static firstName() { randomElement(FakerData.firstNames) }
static firstNameMale() { randomElement(FakerData.firstNamesMale) }
static firstNameFemale() { randomElement(FakerData.firstNamesFemale) }
static lastName() { randomElement(FakerData.lastNames) }
static name() { "%(firstName()) %(lastName())" }
static nameMale() { "%(firstNameMale()) %(lastName())" }
static nameFemale() { "%(firstNameFemale()) %(lastName())" }
static prefix() { randomElement(["Mr.", "Mrs.", "Ms.", "Miss", "Dr."]) }
static namePrefix() { prefix() }
static suffix() { randomElement(["Jr.", "Sr.", "I", "II", "III", "IV", "V", "MD", "PhD"]) }
static nameSuffix() { suffix() }
static gender() { randomElement(["Male", "Female"]) }
static sex() { gender() }
static username() {
var first = Str.toLower(firstName())
var last = Str.toLower(lastName())
var patterns = [
"%(first)%(last)",
"%(first).%(last)",
"%(first)_%(last)",
"%(first)%(FakerRandom.next(1, 99))",
"%(first).%(last)%(FakerRandom.next(1, 99))",
"%(first)_%(FakerRandom.next(100, 999))"
]
return randomElement(patterns)
}
static email() {
var user = username()
var domain = randomElement(FakerData.emailDomains)
return "%(user)@%(domain)"
}
static exampleEmail() {
var user = username()
var domain = randomElement(["example.com", "example.org", "example.net"])
return "%(user)@%(domain)"
}
static city() { randomElement(FakerData.cities) }
static state() {
var s = randomElement(FakerData.states)
return s[0]
}
static stateAbbr() {
var s = randomElement(FakerData.states)
return s[1]
}
static stateFull() { state() }
static stateName() { state() }
static country() {
var c = randomElement(FakerData.countries)
return c[0]
}
static countryCode() {
var c = randomElement(FakerData.countries)
return c[1]
}
static countryName() { country() }
static zipCode() { numerify("#####") }
static postcode() { zipCode() }
static buildingNumber() { FakerRandom.next(1, 9999).toString }
static streetName() {
var name = randomElement(FakerData.streetNames)
var suffix = randomElement(FakerData.streetSuffixes)
return "%(name) %(suffix)"
}
static streetAddress() { "%(buildingNumber()) %(streetName())" }
static address() {
return "%(streetAddress()), %(city()), %(stateAbbr()) %(zipCode())"
}
static latitude() { randomFloat(-90, 90, 6) }
static longitude() { randomFloat(-180, 180, 6) }
static latitude(min, max) { randomFloat(min, max, 6) }
static longitude(min, max) { randomFloat(min, max, 6) }
static ipv4() {
var a = FakerRandom.next(1, 255)
var b = FakerRandom.next(0, 255)
var c = FakerRandom.next(0, 255)
var d = FakerRandom.next(1, 254)
return "%(a).%(b).%(c).%(d)"
}
static ipv6() {
var parts = []
for (i in 0...8) {
var hex = ""
for (j in 0...4) {
var digit = FakerRandom.next(0, 15)
hex = hex + "0123456789abcdef"[digit]
}
parts.add(hex)
}
return parts.join(":")
}
static macAddress() {
var parts = []
for (i in 0...6) {
var hex = ""
for (j in 0...2) {
var digit = FakerRandom.next(0, 15)
hex = hex + "0123456789abcdef"[digit]
}
parts.add(hex)
}
return parts.join(":")
}
static port() { FakerRandom.next(1, 65535) }
static tld() { randomElement(FakerData.topLevelDomains) }
static topLevelDomain() { tld() }
static domainWord() {
var words = [Str.toLower(firstName()), Str.toLower(lastName()), Str.toLower(randomElement(FakerData.companyNouns))]
return randomElement(words)
}
static domainName() { "%(domainWord()).%(tld())" }
static protocol() { randomElement(FakerData.protocols) }
static url() { "%(protocol())://%(domainName())" }
static password() { password(12) }
static password(length) {
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#^&*-_+="
var result = ""
for (i in 0...length) {
result = result + chars[FakerRandom.next(0, chars.count - 1)]
}
return result
}
static httpMethod() { randomElement(FakerData.httpMethods) }
static httpStatusCode() { randomElement([200, 201, 204, 301, 302, 400, 401, 403, 404, 500, 502, 503]) }
static userAgent() { randomElement(FakerData.userAgents) }
static hostname() { "%(domainWord())-%(FakerRandom.next(1, 99)).%(domainName())" }
static uuid() { Uuid.v4() }
static md5() { Hash.toHex(Hash.md5(password(32))) }
static sha1() { Hash.toHex(Hash.sha1(password(32))) }
static sha256() { Hash.toHex(Hash.sha256(password(32))) }
static hexColor() {
var hex = ""
for (i in 0...6) {
var digit = FakerRandom.next(0, 15)
hex = hex + "0123456789abcdef"[digit]
}
return "#" + hex
}
static rgbColor() {
var r = FakerRandom.next(0, 255)
var g = FakerRandom.next(0, 255)
var b = FakerRandom.next(0, 255)
return "rgb(%(r), %(g), %(b))"
}
static rgb() { rgbColor() }
static rgbaCssColor() {
var r = FakerRandom.next(0, 255)
var g = FakerRandom.next(0, 255)
var b = FakerRandom.next(0, 255)
var a = randomFloat(0, 1, 2)
return "rgba(%(r), %(g), %(b), %(a))"
}
static colorName() { randomElement(FakerData.colorNames) }
static date() {
var now = DateTime.now()
var start = now - Duration.fromDays(365 * 10)
var end = now
return dateBetween(start, end)
}
static dateTime() {
var now = DateTime.now()
var start = now - Duration.fromDays(365 * 10)
var range = 365 * 10 * 24 * 60 * 60
var offset = FakerRandom.next(0, range)
var dt = start + Duration.fromSeconds(offset)
return dt.toIso8601
}
static pastDate() { pastDate(1) }
static pastDate(years) {
var now = DateTime.now()
var start = now - Duration.fromDays(365 * years)
return dateBetween(start, now)
}
static futureDate() { futureDate(1) }
static futureDate(years) {
var now = DateTime.now()
var end = now + Duration.fromDays(365 * years)
return dateBetween(now, end)
}
static dateBetween(start, end) {
var startTs = start.timestamp
var endTs = end.timestamp
if (endTs <= startTs) return start.format("\%Y-\%m-\%d")
var range = endTs - startTs
var offset = FakerRandom.next(0, range.floor)
var dt = DateTime.fromTimestamp(startTs + offset)
return dt.format("\%Y-\%m-\%d")
}
static dateOfBirth() { dateOfBirth(18, 80) }
static dateOfBirth(minAge, maxAge) {
var now = DateTime.now()
var end = now - Duration.fromDays(365 * minAge)
var start = now - Duration.fromDays(365 * maxAge)
return dateBetween(start, end)
}
static year() { FakerRandom.next(1950, 2025) }
static month() { FakerRandom.next(1, 12) }
static monthName() { FakerData.months[FakerRandom.next(0, 11)] }
static dayOfWeek() { randomElement(FakerData.daysOfWeek) }
static dayOfMonth() { FakerRandom.next(1, 28) }
static hour() { FakerRandom.next(0, 23) }
static minute() { FakerRandom.next(0, 59) }
static second() { FakerRandom.next(0, 59) }
static amPm() { randomElement(["AM", "PM"]) }
static time() {
var h = hour()
var m = minute()
var s = second()
return "%(h < 10 ? "0" : "")%(h):%(m < 10 ? "0" : "")%(m):%(s < 10 ? "0" : "")%(s)"
}
static word() { randomElement(FakerData.loremWords) }
static words(count) {
var result = []
for (i in 0...count) {
result.add(word())
}
return result
}
static sentence() { sentence(FakerRandom.next(5, 12)) }
static sentence(wordCount) {
var w = words(wordCount)
var first = w[0]
w[0] = first[0].bytes[0] >= 97 && first[0].bytes[0] <= 122 ? String.fromCodePoint(first[0].bytes[0] - 32) + first[1..-1] : first
return w.join(" ") + "."
}
static sentences(count) {
var result = []
for (i in 0...count) {
result.add(sentence())
}
return result.join(" ")
}
static paragraph() { paragraph(FakerRandom.next(3, 6)) }
static paragraph(sentenceCount) { sentences(sentenceCount) }
static paragraphs(count) {
var result = []
for (i in 0...count) {
result.add(paragraph())
}
return result.join("\n\n")
}
static text() { text(200) }
static text(maxChars) {
var result = ""
while (result.count < maxChars) {
result = result + sentence() + " "
}
return result[0...maxChars]
}
static slug() { slug(3) }
static slug(wordCount) {
var w = words(wordCount)
var lower = []
for (word in w) {
lower.add(Str.toLower(word))
}
return lower.join("-")
}
static company() {
var patterns = [
"%(lastName()) %(randomElement(FakerData.companySuffixes))",
"%(lastName())-%(lastName())",
"%(randomElement(FakerData.companyPrefixes)) %(randomElement(FakerData.companyNouns))",
"%(lastName()) %(randomElement(FakerData.companyNouns))"
]
return randomElement(patterns)
}
static companyName() { company() }
static companySuffix() { randomElement(FakerData.companySuffixes) }
static job() { randomElement(FakerData.jobTitles) }
static jobTitle() { job() }
static jobDescriptor() { randomElement(FakerData.jobDescriptors) }
static product() {
var adj = randomElement(FakerData.productAdjectives)
var mat = randomElement(FakerData.productMaterials)
var noun = randomElement(FakerData.productNouns)
return "%(adj) %(mat) %(noun)"
}
static productName() { product() }
static productCategory() { randomElement(FakerData.productCategories) }
static price() { price(1, 1000) }
static price(min, max) { randomFloat(min, max, 2) }
static currency() {
var c = randomElement(FakerData.currencies)
return c[0]
}
static currencyName() {
var c = randomElement(FakerData.currencies)
return c[1]
}
static currencySymbol() {
var c = randomElement(FakerData.currencies)
return c[2]
}
static creditCardType() {
var c = randomElement(FakerData.creditCardTypes)
return c[0]
}
static creditCardNumber() {
var type = randomElement(FakerData.creditCardTypes)
var prefix = type[1]
return prefix + numerify("###-####-####-###")
}
static creditCardCVV() { numerify("###") }
static creditCardExpiryDate() {
var m = FakerRandom.next(1, 12)
var y = FakerRandom.next(25, 30)
return "%(m < 10 ? "0" : "")%(m)/%(y)"
}
static phoneNumber() { numerify("(###) ###-####") }
static phone() { phoneNumber() }
static iban() {
var country = randomElement(["DE", "FR", "GB", "ES", "IT", "NL"])
var check = numerify("##")
var bban = numerify("####################")
return country + check + bban
}
static accountNumber() { accountNumber(10) }
static accountNumber(length) {
var result = ""
for (i in 0...length) {
result = result + FakerRandom.next(0, 9).toString
}
return result
}
static routingNumber() { numerify("#########") }
static fileName() {
var extensions = ["txt", "pdf", "doc", "xls", "jpg", "png", "gif", "mp3", "mp4", "zip"]
var name = Str.toLower(word())
var ext = randomElement(extensions)
return "%(name).%(ext)"
}
static fileExtension() { randomElement(["txt", "pdf", "doc", "xls", "jpg", "png", "gif", "mp3", "mp4", "zip"]) }
static mimeType() {
return randomElement([
"application/json", "application/xml", "application/pdf", "application/zip",
"text/html", "text/plain", "text/css", "text/javascript",
"image/jpeg", "image/png", "image/gif", "image/svg+xml",
"audio/mpeg", "video/mp4"
])
}
static semver() { "%(FakerRandom.next(0, 9)).%(FakerRandom.next(0, 20)).%(FakerRandom.next(0, 50))" }
static digit() { FakerRandom.next(0, 9) }
static randomDigit() { digit() }
static digits(count) {
var result = []
for (i in 0...count) {
result.add(digit())
}
return result
}
static letter() { "abcdefghijklmnopqrstuvwxyz"[FakerRandom.next(0, 25)] }
static letters(count) {
var result = ""
for (i in 0...count) {
result = result + letter()
}
return result
}
static shuffle(list) {
var result = list.toList
for (i in (result.count - 1)..1) {
var j = FakerRandom.next(0, i)
var temp = result[i]
result[i] = result[j]
result[j] = temp
}
return result
}
static profile() {
return {
"username": username(),
"name": name(),
"email": email(),
"address": address(),
"phone": phoneNumber(),
"job": jobTitle(),
"company": company(),
"birthdate": dateOfBirth()
}
}
static simpleProfile() {
return {
"username": username(),
"name": name(),
"email": email(),
"address": address()
}
}
static locale() { "en_US" }
}

894
src/module/faker.wren.inc Normal file
View File

@ -0,0 +1,894 @@
// Please do not edit this file. It has been generated automatically
// from `src/module/faker.wren` using `util/wren_to_c_string.py`
static const char* fakerModuleSource =
"// retoor <retoor@molodetz.nl>\n"
"\n"
"import \"crypto\" for Crypto, Hash\n"
"import \"uuid\" for Uuid\n"
"import \"datetime\" for DateTime, Duration\n"
"import \"strutil\" for Str\n"
"\n"
"class FakerRandom {\n"
" static seed_ { __seed }\n"
" static state_ { __state }\n"
"\n"
" static seed(value) {\n"
" __seed = value\n"
" __state = value\n"
" }\n"
"\n"
" static reset() {\n"
" __seed = null\n"
" __state = null\n"
" }\n"
"\n"
" static next(min, max) {\n"
" if (__seed == null) {\n"
" return Crypto.randomInt(min, max + 1)\n"
" }\n"
" if (__state == null) __state = __seed\n"
" __state = ((__state * 1103515245) + 12345) % 2147483648\n"
" var range = max - min + 1\n"
" return min + (__state % range)\n"
" }\n"
"\n"
" static nextFloat() {\n"
" if (__seed == null) {\n"
" var bytes = Crypto.randomBytes(4)\n"
" var value = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]\n"
" if (value < 0) value = -value\n"
" return value / 2147483648\n"
" }\n"
" if (__state == null) __state = __seed\n"
" __state = ((__state * 1103515245) + 12345) % 2147483648\n"
" return __state / 2147483648\n"
" }\n"
"}\n"
"\n"
"class FakerData {\n"
" static firstNamesMale {\n"
" return [\n"
" \"James\", \"John\", \"Robert\", \"Michael\", \"William\", \"David\", \"Richard\", \"Joseph\", \"Thomas\", \"Charles\",\n"
" \"Christopher\", \"Daniel\", \"Matthew\", \"Anthony\", \"Mark\", \"Donald\", \"Steven\", \"Paul\", \"Andrew\", \"Joshua\",\n"
" \"Kenneth\", \"Kevin\", \"Brian\", \"George\", \"Timothy\", \"Ronald\", \"Edward\", \"Jason\", \"Jeffrey\", \"Ryan\",\n"
" \"Jacob\", \"Gary\", \"Nicholas\", \"Eric\", \"Jonathan\", \"Stephen\", \"Larry\", \"Justin\", \"Scott\", \"Brandon\",\n"
" \"Benjamin\", \"Samuel\", \"Raymond\", \"Gregory\", \"Frank\", \"Alexander\", \"Patrick\", \"Jack\", \"Dennis\", \"Jerry\",\n"
" \"Tyler\", \"Aaron\", \"Jose\", \"Adam\", \"Nathan\", \"Henry\", \"Douglas\", \"Zachary\", \"Peter\", \"Kyle\",\n"
" \"Noah\", \"Ethan\", \"Jeremy\", \"Walter\", \"Christian\", \"Keith\", \"Roger\", \"Terry\", \"Austin\", \"Sean\",\n"
" \"Gerald\", \"Carl\", \"Harold\", \"Dylan\", \"Arthur\", \"Lawrence\", \"Jordan\", \"Jesse\", \"Bryan\", \"Billy\",\n"
" \"Bruce\", \"Gabriel\", \"Joe\", \"Logan\", \"Albert\", \"Willie\", \"Alan\", \"Vincent\", \"Eugene\", \"Russell\",\n"
" \"Elijah\", \"Randy\", \"Philip\", \"Harry\", \"Howard\", \"Roy\", \"Louis\", \"Russell\", \"Bobby\", \"Johnny\"\n"
" ]\n"
" }\n"
"\n"
" static firstNamesFemale {\n"
" return [\n"
" \"Mary\", \"Patricia\", \"Jennifer\", \"Linda\", \"Barbara\", \"Elizabeth\", \"Susan\", \"Jessica\", \"Sarah\", \"Karen\",\n"
" \"Lisa\", \"Nancy\", \"Betty\", \"Margaret\", \"Sandra\", \"Ashley\", \"Kimberly\", \"Emily\", \"Donna\", \"Michelle\",\n"
" \"Dorothy\", \"Carol\", \"Amanda\", \"Melissa\", \"Deborah\", \"Stephanie\", \"Rebecca\", \"Sharon\", \"Laura\", \"Cynthia\",\n"
" \"Kathleen\", \"Amy\", \"Angela\", \"Shirley\", \"Anna\", \"Brenda\", \"Pamela\", \"Emma\", \"Nicole\", \"Helen\",\n"
" \"Samantha\", \"Katherine\", \"Christine\", \"Debra\", \"Rachel\", \"Carolyn\", \"Janet\", \"Catherine\", \"Maria\", \"Heather\",\n"
" \"Diane\", \"Ruth\", \"Julie\", \"Olivia\", \"Joyce\", \"Virginia\", \"Victoria\", \"Kelly\", \"Lauren\", \"Christina\",\n"
" \"Joan\", \"Evelyn\", \"Judith\", \"Megan\", \"Andrea\", \"Cheryl\", \"Hannah\", \"Jacqueline\", \"Martha\", \"Gloria\",\n"
" \"Teresa\", \"Ann\", \"Sara\", \"Madison\", \"Frances\", \"Kathryn\", \"Janice\", \"Jean\", \"Abigail\", \"Alice\",\n"
" \"Judy\", \"Sophia\", \"Grace\", \"Denise\", \"Amber\", \"Doris\", \"Marilyn\", \"Danielle\", \"Beverly\", \"Isabella\",\n"
" \"Theresa\", \"Diana\", \"Natalie\", \"Brittany\", \"Charlotte\", \"Marie\", \"Kayla\", \"Alexis\", \"Lori\", \"Jane\"\n"
" ]\n"
" }\n"
"\n"
" static firstNames {\n"
" return firstNamesMale + firstNamesFemale\n"
" }\n"
"\n"
" static lastNames {\n"
" return [\n"
" \"Smith\", \"Johnson\", \"Williams\", \"Brown\", \"Jones\", \"Garcia\", \"Miller\", \"Davis\", \"Rodriguez\", \"Martinez\",\n"
" \"Hernandez\", \"Lopez\", \"Gonzalez\", \"Wilson\", \"Anderson\", \"Thomas\", \"Taylor\", \"Moore\", \"Jackson\", \"Martin\",\n"
" \"Lee\", \"Perez\", \"Thompson\", \"White\", \"Harris\", \"Sanchez\", \"Clark\", \"Ramirez\", \"Lewis\", \"Robinson\",\n"
" \"Walker\", \"Young\", \"Allen\", \"King\", \"Wright\", \"Scott\", \"Torres\", \"Nguyen\", \"Hill\", \"Flores\",\n"
" \"Green\", \"Adams\", \"Nelson\", \"Baker\", \"Hall\", \"Rivera\", \"Campbell\", \"Mitchell\", \"Carter\", \"Roberts\",\n"
" \"Gomez\", \"Phillips\", \"Evans\", \"Turner\", \"Diaz\", \"Parker\", \"Cruz\", \"Edwards\", \"Collins\", \"Reyes\",\n"
" \"Stewart\", \"Morris\", \"Morales\", \"Murphy\", \"Cook\", \"Rogers\", \"Gutierrez\", \"Ortiz\", \"Morgan\", \"Cooper\",\n"
" \"Peterson\", \"Bailey\", \"Reed\", \"Kelly\", \"Howard\", \"Ramos\", \"Kim\", \"Cox\", \"Ward\", \"Richardson\",\n"
" \"Watson\", \"Brooks\", \"Chavez\", \"Wood\", \"James\", \"Bennett\", \"Gray\", \"Mendoza\", \"Ruiz\", \"Hughes\",\n"
" \"Price\", \"Alvarez\", \"Castillo\", \"Sanders\", \"Patel\", \"Myers\", \"Long\", \"Ross\", \"Foster\", \"Jimenez\",\n"
" \"Powell\", \"Jenkins\", \"Perry\", \"Russell\", \"Sullivan\", \"Bell\", \"Coleman\", \"Butler\", \"Henderson\", \"Barnes\",\n"
" \"Gonzales\", \"Fisher\", \"Vasquez\", \"Simmons\", \"Graham\", \"Patterson\", \"Jordan\", \"Reynolds\", \"Hamilton\", \"Graham\",\n"
" \"Wallace\", \"Cole\", \"West\", \"Stone\", \"Holmes\", \"Meyer\", \"Boyd\", \"Mills\", \"Warren\", \"Fox\",\n"
" \"Rose\", \"Rice\", \"Moreno\", \"Schmidt\", \"Patel\", \"Ferguson\", \"Nichols\", \"Herrera\", \"Medina\", \"Ryan\",\n"
" \"Fernandez\", \"Weaver\", \"Daniels\", \"Stephens\", \"Knight\", \"Hudson\", \"Spencer\", \"Elliott\", \"Bishop\", \"Craig\",\n"
" \"Hunter\", \"Hart\", \"Armstrong\", \"Carpenter\", \"Harvey\", \"Hawkins\", \"Harrison\", \"Fields\", \"Webb\", \"Tucker\",\n"
" \"Mason\", \"Porter\", \"Burns\", \"Gibson\", \"Ellis\", \"Gordon\", \"Kennedy\", \"Willis\", \"Chapman\", \"Palmer\",\n"
" \"Arnold\", \"Lane\", \"Dean\", \"Matthews\", \"Wagner\", \"Austin\", \"Murray\", \"Stanley\", \"Fowler\", \"Grant\",\n"
" \"Kelley\", \"Black\", \"Lynch\", \"Barnes\", \"Dunn\", \"Shaw\", \"Olson\", \"Simpson\", \"George\", \"Marshall\",\n"
" \"Watkins\", \"Owens\", \"Hunt\", \"Hicks\", \"Freeman\", \"Pierce\", \"Snyder\", \"Franklin\", \"Soto\", \"Newman\"\n"
" ]\n"
" }\n"
"\n"
" static emailDomains {\n"
" return [\n"
" \"gmail.com\", \"yahoo.com\", \"hotmail.com\", \"outlook.com\", \"icloud.com\",\n"
" \"mail.com\", \"protonmail.com\", \"aol.com\", \"zoho.com\", \"yandex.com\",\n"
" \"gmx.com\", \"fastmail.com\", \"tutanota.com\", \"inbox.com\", \"live.com\",\n"
" \"example.com\", \"example.org\", \"example.net\", \"test.com\", \"demo.com\",\n"
" \"company.com\", \"corp.com\", \"business.com\", \"enterprise.com\", \"work.com\",\n"
" \"office.com\", \"professional.com\", \"service.com\", \"support.com\", \"info.com\"\n"
" ]\n"
" }\n"
"\n"
" static loremWords {\n"
" return [\n"
" \"lorem\", \"ipsum\", \"dolor\", \"sit\", \"amet\", \"consectetur\", \"adipiscing\", \"elit\", \"sed\", \"do\",\n"
" \"eiusmod\", \"tempor\", \"incididunt\", \"ut\", \"labore\", \"et\", \"dolore\", \"magna\", \"aliqua\", \"enim\",\n"
" \"ad\", \"minim\", \"veniam\", \"quis\", \"nostrud\", \"exercitation\", \"ullamco\", \"laboris\", \"nisi\", \"aliquip\",\n"
" \"ex\", \"ea\", \"commodo\", \"consequat\", \"duis\", \"aute\", \"irure\", \"in\", \"reprehenderit\", \"voluptate\",\n"
" \"velit\", \"esse\", \"cillum\", \"fugiat\", \"nulla\", \"pariatur\", \"excepteur\", \"sint\", \"occaecat\", \"cupidatat\",\n"
" \"non\", \"proident\", \"sunt\", \"culpa\", \"qui\", \"officia\", \"deserunt\", \"mollit\", \"anim\", \"id\",\n"
" \"est\", \"laborum\", \"at\", \"vero\", \"eos\", \"accusamus\", \"iusto\", \"odio\", \"dignissimos\", \"ducimus\",\n"
" \"blanditiis\", \"praesentium\", \"voluptatum\", \"deleniti\", \"atque\", \"corrupti\", \"quos\", \"quas\", \"molestias\",\n"
" \"excepturi\", \"occaecati\", \"cupiditate\", \"provident\", \"similique\", \"mollitia\", \"animi\", \"quasi\", \"architecto\",\n"
" \"beatae\", \"vitae\", \"dicta\", \"explicabo\", \"nemo\", \"ipsam\", \"quia\", \"voluptas\", \"aspernatur\", \"aut\",\n"
" \"odit\", \"fugit\", \"consequuntur\", \"magni\", \"dolores\", \"ratione\", \"sequi\", \"nesciunt\", \"neque\", \"porro\",\n"
" \"quisquam\", \"dolorem\", \"adipisci\", \"numquam\", \"eius\", \"modi\", \"tempora\", \"magnam\", \"quaerat\", \"rem\",\n"
" \"aperiam\", \"eaque\", \"ipsa\", \"quae\", \"ab\", \"illo\", \"inventore\", \"veritatis\", \"quasi\", \"perspiciatis\",\n"
" \"unde\", \"omnis\", \"iste\", \"natus\", \"error\", \"accusantium\", \"doloremque\", \"laudantium\", \"totam\", \"recusandae\",\n"
" \"saepe\", \"eveniet\", \"voluptatem\", \"alias\", \"autem\", \"minima\", \"eligendi\", \"optio\", \"cumque\", \"nihil\",\n"
" \"impedit\", \"quo\", \"minus\", \"quod\", \"maxime\", \"placeat\", \"facere\", \"possimus\", \"assumenda\", \"repellendus\",\n"
" \"temporibus\", \"debitis\", \"rerum\", \"hic\", \"tenetur\", \"sapiente\", \"delectus\", \"reiciendis\", \"perferendis\",\n"
" \"doloribus\", \"asperiores\", \"repellat\", \"nam\", \"libero\", \"soluta\", \"nobis\", \"cum\", \"corporis\", \"suscipit\",\n"
" \"officiis\", \"laboriosam\", \"harum\", \"necessitatibus\", \"praesentium\", \"voluptates\", \"repudiandae\", \"fuga\", \"sint\"\n"
" ]\n"
" }\n"
"\n"
" static cities {\n"
" return [\n"
" \"New York\", \"Los Angeles\", \"Chicago\", \"Houston\", \"Phoenix\", \"Philadelphia\", \"San Antonio\", \"San Diego\",\n"
" \"Dallas\", \"San Jose\", \"Austin\", \"Jacksonville\", \"Fort Worth\", \"Columbus\", \"Charlotte\", \"San Francisco\",\n"
" \"Indianapolis\", \"Seattle\", \"Denver\", \"Washington\", \"Boston\", \"El Paso\", \"Nashville\", \"Detroit\", \"Portland\",\n"
" \"Memphis\", \"Oklahoma City\", \"Las Vegas\", \"Louisville\", \"Baltimore\", \"Milwaukee\", \"Albuquerque\", \"Tucson\",\n"
" \"Fresno\", \"Sacramento\", \"Kansas City\", \"Mesa\", \"Atlanta\", \"Omaha\", \"Colorado Springs\", \"Raleigh\", \"Miami\",\n"
" \"Long Beach\", \"Virginia Beach\", \"Oakland\", \"Minneapolis\", \"Tulsa\", \"Tampa\", \"Arlington\", \"New Orleans\",\n"
" \"Wichita\", \"Cleveland\", \"Bakersfield\", \"Aurora\", \"Anaheim\", \"Honolulu\", \"Santa Ana\", \"Riverside\", \"Corpus Christi\",\n"
" \"Lexington\", \"Henderson\", \"Stockton\", \"Saint Paul\", \"Cincinnati\", \"St. Louis\", \"Pittsburgh\", \"Greensboro\",\n"
" \"Lincoln\", \"Anchorage\", \"Plano\", \"Orlando\", \"Irvine\", \"Newark\", \"Durham\", \"Chula Vista\", \"Toledo\", \"Fort Wayne\",\n"
" \"St. Petersburg\", \"Laredo\", \"Jersey City\", \"Chandler\", \"Madison\", \"Lubbock\", \"Scottsdale\", \"Reno\", \"Buffalo\",\n"
" \"Gilbert\", \"Glendale\", \"North Las Vegas\", \"Winston-Salem\", \"Chesapeake\", \"Norfolk\", \"Fremont\", \"Garland\",\n"
" \"Irving\", \"Hialeah\", \"Richmond\", \"Boise\", \"Spokane\", \"Baton Rouge\", \"Tacoma\", \"San Bernardino\", \"Modesto\",\n"
" \"Fontana\", \"Des Moines\", \"Moreno Valley\", \"Santa Clarita\", \"Fayetteville\", \"Birmingham\", \"Oxnard\", \"Rochester\",\n"
" \"Port St. Lucie\", \"Grand Rapids\", \"Huntsville\", \"Salt Lake City\", \"Frisco\", \"Yonkers\", \"Amarillo\", \"Glendale\",\n"
" \"Huntington Beach\", \"McKinney\", \"Montgomery\", \"Augusta\", \"Aurora\", \"Akron\", \"Little Rock\", \"Tempe\", \"Columbus\",\n"
" \"Overland Park\", \"Grand Prairie\", \"Tallahassee\", \"Cape Coral\", \"Mobile\", \"Knoxville\", \"Shreveport\", \"Worcester\",\n"
" \"Ontario\", \"Vancouver\", \"Sioux Falls\", \"Chattanooga\", \"Brownsville\", \"Fort Lauderdale\", \"Providence\", \"Newport News\",\n"
" \"Rancho Cucamonga\", \"Santa Rosa\", \"Peoria\", \"Oceanside\", \"Elk Grove\", \"Salem\", \"Pembroke Pines\", \"Eugene\", \"Garden Grove\",\n"
" \"Cary\", \"Fort Collins\", \"Corona\", \"Springfield\", \"Jackson\", \"Alexandria\", \"Hayward\", \"Lancaster\", \"Lakewood\",\n"
" \"Clarksville\", \"Palmdale\", \"Salinas\", \"Springfield\", \"Hollywood\", \"Pasadena\", \"Sunnyvale\", \"Macon\", \"Kansas City\",\n"
" \"Pomona\", \"Escondido\", \"Killeen\", \"Naperville\", \"Joliet\", \"Bellevue\", \"Rockford\", \"Savannah\", \"Paterson\", \"Torrance\",\n"
" \"Bridgeport\", \"McAllen\", \"Mesquite\", \"Syracuse\", \"Midland\", \"Pasadena\", \"Murfreesboro\", \"Miramar\", \"Dayton\", \"Fullerton\"\n"
" ]\n"
" }\n"
"\n"
" static states {\n"
" return [\n"
" [\"Alabama\", \"AL\"], [\"Alaska\", \"AK\"], [\"Arizona\", \"AZ\"], [\"Arkansas\", \"AR\"], [\"California\", \"CA\"],\n"
" [\"Colorado\", \"CO\"], [\"Connecticut\", \"CT\"], [\"Delaware\", \"DE\"], [\"Florida\", \"FL\"], [\"Georgia\", \"GA\"],\n"
" [\"Hawaii\", \"HI\"], [\"Idaho\", \"ID\"], [\"Illinois\", \"IL\"], [\"Indiana\", \"IN\"], [\"Iowa\", \"IA\"],\n"
" [\"Kansas\", \"KS\"], [\"Kentucky\", \"KY\"], [\"Louisiana\", \"LA\"], [\"Maine\", \"ME\"], [\"Maryland\", \"MD\"],\n"
" [\"Massachusetts\", \"MA\"], [\"Michigan\", \"MI\"], [\"Minnesota\", \"MN\"], [\"Mississippi\", \"MS\"], [\"Missouri\", \"MO\"],\n"
" [\"Montana\", \"MT\"], [\"Nebraska\", \"NE\"], [\"Nevada\", \"NV\"], [\"New Hampshire\", \"NH\"], [\"New Jersey\", \"NJ\"],\n"
" [\"New Mexico\", \"NM\"], [\"New York\", \"NY\"], [\"North Carolina\", \"NC\"], [\"North Dakota\", \"ND\"], [\"Ohio\", \"OH\"],\n"
" [\"Oklahoma\", \"OK\"], [\"Oregon\", \"OR\"], [\"Pennsylvania\", \"PA\"], [\"Rhode Island\", \"RI\"], [\"South Carolina\", \"SC\"],\n"
" [\"South Dakota\", \"SD\"], [\"Tennessee\", \"TN\"], [\"Texas\", \"TX\"], [\"Utah\", \"UT\"], [\"Vermont\", \"VT\"],\n"
" [\"Virginia\", \"VA\"], [\"Washington\", \"WA\"], [\"West Virginia\", \"WV\"], [\"Wisconsin\", \"WI\"], [\"Wyoming\", \"WY\"]\n"
" ]\n"
" }\n"
"\n"
" static countries {\n"
" return [\n"
" [\"United States\", \"US\"], [\"United Kingdom\", \"GB\"], [\"Canada\", \"CA\"], [\"Australia\", \"AU\"], [\"Germany\", \"DE\"],\n"
" [\"France\", \"FR\"], [\"Italy\", \"IT\"], [\"Spain\", \"ES\"], [\"Netherlands\", \"NL\"], [\"Belgium\", \"BE\"],\n"
" [\"Switzerland\", \"CH\"], [\"Austria\", \"AT\"], [\"Sweden\", \"SE\"], [\"Norway\", \"NO\"], [\"Denmark\", \"DK\"],\n"
" [\"Finland\", \"FI\"], [\"Ireland\", \"IE\"], [\"Portugal\", \"PT\"], [\"Greece\", \"GR\"], [\"Poland\", \"PL\"],\n"
" [\"Czech Republic\", \"CZ\"], [\"Hungary\", \"HU\"], [\"Romania\", \"RO\"], [\"Bulgaria\", \"BG\"], [\"Croatia\", \"HR\"],\n"
" [\"Slovakia\", \"SK\"], [\"Slovenia\", \"SI\"], [\"Estonia\", \"EE\"], [\"Latvia\", \"LV\"], [\"Lithuania\", \"LT\"],\n"
" [\"Japan\", \"JP\"], [\"China\", \"CN\"], [\"South Korea\", \"KR\"], [\"India\", \"IN\"], [\"Singapore\", \"SG\"],\n"
" [\"Malaysia\", \"MY\"], [\"Thailand\", \"TH\"], [\"Indonesia\", \"ID\"], [\"Philippines\", \"PH\"], [\"Vietnam\", \"VN\"],\n"
" [\"Brazil\", \"BR\"], [\"Mexico\", \"MX\"], [\"Argentina\", \"AR\"], [\"Chile\", \"CL\"], [\"Colombia\", \"CO\"],\n"
" [\"Peru\", \"PE\"], [\"Venezuela\", \"VE\"], [\"Ecuador\", \"EC\"], [\"Uruguay\", \"UY\"], [\"Paraguay\", \"PY\"],\n"
" [\"South Africa\", \"ZA\"], [\"Egypt\", \"EG\"], [\"Nigeria\", \"NG\"], [\"Kenya\", \"KE\"], [\"Morocco\", \"MA\"],\n"
" [\"Israel\", \"IL\"], [\"Turkey\", \"TR\"], [\"Saudi Arabia\", \"SA\"], [\"United Arab Emirates\", \"AE\"], [\"Russia\", \"RU\"],\n"
" [\"Ukraine\", \"UA\"], [\"New Zealand\", \"NZ\"], [\"Iceland\", \"IS\"], [\"Luxembourg\", \"LU\"], [\"Malta\", \"MT\"]\n"
" ]\n"
" }\n"
"\n"
" static streetSuffixes {\n"
" return [\n"
" \"Street\", \"Avenue\", \"Boulevard\", \"Drive\", \"Lane\", \"Road\", \"Way\", \"Place\", \"Court\", \"Circle\",\n"
" \"Trail\", \"Parkway\", \"Terrace\", \"Plaza\", \"Commons\", \"Alley\", \"Path\", \"Run\", \"View\", \"Ridge\"\n"
" ]\n"
" }\n"
"\n"
" static streetNames {\n"
" return [\n"
" \"Main\", \"Oak\", \"Maple\", \"Cedar\", \"Pine\", \"Elm\", \"Washington\", \"Lake\", \"Hill\", \"Park\",\n"
" \"River\", \"Spring\", \"Valley\", \"Forest\", \"Sunset\", \"Highland\", \"Meadow\", \"Church\", \"Mill\", \"Union\",\n"
" \"Market\", \"Water\", \"Bridge\", \"School\", \"North\", \"South\", \"East\", \"West\", \"Center\", \"High\",\n"
" \"Front\", \"Broad\", \"College\", \"Prospect\", \"Lincoln\", \"Franklin\", \"Jefferson\", \"Adams\", \"Madison\", \"Jackson\",\n"
" \"Monroe\", \"Wilson\", \"Harrison\", \"Cleveland\", \"Grant\", \"Lee\", \"King\", \"Queen\", \"Duke\", \"Prince\",\n"
" \"Cherry\", \"Walnut\", \"Chestnut\", \"Birch\", \"Willow\", \"Poplar\", \"Spruce\", \"Hickory\", \"Ash\", \"Beech\",\n"
" \"Summit\", \"Ridge\", \"Crest\", \"Mount\", \"Valley\", \"Canyon\", \"Creek\", \"Brook\", \"Falls\", \"Grove\",\n"
" \"Orchard\", \"Garden\", \"Field\", \"Woods\", \"Acres\", \"Estate\", \"Manor\", \"Villa\", \"Terrace\", \"Glen\",\n"
" \"Bay\", \"Harbor\", \"Shore\", \"Beach\", \"Ocean\", \"Sea\", \"Coral\", \"Palm\", \"Sunset\", \"Sunrise\",\n"
" \"Colonial\", \"Liberty\", \"Independence\", \"Victory\", \"Heritage\", \"Pioneer\", \"Frontier\", \"Commerce\", \"Industrial\", \"Technology\"\n"
" ]\n"
" }\n"
"\n"
" static companyPrefixes {\n"
" return [\n"
" \"Global\", \"United\", \"International\", \"National\", \"American\", \"Pacific\", \"Atlantic\", \"Northern\", \"Southern\",\n"
" \"Western\", \"Eastern\", \"Central\", \"Premier\", \"Prime\", \"Elite\", \"Apex\", \"Summit\", \"Peak\", \"Pinnacle\",\n"
" \"First\", \"Advanced\", \"Modern\", \"Future\", \"Next\", \"New\", \"Innovative\", \"Dynamic\", \"Strategic\", \"Smart\",\n"
" \"Digital\", \"Tech\", \"Cyber\", \"Data\", \"Cloud\", \"Net\", \"Web\", \"Quantum\", \"Fusion\", \"Synergy\"\n"
" ]\n"
" }\n"
"\n"
" static companySuffixes {\n"
" return [\n"
" \"Inc.\", \"LLC\", \"Corp.\", \"Ltd.\", \"Co.\", \"Group\", \"Holdings\", \"Industries\", \"Enterprises\", \"Solutions\",\n"
" \"Services\", \"Systems\", \"Technologies\", \"Partners\", \"Associates\", \"Consulting\", \"International\", \"Global\", \"Worldwide\"\n"
" ]\n"
" }\n"
"\n"
" static companyNouns {\n"
" return [\n"
" \"Tech\", \"Systems\", \"Solutions\", \"Data\", \"Software\", \"Digital\", \"Media\", \"Networks\", \"Communications\", \"Industries\",\n"
" \"Dynamics\", \"Logic\", \"Works\", \"Labs\", \"Innovations\", \"Ventures\", \"Capital\", \"Resources\", \"Management\", \"Consulting\"\n"
" ]\n"
" }\n"
"\n"
" static jobTitles {\n"
" return [\n"
" \"Software Engineer\", \"Product Manager\", \"Data Scientist\", \"Marketing Manager\", \"Sales Representative\",\n"
" \"Financial Analyst\", \"Human Resources Manager\", \"Operations Manager\", \"Project Manager\", \"Business Analyst\",\n"
" \"Accountant\", \"Graphic Designer\", \"Content Writer\", \"Customer Service Representative\", \"Administrative Assistant\",\n"
" \"Executive Assistant\", \"Office Manager\", \"Quality Assurance Engineer\", \"DevOps Engineer\", \"System Administrator\",\n"
" \"Network Engineer\", \"Database Administrator\", \"Security Analyst\", \"UX Designer\", \"UI Developer\",\n"
" \"Full Stack Developer\", \"Frontend Developer\", \"Backend Developer\", \"Mobile Developer\", \"Cloud Architect\",\n"
" \"Solutions Architect\", \"Technical Lead\", \"Engineering Manager\", \"Director of Engineering\", \"VP of Engineering\",\n"
" \"Chief Technology Officer\", \"Chief Executive Officer\", \"Chief Financial Officer\", \"Chief Operating Officer\", \"Chief Marketing Officer\"\n"
" ]\n"
" }\n"
"\n"
" static jobDescriptors {\n"
" return [\"Lead\", \"Senior\", \"Junior\", \"Associate\", \"Principal\", \"Staff\", \"Chief\", \"Head\", \"Director\", \"Manager\"]\n"
" }\n"
"\n"
" static productCategories {\n"
" return [\n"
" \"Electronics\", \"Clothing\", \"Home & Garden\", \"Sports & Outdoors\", \"Books\", \"Toys & Games\",\n"
" \"Health & Beauty\", \"Automotive\", \"Food & Grocery\", \"Office Supplies\", \"Pet Supplies\", \"Jewelry\",\n"
" \"Music\", \"Movies\", \"Software\", \"Tools & Hardware\", \"Baby Products\", \"Furniture\", \"Art & Crafts\", \"Industrial\"\n"
" ]\n"
" }\n"
"\n"
" static productAdjectives {\n"
" return [\n"
" \"Incredible\", \"Fantastic\", \"Amazing\", \"Gorgeous\", \"Practical\", \"Sleek\", \"Elegant\", \"Rustic\", \"Modern\",\n"
" \"Handmade\", \"Refined\", \"Premium\", \"Ergonomic\", \"Intelligent\", \"Smart\", \"Licensed\", \"Recycled\", \"Unbranded\", \"Generic\", \"Tasty\"\n"
" ]\n"
" }\n"
"\n"
" static productMaterials {\n"
" return [\n"
" \"Steel\", \"Wooden\", \"Concrete\", \"Plastic\", \"Cotton\", \"Granite\", \"Rubber\", \"Metal\", \"Soft\", \"Fresh\",\n"
" \"Frozen\", \"Bronze\", \"Silver\", \"Gold\", \"Copper\", \"Marble\", \"Leather\", \"Silk\", \"Wool\", \"Linen\"\n"
" ]\n"
" }\n"
"\n"
" static productNouns {\n"
" return [\n"
" \"Chair\", \"Car\", \"Computer\", \"Keyboard\", \"Mouse\", \"Bike\", \"Ball\", \"Gloves\", \"Pants\", \"Shirt\",\n"
" \"Table\", \"Shoes\", \"Hat\", \"Towels\", \"Soap\", \"Tuna\", \"Chicken\", \"Fish\", \"Cheese\", \"Bacon\",\n"
" \"Pizza\", \"Salad\", \"Sausages\", \"Chips\", \"Watch\", \"Phone\", \"Tablet\", \"Camera\", \"Lamp\", \"Clock\"\n"
" ]\n"
" }\n"
"\n"
" static creditCardTypes {\n"
" return [\n"
" [\"Visa\", \"4\"],\n"
" [\"Mastercard\", \"5\"],\n"
" [\"American Express\", \"3\"],\n"
" [\"Discover\", \"6\"]\n"
" ]\n"
" }\n"
"\n"
" static currencies {\n"
" return [\n"
" [\"USD\", \"US Dollar\", \"$\"], [\"EUR\", \"Euro\", \"\"], [\"GBP\", \"British Pound\", \"£\"],\n"
" [\"JPY\", \"Japanese Yen\", \"¥\"], [\"AUD\", \"Australian Dollar\", \"A$\"], [\"CAD\", \"Canadian Dollar\", \"C$\"],\n"
" [\"CHF\", \"Swiss Franc\", \"CHF\"], [\"CNY\", \"Chinese Yuan\", \"¥\"], [\"INR\", \"Indian Rupee\", \"\"],\n"
" [\"MXN\", \"Mexican Peso\", \"$\"], [\"BRL\", \"Brazilian Real\", \"R$\"], [\"RUB\", \"Russian Ruble\", \"\"]\n"
" ]\n"
" }\n"
"\n"
" static colorNames {\n"
" return [\n"
" \"Red\", \"Blue\", \"Green\", \"Yellow\", \"Orange\", \"Purple\", \"Pink\", \"Brown\", \"Black\", \"White\",\n"
" \"Gray\", \"Cyan\", \"Magenta\", \"Lime\", \"Maroon\", \"Navy\", \"Olive\", \"Teal\", \"Aqua\", \"Silver\",\n"
" \"Crimson\", \"Coral\", \"Salmon\", \"Tomato\", \"Gold\", \"Khaki\", \"Indigo\", \"Violet\", \"Plum\", \"Orchid\",\n"
" \"Tan\", \"Beige\", \"Ivory\", \"Lavender\", \"Mint\", \"Turquoise\", \"Azure\", \"Slate\", \"Charcoal\", \"Pearl\"\n"
" ]\n"
" }\n"
"\n"
" static months {\n"
" return [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"]\n"
" }\n"
"\n"
" static daysOfWeek {\n"
" return [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]\n"
" }\n"
"\n"
" static topLevelDomains {\n"
" return [\"com\", \"org\", \"net\", \"edu\", \"gov\", \"io\", \"co\", \"biz\", \"info\", \"me\", \"us\", \"uk\", \"de\", \"fr\", \"jp\"]\n"
" }\n"
"\n"
" static httpMethods {\n"
" return [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"HEAD\", \"OPTIONS\"]\n"
" }\n"
"\n"
" static protocols {\n"
" return [\"http\", \"https\"]\n"
" }\n"
"\n"
" static userAgents {\n"
" return [\n"
" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\",\n"
" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\",\n"
" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0\",\n"
" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15\",\n"
" \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\",\n"
" \"Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1\"\n"
" ]\n"
" }\n"
"}\n"
"\n"
"class Faker {\n"
" static seed(value) { FakerRandom.seed(value) }\n"
" static reset() { FakerRandom.reset() }\n"
"\n"
" static randomElement(list) {\n"
" if (list.count == 0) return null\n"
" return list[FakerRandom.next(0, list.count - 1)]\n"
" }\n"
"\n"
" static randomElements(list, count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(randomElement(list))\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static randomInt(min, max) { FakerRandom.next(min, max) }\n"
"\n"
" static randomFloat(min, max) {\n"
" var f = FakerRandom.nextFloat()\n"
" return min + (f * (max - min))\n"
" }\n"
"\n"
" static randomFloat(min, max, precision) {\n"
" var f = randomFloat(min, max)\n"
" var mult = 1\n"
" for (i in 0...precision) mult = mult * 10\n"
" return (f * mult).round / mult\n"
" }\n"
"\n"
" static boolean() { FakerRandom.next(0, 1) == 1 }\n"
" static bool() { boolean() }\n"
"\n"
" static numerify(format) {\n"
" var result = \"\"\n"
" for (char in format) {\n"
" if (char == \"#\") {\n"
" result = result + FakerRandom.next(0, 9).toString\n"
" } else {\n"
" result = result + char\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static letterify(format) {\n"
" var letters = \"abcdefghijklmnopqrstuvwxyz\"\n"
" var result = \"\"\n"
" for (char in format) {\n"
" if (char == \"?\") {\n"
" result = result + letters[FakerRandom.next(0, 25)]\n"
" } else {\n"
" result = result + char\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static bothify(format) { letterify(numerify(format)) }\n"
"\n"
" static firstName() { randomElement(FakerData.firstNames) }\n"
" static firstNameMale() { randomElement(FakerData.firstNamesMale) }\n"
" static firstNameFemale() { randomElement(FakerData.firstNamesFemale) }\n"
" static lastName() { randomElement(FakerData.lastNames) }\n"
" static name() { \"%(firstName()) %(lastName())\" }\n"
" static nameMale() { \"%(firstNameMale()) %(lastName())\" }\n"
" static nameFemale() { \"%(firstNameFemale()) %(lastName())\" }\n"
"\n"
" static prefix() { randomElement([\"Mr.\", \"Mrs.\", \"Ms.\", \"Miss\", \"Dr.\"]) }\n"
" static namePrefix() { prefix() }\n"
" static suffix() { randomElement([\"Jr.\", \"Sr.\", \"I\", \"II\", \"III\", \"IV\", \"V\", \"MD\", \"PhD\"]) }\n"
" static nameSuffix() { suffix() }\n"
"\n"
" static gender() { randomElement([\"Male\", \"Female\"]) }\n"
" static sex() { gender() }\n"
"\n"
" static username() {\n"
" var first = Str.toLower(firstName())\n"
" var last = Str.toLower(lastName())\n"
" var patterns = [\n"
" \"%(first)%(last)\",\n"
" \"%(first).%(last)\",\n"
" \"%(first)_%(last)\",\n"
" \"%(first)%(FakerRandom.next(1, 99))\",\n"
" \"%(first).%(last)%(FakerRandom.next(1, 99))\",\n"
" \"%(first)_%(FakerRandom.next(100, 999))\"\n"
" ]\n"
" return randomElement(patterns)\n"
" }\n"
"\n"
" static email() {\n"
" var user = username()\n"
" var domain = randomElement(FakerData.emailDomains)\n"
" return \"%(user)@%(domain)\"\n"
" }\n"
"\n"
" static exampleEmail() {\n"
" var user = username()\n"
" var domain = randomElement([\"example.com\", \"example.org\", \"example.net\"])\n"
" return \"%(user)@%(domain)\"\n"
" }\n"
"\n"
" static city() { randomElement(FakerData.cities) }\n"
"\n"
" static state() {\n"
" var s = randomElement(FakerData.states)\n"
" return s[0]\n"
" }\n"
"\n"
" static stateAbbr() {\n"
" var s = randomElement(FakerData.states)\n"
" return s[1]\n"
" }\n"
"\n"
" static stateFull() { state() }\n"
" static stateName() { state() }\n"
"\n"
" static country() {\n"
" var c = randomElement(FakerData.countries)\n"
" return c[0]\n"
" }\n"
"\n"
" static countryCode() {\n"
" var c = randomElement(FakerData.countries)\n"
" return c[1]\n"
" }\n"
"\n"
" static countryName() { country() }\n"
"\n"
" static zipCode() { numerify(\"#####\") }\n"
" static postcode() { zipCode() }\n"
"\n"
" static buildingNumber() { FakerRandom.next(1, 9999).toString }\n"
"\n"
" static streetName() {\n"
" var name = randomElement(FakerData.streetNames)\n"
" var suffix = randomElement(FakerData.streetSuffixes)\n"
" return \"%(name) %(suffix)\"\n"
" }\n"
"\n"
" static streetAddress() { \"%(buildingNumber()) %(streetName())\" }\n"
"\n"
" static address() {\n"
" return \"%(streetAddress()), %(city()), %(stateAbbr()) %(zipCode())\"\n"
" }\n"
"\n"
" static latitude() { randomFloat(-90, 90, 6) }\n"
" static longitude() { randomFloat(-180, 180, 6) }\n"
"\n"
" static latitude(min, max) { randomFloat(min, max, 6) }\n"
" static longitude(min, max) { randomFloat(min, max, 6) }\n"
"\n"
" static ipv4() {\n"
" var a = FakerRandom.next(1, 255)\n"
" var b = FakerRandom.next(0, 255)\n"
" var c = FakerRandom.next(0, 255)\n"
" var d = FakerRandom.next(1, 254)\n"
" return \"%(a).%(b).%(c).%(d)\"\n"
" }\n"
"\n"
" static ipv6() {\n"
" var parts = []\n"
" for (i in 0...8) {\n"
" var hex = \"\"\n"
" for (j in 0...4) {\n"
" var digit = FakerRandom.next(0, 15)\n"
" hex = hex + \"0123456789abcdef\"[digit]\n"
" }\n"
" parts.add(hex)\n"
" }\n"
" return parts.join(\":\")\n"
" }\n"
"\n"
" static macAddress() {\n"
" var parts = []\n"
" for (i in 0...6) {\n"
" var hex = \"\"\n"
" for (j in 0...2) {\n"
" var digit = FakerRandom.next(0, 15)\n"
" hex = hex + \"0123456789abcdef\"[digit]\n"
" }\n"
" parts.add(hex)\n"
" }\n"
" return parts.join(\":\")\n"
" }\n"
"\n"
" static port() { FakerRandom.next(1, 65535) }\n"
"\n"
" static tld() { randomElement(FakerData.topLevelDomains) }\n"
" static topLevelDomain() { tld() }\n"
"\n"
" static domainWord() {\n"
" var words = [Str.toLower(firstName()), Str.toLower(lastName()), Str.toLower(randomElement(FakerData.companyNouns))]\n"
" return randomElement(words)\n"
" }\n"
"\n"
" static domainName() { \"%(domainWord()).%(tld())\" }\n"
"\n"
" static protocol() { randomElement(FakerData.protocols) }\n"
"\n"
" static url() { \"%(protocol())://%(domainName())\" }\n"
"\n"
" static password() { password(12) }\n"
"\n"
" static password(length) {\n"
" var chars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#^&*-_+=\"\n"
" var result = \"\"\n"
" for (i in 0...length) {\n"
" result = result + chars[FakerRandom.next(0, chars.count - 1)]\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static httpMethod() { randomElement(FakerData.httpMethods) }\n"
" static httpStatusCode() { randomElement([200, 201, 204, 301, 302, 400, 401, 403, 404, 500, 502, 503]) }\n"
" static userAgent() { randomElement(FakerData.userAgents) }\n"
" static hostname() { \"%(domainWord())-%(FakerRandom.next(1, 99)).%(domainName())\" }\n"
"\n"
" static uuid() { Uuid.v4() }\n"
"\n"
" static md5() { Hash.toHex(Hash.md5(password(32))) }\n"
" static sha1() { Hash.toHex(Hash.sha1(password(32))) }\n"
" static sha256() { Hash.toHex(Hash.sha256(password(32))) }\n"
"\n"
" static hexColor() {\n"
" var hex = \"\"\n"
" for (i in 0...6) {\n"
" var digit = FakerRandom.next(0, 15)\n"
" hex = hex + \"0123456789abcdef\"[digit]\n"
" }\n"
" return \"#\" + hex\n"
" }\n"
"\n"
" static rgbColor() {\n"
" var r = FakerRandom.next(0, 255)\n"
" var g = FakerRandom.next(0, 255)\n"
" var b = FakerRandom.next(0, 255)\n"
" return \"rgb(%(r), %(g), %(b))\"\n"
" }\n"
"\n"
" static rgb() { rgbColor() }\n"
"\n"
" static rgbaCssColor() {\n"
" var r = FakerRandom.next(0, 255)\n"
" var g = FakerRandom.next(0, 255)\n"
" var b = FakerRandom.next(0, 255)\n"
" var a = randomFloat(0, 1, 2)\n"
" return \"rgba(%(r), %(g), %(b), %(a))\"\n"
" }\n"
"\n"
" static colorName() { randomElement(FakerData.colorNames) }\n"
"\n"
" static date() {\n"
" var now = DateTime.now()\n"
" var start = now - Duration.fromDays(365 * 10)\n"
" var end = now\n"
" return dateBetween(start, end)\n"
" }\n"
"\n"
" static dateTime() {\n"
" var now = DateTime.now()\n"
" var start = now - Duration.fromDays(365 * 10)\n"
" var range = 365 * 10 * 24 * 60 * 60\n"
" var offset = FakerRandom.next(0, range)\n"
" var dt = start + Duration.fromSeconds(offset)\n"
" return dt.toIso8601\n"
" }\n"
"\n"
" static pastDate() { pastDate(1) }\n"
"\n"
" static pastDate(years) {\n"
" var now = DateTime.now()\n"
" var start = now - Duration.fromDays(365 * years)\n"
" return dateBetween(start, now)\n"
" }\n"
"\n"
" static futureDate() { futureDate(1) }\n"
"\n"
" static futureDate(years) {\n"
" var now = DateTime.now()\n"
" var end = now + Duration.fromDays(365 * years)\n"
" return dateBetween(now, end)\n"
" }\n"
"\n"
" static dateBetween(start, end) {\n"
" var startTs = start.timestamp\n"
" var endTs = end.timestamp\n"
" if (endTs <= startTs) return start.format(\"\\%Y-\\%m-\\%d\")\n"
" var range = endTs - startTs\n"
" var offset = FakerRandom.next(0, range.floor)\n"
" var dt = DateTime.fromTimestamp(startTs + offset)\n"
" return dt.format(\"\\%Y-\\%m-\\%d\")\n"
" }\n"
"\n"
" static dateOfBirth() { dateOfBirth(18, 80) }\n"
"\n"
" static dateOfBirth(minAge, maxAge) {\n"
" var now = DateTime.now()\n"
" var end = now - Duration.fromDays(365 * minAge)\n"
" var start = now - Duration.fromDays(365 * maxAge)\n"
" return dateBetween(start, end)\n"
" }\n"
"\n"
" static year() { FakerRandom.next(1950, 2025) }\n"
" static month() { FakerRandom.next(1, 12) }\n"
" static monthName() { FakerData.months[FakerRandom.next(0, 11)] }\n"
" static dayOfWeek() { randomElement(FakerData.daysOfWeek) }\n"
" static dayOfMonth() { FakerRandom.next(1, 28) }\n"
" static hour() { FakerRandom.next(0, 23) }\n"
" static minute() { FakerRandom.next(0, 59) }\n"
" static second() { FakerRandom.next(0, 59) }\n"
" static amPm() { randomElement([\"AM\", \"PM\"]) }\n"
"\n"
" static time() {\n"
" var h = hour()\n"
" var m = minute()\n"
" var s = second()\n"
" return \"%(h < 10 ? \"0\" : \"\")%(h):%(m < 10 ? \"0\" : \"\")%(m):%(s < 10 ? \"0\" : \"\")%(s)\"\n"
" }\n"
"\n"
" static word() { randomElement(FakerData.loremWords) }\n"
"\n"
" static words(count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(word())\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static sentence() { sentence(FakerRandom.next(5, 12)) }\n"
"\n"
" static sentence(wordCount) {\n"
" var w = words(wordCount)\n"
" var first = w[0]\n"
" w[0] = first[0].bytes[0] >= 97 && first[0].bytes[0] <= 122 ? String.fromCodePoint(first[0].bytes[0] - 32) + first[1..-1] : first\n"
" return w.join(\" \") + \".\"\n"
" }\n"
"\n"
" static sentences(count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(sentence())\n"
" }\n"
" return result.join(\" \")\n"
" }\n"
"\n"
" static paragraph() { paragraph(FakerRandom.next(3, 6)) }\n"
"\n"
" static paragraph(sentenceCount) { sentences(sentenceCount) }\n"
"\n"
" static paragraphs(count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(paragraph())\n"
" }\n"
" return result.join(\"\\n\\n\")\n"
" }\n"
"\n"
" static text() { text(200) }\n"
"\n"
" static text(maxChars) {\n"
" var result = \"\"\n"
" while (result.count < maxChars) {\n"
" result = result + sentence() + \" \"\n"
" }\n"
" return result[0...maxChars]\n"
" }\n"
"\n"
" static slug() { slug(3) }\n"
"\n"
" static slug(wordCount) {\n"
" var w = words(wordCount)\n"
" var lower = []\n"
" for (word in w) {\n"
" lower.add(Str.toLower(word))\n"
" }\n"
" return lower.join(\"-\")\n"
" }\n"
"\n"
" static company() {\n"
" var patterns = [\n"
" \"%(lastName()) %(randomElement(FakerData.companySuffixes))\",\n"
" \"%(lastName())-%(lastName())\",\n"
" \"%(randomElement(FakerData.companyPrefixes)) %(randomElement(FakerData.companyNouns))\",\n"
" \"%(lastName()) %(randomElement(FakerData.companyNouns))\"\n"
" ]\n"
" return randomElement(patterns)\n"
" }\n"
"\n"
" static companyName() { company() }\n"
" static companySuffix() { randomElement(FakerData.companySuffixes) }\n"
"\n"
" static job() { randomElement(FakerData.jobTitles) }\n"
" static jobTitle() { job() }\n"
" static jobDescriptor() { randomElement(FakerData.jobDescriptors) }\n"
"\n"
" static product() {\n"
" var adj = randomElement(FakerData.productAdjectives)\n"
" var mat = randomElement(FakerData.productMaterials)\n"
" var noun = randomElement(FakerData.productNouns)\n"
" return \"%(adj) %(mat) %(noun)\"\n"
" }\n"
"\n"
" static productName() { product() }\n"
" static productCategory() { randomElement(FakerData.productCategories) }\n"
"\n"
" static price() { price(1, 1000) }\n"
" static price(min, max) { randomFloat(min, max, 2) }\n"
"\n"
" static currency() {\n"
" var c = randomElement(FakerData.currencies)\n"
" return c[0]\n"
" }\n"
"\n"
" static currencyName() {\n"
" var c = randomElement(FakerData.currencies)\n"
" return c[1]\n"
" }\n"
"\n"
" static currencySymbol() {\n"
" var c = randomElement(FakerData.currencies)\n"
" return c[2]\n"
" }\n"
"\n"
" static creditCardType() {\n"
" var c = randomElement(FakerData.creditCardTypes)\n"
" return c[0]\n"
" }\n"
"\n"
" static creditCardNumber() {\n"
" var type = randomElement(FakerData.creditCardTypes)\n"
" var prefix = type[1]\n"
" return prefix + numerify(\"###-####-####-###\")\n"
" }\n"
"\n"
" static creditCardCVV() { numerify(\"###\") }\n"
"\n"
" static creditCardExpiryDate() {\n"
" var m = FakerRandom.next(1, 12)\n"
" var y = FakerRandom.next(25, 30)\n"
" return \"%(m < 10 ? \"0\" : \"\")%(m)/%(y)\"\n"
" }\n"
"\n"
" static phoneNumber() { numerify(\"(###) ###-####\") }\n"
" static phone() { phoneNumber() }\n"
"\n"
" static iban() {\n"
" var country = randomElement([\"DE\", \"FR\", \"GB\", \"ES\", \"IT\", \"NL\"])\n"
" var check = numerify(\"##\")\n"
" var bban = numerify(\"####################\")\n"
" return country + check + bban\n"
" }\n"
"\n"
" static accountNumber() { accountNumber(10) }\n"
" static accountNumber(length) {\n"
" var result = \"\"\n"
" for (i in 0...length) {\n"
" result = result + FakerRandom.next(0, 9).toString\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static routingNumber() { numerify(\"#########\") }\n"
"\n"
" static fileName() {\n"
" var extensions = [\"txt\", \"pdf\", \"doc\", \"xls\", \"jpg\", \"png\", \"gif\", \"mp3\", \"mp4\", \"zip\"]\n"
" var name = Str.toLower(word())\n"
" var ext = randomElement(extensions)\n"
" return \"%(name).%(ext)\"\n"
" }\n"
"\n"
" static fileExtension() { randomElement([\"txt\", \"pdf\", \"doc\", \"xls\", \"jpg\", \"png\", \"gif\", \"mp3\", \"mp4\", \"zip\"]) }\n"
"\n"
" static mimeType() {\n"
" return randomElement([\n"
" \"application/json\", \"application/xml\", \"application/pdf\", \"application/zip\",\n"
" \"text/html\", \"text/plain\", \"text/css\", \"text/javascript\",\n"
" \"image/jpeg\", \"image/png\", \"image/gif\", \"image/svg+xml\",\n"
" \"audio/mpeg\", \"video/mp4\"\n"
" ])\n"
" }\n"
"\n"
" static semver() { \"%(FakerRandom.next(0, 9)).%(FakerRandom.next(0, 20)).%(FakerRandom.next(0, 50))\" }\n"
"\n"
" static digit() { FakerRandom.next(0, 9) }\n"
" static randomDigit() { digit() }\n"
" static digits(count) {\n"
" var result = []\n"
" for (i in 0...count) {\n"
" result.add(digit())\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static letter() { \"abcdefghijklmnopqrstuvwxyz\"[FakerRandom.next(0, 25)] }\n"
" static letters(count) {\n"
" var result = \"\"\n"
" for (i in 0...count) {\n"
" result = result + letter()\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static shuffle(list) {\n"
" var result = list.toList\n"
" for (i in (result.count - 1)..1) {\n"
" var j = FakerRandom.next(0, i)\n"
" var temp = result[i]\n"
" result[i] = result[j]\n"
" result[j] = temp\n"
" }\n"
" return result\n"
" }\n"
"\n"
" static profile() {\n"
" return {\n"
" \"username\": username(),\n"
" \"name\": name(),\n"
" \"email\": email(),\n"
" \"address\": address(),\n"
" \"phone\": phoneNumber(),\n"
" \"job\": jobTitle(),\n"
" \"company\": company(),\n"
" \"birthdate\": dateOfBirth()\n"
" }\n"
" }\n"
"\n"
" static simpleProfile() {\n"
" return {\n"
" \"username\": username(),\n"
" \"name\": name(),\n"
" \"email\": email(),\n"
" \"address\": address()\n"
" }\n"
" }\n"
"\n"
" static locale() { \"en_US\" }\n"
"}\n";

View File

@ -1,13 +1,30 @@
// retoor <retoor@molodetz.nl>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include "subprocess.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
#include "uv.h"
#ifdef __linux__
#include <pty.h>
#include <termios.h>
#include <sys/ioctl.h>
#elif defined(__APPLE__) || defined(__FreeBSD__)
#include <util.h>
#include <termios.h>
#include <sys/ioctl.h>
#endif
#define STREAM_STDIN 0
#define STREAM_STDOUT 1
#define STREAM_STDERR 2
typedef struct {
WrenHandle* fiber;
uv_process_t process;
@ -23,6 +40,38 @@ typedef struct {
int handlesOpen;
} ProcessData;
typedef struct StreamData {
uv_pipe_t pipe;
WrenHandle* readFiber;
char* buffer;
size_t bufLen;
size_t bufCap;
bool isOpen;
bool isReadable;
bool isWritable;
bool readPending;
struct PopenData* parent;
} StreamData;
typedef struct PopenData {
uv_process_t process;
WrenHandle* waitFiber;
StreamData streams[3];
int64_t exitCode;
bool exited;
bool usePty;
int ptyMaster;
int handlesOpen;
int pid;
} PopenData;
typedef struct {
uv_write_t req;
uv_buf_t buf;
WrenHandle* fiber;
StreamData* stream;
} WriteRequest;
static void appendBuffer(char** buf, size_t* len, size_t* cap, const char* data, size_t dataLen) {
if (*len + dataLen >= *cap) {
size_t newCap = (*cap == 0) ? 1024 : *cap * 2;
@ -182,3 +231,404 @@ void subprocessRun(WrenVM* vm) {
uv_read_start((uv_stream_t*)&data->stdoutPipe, allocBuffer, onStdoutRead);
uv_read_start((uv_stream_t*)&data->stderrPipe, allocBuffer, onStderrRead);
}
static void onPopenProcessExit(uv_process_t* process, int64_t exitStatus, int termSignal) {
PopenData* data = (PopenData*)process->data;
data->exitCode = termSignal ? 128 + termSignal : exitStatus;
data->exited = true;
if (data->waitFiber != NULL) {
WrenVM* vm = getVM();
WrenHandle* fiber = data->waitFiber;
data->waitFiber = NULL;
wrenEnsureSlots(vm, 3);
wrenSetSlotDouble(vm, 2, (double)data->exitCode);
schedulerResume(fiber, true);
schedulerFinishResume();
}
}
static void onPopenStreamRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
StreamData* sdata = (StreamData*)stream->data;
if (nread > 0) {
appendBuffer(&sdata->buffer, &sdata->bufLen, &sdata->bufCap, buf->base, nread);
}
if (buf->base != NULL) free(buf->base);
if (nread < 0 || (sdata->readPending && sdata->bufLen > 0)) {
uv_read_stop(stream);
if (nread < 0 && nread != UV_EOF) {
sdata->isOpen = false;
}
if (sdata->readFiber != NULL) {
WrenVM* vm = getVM();
WrenHandle* fiber = sdata->readFiber;
sdata->readFiber = NULL;
sdata->readPending = false;
wrenEnsureSlots(vm, 3);
if (sdata->buffer != NULL) {
wrenSetSlotString(vm, 2, sdata->buffer);
free(sdata->buffer);
sdata->buffer = NULL;
sdata->bufLen = 0;
sdata->bufCap = 0;
} else {
wrenSetSlotString(vm, 2, "");
}
schedulerResume(fiber, true);
schedulerFinishResume();
}
}
}
static void onPopenWriteComplete(uv_write_t* req, int status) {
WriteRequest* wreq = (WriteRequest*)req;
WrenHandle* fiber = wreq->fiber;
free(wreq->buf.base);
free(wreq);
WrenVM* vm = getVM();
wrenEnsureSlots(vm, 3);
if (status < 0) {
wrenSetSlotBool(vm, 2, false);
} else {
wrenSetSlotBool(vm, 2, true);
}
schedulerResume(fiber, true);
schedulerFinishResume();
}
void popenAllocate(WrenVM* vm) {
PopenData* data = (PopenData*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(PopenData));
memset(data, 0, sizeof(PopenData));
int argCount = wrenGetListCount(vm, 1);
if (argCount == 0) {
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
char** args = (char**)malloc((argCount + 1) * sizeof(char*));
if (args == NULL) {
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
for (int i = 0; i < argCount; i++) {
wrenGetListElement(vm, 1, i, 2);
const char* arg = wrenGetSlotString(vm, 2);
args[i] = strdup(arg);
}
args[argCount] = NULL;
bool usePty = false;
if (wrenGetSlotCount(vm) > 2 && wrenGetSlotType(vm, 2) == WREN_TYPE_BOOL) {
usePty = wrenGetSlotBool(vm, 2);
}
data->usePty = usePty;
uv_loop_t* loop = getLoop();
for (int i = 0; i < 3; i++) {
data->streams[i].parent = data;
data->streams[i].isOpen = false;
}
if (usePty) {
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
int master, slave;
struct winsize ws = { .ws_row = 24, .ws_col = 80 };
if (openpty(&master, &slave, NULL, NULL, &ws) < 0) {
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
data->ptyMaster = master;
uv_pipe_init(loop, &data->streams[STREAM_STDIN].pipe, 0);
uv_pipe_open(&data->streams[STREAM_STDIN].pipe, master);
data->streams[STREAM_STDIN].pipe.data = &data->streams[STREAM_STDIN];
data->streams[STREAM_STDIN].isOpen = true;
data->streams[STREAM_STDIN].isWritable = true;
data->streams[STREAM_STDIN].isReadable = true;
data->streams[STREAM_STDOUT].isOpen = true;
data->streams[STREAM_STDOUT].isReadable = true;
data->handlesOpen = 1;
uv_stdio_container_t stdio[3];
stdio[0].flags = UV_INHERIT_FD;
stdio[0].data.fd = slave;
stdio[1].flags = UV_INHERIT_FD;
stdio[1].data.fd = slave;
stdio[2].flags = UV_INHERIT_FD;
stdio[2].data.fd = slave;
uv_process_options_t options;
memset(&options, 0, sizeof(options));
options.file = args[0];
options.args = args;
options.stdio = stdio;
options.stdio_count = 3;
options.exit_cb = onPopenProcessExit;
data->process.data = data;
int result = uv_spawn(loop, &data->process, &options);
close(slave);
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
if (result != 0) {
close(master);
data->streams[STREAM_STDIN].isOpen = false;
data->streams[STREAM_STDOUT].isOpen = false;
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
data->pid = data->process.pid;
data->handlesOpen++;
#else
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
#endif
} else {
uv_pipe_init(loop, &data->streams[STREAM_STDIN].pipe, 0);
uv_pipe_init(loop, &data->streams[STREAM_STDOUT].pipe, 0);
uv_pipe_init(loop, &data->streams[STREAM_STDERR].pipe, 0);
data->streams[STREAM_STDIN].pipe.data = &data->streams[STREAM_STDIN];
data->streams[STREAM_STDOUT].pipe.data = &data->streams[STREAM_STDOUT];
data->streams[STREAM_STDERR].pipe.data = &data->streams[STREAM_STDERR];
data->streams[STREAM_STDIN].isOpen = true;
data->streams[STREAM_STDIN].isWritable = true;
data->streams[STREAM_STDOUT].isOpen = true;
data->streams[STREAM_STDOUT].isReadable = true;
data->streams[STREAM_STDERR].isOpen = true;
data->streams[STREAM_STDERR].isReadable = true;
data->handlesOpen = 3;
uv_stdio_container_t stdio[3];
stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
stdio[0].data.stream = (uv_stream_t*)&data->streams[STREAM_STDIN].pipe;
stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
stdio[1].data.stream = (uv_stream_t*)&data->streams[STREAM_STDOUT].pipe;
stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
stdio[2].data.stream = (uv_stream_t*)&data->streams[STREAM_STDERR].pipe;
uv_process_options_t options;
memset(&options, 0, sizeof(options));
options.file = args[0];
options.args = args;
options.stdio = stdio;
options.stdio_count = 3;
options.exit_cb = onPopenProcessExit;
data->process.data = data;
int result = uv_spawn(loop, &data->process, &options);
for (int i = 0; i < argCount; i++) free(args[i]);
free(args);
if (result != 0) {
data->handlesOpen = 0;
data->streams[STREAM_STDIN].isOpen = false;
data->streams[STREAM_STDOUT].isOpen = false;
data->streams[STREAM_STDERR].isOpen = false;
data->pid = -1;
data->exited = true;
data->exitCode = -1;
return;
}
data->pid = data->process.pid;
data->handlesOpen++;
}
}
void popenFinalize(void* userData) {
}
void popenPid(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotDouble(vm, 0, (double)data->pid);
}
void popenIsRunning(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
wrenSetSlotBool(vm, 0, !data->exited);
}
void popenWait(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
if (data->exited) {
wrenSetSlotDouble(vm, 0, (double)data->exitCode);
return;
}
data->waitFiber = wrenGetSlotHandle(vm, 1);
}
void popenKill(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int signal = (int)wrenGetSlotDouble(vm, 1);
if (!data->exited) {
uv_process_kill(&data->process, signal);
}
}
void popenReadStream(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int index = (int)wrenGetSlotDouble(vm, 1);
if (index < 0 || index > 2) {
wrenSetSlotString(vm, 0, "Invalid stream index.");
wrenAbortFiber(vm, 0);
return;
}
StreamData* stream = &data->streams[index];
if (!stream->isReadable) {
wrenSetSlotString(vm, 0, "Stream is not readable.");
wrenAbortFiber(vm, 0);
return;
}
if (!stream->isOpen) {
wrenSetSlotString(vm, 0, "");
return;
}
stream->readFiber = wrenGetSlotHandle(vm, 2);
stream->readPending = true;
if (data->usePty && index == STREAM_STDOUT) {
uv_read_start((uv_stream_t*)&data->streams[STREAM_STDIN].pipe, allocBuffer, onPopenStreamRead);
} else {
uv_read_start((uv_stream_t*)&stream->pipe, allocBuffer, onPopenStreamRead);
}
}
void popenWriteStream(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int index = (int)wrenGetSlotDouble(vm, 1);
const char* str = wrenGetSlotString(vm, 2);
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
if (index != STREAM_STDIN) {
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Can only write to stdin.");
wrenAbortFiber(vm, 0);
return;
}
StreamData* stream = &data->streams[STREAM_STDIN];
if (!stream->isOpen) {
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Stream is closed.");
wrenAbortFiber(vm, 0);
return;
}
size_t len = strlen(str);
WriteRequest* req = (WriteRequest*)malloc(sizeof(WriteRequest));
if (req == NULL) {
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
req->buf.base = (char*)malloc(len);
if (req->buf.base == NULL) {
free(req);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
memcpy(req->buf.base, str, len);
req->buf.len = len;
req->fiber = fiber;
req->stream = stream;
int result = uv_write(&req->req, (uv_stream_t*)&stream->pipe, &req->buf, 1, onPopenWriteComplete);
if (result != 0) {
free(req->buf.base);
free(req);
wrenReleaseHandle(vm, fiber);
wrenSetSlotString(vm, 0, uv_strerror(result));
wrenAbortFiber(vm, 0);
}
}
static void onStreamClosed(uv_handle_t* handle) {
StreamData* stream = (StreamData*)handle->data;
stream->isOpen = false;
stream->parent->handlesOpen--;
}
void popenCloseStream(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int index = (int)wrenGetSlotDouble(vm, 1);
if (index < 0 || index > 2) {
return;
}
StreamData* stream = &data->streams[index];
if (!stream->isOpen) {
return;
}
if (data->usePty) {
if (index == STREAM_STDIN) {
stream->isOpen = false;
data->streams[STREAM_STDOUT].isOpen = false;
uv_close((uv_handle_t*)&stream->pipe, onStreamClosed);
}
} else {
stream->isOpen = false;
uv_close((uv_handle_t*)&stream->pipe, onStreamClosed);
}
}
void popenIsStreamOpen(WrenVM* vm) {
PopenData* data = (PopenData*)wrenGetSlotForeign(vm, 0);
int index = (int)wrenGetSlotDouble(vm, 1);
if (index < 0 || index > 2) {
wrenSetSlotBool(vm, 0, false);
return;
}
wrenSetSlotBool(vm, 0, data->streams[index].isOpen);
}

View File

@ -7,4 +7,15 @@
void subprocessRun(WrenVM* vm);
void popenAllocate(WrenVM* vm);
void popenFinalize(void* data);
void popenPid(WrenVM* vm);
void popenIsRunning(WrenVM* vm);
void popenWait(WrenVM* vm);
void popenKill(WrenVM* vm);
void popenReadStream(WrenVM* vm);
void popenWriteStream(WrenVM* vm);
void popenCloseStream(WrenVM* vm);
void popenIsStreamOpen(WrenVM* vm);
#endif

View File

@ -45,3 +45,49 @@ class Subprocess {
return ProcessResult.new_(result[0], result[1], result[2])
}
}
class ProcessStream {
construct new_(popen, index) {
_popen = popen
_index = index
}
read() {
return Scheduler.await_ { _popen.readStream_(_index, Fiber.current) }
}
write(data) {
return Scheduler.await_ { _popen.writeStream_(_index, data, Fiber.current) }
}
close() { _popen.closeStream_(_index) }
isOpen { _popen.isStreamOpen_(_index) }
}
foreign class Popen {
construct new(args) {}
construct new(args, usePty) {}
stdin { ProcessStream.new_(this, 0) }
stdout { ProcessStream.new_(this, 1) }
stderr { ProcessStream.new_(this, 2) }
foreign pid
foreign isRunning
foreign wait_(fiber)
foreign kill_(signal)
foreign readStream_(index, fiber)
foreign writeStream_(index, data, fiber)
foreign closeStream_(index)
foreign isStreamOpen_(index)
wait() {
return Scheduler.await_ { wait_(Fiber.current) }
}
kill() { kill_(15) }
kill(signal) { kill_(signal) }
}

View File

@ -48,4 +48,50 @@ static const char* subprocessModuleSource =
" var result = Scheduler.runNextScheduled_()\n"
" return ProcessResult.new_(result[0], result[1], result[2])\n"
" }\n"
"}\n"
"\n"
"class ProcessStream {\n"
" construct new_(popen, index) {\n"
" _popen = popen\n"
" _index = index\n"
" }\n"
"\n"
" read() {\n"
" return Scheduler.await_ { _popen.readStream_(_index, Fiber.current) }\n"
" }\n"
"\n"
" write(data) {\n"
" return Scheduler.await_ { _popen.writeStream_(_index, data, Fiber.current) }\n"
" }\n"
"\n"
" close() { _popen.closeStream_(_index) }\n"
"\n"
" isOpen { _popen.isStreamOpen_(_index) }\n"
"}\n"
"\n"
"foreign class Popen {\n"
" construct new(args) {}\n"
" construct new(args, usePty) {}\n"
"\n"
" stdin { ProcessStream.new_(this, 0) }\n"
" stdout { ProcessStream.new_(this, 1) }\n"
" stderr { ProcessStream.new_(this, 2) }\n"
"\n"
" foreign pid\n"
" foreign isRunning\n"
"\n"
" foreign wait_(fiber)\n"
" foreign kill_(signal)\n"
"\n"
" foreign readStream_(index, fiber)\n"
" foreign writeStream_(index, data, fiber)\n"
" foreign closeStream_(index)\n"
" foreign isStreamOpen_(index)\n"
"\n"
" wait() {\n"
" return Scheduler.await_ { wait_(Fiber.current) }\n"
" }\n"
"\n"
" kill() { kill_(15) }\n"
" kill(signal) { kill_(signal) }\n"
"}\n";

332
src/module/web.wren vendored
View File

@ -10,6 +10,9 @@ import "uuid" for Uuid
import "datetime" for DateTime
import "io" for File
import "strutil" for Str
import "crypto" for Crypto, Hash
import "base64" for Base64
import "bytes" for Bytes
class Request {
construct new_(method, path, query, headers, body, params, socket) {
@ -213,6 +216,302 @@ class Response {
}
}
class WebSocketResponse {
construct new() {
_socket = null
_isPrepared = false
_isOpen = false
_readBuffer = ""
_fragmentBuffer = []
_fragmentOpcode = null
}
isPrepared { _isPrepared }
isOpen { _isOpen }
prepare(request) {
if (_isPrepared) Fiber.abort("WebSocket already prepared.")
var key = request.header("Sec-WebSocket-Key")
if (key == null) Fiber.abort("Missing Sec-WebSocket-Key header.")
_socket = request.socket
var acceptKey = computeAcceptKey_(key)
var response = "HTTP/1.1 101 Switching Protocols\r\n"
response = response + "Upgrade: websocket\r\n"
response = response + "Connection: Upgrade\r\n"
response = response + "Sec-WebSocket-Accept: %(acceptKey)\r\n"
response = response + "\r\n"
_socket.write(response)
_isPrepared = true
_isOpen = true
return this
}
send(text) { sendText(text) }
sendText(text) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
if (!(text is String)) Fiber.abort("Data must be a string.")
var payload = stringToBytes_(text)
sendFrame_(1, payload)
}
sendBinary(bytes) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
if (!(bytes is List)) Fiber.abort("Data must be a list of bytes.")
sendFrame_(2, bytes)
}
ping() { ping([]) }
ping(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var payload = data
if (data is String) payload = stringToBytes_(data)
if (payload.count > 125) Fiber.abort("Ping payload too large (max 125 bytes).")
sendFrame_(9, payload)
}
pong(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var payload = data
if (data is String) payload = stringToBytes_(data)
if (payload.count > 125) Fiber.abort("Pong payload too large (max 125 bytes).")
sendFrame_(10, payload)
}
close() { close(1000, "") }
close(code, reason) {
if (!_isOpen) return
var payload = []
payload.add((code >> 8) & 0xFF)
payload.add(code & 0xFF)
for (b in stringToBytes_(reason)) {
payload.add(b)
}
sendFrame_(8, payload)
_isOpen = false
_socket.close()
}
receive() {
if (!_isOpen) return null
while (true) {
var frame = readFrame_()
if (frame == null) {
_isOpen = false
return null
}
var opcode = frame.opcode
var payload = frame.payload
var fin = frame.fin
if (opcode == 8) {
_isOpen = false
return frame
}
if (opcode == 9) {
sendFrame_(10, payload)
continue
}
if (opcode == 10) {
continue
}
if (opcode == 0) {
for (b in payload) {
_fragmentBuffer.add(b)
}
if (fin) {
var completePayload = _fragmentBuffer
var completeOpcode = _fragmentOpcode
_fragmentBuffer = []
_fragmentOpcode = null
return WebSocketMessage.new_(completeOpcode, completePayload, true)
}
continue
}
if (opcode == 1 || opcode == 2) {
if (fin) {
return frame
} else {
_fragmentOpcode = opcode
_fragmentBuffer = []
for (b in payload) {
_fragmentBuffer.add(b)
}
continue
}
}
Fiber.abort("Unknown opcode: %(opcode)")
}
}
iterate(callback) {
while (_isOpen) {
var msg = receive()
if (msg == null || msg.isClose) break
callback.call(msg)
}
}
sendJson(data) {
if (!_isOpen) Fiber.abort("WebSocket is not open.")
var text = Json.stringify(data)
sendText(text)
}
receiveJson() {
var msg = receive()
if (msg == null) return null
if (msg.isClose) return msg
if (!msg.isText) Fiber.abort("Expected text frame for JSON, got binary.")
return Json.parse(msg.text)
}
receiveText() {
var msg = receive()
if (msg == null) return null
if (msg.isClose) return null
if (!msg.isText) return null
return msg.text
}
receiveBinary() {
var msg = receive()
if (msg == null) return null
if (msg.isClose) return null
if (!msg.isBinary) return null
return msg.bytes
}
sendFrame_(opcode, payload) {
var frame = encodeFrame_(opcode, payload)
_socket.write(Bytes.fromList(frame))
}
encodeFrame_(opcode, payload) {
var frame = []
frame.add(0x80 | opcode)
var len = payload.count
if (len < 126) {
frame.add(len)
} else if (len < 65536) {
frame.add(126)
frame.add((len >> 8) & 0xFF)
frame.add(len & 0xFF)
} else {
frame.add(127)
for (i in 0...4) frame.add(0)
frame.add((len >> 24) & 0xFF)
frame.add((len >> 16) & 0xFF)
frame.add((len >> 8) & 0xFF)
frame.add(len & 0xFF)
}
for (b in payload) frame.add(b)
return frame
}
readFrame_() {
var header = readBytes_(2)
if (header == null || Bytes.length(header) < 2) return null
var headerList = Bytes.toList(header)
var fin = (headerList[0] & 0x80) != 0
var opcode = headerList[0] & 0x0F
var masked = (headerList[1] & 0x80) != 0
var len = headerList[1] & 0x7F
if (len == 126) {
var ext = readBytes_(2)
if (ext == null || Bytes.length(ext) < 2) return null
var extList = Bytes.toList(ext)
len = (extList[0] << 8) | extList[1]
} else if (len == 127) {
var ext = readBytes_(8)
if (ext == null || Bytes.length(ext) < 8) return null
var extList = Bytes.toList(ext)
len = 0
for (i in 4...8) {
len = (len << 8) | extList[i]
}
}
var mask = null
if (masked) {
mask = readBytes_(4)
if (mask == null || Bytes.length(mask) < 4) return null
}
var payload = ""
if (len > 0) {
payload = readBytes_(len)
if (payload == null) return null
}
if (masked && mask != null) {
payload = Bytes.xorMask(payload, mask)
}
return WebSocketMessage.new_(opcode, Bytes.toList(payload), fin)
}
readBytes_(count) {
while (Bytes.length(_readBuffer) < count) {
var chunk = _socket.read()
if (chunk == null || chunk.bytes.count == 0) {
if (Bytes.length(_readBuffer) == 0) return null
var result = _readBuffer
_readBuffer = ""
return result
}
_readBuffer = Bytes.concat(_readBuffer, chunk)
}
var result = Bytes.slice(_readBuffer, 0, count)
_readBuffer = Bytes.slice(_readBuffer, count, Bytes.length(_readBuffer))
return result
}
computeAcceptKey_(key) {
var magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
var combined = key + magic
var sha1 = Hash.sha1(combined)
var sha1Str = bytesToString_(sha1)
return Base64.encode(sha1Str)
}
bytesToString_(bytes) {
var parts = []
for (b in bytes) {
parts.add(String.fromByte(b))
}
return parts.join("")
}
stringToBytes_(str) {
var bytes = []
for (b in str.bytes) {
bytes.add(b)
}
return bytes
}
}
class Session {
construct new_(id, data) {
_id = id
@ -343,6 +642,7 @@ class View {
put(request) { methodNotAllowed_() }
delete(request) { methodNotAllowed_() }
patch(request) { methodNotAllowed_() }
websocket(request) { methodNotAllowed_() }
methodNotAllowed_() {
var r = Response.new()
@ -351,7 +651,15 @@ class View {
return r
}
isWebSocketRequest_(request) {
var upgrade = request.header("Upgrade")
var connection = request.header("Connection")
if (upgrade == null || connection == null) return false
return Str.toLower(upgrade) == "websocket" && Str.toLower(connection).contains("upgrade")
}
dispatch(request) {
if (isWebSocketRequest_(request)) return websocket(request)
if (request.method == "GET") return get(request)
if (request.method == "POST") return post(request)
if (request.method == "PUT") return put(request)
@ -499,6 +807,14 @@ class Application {
var request = Request.new_(method, path, query, headers, body, {}, socket)
loadSession_(request)
if (isWebSocketRequest_(headers)) {
if (_wsHandlers.containsKey(path)) {
var handler = _wsHandlers[path]
var result = handler.call(request)
return
}
}
var response = null
for (sp in _staticPrefixes) {
@ -531,11 +847,27 @@ class Application {
}
}
if (response is WebSocketResponse) {
return
}
saveSession_(request, response)
socket.write(response.build())
socket.close()
}
isWebSocketRequest_(headers) {
var upgrade = null
var connection = null
for (key in headers.keys) {
var lower = Str.toLower(key)
if (lower == "upgrade") upgrade = headers[key]
if (lower == "connection") connection = headers[key]
}
if (upgrade == null || connection == null) return false
return Str.toLower(upgrade) == "websocket" && Str.toLower(connection).contains("upgrade")
}
loadSession_(request) {
var sessionId = request.cookies[_sessionCookieName]
if (sessionId != null) {

View File

@ -14,6 +14,9 @@ static const char* webModuleSource =
"import \"datetime\" for DateTime\n"
"import \"io\" for File\n"
"import \"strutil\" for Str\n"
"import \"crypto\" for Crypto, Hash\n"
"import \"base64\" for Base64\n"
"import \"bytes\" for Bytes\n"
"\n"
"class Request {\n"
" construct new_(method, path, query, headers, body, params, socket) {\n"
@ -217,6 +220,302 @@ static const char* webModuleSource =
" }\n"
"}\n"
"\n"
"class WebSocketResponse {\n"
" construct new() {\n"
" _socket = null\n"
" _isPrepared = false\n"
" _isOpen = false\n"
" _readBuffer = \"\"\n"
" _fragmentBuffer = []\n"
" _fragmentOpcode = null\n"
" }\n"
"\n"
" isPrepared { _isPrepared }\n"
" isOpen { _isOpen }\n"
"\n"
" prepare(request) {\n"
" if (_isPrepared) Fiber.abort(\"WebSocket already prepared.\")\n"
"\n"
" var key = request.header(\"Sec-WebSocket-Key\")\n"
" if (key == null) Fiber.abort(\"Missing Sec-WebSocket-Key header.\")\n"
"\n"
" _socket = request.socket\n"
" var acceptKey = computeAcceptKey_(key)\n"
"\n"
" var response = \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n"
" response = response + \"Upgrade: websocket\\r\\n\"\n"
" response = response + \"Connection: Upgrade\\r\\n\"\n"
" response = response + \"Sec-WebSocket-Accept: %(acceptKey)\\r\\n\"\n"
" response = response + \"\\r\\n\"\n"
"\n"
" _socket.write(response)\n"
" _isPrepared = true\n"
" _isOpen = true\n"
" return this\n"
" }\n"
"\n"
" send(text) { sendText(text) }\n"
"\n"
" sendText(text) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" if (!(text is String)) Fiber.abort(\"Data must be a string.\")\n"
" var payload = stringToBytes_(text)\n"
" sendFrame_(1, payload)\n"
" }\n"
"\n"
" sendBinary(bytes) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" if (!(bytes is List)) Fiber.abort(\"Data must be a list of bytes.\")\n"
" sendFrame_(2, bytes)\n"
" }\n"
"\n"
" ping() { ping([]) }\n"
"\n"
" ping(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var payload = data\n"
" if (data is String) payload = stringToBytes_(data)\n"
" if (payload.count > 125) Fiber.abort(\"Ping payload too large (max 125 bytes).\")\n"
" sendFrame_(9, payload)\n"
" }\n"
"\n"
" pong(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var payload = data\n"
" if (data is String) payload = stringToBytes_(data)\n"
" if (payload.count > 125) Fiber.abort(\"Pong payload too large (max 125 bytes).\")\n"
" sendFrame_(10, payload)\n"
" }\n"
"\n"
" close() { close(1000, \"\") }\n"
"\n"
" close(code, reason) {\n"
" if (!_isOpen) return\n"
"\n"
" var payload = []\n"
" payload.add((code >> 8) & 0xFF)\n"
" payload.add(code & 0xFF)\n"
" for (b in stringToBytes_(reason)) {\n"
" payload.add(b)\n"
" }\n"
"\n"
" sendFrame_(8, payload)\n"
" _isOpen = false\n"
" _socket.close()\n"
" }\n"
"\n"
" receive() {\n"
" if (!_isOpen) return null\n"
"\n"
" while (true) {\n"
" var frame = readFrame_()\n"
" if (frame == null) {\n"
" _isOpen = false\n"
" return null\n"
" }\n"
"\n"
" var opcode = frame.opcode\n"
" var payload = frame.payload\n"
" var fin = frame.fin\n"
"\n"
" if (opcode == 8) {\n"
" _isOpen = false\n"
" return frame\n"
" }\n"
"\n"
" if (opcode == 9) {\n"
" sendFrame_(10, payload)\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 10) {\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 0) {\n"
" for (b in payload) {\n"
" _fragmentBuffer.add(b)\n"
" }\n"
" if (fin) {\n"
" var completePayload = _fragmentBuffer\n"
" var completeOpcode = _fragmentOpcode\n"
" _fragmentBuffer = []\n"
" _fragmentOpcode = null\n"
" return WebSocketMessage.new_(completeOpcode, completePayload, true)\n"
" }\n"
" continue\n"
" }\n"
"\n"
" if (opcode == 1 || opcode == 2) {\n"
" if (fin) {\n"
" return frame\n"
" } else {\n"
" _fragmentOpcode = opcode\n"
" _fragmentBuffer = []\n"
" for (b in payload) {\n"
" _fragmentBuffer.add(b)\n"
" }\n"
" continue\n"
" }\n"
" }\n"
"\n"
" Fiber.abort(\"Unknown opcode: %(opcode)\")\n"
" }\n"
" }\n"
"\n"
" iterate(callback) {\n"
" while (_isOpen) {\n"
" var msg = receive()\n"
" if (msg == null || msg.isClose) break\n"
" callback.call(msg)\n"
" }\n"
" }\n"
"\n"
" sendJson(data) {\n"
" if (!_isOpen) Fiber.abort(\"WebSocket is not open.\")\n"
" var text = Json.stringify(data)\n"
" sendText(text)\n"
" }\n"
"\n"
" receiveJson() {\n"
" var msg = receive()\n"
" if (msg == null) return null\n"
" if (msg.isClose) return msg\n"
" if (!msg.isText) Fiber.abort(\"Expected text frame for JSON, got binary.\")\n"
" return Json.parse(msg.text)\n"
" }\n"
"\n"
" receiveText() {\n"
" var msg = receive()\n"
" if (msg == null) return null\n"
" if (msg.isClose) return null\n"
" if (!msg.isText) return null\n"
" return msg.text\n"
" }\n"
"\n"
" receiveBinary() {\n"
" var msg = receive()\n"
" if (msg == null) return null\n"
" if (msg.isClose) return null\n"
" if (!msg.isBinary) return null\n"
" return msg.bytes\n"
" }\n"
"\n"
" sendFrame_(opcode, payload) {\n"
" var frame = encodeFrame_(opcode, payload)\n"
" _socket.write(Bytes.fromList(frame))\n"
" }\n"
"\n"
" encodeFrame_(opcode, payload) {\n"
" var frame = []\n"
" frame.add(0x80 | opcode)\n"
"\n"
" var len = payload.count\n"
"\n"
" if (len < 126) {\n"
" frame.add(len)\n"
" } else if (len < 65536) {\n"
" frame.add(126)\n"
" frame.add((len >> 8) & 0xFF)\n"
" frame.add(len & 0xFF)\n"
" } else {\n"
" frame.add(127)\n"
" for (i in 0...4) frame.add(0)\n"
" frame.add((len >> 24) & 0xFF)\n"
" frame.add((len >> 16) & 0xFF)\n"
" frame.add((len >> 8) & 0xFF)\n"
" frame.add(len & 0xFF)\n"
" }\n"
"\n"
" for (b in payload) frame.add(b)\n"
" return frame\n"
" }\n"
"\n"
" readFrame_() {\n"
" var header = readBytes_(2)\n"
" if (header == null || Bytes.length(header) < 2) return null\n"
"\n"
" var headerList = Bytes.toList(header)\n"
" var fin = (headerList[0] & 0x80) != 0\n"
" var opcode = headerList[0] & 0x0F\n"
" var masked = (headerList[1] & 0x80) != 0\n"
" var len = headerList[1] & 0x7F\n"
"\n"
" if (len == 126) {\n"
" var ext = readBytes_(2)\n"
" if (ext == null || Bytes.length(ext) < 2) return null\n"
" var extList = Bytes.toList(ext)\n"
" len = (extList[0] << 8) | extList[1]\n"
" } else if (len == 127) {\n"
" var ext = readBytes_(8)\n"
" if (ext == null || Bytes.length(ext) < 8) return null\n"
" var extList = Bytes.toList(ext)\n"
" len = 0\n"
" for (i in 4...8) {\n"
" len = (len << 8) | extList[i]\n"
" }\n"
" }\n"
"\n"
" var mask = null\n"
" if (masked) {\n"
" mask = readBytes_(4)\n"
" if (mask == null || Bytes.length(mask) < 4) return null\n"
" }\n"
"\n"
" var payload = \"\"\n"
" if (len > 0) {\n"
" payload = readBytes_(len)\n"
" if (payload == null) return null\n"
" }\n"
"\n"
" if (masked && mask != null) {\n"
" payload = Bytes.xorMask(payload, mask)\n"
" }\n"
"\n"
" return WebSocketMessage.new_(opcode, Bytes.toList(payload), fin)\n"
" }\n"
"\n"
" readBytes_(count) {\n"
" while (Bytes.length(_readBuffer) < count) {\n"
" var chunk = _socket.read()\n"
" if (chunk == null || chunk.bytes.count == 0) {\n"
" if (Bytes.length(_readBuffer) == 0) return null\n"
" var result = _readBuffer\n"
" _readBuffer = \"\"\n"
" return result\n"
" }\n"
" _readBuffer = Bytes.concat(_readBuffer, chunk)\n"
" }\n"
" var result = Bytes.slice(_readBuffer, 0, count)\n"
" _readBuffer = Bytes.slice(_readBuffer, count, Bytes.length(_readBuffer))\n"
" return result\n"
" }\n"
"\n"
" computeAcceptKey_(key) {\n"
" var magic = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n"
" var combined = key + magic\n"
" var sha1 = Hash.sha1(combined)\n"
" var sha1Str = bytesToString_(sha1)\n"
" return Base64.encode(sha1Str)\n"
" }\n"
"\n"
" bytesToString_(bytes) {\n"
" var parts = []\n"
" for (b in bytes) {\n"
" parts.add(String.fromByte(b))\n"
" }\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" stringToBytes_(str) {\n"
" var bytes = []\n"
" for (b in str.bytes) {\n"
" bytes.add(b)\n"
" }\n"
" return bytes\n"
" }\n"
"}\n"
"\n"
"class Session {\n"
" construct new_(id, data) {\n"
" _id = id\n"
@ -347,6 +646,7 @@ static const char* webModuleSource =
" put(request) { methodNotAllowed_() }\n"
" delete(request) { methodNotAllowed_() }\n"
" patch(request) { methodNotAllowed_() }\n"
" websocket(request) { methodNotAllowed_() }\n"
"\n"
" methodNotAllowed_() {\n"
" var r = Response.new()\n"
@ -355,7 +655,15 @@ static const char* webModuleSource =
" return r\n"
" }\n"
"\n"
" isWebSocketRequest_(request) {\n"
" var upgrade = request.header(\"Upgrade\")\n"
" var connection = request.header(\"Connection\")\n"
" if (upgrade == null || connection == null) return false\n"
" return Str.toLower(upgrade) == \"websocket\" && Str.toLower(connection).contains(\"upgrade\")\n"
" }\n"
"\n"
" dispatch(request) {\n"
" if (isWebSocketRequest_(request)) return websocket(request)\n"
" if (request.method == \"GET\") return get(request)\n"
" if (request.method == \"POST\") return post(request)\n"
" if (request.method == \"PUT\") return put(request)\n"
@ -503,6 +811,14 @@ static const char* webModuleSource =
" var request = Request.new_(method, path, query, headers, body, {}, socket)\n"
" loadSession_(request)\n"
"\n"
" if (isWebSocketRequest_(headers)) {\n"
" if (_wsHandlers.containsKey(path)) {\n"
" var handler = _wsHandlers[path]\n"
" var result = handler.call(request)\n"
" return\n"
" }\n"
" }\n"
"\n"
" var response = null\n"
"\n"
" for (sp in _staticPrefixes) {\n"
@ -535,11 +851,27 @@ static const char* webModuleSource =
" }\n"
" }\n"
"\n"
" if (response is WebSocketResponse) {\n"
" return\n"
" }\n"
"\n"
" saveSession_(request, response)\n"
" socket.write(response.build())\n"
" socket.close()\n"
" }\n"
"\n"
" isWebSocketRequest_(headers) {\n"
" var upgrade = null\n"
" var connection = null\n"
" for (key in headers.keys) {\n"
" var lower = Str.toLower(key)\n"
" if (lower == \"upgrade\") upgrade = headers[key]\n"
" if (lower == \"connection\") connection = headers[key]\n"
" }\n"
" if (upgrade == null || connection == null) return false\n"
" return Str.toLower(upgrade) == \"websocket\" && Str.toLower(connection).contains(\"upgrade\")\n"
" }\n"
"\n"
" loadSession_(request) {\n"
" var sessionId = request.cookies[_sessionCookieName]\n"
" if (sessionId != null) {\n"

63
test/faker/basic.wren vendored Normal file
View File

@ -0,0 +1,63 @@
// retoor <retoor@molodetz.nl>
import "faker" for Faker
var name = Faker.name()
System.print(name.contains(" ")) // expect: true
var email = Faker.email()
System.print(email.contains("@")) // expect: true
var firstName = Faker.firstName()
System.print(firstName.count > 0) // expect: true
var lastName = Faker.lastName()
System.print(lastName.count > 0) // expect: true
var city = Faker.city()
System.print(city.count > 0) // expect: true
var address = Faker.address()
System.print(address.contains(",")) // expect: true
var ipv4 = Faker.ipv4()
System.print(ipv4.contains(".")) // expect: true
var uuid = Faker.uuid()
System.print(uuid.count == 36) // expect: true
var phone = Faker.phoneNumber()
System.print(phone.contains("(")) // expect: true
var company = Faker.company()
System.print(company.count > 0) // expect: true
var job = Faker.jobTitle()
System.print(job.count > 0) // expect: true
var word = Faker.word()
System.print(word.count > 0) // expect: true
var sentence = Faker.sentence()
System.print(sentence.endsWith(".")) // expect: true
var hexColor = Faker.hexColor()
System.print(hexColor.startsWith("#")) // expect: true
System.print(hexColor.count == 7) // expect: true
var price = Faker.price()
System.print(price >= 1) // expect: true
System.print(price <= 1000) // expect: true
var bool1 = Faker.boolean()
System.print(bool1 is Bool) // expect: true
var digits = Faker.numerify("###-##-####")
System.print(digits.count == 11) // expect: true
System.print(digits[3] == "-") // expect: true
var letters = Faker.letterify("???-???")
System.print(letters.count == 7) // expect: true
System.print(letters[3] == "-") // expect: true
System.print("All basic tests passed") // expect: All basic tests passed

28
test/faker/seeding.wren vendored Normal file
View File

@ -0,0 +1,28 @@
// retoor <retoor@molodetz.nl>
import "faker" for Faker
Faker.seed(12345)
var name1 = Faker.name()
var email1 = Faker.email()
var city1 = Faker.city()
var int1 = Faker.randomInt(1, 100)
Faker.seed(12345)
var name2 = Faker.name()
var email2 = Faker.email()
var city2 = Faker.city()
var int2 = Faker.randomInt(1, 100)
System.print(name1 == name2) // expect: true
System.print(email1 == email2) // expect: true
System.print(city1 == city2) // expect: true
System.print(int1 == int2) // expect: true
Faker.seed(99999)
var name3 = Faker.name()
System.print(name1 != name3) // expect: true
Faker.reset()
System.print("Seeding tests passed") // expect: Seeding tests passed

16
test/subprocess/popen.wren vendored Normal file
View File

@ -0,0 +1,16 @@
// retoor <retoor@molodetz.nl>
import "subprocess" for Popen
var p1 = Popen.new(["echo", "test"])
System.print(p1.pid > 0) // expect: true
var out1 = p1.stdout.read()
System.print(out1.trim()) // expect: test
System.print(p1.wait()) // expect: 0
var p2 = Popen.new(["cat"])
p2.stdin.write("hello")
p2.stdin.close()
var out2 = p2.stdout.read()
System.print(out2) // expect: hello
System.print(p2.wait()) // expect: 0

9
test/subprocess/popen_basic.wren vendored Normal file
View File

@ -0,0 +1,9 @@
// retoor <retoor@molodetz.nl>
import "subprocess" for Popen
var p = Popen.new(["echo", "hello"])
var output = p.stdout.read()
System.print(output.trim()) // expect: hello
var code = p.wait()
System.print(code) // expect: 0

11
test/subprocess/popen_pty.wren vendored Normal file
View File

@ -0,0 +1,11 @@
// retoor <retoor@molodetz.nl>
import "subprocess" for Popen
var p = Popen.new(["sh"], true)
System.print(p.pid > 0) // expect: true
p.stdin.write("echo pty_test\n")
p.stdin.write("exit\n")
var out = p.stdout.read()
System.print(out.contains("pty_test")) // expect: true
System.print(p.wait()) // expect: 0

25
test/web/client_batch.wren vendored Normal file
View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler, Future
var items = ["a", "b", "c"]
var futures = []
for (item in items) {
futures.add(async { "processed_" + item })
}
System.print(futures.count) // expect: 3
System.print(futures[0] is Future) // expect: true
System.print(futures[1] is Future) // expect: true
System.print(futures[2] is Future) // expect: true
var results = []
for (f in futures) {
results.add(await f)
}
System.print(results.count) // expect: 3
System.print(results[0]) // expect: processed_a
System.print(results[1]) // expect: processed_b
System.print(results[2]) // expect: processed_c

24
test/web/client_concurrent.wren vendored Normal file
View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler, Future
System.print("Creating futures") // expect: Creating futures
var future1 = async { {"url": "url1", "status": 200} }
var future2 = async { {"url": "url2", "status": 201} }
var future3 = async { {"url": "url3", "status": 202} }
System.print(future1 is Future) // expect: true
System.print(future2 is Future) // expect: true
System.print(future3 is Future) // expect: true
var result1 = await future1
var result2 = await future2
var result3 = await future3
System.print(result1["url"]) // expect: url1
System.print(result1["status"]) // expect: 200
System.print(result2["url"]) // expect: url2
System.print(result2["status"]) // expect: 201
System.print(result3["url"]) // expect: url3
System.print(result3["status"]) // expect: 202

13
test/web/future_state.wren vendored Normal file
View File

@ -0,0 +1,13 @@
// retoor <retoor@molodetz.nl>
import "scheduler" for Scheduler, Future
var future = async { 42 }
System.print(future is Future) // expect: true
var result = await future
System.print(result) // expect: 42
System.print(future.isDone) // expect: true
System.print(future.result) // expect: 42

12
test/web/websocket_response.wren vendored Normal file
View File

@ -0,0 +1,12 @@
// retoor <retoor@molodetz.nl>
import "web" for WebSocketResponse
var ws = WebSocketResponse.new()
System.print(ws.isPrepared) // expect: false
System.print(ws.isOpen) // expect: false
var ws2 = WebSocketResponse.new()
System.print(ws2 is WebSocketResponse) // expect: true
System.print(ws2.isPrepared == false) // expect: true
System.print(ws2.isOpen == false) // expect: true

115
util/build_manual.py Executable file
View File

@ -0,0 +1,115 @@
#!/usr/bin/env python3
# retoor <retoor@molodetz.nl>
import json
import re
import shutil
import yaml
from pathlib import Path
from jinja2 import Environment, FileSystemLoader, ChoiceLoader
class ManualBuilder:
def __init__(self):
self.root = Path(__file__).parent.parent
self.src = self.root / 'manual_src'
self.output = self.root / 'bin' / 'manual'
self.site = self.load_yaml('data/site.yaml')
self.nav = self.load_yaml('data/navigation.yaml')
templates_loader = FileSystemLoader(str(self.src / 'templates'))
pages_loader = FileSystemLoader(str(self.src))
self.env = Environment(
loader=ChoiceLoader([templates_loader, pages_loader]),
trim_blocks=True,
lstrip_blocks=True
)
self.env.globals.update({
'site': self.site,
'nav': self.nav
})
def load_yaml(self, path):
with open(self.src / path) as f:
return yaml.safe_load(f)
def build(self):
if self.output.exists():
shutil.rmtree(self.output)
self.output.mkdir(parents=True)
search_index = self.build_search_index()
self.env.globals['search_index_json'] = json.dumps(search_index)
self.build_pages()
self.copy_static()
print(f"Built manual to {self.output}")
def build_pages(self):
pages_dir = self.src / 'pages'
for html_file in pages_dir.rglob('*.html'):
rel_path = html_file.relative_to(pages_dir)
self.build_page(html_file, rel_path)
def build_page(self, src_path, rel_path):
template_path = f'pages/{rel_path}'
template = self.env.get_template(template_path)
depth = len(rel_path.parts) - 1
static_prefix = '../' * depth if depth > 0 else './'
html = template.render(
current_path=str(rel_path),
static_prefix=static_prefix,
depth=depth
)
out_path = self.output / rel_path
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(html)
print(f" {rel_path}")
def copy_static(self):
static_src = self.src / 'static'
for item in static_src.rglob('*'):
if item.is_file():
rel = item.relative_to(static_src)
dest = self.output / rel
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(item, dest)
def build_search_index(self):
index = {'pages': []}
for section in self.nav['sections']:
section_title = section['title']
section_dir = section['directory']
for page in section.get('pages', []):
url = f"{section_dir}/{page['file']}.html"
index['pages'].append({
'url': url,
'title': page['title'],
'section': section_title,
'description': page.get('description', ''),
'methods': page.get('methods', []),
'content': ''
})
(self.output / 'search-index.json').write_text(
json.dumps(index, indent=2)
)
return index
def main():
builder = ManualBuilder()
builder.build()
if __name__ == '__main__':
main()