Update.
This commit is contained in:
parent
a830238d11
commit
1ee28a1644
5
Makefile
5
Makefile
@ -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
117
example/faker_demo.wren
vendored
Normal 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
58
example/subprocess_async_demo.wren
vendored
Normal 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
86
example/subprocess_concurrent_demo.wren
vendored
Normal 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
74
example/subprocess_fiber_demo.wren
vendored
Normal 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!")
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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("<script>var ws=new WebSocket('ws://localhost:8080/ws');ws.onmessage=e=>console.log(e.data);ws.onopen=()=>ws.send('hello');</script>")
|
||||
})
|
||||
|
||||
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 && data["type"] == "ping") {
|
||||
ws.sendJson({"type": "pong", "timestamp": data["timestamp"]})
|
||||
} else if (data is Map && 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("<h1>Chat Room</h1><p>Connect via WebSocket</p>")
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@ -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">
|
||||
|
||||
548
manual/contributing/async-patterns.html
Normal file
548
manual/contributing/async-patterns.html
Normal 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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <uv.h>
|
||||
#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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <uv.h>
|
||||
#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 <retoor@molodetz.nl>
|
||||
|
||||
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>
|
||||
540
manual/contributing/c-backed-module.html
Normal file
540
manual/contributing/c-backed-module.html
Normal 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/<name>.wren</code> with foreign method declarations:</p>
|
||||
|
||||
<pre><code>// retoor <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#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 <retoor@molodetz.nl>
|
||||
|
||||
#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 $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"</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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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 <retoor@molodetz.nl>
|
||||
|
||||
#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/<name>.wren</code> with foreign declarations</li>
|
||||
<li>Generated <code>.wren.inc</code></li>
|
||||
<li>Created <code>src/module/<name>.c</code></li>
|
||||
<li>Created <code>src/module/<name>.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>
|
||||
498
manual/contributing/documentation.html
Normal file
498
manual/contributing/documentation.html
Normal 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><!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>[Page Title] - 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">
|
||||
<!-- Auto-generated by sync_sidebar.py -->
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">API Reference</a>
|
||||
<span class="separator">/</span>
|
||||
<span>[Current Page]</span>
|
||||
</nav>
|
||||
<article>
|
||||
<h1>[Page Title]</h1>
|
||||
<!-- Content -->
|
||||
</article>
|
||||
<footer class="page-footer">
|
||||
<a href="[prev].html" class="prev">[Previous]</a>
|
||||
<a href="[next].html" class="next">[Next]</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html></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><h1>modulename</h1>
|
||||
<p>Description paragraph.</p>
|
||||
<pre><code>import "modulename" for ClassName</code></pre>
|
||||
|
||||
<div class="class-header"><h2>Class: ClassName</h2></div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">ClassName.methodName</span>(<span class="param">arg</span>)
|
||||
&rarr; <span class="type">ReturnType</span>
|
||||
</div>
|
||||
<p>Method description.</p>
|
||||
|
||||
<h3>Examples</h3>
|
||||
<pre><code>import "modulename" for ClassName
|
||||
var result = ClassName.methodName("hello")
|
||||
System.print(result)</code></pre></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><div class="method-signature">
|
||||
<span class="method-name">ClassName.methodName</span>(<span class="param">arg1</span>, <span class="param">arg2</span>)
|
||||
&rarr; <span class="type">String</span>
|
||||
</div></code></pre>
|
||||
|
||||
<p>Instance method:</p>
|
||||
<pre><code><div class="method-signature">
|
||||
<span class="method-name">instance.methodName</span>(<span class="param">arg</span>)
|
||||
&rarr; <span class="type">Bool</span>
|
||||
</div></code></pre>
|
||||
|
||||
<p>Property (getter):</p>
|
||||
<pre><code><div class="method-signature">
|
||||
<span class="method-name">instance.propertyName</span>
|
||||
&rarr; <span class="type">Num</span>
|
||||
</div></code></pre>
|
||||
|
||||
<h2>Admonition Blocks</h2>
|
||||
|
||||
<p>Use admonitions for notes, warnings, and tips:</p>
|
||||
|
||||
<h3>Note</h3>
|
||||
<pre><code><div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Additional information.</p>
|
||||
</div></code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Additional information.</p>
|
||||
</div>
|
||||
|
||||
<h3>Warning</h3>
|
||||
<pre><code><div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Important caution.</p>
|
||||
</div></code></pre>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Important caution.</p>
|
||||
</div>
|
||||
|
||||
<h3>Tip</h3>
|
||||
<pre><code><div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Helpful suggestion.</p>
|
||||
</div></code></pre>
|
||||
|
||||
<h2>Parameter Lists</h2>
|
||||
|
||||
<p>For methods with complex parameters:</p>
|
||||
|
||||
<pre><code><div class="param-list">
|
||||
<div class="param-item">
|
||||
<span class="param-name">path</span>
|
||||
<span class="param-type">String</span>
|
||||
<p>The file path to read.</p>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<span class="param-name">encoding</span>
|
||||
<span class="param-type">String</span>
|
||||
<p>The character encoding (default: "utf-8").</p>
|
||||
</div>
|
||||
</div></code></pre>
|
||||
|
||||
<h2>Tables</h2>
|
||||
|
||||
<p>Use tables for options, type mappings, or comparisons:</p>
|
||||
|
||||
<pre><code><table>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>"r"</code></td>
|
||||
<td>Read mode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>"w"</code></td>
|
||||
<td>Write mode</td>
|
||||
</tr>
|
||||
</table></code></pre>
|
||||
|
||||
<h2>Footer Navigation</h2>
|
||||
|
||||
<p>Every page has previous/next navigation:</p>
|
||||
|
||||
<pre><code><footer class="page-footer">
|
||||
<a href="previous.html" class="prev">Previous Page</a>
|
||||
<a href="next.html" class="next">Next Page</a>
|
||||
</footer></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><nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">API Reference</a>
|
||||
<span class="separator">/</span>
|
||||
<span>io</span>
|
||||
</nav></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><nav class="sidebar-nav"></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/<name>.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><!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>mymodule - 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">
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">API Reference</a>
|
||||
<span class="separator">/</span>
|
||||
<span>mymodule</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>mymodule</h1>
|
||||
|
||||
<p>The <code>mymodule</code> module provides...</p>
|
||||
|
||||
<pre><code>import "mymodule" for MyClass</code></pre>
|
||||
|
||||
<h2>MyClass</h2>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">MyClass.process</span>(<span class="param">input</span>)
|
||||
&rarr; <span class="type">String</span>
|
||||
</div>
|
||||
<p>Processes the input and returns a result.</p>
|
||||
|
||||
<h3>Examples</h3>
|
||||
<pre><code>import "mymodule" for MyClass
|
||||
|
||||
var result = MyClass.process("hello")
|
||||
System.print(result)</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="math.html" class="prev">math</a>
|
||||
<a href="net.html" class="next">net</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html></code></pre>
|
||||
|
||||
<h2>Checklist for New Documentation</h2>
|
||||
|
||||
<ul>
|
||||
<li>Author comment on line 2</li>
|
||||
<li>Correct title in <code><title></code> and <code><h1></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>
|
||||
511
manual/contributing/foreign-classes.html
Normal file
511
manual/contributing/foreign-classes.html
Normal 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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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>
|
||||
254
manual/contributing/index.html
Normal file
254
manual/contributing/index.html
Normal 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/<name>.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/<name>/</code></li>
|
||||
<li>Create example in <code>example/<name>_demo.wren</code></li>
|
||||
<li>Create <code>manual/api/<name>.html</code></li>
|
||||
<li>Run <code>make sync-manual</code></li>
|
||||
<li>Verify with <code>python3 util/test.py <name></code></li>
|
||||
</ol>
|
||||
|
||||
<h3>C-Backed Module</h3>
|
||||
<ol>
|
||||
<li>All pure-Wren steps, plus:</li>
|
||||
<li>Create <code>src/module/<name>.c</code> with foreign method implementations</li>
|
||||
<li>Create <code>src/module/<name>.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/
|
||||
<name>.wren # Wren interface source
|
||||
<name>.wren.inc # Generated C string literal
|
||||
<name>.c # C implementation (if foreign methods)
|
||||
<name>.h # C header (if foreign methods)
|
||||
|
||||
test/
|
||||
<name>/
|
||||
<feature>.wren # Test files with annotations
|
||||
|
||||
example/
|
||||
<name>_demo.wren # Usage demonstrations
|
||||
|
||||
manual/
|
||||
api/<name>.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>
|
||||
349
manual/contributing/module-overview.html
Normal file
349
manual/contributing/module-overview.html
Normal 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/
|
||||
<name>.wren # Wren interface source
|
||||
<name>.wren.inc # Generated C string literal (do not edit)
|
||||
<name>.c # C implementation (only for foreign methods)
|
||||
<name>.h # C header (only for foreign methods)
|
||||
|
||||
test/
|
||||
<modulename>/
|
||||
<testname>.wren # Test files with inline annotations
|
||||
|
||||
example/
|
||||
<modulename>_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/<name>.wren</code></td>
|
||||
<td>Always</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Generated C string</td>
|
||||
<td><code>src/module/<name>.wren.inc</code></td>
|
||||
<td>Always</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C implementation</td>
|
||||
<td><code>src/module/<name>.c</code></td>
|
||||
<td>Only if foreign methods</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C header</td>
|
||||
<td><code>src/module/<name>.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>
|
||||
375
manual/contributing/pure-wren-module.html
Normal file
375
manual/contributing/pure-wren-module.html
Normal 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/<name>.wren</code> with your module implementation:</p>
|
||||
|
||||
<pre><code>// retoor <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>\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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
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/<name>.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/<name>/</code></li>
|
||||
<li>Created example in <code>example/<name>_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>
|
||||
442
manual/contributing/testing.html
Normal file
442
manual/contributing/testing.html
Normal 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/
|
||||
<modulename>/
|
||||
<feature>.wren # Functional tests
|
||||
error_<scenario>.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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
class Test {
|
||||
foo( // expect error
|
||||
}</code></pre>
|
||||
|
||||
<p>Or specify a different line number:</p>
|
||||
|
||||
<pre><code>// retoor <retoor@molodetz.nl>
|
||||
|
||||
// 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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
// 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 <retoor@molodetz.nl>
|
||||
// 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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
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 <retoor@molodetz.nl>
|
||||
|
||||
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>
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
1332
manual/tutorials/web-server.html
Normal file
1332
manual/tutorials/web-server.html
Normal file
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
|
||||
@ -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
890
src/module/faker.wren
vendored
Normal 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
894
src/module/faker.wren.inc
Normal 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";
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
46
src/module/subprocess.wren
vendored
46
src/module/subprocess.wren
vendored
@ -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) }
|
||||
}
|
||||
|
||||
@ -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
332
src/module/web.wren
vendored
@ -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) {
|
||||
|
||||
@ -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
63
test/faker/basic.wren
vendored
Normal 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
28
test/faker/seeding.wren
vendored
Normal 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
16
test/subprocess/popen.wren
vendored
Normal 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
9
test/subprocess/popen_basic.wren
vendored
Normal 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
11
test/subprocess/popen_pty.wren
vendored
Normal 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
25
test/web/client_batch.wren
vendored
Normal 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
24
test/web/client_concurrent.wren
vendored
Normal 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
13
test/web/future_state.wren
vendored
Normal 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
12
test/web/websocket_response.wren
vendored
Normal 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
115
util/build_manual.py
Executable 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()
|
||||
Loading…
Reference in New Issue
Block a user