Compare commits
4 Commits
1a0f7f51a1
...
e4a94d576b
| Author | SHA1 | Date | |
|---|---|---|---|
| e4a94d576b | |||
| 2e425571ca | |||
| 957e3a4762 | |||
| 8c1a721c4c |
24
Makefile
Normal file
24
Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
.PHONY: build tests clean debug
|
||||
|
||||
build:
|
||||
cd projects/make && $(MAKE) -f wren_cli.make -j $$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
|
||||
|
||||
debug:
|
||||
cd projects/make && $(MAKE) -f wren_cli.make config=debug_64bit -j $$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
|
||||
|
||||
tests: build
|
||||
python3 util/test.py
|
||||
|
||||
create_docs:
|
||||
wren apps/merge_docs.wren
|
||||
|
||||
create_training_data:
|
||||
python create_training_data.py --manual-html manual.md --out-train training_data.jsonl --out-val training_data_val.jsonl
|
||||
|
||||
install:
|
||||
cp bin/wren_cli /usr/local/bin/wren
|
||||
|
||||
clean:
|
||||
cd projects/make && $(MAKE) -f wren_cli.make clean
|
||||
78
README.md
78
README.md
@ -1,59 +1,63 @@
|
||||
# Wren CLI
|
||||
# Wren-CLI
|
||||
|
||||
## The CLI project is a small and simple REPL and CLI tool for running Wren scripts.
|
||||
retoor <retoor@molodetz.nl>
|
||||
|
||||
It is backed by [libuv](http://libuv.org/) to implement IO functionality, and is a work in progress.
|
||||
A command-line interface and REPL for the [Wren](http://wren.io) programming language. Built on [libuv](http://libuv.org/) for asynchronous I/O with native support for HTTP/HTTPS, WebSockets, TLS, SQLite, JSON, regex, and more.
|
||||
|
||||
For documentation and usage, see http://wren.io/cli
|
||||
For more information about Wren, the language used by the CLI, see http://wren.io
|
||||
## Prerequisites
|
||||
|
||||
Like with Wren itself, we welcome [contributions][contribute].
|
||||
- C compiler (GCC or Clang)
|
||||
- Make
|
||||
- OpenSSL development libraries
|
||||
- Python 3 (for tests)
|
||||
|
||||
[contribute]: http://wren.io/contributing.html
|
||||
## Build
|
||||
|
||||
[](https://travis-ci.org/wren-lang/wren-cli)
|
||||
```bash
|
||||
git clone https://github.com/wren-lang/wren-cli.git
|
||||
cd wren-cli
|
||||
make build
|
||||
```
|
||||
|
||||
---
|
||||
Output binary: `bin/wren_cli`
|
||||
|
||||
## To build the Wren CLI
|
||||
### Platform-Specific Builds
|
||||
|
||||
### Windows
|
||||
| Platform | Command |
|
||||
|----------|---------|
|
||||
| Linux | `make build` |
|
||||
| macOS | `cd projects/make.mac && make` |
|
||||
| FreeBSD | `cd projects/make.bsd && gmake` |
|
||||
|
||||
The `projects/vs20xx` folders contain Visual Studio projects.
|
||||
## Test
|
||||
|
||||
### macOS
|
||||
```bash
|
||||
make tests
|
||||
```
|
||||
|
||||
The `projects/xcode` folder contains an Xcode project.
|
||||
Run a specific test suite:
|
||||
|
||||
The `projects/make.mac` folder also contains a `make` project.
|
||||
From that folder, run `make`.
|
||||
```bash
|
||||
python3 util/test.py json
|
||||
```
|
||||
|
||||
`cd projects/make.mac`
|
||||
`make`
|
||||
## Usage
|
||||
|
||||
### Linux
|
||||
```bash
|
||||
bin/wren_cli script.wren # Execute a script
|
||||
bin/wren_cli # Start the REPL
|
||||
```
|
||||
|
||||
The `projects/make` folder contains a `make` project.
|
||||
From that folder, run `make`.
|
||||
## Modules
|
||||
|
||||
`cd projects/make`
|
||||
`make`
|
||||
**Core**: io, os, scheduler, timer, net, repl
|
||||
|
||||
### FreeBSD
|
||||
**Extended**: json, env, math, base64, datetime, dns, signal, subprocess, crypto, regex, http, tls, sqlite, websocket, jinja, pathlib, tempfile, uuid
|
||||
|
||||
The `projects/make.bsd` folder contains a `make` project.
|
||||
From that folder, run `make`.
|
||||
## Documentation
|
||||
|
||||
`cd projects/make.bsd`
|
||||
`gmake`
|
||||
|
||||
## Alternative build options
|
||||
|
||||
The projects are generated by premake, found inside `projects/premake`.
|
||||
You can use premake5 (alpha 14 was used) to generate other projects.
|
||||
Generate other system's projects via the premake `--os` flag,
|
||||
i.e if on linux, `premake vs2019 --os=windows` is valid.
|
||||
|
||||
---
|
||||
See the `manual/` directory for the full reference including API docs, tutorials, and how-to guides.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
31
apps/merge_all_wren.wren
vendored
Normal file
31
apps/merge_all_wren.wren
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import "io" for File, Directory
|
||||
|
||||
class WrenFileReader {
|
||||
static listWrenFiles(dir) {
|
||||
var files = []
|
||||
var entries = Directory.list(dir)
|
||||
for (entry in entries) {
|
||||
var fullPath = "%(dir)/%(entry)"
|
||||
if (Directory.exists(fullPath)) {
|
||||
files.addAll(listWrenFiles(fullPath))
|
||||
} else if (entry.endsWith(".wren")) {
|
||||
files.add(fullPath)
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
static readFiles(files) {
|
||||
for (file in files) {
|
||||
System.print("--- %(file) ---")
|
||||
System.print(File.read(file))
|
||||
}
|
||||
}
|
||||
|
||||
static main() {
|
||||
var wrenFiles = listWrenFiles(".")
|
||||
readFiles(wrenFiles)
|
||||
}
|
||||
}
|
||||
|
||||
WrenFileReader.main()
|
||||
42
apps/merge_docs.wren
vendored
Normal file
42
apps/merge_docs.wren
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
import "pathlib" for Path
|
||||
|
||||
var startTime = System.clock
|
||||
|
||||
var all = []
|
||||
|
||||
all.add("# Customized version of the wren programming language.")
|
||||
all.add("This is the documentation of the highly customized wren programming language.")
|
||||
all.add("")
|
||||
all.add("## Extensions")
|
||||
all.add("The following futures are built on top of the native wren interpreter:")
|
||||
all.add(" - wdantic library based on PyDantic")
|
||||
all.add(" - dataset library based on Python dataset module (ornm for lazy people)")
|
||||
all.add(" - aiohttp library based fromework called `web`")
|
||||
all.add(" - pathlib library based on Python pathlib")
|
||||
all.add(" - regex validator library")
|
||||
all.add(" - json encoder / decoder library")
|
||||
all.add(" - networking library")
|
||||
all.add(" - html library")
|
||||
all.add(" - websocket library")
|
||||
all.add("")
|
||||
all.add("## Documentation")
|
||||
|
||||
for (path in Path.new("manual").rglob("*.html")) {
|
||||
all.add("### " + path.name)
|
||||
all.add("```html```")
|
||||
all.add(path.readText())
|
||||
all.add("```")
|
||||
all.add("---")
|
||||
all.add("")
|
||||
}
|
||||
|
||||
var destination = Path.new("manual.md")
|
||||
destination.writeText(all.join("\n"))
|
||||
|
||||
|
||||
|
||||
var duration = System.clock - startTime
|
||||
|
||||
System.print("Written to: manual.md.")
|
||||
System.print("Duration: " + duration.toString + " Ms.")
|
||||
System.print("Size: " + destination.stat().size.toString + " bytes.")
|
||||
61
deps/wren/src/vm/wren_compiler.c
vendored
61
deps/wren/src/vm/wren_compiler.c
vendored
@ -108,6 +108,8 @@ typedef enum
|
||||
TOKEN_TRUE,
|
||||
TOKEN_VAR,
|
||||
TOKEN_WHILE,
|
||||
TOKEN_ASYNC,
|
||||
TOKEN_AWAIT,
|
||||
|
||||
TOKEN_FIELD,
|
||||
TOKEN_STATIC_FIELD,
|
||||
@ -619,6 +621,8 @@ static Keyword keywords[] =
|
||||
{"true", 4, TOKEN_TRUE},
|
||||
{"var", 3, TOKEN_VAR},
|
||||
{"while", 5, TOKEN_WHILE},
|
||||
{"async", 5, TOKEN_ASYNC},
|
||||
{"await", 5, TOKEN_AWAIT},
|
||||
{NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array.
|
||||
};
|
||||
|
||||
@ -1746,6 +1750,8 @@ static void expression(Compiler* compiler);
|
||||
static void statement(Compiler* compiler);
|
||||
static void definition(Compiler* compiler);
|
||||
static void parsePrecedence(Compiler* compiler, Precedence precedence);
|
||||
static void async_(Compiler* compiler, bool canAssign);
|
||||
static void await_(Compiler* compiler, bool canAssign);
|
||||
|
||||
// Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF
|
||||
// instruction with an offset that jumps to the current end of bytecode.
|
||||
@ -2499,6 +2505,59 @@ static void this_(Compiler* compiler, bool canAssign)
|
||||
loadThis(compiler);
|
||||
}
|
||||
|
||||
static int resolveScheduler(Compiler* compiler)
|
||||
{
|
||||
int symbol = wrenSymbolTableFind(
|
||||
&compiler->parser->module->variableNames,
|
||||
"Scheduler", 9);
|
||||
|
||||
if (symbol == -1)
|
||||
{
|
||||
symbol = wrenDeclareVariable(compiler->parser->vm,
|
||||
compiler->parser->module,
|
||||
"Scheduler", 9,
|
||||
compiler->parser->previous.line);
|
||||
if (symbol == -2)
|
||||
{
|
||||
error(compiler, "Too many module variables defined.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
static void await_(Compiler* compiler, bool canAssign)
|
||||
{
|
||||
int schedulerSymbol = resolveScheduler(compiler);
|
||||
if (schedulerSymbol == -1) return;
|
||||
|
||||
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, schedulerSymbol);
|
||||
|
||||
ignoreNewlines(compiler);
|
||||
expression(compiler);
|
||||
|
||||
callMethod(compiler, 1, "await_(_)", 9);
|
||||
}
|
||||
|
||||
static void async_(Compiler* compiler, bool canAssign)
|
||||
{
|
||||
int schedulerSymbol = resolveScheduler(compiler);
|
||||
if (schedulerSymbol == -1) return;
|
||||
|
||||
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, schedulerSymbol);
|
||||
|
||||
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after 'async'.");
|
||||
|
||||
Compiler fnCompiler;
|
||||
initCompiler(&fnCompiler, compiler->parser, compiler, false);
|
||||
fnCompiler.fn->arity = 0;
|
||||
|
||||
finishBody(&fnCompiler);
|
||||
endCompiler(&fnCompiler, "[async]", 7);
|
||||
|
||||
callMethod(compiler, 1, "async_(_)", 9);
|
||||
}
|
||||
|
||||
// Subscript or "array indexing" operator like `foo[bar]`.
|
||||
static void subscript(Compiler* compiler, bool canAssign)
|
||||
{
|
||||
@ -2799,6 +2858,8 @@ GrammarRule rules[] =
|
||||
/* TOKEN_TRUE */ PREFIX(boolean),
|
||||
/* TOKEN_VAR */ UNUSED,
|
||||
/* TOKEN_WHILE */ UNUSED,
|
||||
/* TOKEN_ASYNC */ PREFIX(async_),
|
||||
/* TOKEN_AWAIT */ PREFIX(await_),
|
||||
/* TOKEN_FIELD */ PREFIX(field),
|
||||
/* TOKEN_STATIC_FIELD */ PREFIX(staticField),
|
||||
/* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL },
|
||||
|
||||
148
deps/wren/src/vm/wren_core.c
vendored
148
deps/wren/src/vm/wren_core.c
vendored
@ -2,6 +2,7 @@
|
||||
#include <errno.h>
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
@ -647,6 +648,7 @@ DEF_NUM_CONSTANT(smallest, DBL_MIN)
|
||||
|
||||
DEF_NUM_CONSTANT(maxSafeInteger, 9007199254740991.0)
|
||||
DEF_NUM_CONSTANT(minSafeInteger, -9007199254740991.0)
|
||||
DEF_NUM_CONSTANT(e, 2.71828182845904523536)
|
||||
|
||||
// Defines a primitive on Num that calls infix [op] and returns [type].
|
||||
#define DEF_NUM_INFIX(name, op, type) \
|
||||
@ -704,6 +706,10 @@ DEF_NUM_FN(tan, tan)
|
||||
DEF_NUM_FN(log, log)
|
||||
DEF_NUM_FN(log2, log2)
|
||||
DEF_NUM_FN(exp, exp)
|
||||
DEF_NUM_FN(log10, log10)
|
||||
DEF_NUM_FN(sinh, sinh)
|
||||
DEF_NUM_FN(cosh, cosh)
|
||||
DEF_NUM_FN(tanh, tanh)
|
||||
|
||||
DEF_PRIMITIVE(num_mod)
|
||||
{
|
||||
@ -843,6 +849,65 @@ DEF_PRIMITIVE(num_truncate)
|
||||
RETURN_NUM(integer);
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(num_toBase)
|
||||
{
|
||||
if (!validateInt(vm, args[1], "Radix")) return false;
|
||||
int radix = (int)AS_NUM(args[1]);
|
||||
if (radix < 2 || radix > 36)
|
||||
{
|
||||
RETURN_ERROR("Radix must be between 2 and 36.");
|
||||
}
|
||||
double raw = AS_NUM(args[0]);
|
||||
if (isnan(raw) || isinf(raw))
|
||||
{
|
||||
RETURN_ERROR("Cannot convert NaN or Infinity to a base string.");
|
||||
}
|
||||
int64_t value = (int64_t)trunc(raw);
|
||||
bool negative = value < 0;
|
||||
if (negative) value = -value;
|
||||
static const char lookup[] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
char buf[66];
|
||||
int len = 0;
|
||||
if (value == 0)
|
||||
{
|
||||
buf[len++] = '0';
|
||||
}
|
||||
else
|
||||
{
|
||||
while (value > 0)
|
||||
{
|
||||
buf[len++] = lookup[value % radix];
|
||||
value = value / radix;
|
||||
}
|
||||
}
|
||||
if (negative) buf[len++] = '-';
|
||||
for (int i = 0; i < len / 2; i++)
|
||||
{
|
||||
char tmp = buf[i];
|
||||
buf[i] = buf[len - 1 - i];
|
||||
buf[len - 1 - i] = tmp;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
RETURN_VAL(wrenNewStringLength(vm, buf, len));
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(num_format)
|
||||
{
|
||||
if (!validateInt(vm, args[1], "Decimal places")) return false;
|
||||
int decimals = (int)AS_NUM(args[1]);
|
||||
if (decimals < 0 || decimals > 20)
|
||||
{
|
||||
RETURN_ERROR("Decimal places must be between 0 and 20.");
|
||||
}
|
||||
char buf[64];
|
||||
int len = snprintf(buf, sizeof(buf), "%.*f", decimals, AS_NUM(args[0]));
|
||||
if (len < 0 || len >= (int)sizeof(buf))
|
||||
{
|
||||
RETURN_ERROR("Formatting failed.");
|
||||
}
|
||||
RETURN_VAL(wrenNewStringLength(vm, buf, (size_t)len));
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(object_same)
|
||||
{
|
||||
RETURN_BOOL(wrenValuesEqual(args[1], args[2]));
|
||||
@ -1167,6 +1232,46 @@ DEF_PRIMITIVE(string_plus)
|
||||
RETURN_VAL(wrenStringFormat(vm, "@@", args[0], args[1]));
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string_lt)
|
||||
{
|
||||
if (!validateString(vm, args[1], "Right operand")) return false;
|
||||
ObjString* left = AS_STRING(args[0]);
|
||||
ObjString* right = AS_STRING(args[1]);
|
||||
uint32_t minLen = left->length < right->length ? left->length : right->length;
|
||||
int cmp = memcmp(left->value, right->value, minLen);
|
||||
RETURN_BOOL(cmp < 0 || (cmp == 0 && left->length < right->length));
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string_gt)
|
||||
{
|
||||
if (!validateString(vm, args[1], "Right operand")) return false;
|
||||
ObjString* left = AS_STRING(args[0]);
|
||||
ObjString* right = AS_STRING(args[1]);
|
||||
uint32_t minLen = left->length < right->length ? left->length : right->length;
|
||||
int cmp = memcmp(left->value, right->value, minLen);
|
||||
RETURN_BOOL(cmp > 0 || (cmp == 0 && left->length > right->length));
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string_lte)
|
||||
{
|
||||
if (!validateString(vm, args[1], "Right operand")) return false;
|
||||
ObjString* left = AS_STRING(args[0]);
|
||||
ObjString* right = AS_STRING(args[1]);
|
||||
uint32_t minLen = left->length < right->length ? left->length : right->length;
|
||||
int cmp = memcmp(left->value, right->value, minLen);
|
||||
RETURN_BOOL(cmp < 0 || (cmp == 0 && left->length <= right->length));
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string_gte)
|
||||
{
|
||||
if (!validateString(vm, args[1], "Right operand")) return false;
|
||||
ObjString* left = AS_STRING(args[0]);
|
||||
ObjString* right = AS_STRING(args[1]);
|
||||
uint32_t minLen = left->length < right->length ? left->length : right->length;
|
||||
int cmp = memcmp(left->value, right->value, minLen);
|
||||
RETURN_BOOL(cmp > 0 || (cmp == 0 && left->length >= right->length));
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string_subscript)
|
||||
{
|
||||
ObjString* string = AS_STRING(args[0]);
|
||||
@ -1197,6 +1302,36 @@ DEF_PRIMITIVE(string_toString)
|
||||
RETURN_VAL(args[0]);
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string_lower)
|
||||
{
|
||||
ObjString* str = AS_STRING(args[0]);
|
||||
char* buf = ALLOCATE_ARRAY(vm, char, str->length + 1);
|
||||
for (uint32_t i = 0; i < str->length; i++)
|
||||
{
|
||||
uint8_t c = (uint8_t)str->value[i];
|
||||
buf[i] = (c >= 'A' && c <= 'Z') ? (char)(c + 32) : (char)c;
|
||||
}
|
||||
buf[str->length] = '\0';
|
||||
Value result = wrenNewStringLength(vm, buf, str->length);
|
||||
DEALLOCATE(vm, buf);
|
||||
RETURN_VAL(result);
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(string_upper)
|
||||
{
|
||||
ObjString* str = AS_STRING(args[0]);
|
||||
char* buf = ALLOCATE_ARRAY(vm, char, str->length + 1);
|
||||
for (uint32_t i = 0; i < str->length; i++)
|
||||
{
|
||||
uint8_t c = (uint8_t)str->value[i];
|
||||
buf[i] = (c >= 'a' && c <= 'z') ? (char)(c - 32) : (char)c;
|
||||
}
|
||||
buf[str->length] = '\0';
|
||||
Value result = wrenNewStringLength(vm, buf, str->length);
|
||||
DEALLOCATE(vm, buf);
|
||||
RETURN_VAL(result);
|
||||
}
|
||||
|
||||
DEF_PRIMITIVE(system_clock)
|
||||
{
|
||||
RETURN_NUM((double)clock() / CLOCKS_PER_SEC);
|
||||
@ -1358,6 +1493,7 @@ void wrenInitializeCore(WrenVM* vm)
|
||||
PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest);
|
||||
PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger);
|
||||
PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger);
|
||||
PRIMITIVE(vm->numClass->obj.classObj, "e", num_e);
|
||||
PRIMITIVE(vm->numClass, "-(_)", num_minus);
|
||||
PRIMITIVE(vm->numClass, "+(_)", num_plus);
|
||||
PRIMITIVE(vm->numClass, "*(_)", num_multiply);
|
||||
@ -1403,6 +1539,12 @@ void wrenInitializeCore(WrenVM* vm)
|
||||
PRIMITIVE(vm->numClass, "sign", num_sign);
|
||||
PRIMITIVE(vm->numClass, "toString", num_toString);
|
||||
PRIMITIVE(vm->numClass, "truncate", num_truncate);
|
||||
PRIMITIVE(vm->numClass, "log10", num_log10);
|
||||
PRIMITIVE(vm->numClass, "sinh", num_sinh);
|
||||
PRIMITIVE(vm->numClass, "cosh", num_cosh);
|
||||
PRIMITIVE(vm->numClass, "tanh", num_tanh);
|
||||
PRIMITIVE(vm->numClass, "toBase(_)", num_toBase);
|
||||
PRIMITIVE(vm->numClass, "format(_)", num_format);
|
||||
|
||||
// These are defined just so that 0 and -0 are equal, which is specified by
|
||||
// IEEE 754 even though they have different bit representations.
|
||||
@ -1413,6 +1555,10 @@ void wrenInitializeCore(WrenVM* vm)
|
||||
PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint);
|
||||
PRIMITIVE(vm->stringClass->obj.classObj, "fromByte(_)", string_fromByte);
|
||||
PRIMITIVE(vm->stringClass, "+(_)", string_plus);
|
||||
PRIMITIVE(vm->stringClass, "<(_)", string_lt);
|
||||
PRIMITIVE(vm->stringClass, ">(_)", string_gt);
|
||||
PRIMITIVE(vm->stringClass, "<=(_)", string_lte);
|
||||
PRIMITIVE(vm->stringClass, ">=(_)", string_gte);
|
||||
PRIMITIVE(vm->stringClass, "[_]", string_subscript);
|
||||
PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt);
|
||||
PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount);
|
||||
@ -1426,6 +1572,8 @@ void wrenInitializeCore(WrenVM* vm)
|
||||
PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue);
|
||||
PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith);
|
||||
PRIMITIVE(vm->stringClass, "toString", string_toString);
|
||||
PRIMITIVE(vm->stringClass, "lower_", string_lower);
|
||||
PRIMITIVE(vm->stringClass, "upper_", string_upper);
|
||||
|
||||
vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List"));
|
||||
PRIMITIVE(vm->listClass->obj.classObj, "filled(_,_)", list_filled);
|
||||
|
||||
283
deps/wren/src/vm/wren_core.wren
vendored
283
deps/wren/src/vm/wren_core.wren
vendored
@ -2,7 +2,62 @@ class Bool {}
|
||||
class Fiber {}
|
||||
class Fn {}
|
||||
class Null {}
|
||||
class Num {}
|
||||
class Num {
|
||||
isZero { this == 0 }
|
||||
isPositive { this > 0 }
|
||||
isNegative { this < 0 }
|
||||
isFinite { !isInfinity && !isNan }
|
||||
isEven { isInteger && this % 2 == 0 }
|
||||
isOdd { isInteger && this % 2 != 0 }
|
||||
|
||||
isBetween(min, max) { this >= min && this <= max }
|
||||
|
||||
toDegrees { this * 180 / Num.pi }
|
||||
toRadians { this * Num.pi / 180 }
|
||||
|
||||
toChar { String.fromCodePoint(this) }
|
||||
toHex { toBase(16) }
|
||||
toBinary { toBase(2) }
|
||||
toOctal { toBase(8) }
|
||||
|
||||
gcd(other) {
|
||||
var a = this.abs
|
||||
var b = other.abs
|
||||
while (b != 0) {
|
||||
var t = b
|
||||
b = a % b
|
||||
a = t
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
lcm(other) {
|
||||
if (this == 0 && other == 0) return 0
|
||||
return (this * other).abs / gcd(other)
|
||||
}
|
||||
|
||||
digits {
|
||||
if (!isInteger) Fiber.abort("Value must be an integer.")
|
||||
var n = this.abs
|
||||
if (n == 0) {
|
||||
var z = List.new()
|
||||
z.add(0)
|
||||
return z
|
||||
}
|
||||
var result = List.new()
|
||||
while (n > 0) {
|
||||
result.add(n % 10)
|
||||
n = (n / 10).floor
|
||||
}
|
||||
var reversed = List.new()
|
||||
var i = result.count - 1
|
||||
while (i >= 0) {
|
||||
reversed.add(result[i])
|
||||
i = i - 1
|
||||
}
|
||||
return reversed
|
||||
}
|
||||
}
|
||||
|
||||
class Sequence {
|
||||
all(f) {
|
||||
@ -289,6 +344,230 @@ class String is Sequence {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
lower { lower_ }
|
||||
upper { upper_ }
|
||||
|
||||
capitalize {
|
||||
if (isEmpty) return this
|
||||
return this[0].upper + this[1..-1].lower
|
||||
}
|
||||
|
||||
title {
|
||||
if (isEmpty) return this
|
||||
var result = ""
|
||||
var capitalizeNext = true
|
||||
for (c in this) {
|
||||
if (c == " " || c == "\t" || c == "\n" || c == "\r") {
|
||||
result = result + c
|
||||
capitalizeNext = true
|
||||
} else if (capitalizeNext) {
|
||||
result = result + c.upper
|
||||
capitalizeNext = false
|
||||
} else {
|
||||
result = result + c.lower
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
swapCase {
|
||||
var result = ""
|
||||
for (c in this) {
|
||||
var cp = c.codePoints.toList[0]
|
||||
if (cp >= 65 && cp <= 90) {
|
||||
result = result + c.lower
|
||||
} else if (cp >= 97 && cp <= 122) {
|
||||
result = result + c.upper
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
lastIndexOf(search) {
|
||||
var last = -1
|
||||
var index = 0
|
||||
var size = byteCount_
|
||||
var searchSize = search.byteCount_
|
||||
if (searchSize > size) return -1
|
||||
while (index <= size - searchSize) {
|
||||
var found = indexOf(search, index)
|
||||
if (found == -1) break
|
||||
last = found
|
||||
index = found + 1
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
lastIndexOf(search, start) {
|
||||
var last = -1
|
||||
var index = 0
|
||||
var searchSize = search.byteCount_
|
||||
if (searchSize > start + searchSize) return -1
|
||||
while (index <= start) {
|
||||
var found = indexOf(search, index)
|
||||
if (found == -1 || found > start) break
|
||||
last = found
|
||||
index = found + 1
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
isLower {
|
||||
if (isEmpty) return false
|
||||
var hasAlpha = false
|
||||
for (b in bytes) {
|
||||
if (b >= 65 && b <= 90) return false
|
||||
if (b >= 97 && b <= 122) hasAlpha = true
|
||||
}
|
||||
return hasAlpha
|
||||
}
|
||||
|
||||
isUpper {
|
||||
if (isEmpty) return false
|
||||
var hasAlpha = false
|
||||
for (b in bytes) {
|
||||
if (b >= 97 && b <= 122) return false
|
||||
if (b >= 65 && b <= 90) hasAlpha = true
|
||||
}
|
||||
return hasAlpha
|
||||
}
|
||||
|
||||
isDigit {
|
||||
if (isEmpty) return false
|
||||
for (b in bytes) {
|
||||
if (b < 48 || b > 57) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
isAlpha {
|
||||
if (isEmpty) return false
|
||||
for (b in bytes) {
|
||||
if (!((b >= 65 && b <= 90) || (b >= 97 && b <= 122))) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
isAlphaNumeric {
|
||||
if (isEmpty) return false
|
||||
for (b in bytes) {
|
||||
if (!((b >= 65 && b <= 90) || (b >= 97 && b <= 122) || (b >= 48 && b <= 57))) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
isSpace {
|
||||
if (isEmpty) return false
|
||||
for (b in bytes) {
|
||||
if (b != 32 && b != 9 && b != 10 && b != 13 && b != 12 && b != 11) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
isAscii {
|
||||
for (b in bytes) {
|
||||
if (b >= 128) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
reverse {
|
||||
var list = []
|
||||
for (c in this) {
|
||||
list.insert(0, c)
|
||||
}
|
||||
return list.join("")
|
||||
}
|
||||
|
||||
center(width) { center(width, " ") }
|
||||
|
||||
center(width, char) {
|
||||
if (!(width is Num) || !width.isInteger) {
|
||||
Fiber.abort("Width must be an integer.")
|
||||
}
|
||||
if (!(char is String) || char.isEmpty) {
|
||||
Fiber.abort("Fill character must be a non-empty string.")
|
||||
}
|
||||
var len = count
|
||||
if (len >= width) return this
|
||||
var total = width - len
|
||||
var left = (total / 2).floor
|
||||
var right = total - left
|
||||
return (char * (left + 1))[0...left] + this + (char * (right + 1))[0...right]
|
||||
}
|
||||
|
||||
lpad(width, char) {
|
||||
if (!(width is Num) || !width.isInteger) {
|
||||
Fiber.abort("Width must be an integer.")
|
||||
}
|
||||
if (!(char is String) || char.isEmpty) {
|
||||
Fiber.abort("Fill character must be a non-empty string.")
|
||||
}
|
||||
var len = count
|
||||
if (len >= width) return this
|
||||
var pad = width - len
|
||||
return (char * (pad + 1))[0...pad] + this
|
||||
}
|
||||
|
||||
rpad(width, char) {
|
||||
if (!(width is Num) || !width.isInteger) {
|
||||
Fiber.abort("Width must be an integer.")
|
||||
}
|
||||
if (!(char is String) || char.isEmpty) {
|
||||
Fiber.abort("Fill character must be a non-empty string.")
|
||||
}
|
||||
var len = count
|
||||
if (len >= width) return this
|
||||
var pad = width - len
|
||||
return this + (char * (pad + 1))[0...pad]
|
||||
}
|
||||
|
||||
zfill(width) {
|
||||
if (!(width is Num) || !width.isInteger) {
|
||||
Fiber.abort("Width must be an integer.")
|
||||
}
|
||||
var len = count
|
||||
if (len >= width) return this
|
||||
if (startsWith("-") || startsWith("+")) {
|
||||
return this[0] + ("0" * (width - len)) + this[1..-1]
|
||||
}
|
||||
return ("0" * (width - len)) + this
|
||||
}
|
||||
|
||||
removePrefix(prefix) {
|
||||
if (!(prefix is String)) {
|
||||
Fiber.abort("Prefix must be a string.")
|
||||
}
|
||||
if (startsWith(prefix)) return this[prefix.byteCount_..-1]
|
||||
return this
|
||||
}
|
||||
|
||||
removeSuffix(suffix) {
|
||||
if (!(suffix is String)) {
|
||||
Fiber.abort("Suffix must be a string.")
|
||||
}
|
||||
if (endsWith(suffix)) {
|
||||
var end = byteCount_ - suffix.byteCount_ - 1
|
||||
if (end < 0) return ""
|
||||
return this[0..end]
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
splitLines { replace("\r\n", "\n").replace("\r", "\n").split("\n") }
|
||||
|
||||
chars {
|
||||
var result = []
|
||||
for (c in this) {
|
||||
result.add(c)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
toNum { Num.fromString(this) }
|
||||
}
|
||||
|
||||
class StringByteSequence is Sequence {
|
||||
@ -480,4 +759,4 @@ class ClassAttributes {
|
||||
_methods = methods
|
||||
}
|
||||
toString { "attributes:%(_attributes) methods:%(_methods)" }
|
||||
}
|
||||
}
|
||||
|
||||
298
deps/wren/src/vm/wren_core.wren.inc
vendored
298
deps/wren/src/vm/wren_core.wren.inc
vendored
@ -1,10 +1,67 @@
|
||||
// Generated automatically from src/vm/wren_core.wren. Do not edit.
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `deps/wren/src/vm/wren_core.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* coreModuleSource =
|
||||
"class Bool {}\n"
|
||||
"class Fiber {}\n"
|
||||
"class Fn {}\n"
|
||||
"class Null {}\n"
|
||||
"class Num {}\n"
|
||||
"class Num {\n"
|
||||
" isZero { this == 0 }\n"
|
||||
" isPositive { this > 0 }\n"
|
||||
" isNegative { this < 0 }\n"
|
||||
" isFinite { !isInfinity && !isNan }\n"
|
||||
" isEven { isInteger && this % 2 == 0 }\n"
|
||||
" isOdd { isInteger && this % 2 != 0 }\n"
|
||||
"\n"
|
||||
" isBetween(min, max) { this >= min && this <= max }\n"
|
||||
"\n"
|
||||
" toDegrees { this * 180 / Num.pi }\n"
|
||||
" toRadians { this * Num.pi / 180 }\n"
|
||||
"\n"
|
||||
" toChar { String.fromCodePoint(this) }\n"
|
||||
" toHex { toBase(16) }\n"
|
||||
" toBinary { toBase(2) }\n"
|
||||
" toOctal { toBase(8) }\n"
|
||||
"\n"
|
||||
" gcd(other) {\n"
|
||||
" var a = this.abs\n"
|
||||
" var b = other.abs\n"
|
||||
" while (b != 0) {\n"
|
||||
" var t = b\n"
|
||||
" b = a % b\n"
|
||||
" a = t\n"
|
||||
" }\n"
|
||||
" return a\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" lcm(other) {\n"
|
||||
" if (this == 0 && other == 0) return 0\n"
|
||||
" return (this * other).abs / gcd(other)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" digits {\n"
|
||||
" if (!isInteger) Fiber.abort(\"Value must be an integer.\")\n"
|
||||
" var n = this.abs\n"
|
||||
" if (n == 0) {\n"
|
||||
" var z = List.new()\n"
|
||||
" z.add(0)\n"
|
||||
" return z\n"
|
||||
" }\n"
|
||||
" var result = List.new()\n"
|
||||
" while (n > 0) {\n"
|
||||
" result.add(n % 10)\n"
|
||||
" n = (n / 10).floor\n"
|
||||
" }\n"
|
||||
" var reversed = List.new()\n"
|
||||
" var i = result.count - 1\n"
|
||||
" while (i >= 0) {\n"
|
||||
" reversed.add(result[i])\n"
|
||||
" i = i - 1\n"
|
||||
" }\n"
|
||||
" return reversed\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Sequence {\n"
|
||||
" all(f) {\n"
|
||||
@ -238,11 +295,11 @@ static const char* coreModuleSource =
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" trim() { trim_(\"\t\r\n \", true, true) }\n"
|
||||
" trim() { trim_(\"\\t\\r\\n \", true, true) }\n"
|
||||
" trim(chars) { trim_(chars, true, true) }\n"
|
||||
" trimEnd() { trim_(\"\t\r\n \", false, true) }\n"
|
||||
" trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n"
|
||||
" trimEnd(chars) { trim_(chars, false, true) }\n"
|
||||
" trimStart() { trim_(\"\t\r\n \", true, false) }\n"
|
||||
" trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n"
|
||||
" trimStart(chars) { trim_(chars, true, false) }\n"
|
||||
"\n"
|
||||
" trim_(chars, trimStart, trimEnd) {\n"
|
||||
@ -291,6 +348,230 @@ static const char* coreModuleSource =
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" lower { lower_ }\n"
|
||||
" upper { upper_ }\n"
|
||||
"\n"
|
||||
" capitalize {\n"
|
||||
" if (isEmpty) return this\n"
|
||||
" return this[0].upper + this[1..-1].lower\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" title {\n"
|
||||
" if (isEmpty) return this\n"
|
||||
" var result = \"\"\n"
|
||||
" var capitalizeNext = true\n"
|
||||
" for (c in this) {\n"
|
||||
" if (c == \" \" || c == \"\\t\" || c == \"\\n\" || c == \"\\r\") {\n"
|
||||
" result = result + c\n"
|
||||
" capitalizeNext = true\n"
|
||||
" } else if (capitalizeNext) {\n"
|
||||
" result = result + c.upper\n"
|
||||
" capitalizeNext = false\n"
|
||||
" } else {\n"
|
||||
" result = result + c.lower\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" swapCase {\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in this) {\n"
|
||||
" var cp = c.codePoints.toList[0]\n"
|
||||
" if (cp >= 65 && cp <= 90) {\n"
|
||||
" result = result + c.lower\n"
|
||||
" } else if (cp >= 97 && cp <= 122) {\n"
|
||||
" result = result + c.upper\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" lastIndexOf(search) {\n"
|
||||
" var last = -1\n"
|
||||
" var index = 0\n"
|
||||
" var size = byteCount_\n"
|
||||
" var searchSize = search.byteCount_\n"
|
||||
" if (searchSize > size) return -1\n"
|
||||
" while (index <= size - searchSize) {\n"
|
||||
" var found = indexOf(search, index)\n"
|
||||
" if (found == -1) break\n"
|
||||
" last = found\n"
|
||||
" index = found + 1\n"
|
||||
" }\n"
|
||||
" return last\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" lastIndexOf(search, start) {\n"
|
||||
" var last = -1\n"
|
||||
" var index = 0\n"
|
||||
" var searchSize = search.byteCount_\n"
|
||||
" if (searchSize > start + searchSize) return -1\n"
|
||||
" while (index <= start) {\n"
|
||||
" var found = indexOf(search, index)\n"
|
||||
" if (found == -1 || found > start) break\n"
|
||||
" last = found\n"
|
||||
" index = found + 1\n"
|
||||
" }\n"
|
||||
" return last\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isLower {\n"
|
||||
" if (isEmpty) return false\n"
|
||||
" var hasAlpha = false\n"
|
||||
" for (b in bytes) {\n"
|
||||
" if (b >= 65 && b <= 90) return false\n"
|
||||
" if (b >= 97 && b <= 122) hasAlpha = true\n"
|
||||
" }\n"
|
||||
" return hasAlpha\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isUpper {\n"
|
||||
" if (isEmpty) return false\n"
|
||||
" var hasAlpha = false\n"
|
||||
" for (b in bytes) {\n"
|
||||
" if (b >= 97 && b <= 122) return false\n"
|
||||
" if (b >= 65 && b <= 90) hasAlpha = true\n"
|
||||
" }\n"
|
||||
" return hasAlpha\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isDigit {\n"
|
||||
" if (isEmpty) return false\n"
|
||||
" for (b in bytes) {\n"
|
||||
" if (b < 48 || b > 57) return false\n"
|
||||
" }\n"
|
||||
" return true\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isAlpha {\n"
|
||||
" if (isEmpty) return false\n"
|
||||
" for (b in bytes) {\n"
|
||||
" if (!((b >= 65 && b <= 90) || (b >= 97 && b <= 122))) return false\n"
|
||||
" }\n"
|
||||
" return true\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isAlphaNumeric {\n"
|
||||
" if (isEmpty) return false\n"
|
||||
" for (b in bytes) {\n"
|
||||
" if (!((b >= 65 && b <= 90) || (b >= 97 && b <= 122) || (b >= 48 && b <= 57))) return false\n"
|
||||
" }\n"
|
||||
" return true\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isSpace {\n"
|
||||
" if (isEmpty) return false\n"
|
||||
" for (b in bytes) {\n"
|
||||
" if (b != 32 && b != 9 && b != 10 && b != 13 && b != 12 && b != 11) return false\n"
|
||||
" }\n"
|
||||
" return true\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isAscii {\n"
|
||||
" for (b in bytes) {\n"
|
||||
" if (b >= 128) return false\n"
|
||||
" }\n"
|
||||
" return true\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" reverse {\n"
|
||||
" var list = []\n"
|
||||
" for (c in this) {\n"
|
||||
" list.insert(0, c)\n"
|
||||
" }\n"
|
||||
" return list.join(\"\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" center(width) { center(width, \" \") }\n"
|
||||
"\n"
|
||||
" center(width, char) {\n"
|
||||
" if (!(width is Num) || !width.isInteger) {\n"
|
||||
" Fiber.abort(\"Width must be an integer.\")\n"
|
||||
" }\n"
|
||||
" if (!(char is String) || char.isEmpty) {\n"
|
||||
" Fiber.abort(\"Fill character must be a non-empty string.\")\n"
|
||||
" }\n"
|
||||
" var len = count\n"
|
||||
" if (len >= width) return this\n"
|
||||
" var total = width - len\n"
|
||||
" var left = (total / 2).floor\n"
|
||||
" var right = total - left\n"
|
||||
" return (char * (left + 1))[0...left] + this + (char * (right + 1))[0...right]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" lpad(width, char) {\n"
|
||||
" if (!(width is Num) || !width.isInteger) {\n"
|
||||
" Fiber.abort(\"Width must be an integer.\")\n"
|
||||
" }\n"
|
||||
" if (!(char is String) || char.isEmpty) {\n"
|
||||
" Fiber.abort(\"Fill character must be a non-empty string.\")\n"
|
||||
" }\n"
|
||||
" var len = count\n"
|
||||
" if (len >= width) return this\n"
|
||||
" var pad = width - len\n"
|
||||
" return (char * (pad + 1))[0...pad] + this\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" rpad(width, char) {\n"
|
||||
" if (!(width is Num) || !width.isInteger) {\n"
|
||||
" Fiber.abort(\"Width must be an integer.\")\n"
|
||||
" }\n"
|
||||
" if (!(char is String) || char.isEmpty) {\n"
|
||||
" Fiber.abort(\"Fill character must be a non-empty string.\")\n"
|
||||
" }\n"
|
||||
" var len = count\n"
|
||||
" if (len >= width) return this\n"
|
||||
" var pad = width - len\n"
|
||||
" return this + (char * (pad + 1))[0...pad]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" zfill(width) {\n"
|
||||
" if (!(width is Num) || !width.isInteger) {\n"
|
||||
" Fiber.abort(\"Width must be an integer.\")\n"
|
||||
" }\n"
|
||||
" var len = count\n"
|
||||
" if (len >= width) return this\n"
|
||||
" if (startsWith(\"-\") || startsWith(\"+\")) {\n"
|
||||
" return this[0] + (\"0\" * (width - len)) + this[1..-1]\n"
|
||||
" }\n"
|
||||
" return (\"0\" * (width - len)) + this\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" removePrefix(prefix) {\n"
|
||||
" if (!(prefix is String)) {\n"
|
||||
" Fiber.abort(\"Prefix must be a string.\")\n"
|
||||
" }\n"
|
||||
" if (startsWith(prefix)) return this[prefix.byteCount_..-1]\n"
|
||||
" return this\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" removeSuffix(suffix) {\n"
|
||||
" if (!(suffix is String)) {\n"
|
||||
" Fiber.abort(\"Suffix must be a string.\")\n"
|
||||
" }\n"
|
||||
" if (endsWith(suffix)) {\n"
|
||||
" var end = byteCount_ - suffix.byteCount_ - 1\n"
|
||||
" if (end < 0) return \"\"\n"
|
||||
" return this[0..end]\n"
|
||||
" }\n"
|
||||
" return this\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" splitLines { replace(\"\\r\\n\", \"\\n\").replace(\"\\r\", \"\\n\").split(\"\\n\") }\n"
|
||||
"\n"
|
||||
" chars {\n"
|
||||
" var result = []\n"
|
||||
" for (c in this) {\n"
|
||||
" result.add(c)\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" toNum { Num.fromString(this) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class StringByteSequence is Sequence {\n"
|
||||
@ -441,18 +722,18 @@ static const char* coreModuleSource =
|
||||
"\n"
|
||||
"class System {\n"
|
||||
" static print() {\n"
|
||||
" writeString_(\"\n\")\n"
|
||||
" writeString_(\"\\n\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static print(obj) {\n"
|
||||
" writeObject_(obj)\n"
|
||||
" writeString_(\"\n\")\n"
|
||||
" writeString_(\"\\n\")\n"
|
||||
" return obj\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static printAll(sequence) {\n"
|
||||
" for (object in sequence) writeObject_(object)\n"
|
||||
" writeString_(\"\n\")\n"
|
||||
" writeString_(\"\\n\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static write(obj) {\n"
|
||||
@ -483,4 +764,3 @@ static const char* coreModuleSource =
|
||||
" }\n"
|
||||
" toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n"
|
||||
"}\n";
|
||||
|
||||
|
||||
35
example/argparse_demo.wren
vendored
Normal file
35
example/argparse_demo.wren
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "argparse" for ArgumentParser
|
||||
|
||||
System.print("=== Argparse Module Demo ===")
|
||||
|
||||
System.print("\n--- Basic Usage ---")
|
||||
var parser = ArgumentParser.new("A sample program")
|
||||
parser.prog = "sample"
|
||||
parser.addArgument("input", {"help": "Input file"})
|
||||
parser.addArgument("-o", {"long": "--output", "default": "output.txt", "help": "Output file"})
|
||||
parser.addArgument("-v", {"long": "--verbose", "action": "storeTrue", "help": "Verbose output"})
|
||||
parser.addArgument("-n", {"type": "int", "default": 1, "help": "Number of iterations"})
|
||||
|
||||
System.print("Help message:")
|
||||
parser.printHelp()
|
||||
|
||||
System.print("\n--- Parsing Example Args ---")
|
||||
var args = parser.parseArgs(["data.txt", "-v", "-n", "5", "--output", "result.txt"])
|
||||
System.print("Input: %(args["input"])")
|
||||
System.print("Output: %(args["output"])")
|
||||
System.print("Verbose: %(args["verbose"])")
|
||||
System.print("Iterations: %(args["n"])")
|
||||
|
||||
System.print("\n--- Count Action ---")
|
||||
var parser2 = ArgumentParser.new()
|
||||
parser2.addArgument("-v", {"action": "count"})
|
||||
var args2 = parser2.parseArgs(["-v", "-v", "-v"])
|
||||
System.print("Verbosity level: %(args2["v"])")
|
||||
|
||||
System.print("\n--- Append Action ---")
|
||||
var parser3 = ArgumentParser.new()
|
||||
parser3.addArgument("-i", {"long": "--include", "action": "append"})
|
||||
var args3 = parser3.parseArgs(["-i", "module1", "-i", "module2", "--include", "module3"])
|
||||
System.print("Included modules: %(args3["include"])")
|
||||
22
example/await_demo.wren
vendored
Normal file
22
example/await_demo.wren
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler, Future
|
||||
import "timer" for Timer
|
||||
|
||||
System.print("=== Await Demo ===\n")
|
||||
|
||||
System.print("--- Basic Await ---")
|
||||
await Timer.sleep(1)
|
||||
System.print("Timer completed")
|
||||
|
||||
System.print("\n--- Concurrent Async ---")
|
||||
var task1 = async { Timer.sleep(1) }
|
||||
var task2 = async { Timer.sleep(1) }
|
||||
await task1
|
||||
await task2
|
||||
System.print("Both tasks completed concurrently")
|
||||
|
||||
System.print("\n--- Async With Result ---")
|
||||
var task = async { "computed value" }
|
||||
var result = await task
|
||||
System.print("Result: %(result)")
|
||||
39
example/bytes_demo.wren
vendored
Normal file
39
example/bytes_demo.wren
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "bytes" for Bytes
|
||||
|
||||
System.print("=== Bytes Module Demo ===\n")
|
||||
|
||||
System.print("--- fromList / toList ---")
|
||||
var list = [72, 101, 108, 108, 111]
|
||||
var str = Bytes.fromList(list)
|
||||
System.print("fromList([72, 101, 108, 108, 111]) = %(str)")
|
||||
System.print("toList(%(str)) = %(Bytes.toList(str))")
|
||||
|
||||
System.print("\n--- length ---")
|
||||
System.print("length('Hello World') = %(Bytes.length("Hello World"))")
|
||||
|
||||
System.print("\n--- concat ---")
|
||||
var a = "Hello"
|
||||
var b = " World"
|
||||
System.print("concat('%(a)', '%(b)') = %(Bytes.concat(a, b))")
|
||||
|
||||
System.print("\n--- slice ---")
|
||||
var data = "Hello World"
|
||||
System.print("slice('%(data)', 0, 5) = %(Bytes.slice(data, 0, 5))")
|
||||
System.print("slice('%(data)', 6, 11) = %(Bytes.slice(data, 6, 11))")
|
||||
|
||||
System.print("\n--- xorMask ---")
|
||||
var payload = Bytes.fromList([1, 2, 3, 4, 5, 6, 7, 8])
|
||||
var mask = Bytes.fromList([0x12, 0x34, 0x56, 0x78])
|
||||
var masked = Bytes.xorMask(payload, mask)
|
||||
System.print("Original: %(Bytes.toList(payload))")
|
||||
System.print("Mask: %(Bytes.toList(mask))")
|
||||
System.print("XOR'd: %(Bytes.toList(masked))")
|
||||
var unmasked = Bytes.xorMask(masked, mask)
|
||||
System.print("Unmasked: %(Bytes.toList(unmasked))")
|
||||
|
||||
System.print("\n--- Binary data handling ---")
|
||||
var binary = Bytes.fromList([0, 127, 128, 255])
|
||||
System.print("Binary data length: %(Bytes.length(binary))")
|
||||
System.print("Binary data bytes: %(Bytes.toList(binary))")
|
||||
52
example/dataset_demo.wren
vendored
Normal file
52
example/dataset_demo.wren
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
System.print("=== Dataset Module Demo ===")
|
||||
|
||||
var ds = Dataset.memory()
|
||||
|
||||
System.print("\n--- Insert Records ---")
|
||||
var users = ds["users"]
|
||||
var alice = users.insert({"name": "Alice", "age": 30, "email": "alice@example.com"})
|
||||
System.print("Inserted: %(alice["name"]) with uid: %(alice["uid"])")
|
||||
|
||||
var bob = users.insert({"name": "Bob", "age": 25, "email": "bob@example.com"})
|
||||
var charlie = users.insert({"name": "Charlie", "age": 35, "email": "charlie@example.com"})
|
||||
|
||||
System.print("Total users: %(users.count())")
|
||||
|
||||
System.print("\n--- Query Records ---")
|
||||
var all = users.all()
|
||||
System.print("All users:")
|
||||
for (user in all) {
|
||||
System.print(" - %(user["name"]) (%(user["age"]))")
|
||||
}
|
||||
|
||||
System.print("\nUsers older than 28:")
|
||||
var older = users.find({"age__gt": 28})
|
||||
for (user in older) {
|
||||
System.print(" - %(user["name"])")
|
||||
}
|
||||
|
||||
System.print("\n--- Update Record ---")
|
||||
users.update({"uid": alice["uid"], "age": 31})
|
||||
var updated = users.findOne({"uid": alice["uid"]})
|
||||
System.print("Alice's new age: %(updated["age"])")
|
||||
|
||||
System.print("\n--- Delete Record ---")
|
||||
System.print("Before delete: %(users.count()) users")
|
||||
users.delete(bob["uid"])
|
||||
System.print("After delete: %(users.count()) users")
|
||||
|
||||
System.print("\n--- Auto Schema ---")
|
||||
var products = ds["products"]
|
||||
products.insert({"name": "Widget", "price": 9.99})
|
||||
products.insert({"name": "Gadget", "price": 19.99, "stock": 100})
|
||||
System.print("Product columns: %(products.columns.keys.toList)")
|
||||
|
||||
System.print("\n--- Tables ---")
|
||||
System.print("All tables: %(ds.tables)")
|
||||
|
||||
ds.close()
|
||||
System.print("\nDone.")
|
||||
39
example/html_demo.wren
vendored
Normal file
39
example/html_demo.wren
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "html" for Html
|
||||
|
||||
System.print("=== HTML Module Demo ===")
|
||||
|
||||
System.print("\n--- URL Encoding ---")
|
||||
var text = "hello world & special=chars"
|
||||
var encoded = Html.urlencode(text)
|
||||
var decoded = Html.urldecode(encoded)
|
||||
System.print("Original: %(text)")
|
||||
System.print("Encoded: %(encoded)")
|
||||
System.print("Decoded: %(decoded)")
|
||||
|
||||
System.print("\n--- Slugify ---")
|
||||
var titles = [
|
||||
"Hello World",
|
||||
"This is a Test Article!",
|
||||
"Product Name (2024)",
|
||||
"FAQ & Help"
|
||||
]
|
||||
for (title in titles) {
|
||||
System.print("%(title) -> %(Html.slugify(title))")
|
||||
}
|
||||
|
||||
System.print("\n--- HTML Escaping ---")
|
||||
var unsafe = "<script>alert('XSS')</script>"
|
||||
var safe = Html.quote(unsafe)
|
||||
System.print("Unsafe: %(unsafe)")
|
||||
System.print("Safe: %(safe)")
|
||||
System.print("Unescaped: %(Html.unquote(safe))")
|
||||
|
||||
System.print("\n--- Query Parameters ---")
|
||||
var params = {"name": "John Doe", "city": "New York", "age": 30}
|
||||
var queryString = Html.encodeParams(params)
|
||||
System.print("Params: %(params)")
|
||||
System.print("Query string: %(queryString)")
|
||||
var parsed = Html.decodeParams(queryString)
|
||||
System.print("Parsed back: %(parsed)")
|
||||
67
example/jinja_markdown.wren
vendored
Normal file
67
example/jinja_markdown.wren
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
System.print("=" * 60)
|
||||
System.print("JINJA MARKDOWN - Markdown/HTML Conversion in Templates")
|
||||
System.print("=" * 60)
|
||||
|
||||
var templates = {
|
||||
"md_to_html": "{\% markdowntohtml \%}# {{ title }}
|
||||
|
||||
{{ description }}
|
||||
|
||||
- Item **one**
|
||||
- Item **two**
|
||||
- Item **three**
|
||||
{\% endmarkdowntohtml \%}",
|
||||
|
||||
"html_to_md": "{\% markdownfromhtml \%}<h1>{{ title }}</h1>
|
||||
<p>This is a <strong>paragraph</strong> with <em>formatting</em>.</p>
|
||||
<ul>
|
||||
<li>First</li>
|
||||
<li>Second</li>
|
||||
</ul>{\% endmarkdownfromhtml \%}",
|
||||
|
||||
"filter_md": "{{ content|markdown }}",
|
||||
|
||||
"filter_fromhtml": "{{ content|markdownfromhtml }}",
|
||||
|
||||
"mixed": "<div class=\"article\">
|
||||
{\% markdowntohtml \%}## {{ article.title }}
|
||||
|
||||
{{ article.body }}
|
||||
{\% endmarkdowntohtml \%}
|
||||
</div>"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
|
||||
System.print("\n--- Markdown to HTML (block tag) ---")
|
||||
System.print(env.getTemplate("md_to_html").render({
|
||||
"title": "Welcome",
|
||||
"description": "This is a *template* with markdown support."
|
||||
}))
|
||||
|
||||
System.print("\n--- HTML to Markdown (block tag) ---")
|
||||
System.print(env.getTemplate("html_to_md").render({
|
||||
"title": "Documentation"
|
||||
}))
|
||||
|
||||
System.print("\n--- Markdown filter ---")
|
||||
System.print(env.getTemplate("filter_md").render({
|
||||
"content": "**Bold** and *italic* text."
|
||||
}))
|
||||
|
||||
System.print("\n--- Markdown from HTML filter ---")
|
||||
System.print(env.getTemplate("filter_fromhtml").render({
|
||||
"content": "<h2>Section</h2><p>A paragraph.</p>"
|
||||
}))
|
||||
|
||||
System.print("\n--- Mixed HTML and Markdown ---")
|
||||
System.print(env.getTemplate("mixed").render({
|
||||
"article": {
|
||||
"title": "Getting Started",
|
||||
"body": "Read the **documentation** for details."
|
||||
}
|
||||
}))
|
||||
52
example/markdown_demo.wren
vendored
Normal file
52
example/markdown_demo.wren
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
System.print("=== Markdown Module Demo ===")
|
||||
|
||||
var markdown = "# Welcome to Markdown
|
||||
|
||||
This is a **bold** statement and this is *italic*.
|
||||
|
||||
## Features
|
||||
|
||||
- Easy to read
|
||||
- Easy to write
|
||||
- Converts to HTML
|
||||
|
||||
### Code Example
|
||||
|
||||
Here is some `inline code`.
|
||||
|
||||
```
|
||||
function hello() {
|
||||
console.log(\"Hello, World!\")
|
||||
}
|
||||
```
|
||||
|
||||
### Links and Images
|
||||
|
||||
Check out [Wren](https://wren.io) for more info.
|
||||
|
||||

|
||||
|
||||
> This is a blockquote.
|
||||
> It can span multiple lines.
|
||||
|
||||
---
|
||||
|
||||
1. First item
|
||||
2. Second item
|
||||
3. Third item
|
||||
|
||||
That's all folks!"
|
||||
|
||||
System.print("\n--- Source Markdown ---")
|
||||
System.print(markdown)
|
||||
|
||||
System.print("\n--- Generated HTML ---")
|
||||
System.print(Markdown.toHtml(markdown))
|
||||
|
||||
System.print("\n--- Safe Mode Example ---")
|
||||
var unsafe = "# Title\n\n<script>alert('xss')</script>\n\nSafe content."
|
||||
System.print(Markdown.toHtml(unsafe, {"safeMode": true}))
|
||||
51
example/number_demo.wren
vendored
Normal file
51
example/number_demo.wren
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
System.print("=== Num Extensions Demo ===\n")
|
||||
|
||||
System.print("--- Constants ---")
|
||||
System.print("Num.pi: %(Num.pi)")
|
||||
System.print("Num.tau: %(Num.tau)")
|
||||
System.print("Num.e: %(Num.e)")
|
||||
|
||||
System.print("\n--- Query Methods ---")
|
||||
System.print("0.isZero: %(0.isZero)")
|
||||
System.print("5.isPositive: %(5.isPositive)")
|
||||
System.print("(-3).isNegative: %((-3).isNegative)")
|
||||
System.print("42.isFinite: %(42.isFinite)")
|
||||
System.print("(1/0).isFinite: %((1/0).isFinite)")
|
||||
System.print("4.isEven: %(4.isEven)")
|
||||
System.print("3.isOdd: %(3.isOdd)")
|
||||
System.print("5.isBetween(1, 10): %(5.isBetween(1, 10))")
|
||||
|
||||
System.print("\n--- Math Functions ---")
|
||||
System.print("100.log10: %(100.log10)")
|
||||
System.print("0.sinh: %(0.sinh)")
|
||||
System.print("0.cosh: %(0.cosh)")
|
||||
System.print("0.tanh: %(0.tanh)")
|
||||
|
||||
System.print("\n--- Angle Conversion ---")
|
||||
var pi = Num.pi
|
||||
System.print("pi.toDegrees: %(pi.toDegrees)")
|
||||
System.print("180.toRadians: %(180.toRadians)")
|
||||
|
||||
System.print("\n--- Base Conversion ---")
|
||||
System.print("255.toHex: %(255.toHex)")
|
||||
System.print("10.toBinary: %(10.toBinary)")
|
||||
System.print("8.toOctal: %(8.toOctal)")
|
||||
System.print("255.toBase(16): %(255.toBase(16))")
|
||||
System.print("100.toBase(36): %(100.toBase(36))")
|
||||
|
||||
System.print("\n--- Character Conversion ---")
|
||||
System.print("65.toChar: %(65.toChar)")
|
||||
System.print("97.toChar: %(97.toChar)")
|
||||
|
||||
System.print("\n--- Formatting ---")
|
||||
System.print("3.14159.format(2): %(3.14159.format(2))")
|
||||
System.print("42.format(3): %(42.format(3))")
|
||||
System.print("(-1.5).format(1): %((-1.5).format(1))")
|
||||
|
||||
System.print("\n--- Integer Operations ---")
|
||||
System.print("12.gcd(8): %(12.gcd(8))")
|
||||
System.print("4.lcm(6): %(4.lcm(6))")
|
||||
System.print("123.digits: %(123.digits)")
|
||||
System.print("0.digits: %(0.digits)")
|
||||
167
example/pathlib_demo.wren
vendored
Normal file
167
example/pathlib_demo.wren
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "pathlib" for Path
|
||||
|
||||
System.print("=== Pathlib Module Demo ===\n")
|
||||
|
||||
System.print("--- Path Construction ---")
|
||||
var p = Path.new("/home/user/documents/report.tar.gz")
|
||||
System.print("Path: %(p)")
|
||||
System.print("Name: %(p.name)")
|
||||
System.print("Stem: %(p.stem)")
|
||||
System.print("Suffix: %(p.suffix)")
|
||||
System.print("Suffixes: %(p.suffixes)")
|
||||
System.print("Parts: %(p.parts)")
|
||||
|
||||
System.print("\n--- Parent Navigation ---")
|
||||
System.print("Parent: %(p.parent)")
|
||||
System.print("Parents:")
|
||||
for (ancestor in p.parents) {
|
||||
System.print(" %(ancestor)")
|
||||
}
|
||||
|
||||
System.print("\n--- Path Properties ---")
|
||||
System.print("Root: %(p.root)")
|
||||
System.print("Anchor: %(p.anchor)")
|
||||
System.print("Is absolute: %(p.isAbsolute)")
|
||||
var rel = Path.new("relative/path.txt")
|
||||
System.print("%(rel) is absolute: %(rel.isAbsolute)")
|
||||
|
||||
System.print("\n--- Join Operator (/) ---")
|
||||
var base = Path.new("/home/user")
|
||||
var config = base / ".config" / "myapp" / "settings.json"
|
||||
System.print("%(base) / .config / myapp / settings.json = %(config)")
|
||||
|
||||
System.print("\n--- joinpath ---")
|
||||
var multi = Path.new("/opt").joinpath(["local", "bin", "wren_cli"])
|
||||
System.print("Joined: %(multi)")
|
||||
|
||||
System.print("\n--- withName / withStem / withSuffix ---")
|
||||
var orig = Path.new("/data/archive.tar.gz")
|
||||
System.print("Original: %(orig)")
|
||||
System.print("withName(\"backup.zip\"): %(orig.withName("backup.zip"))")
|
||||
System.print("withStem(\"snapshot\"): %(orig.withStem("snapshot"))")
|
||||
System.print("withSuffix(\".bak\"): %(orig.withSuffix(".bak"))")
|
||||
|
||||
System.print("\n--- relativeTo ---")
|
||||
var full = Path.new("/home/user/projects/wren/src/main.c")
|
||||
var project = Path.new("/home/user/projects/wren")
|
||||
System.print("%(full) relative to %(project) = %(full.relativeTo(project))")
|
||||
|
||||
System.print("\n--- Glob Matching ---")
|
||||
var file = Path.new("/tmp/notes.txt")
|
||||
System.print("%(file.name) matches *.txt: %(file.match("*.txt"))")
|
||||
System.print("%(file.name) matches *.md: %(file.match("*.md"))")
|
||||
System.print("%(file.name) matches note?: %(file.match("note?"))")
|
||||
System.print("%(file.name) matches n*s.txt: %(file.match("n*s.txt"))")
|
||||
|
||||
System.print("\n--- Home and CWD ---")
|
||||
System.print("Home: %(Path.home)")
|
||||
System.print("CWD: %(Path.cwd)")
|
||||
|
||||
System.print("\n--- Expand User ---")
|
||||
var tilde = Path.new("~/.bashrc")
|
||||
System.print("%(tilde) -> %(tilde.expanduser())")
|
||||
|
||||
System.print("\n--- Equality ---")
|
||||
var a = Path.new("/tmp/test")
|
||||
var b = Path.new("/tmp/test")
|
||||
var c = Path.new("/tmp/other")
|
||||
System.print("%(a) == %(b): %(a == b)")
|
||||
System.print("%(a) == %(c): %(a == c)")
|
||||
System.print("%(a) != %(c): %(a != c)")
|
||||
|
||||
System.print("\n--- Filesystem Operations ---")
|
||||
var testDir = Path.new("/tmp/wren_pathlib_demo")
|
||||
if (testDir.exists()) testDir.rmtree()
|
||||
|
||||
testDir.mkdir(true)
|
||||
System.print("Created: %(testDir)")
|
||||
System.print("Exists: %(testDir.exists())")
|
||||
System.print("Is dir: %(testDir.isDir())")
|
||||
|
||||
var testFile = testDir / "hello.txt"
|
||||
testFile.writeText("Hello from pathlib!")
|
||||
System.print("Wrote: %(testFile)")
|
||||
System.print("Content: %(testFile.readText())")
|
||||
System.print("Is file: %(testFile.isFile())")
|
||||
|
||||
System.print("\n--- Stat ---")
|
||||
var s = testFile.stat()
|
||||
System.print("Size: %(s.size) bytes")
|
||||
|
||||
System.print("\n--- Touch ---")
|
||||
var marker = testDir / "marker"
|
||||
marker.touch()
|
||||
System.print("Touched: %(marker) (exists: %(marker.exists()))")
|
||||
|
||||
System.print("\n--- Copy ---")
|
||||
var copy = testDir / "hello_copy.txt"
|
||||
testFile.copyfile(copy)
|
||||
System.print("Copied to: %(copy)")
|
||||
System.print("Copy content: %(copy.readText())")
|
||||
|
||||
System.print("\n--- Rename ---")
|
||||
var renamed = testFile.rename(testDir / "greeting.txt")
|
||||
System.print("Renamed to: %(renamed)")
|
||||
System.print("Old exists: %(testFile.exists())")
|
||||
System.print("New content: %(renamed.readText())")
|
||||
|
||||
System.print("\n--- Resolve ---")
|
||||
var resolved = Path.new(".").resolve()
|
||||
System.print("Resolved '.': %(resolved)")
|
||||
|
||||
System.print("\n--- Same File ---")
|
||||
System.print("Same file: %(renamed.samefile(renamed))")
|
||||
|
||||
System.print("\n--- Owner and Group ---")
|
||||
System.print("Owner: %(renamed.owner())")
|
||||
System.print("Group: %(renamed.group())")
|
||||
|
||||
System.print("\n--- Mkdir with Parents ---")
|
||||
var deep = testDir / "a" / "b" / "c"
|
||||
deep.mkdir(true)
|
||||
System.print("Created: %(deep)")
|
||||
System.print("Exists: %(deep.exists())")
|
||||
|
||||
System.print("\n--- Iterdir ---")
|
||||
(testDir / "file1.txt").writeText("one")
|
||||
(testDir / "file2.txt").writeText("two")
|
||||
var entries = testDir.iterdir()
|
||||
System.print("Entries in %(testDir.name):")
|
||||
for (entry in entries) {
|
||||
var kind = entry.isDir() ? "dir" : "file"
|
||||
System.print(" %(entry.name) (%(kind))")
|
||||
}
|
||||
|
||||
System.print("\n--- Glob ---")
|
||||
var txtFiles = testDir.glob("*.txt")
|
||||
System.print("Text files in %(testDir.name):")
|
||||
for (f in txtFiles) {
|
||||
System.print(" %(f.name)")
|
||||
}
|
||||
|
||||
System.print("\n--- Recursive Glob ---")
|
||||
(testDir / "a" / "nested.txt").writeText("nested")
|
||||
(testDir / "a" / "b" / "deep.txt").writeText("deep")
|
||||
var allTxt = testDir.rglob("*.txt")
|
||||
System.print("All text files (recursive):")
|
||||
for (f in allTxt) {
|
||||
System.print(" %(f.relativeTo(testDir))")
|
||||
}
|
||||
|
||||
System.print("\n--- Walk ---")
|
||||
System.print("Directory tree:")
|
||||
for (entry in testDir.walk()) {
|
||||
var dir = entry[0]
|
||||
var dirs = entry[1]
|
||||
var files = entry[2]
|
||||
var indent = dir == testDir ? "" : " "
|
||||
System.print("%(indent)%(dir.relativeTo(testDir))/ (%(dirs.count) dirs, %(files.count) files)")
|
||||
}
|
||||
|
||||
System.print("\n--- Cleanup ---")
|
||||
testDir.rmtree()
|
||||
System.print("Cleaned up: %(testDir) (exists: %(testDir.exists()))")
|
||||
|
||||
System.print("\n=== Demo Complete ===")
|
||||
63
example/string_demo.wren
vendored
Normal file
63
example/string_demo.wren
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
System.print("=== String Methods Demo ===\n")
|
||||
|
||||
System.print("--- Case Conversion ---")
|
||||
var text = "Hello World"
|
||||
System.print("Original: %(text)")
|
||||
System.print("lower: %(text.lower)")
|
||||
System.print("upper: %(text.upper)")
|
||||
System.print("capitalize: %("hello world".capitalize)")
|
||||
System.print("title: %("hello world".title)")
|
||||
System.print("swapCase: %(text.swapCase)")
|
||||
|
||||
System.print("\n--- Character Testing ---")
|
||||
System.print("\"hello\".isLower: %("hello".isLower)")
|
||||
System.print("\"HELLO\".isUpper: %("HELLO".isUpper)")
|
||||
System.print("\"12345\".isDigit: %("12345".isDigit)")
|
||||
System.print("\"hello\".isAlpha: %("hello".isAlpha)")
|
||||
System.print("\"hello1\".isAlphaNumeric: %("hello1".isAlphaNumeric)")
|
||||
System.print("\" \".isSpace: %(" ".isSpace)")
|
||||
System.print("\"hello\".isAscii: %("hello".isAscii)")
|
||||
|
||||
System.print("\n--- Search ---")
|
||||
var sentence = "hello world hello"
|
||||
System.print("Text: %(sentence)")
|
||||
System.print("lastIndexOf(\"hello\"): %(sentence.lastIndexOf("hello"))")
|
||||
System.print("lastIndexOf(\"hello\", 11): %(sentence.lastIndexOf("hello", 11))")
|
||||
|
||||
System.print("\n--- Transformation ---")
|
||||
System.print("\"hello\".reverse: %("hello".reverse)")
|
||||
System.print("\"hi\".center(10, \"-\"): %("hi".center(10, "-"))")
|
||||
System.print("\"42\".lpad(5, \"0\"): %("42".lpad(5, "0"))")
|
||||
System.print("\"hi\".rpad(5, \".\"): %("hi".rpad(5, "."))")
|
||||
System.print("\"42\".zfill(5): %("42".zfill(5))")
|
||||
System.print("\"-42\".zfill(6): %("-42".zfill(6))")
|
||||
|
||||
System.print("\n--- Prefix/Suffix ---")
|
||||
System.print("removePrefix(\"Hello\"): %("HelloWorld".removePrefix("Hello"))")
|
||||
System.print("removeSuffix(\"World\"): %("HelloWorld".removeSuffix("World"))")
|
||||
|
||||
System.print("\n--- Comparison ---")
|
||||
System.print("\"apple\" < \"banana\": %("apple" < "banana")")
|
||||
System.print("\"banana\" > \"apple\": %("banana" > "apple")")
|
||||
System.print("\"abc\" <= \"abc\": %("abc" <= "abc")")
|
||||
System.print("\"abc\" >= \"abc\": %("abc" >= "abc")")
|
||||
System.print("\"abc\" < \"abcd\": %("abc" < "abcd")")
|
||||
System.print("\"abcd\" > \"abc\": %("abcd" > "abc")")
|
||||
System.print("\"Z\" < \"a\": %("Z" < "a")")
|
||||
|
||||
var fruits = ["cherry", "apple", "banana"]
|
||||
System.print("Sorted: %(fruits.sort {|a, b| a < b})")
|
||||
|
||||
System.print("\n--- Splitting ---")
|
||||
var lines = "line1\nline2\nline3".splitLines
|
||||
System.print("splitLines: %(lines)")
|
||||
|
||||
var chars = "abc".chars
|
||||
System.print("chars: %(chars)")
|
||||
|
||||
System.print("\n--- Conversion ---")
|
||||
System.print("\"42\".toNum: %("42".toNum)")
|
||||
System.print("\"3.14\".toNum: %("3.14".toNum)")
|
||||
System.print("\"abc\".toNum: %("abc".toNum)")
|
||||
44
example/strutil_demo.wren
vendored
Normal file
44
example/strutil_demo.wren
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "strutil" for Str
|
||||
import "crypto" for Hash
|
||||
|
||||
System.print("=== String Utility Module Demo ===\n")
|
||||
|
||||
System.print("--- Case Conversion ---")
|
||||
System.print("toLower('HELLO World'): " + Str.toLower("HELLO World"))
|
||||
System.print("toUpper('hello world'): " + Str.toUpper("hello world"))
|
||||
|
||||
System.print("\n--- Hex Encoding ---")
|
||||
var data = "ABC"
|
||||
var hex = Str.hexEncode(data)
|
||||
System.print("hexEncode('ABC'): " + hex)
|
||||
System.print("hexDecode('" + hex + "'): " + Str.hexDecode(hex))
|
||||
|
||||
System.print("\n--- SHA256 Hash with Hex ---")
|
||||
var hash = Hash.sha256("hello")
|
||||
System.print("SHA256('hello'): " + Hash.toHex(hash))
|
||||
|
||||
System.print("\n--- String Repetition ---")
|
||||
System.print("repeat('ab', 5): " + Str.repeat("ab", 5))
|
||||
|
||||
System.print("\n--- Padding ---")
|
||||
System.print("padLeft('42', 5, '0'): " + Str.padLeft("42", 5, "0"))
|
||||
System.print("padRight('x', 5, '-'): " + Str.padRight("x", 5, "-"))
|
||||
|
||||
System.print("\n--- HTML Escaping ---")
|
||||
var html = "<script>alert('xss')</script>"
|
||||
System.print("Original: " + html)
|
||||
System.print("Escaped: " + Str.escapeHtml(html))
|
||||
|
||||
System.print("\n--- JSON Escaping ---")
|
||||
var text = "Line 1\nLine 2\tTabbed"
|
||||
System.print("Original: Line 1\\nLine 2\\tTabbed")
|
||||
System.print("Escaped: " + Str.escapeJson(text))
|
||||
|
||||
System.print("\n--- URL Encoding ---")
|
||||
var url = "hello world & more"
|
||||
var encoded = Str.urlEncode(url)
|
||||
System.print("Original: " + url)
|
||||
System.print("Encoded: " + encoded)
|
||||
System.print("Decoded: " + Str.urlDecode(encoded))
|
||||
60
example/tempfile_demo.wren
vendored
Normal file
60
example/tempfile_demo.wren
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "tempfile" for TempFile, NamedTemporaryFile, TemporaryDirectory
|
||||
import "pathlib" for Path
|
||||
|
||||
System.print("=== TempFile Module Demo ===\n")
|
||||
|
||||
System.print("--- Utility Functions ---")
|
||||
System.print("Temp directory: %(TempFile.gettempdir())")
|
||||
System.print("Temp prefix: %(TempFile.gettempprefix())")
|
||||
|
||||
System.print("\n--- mktemp (name only, no file created) ---")
|
||||
var name = TempFile.mktemp(".txt", "demo_")
|
||||
System.print("Generated name: %(name)")
|
||||
System.print("File exists: %(Path.new(name).exists())")
|
||||
|
||||
System.print("\n--- mkstemp (creates file atomically) ---")
|
||||
var path = TempFile.mkstemp(".dat", "demo_")
|
||||
System.print("Created file: %(path)")
|
||||
System.print("File exists: %(Path.new(path).exists())")
|
||||
Path.new(path).unlink()
|
||||
|
||||
System.print("\n--- mkdtemp (creates directory) ---")
|
||||
var dir = TempFile.mkdtemp("_work", "demo_")
|
||||
System.print("Created directory: %(dir)")
|
||||
System.print("Is directory: %(Path.new(dir).isDir())")
|
||||
Path.new(dir).rmdir()
|
||||
|
||||
System.print("\n--- NamedTemporaryFile ---")
|
||||
var tmp = NamedTemporaryFile.new(".txt", "demo_")
|
||||
System.print("Temp file: %(tmp.name)")
|
||||
tmp.write("Hello from tempfile module!")
|
||||
System.print("Content: %(tmp.read())")
|
||||
System.print("Auto-delete: %(tmp.delete)")
|
||||
tmp.close()
|
||||
System.print("Deleted after close: %(!(Path.new(tmp.name).exists()))")
|
||||
|
||||
System.print("\n--- NamedTemporaryFile.use (context manager) ---")
|
||||
NamedTemporaryFile.new(".log").use {|f|
|
||||
f.write("Log entry: operation completed")
|
||||
System.print("Inside use block: %(f.read())")
|
||||
}
|
||||
System.print("Automatically cleaned up after use block")
|
||||
|
||||
System.print("\n--- TemporaryDirectory ---")
|
||||
var tmpDir = TemporaryDirectory.new("_session", "demo_")
|
||||
System.print("Temp dir: %(tmpDir.name)")
|
||||
System.print("Is directory: %(tmpDir.path.isDir())")
|
||||
tmpDir.cleanup()
|
||||
System.print("Deleted after cleanup: %(!(Path.new(tmpDir.name).exists()))")
|
||||
|
||||
System.print("\n--- TemporaryDirectory.use (context manager) ---")
|
||||
TemporaryDirectory.new().use {|d|
|
||||
System.print("Working in: %(d.name)")
|
||||
Path.new(d.name + "/data.txt").writeText("temporary data")
|
||||
System.print("Created file inside temp dir")
|
||||
}
|
||||
System.print("Automatically cleaned up with all contents")
|
||||
|
||||
System.print("\n=== Done ===")
|
||||
27
example/uuid_demo.wren
vendored
Normal file
27
example/uuid_demo.wren
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "uuid" for Uuid
|
||||
|
||||
System.print("=== UUID Module Demo ===")
|
||||
|
||||
System.print("\n--- Generating UUIDs ---")
|
||||
for (i in 1..5) {
|
||||
System.print("UUID %(i): %(Uuid.v4())")
|
||||
}
|
||||
|
||||
System.print("\n--- UUID Validation ---")
|
||||
var testUuid = Uuid.v4()
|
||||
System.print("Generated: %(testUuid)")
|
||||
System.print("isValid: %(Uuid.isValid(testUuid))")
|
||||
System.print("isV4: %(Uuid.isV4(testUuid))")
|
||||
|
||||
System.print("\n--- Invalid UUID Examples ---")
|
||||
var invalidUuids = [
|
||||
"invalid",
|
||||
"550e8400-e29b-41d4-a716",
|
||||
"550e8400-e29b-41d4-a716-44665544000X",
|
||||
123
|
||||
]
|
||||
for (uuid in invalidUuids) {
|
||||
System.print("%(uuid): isValid = %(Uuid.isValid(uuid))")
|
||||
}
|
||||
72
example/wdantic_demo.wren
vendored
Normal file
72
example/wdantic_demo.wren
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "wdantic" for Validator, Schema, Field
|
||||
|
||||
System.print("=== Wdantic Module Demo ===")
|
||||
|
||||
System.print("\n--- Validators ---")
|
||||
var testEmail = "user@example.com"
|
||||
System.print("Email '%(testEmail)' valid: %(Validator.email(testEmail))")
|
||||
|
||||
var testUrl = "https://example.com/path"
|
||||
System.print("URL '%(testUrl)' valid: %(Validator.url(testUrl))")
|
||||
|
||||
var testUuid = "550e8400-e29b-41d4-a716-446655440000"
|
||||
System.print("UUID '%(testUuid)' valid: %(Validator.uuid(testUuid))")
|
||||
|
||||
System.print("\n--- Schema Validation ---")
|
||||
var userSchema = Schema.new({
|
||||
"name": Field.string({"minLength": 1, "maxLength": 100}),
|
||||
"age": Field.integer({"min": 0, "max": 150}),
|
||||
"email": Field.email(),
|
||||
"active": Field.boolean()
|
||||
})
|
||||
|
||||
var validUser = {
|
||||
"name": "John Doe",
|
||||
"age": 30,
|
||||
"email": "john@example.com",
|
||||
"active": true
|
||||
}
|
||||
|
||||
var result = userSchema.validate(validUser)
|
||||
System.print("Valid user result: %(result.isValid)")
|
||||
if (result.isValid) {
|
||||
System.print("Validated data: %(result.data)")
|
||||
}
|
||||
|
||||
System.print("\n--- Invalid Data ---")
|
||||
var invalidUser = {
|
||||
"name": "",
|
||||
"age": -5,
|
||||
"email": "not-an-email",
|
||||
"active": "yes"
|
||||
}
|
||||
|
||||
var result2 = userSchema.validate(invalidUser)
|
||||
System.print("Invalid user result: %(result2.isValid)")
|
||||
System.print("Errors:")
|
||||
for (error in result2.errors) {
|
||||
System.print(" - %(error)")
|
||||
}
|
||||
|
||||
System.print("\n--- Optional Fields ---")
|
||||
var profileSchema = Schema.new({
|
||||
"username": Field.string(),
|
||||
"bio": Field.optional(Field.string({"maxLength": 500})),
|
||||
"website": Field.optional(Field.string())
|
||||
})
|
||||
|
||||
var minimalProfile = {"username": "alice"}
|
||||
var fullProfile = {"username": "bob", "bio": "Developer", "website": "https://bob.dev"}
|
||||
|
||||
System.print("Minimal profile valid: %(profileSchema.validate(minimalProfile).isValid)")
|
||||
System.print("Full profile valid: %(profileSchema.validate(fullProfile).isValid)")
|
||||
|
||||
System.print("\n--- List Validation ---")
|
||||
var tagsSchema = Schema.new({
|
||||
"tags": Field.list(Field.string())
|
||||
})
|
||||
|
||||
var tagData = {"tags": ["wren", "programming", "cli"]}
|
||||
System.print("Tags valid: %(tagsSchema.validate(tagData).isValid)")
|
||||
122
example/web_chat_demo.wren
vendored
Normal file
122
example/web_chat_demo.wren
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "web" for Application, Response, View
|
||||
import "wdantic" for Schema, Field
|
||||
import "dataset" for Dataset
|
||||
import "uuid" for Uuid
|
||||
import "json" for Json
|
||||
|
||||
var db = Dataset.memory()
|
||||
var messages = db["messages"]
|
||||
|
||||
var messageSchema = Schema.new({
|
||||
"username": Field.string({"minLength": 1, "maxLength": 50}),
|
||||
"content": Field.string({"minLength": 1, "maxLength": 500})
|
||||
})
|
||||
|
||||
class MessagesView is View {
|
||||
get(request) {
|
||||
var allMessages = messages.all()
|
||||
return Response.json(allMessages)
|
||||
}
|
||||
|
||||
post(request) {
|
||||
var data = request.json
|
||||
var result = messageSchema.validate(data)
|
||||
if (!result.isValid) {
|
||||
var r = Response.json({"error": "Validation failed", "details": result.errors.map { |e| e.toString }.toList})
|
||||
r.status = 400
|
||||
return r
|
||||
}
|
||||
var msg = messages.insert({
|
||||
"username": data["username"],
|
||||
"content": data["content"]
|
||||
})
|
||||
return Response.json(msg)
|
||||
}
|
||||
}
|
||||
|
||||
class MessageView is View {
|
||||
get(request) {
|
||||
var id = request.params["id"]
|
||||
var msg = messages.findOne({"uid": id})
|
||||
if (msg == null) {
|
||||
var r = Response.json({"error": "Message not found"})
|
||||
r.status = 404
|
||||
return r
|
||||
}
|
||||
return Response.json(msg)
|
||||
}
|
||||
|
||||
delete(request) {
|
||||
var id = request.params["id"]
|
||||
var deleted = messages.delete(id)
|
||||
if (!deleted) {
|
||||
var r = Response.json({"error": "Message not found"})
|
||||
r.status = 404
|
||||
return r
|
||||
}
|
||||
return Response.json({"status": "deleted"})
|
||||
}
|
||||
}
|
||||
|
||||
var indexHtml = "<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Chat Demo</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
||||
#messages { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: auto; margin-bottom: 10px; }
|
||||
.message { margin: 5px 0; padding: 5px; background: #f5f5f5; }
|
||||
.message .username { font-weight: bold; }
|
||||
form { display: flex; gap: 10px; }
|
||||
input, button { padding: 8px; }
|
||||
input[name=content] { flex: 1; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Chat Demo</h1>
|
||||
<div id=\"messages\"></div>
|
||||
<form id=\"form\">
|
||||
<input name=\"username\" placeholder=\"Username\" required>
|
||||
<input name=\"content\" placeholder=\"Message\" required>
|
||||
<button type=\"submit\">Send</button>
|
||||
</form>
|
||||
<script>
|
||||
async function loadMessages() {
|
||||
const res = await fetch('/api/messages');
|
||||
const msgs = await res.json();
|
||||
const div = document.getElementById('messages');
|
||||
div.innerHTML = msgs.map(m =>
|
||||
'<div class=\"message\"><span class=\"username\">' + m.username + ':</span> ' + m.content + '</div>'
|
||||
).join('');
|
||||
div.scrollTop = div.scrollHeight;
|
||||
}
|
||||
document.getElementById('form').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
await fetch('/api/messages', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
username: form.username.value,
|
||||
content: form.content.value
|
||||
})
|
||||
});
|
||||
form.content.value = '';
|
||||
loadMessages();
|
||||
};
|
||||
loadMessages();
|
||||
setInterval(loadMessages, 3000);
|
||||
</script>
|
||||
</body>
|
||||
</html>"
|
||||
|
||||
var app = Application.new()
|
||||
|
||||
app.get("/", Fn.new { |req| Response.html(indexHtml) })
|
||||
app.addView("/api/messages", MessagesView)
|
||||
app.addView("/api/messages/:id", MessageView)
|
||||
|
||||
System.print("Chat demo running on http://localhost:8080")
|
||||
app.run("0.0.0.0", 8080)
|
||||
58
example/web_demo.wren
vendored
Normal file
58
example/web_demo.wren
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "web" for Application, Response, View
|
||||
import "json" for Json
|
||||
|
||||
class HelloView is View {
|
||||
get(request) {
|
||||
return Response.text("Hello, World!")
|
||||
}
|
||||
}
|
||||
|
||||
class ApiView is View {
|
||||
get(request) {
|
||||
var data = {"message": "API response", "method": "GET"}
|
||||
return Response.json(data)
|
||||
}
|
||||
|
||||
post(request) {
|
||||
var body = request.json
|
||||
var data = {"message": "Data received", "received": body}
|
||||
return Response.json(data)
|
||||
}
|
||||
}
|
||||
|
||||
class UserView is View {
|
||||
get(request) {
|
||||
var userId = request.params["id"]
|
||||
return Response.json({"user_id": userId, "action": "get"})
|
||||
}
|
||||
}
|
||||
|
||||
var app = Application.new()
|
||||
|
||||
app.get("/", Fn.new { |req|
|
||||
return Response.html("<h1>Welcome to Wren Web!</h1><p>A simple web framework.</p>")
|
||||
})
|
||||
|
||||
app.addView("/hello", HelloView)
|
||||
app.addView("/api", ApiView)
|
||||
app.addView("/users/:id", UserView)
|
||||
|
||||
app.get("/greet/:name", Fn.new { |req|
|
||||
var name = req.params["name"]
|
||||
return Response.text("Hello, " + name + "!")
|
||||
})
|
||||
|
||||
app.get("/session", Fn.new { |req|
|
||||
var count = req.session["count"]
|
||||
if (count == null) count = 0
|
||||
count = count + 1
|
||||
req.session["count"] = count
|
||||
return Response.json({"visit_count": count})
|
||||
})
|
||||
|
||||
app.static_("/static", "./static")
|
||||
|
||||
System.print("Starting server...")
|
||||
app.run("0.0.0.0", 8080)
|
||||
399
manual/api/argparse.html
Normal file
399
manual/api/argparse.html
Normal file
@ -0,0 +1,399 @@
|
||||
<!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>argparse - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html" class="active">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>argparse</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>argparse</h1>
|
||||
|
||||
<p>The <code>argparse</code> module provides command-line argument parsing with support for positional arguments, optional flags, type conversion, and help generation.</p>
|
||||
|
||||
<pre><code>import "argparse" for ArgumentParser</code></pre>
|
||||
|
||||
<h2>ArgumentParser Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>ArgumentParser</h3>
|
||||
<p>Command-line argument parser</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructors</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">ArgumentParser.new</span>() → <span class="type">ArgumentParser</span>
|
||||
</div>
|
||||
<p>Creates a new argument parser with no description.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">ArgumentParser.new</span>(<span class="param">description</span>) → <span class="type">ArgumentParser</span>
|
||||
</div>
|
||||
<p>Creates a new argument parser with a description shown in help.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">description</span> <span class="param-type">(String)</span> - Description of the program</li>
|
||||
</ul>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">prog</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Gets or sets the program name shown in help output.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">description</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Gets or sets the program description.</p>
|
||||
|
||||
<h3>Instance Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">addArgument</span>(<span class="param">name</span>) → <span class="type">ArgumentParser</span>
|
||||
</div>
|
||||
<p>Adds an argument with default options. Returns the parser for chaining.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Argument name (positional) or flag (starts with <code>-</code>)</li>
|
||||
</ul>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">addArgument</span>(<span class="param">name</span>, <span class="param">options</span>) → <span class="type">ArgumentParser</span>
|
||||
</div>
|
||||
<p>Adds an argument with specified options.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Argument name or flag</li>
|
||||
<li><span class="param-name">options</span> <span class="param-type">(Map)</span> - Configuration options</li>
|
||||
</ul>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">parseArgs</span>() → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Parses arguments from <code>Process.arguments</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">parseArgs</span>(<span class="param">args</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Parses the provided argument list.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">args</span> <span class="param-type">(List)</span> - List of argument strings</li>
|
||||
<li><span class="returns">Returns:</span> Map of argument names to values</li>
|
||||
</ul>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">printHelp</span>()
|
||||
</div>
|
||||
<p>Prints formatted help information to stdout.</p>
|
||||
|
||||
<h2>Argument Options</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>long</code></td>
|
||||
<td>String</td>
|
||||
<td>Long form of the flag (e.g., <code>--verbose</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>type</code></td>
|
||||
<td>String</td>
|
||||
<td><code>"string"</code>, <code>"int"</code>, <code>"float"</code>, or <code>"bool"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>default</code></td>
|
||||
<td>any</td>
|
||||
<td>Default value if not provided</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>required</code></td>
|
||||
<td>Bool</td>
|
||||
<td>Whether the argument is required</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>help</code></td>
|
||||
<td>String</td>
|
||||
<td>Help text description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>choices</code></td>
|
||||
<td>List</td>
|
||||
<td>List of valid values</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>action</code></td>
|
||||
<td>String</td>
|
||||
<td>How to handle the argument</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>nargs</code></td>
|
||||
<td>String/Num</td>
|
||||
<td>Number of values to consume</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dest</code></td>
|
||||
<td>String</td>
|
||||
<td>Name for the result map key</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Actions</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Action</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>store</code></td>
|
||||
<td>Store the value (default)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>storeTrue</code></td>
|
||||
<td>Store <code>true</code> when flag is present</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>storeFalse</code></td>
|
||||
<td>Store <code>false</code> when flag is present</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>count</code></td>
|
||||
<td>Count occurrences of the flag</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>append</code></td>
|
||||
<td>Append values to a list</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Nargs Values</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>*</code></td>
|
||||
<td>Zero or more values (returns list)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>+</code></td>
|
||||
<td>One or more values (returns list)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>N</code> (number)</td>
|
||||
<td>Exactly N values (returns list)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Basic Usage</h3>
|
||||
<pre><code>import "argparse" for ArgumentParser
|
||||
|
||||
var parser = ArgumentParser.new("File processing utility")
|
||||
parser.prog = "myapp"
|
||||
|
||||
parser.addArgument("filename", {"help": "Input file to process"})
|
||||
parser.addArgument("-o", {
|
||||
"long": "--output",
|
||||
"help": "Output file path",
|
||||
"default": "output.txt"
|
||||
})
|
||||
|
||||
var args = parser.parseArgs()
|
||||
System.print("Input: %(args["filename"])")
|
||||
System.print("Output: %(args["output"])")</code></pre>
|
||||
|
||||
<h3>Boolean Flags</h3>
|
||||
<pre><code>import "argparse" for ArgumentParser
|
||||
|
||||
var parser = ArgumentParser.new()
|
||||
parser.addArgument("-v", {
|
||||
"long": "--verbose",
|
||||
"action": "storeTrue",
|
||||
"help": "Enable verbose output"
|
||||
})
|
||||
parser.addArgument("-q", {
|
||||
"long": "--quiet",
|
||||
"action": "storeFalse",
|
||||
"dest": "verbose",
|
||||
"help": "Disable verbose output"
|
||||
})
|
||||
|
||||
var args = parser.parseArgs(["-v"])
|
||||
System.print(args["verbose"]) // true</code></pre>
|
||||
|
||||
<h3>Type Conversion</h3>
|
||||
<pre><code>import "argparse" for ArgumentParser
|
||||
|
||||
var parser = ArgumentParser.new()
|
||||
parser.addArgument("-n", {
|
||||
"long": "--count",
|
||||
"type": "int",
|
||||
"default": 1,
|
||||
"help": "Number of iterations"
|
||||
})
|
||||
parser.addArgument("-t", {
|
||||
"long": "--threshold",
|
||||
"type": "float",
|
||||
"default": 0.5,
|
||||
"help": "Detection threshold"
|
||||
})
|
||||
|
||||
var args = parser.parseArgs(["-n", "10", "-t", "0.75"])
|
||||
System.print(args["count"]) // 10 (Num)
|
||||
System.print(args["threshold"]) // 0.75 (Num)</code></pre>
|
||||
|
||||
<h3>Multiple Values</h3>
|
||||
<pre><code>import "argparse" for ArgumentParser
|
||||
|
||||
var parser = ArgumentParser.new()
|
||||
parser.addArgument("files", {
|
||||
"nargs": "+",
|
||||
"help": "Files to process"
|
||||
})
|
||||
parser.addArgument("-e", {
|
||||
"long": "--exclude",
|
||||
"action": "append",
|
||||
"help": "Patterns to exclude"
|
||||
})
|
||||
|
||||
var args = parser.parseArgs(["file1.txt", "file2.txt", "-e", "*.tmp", "-e", "*.bak"])
|
||||
System.print(args["files"]) // ["file1.txt", "file2.txt"]
|
||||
System.print(args["exclude"]) // ["*.tmp", "*.bak"]</code></pre>
|
||||
|
||||
<h3>Choices</h3>
|
||||
<pre><code>import "argparse" for ArgumentParser
|
||||
|
||||
var parser = ArgumentParser.new()
|
||||
parser.addArgument("-f", {
|
||||
"long": "--format",
|
||||
"choices": ["json", "xml", "csv"],
|
||||
"default": "json",
|
||||
"help": "Output format"
|
||||
})
|
||||
|
||||
var args = parser.parseArgs(["-f", "csv"])
|
||||
System.print(args["format"]) // csv</code></pre>
|
||||
|
||||
<h3>Verbosity Counter</h3>
|
||||
<pre><code>import "argparse" for ArgumentParser
|
||||
|
||||
var parser = ArgumentParser.new()
|
||||
parser.addArgument("-v", {
|
||||
"action": "count",
|
||||
"help": "Increase verbosity"
|
||||
})
|
||||
|
||||
var args = parser.parseArgs(["-v", "-v", "-v"])
|
||||
System.print(args["v"]) // 3</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Argument names starting with <code>-</code> are treated as optional flags. Names without a leading dash are positional arguments. Hyphens in argument names are converted to underscores in the result map.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="html.html" class="prev">html</a>
|
||||
<a href="wdantic.html" class="next">wdantic</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
270
manual/api/base64.html
Normal file
270
manual/api/base64.html
Normal file
@ -0,0 +1,270 @@
|
||||
<!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>base64 - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html" class="active">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>base64</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>base64</h1>
|
||||
|
||||
<p>The <code>base64</code> module provides Base64 encoding and decoding functionality, including URL-safe variants.</p>
|
||||
|
||||
<pre><code>import "base64" for Base64</code></pre>
|
||||
|
||||
<h2>Base64 Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Base64</h3>
|
||||
<p>Base64 encoding and decoding utilities</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Base64.encode</span>(<span class="param">data</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Encodes data to standard Base64.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Data to encode</li>
|
||||
<li><span class="returns">Returns:</span> Base64-encoded string</li>
|
||||
</ul>
|
||||
<pre><code>var encoded = Base64.encode("Hello, World!")
|
||||
System.print(encoded) // SGVsbG8sIFdvcmxkIQ==</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Base64.decode</span>(<span class="param">data</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Decodes a Base64-encoded string.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Base64 string to decode</li>
|
||||
<li><span class="returns">Returns:</span> Decoded data as a string</li>
|
||||
</ul>
|
||||
<pre><code>var decoded = Base64.decode("SGVsbG8sIFdvcmxkIQ==")
|
||||
System.print(decoded) // Hello, World!</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Base64.encodeUrl</span>(<span class="param">data</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Encodes data to URL-safe Base64. Replaces <code>+</code> with <code>-</code>, <code>/</code> with <code>_</code>, and removes padding.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Data to encode</li>
|
||||
<li><span class="returns">Returns:</span> URL-safe Base64 string (no padding)</li>
|
||||
</ul>
|
||||
<pre><code>var encoded = Base64.encodeUrl("Hello, World!")
|
||||
System.print(encoded) // SGVsbG8sIFdvcmxkIQ</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Base64.decodeUrl</span>(<span class="param">data</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Decodes a URL-safe Base64 string. Handles strings with or without padding.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - URL-safe Base64 string to decode</li>
|
||||
<li><span class="returns">Returns:</span> Decoded data as a string</li>
|
||||
</ul>
|
||||
<pre><code>var decoded = Base64.decodeUrl("SGVsbG8sIFdvcmxkIQ")
|
||||
System.print(decoded) // Hello, World!</code></pre>
|
||||
|
||||
<h2>Encoding Comparison</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Alphabet</th>
|
||||
<th>Padding</th>
|
||||
<th>Use Case</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>encode</code></td>
|
||||
<td>A-Z, a-z, 0-9, +, /</td>
|
||||
<td>Yes (=)</td>
|
||||
<td>General purpose, email, file storage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>encodeUrl</code></td>
|
||||
<td>A-Z, a-z, 0-9, -, _</td>
|
||||
<td>No</td>
|
||||
<td>URLs, filenames, JWT tokens</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Basic Encoding and Decoding</h3>
|
||||
<pre><code>import "base64" for Base64
|
||||
|
||||
var original = "The quick brown fox jumps over the lazy dog"
|
||||
|
||||
var encoded = Base64.encode(original)
|
||||
System.print("Encoded: %(encoded)")
|
||||
|
||||
var decoded = Base64.decode(encoded)
|
||||
System.print("Decoded: %(decoded)")
|
||||
System.print("Match: %(original == decoded)")</code></pre>
|
||||
|
||||
<h3>Binary Data Encoding</h3>
|
||||
<pre><code>import "base64" for Base64
|
||||
|
||||
var binaryData = String.fromCodePoint(0) +
|
||||
String.fromCodePoint(1) +
|
||||
String.fromCodePoint(255)
|
||||
|
||||
var encoded = Base64.encode(binaryData)
|
||||
System.print("Encoded binary: %(encoded)")
|
||||
|
||||
var decoded = Base64.decode(encoded)
|
||||
System.print("Byte 0: %(decoded.bytes[0])")
|
||||
System.print("Byte 1: %(decoded.bytes[1])")
|
||||
System.print("Byte 2: %(decoded.bytes[2])")</code></pre>
|
||||
|
||||
<h3>URL-Safe Encoding for Tokens</h3>
|
||||
<pre><code>import "base64" for Base64
|
||||
import "crypto" for Crypto, Hash
|
||||
|
||||
var data = "user:12345"
|
||||
var token = Base64.encodeUrl(data)
|
||||
System.print("Token: %(token)")
|
||||
|
||||
var decodedToken = Base64.decodeUrl(token)
|
||||
System.print("Decoded: %(decodedToken)")</code></pre>
|
||||
|
||||
<h3>HTTP Basic Authentication</h3>
|
||||
<pre><code>import "base64" for Base64
|
||||
import "http" for Http
|
||||
|
||||
var username = "alice"
|
||||
var password = "secret123"
|
||||
var credentials = Base64.encode("%(username):%(password)")
|
||||
|
||||
var headers = {
|
||||
"Authorization": "Basic %(credentials)"
|
||||
}
|
||||
|
||||
var response = Http.get("https://api.example.com/protected", headers)
|
||||
System.print(response.body)</code></pre>
|
||||
|
||||
<h3>Data URI Encoding</h3>
|
||||
<pre><code>import "base64" for Base64
|
||||
import "io" for File
|
||||
|
||||
var imageData = File.read("image.png")
|
||||
var encoded = Base64.encode(imageData)
|
||||
var dataUri = "data:image/png;base64,%(encoded)"
|
||||
System.print(dataUri)</code></pre>
|
||||
|
||||
<h3>JWT-Style Token Parts</h3>
|
||||
<pre><code>import "base64" for Base64
|
||||
import "json" for Json
|
||||
|
||||
var header = {"alg": "HS256", "typ": "JWT"}
|
||||
var payload = {"sub": "1234567890", "name": "John Doe"}
|
||||
|
||||
var headerB64 = Base64.encodeUrl(Json.stringify(header))
|
||||
var payloadB64 = Base64.encodeUrl(Json.stringify(payload))
|
||||
|
||||
System.print("Header: %(headerB64)")
|
||||
System.print("Payload: %(payloadB64)")</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Base64 encoding increases data size by approximately 33%. A 3-byte input becomes 4 Base64 characters.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Use <code>encodeUrl</code> and <code>decodeUrl</code> when the encoded string will be used in URLs, query parameters, or filenames where <code>+</code>, <code>/</code>, and <code>=</code> characters may cause issues.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="json.html" class="prev">json</a>
|
||||
<a href="regex.html" class="next">regex</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
318
manual/api/crypto.html
Normal file
318
manual/api/crypto.html
Normal file
@ -0,0 +1,318 @@
|
||||
<!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>crypto - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html" class="active">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>crypto</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>crypto</h1>
|
||||
|
||||
<p>The <code>crypto</code> module provides cryptographic functions including secure random number generation and hash algorithms.</p>
|
||||
|
||||
<pre><code>import "crypto" for Crypto, Hash</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#crypto-class">Crypto Class</a></li>
|
||||
<li><a href="#hash-class">Hash Class</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="crypto-class">Crypto Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Crypto</h3>
|
||||
<p>Cryptographic random number generation</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Crypto.randomBytes</span>(<span class="param">length</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Generates cryptographically secure random bytes.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">length</span> <span class="param-type">(Num)</span> - Number of bytes to generate (must be non-negative)</li>
|
||||
<li><span class="returns">Returns:</span> List of random byte values (0-255)</li>
|
||||
</ul>
|
||||
<pre><code>var bytes = Crypto.randomBytes(16)
|
||||
System.print(bytes) // [142, 55, 201, 89, ...]</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Crypto.randomInt</span>(<span class="param">min</span>, <span class="param">max</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Generates a cryptographically secure random integer in the specified range.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">min</span> <span class="param-type">(Num)</span> - Minimum value (inclusive)</li>
|
||||
<li><span class="param-name">max</span> <span class="param-type">(Num)</span> - Maximum value (exclusive)</li>
|
||||
<li><span class="returns">Returns:</span> Random integer in range [min, max)</li>
|
||||
</ul>
|
||||
<pre><code>var roll = Crypto.randomInt(1, 7) // Dice roll: 1-6
|
||||
System.print(roll)</code></pre>
|
||||
|
||||
<h2 id="hash-class">Hash Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Hash</h3>
|
||||
<p>Cryptographic hash functions</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Hash.md5</span>(<span class="param">data</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Computes the MD5 hash of the input data.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">data</span> <span class="param-type">(String|List)</span> - Data to hash (string or list of bytes)</li>
|
||||
<li><span class="returns">Returns:</span> 16-byte hash as a list of bytes</li>
|
||||
</ul>
|
||||
<pre><code>var hash = Hash.md5("Hello, World!")
|
||||
System.print(Hash.toHex(hash)) // 65a8e27d8879283831b664bd8b7f0ad4</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Hash.sha1</span>(<span class="param">data</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Computes the SHA-1 hash of the input data.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">data</span> <span class="param-type">(String|List)</span> - Data to hash</li>
|
||||
<li><span class="returns">Returns:</span> 20-byte hash as a list of bytes</li>
|
||||
</ul>
|
||||
<pre><code>var hash = Hash.sha1("Hello, World!")
|
||||
System.print(Hash.toHex(hash)) // 0a0a9f2a6772942557ab5355d76af442f8f65e01</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Hash.sha256</span>(<span class="param">data</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Computes the SHA-256 hash of the input data.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">data</span> <span class="param-type">(String|List)</span> - Data to hash</li>
|
||||
<li><span class="returns">Returns:</span> 32-byte hash as a list of bytes</li>
|
||||
</ul>
|
||||
<pre><code>var hash = Hash.sha256("Hello, World!")
|
||||
System.print(Hash.toHex(hash)) // dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Hash.toHex</span>(<span class="param">bytes</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts a list of bytes to a hexadecimal string.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">bytes</span> <span class="param-type">(List)</span> - List of byte values</li>
|
||||
<li><span class="returns">Returns:</span> Lowercase hexadecimal string</li>
|
||||
</ul>
|
||||
<pre><code>var hex = Hash.toHex([0xDE, 0xAD, 0xBE, 0xEF])
|
||||
System.print(hex) // deadbeef</code></pre>
|
||||
|
||||
<h2>Hash Algorithm Comparison</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Algorithm</th>
|
||||
<th>Output Size</th>
|
||||
<th>Security</th>
|
||||
<th>Use Case</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MD5</td>
|
||||
<td>128 bits (16 bytes)</td>
|
||||
<td>Broken</td>
|
||||
<td>Checksums only (not for security)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SHA-1</td>
|
||||
<td>160 bits (20 bytes)</td>
|
||||
<td>Weak</td>
|
||||
<td>Legacy compatibility only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SHA-256</td>
|
||||
<td>256 bits (32 bytes)</td>
|
||||
<td>Strong</td>
|
||||
<td>Recommended for new applications</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Generating a Random Token</h3>
|
||||
<pre><code>import "crypto" for Crypto, Hash
|
||||
|
||||
var bytes = Crypto.randomBytes(32)
|
||||
var token = Hash.toHex(bytes)
|
||||
System.print("Random token: %(token)")</code></pre>
|
||||
|
||||
<h3>Password Hashing</h3>
|
||||
<pre><code>import "crypto" for Crypto, Hash
|
||||
|
||||
var password = "mysecretpassword"
|
||||
var salt = Crypto.randomBytes(16)
|
||||
var saltHex = Hash.toHex(salt)
|
||||
|
||||
var saltedPassword = saltHex + password
|
||||
var hash = Hash.sha256(saltedPassword)
|
||||
var hashHex = Hash.toHex(hash)
|
||||
|
||||
System.print("Salt: %(saltHex)")
|
||||
System.print("Hash: %(hashHex)")</code></pre>
|
||||
|
||||
<h3>File Checksum</h3>
|
||||
<pre><code>import "crypto" for Hash
|
||||
import "io" for File
|
||||
|
||||
var content = File.read("document.txt")
|
||||
var hash = Hash.sha256(content)
|
||||
System.print("SHA-256: %(Hash.toHex(hash))")</code></pre>
|
||||
|
||||
<h3>Random Selection</h3>
|
||||
<pre><code>import "crypto" for Crypto
|
||||
|
||||
var items = ["apple", "banana", "cherry", "date", "elderberry"]
|
||||
var index = Crypto.randomInt(0, items.count)
|
||||
System.print("Selected: %(items[index])")</code></pre>
|
||||
|
||||
<h3>Generating Random IDs</h3>
|
||||
<pre><code>import "crypto" for Crypto, Hash
|
||||
import "base64" for Base64
|
||||
|
||||
var bytes = Crypto.randomBytes(12)
|
||||
var id = ""
|
||||
for (b in bytes) {
|
||||
id = id + String.fromCodePoint(b)
|
||||
}
|
||||
var encoded = Base64.encodeUrl(id)
|
||||
System.print("Random ID: %(encoded)")</code></pre>
|
||||
|
||||
<h3>Secure Dice Roll</h3>
|
||||
<pre><code>import "crypto" for Crypto
|
||||
|
||||
var numDice = 5
|
||||
var results = []
|
||||
|
||||
for (i in 0...numDice) {
|
||||
results.add(Crypto.randomInt(1, 7))
|
||||
}
|
||||
|
||||
System.print("Dice rolls: %(results)")</code></pre>
|
||||
|
||||
<h3>Comparing Hashes</h3>
|
||||
<pre><code>import "crypto" for Hash
|
||||
|
||||
var original = "Hello, World!"
|
||||
var hash1 = Hash.sha256(original)
|
||||
var hash2 = Hash.sha256(original)
|
||||
var hash3 = Hash.sha256("Different text")
|
||||
|
||||
System.print("Same input, same hash: %(Hash.toHex(hash1) == Hash.toHex(hash2))")
|
||||
System.print("Different input, different hash: %(Hash.toHex(hash1) != Hash.toHex(hash3))")</code></pre>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>MD5 and SHA-1 are cryptographically broken and should not be used for security-sensitive applications. Use SHA-256 for new applications requiring secure hashing.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Hash functions accept both strings and lists of bytes. When a string is provided, it is automatically converted to its byte representation before hashing.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>For password storage, always use a salt (random bytes prepended to the password) before hashing. Store the salt alongside the hash for verification.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="jinja.html" class="prev">jinja</a>
|
||||
<a href="os.html" class="next">os</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
392
manual/api/dataset.html
Normal file
392
manual/api/dataset.html
Normal file
@ -0,0 +1,392 @@
|
||||
<!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>dataset - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html" class="active">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>dataset</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>dataset</h1>
|
||||
|
||||
<p>The <code>dataset</code> module provides a simple ORM-like interface for SQLite databases with automatic schema management. Tables and columns are created automatically as you insert data.</p>
|
||||
|
||||
<pre><code>import "dataset" for Dataset, Table</code></pre>
|
||||
|
||||
<h2>Dataset Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Dataset</h3>
|
||||
<p>Database connection and table access</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructors</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Dataset.open</span>(<span class="param">path</span>) → <span class="type">Dataset</span>
|
||||
</div>
|
||||
<p>Opens or creates a SQLite database file.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">path</span> <span class="param-type">(String)</span> - Path to the database file</li>
|
||||
</ul>
|
||||
<pre><code>var db = Dataset.open("data.db")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Dataset.memory</span>() → <span class="type">Dataset</span>
|
||||
</div>
|
||||
<p>Creates an in-memory database (data is lost when closed).</p>
|
||||
<pre><code>var db = Dataset.memory()</code></pre>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">db</span> → <span class="type">Database</span>
|
||||
</div>
|
||||
<p>Access to the underlying SQLite database for raw queries.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">tables</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>List of table names in the database.</p>
|
||||
|
||||
<h3>Subscript Access</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">[tableName]</span> → <span class="type">Table</span>
|
||||
</div>
|
||||
<p>Gets a table by name. Creates the table if it does not exist on first insert.</p>
|
||||
<pre><code>var users = db["users"]</code></pre>
|
||||
|
||||
<h3>Instance Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">close</span>()
|
||||
</div>
|
||||
<p>Closes the database connection.</p>
|
||||
|
||||
<h2>Table Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Table</h3>
|
||||
<p>CRUD operations on a database table</p>
|
||||
</div>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">name</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The table name.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">columns</span> → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Map of column names to SQL types.</p>
|
||||
|
||||
<h3>Instance Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">insert</span>(<span class="param">record</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Inserts a record and returns it with generated <code>uid</code> and <code>created_at</code>.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">record</span> <span class="param-type">(Map)</span> - Data to insert</li>
|
||||
<li><span class="returns">Returns:</span> Inserted record with uid and created_at</li>
|
||||
</ul>
|
||||
<pre><code>var user = db["users"].insert({
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com"
|
||||
})
|
||||
System.print(user["uid"]) // auto-generated UUID</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">update</span>(<span class="param">record</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Updates a record by uid. Returns number of rows affected.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">record</span> <span class="param-type">(Map)</span> - Must contain <code>uid</code> and fields to update</li>
|
||||
</ul>
|
||||
<pre><code>db["users"].update({
|
||||
"uid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Alice Smith"
|
||||
})</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">delete</span>(<span class="param">uid</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Soft deletes a record (sets <code>deleted_at</code> timestamp). Returns true if record was deleted.</p>
|
||||
<pre><code>db["users"].delete("550e8400-e29b-41d4-a716-446655440000")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">hardDelete</span>(<span class="param">uid</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Permanently deletes a record from the database.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">find</span>(<span class="param">conditions</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Finds records matching conditions. Returns list of records (excludes soft-deleted).</p>
|
||||
<pre><code>var admins = db["users"].find({"role": "admin"})</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">findOne</span>(<span class="param">conditions</span>) → <span class="type">Map|null</span>
|
||||
</div>
|
||||
<p>Finds first record matching conditions or null.</p>
|
||||
<pre><code>var user = db["users"].findOne({"email": "alice@example.com"})</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">all</span>() → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Returns all non-deleted records.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">count</span>() → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns count of non-deleted records.</p>
|
||||
|
||||
<h2>Query Operators</h2>
|
||||
|
||||
<p>Use suffixes in condition keys for comparison operators:</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Suffix</th>
|
||||
<th>SQL Operator</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__gt</code></td>
|
||||
<td>></td>
|
||||
<td><code>{"age__gt": 18}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__lt</code></td>
|
||||
<td><</td>
|
||||
<td><code>{"price__lt": 100}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__gte</code></td>
|
||||
<td>>=</td>
|
||||
<td><code>{"score__gte": 90}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__lte</code></td>
|
||||
<td><=</td>
|
||||
<td><code>{"score__lte": 100}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__ne</code></td>
|
||||
<td>!=</td>
|
||||
<td><code>{"status__ne": "deleted"}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__like</code></td>
|
||||
<td>LIKE</td>
|
||||
<td><code>{"name__like": "A\%"}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__in</code></td>
|
||||
<td>IN</td>
|
||||
<td><code>{"status__in": ["active", "pending"]}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>__null</code></td>
|
||||
<td>IS NULL / IS NOT NULL</td>
|
||||
<td><code>{"deleted_at__null": true}</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Automatic Features</h2>
|
||||
|
||||
<h3>Auto-Generated Fields</h3>
|
||||
<ul>
|
||||
<li><code>uid</code> - UUID v4 primary key (auto-generated if not provided)</li>
|
||||
<li><code>created_at</code> - ISO timestamp (auto-generated on insert)</li>
|
||||
<li><code>deleted_at</code> - ISO timestamp (set by soft delete)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Auto Schema</h3>
|
||||
<ul>
|
||||
<li>Tables are created automatically on first insert</li>
|
||||
<li>Columns are added automatically when new fields appear</li>
|
||||
<li>Type inference: Num → INTEGER/REAL, Bool → INTEGER, Map/List → TEXT (JSON)</li>
|
||||
</ul>
|
||||
|
||||
<h3>JSON Serialization</h3>
|
||||
<p>Maps and Lists are automatically serialized to JSON when stored and deserialized when retrieved.</p>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Basic CRUD</h3>
|
||||
<pre><code>import "dataset" for Dataset
|
||||
|
||||
var db = Dataset.open("app.db")
|
||||
var users = db["users"]
|
||||
|
||||
var user = users.insert({
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com",
|
||||
"settings": {"theme": "dark", "notifications": true}
|
||||
})
|
||||
|
||||
System.print("Created user: %(user["uid"])")
|
||||
|
||||
users.update({
|
||||
"uid": user["uid"],
|
||||
"name": "Alice Smith"
|
||||
})
|
||||
|
||||
var found = users.findOne({"email": "alice@example.com"})
|
||||
System.print("Found: %(found["name"])")
|
||||
|
||||
users.delete(user["uid"])
|
||||
|
||||
db.close()</code></pre>
|
||||
|
||||
<h3>Querying with Operators</h3>
|
||||
<pre><code>import "dataset" for Dataset
|
||||
|
||||
var db = Dataset.open("products.db")
|
||||
var products = db["products"]
|
||||
|
||||
var expensive = products.find({"price__gt": 100})
|
||||
|
||||
var cheap = products.find({"price__lte": 10})
|
||||
|
||||
var search = products.find({"name__like": "\%phone\%"})
|
||||
|
||||
var featured = products.find({
|
||||
"category__in": ["electronics", "gadgets"],
|
||||
"stock__gt": 0
|
||||
})
|
||||
|
||||
db.close()</code></pre>
|
||||
|
||||
<h3>Working with JSON Data</h3>
|
||||
<pre><code>import "dataset" for Dataset
|
||||
|
||||
var db = Dataset.memory()
|
||||
|
||||
db["posts"].insert({
|
||||
"title": "First Post",
|
||||
"tags": ["wren", "programming", "tutorial"],
|
||||
"metadata": {
|
||||
"author": "Alice",
|
||||
"views": 100,
|
||||
"featured": true
|
||||
}
|
||||
})
|
||||
|
||||
var post = db["posts"].findOne({"title": "First Post"})
|
||||
System.print(post["tags"]) // ["wren", "programming", "tutorial"]
|
||||
System.print(post["metadata"]) // {"author": "Alice", ...}</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Soft delete is the default behavior. Records are not permanently removed but marked with a <code>deleted_at</code> timestamp. Use <code>hardDelete()</code> for permanent removal. All query methods automatically exclude soft-deleted records.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>The <code>uid</code> field is the primary key. Do not use <code>id</code> as a field name. If you provide a <code>uid</code> during insert, it will be used instead of auto-generating one.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="wdantic.html" class="prev">wdantic</a>
|
||||
<a href="markdown.html" class="next">markdown</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
467
manual/api/datetime.html
Normal file
467
manual/api/datetime.html
Normal file
@ -0,0 +1,467 @@
|
||||
<!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>datetime - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html" class="active">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>datetime</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>datetime</h1>
|
||||
|
||||
<p>The <code>datetime</code> module provides date and time handling with formatting, arithmetic, and duration support.</p>
|
||||
|
||||
<pre><code>import "datetime" for DateTime, Duration</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#datetime-class">DateTime Class</a></li>
|
||||
<li><a href="#duration-class">Duration Class</a></li>
|
||||
<li><a href="#format-patterns">Format Patterns</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="datetime-class">DateTime Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>DateTime</h3>
|
||||
<p>Date and time representation</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructors</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">DateTime.now</span>() → <span class="type">DateTime</span>
|
||||
</div>
|
||||
<p>Creates a DateTime representing the current local time.</p>
|
||||
<pre><code>var now = DateTime.now()
|
||||
System.print(now) // 2024-01-15T10:30:45</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">DateTime.fromTimestamp</span>(<span class="param">timestamp</span>) → <span class="type">DateTime</span>
|
||||
</div>
|
||||
<p>Creates a DateTime from a Unix timestamp (seconds since epoch).</p>
|
||||
<pre><code>var dt = DateTime.fromTimestamp(1705312245)
|
||||
System.print(dt) // 2024-01-15T10:30:45</code></pre>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>timestamp</code></td>
|
||||
<td>Num</td>
|
||||
<td>Unix timestamp (seconds since epoch)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>year</code></td>
|
||||
<td>Num</td>
|
||||
<td>Year (e.g., 2024)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>month</code></td>
|
||||
<td>Num</td>
|
||||
<td>Month (1-12)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>day</code></td>
|
||||
<td>Num</td>
|
||||
<td>Day of month (1-31)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>hour</code></td>
|
||||
<td>Num</td>
|
||||
<td>Hour (0-23)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>minute</code></td>
|
||||
<td>Num</td>
|
||||
<td>Minute (0-59)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>second</code></td>
|
||||
<td>Num</td>
|
||||
<td>Second (0-59)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dayOfWeek</code></td>
|
||||
<td>Num</td>
|
||||
<td>Day of week (0=Sunday, 6=Saturday)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dayOfYear</code></td>
|
||||
<td>Num</td>
|
||||
<td>Day of year (1-366)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>isDst</code></td>
|
||||
<td>Bool</td>
|
||||
<td>True if daylight saving time is in effect</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">format</span>(<span class="param">pattern</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Formats the date/time using strftime-style patterns.</p>
|
||||
<pre><code>var now = DateTime.now()
|
||||
System.print(now.format("\%Y-\%m-\%d")) // 2024-01-15
|
||||
System.print(now.format("\%H:\%M:\%S")) // 10:30:45
|
||||
System.print(now.format("\%A, \%B \%d, \%Y")) // Monday, January 15, 2024</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toIso8601</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the date/time in ISO 8601 format.</p>
|
||||
<pre><code>System.print(DateTime.now().toIso8601) // 2024-01-15T10:30:45</code></pre>
|
||||
|
||||
<h3>Operators</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">+</span>(<span class="param">duration</span>) → <span class="type">DateTime</span>
|
||||
</div>
|
||||
<p>Adds a Duration to the DateTime.</p>
|
||||
<pre><code>var now = DateTime.now()
|
||||
var later = now + Duration.fromHours(2)
|
||||
System.print(later)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">-</span>(<span class="param">other</span>) → <span class="type">DateTime|Duration</span>
|
||||
</div>
|
||||
<p>Subtracts a Duration (returns DateTime) or another DateTime (returns Duration).</p>
|
||||
<pre><code>var now = DateTime.now()
|
||||
var earlier = now - Duration.fromDays(1)
|
||||
|
||||
var start = DateTime.fromTimestamp(1705312245)
|
||||
var end = DateTime.now()
|
||||
var elapsed = end - start // Duration</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">==</span>, <span class="method-name"><</span>, <span class="method-name">></span>, <span class="method-name"><=</span>, <span class="method-name">>=</span>
|
||||
</div>
|
||||
<p>Comparison operators for comparing two DateTimes.</p>
|
||||
|
||||
<h2 id="duration-class">Duration Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Duration</h3>
|
||||
<p>Time duration representation</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructors</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Duration.fromMilliseconds</span>(<span class="param">ms</span>) → <span class="type">Duration</span>
|
||||
</div>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Duration.fromSeconds</span>(<span class="param">s</span>) → <span class="type">Duration</span>
|
||||
</div>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Duration.fromMinutes</span>(<span class="param">m</span>) → <span class="type">Duration</span>
|
||||
</div>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Duration.fromHours</span>(<span class="param">h</span>) → <span class="type">Duration</span>
|
||||
</div>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Duration.fromDays</span>(<span class="param">d</span>) → <span class="type">Duration</span>
|
||||
</div>
|
||||
|
||||
<pre><code>var oneHour = Duration.fromHours(1)
|
||||
var threeMinutes = Duration.fromMinutes(3)
|
||||
var twoAndHalfDays = Duration.fromDays(2.5)</code></pre>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>milliseconds</code></td>
|
||||
<td>Num</td>
|
||||
<td>Total milliseconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>seconds</code></td>
|
||||
<td>Num</td>
|
||||
<td>Total seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>minutes</code></td>
|
||||
<td>Num</td>
|
||||
<td>Total minutes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>hours</code></td>
|
||||
<td>Num</td>
|
||||
<td>Total hours</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>days</code></td>
|
||||
<td>Num</td>
|
||||
<td>Total days</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Operators</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">+</span>, <span class="method-name">-</span>(<span class="param">duration</span>) → <span class="type">Duration</span>
|
||||
</div>
|
||||
<p>Add or subtract durations.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">*</span>(<span class="param">factor</span>) → <span class="type">Duration</span>
|
||||
</div>
|
||||
<p>Multiply duration by a factor.</p>
|
||||
|
||||
<pre><code>var d1 = Duration.fromHours(2)
|
||||
var d2 = Duration.fromMinutes(30)
|
||||
var total = d1 + d2 // 2.5 hours
|
||||
var doubled = d1 * 2 // 4 hours</code></pre>
|
||||
|
||||
<h2 id="format-patterns">Format Patterns</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Pattern</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%Y</code></td>
|
||||
<td>4-digit year</td>
|
||||
<td>2024</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%y</code></td>
|
||||
<td>2-digit year</td>
|
||||
<td>24</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%m</code></td>
|
||||
<td>Month (01-12)</td>
|
||||
<td>01</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%d</code></td>
|
||||
<td>Day of month (01-31)</td>
|
||||
<td>15</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%H</code></td>
|
||||
<td>Hour 24h (00-23)</td>
|
||||
<td>14</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%I</code></td>
|
||||
<td>Hour 12h (01-12)</td>
|
||||
<td>02</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%M</code></td>
|
||||
<td>Minute (00-59)</td>
|
||||
<td>30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%S</code></td>
|
||||
<td>Second (00-59)</td>
|
||||
<td>45</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%p</code></td>
|
||||
<td>AM/PM</td>
|
||||
<td>PM</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%A</code></td>
|
||||
<td>Full weekday name</td>
|
||||
<td>Monday</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%a</code></td>
|
||||
<td>Abbreviated weekday</td>
|
||||
<td>Mon</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%B</code></td>
|
||||
<td>Full month name</td>
|
||||
<td>January</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%b</code></td>
|
||||
<td>Abbreviated month</td>
|
||||
<td>Jan</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%j</code></td>
|
||||
<td>Day of year (001-366)</td>
|
||||
<td>015</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>\%w</code></td>
|
||||
<td>Weekday (0-6, Sun=0)</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>In Wren strings, <code>%</code> starts string interpolation, so use <code>\%</code> for literal percent signs in format patterns.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Current Date and Time</h3>
|
||||
<pre><code>import "datetime" for DateTime
|
||||
|
||||
var now = DateTime.now()
|
||||
System.print("Year: %(now.year)")
|
||||
System.print("Month: %(now.month)")
|
||||
System.print("Day: %(now.day)")
|
||||
System.print("Time: %(now.hour):%(now.minute):%(now.second)")</code></pre>
|
||||
|
||||
<h3>Formatting Dates</h3>
|
||||
<pre><code>import "datetime" for DateTime
|
||||
|
||||
var now = DateTime.now()
|
||||
System.print(now.format("\%Y-\%m-\%d")) // 2024-01-15
|
||||
System.print(now.format("\%B \%d, \%Y")) // January 15, 2024
|
||||
System.print(now.format("\%I:\%M \%p")) // 02:30 PM</code></pre>
|
||||
|
||||
<h3>Date Arithmetic</h3>
|
||||
<pre><code>import "datetime" for DateTime, Duration
|
||||
|
||||
var now = DateTime.now()
|
||||
var tomorrow = now + Duration.fromDays(1)
|
||||
var nextWeek = now + Duration.fromDays(7)
|
||||
var inTwoHours = now + Duration.fromHours(2)
|
||||
|
||||
System.print("Tomorrow: %(tomorrow.format("\%Y-\%m-\%d"))")
|
||||
System.print("Next week: %(nextWeek.format("\%Y-\%m-\%d"))")</code></pre>
|
||||
|
||||
<h3>Calculating Time Differences</h3>
|
||||
<pre><code>import "datetime" for DateTime
|
||||
|
||||
var start = DateTime.fromTimestamp(1705312245)
|
||||
var end = DateTime.now()
|
||||
var elapsed = end - start
|
||||
|
||||
System.print("Elapsed: %(elapsed.days.floor) days, %(elapsed.hours.floor \% 24) hours")</code></pre>
|
||||
|
||||
<h3>Comparing Dates</h3>
|
||||
<pre><code>import "datetime" for DateTime, Duration
|
||||
|
||||
var now = DateTime.now()
|
||||
var deadline = now + Duration.fromDays(7)
|
||||
|
||||
if (now < deadline) {
|
||||
System.print("Still have time!")
|
||||
}</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="sqlite.html" class="prev">sqlite</a>
|
||||
<a href="timer.html" class="next">timer</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
253
manual/api/dns.html
Normal file
253
manual/api/dns.html
Normal file
@ -0,0 +1,253 @@
|
||||
<!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>dns - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html" class="active">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>dns</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>dns</h1>
|
||||
|
||||
<p>The <code>dns</code> module provides DNS resolution functionality for looking up hostnames and resolving them to IP addresses.</p>
|
||||
|
||||
<pre><code>import "dns" for Dns</code></pre>
|
||||
|
||||
<h2>Dns Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Dns</h3>
|
||||
<p>DNS resolution utilities</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Dns.lookup</span>(<span class="param">hostname</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Resolves a hostname to an IP address. Uses the system's default address family preference.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">hostname</span> <span class="param-type">(String)</span> - Hostname to resolve</li>
|
||||
<li><span class="returns">Returns:</span> IP address as a string</li>
|
||||
</ul>
|
||||
<pre><code>var ip = Dns.lookup("example.com")
|
||||
System.print(ip) // 93.184.216.34</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Dns.lookup</span>(<span class="param">hostname</span>, <span class="param">family</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Resolves a hostname to an IP address with a specific address family.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">hostname</span> <span class="param-type">(String)</span> - Hostname to resolve</li>
|
||||
<li><span class="param-name">family</span> <span class="param-type">(Num)</span> - Address family: 0 (any), 4 (IPv4), or 6 (IPv6)</li>
|
||||
<li><span class="returns">Returns:</span> IP address as a string</li>
|
||||
</ul>
|
||||
<pre><code>var ipv4 = Dns.lookup("example.com", 4)
|
||||
System.print(ipv4) // 93.184.216.34
|
||||
|
||||
var ipv6 = Dns.lookup("example.com", 6)
|
||||
System.print(ipv6) // 2606:2800:220:1:248:1893:25c8:1946</code></pre>
|
||||
|
||||
<h3>Address Family Values</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>0</code></td>
|
||||
<td>Any (system default, typically prefers IPv4)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>4</code></td>
|
||||
<td>IPv4 only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>6</code></td>
|
||||
<td>IPv6 only</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Basic DNS Lookup</h3>
|
||||
<pre><code>import "dns" for Dns
|
||||
|
||||
var domains = ["example.com", "google.com", "github.com"]
|
||||
|
||||
for (domain in domains) {
|
||||
var ip = Dns.lookup(domain)
|
||||
System.print("%(domain) -> %(ip)")
|
||||
}</code></pre>
|
||||
|
||||
<h3>IPv4 vs IPv6</h3>
|
||||
<pre><code>import "dns" for Dns
|
||||
|
||||
var hostname = "google.com"
|
||||
|
||||
var ipv4 = Dns.lookup(hostname, 4)
|
||||
System.print("IPv4: %(ipv4)")
|
||||
|
||||
var fiber = Fiber.new {
|
||||
return Dns.lookup(hostname, 6)
|
||||
}
|
||||
var result = fiber.try()
|
||||
if (fiber.error != null) {
|
||||
System.print("IPv6: Not available")
|
||||
} else {
|
||||
System.print("IPv6: %(result)")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Using with Socket Connection</h3>
|
||||
<pre><code>import "dns" for Dns
|
||||
import "net" for Socket
|
||||
|
||||
var hostname = "httpbin.org"
|
||||
var ip = Dns.lookup(hostname, 4)
|
||||
System.print("Connecting to %(hostname) (%(ip))")
|
||||
|
||||
var socket = Socket.connect(ip, 80)
|
||||
socket.write("GET /ip HTTP/1.1\r\nHost: %(hostname)\r\nConnection: close\r\n\r\n")
|
||||
|
||||
var response = ""
|
||||
while (true) {
|
||||
var data = socket.read()
|
||||
if (data == null) break
|
||||
response = response + data
|
||||
}
|
||||
|
||||
socket.close()
|
||||
System.print(response)</code></pre>
|
||||
|
||||
<h3>Using with TLS Connection</h3>
|
||||
<pre><code>import "dns" for Dns
|
||||
import "tls" for TlsSocket
|
||||
|
||||
var hostname = "example.com"
|
||||
var ip = Dns.lookup(hostname, 4)
|
||||
|
||||
var socket = TlsSocket.connect(ip, 443, hostname)
|
||||
socket.write("GET / HTTP/1.1\r\nHost: %(hostname)\r\nConnection: close\r\n\r\n")
|
||||
|
||||
var response = socket.read()
|
||||
System.print(response)
|
||||
|
||||
socket.close()</code></pre>
|
||||
|
||||
<h3>Error Handling</h3>
|
||||
<pre><code>import "dns" for Dns
|
||||
|
||||
var fiber = Fiber.new {
|
||||
return Dns.lookup("nonexistent.invalid.domain")
|
||||
}
|
||||
|
||||
var result = fiber.try()
|
||||
if (fiber.error != null) {
|
||||
System.print("DNS lookup failed: %(fiber.error)")
|
||||
} else {
|
||||
System.print("Resolved to: %(result)")
|
||||
}</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>DNS lookups are asynchronous operations that use libuv's thread pool. The calling fiber will suspend until the lookup completes.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>The hostname must be a string. Passing a non-string value will abort the fiber with an error. Similarly, the family parameter must be 0, 4, or 6.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="net.html" class="prev">net</a>
|
||||
<a href="json.html" class="next">json</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
300
manual/api/env.html
Normal file
300
manual/api/env.html
Normal file
@ -0,0 +1,300 @@
|
||||
<!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>env - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html" class="active">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>env</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>env</h1>
|
||||
|
||||
<p>The <code>env</code> module provides access to environment variables for reading, writing, and deleting values.</p>
|
||||
|
||||
<pre><code>import "env" for Environment</code></pre>
|
||||
|
||||
<h2>Environment Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Environment</h3>
|
||||
<p>Environment variable access</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Environment.get</span>(<span class="param">name</span>) → <span class="type">String|null</span>
|
||||
</div>
|
||||
<p>Gets the value of an environment variable.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Name of the environment variable</li>
|
||||
<li><span class="returns">Returns:</span> Value of the variable, or null if not set</li>
|
||||
</ul>
|
||||
<pre><code>var home = Environment.get("HOME")
|
||||
System.print(home) // /home/alice
|
||||
|
||||
var missing = Environment.get("UNDEFINED_VAR")
|
||||
System.print(missing) // null</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Environment.set</span>(<span class="param">name</span>, <span class="param">value</span>)
|
||||
</div>
|
||||
<p>Sets an environment variable.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Name of the environment variable</li>
|
||||
<li><span class="param-name">value</span> <span class="param-type">(String)</span> - Value to set</li>
|
||||
</ul>
|
||||
<pre><code>Environment.set("MY_APP_DEBUG", "true")
|
||||
System.print(Environment.get("MY_APP_DEBUG")) // true</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Environment.delete</span>(<span class="param">name</span>)
|
||||
</div>
|
||||
<p>Deletes an environment variable.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Name of the environment variable to delete</li>
|
||||
</ul>
|
||||
<pre><code>Environment.delete("MY_APP_DEBUG")
|
||||
System.print(Environment.get("MY_APP_DEBUG")) // null</code></pre>
|
||||
|
||||
<h3>Static Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Environment.all</span> → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Returns a map of all environment variables.</p>
|
||||
<pre><code>var env = Environment.all
|
||||
for (entry in env) {
|
||||
System.print("%(entry.key) = %(entry.value)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Reading Configuration from Environment</h3>
|
||||
<pre><code>import "env" for Environment
|
||||
|
||||
var host = Environment.get("APP_HOST")
|
||||
if (host == null) host = "localhost"
|
||||
|
||||
var port = Environment.get("APP_PORT")
|
||||
if (port == null) port = "8080"
|
||||
|
||||
var debug = Environment.get("APP_DEBUG") == "true"
|
||||
|
||||
System.print("Starting server on %(host):%(port)")
|
||||
System.print("Debug mode: %(debug)")</code></pre>
|
||||
|
||||
<h3>Default Values Pattern</h3>
|
||||
<pre><code>import "env" for Environment
|
||||
|
||||
class Config {
|
||||
static get(name, defaultValue) {
|
||||
var value = Environment.get(name)
|
||||
return value != null ? value : defaultValue
|
||||
}
|
||||
|
||||
static getInt(name, defaultValue) {
|
||||
var value = Environment.get(name)
|
||||
if (value == null) return defaultValue
|
||||
return Num.fromString(value)
|
||||
}
|
||||
|
||||
static getBool(name, defaultValue) {
|
||||
var value = Environment.get(name)
|
||||
if (value == null) return defaultValue
|
||||
return value == "true" || value == "1"
|
||||
}
|
||||
}
|
||||
|
||||
var timeout = Config.getInt("TIMEOUT", 30)
|
||||
var verbose = Config.getBool("VERBOSE", false)
|
||||
var logFile = Config.get("LOG_FILE", "/var/log/app.log")
|
||||
|
||||
System.print("Timeout: %(timeout)s")
|
||||
System.print("Verbose: %(verbose)")
|
||||
System.print("Log file: %(logFile)")</code></pre>
|
||||
|
||||
<h3>Setting Environment for Subprocesses</h3>
|
||||
<pre><code>import "env" for Environment
|
||||
import "subprocess" for Subprocess
|
||||
|
||||
Environment.set("NODE_ENV", "production")
|
||||
Environment.set("PORT", "3000")
|
||||
|
||||
var result = Subprocess.run("node server.js")
|
||||
System.print(result.stdout)</code></pre>
|
||||
|
||||
<h3>Listing All Environment Variables</h3>
|
||||
<pre><code>import "env" for Environment
|
||||
|
||||
System.print("=== Environment Variables ===")
|
||||
var env = Environment.all
|
||||
var keys = []
|
||||
for (entry in env) {
|
||||
keys.add(entry.key)
|
||||
}
|
||||
keys.sort()
|
||||
|
||||
for (key in keys) {
|
||||
System.print("%(key) = %(env[key])")</code></pre>
|
||||
|
||||
<h3>Checking Required Environment Variables</h3>
|
||||
<pre><code>import "env" for Environment
|
||||
|
||||
var required = ["DATABASE_URL", "API_KEY", "SECRET_KEY"]
|
||||
var missing = []
|
||||
|
||||
for (name in required) {
|
||||
if (Environment.get(name) == null) {
|
||||
missing.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.count > 0) {
|
||||
System.print("Missing required environment variables:")
|
||||
for (name in missing) {
|
||||
System.print(" - %(name)")
|
||||
}
|
||||
Fiber.abort("Configuration error")
|
||||
}
|
||||
|
||||
System.print("All required environment variables are set")</code></pre>
|
||||
|
||||
<h3>Temporary Environment Modification</h3>
|
||||
<pre><code>import "env" for Environment
|
||||
|
||||
var originalPath = Environment.get("PATH")
|
||||
|
||||
Environment.set("PATH", "/custom/bin:" + originalPath)
|
||||
System.print("Modified PATH for operations...")
|
||||
|
||||
Environment.set("PATH", originalPath)
|
||||
System.print("Restored original PATH")</code></pre>
|
||||
|
||||
<h3>Database Connection String from Environment</h3>
|
||||
<pre><code>import "env" for Environment
|
||||
|
||||
var dbUrl = Environment.get("DATABASE_URL")
|
||||
if (dbUrl != null) {
|
||||
System.print("Using database: %(dbUrl)")
|
||||
} else {
|
||||
var host = Environment.get("DB_HOST")
|
||||
if (host == null) host = "localhost"
|
||||
var port = Environment.get("DB_PORT")
|
||||
if (port == null) port = "5432"
|
||||
var name = Environment.get("DB_NAME")
|
||||
if (name == null) name = "myapp"
|
||||
var user = Environment.get("DB_USER")
|
||||
if (user == null) user = "postgres"
|
||||
|
||||
dbUrl = "postgres://%(user)@%(host):%(port)/%(name)"
|
||||
System.print("Constructed database URL: %(dbUrl)")
|
||||
}</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Environment variables modified with <code>set</code> or <code>delete</code> only affect the current process and any child processes. They do not modify the parent shell's environment.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>For configuration that may vary between environments (development, staging, production), use environment variables. This follows the twelve-factor app methodology.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Never log or print sensitive environment variables like API keys, passwords, or secrets. Use them directly in your application without exposing their values.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="os.html" class="prev">os</a>
|
||||
<a href="signal.html" class="next">signal</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
284
manual/api/html.html
Normal file
284
manual/api/html.html
Normal file
@ -0,0 +1,284 @@
|
||||
<!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>html - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html" class="active">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>html</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>html</h1>
|
||||
|
||||
<p>The <code>html</code> module provides utilities for HTML and URL encoding/decoding, slug generation, and query string handling.</p>
|
||||
|
||||
<pre><code>import "html" for Html</code></pre>
|
||||
|
||||
<h2>Html Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Html</h3>
|
||||
<p>HTML and URL encoding utilities</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Html.urlencode</span>(<span class="param">string</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>URL-encodes a string for use in URLs and query parameters. Spaces become <code>+</code>, special characters become percent-encoded.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to encode</li>
|
||||
<li><span class="returns">Returns:</span> URL-encoded string</li>
|
||||
</ul>
|
||||
<pre><code>System.print(Html.urlencode("hello world")) // hello+world
|
||||
System.print(Html.urlencode("a=b&c=d")) // a\%3Db\%26c\%3Dd
|
||||
System.print(Html.urlencode("café")) // caf\%C3\%A9</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Html.urldecode</span>(<span class="param">string</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Decodes a URL-encoded string. Converts <code>+</code> to space and decodes percent-encoded characters.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The URL-encoded string</li>
|
||||
<li><span class="returns">Returns:</span> Decoded string</li>
|
||||
</ul>
|
||||
<pre><code>System.print(Html.urldecode("hello+world")) // hello world
|
||||
System.print(Html.urldecode("caf\%C3\%A9")) // café</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Html.slugify</span>(<span class="param">string</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts a string to a URL-friendly slug. Lowercase, alphanumeric characters with hyphens.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to slugify</li>
|
||||
<li><span class="returns">Returns:</span> URL-friendly slug</li>
|
||||
</ul>
|
||||
<pre><code>System.print(Html.slugify("Hello World")) // hello-world
|
||||
System.print(Html.slugify("My Blog Post!")) // my-blog-post
|
||||
System.print(Html.slugify(" Multiple Spaces ")) // multiple-spaces</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Html.quote</span>(<span class="param">string</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Escapes HTML special characters to prevent XSS attacks.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to escape</li>
|
||||
<li><span class="returns">Returns:</span> HTML-escaped string</li>
|
||||
</ul>
|
||||
<pre><code>System.print(Html.quote("<script>alert('xss')</script>"))
|
||||
// &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;
|
||||
|
||||
System.print(Html.quote("A & B")) // A &amp; B
|
||||
System.print(Html.quote("\"quoted\"")) // &quot;quoted&quot;</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Html.unquote</span>(<span class="param">string</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Unescapes HTML entities back to their original characters.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The HTML-escaped string</li>
|
||||
<li><span class="returns">Returns:</span> Unescaped string</li>
|
||||
</ul>
|
||||
<pre><code>System.print(Html.unquote("&lt;div&gt;")) // <div>
|
||||
System.print(Html.unquote("A &amp; B")) // A & B</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Html.encodeParams</span>(<span class="param">params</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Encodes a map of key-value pairs into a URL query string.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">params</span> <span class="param-type">(Map)</span> - Map of parameters to encode</li>
|
||||
<li><span class="returns">Returns:</span> URL-encoded query string</li>
|
||||
</ul>
|
||||
<pre><code>var params = {"name": "John Doe", "age": 30}
|
||||
System.print(Html.encodeParams(params)) // name=John+Doe&age=30
|
||||
|
||||
var search = {"q": "wren lang", "page": 1}
|
||||
System.print(Html.encodeParams(search)) // q=wren+lang&page=1</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Html.decodeParams</span>(<span class="param">string</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Decodes a URL query string into a map of key-value pairs.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - URL-encoded query string</li>
|
||||
<li><span class="returns">Returns:</span> Map of decoded parameters</li>
|
||||
</ul>
|
||||
<pre><code>var params = Html.decodeParams("name=John+Doe&age=30")
|
||||
System.print(params["name"]) // John Doe
|
||||
System.print(params["age"]) // 30</code></pre>
|
||||
|
||||
<h2>Entity Reference</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Character</th>
|
||||
<th>Entity</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>&</td>
|
||||
<td>&amp;</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><</td>
|
||||
<td>&lt;</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>></td>
|
||||
<td>&gt;</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"</td>
|
||||
<td>&quot;</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>'</td>
|
||||
<td>&#39;</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Building URLs</h3>
|
||||
<pre><code>import "html" for Html
|
||||
|
||||
var baseUrl = "https://api.example.com/search"
|
||||
var params = {
|
||||
"query": "wren programming",
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}
|
||||
|
||||
var url = baseUrl + "?" + Html.encodeParams(params)
|
||||
System.print(url)
|
||||
// https://api.example.com/search?query=wren+programming&limit=10&offset=0</code></pre>
|
||||
|
||||
<h3>Safe HTML Output</h3>
|
||||
<pre><code>import "html" for Html
|
||||
|
||||
var userInput = "<script>alert('xss')</script>"
|
||||
var safeHtml = "<div class=\"comment\">" + Html.quote(userInput) + "</div>"
|
||||
System.print(safeHtml)</code></pre>
|
||||
|
||||
<h3>Generating Slugs for URLs</h3>
|
||||
<pre><code>import "html" for Html
|
||||
|
||||
var title = "How to Build Web Apps with Wren!"
|
||||
var slug = Html.slugify(title)
|
||||
var url = "/blog/" + slug
|
||||
System.print(url) // /blog/how-to-build-web-apps-with-wren</code></pre>
|
||||
|
||||
<h3>Parsing Query Strings</h3>
|
||||
<pre><code>import "html" for Html
|
||||
|
||||
var queryString = "category=books&sort=price&order=asc"
|
||||
var params = Html.decodeParams(queryString)
|
||||
|
||||
for (key in params.keys) {
|
||||
System.print("%(key): %(params[key])")
|
||||
}</code></pre>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Always use <code>Html.quote()</code> when inserting user-provided content into HTML to prevent cross-site scripting (XSS) attacks.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="uuid.html" class="prev">uuid</a>
|
||||
<a href="argparse.html" class="next">argparse</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
432
manual/api/http.html
Normal file
432
manual/api/http.html
Normal file
@ -0,0 +1,432 @@
|
||||
<!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>http - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html" class="active">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</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>
|
||||
</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>
|
||||
</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>http</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>http</h1>
|
||||
|
||||
<p>The <code>http</code> module provides an HTTP client for making requests to web servers. It supports both HTTP and HTTPS, all common HTTP methods, custom headers, and JSON handling.</p>
|
||||
|
||||
<pre><code>import "http" for Http, HttpResponse, Url</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#http-class">Http Class</a></li>
|
||||
<li><a href="#httpresponse-class">HttpResponse Class</a></li>
|
||||
<li><a href="#url-class">Url Class</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="http-class">Http Class</h2>
|
||||
<p>The main class for making HTTP requests. All methods are static.</p>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Http</h3>
|
||||
<p>Static class for making HTTP requests</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.get</span>(<span class="param">url</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP GET request.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to request</li>
|
||||
<li><span class="returns">Returns:</span> HttpResponse object</li>
|
||||
</ul>
|
||||
<pre><code>var response = Http.get("https://api.example.com/users")
|
||||
System.print(response.body)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.get</span>(<span class="param">url</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP GET request with custom headers.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to request</li>
|
||||
<li><span class="param-name">headers</span> <span class="param-type">(Map)</span> - Custom headers to include</li>
|
||||
<li><span class="returns">Returns:</span> HttpResponse object</li>
|
||||
</ul>
|
||||
<pre><code>var headers = {"Authorization": "Bearer token123"}
|
||||
var response = Http.get("https://api.example.com/me", headers)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.post</span>(<span class="param">url</span>, <span class="param">body</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP POST request with a body.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to post to</li>
|
||||
<li><span class="param-name">body</span> <span class="param-type">(String|Map|List)</span> - Request body. Maps and Lists are JSON-encoded automatically.</li>
|
||||
<li><span class="returns">Returns:</span> HttpResponse object</li>
|
||||
</ul>
|
||||
<pre><code>var data = {"name": "Alice", "email": "alice@example.com"}
|
||||
var response = Http.post("https://api.example.com/users", data)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.post</span>(<span class="param">url</span>, <span class="param">body</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP POST request with a body and custom headers.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to post to</li>
|
||||
<li><span class="param-name">body</span> <span class="param-type">(String|Map|List)</span> - Request body</li>
|
||||
<li><span class="param-name">headers</span> <span class="param-type">(Map)</span> - Custom headers</li>
|
||||
<li><span class="returns">Returns:</span> HttpResponse object</li>
|
||||
</ul>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.put</span>(<span class="param">url</span>, <span class="param">body</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP PUT request.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.put</span>(<span class="param">url</span>, <span class="param">body</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP PUT request with custom headers.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.delete</span>(<span class="param">url</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP DELETE request.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.delete</span>(<span class="param">url</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP DELETE request with custom headers.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.patch</span>(<span class="param">url</span>, <span class="param">body</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP PATCH request.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.patch</span>(<span class="param">url</span>, <span class="param">body</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs an HTTP PATCH request with custom headers.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Http.request</span>(<span class="param">url</span>, <span class="param">method</span>, <span class="param">body</span>, <span class="param">headers</span>) → <span class="type">HttpResponse</span>
|
||||
</div>
|
||||
<p>Performs a custom HTTP request with full control over method, body, and headers.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - The URL to request</li>
|
||||
<li><span class="param-name">method</span> <span class="param-type">(String)</span> - HTTP method (GET, POST, PUT, DELETE, PATCH, etc.)</li>
|
||||
<li><span class="param-name">body</span> <span class="param-type">(String|Map|List|null)</span> - Request body or null</li>
|
||||
<li><span class="param-name">headers</span> <span class="param-type">(Map)</span> - Custom headers</li>
|
||||
<li><span class="returns">Returns:</span> HttpResponse object</li>
|
||||
</ul>
|
||||
<pre><code>var response = Http.request(
|
||||
"https://api.example.com/users/1",
|
||||
"OPTIONS",
|
||||
null,
|
||||
{}
|
||||
)</code></pre>
|
||||
|
||||
<h2 id="httpresponse-class">HttpResponse Class</h2>
|
||||
<p>Represents an HTTP response returned from a request.</p>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>HttpResponse</h3>
|
||||
<p>HTTP response object with status, headers, and body</p>
|
||||
</div>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">statusCode</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>The HTTP status code (e.g., 200, 404, 500).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">statusText</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The HTTP status text (e.g., "OK", "Not Found").</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">headers</span> → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>A map of response headers (header name to value).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">body</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The response body as a string.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">ok</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>True if the status code is in the 200-299 range.</p>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">header</span>(<span class="param">name</span>) → <span class="type">String|null</span>
|
||||
</div>
|
||||
<p>Gets a header value by name (case-insensitive).</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Header name</li>
|
||||
<li><span class="returns">Returns:</span> Header value or null if not found</li>
|
||||
</ul>
|
||||
<pre><code>var contentType = response.header("Content-Type")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">json</span> → <span class="type">Map|List|null</span>
|
||||
</div>
|
||||
<p>Parses the response body as JSON and returns the result.</p>
|
||||
<pre><code>var data = response.json
|
||||
System.print(data["name"])</code></pre>
|
||||
|
||||
<h2 id="url-class">Url Class</h2>
|
||||
<p>Utility class for parsing URLs into their components.</p>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Url</h3>
|
||||
<p>URL parser</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructor</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Url.parse</span>(<span class="param">url</span>) → <span class="type">Url</span>
|
||||
</div>
|
||||
<p>Parses a URL string into its components.</p>
|
||||
<pre><code>var url = Url.parse("https://example.com:8080/path?query=value")
|
||||
System.print(url.scheme) // https
|
||||
System.print(url.host) // example.com
|
||||
System.print(url.port) // 8080
|
||||
System.print(url.path) // /path
|
||||
System.print(url.query) // query=value</code></pre>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>scheme</code></td>
|
||||
<td>String</td>
|
||||
<td>URL scheme (http, https)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>host</code></td>
|
||||
<td>String</td>
|
||||
<td>Hostname</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>port</code></td>
|
||||
<td>Num</td>
|
||||
<td>Port number (80 for http, 443 for https by default)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>String</td>
|
||||
<td>URL path</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>query</code></td>
|
||||
<td>String</td>
|
||||
<td>Query string (without leading ?)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fullPath</code></td>
|
||||
<td>String</td>
|
||||
<td>Path with query string</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Simple GET Request</h3>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://httpbin.org/get")
|
||||
System.print("Status: %(response.statusCode)")
|
||||
System.print("Body: %(response.body)")</code></pre>
|
||||
|
||||
<h3>POST with JSON Body</h3>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var data = {
|
||||
"username": "alice",
|
||||
"email": "alice@example.com"
|
||||
}
|
||||
|
||||
var response = Http.post("https://httpbin.org/post", data)
|
||||
|
||||
if (response.ok) {
|
||||
System.print("User created!")
|
||||
System.print(response.json)
|
||||
} else {
|
||||
System.print("Error: %(response.statusCode)")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Custom Headers with Authentication</h3>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var headers = {
|
||||
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs...",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
|
||||
var response = Http.get("https://api.example.com/me", headers)
|
||||
var user = response.json
|
||||
System.print("Hello, %(user["name"])!")</code></pre>
|
||||
|
||||
<h3>Error Handling</h3>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var fiber = Fiber.new {
|
||||
var response = Http.get("https://invalid-domain.example")
|
||||
return response
|
||||
}
|
||||
|
||||
var result = fiber.try()
|
||||
if (fiber.error) {
|
||||
System.print("Request failed: %(fiber.error)")
|
||||
} else {
|
||||
System.print("Got response: %(result.statusCode)")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Sending Form Data</h3>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var formData = "username=alice&password=secret"
|
||||
var headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
var response = Http.post("https://example.com/login", formData, headers)</code></pre>
|
||||
|
||||
<h3>Checking Response Headers</h3>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://example.com")
|
||||
|
||||
System.print("Content-Type: %(response.header("Content-Type"))")
|
||||
System.print("Server: %(response.header("Server"))")
|
||||
|
||||
for (entry in response.headers) {
|
||||
System.print("%(entry.key): %(entry.value)")
|
||||
}</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>The HTTP module automatically handles HTTPS by using the TLS module. No additional configuration is needed for HTTPS URLs.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>When posting a Map or List, the module automatically sets <code>Content-Type: application/json</code> and JSON-encodes the body. For other content types, pass a string body and set the Content-Type header explicitly.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="index.html" class="prev">API Overview</a>
|
||||
<a href="websocket.html" class="next">websocket</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
430
manual/api/index.html
Normal file
430
manual/api/index.html
Normal file
@ -0,0 +1,430 @@
|
||||
<!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>API Reference - 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="index.html" class="active">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<span>API Reference</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>API Reference</h1>
|
||||
|
||||
<p>Wren-CLI provides 29 built-in modules covering networking, file I/O, data processing, system operations, and more. All modules are imported using the <code>import</code> statement.</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
import "io" for File</code></pre>
|
||||
|
||||
<h2>Core Types</h2>
|
||||
<p>Extended methods on built-in types, available without imports.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="string.html">String</a></h3>
|
||||
<p>Case conversion, character testing, padding, splitting, reversing, and more on all strings.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="number.html">Num</a></h3>
|
||||
<p>Query predicates, hyperbolic functions, base conversion, formatting, GCD/LCM, and more on all numbers.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Networking</h2>
|
||||
<p>Modules for HTTP, WebSocket, and low-level network operations.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="http.html">http</a></h3>
|
||||
<p>HTTP client for making GET, POST, PUT, DELETE, and PATCH requests. Supports HTTPS.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="websocket.html">websocket</a></h3>
|
||||
<p>WebSocket client and server implementation with full protocol support.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="tls.html">tls</a></h3>
|
||||
<p>TLS/SSL socket wrapper for encrypted connections using OpenSSL.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="net.html">net</a></h3>
|
||||
<p>Low-level TCP sockets and servers for custom network protocols.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="dns.html">dns</a></h3>
|
||||
<p>DNS resolution for hostname to IP address lookups.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Data Processing</h2>
|
||||
<p>Modules for parsing, encoding, and transforming data.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="json.html">json</a></h3>
|
||||
<p>Parse and stringify JSON data with pretty-printing support.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="base64.html">base64</a></h3>
|
||||
<p>Base64 encoding and decoding for binary-to-text conversion.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="regex.html">regex</a></h3>
|
||||
<p>Regular expression matching, replacement, and splitting.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="jinja.html">jinja</a></h3>
|
||||
<p>Jinja2-compatible template engine with filters, inheritance, and macros.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="crypto.html">crypto</a></h3>
|
||||
<p>Cryptographic hashing (MD5, SHA-1, SHA-256) and random byte generation.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="uuid.html">uuid</a></h3>
|
||||
<p>UUID generation and validation with v4 support.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="html.html">html</a></h3>
|
||||
<p>HTML/URL encoding, decoding, slug generation, and query string handling.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="markdown.html">markdown</a></h3>
|
||||
<p>Convert Markdown text to HTML with safe mode support.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>System</h2>
|
||||
<p>Modules for interacting with the operating system and environment.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="os.html">os</a></h3>
|
||||
<p>Platform information, process details, and command-line arguments.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="env.html">env</a></h3>
|
||||
<p>Read and write environment variables.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="signal.html">signal</a></h3>
|
||||
<p>Handle Unix signals like SIGINT, SIGTERM, and SIGHUP.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="subprocess.html">subprocess</a></h3>
|
||||
<p>Run external commands and capture their output.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="io.html">io</a></h3>
|
||||
<p>File and directory operations, stdin/stdout handling.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="pathlib.html">pathlib</a></h3>
|
||||
<p>Object-oriented filesystem paths with glob, walk, and tree operations.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Data & Time</h2>
|
||||
<p>Modules for databases, time, and scheduling.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="sqlite.html">sqlite</a></h3>
|
||||
<p>SQLite database for persistent data storage with SQL queries.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="datetime.html">datetime</a></h3>
|
||||
<p>Date and time manipulation with formatting and arithmetic.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="timer.html">timer</a></h3>
|
||||
<p>Sleep, timeouts, and interval timers.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="math.html">math</a></h3>
|
||||
<p>Mathematical functions like sqrt, sin, cos, and constants like PI.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="scheduler.html">scheduler</a></h3>
|
||||
<p>Async fiber scheduling for non-blocking I/O operations.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="dataset.html">dataset</a></h3>
|
||||
<p>Simple ORM for SQLite with automatic schema management.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Application Development</h2>
|
||||
<p>Modules for building web applications and command-line tools.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="web.html">web</a></h3>
|
||||
<p>Web framework with routing, middleware, sessions, and HTTP client.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="argparse.html">argparse</a></h3>
|
||||
<p>Command-line argument parsing with type conversion and help generation.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="wdantic.html">wdantic</a></h3>
|
||||
<p>Data validation with schema definitions and built-in validators.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Module Summary</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Module</th>
|
||||
<th>Main Classes</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="http.html">http</a></td>
|
||||
<td>Http, HttpResponse, Url</td>
|
||||
<td>HTTP/HTTPS client</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="websocket.html">websocket</a></td>
|
||||
<td>WebSocket, WebSocketServer, WebSocketMessage</td>
|
||||
<td>WebSocket protocol</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="tls.html">tls</a></td>
|
||||
<td>TlsSocket</td>
|
||||
<td>TLS/SSL sockets</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="net.html">net</a></td>
|
||||
<td>Socket, Server</td>
|
||||
<td>TCP networking</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="dns.html">dns</a></td>
|
||||
<td>Dns</td>
|
||||
<td>DNS resolution</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="json.html">json</a></td>
|
||||
<td>Json</td>
|
||||
<td>JSON parsing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="base64.html">base64</a></td>
|
||||
<td>Base64</td>
|
||||
<td>Base64 encoding</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="regex.html">regex</a></td>
|
||||
<td>Regex, Match</td>
|
||||
<td>Regular expressions</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="jinja.html">jinja</a></td>
|
||||
<td>Environment, Template, DictLoader, FileSystemLoader</td>
|
||||
<td>Template engine</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="crypto.html">crypto</a></td>
|
||||
<td>Crypto, Hash</td>
|
||||
<td>Cryptography</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="os.html">os</a></td>
|
||||
<td>Process, Platform</td>
|
||||
<td>OS information</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="env.html">env</a></td>
|
||||
<td>Env</td>
|
||||
<td>Environment variables</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="signal.html">signal</a></td>
|
||||
<td>Signal</td>
|
||||
<td>Unix signals</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="subprocess.html">subprocess</a></td>
|
||||
<td>Subprocess</td>
|
||||
<td>External processes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="sqlite.html">sqlite</a></td>
|
||||
<td>Sqlite</td>
|
||||
<td>SQLite database</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="datetime.html">datetime</a></td>
|
||||
<td>DateTime, Duration</td>
|
||||
<td>Date/time handling</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="timer.html">timer</a></td>
|
||||
<td>Timer</td>
|
||||
<td>Timers and delays</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="io.html">io</a></td>
|
||||
<td>File, Directory, Stdin, Stdout</td>
|
||||
<td>File I/O</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="pathlib.html">pathlib</a></td>
|
||||
<td>Path, PurePath</td>
|
||||
<td>Filesystem paths</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="scheduler.html">scheduler</a></td>
|
||||
<td>Scheduler</td>
|
||||
<td>Async scheduling</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="math.html">math</a></td>
|
||||
<td>Math</td>
|
||||
<td>Math functions</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="uuid.html">uuid</a></td>
|
||||
<td>Uuid</td>
|
||||
<td>UUID generation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="html.html">html</a></td>
|
||||
<td>Html</td>
|
||||
<td>HTML/URL encoding</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="argparse.html">argparse</a></td>
|
||||
<td>ArgumentParser</td>
|
||||
<td>CLI arguments</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="wdantic.html">wdantic</a></td>
|
||||
<td>Validator, Field, Schema, ValidationResult</td>
|
||||
<td>Data validation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="dataset.html">dataset</a></td>
|
||||
<td>Dataset, Table</td>
|
||||
<td>Simple ORM</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="markdown.html">markdown</a></td>
|
||||
<td>Markdown</td>
|
||||
<td>Markdown to HTML</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="web.html">web</a></td>
|
||||
<td>Application, Router, Request, Response, View, Session, Client</td>
|
||||
<td>Web framework</td>
|
||||
</tr>
|
||||
</table>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="../language/modules.html" class="prev">Modules</a>
|
||||
<a href="http.html" class="next">http</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
286
manual/api/io.html
Normal file
286
manual/api/io.html
Normal file
@ -0,0 +1,286 @@
|
||||
<!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>io - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html" class="active">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>io</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>io</h1>
|
||||
|
||||
<p>The <code>io</code> module provides file and directory operations, as well as stdin/stdout handling.</p>
|
||||
|
||||
<pre><code>import "io" for File, Directory, Stdin, Stdout</code></pre>
|
||||
|
||||
<h2>File Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>File</h3>
|
||||
<p>File operations</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">File.read</span>(<span class="param">path</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Reads the entire contents of a file.</p>
|
||||
<pre><code>var content = File.read("config.txt")
|
||||
System.print(content)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">File.write</span>(<span class="param">path</span>, <span class="param">content</span>)
|
||||
</div>
|
||||
<p>Writes content to a file (creates or overwrites).</p>
|
||||
<pre><code>File.write("output.txt", "Hello, World!")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">File.exists</span>(<span class="param">path</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns true if the file exists.</p>
|
||||
<pre><code>if (File.exists("config.txt")) {
|
||||
System.print("Config found")
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">File.delete</span>(<span class="param">path</span>)
|
||||
</div>
|
||||
<p>Deletes a file.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">File.size</span>(<span class="param">path</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the size of a file in bytes.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">File.copy</span>(<span class="param">source</span>, <span class="param">dest</span>)
|
||||
</div>
|
||||
<p>Copies a file from source to destination.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">File.rename</span>(<span class="param">oldPath</span>, <span class="param">newPath</span>)
|
||||
</div>
|
||||
<p>Renames or moves a file.</p>
|
||||
|
||||
<h2>Directory Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Directory</h3>
|
||||
<p>Directory operations</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Directory.list</span>(<span class="param">path</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Lists the contents of a directory.</p>
|
||||
<pre><code>var files = Directory.list(".")
|
||||
for (file in files) {
|
||||
System.print(file)
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Directory.exists</span>(<span class="param">path</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns true if the directory exists.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Directory.create</span>(<span class="param">path</span>)
|
||||
</div>
|
||||
<p>Creates a directory.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Directory.delete</span>(<span class="param">path</span>)
|
||||
</div>
|
||||
<p>Deletes an empty directory.</p>
|
||||
|
||||
<h2>Stdin Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Stdin</h3>
|
||||
<p>Standard input</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Stdin.readLine</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Reads a line from standard input.</p>
|
||||
<pre><code>System.write("Enter your name: ")
|
||||
var name = Stdin.readLine()
|
||||
System.print("Hello, %(name)!")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Stdin.read</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Reads all available data from stdin.</p>
|
||||
|
||||
<h2>Stdout Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Stdout</h3>
|
||||
<p>Standard output</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Stdout.flush</span>()
|
||||
</div>
|
||||
<p>Flushes the stdout buffer.</p>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Reading and Writing Files</h3>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var content = File.read("input.txt")
|
||||
var processed = content.replace("old", "new")
|
||||
File.write("output.txt", processed)</code></pre>
|
||||
|
||||
<h3>Working with JSON Files</h3>
|
||||
<pre><code>import "io" for File
|
||||
import "json" for Json
|
||||
|
||||
var config = Json.parse(File.read("config.json"))
|
||||
config["updated"] = true
|
||||
File.write("config.json", Json.stringify(config, 2))</code></pre>
|
||||
|
||||
<h3>Processing Directory Contents</h3>
|
||||
<pre><code>import "io" for File, Directory
|
||||
|
||||
var files = Directory.list("./data")
|
||||
for (file in files) {
|
||||
if (file.endsWith(".txt")) {
|
||||
var path = "./data/%(file)"
|
||||
var size = File.size(path)
|
||||
System.print("%(file): %(size) bytes")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Interactive Input</h3>
|
||||
<pre><code>import "io" for Stdin
|
||||
|
||||
System.write("Username: ")
|
||||
var username = Stdin.readLine()
|
||||
|
||||
System.write("Age: ")
|
||||
var age = Num.fromString(Stdin.readLine())
|
||||
|
||||
System.print("Hello %(username), you are %(age) years old")</code></pre>
|
||||
|
||||
<h3>File Backup</h3>
|
||||
<pre><code>import "io" for File
|
||||
import "datetime" for DateTime
|
||||
|
||||
var backup = Fn.new { |path|
|
||||
if (!File.exists(path)) return
|
||||
|
||||
var timestamp = DateTime.now().format("\%Y\%m\%d_\%H\%M\%S")
|
||||
var backupPath = "%(path).%(timestamp).bak"
|
||||
File.copy(path, backupPath)
|
||||
System.print("Backed up to %(backupPath)")
|
||||
}
|
||||
|
||||
backup.call("important.txt")</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>File operations are synchronous but use libuv internally for async I/O. The fiber suspends during I/O operations.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="timer.html" class="prev">timer</a>
|
||||
<a href="pathlib.html" class="next">pathlib</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1094
manual/api/jinja.html
Normal file
1094
manual/api/jinja.html
Normal file
File diff suppressed because it is too large
Load Diff
258
manual/api/json.html
Normal file
258
manual/api/json.html
Normal file
@ -0,0 +1,258 @@
|
||||
<!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>json - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html" class="active">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>json</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>json</h1>
|
||||
|
||||
<p>The <code>json</code> module provides JSON parsing and stringification. It uses the cJSON library for parsing and implements stringify in Wren.</p>
|
||||
|
||||
<pre><code>import "json" for Json</code></pre>
|
||||
|
||||
<h2>Json Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Json</h3>
|
||||
<p>JSON parsing and stringification</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Json.parse</span>(<span class="param">string</span>) → <span class="type">Map|List|String|Num|Bool|null</span>
|
||||
</div>
|
||||
<p>Parses a JSON string and returns the corresponding Wren value.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - JSON string to parse</li>
|
||||
<li><span class="returns">Returns:</span> Parsed value (Map, List, String, Num, Bool, or null)</li>
|
||||
</ul>
|
||||
<pre><code>var data = Json.parse('{"name": "Alice", "age": 30}')
|
||||
System.print(data["name"]) // Alice
|
||||
System.print(data["age"]) // 30
|
||||
|
||||
var list = Json.parse('[1, 2, 3]')
|
||||
System.print(list[0]) // 1</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Json.stringify</span>(<span class="param">value</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts a Wren value to a JSON string (compact, no whitespace).</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">value</span> <span class="param-type">(any)</span> - Value to stringify</li>
|
||||
<li><span class="returns">Returns:</span> JSON string</li>
|
||||
</ul>
|
||||
<pre><code>var json = Json.stringify({"name": "Alice", "age": 30})
|
||||
System.print(json) // {"name":"Alice","age":30}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Json.stringify</span>(<span class="param">value</span>, <span class="param">indent</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts a Wren value to a formatted JSON string with indentation.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">value</span> <span class="param-type">(any)</span> - Value to stringify</li>
|
||||
<li><span class="param-name">indent</span> <span class="param-type">(Num|String)</span> - Number of spaces or indent string</li>
|
||||
<li><span class="returns">Returns:</span> Formatted JSON string</li>
|
||||
</ul>
|
||||
<pre><code>var json = Json.stringify({"name": "Alice"}, 2)
|
||||
System.print(json)
|
||||
// {
|
||||
// "name": "Alice"
|
||||
// }
|
||||
|
||||
var json2 = Json.stringify({"name": "Bob"}, "\t")
|
||||
// Uses tab for indentation</code></pre>
|
||||
|
||||
<h2>Type Mapping</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>JSON Type</th>
|
||||
<th>Wren Type</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>object</td>
|
||||
<td>Map</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>array</td>
|
||||
<td>List</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>String</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>number</td>
|
||||
<td>Num</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>true/false</td>
|
||||
<td>Bool</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>null</td>
|
||||
<td>null</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Parsing JSON</h3>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var jsonString = '{"users": [{"name": "Alice"}, {"name": "Bob"}]}'
|
||||
var data = Json.parse(jsonString)
|
||||
|
||||
for (user in data["users"]) {
|
||||
System.print("User: %(user["name"])")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Building and Stringifying</h3>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = {
|
||||
"name": "Product",
|
||||
"price": 29.99,
|
||||
"tags": ["electronics", "sale"],
|
||||
"inStock": true,
|
||||
"metadata": null
|
||||
}
|
||||
|
||||
System.print(Json.stringify(data, 2))</code></pre>
|
||||
|
||||
<h3>Nested Structures</h3>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var config = {
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": 8080
|
||||
},
|
||||
"database": {
|
||||
"url": "sqlite://data.db"
|
||||
}
|
||||
}
|
||||
|
||||
var json = Json.stringify(config)
|
||||
System.print(json)
|
||||
|
||||
var parsed = Json.parse(json)
|
||||
System.print(parsed["server"]["port"]) // 8080</code></pre>
|
||||
|
||||
<h3>Special Values</h3>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var special = {
|
||||
"infinity": 1/0,
|
||||
"nan": 0/0
|
||||
}
|
||||
|
||||
System.print(Json.stringify(special))
|
||||
// {"infinity":null,"nan":null}
|
||||
// Infinity and NaN are converted to null</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Map keys are converted to strings in JSON output. Non-string keys will have their <code>toString</code> method called.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Invalid JSON strings will cause a runtime error. Use <code>Fiber.try()</code> to catch parsing errors.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="dns.html" class="prev">dns</a>
|
||||
<a href="base64.html" class="next">base64</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
433
manual/api/markdown.html
Normal file
433
manual/api/markdown.html
Normal file
@ -0,0 +1,433 @@
|
||||
<!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>markdown - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html" class="active">markdown</a></li>
|
||||
<li><a href="web.html">web</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>markdown</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>markdown</h1>
|
||||
|
||||
<p>The <code>markdown</code> module provides bidirectional conversion between Markdown and HTML. It supports common Markdown syntax including headings, emphasis, code blocks, lists, links, and images.</p>
|
||||
|
||||
<pre><code>import "markdown" for Markdown</code></pre>
|
||||
|
||||
<h2>Markdown Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Markdown</h3>
|
||||
<p>Markdown to HTML converter</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Markdown.toHtml</span>(<span class="param">text</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts Markdown text to HTML.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Markdown text</li>
|
||||
<li><span class="returns">Returns:</span> HTML string</li>
|
||||
</ul>
|
||||
<pre><code>var html = Markdown.toHtml("# Hello World")
|
||||
System.print(html) // <h1>Hello World</h1></code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Markdown.toHtml</span>(<span class="param">text</span>, <span class="param">options</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts Markdown text to HTML with options.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Markdown text</li>
|
||||
<li><span class="param-name">options</span> <span class="param-type">(Map)</span> - Conversion options</li>
|
||||
<li><span class="returns">Returns:</span> HTML string</li>
|
||||
</ul>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Markdown.fromHtml</span>(<span class="param">html</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts HTML to Markdown text.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">html</span> <span class="param-type">(String)</span> - HTML string</li>
|
||||
<li><span class="returns">Returns:</span> Markdown text</li>
|
||||
</ul>
|
||||
<pre><code>var md = Markdown.fromHtml("<h1>Hello World</h1>")
|
||||
System.print(md) // # Hello World</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Markdown.fromHtml</span>(<span class="param">html</span>, <span class="param">options</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts HTML to Markdown text with options.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">html</span> <span class="param-type">(String)</span> - HTML string</li>
|
||||
<li><span class="param-name">options</span> <span class="param-type">(Map)</span> - Conversion options</li>
|
||||
<li><span class="returns">Returns:</span> Markdown text</li>
|
||||
</ul>
|
||||
|
||||
<h2>Options</h2>
|
||||
|
||||
<h3>toHtml Options</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Type</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>safeMode</code></td>
|
||||
<td>Bool</td>
|
||||
<td><code>false</code></td>
|
||||
<td>Escape HTML in input to prevent XSS</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>fromHtml Options</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Type</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>stripUnknown</code></td>
|
||||
<td>Bool</td>
|
||||
<td><code>true</code></td>
|
||||
<td>Strip unknown HTML tags, keeping their content</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Supported Syntax</h2>
|
||||
|
||||
<h3>Headings</h3>
|
||||
<pre><code># Heading 1
|
||||
## Heading 2
|
||||
### Heading 3
|
||||
#### Heading 4
|
||||
##### Heading 5
|
||||
###### Heading 6</code></pre>
|
||||
<p>Converts to <code><h1></code> through <code><h6></code> tags.</p>
|
||||
|
||||
<h3>Emphasis</h3>
|
||||
<pre><code>*italic* or _italic_
|
||||
**bold** or __bold__
|
||||
~~strikethrough~~</code></pre>
|
||||
<p>Converts to <code><em></code>, <code><strong></code>, and <code><del></code> tags.</p>
|
||||
|
||||
<h3>Code</h3>
|
||||
<pre><code>Inline `code` here
|
||||
|
||||
```
|
||||
Code block
|
||||
Multiple lines
|
||||
```</code></pre>
|
||||
<p>Inline code uses <code><code></code>, blocks use <code><pre><code></code>.</p>
|
||||
|
||||
<h3>Lists</h3>
|
||||
<pre><code>Unordered:
|
||||
- Item 1
|
||||
- Item 2
|
||||
* Also works
|
||||
+ And this
|
||||
|
||||
Ordered:
|
||||
1. First
|
||||
2. Second
|
||||
3. Third</code></pre>
|
||||
<p>Creates <code><ul></code> and <code><ol></code> with <code><li></code> items.</p>
|
||||
|
||||
<h3>Links and Images</h3>
|
||||
<pre><code>[Link text](https://example.com)
|
||||
</code></pre>
|
||||
<p>Creates <code><a href="..."></code> and <code><img src="..." alt="..."></code>.</p>
|
||||
|
||||
<h3>Blockquotes</h3>
|
||||
<pre><code>> This is a quote
|
||||
> Multiple lines</code></pre>
|
||||
<p>Creates <code><blockquote></code> with <code><p></code> content.</p>
|
||||
|
||||
<h3>Horizontal Rule</h3>
|
||||
<pre><code>---
|
||||
***
|
||||
___</code></pre>
|
||||
<p>Creates <code><hr></code> tag.</p>
|
||||
|
||||
<h3>Paragraphs</h3>
|
||||
<p>Text separated by blank lines becomes <code><p></code> elements.</p>
|
||||
|
||||
<h2>HTML to Markdown Conversions</h2>
|
||||
|
||||
<p>The <code>fromHtml</code> method converts the following HTML elements to Markdown:</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>HTML</th>
|
||||
<th>Markdown</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><h1></code> to <code><h6></code></td>
|
||||
<td><code>#</code> to <code>######</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><strong></code>, <code><b></code></td>
|
||||
<td><code>**text**</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><em></code>, <code><i></code></td>
|
||||
<td><code>*text*</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><code></code></td>
|
||||
<td><code>`text`</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><pre><code></code></td>
|
||||
<td>Fenced code block</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><a href="url"></code></td>
|
||||
<td><code>[text](url)</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><img src="url" alt=""></code></td>
|
||||
<td><code></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><ul><li></code></td>
|
||||
<td><code>- item</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><ol><li></code></td>
|
||||
<td><code>1. item</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><blockquote></code></td>
|
||||
<td><code>> text</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><hr></code></td>
|
||||
<td><code>---</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><del></code>, <code><s></code></td>
|
||||
<td><code>~~text~~</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>Container tags (<code><div></code>, <code><span></code>, <code><section></code>, etc.) are stripped but their content is preserved. Script and style tags are removed entirely.</p>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Basic Conversion</h3>
|
||||
<pre><code>import "markdown" for Markdown
|
||||
|
||||
var md = "
|
||||
# Welcome
|
||||
|
||||
This is a **Markdown** document with:
|
||||
|
||||
- Lists
|
||||
- *Emphasis*
|
||||
- `Code`
|
||||
|
||||
Visit [Wren](https://wren.io) for more.
|
||||
"
|
||||
|
||||
var html = Markdown.toHtml(md)
|
||||
System.print(html)</code></pre>
|
||||
|
||||
<h3>Safe Mode for User Content</h3>
|
||||
<pre><code>import "markdown" for Markdown
|
||||
|
||||
var userInput = "# Title\n<script>alert('xss')</script>\nContent here."
|
||||
|
||||
var safeHtml = Markdown.toHtml(userInput, {"safeMode": true})
|
||||
System.print(safeHtml)</code></pre>
|
||||
|
||||
<h3>Rendering a Blog Post</h3>
|
||||
<pre><code>import "markdown" for Markdown
|
||||
import "io" for File
|
||||
|
||||
var postContent = File.read("post.md")
|
||||
var html = Markdown.toHtml(postContent)
|
||||
|
||||
var page = "<html>
|
||||
<head><title>Blog</title></head>
|
||||
<body>
|
||||
<article>
|
||||
%(html)
|
||||
</article>
|
||||
</body>
|
||||
</html>"
|
||||
|
||||
File.write("post.html", page)</code></pre>
|
||||
|
||||
<h3>Code Blocks with Language Hints</h3>
|
||||
<pre><code>import "markdown" for Markdown
|
||||
|
||||
var md = "
|
||||
```wren
|
||||
System.print(\"Hello, World!\")
|
||||
```
|
||||
"
|
||||
|
||||
var html = Markdown.toHtml(md)
|
||||
System.print(html)
|
||||
// <pre><code>System.print("Hello, World!")</code></pre></code></pre>
|
||||
|
||||
<h3>Combining with Templates</h3>
|
||||
<pre><code>import "markdown" for Markdown
|
||||
import "jinja" for Environment, DictLoader
|
||||
|
||||
var env = Environment.new(DictLoader.new({
|
||||
"base": "<html><body>{{ content }}</body></html>"
|
||||
}))
|
||||
|
||||
var md = "# Hello\n\nThis is **Markdown**."
|
||||
var content = Markdown.toHtml(md)
|
||||
|
||||
var html = env.getTemplate("base").render({"content": content})
|
||||
System.print(html)</code></pre>
|
||||
|
||||
<h3>Converting HTML to Markdown</h3>
|
||||
<pre><code>import "markdown" for Markdown
|
||||
|
||||
var html = "
|
||||
<html>
|
||||
<body>
|
||||
<h1>Article Title</h1>
|
||||
<p>This is <strong>important</strong> content.</p>
|
||||
<ul>
|
||||
<li>First item</li>
|
||||
<li>Second item</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
|
||||
var md = Markdown.fromHtml(html)
|
||||
System.print(md)
|
||||
// # Article Title
|
||||
//
|
||||
// This is **important** content.
|
||||
//
|
||||
// - First item
|
||||
// - Second item</code></pre>
|
||||
|
||||
<h3>Cleaning HTML for Plain Text</h3>
|
||||
<pre><code>import "markdown" for Markdown
|
||||
|
||||
var webContent = "<div><script>alert('xss')</script><p>Safe <b>content</b></p></div>"
|
||||
var clean = Markdown.fromHtml(webContent)
|
||||
System.print(clean) // Safe **content**</code></pre>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>When rendering user-provided Markdown, always use <code>safeMode: true</code> to prevent cross-site scripting (XSS) attacks. Safe mode escapes HTML entities in the input text.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Language identifiers after code fences (e.g., <code>```wren</code>) are currently ignored. The code is rendered without syntax highlighting. Consider using a client-side syntax highlighter like Prism.js or highlight.js for the resulting HTML.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="dataset.html" class="prev">dataset</a>
|
||||
<a href="web.html" class="next">web</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
292
manual/api/math.html
Normal file
292
manual/api/math.html
Normal file
@ -0,0 +1,292 @@
|
||||
<!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>math - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html" class="active">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>math</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>math</h1>
|
||||
|
||||
<p>The <code>math</code> module provides mathematical functions and constants.</p>
|
||||
|
||||
<pre><code>import "math" for Math</code></pre>
|
||||
|
||||
<h2>Math Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Math</h3>
|
||||
<p>Mathematical functions and constants</p>
|
||||
</div>
|
||||
|
||||
<h3>Constants</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Constant</th>
|
||||
<th>Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Math.pi</code></td>
|
||||
<td>3.14159...</td>
|
||||
<td>Pi</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Math.e</code></td>
|
||||
<td>2.71828...</td>
|
||||
<td>Euler's number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Math.tau</code></td>
|
||||
<td>6.28318...</td>
|
||||
<td>Tau (2 * Pi)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Basic Functions</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.abs</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the absolute value.</p>
|
||||
<pre><code>Math.abs(-5) // 5
|
||||
Math.abs(3.14) // 3.14</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.min</span>(<span class="param">a</span>, <span class="param">b</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the smaller of two values.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.max</span>(<span class="param">a</span>, <span class="param">b</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the larger of two values.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.clamp</span>(<span class="param">value</span>, <span class="param">min</span>, <span class="param">max</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Clamps a value to a range.</p>
|
||||
<pre><code>Math.clamp(15, 0, 10) // 10
|
||||
Math.clamp(5, 0, 10) // 5
|
||||
Math.clamp(-5, 0, 10) // 0</code></pre>
|
||||
|
||||
<h3>Rounding</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.floor</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Rounds down to the nearest integer.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.ceil</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Rounds up to the nearest integer.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.round</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Rounds to the nearest integer.</p>
|
||||
|
||||
<pre><code>Math.floor(3.7) // 3
|
||||
Math.ceil(3.2) // 4
|
||||
Math.round(3.5) // 4</code></pre>
|
||||
|
||||
<h3>Powers and Roots</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.sqrt</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the square root.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.pow</span>(<span class="param">base</span>, <span class="param">exp</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns base raised to the power of exp.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.exp</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns e raised to the power of x.</p>
|
||||
|
||||
<pre><code>Math.sqrt(16) // 4
|
||||
Math.pow(2, 10) // 1024
|
||||
Math.exp(1) // 2.71828...</code></pre>
|
||||
|
||||
<h3>Logarithms</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.log</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the natural logarithm (base e).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.log10</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the base-10 logarithm.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.log2</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the base-2 logarithm.</p>
|
||||
|
||||
<h3>Trigonometry</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.sin</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the sine (x in radians).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.cos</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the cosine (x in radians).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.tan</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the tangent (x in radians).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.asin</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the arcsine in radians.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.acos</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the arccosine in radians.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.atan</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the arctangent in radians.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Math.atan2</span>(<span class="param">y</span>, <span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the angle in radians between the positive x-axis and the point (x, y).</p>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Distance Calculation</h3>
|
||||
<pre><code>import "math" for Math
|
||||
|
||||
var distance = Fn.new { |x1, y1, x2, y2|
|
||||
var dx = x2 - x1
|
||||
var dy = y2 - y1
|
||||
return Math.sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
|
||||
System.print(distance.call(0, 0, 3, 4)) // 5</code></pre>
|
||||
|
||||
<h3>Degrees to Radians</h3>
|
||||
<pre><code>import "math" for Math
|
||||
|
||||
var toRadians = Fn.new { |degrees| degrees * Math.pi / 180 }
|
||||
var toDegrees = Fn.new { |radians| radians * 180 / Math.pi }
|
||||
|
||||
System.print(Math.sin(toRadians.call(90))) // 1</code></pre>
|
||||
|
||||
<h3>Circle Area</h3>
|
||||
<pre><code>import "math" for Math
|
||||
|
||||
var circleArea = Fn.new { |radius|
|
||||
return Math.pi * radius * radius
|
||||
}
|
||||
|
||||
System.print(circleArea.call(5)) // 78.539...</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="scheduler.html" class="prev">scheduler</a>
|
||||
<a href="../tutorials/index.html" class="next">Tutorials</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
332
manual/api/net.html
Normal file
332
manual/api/net.html
Normal file
@ -0,0 +1,332 @@
|
||||
<!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>net - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html" class="active">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>net</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>net</h1>
|
||||
|
||||
<p>The <code>net</code> module provides low-level TCP socket and server functionality for network programming. All operations are asynchronous using libuv.</p>
|
||||
|
||||
<pre><code>import "net" for Socket, Server</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#socket-class">Socket Class</a></li>
|
||||
<li><a href="#server-class">Server Class</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="socket-class">Socket Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Socket</h3>
|
||||
<p>TCP socket for client connections</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Socket.connect</span>(<span class="param">host</span>, <span class="param">port</span>) → <span class="type">Socket</span>
|
||||
</div>
|
||||
<p>Establishes a TCP connection to a remote server.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - Hostname or IP address to connect to</li>
|
||||
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number</li>
|
||||
<li><span class="returns">Returns:</span> Connected Socket instance</li>
|
||||
</ul>
|
||||
<pre><code>var socket = Socket.connect("127.0.0.1", 8080)</code></pre>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">write</span>(<span class="param">data</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Writes data to the socket. Blocks until the data is sent.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">data</span> <span class="param-type">(String)</span> - Data to send</li>
|
||||
<li><span class="returns">Returns:</span> Number of bytes written</li>
|
||||
</ul>
|
||||
<pre><code>socket.write("Hello, server!")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">read</span>() → <span class="type">String|null</span>
|
||||
</div>
|
||||
<p>Reads data from the socket. Blocks until data is available. Returns null when the connection is closed by the remote end.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="returns">Returns:</span> Data received as a string, or null if connection closed</li>
|
||||
</ul>
|
||||
<pre><code>var data = socket.read()
|
||||
if (data != null) {
|
||||
System.print("Received: %(data)")
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">close</span>()
|
||||
</div>
|
||||
<p>Closes the socket connection.</p>
|
||||
<pre><code>socket.close()</code></pre>
|
||||
|
||||
<h2 id="server-class">Server Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Server</h3>
|
||||
<p>TCP server for accepting client connections</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Server.bind</span>(<span class="param">host</span>, <span class="param">port</span>) → <span class="type">Server</span>
|
||||
</div>
|
||||
<p>Creates a TCP server listening on the specified host and port.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - Host to bind to (e.g., "0.0.0.0" for all interfaces, "127.0.0.1" for localhost only)</li>
|
||||
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number to listen on</li>
|
||||
<li><span class="returns">Returns:</span> Bound Server instance</li>
|
||||
</ul>
|
||||
<pre><code>var server = Server.bind("0.0.0.0", 8080)</code></pre>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">accept</span>() → <span class="type">Socket|null</span>
|
||||
</div>
|
||||
<p>Accepts an incoming connection. Blocks until a client connects. Returns a Socket instance for the connected client.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="returns">Returns:</span> Socket for the accepted connection</li>
|
||||
</ul>
|
||||
<pre><code>var client = server.accept()
|
||||
System.print("Client connected!")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">close</span>()
|
||||
</div>
|
||||
<p>Stops the server and closes the listening socket.</p>
|
||||
<pre><code>server.close()</code></pre>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Simple TCP Client</h3>
|
||||
<pre><code>import "net" for Socket
|
||||
|
||||
var socket = Socket.connect("example.com", 80)
|
||||
|
||||
socket.write("GET / HTTP/1.1\r\n")
|
||||
socket.write("Host: example.com\r\n")
|
||||
socket.write("Connection: close\r\n")
|
||||
socket.write("\r\n")
|
||||
|
||||
var response = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null) break
|
||||
response = response + chunk
|
||||
}
|
||||
|
||||
socket.close()
|
||||
System.print(response)</code></pre>
|
||||
|
||||
<h3>Echo Server</h3>
|
||||
<pre><code>import "net" for Server
|
||||
|
||||
var server = Server.bind("0.0.0.0", 8080)
|
||||
System.print("Echo server listening on port 8080")
|
||||
|
||||
while (true) {
|
||||
var client = server.accept()
|
||||
System.print("Client connected")
|
||||
|
||||
while (true) {
|
||||
var data = client.read()
|
||||
if (data == null) {
|
||||
System.print("Client disconnected")
|
||||
break
|
||||
}
|
||||
System.print("Received: %(data)")
|
||||
client.write(data)
|
||||
}
|
||||
|
||||
client.close()
|
||||
}</code></pre>
|
||||
|
||||
<h3>Line-Based Protocol Server</h3>
|
||||
<pre><code>import "net" for Server
|
||||
|
||||
var server = Server.bind("127.0.0.1", 9000)
|
||||
System.print("Chat server on port 9000")
|
||||
|
||||
while (true) {
|
||||
var client = server.accept()
|
||||
client.write("Welcome! Type messages and press Enter.\n")
|
||||
|
||||
var buffer = ""
|
||||
while (true) {
|
||||
var data = client.read()
|
||||
if (data == null) break
|
||||
|
||||
buffer = buffer + data
|
||||
while (buffer.contains("\n")) {
|
||||
var lineEnd = buffer.indexOf("\n")
|
||||
var line = buffer[0...lineEnd]
|
||||
buffer = buffer[lineEnd + 1..-1]
|
||||
|
||||
if (line == "quit") {
|
||||
client.write("Goodbye!\n")
|
||||
client.close()
|
||||
break
|
||||
}
|
||||
|
||||
client.write("You said: %(line)\n")
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Connecting with DNS Resolution</h3>
|
||||
<pre><code>import "net" for Socket
|
||||
import "dns" for Dns
|
||||
|
||||
var hostname = "httpbin.org"
|
||||
var ip = Dns.lookup(hostname)
|
||||
System.print("Resolved %(hostname) to %(ip)")
|
||||
|
||||
var socket = Socket.connect(ip, 80)
|
||||
socket.write("GET /ip HTTP/1.1\r\nHost: %(hostname)\r\nConnection: close\r\n\r\n")
|
||||
|
||||
var response = ""
|
||||
while (true) {
|
||||
var data = socket.read()
|
||||
if (data == null) break
|
||||
response = response + data
|
||||
}
|
||||
|
||||
socket.close()
|
||||
System.print(response)</code></pre>
|
||||
|
||||
<h3>Simple Request-Response Client</h3>
|
||||
<pre><code>import "net" for Socket
|
||||
|
||||
var socket = Socket.connect("127.0.0.1", 8080)
|
||||
|
||||
socket.write("PING\n")
|
||||
var response = socket.read()
|
||||
System.print("Server responded: %(response)")
|
||||
|
||||
socket.write("ECHO Hello World\n")
|
||||
response = socket.read()
|
||||
System.print("Server responded: %(response)")
|
||||
|
||||
socket.close()</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>The Socket class uses TCP, which is a stream-based protocol. Data may arrive in chunks that do not correspond to message boundaries. For protocols that require message framing, implement appropriate buffering and parsing logic.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>For secure connections, use the <code>tls</code> module's TlsSocket class instead of Socket. For HTTP/HTTPS requests, the higher-level <code>http</code> module handles protocol details automatically.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Both host and port arguments are validated. Providing a non-string host or non-number port will abort the fiber with an error message.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="tls.html" class="prev">tls</a>
|
||||
<a href="dns.html" class="next">dns</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
493
manual/api/number.html
Normal file
493
manual/api/number.html
Normal file
@ -0,0 +1,493 @@
|
||||
<!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>Num - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html" class="active">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>Num</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Num</h1>
|
||||
|
||||
<p>The <code>Num</code> class is the core numeric type in Wren. All numbers are double-precision floating point. The class provides arithmetic, trigonometric, bitwise, rounding, query, conversion, and formatting methods.</p>
|
||||
|
||||
<h2>Static Constants</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.pi</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>The ratio of a circle's circumference to its diameter (3.14159...).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.tau</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Two times pi (6.28318...).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.e</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Euler's number (2.71828...).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.infinity</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Positive infinity.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.nan</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Not-a-number value.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.largest</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>The largest representable double value.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.smallest</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>The smallest positive representable double value.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.maxSafeInteger</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>The largest integer representable without loss of precision (9007199254740991).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.minSafeInteger</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>The smallest integer representable without loss of precision (-9007199254740991).</p>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Num.fromString</span>(<span class="param">string</span>) → <span class="type">Num|null</span>
|
||||
</div>
|
||||
<p>Parses a string as a number. Returns <code>null</code> if the string is not a valid number.</p>
|
||||
<pre><code>Num.fromString("42") // 42
|
||||
Num.fromString("3.14") // 3.14
|
||||
Num.fromString("nope") // null</code></pre>
|
||||
|
||||
<h2>Query Methods</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isInteger</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is an integer (has no fractional part).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isNan</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is NaN.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isInfinity</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is positive or negative infinity.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isFinite</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is finite (not infinity and not NaN).</p>
|
||||
<pre><code>42.isFinite // true
|
||||
(1/0).isFinite // false
|
||||
(0/0).isFinite // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isZero</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number equals zero.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isPositive</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is strictly greater than zero.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isNegative</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is strictly less than zero.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isEven</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is an even integer.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isOdd</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is an odd integer.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isBetween</span>(<span class="param">min</span>, <span class="param">max</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the number is within the inclusive range [min, max].</p>
|
||||
<pre><code>5.isBetween(1, 10) // true
|
||||
0.isBetween(1, 10) // false
|
||||
10.isBetween(1, 10) // true</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">sign</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns 1 for positive, -1 for negative, or 0 for zero.</p>
|
||||
|
||||
<h2>Arithmetic</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">abs</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Absolute value.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">min</span>(<span class="param">other</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the smaller of this number and <code>other</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">max</span>(<span class="param">other</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the larger of this number and <code>other</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">clamp</span>(<span class="param">min</span>, <span class="param">max</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Constrains this number to the range [min, max].</p>
|
||||
<pre><code>15.clamp(0, 10) // 10
|
||||
5.clamp(0, 10) // 5
|
||||
(-3).clamp(0, 10) // 0</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">pow</span>(<span class="param">exponent</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Raises this number to the given power.</p>
|
||||
|
||||
<h2>Rounding</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">ceil</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Rounds up to the nearest integer.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">floor</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Rounds down to the nearest integer.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">round</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Rounds to the nearest integer.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">truncate</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Removes the fractional part, rounding toward zero.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">fraction</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the fractional part of the number.</p>
|
||||
|
||||
<h2>Roots & Exponents</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">sqrt</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Square root.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">cbrt</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Cube root.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">exp</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns e raised to this power.</p>
|
||||
|
||||
<h2>Logarithms</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">log</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Natural logarithm (base e).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">log2</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Base-2 logarithm.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">log10</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Base-10 logarithm.</p>
|
||||
<pre><code>100.log10 // 2
|
||||
1000.log10 // 3
|
||||
1.log10 // 0</code></pre>
|
||||
|
||||
<h2>Trigonometry</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">sin</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Sine (argument in radians).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">cos</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Cosine (argument in radians).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">tan</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Tangent (argument in radians).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">asin</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Arc sine, returns radians.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">acos</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Arc cosine, returns radians.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">atan</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Arc tangent, returns radians.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">atan</span>(<span class="param">x</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Two-argument arc tangent (atan2). Returns the angle between the positive x-axis and the point (x, this).</p>
|
||||
|
||||
<h3>Hyperbolic Functions</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">sinh</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Hyperbolic sine.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">cosh</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Hyperbolic cosine.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">tanh</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Hyperbolic tangent.</p>
|
||||
<pre><code>0.sinh // 0
|
||||
0.cosh // 1
|
||||
0.tanh // 0</code></pre>
|
||||
|
||||
<h2>Angle Conversion</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toDegrees</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Converts radians to degrees.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toRadians</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Converts degrees to radians.</p>
|
||||
<pre><code>Num.pi.toDegrees // 180
|
||||
180.toRadians // 3.14159...</code></pre>
|
||||
|
||||
<h2>Conversion</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toString</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts the number to its string representation.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toChar</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the Unicode character for this code point.</p>
|
||||
<pre><code>65.toChar // "A"
|
||||
97.toChar // "a"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toBase</span>(<span class="param">radix</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Converts the integer part to a string in the given base (2-36).</p>
|
||||
<pre><code>255.toBase(16) // "ff"
|
||||
255.toBase(2) // "11111111"
|
||||
255.toBase(8) // "377"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toHex</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Shorthand for <code>toBase(16)</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toBinary</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Shorthand for <code>toBase(2)</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toOctal</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Shorthand for <code>toBase(8)</code>.</p>
|
||||
|
||||
<h2>Formatting</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">format</span>(<span class="param">decimals</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Formats the number with a fixed number of decimal places (0-20).</p>
|
||||
<pre><code>3.14159.format(2) // "3.14"
|
||||
42.format(3) // "42.000"
|
||||
(-1.5).format(1) // "-1.5"</code></pre>
|
||||
|
||||
<h2>Integer Operations</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">gcd</span>(<span class="param">other</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Greatest common divisor using the Euclidean algorithm. Operates on absolute values.</p>
|
||||
<pre><code>12.gcd(8) // 4
|
||||
54.gcd(24) // 6
|
||||
7.gcd(13) // 1</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">lcm</span>(<span class="param">other</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Least common multiple.</p>
|
||||
<pre><code>4.lcm(6) // 12
|
||||
3.lcm(5) // 15</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">digits</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Returns a list of the individual digits of the integer's absolute value. Aborts if the number is not an integer.</p>
|
||||
<pre><code>123.digits // [1, 2, 3]
|
||||
0.digits // [0]
|
||||
(-456).digits // [4, 5, 6]</code></pre>
|
||||
|
||||
<h2>Bitwise Operations</h2>
|
||||
|
||||
<p>Bitwise operators work on 32-bit unsigned integers.</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Operator</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr><td><code>&(other)</code></td><td>Bitwise AND</td></tr>
|
||||
<tr><td><code>|(other)</code></td><td>Bitwise OR</td></tr>
|
||||
<tr><td><code>^(other)</code></td><td>Bitwise XOR</td></tr>
|
||||
<tr><td><code><<(other)</code></td><td>Left shift</td></tr>
|
||||
<tr><td><code>>>(other)</code></td><td>Right shift</td></tr>
|
||||
<tr><td><code>~</code></td><td>Bitwise NOT</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>Ranges</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">..</span>(<span class="param">to</span>) → <span class="type">Range</span>
|
||||
</div>
|
||||
<p>Creates an inclusive range from this number to <code>to</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">...</span>(<span class="param">to</span>) → <span class="type">Range</span>
|
||||
</div>
|
||||
<p>Creates an exclusive range from this number to <code>to</code> (excludes the end).</p>
|
||||
<pre><code>for (i in 1..5) System.print(i) // 1 2 3 4 5
|
||||
for (i in 1...5) System.print(i) // 1 2 3 4</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="string.html" class="prev">String</a>
|
||||
<a href="http.html" class="next">http</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
304
manual/api/os.html
Normal file
304
manual/api/os.html
Normal file
@ -0,0 +1,304 @@
|
||||
<!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>os - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html" class="active">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>os</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>os</h1>
|
||||
|
||||
<p>The <code>os</code> module provides information about the operating system, platform, and current process.</p>
|
||||
|
||||
<pre><code>import "os" for Platform, Process</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#platform-class">Platform Class</a></li>
|
||||
<li><a href="#process-class">Process Class</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="platform-class">Platform Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Platform</h3>
|
||||
<p>Operating system and platform information</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Platform.name</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The name of the operating system (e.g., "Linux", "macOS", "Windows", "FreeBSD").</p>
|
||||
<pre><code>System.print(Platform.name) // Linux</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Platform.isPosix</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>True if running on a POSIX-compatible system (Linux, macOS, BSD, etc.).</p>
|
||||
<pre><code>if (Platform.isPosix) {
|
||||
System.print("Running on a POSIX system")
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Platform.isWindows</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>True if running on Windows.</p>
|
||||
<pre><code>if (Platform.isWindows) {
|
||||
System.print("Running on Windows")
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Platform.homePath</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The current user's home directory path.</p>
|
||||
<pre><code>System.print(Platform.homePath) // /home/alice (Linux) or C:\Users\alice (Windows)</code></pre>
|
||||
|
||||
<h2 id="process-class">Process Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Process</h3>
|
||||
<p>Current process information and arguments</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Process.arguments</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Command-line arguments passed to the script (excludes the interpreter and script path).</p>
|
||||
<pre><code>// Running: wren_cli script.wren arg1 arg2
|
||||
System.print(Process.arguments) // [arg1, arg2]</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Process.allArguments</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>All command-line arguments including the interpreter path and script path.</p>
|
||||
<pre><code>// Running: wren_cli script.wren arg1 arg2
|
||||
System.print(Process.allArguments) // [wren_cli, script.wren, arg1, arg2]</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Process.cwd</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The current working directory.</p>
|
||||
<pre><code>System.print(Process.cwd) // /home/alice/projects</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Process.pid</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>The process ID of the current process.</p>
|
||||
<pre><code>System.print(Process.pid) // 12345</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Process.ppid</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>The parent process ID.</p>
|
||||
<pre><code>System.print(Process.ppid) // 12300</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Process.version</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The version string of the Wren-CLI runtime.</p>
|
||||
<pre><code>System.print(Process.version) // 0.4.0</code></pre>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Platform-Specific Behavior</h3>
|
||||
<pre><code>import "os" for Platform
|
||||
|
||||
var configPath
|
||||
if (Platform.isWindows) {
|
||||
configPath = Platform.homePath + "\\AppData\\Local\\myapp\\config.json"
|
||||
} else {
|
||||
configPath = Platform.homePath + "/.config/myapp/config.json"
|
||||
}
|
||||
|
||||
System.print("Config path: %(configPath)")</code></pre>
|
||||
|
||||
<h3>Processing Command-Line Arguments</h3>
|
||||
<pre><code>import "os" for Process
|
||||
|
||||
var args = Process.arguments
|
||||
|
||||
if (args.count == 0) {
|
||||
System.print("Usage: script.wren <command> [options]")
|
||||
} else {
|
||||
var command = args[0]
|
||||
if (command == "help") {
|
||||
System.print("Available commands: help, version, run")
|
||||
} else if (command == "version") {
|
||||
System.print("Version 1.0.0")
|
||||
} else if (command == "run") {
|
||||
if (args.count > 1) {
|
||||
System.print("Running: %(args[1])")
|
||||
} else {
|
||||
System.print("Error: run requires a file argument")
|
||||
}
|
||||
} else {
|
||||
System.print("Unknown command: %(command)")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Script Information</h3>
|
||||
<pre><code>import "os" for Platform, Process
|
||||
|
||||
System.print("=== System Information ===")
|
||||
System.print("Platform: %(Platform.name)")
|
||||
System.print("POSIX: %(Platform.isPosix)")
|
||||
System.print("Home: %(Platform.homePath)")
|
||||
System.print("")
|
||||
System.print("=== Process Information ===")
|
||||
System.print("PID: %(Process.pid)")
|
||||
System.print("Parent PID: %(Process.ppid)")
|
||||
System.print("Working Directory: %(Process.cwd)")
|
||||
System.print("Wren Version: %(Process.version)")
|
||||
System.print("")
|
||||
System.print("=== Arguments ===")
|
||||
System.print("Arguments: %(Process.arguments)")</code></pre>
|
||||
|
||||
<h3>Building File Paths</h3>
|
||||
<pre><code>import "os" for Platform, Process
|
||||
|
||||
var separator = Platform.isWindows ? "\\" : "/"
|
||||
|
||||
var dataDir = Process.cwd + separator + "data"
|
||||
var configFile = Platform.homePath + separator + ".myapprc"
|
||||
|
||||
System.print("Data directory: %(dataDir)")
|
||||
System.print("Config file: %(configFile)")</code></pre>
|
||||
|
||||
<h3>Argument Parsing with Flags</h3>
|
||||
<pre><code>import "os" for Process
|
||||
|
||||
var verbose = false
|
||||
var outputFile = "output.txt"
|
||||
var inputFiles = []
|
||||
|
||||
var i = 0
|
||||
var args = Process.arguments
|
||||
while (i < args.count) {
|
||||
var arg = args[i]
|
||||
if (arg == "-v" || arg == "--verbose") {
|
||||
verbose = true
|
||||
} else if (arg == "-o" || arg == "--output") {
|
||||
i = i + 1
|
||||
if (i < args.count) {
|
||||
outputFile = args[i]
|
||||
}
|
||||
} else if (!arg.startsWith("-")) {
|
||||
inputFiles.add(arg)
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
System.print("Verbose: %(verbose)")
|
||||
System.print("Output: %(outputFile)")
|
||||
System.print("Input files: %(inputFiles)")</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p><code>Process.arguments</code> returns only the user's arguments (after the script path), while <code>Process.allArguments</code> includes the full command line including the interpreter.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Use <code>Platform.isPosix</code> to write cross-platform code that handles path separators, shell commands, and other platform-specific differences.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="crypto.html" class="prev">crypto</a>
|
||||
<a href="env.html" class="next">env</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
659
manual/api/pathlib.html
Normal file
659
manual/api/pathlib.html
Normal file
@ -0,0 +1,659 @@
|
||||
<!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>pathlib - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html" class="active">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</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>
|
||||
</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>
|
||||
</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>pathlib</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>pathlib</h1>
|
||||
|
||||
<p>The <code>pathlib</code> module provides object-oriented filesystem path operations. Inspired by Python's <code>pathlib</code>, it offers immutable <code>Path</code> objects that combine path manipulation with filesystem I/O in a single interface.</p>
|
||||
|
||||
<pre><code>import "pathlib" for Path</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#purepath-class">PurePath Class</a></li>
|
||||
<li><a href="#path-class">Path Class</a></li>
|
||||
<li><a href="#path-properties">Path Properties</a></li>
|
||||
<li><a href="#path-manipulation">Path Manipulation</a></li>
|
||||
<li><a href="#filesystem-operations">Filesystem Operations</a></li>
|
||||
<li><a href="#directory-operations">Directory Operations</a></li>
|
||||
<li><a href="#symlink-operations">Symlink Operations</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="purepath-class">PurePath Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>PurePath</h3>
|
||||
<p>String-only path operations without filesystem access. Base class for <code>Path</code>.</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructor</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">PurePath.new</span>(<span class="param">path</span>) → <span class="type">PurePath</span>
|
||||
</div>
|
||||
<p>Creates a new PurePath from a string or another PurePath.</p>
|
||||
<pre><code>var p = PurePath.new("/home/user/file.txt")</code></pre>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">parts</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>The individual components of the path.</p>
|
||||
<pre><code>var p = Path.new("/home/user/file.txt")
|
||||
System.print(p.parts) // [/, home, user, file.txt]</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">name</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The final component of the path (filename with extension).</p>
|
||||
<pre><code>var p = Path.new("/home/user/file.tar.gz")
|
||||
System.print(p.name) // file.tar.gz</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">stem</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The filename without the last extension.</p>
|
||||
<pre><code>var p = Path.new("/home/user/file.tar.gz")
|
||||
System.print(p.stem) // file.tar</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">suffix</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The last file extension, including the leading dot.</p>
|
||||
<pre><code>var p = Path.new("/home/user/file.tar.gz")
|
||||
System.print(p.suffix) // .gz</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">suffixes</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>All file extensions as a list.</p>
|
||||
<pre><code>var p = Path.new("/home/user/file.tar.gz")
|
||||
System.print(p.suffixes) // [.tar, .gz]</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">parent</span> → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>The logical parent of the path.</p>
|
||||
<pre><code>var p = Path.new("/home/user/file.txt")
|
||||
System.print(p.parent) // /home/user</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">parents</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>A list of all ancestor paths, from immediate parent to root.</p>
|
||||
<pre><code>var p = Path.new("/home/user/docs/file.txt")
|
||||
for (ancestor in p.parents) {
|
||||
System.print(ancestor)
|
||||
}
|
||||
// /home/user/docs
|
||||
// /home/user
|
||||
// /home
|
||||
// /</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">drive</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The drive letter (Windows only). Returns empty string on POSIX.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">root</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The root of the path. Returns <code>"/"</code> for absolute paths, empty string for relative.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">anchor</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The concatenation of drive and root.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isAbsolute</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>True if the path is absolute (has a root).</p>
|
||||
<pre><code>System.print(Path.new("/etc/hosts").isAbsolute) // true
|
||||
System.print(Path.new("relative/path").isAbsolute) // false</code></pre>
|
||||
|
||||
<h2 id="path-manipulation">Path Manipulation</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">path</span> / <span class="param">other</span> → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Joins two paths using the <code>/</code> operator. If <code>other</code> is absolute, it replaces the current path.</p>
|
||||
<pre><code>var base = Path.new("/home/user")
|
||||
var full = base / "documents" / "file.txt"
|
||||
System.print(full) // /home/user/documents/file.txt</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">joinpath</span>(<span class="param">other</span>) → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Joins paths. Accepts a string, Path, or List of components.</p>
|
||||
<pre><code>var p = Path.new("/home").joinpath("user").joinpath("file.txt")
|
||||
System.print(p) // /home/user/file.txt
|
||||
|
||||
var q = Path.new("/home").joinpath(["user", "docs", "file.txt"])
|
||||
System.print(q) // /home/user/docs/file.txt</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">withName</span>(<span class="param">newName</span>) → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Returns a new path with the filename replaced.</p>
|
||||
<pre><code>var p = Path.new("/home/user/old.txt")
|
||||
System.print(p.withName("new.txt")) // /home/user/new.txt</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">withStem</span>(<span class="param">newStem</span>) → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Returns a new path with the stem replaced, keeping the extension.</p>
|
||||
<pre><code>var p = Path.new("/data/archive.tar.gz")
|
||||
System.print(p.withStem("backup")) // /data/backup.gz</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">withSuffix</span>(<span class="param">newSuffix</span>) → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Returns a new path with the extension replaced.</p>
|
||||
<pre><code>var p = Path.new("/data/file.txt")
|
||||
System.print(p.withSuffix(".md")) // /data/file.md</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">relativeTo</span>(<span class="param">base</span>) → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Returns a relative path from <code>base</code> to this path. Aborts if the path is not relative to <code>base</code>.</p>
|
||||
<pre><code>var p = Path.new("/home/user/docs/file.txt")
|
||||
System.print(p.relativeTo("/home/user")) // docs/file.txt</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">match</span>(<span class="param">pattern</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Matches the filename against a glob pattern. Supports <code>*</code> and <code>?</code> wildcards.</p>
|
||||
<pre><code>var p = Path.new("/home/user/notes.txt")
|
||||
System.print(p.match("*.txt")) // true
|
||||
System.print(p.match("*.md")) // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">asPosix</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the path string with forward slashes (converts backslashes on Windows).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">expanduser</span>() → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Expands a leading <code>~</code> to the user's home directory.</p>
|
||||
<pre><code>var p = Path.new("~/.config/app")
|
||||
System.print(p.expanduser()) // /home/alice/.config/app</code></pre>
|
||||
|
||||
<h3>Operators</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">==</span>(<span class="param">other</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Compares two paths for equality by their string representation.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">!=</span>(<span class="param">other</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Compares two paths for inequality.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toString</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the path as a string.</p>
|
||||
|
||||
<h2 id="path-class">Path Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Path</h3>
|
||||
<p>Extends PurePath with filesystem operations. This is the primary class for working with paths.</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructor</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Path.new</span>(<span class="param">path</span>) → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Creates a new Path from a string or another Path.</p>
|
||||
<pre><code>var p = Path.new("/home/user/file.txt")</code></pre>
|
||||
|
||||
<h3>Class Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Path.cwd</span> → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>The current working directory as a Path.</p>
|
||||
<pre><code>System.print(Path.cwd) // /home/user/projects</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Path.home</span> → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>The current user's home directory as a Path.</p>
|
||||
<pre><code>System.print(Path.home) // /home/alice</code></pre>
|
||||
|
||||
<h2 id="filesystem-operations">Filesystem Operations</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">exists</span>() → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns true if the path points to an existing file or directory.</p>
|
||||
<pre><code>if (Path.new("/etc/hosts").exists()) {
|
||||
System.print("File exists")
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isFile</span>() → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns true if the path points to a regular file.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isDir</span>() → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns true if the path points to a directory.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isSymlink</span>() → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns true if the path is a symbolic link.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">stat</span>() → <span class="type">Stat</span>
|
||||
</div>
|
||||
<p>Returns a Stat object with file metadata (size, mode, timestamps).</p>
|
||||
<pre><code>var s = Path.new("/etc/hosts").stat()
|
||||
System.print(s.size) // 234</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">lstat</span>() → <span class="type">Stat</span>
|
||||
</div>
|
||||
<p>Like <code>stat()</code> but does not follow symbolic links.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">readText</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Reads the entire file contents as a string.</p>
|
||||
<pre><code>var content = Path.new("config.json").readText()
|
||||
System.print(content)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">readBytes</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Reads the entire file contents as bytes.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">writeText</span>(<span class="param">content</span>)
|
||||
</div>
|
||||
<p>Writes a string to the file, creating or overwriting it.</p>
|
||||
<pre><code>Path.new("output.txt").writeText("Hello, World!")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">writeBytes</span>(<span class="param">content</span>)
|
||||
</div>
|
||||
<p>Writes bytes to the file, creating or overwriting it.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">touch</span>()
|
||||
</div>
|
||||
<p>Creates an empty file if it does not exist, or updates its modification time if it does.</p>
|
||||
<pre><code>Path.new("marker.txt").touch()</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">unlink</span>()
|
||||
</div>
|
||||
<p>Deletes the file.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">rename</span>(<span class="param">target</span>) → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Moves or renames the file to <code>target</code>. Returns the new Path.</p>
|
||||
<pre><code>var old = Path.new("draft.txt")
|
||||
var published = old.rename("article.txt")
|
||||
System.print(published) // article.txt</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">replace</span>(<span class="param">target</span>) → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Alias for <code>rename()</code>. Replaces the target if it exists.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">copyfile</span>(<span class="param">dest</span>)
|
||||
</div>
|
||||
<p>Copies this file to <code>dest</code>.</p>
|
||||
<pre><code>Path.new("original.txt").copyfile("backup.txt")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">chmod</span>(<span class="param">mode</span>)
|
||||
</div>
|
||||
<p>Changes the file permissions. Mode is an octal number.</p>
|
||||
<pre><code>Path.new("script.sh").chmod(0x1ed) // 0755 in octal</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">resolve</span>() → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>Returns the absolute real path, resolving any symlinks.</p>
|
||||
<pre><code>var real = Path.new("./relative/../file.txt").resolve()
|
||||
System.print(real) // /home/user/file.txt</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">samefile</span>(<span class="param">other</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns true if this path and <code>other</code> refer to the same file (compares inodes).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">owner</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the username of the file owner (POSIX only).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">group</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the group name of the file (POSIX only).</p>
|
||||
|
||||
<h2 id="directory-operations">Directory Operations</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">mkdir</span>()
|
||||
</div>
|
||||
<p>Creates the directory.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">mkdir</span>(<span class="param">parents</span>)
|
||||
</div>
|
||||
<p>Creates the directory. If <code>parents</code> is true, creates all intermediate directories.</p>
|
||||
<pre><code>Path.new("/tmp/a/b/c").mkdir(true)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">rmdir</span>()
|
||||
</div>
|
||||
<p>Removes an empty directory.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">rmtree</span>()
|
||||
</div>
|
||||
<p>Recursively deletes the directory and all its contents.</p>
|
||||
<pre><code>Path.new("/tmp/build_output").rmtree()</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">iterdir</span>() → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Returns a list of Path objects for each entry in the directory.</p>
|
||||
<pre><code>for (entry in Path.new(".").iterdir()) {
|
||||
System.print(entry.name)
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">glob</span>(<span class="param">pattern</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Returns paths matching the glob pattern in this directory. Supports <code>*</code> and <code>?</code> wildcards.</p>
|
||||
<pre><code>var txtFiles = Path.new(".").glob("*.txt")
|
||||
for (f in txtFiles) {
|
||||
System.print(f.name)
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">rglob</span>(<span class="param">pattern</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Recursively matches files in this directory and all subdirectories.</p>
|
||||
<pre><code>var allWren = Path.cwd.rglob("*.wren")
|
||||
System.print("Found %(allWren.count) Wren files")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">walk</span>() → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Recursively walks the directory tree top-down. Returns a list of <code>[dirPath, dirNames, fileNames]</code> tuples.</p>
|
||||
<pre><code>for (entry in Path.new("src").walk()) {
|
||||
var dir = entry[0]
|
||||
var dirs = entry[1]
|
||||
var files = entry[2]
|
||||
for (f in files) {
|
||||
System.print(dir / f)
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">walk</span>(<span class="param">topDown</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Walks the directory tree. If <code>topDown</code> is false, visits child directories before parents.</p>
|
||||
|
||||
<h2 id="symlink-operations">Symlink Operations</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">symlinkTo</span>(<span class="param">target</span>)
|
||||
</div>
|
||||
<p>Creates a symbolic link at this path pointing to <code>target</code>.</p>
|
||||
<pre><code>Path.new("/tmp/link").symlinkTo("/etc/hosts")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">hardlinkTo</span>(<span class="param">target</span>)
|
||||
</div>
|
||||
<p>Creates a hard link at this path pointing to <code>target</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">readlink</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the target of the symbolic link.</p>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Configuration File Management</h3>
|
||||
<pre><code>import "pathlib" for Path
|
||||
import "json" for Json
|
||||
|
||||
var configDir = Path.home / ".config" / "myapp"
|
||||
if (!configDir.exists()) {
|
||||
configDir.mkdir(true)
|
||||
}
|
||||
|
||||
var configFile = configDir / "settings.json"
|
||||
if (!configFile.exists()) {
|
||||
configFile.writeText(Json.stringify({
|
||||
"theme": "dark",
|
||||
"language": "en"
|
||||
}, 2))
|
||||
}
|
||||
|
||||
var settings = Json.parse(configFile.readText())
|
||||
System.print("Theme: %(settings["theme"])")</code></pre>
|
||||
|
||||
<h3>Batch File Processing</h3>
|
||||
<pre><code>import "pathlib" for Path
|
||||
|
||||
var dataDir = Path.new("./data")
|
||||
for (file in dataDir.glob("*.csv")) {
|
||||
var size = file.stat().size
|
||||
System.print("%(file.name): %(size) bytes")
|
||||
}
|
||||
|
||||
var total = 0
|
||||
for (wren in Path.cwd.rglob("*.wren")) {
|
||||
total = total + 1
|
||||
}
|
||||
System.print("Found %(total) Wren source files")</code></pre>
|
||||
|
||||
<h3>Directory Tree Traversal</h3>
|
||||
<pre><code>import "pathlib" for Path
|
||||
|
||||
var src = Path.new("src")
|
||||
for (entry in src.walk()) {
|
||||
var dir = entry[0]
|
||||
var files = entry[2]
|
||||
if (files.count > 0) {
|
||||
System.print("%(dir.relativeTo(src)):")
|
||||
for (f in files) {
|
||||
System.print(" %(f)")
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Path Manipulation</h3>
|
||||
<pre><code>import "pathlib" for Path
|
||||
|
||||
var p = Path.new("/home/user/documents/report.tar.gz")
|
||||
|
||||
System.print(p.name) // report.tar.gz
|
||||
System.print(p.stem) // report.tar
|
||||
System.print(p.suffix) // .gz
|
||||
System.print(p.suffixes) // [.tar, .gz]
|
||||
System.print(p.parent) // /home/user/documents
|
||||
|
||||
var backup = p.withSuffix(".bak")
|
||||
System.print(backup) // /home/user/documents/report.tar.bak
|
||||
|
||||
var renamed = p.withName("summary.pdf")
|
||||
System.print(renamed) // /home/user/documents/summary.pdf</code></pre>
|
||||
|
||||
<h3>Temporary File Cleanup</h3>
|
||||
<pre><code>import "pathlib" for Path
|
||||
|
||||
var tmpDir = Path.new("/tmp/build_artifacts")
|
||||
if (tmpDir.exists()) {
|
||||
tmpDir.rmtree()
|
||||
}
|
||||
|
||||
tmpDir.mkdir(true)
|
||||
(tmpDir / "output.o").touch()
|
||||
(tmpDir / "output.bin").touch()
|
||||
|
||||
var count = tmpDir.iterdir().count
|
||||
System.print("%(count) files in build directory")
|
||||
|
||||
tmpDir.rmtree()</code></pre>
|
||||
|
||||
<h3>Home Directory Expansion</h3>
|
||||
<pre><code>import "pathlib" for Path
|
||||
|
||||
var shortcuts = [
|
||||
Path.new("~/.bashrc"),
|
||||
Path.new("~/.config"),
|
||||
Path.new("~/Documents")
|
||||
]
|
||||
|
||||
for (p in shortcuts) {
|
||||
var expanded = p.expanduser()
|
||||
System.print("%(p) -> %(expanded) (exists: %(expanded.exists()))")
|
||||
}</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Path objects are immutable. Methods like <code>withName()</code>, <code>withSuffix()</code>, and <code>joinpath()</code> return new Path objects rather than modifying the original.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Use the <code>/</code> operator for readable path construction: <code>Path.home / ".config" / "myapp" / "settings.json"</code> reads naturally and handles separator insertion automatically.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="io.html" class="prev">io</a>
|
||||
<a href="scheduler.html" class="next">scheduler</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
356
manual/api/regex.html
Normal file
356
manual/api/regex.html
Normal file
@ -0,0 +1,356 @@
|
||||
<!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>regex - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html" class="active">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>regex</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>regex</h1>
|
||||
|
||||
<p>The <code>regex</code> module provides regular expression matching, replacement, and splitting using PCRE-compatible patterns.</p>
|
||||
|
||||
<pre><code>import "regex" for Regex, Match</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#regex-class">Regex Class</a></li>
|
||||
<li><a href="#match-class">Match Class</a></li>
|
||||
<li><a href="#pattern-syntax">Pattern Syntax</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="regex-class">Regex Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Regex</h3>
|
||||
<p>Regular expression pattern</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructors</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Regex.new</span>(<span class="param">pattern</span>) → <span class="type">Regex</span>
|
||||
</div>
|
||||
<p>Creates a new regex from a pattern string.</p>
|
||||
<pre><code>var re = Regex.new("\\d+") // Match digits</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Regex.new</span>(<span class="param">pattern</span>, <span class="param">flags</span>) → <span class="type">Regex</span>
|
||||
</div>
|
||||
<p>Creates a regex with flags.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">pattern</span> <span class="param-type">(String)</span> - Regex pattern</li>
|
||||
<li><span class="param-name">flags</span> <span class="param-type">(String)</span> - Flags: "i" (case-insensitive), "m" (multiline), "s" (dotall)</li>
|
||||
</ul>
|
||||
<pre><code>var re = Regex.new("hello", "i") // Case-insensitive</code></pre>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">pattern</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The pattern string.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">flags</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The flags string.</p>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">test</span>(<span class="param">string</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Tests if the pattern matches anywhere in the string.</p>
|
||||
<pre><code>var re = Regex.new("\\d+")
|
||||
System.print(re.test("abc123")) // true
|
||||
System.print(re.test("abc")) // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">match</span>(<span class="param">string</span>) → <span class="type">Match|null</span>
|
||||
</div>
|
||||
<p>Finds the first match in the string.</p>
|
||||
<pre><code>var re = Regex.new("(\\w+)@(\\w+\\.\\w+)")
|
||||
var m = re.match("email: alice@example.com")
|
||||
if (m != null) {
|
||||
System.print(m.text) // alice@example.com
|
||||
System.print(m.group(1)) // alice
|
||||
System.print(m.group(2)) // example.com
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">matchAll</span>(<span class="param">string</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Finds all matches in the string.</p>
|
||||
<pre><code>var re = Regex.new("\\d+")
|
||||
var matches = re.matchAll("a1 b22 c333")
|
||||
for (m in matches) {
|
||||
System.print(m.text) // 1, 22, 333
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">replace</span>(<span class="param">string</span>, <span class="param">replacement</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Replaces the first match with the replacement string.</p>
|
||||
<pre><code>var re = Regex.new("\\d+")
|
||||
System.print(re.replace("a1b2c3", "X")) // aXb2c3</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">replaceAll</span>(<span class="param">string</span>, <span class="param">replacement</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Replaces all matches with the replacement string.</p>
|
||||
<pre><code>var re = Regex.new("\\d+")
|
||||
System.print(re.replaceAll("a1b2c3", "X")) // aXbXcX</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">split</span>(<span class="param">string</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Splits the string by the pattern.</p>
|
||||
<pre><code>var re = Regex.new("[,;\\s]+")
|
||||
var parts = re.split("a, b; c d")
|
||||
System.print(parts) // [a, b, c, d]</code></pre>
|
||||
|
||||
<h2 id="match-class">Match Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Match</h3>
|
||||
<p>Regex match result</p>
|
||||
</div>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>text</code></td>
|
||||
<td>String</td>
|
||||
<td>The matched text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>start</code></td>
|
||||
<td>Num</td>
|
||||
<td>Start index in the original string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>end</code></td>
|
||||
<td>Num</td>
|
||||
<td>End index in the original string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>groups</code></td>
|
||||
<td>List</td>
|
||||
<td>List of captured groups</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">group</span>(<span class="param">index</span>) → <span class="type">String|null</span>
|
||||
</div>
|
||||
<p>Gets a captured group by index. Group 0 is the entire match.</p>
|
||||
<pre><code>var re = Regex.new("(\\w+)-(\\d+)")
|
||||
var m = re.match("item-42")
|
||||
System.print(m.group(0)) // item-42
|
||||
System.print(m.group(1)) // item
|
||||
System.print(m.group(2)) // 42</code></pre>
|
||||
|
||||
<h2 id="pattern-syntax">Pattern Syntax</h2>
|
||||
|
||||
<h3>Character Classes</h3>
|
||||
<table>
|
||||
<tr><th>Pattern</th><th>Matches</th></tr>
|
||||
<tr><td><code>.</code></td><td>Any character (except newline)</td></tr>
|
||||
<tr><td><code>\d</code></td><td>Digit (0-9)</td></tr>
|
||||
<tr><td><code>\D</code></td><td>Non-digit</td></tr>
|
||||
<tr><td><code>\w</code></td><td>Word character (a-z, A-Z, 0-9, _)</td></tr>
|
||||
<tr><td><code>\W</code></td><td>Non-word character</td></tr>
|
||||
<tr><td><code>\s</code></td><td>Whitespace</td></tr>
|
||||
<tr><td><code>\S</code></td><td>Non-whitespace</td></tr>
|
||||
<tr><td><code>[abc]</code></td><td>Any of a, b, or c</td></tr>
|
||||
<tr><td><code>[^abc]</code></td><td>Not a, b, or c</td></tr>
|
||||
<tr><td><code>[a-z]</code></td><td>Range a through z</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Quantifiers</h3>
|
||||
<table>
|
||||
<tr><th>Pattern</th><th>Matches</th></tr>
|
||||
<tr><td><code>*</code></td><td>0 or more</td></tr>
|
||||
<tr><td><code>+</code></td><td>1 or more</td></tr>
|
||||
<tr><td><code>?</code></td><td>0 or 1</td></tr>
|
||||
<tr><td><code>{n}</code></td><td>Exactly n times</td></tr>
|
||||
<tr><td><code>{n,}</code></td><td>n or more times</td></tr>
|
||||
<tr><td><code>{n,m}</code></td><td>Between n and m times</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Anchors</h3>
|
||||
<table>
|
||||
<tr><th>Pattern</th><th>Matches</th></tr>
|
||||
<tr><td><code>^</code></td><td>Start of string/line</td></tr>
|
||||
<tr><td><code>$</code></td><td>End of string/line</td></tr>
|
||||
<tr><td><code>\b</code></td><td>Word boundary</td></tr>
|
||||
<tr><td><code>\B</code></td><td>Non-word boundary</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Groups</h3>
|
||||
<table>
|
||||
<tr><th>Pattern</th><th>Description</th></tr>
|
||||
<tr><td><code>(abc)</code></td><td>Capturing group</td></tr>
|
||||
<tr><td><code>(?:abc)</code></td><td>Non-capturing group</td></tr>
|
||||
<tr><td><code>a|b</code></td><td>Alternation (a or b)</td></tr>
|
||||
</table>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Email Validation</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var emailRe = Regex.new("^[\\w.+-]+@[\\w-]+\\.[\\w.-]+$")
|
||||
|
||||
var emails = ["alice@example.com", "invalid", "bob@test.org"]
|
||||
for (email in emails) {
|
||||
if (emailRe.test(email)) {
|
||||
System.print("%(email) is valid")
|
||||
} else {
|
||||
System.print("%(email) is invalid")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Extracting Data</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var logRe = Regex.new("(\\d{4}-\\d{2}-\\d{2}) (\\w+): (.+)")
|
||||
var log = "2024-01-15 ERROR: Connection failed"
|
||||
|
||||
var m = logRe.match(log)
|
||||
if (m != null) {
|
||||
System.print("Date: %(m.group(1))") // 2024-01-15
|
||||
System.print("Level: %(m.group(2))") // ERROR
|
||||
System.print("Message: %(m.group(3))") // Connection failed
|
||||
}</code></pre>
|
||||
|
||||
<h3>Find and Replace</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var text = "Call 555-1234 or 555-5678"
|
||||
var phoneRe = Regex.new("\\d{3}-\\d{4}")
|
||||
|
||||
var redacted = phoneRe.replaceAll(text, "XXX-XXXX")
|
||||
System.print(redacted) // Call XXX-XXXX or XXX-XXXX</code></pre>
|
||||
|
||||
<h3>Parsing URLs</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var urlRe = Regex.new("(https?)://([^/]+)(/.*)?")
|
||||
var url = "https://example.com/path/to/page"
|
||||
|
||||
var m = urlRe.match(url)
|
||||
System.print("Scheme: %(m.group(1))") // https
|
||||
System.print("Host: %(m.group(2))") // example.com
|
||||
System.print("Path: %(m.group(3))") // /path/to/page</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Remember to escape backslashes in Wren strings. Use <code>\\d</code> instead of <code>\d</code>.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="base64.html" class="prev">base64</a>
|
||||
<a href="jinja.html" class="next">jinja</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
196
manual/api/scheduler.html
Normal file
196
manual/api/scheduler.html
Normal file
@ -0,0 +1,196 @@
|
||||
<!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>scheduler - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html" class="active">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>scheduler</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>scheduler</h1>
|
||||
|
||||
<p>The <code>scheduler</code> module manages the async event loop and fiber scheduling. It is the foundation for all async operations in Wren-CLI.</p>
|
||||
|
||||
<pre><code>import "scheduler" for Scheduler</code></pre>
|
||||
|
||||
<h2>Scheduler Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Scheduler</h3>
|
||||
<p>Async fiber scheduler</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Scheduler.await_</span>(<span class="param">block</span>) → <span class="type">any</span>
|
||||
</div>
|
||||
<p>Suspends the current fiber until an async operation completes. Used internally by modules to implement async operations.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">block</span> <span class="param-type">(Fn)</span> - Block that initiates the async operation</li>
|
||||
<li><span class="returns">Returns:</span> Result of the async operation</li>
|
||||
</ul>
|
||||
<pre><code>var result = Scheduler.await_ {
|
||||
Timer.sleep_(1000, Fiber.current)
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Scheduler.resume_</span>(<span class="param">fiber</span>)
|
||||
</div>
|
||||
<p>Resumes a suspended fiber. Used by native code to wake up fibers when async operations complete.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Scheduler.resume_</span>(<span class="param">fiber</span>, <span class="param">value</span>)
|
||||
</div>
|
||||
<p>Resumes a fiber with a result value.</p>
|
||||
|
||||
<h2>How Async Works</h2>
|
||||
|
||||
<p>Wren-CLI uses an event loop (libuv) for non-blocking I/O. Here is how async operations work:</p>
|
||||
|
||||
<ol>
|
||||
<li>A Wren fiber calls an async method (e.g., <code>Http.get</code>)</li>
|
||||
<li>The method starts a native async operation and suspends the fiber</li>
|
||||
<li>The event loop continues processing other events</li>
|
||||
<li>When the operation completes, the fiber is resumed with the result</li>
|
||||
</ol>
|
||||
|
||||
<h3>Under the Hood</h3>
|
||||
<pre><code>// How Timer.sleep works internally
|
||||
class Timer {
|
||||
static sleep(ms) {
|
||||
return Scheduler.await_ {
|
||||
Timer.sleep_(ms, Fiber.current)
|
||||
}
|
||||
}
|
||||
|
||||
foreign static sleep_(ms, fiber)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Sequential vs Parallel</h3>
|
||||
<p>Operations are sequential by default:</p>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
// 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>
|
||||
|
||||
<p>For parallel operations, use multiple fibers:</p>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
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") }
|
||||
]
|
||||
|
||||
// Start all fibers
|
||||
for (f in fibers) f.call()
|
||||
|
||||
// Results are already available when fibers complete</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>The scheduler is used internally by modules. Most user code does not need to interact with it directly. Use higher-level modules like <code>timer</code>, <code>http</code>, and <code>io</code> instead.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Async operations in Wren-CLI are cooperative, not preemptive. A fiber runs until it explicitly yields or calls an async operation.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="pathlib.html" class="prev">pathlib</a>
|
||||
<a href="math.html" class="next">math</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
352
manual/api/signal.html
Normal file
352
manual/api/signal.html
Normal file
@ -0,0 +1,352 @@
|
||||
<!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>signal - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html" class="active">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>signal</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>signal</h1>
|
||||
|
||||
<p>The <code>signal</code> module provides Unix signal handling capabilities, allowing scripts to trap, ignore, or reset signal handlers.</p>
|
||||
|
||||
<pre><code>import "signal" for Signal</code></pre>
|
||||
|
||||
<h2>Signal Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Signal</h3>
|
||||
<p>Unix signal handling</p>
|
||||
</div>
|
||||
|
||||
<h3>Signal Constants</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Constant</th>
|
||||
<th>Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Signal.SIGHUP</code></td>
|
||||
<td>1</td>
|
||||
<td>Hangup (terminal closed or controlling process ended)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Signal.SIGINT</code></td>
|
||||
<td>2</td>
|
||||
<td>Interrupt (Ctrl+C)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Signal.SIGQUIT</code></td>
|
||||
<td>3</td>
|
||||
<td>Quit (Ctrl+\)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Signal.SIGTERM</code></td>
|
||||
<td>15</td>
|
||||
<td>Termination request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Signal.SIGUSR1</code></td>
|
||||
<td>10</td>
|
||||
<td>User-defined signal 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Signal.SIGUSR2</code></td>
|
||||
<td>12</td>
|
||||
<td>User-defined signal 2</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Signal.trap</span>(<span class="param">signum</span>, <span class="param">fn</span>)
|
||||
</div>
|
||||
<p>Registers a handler function to be called when the signal is received. The handler will be called each time the signal is received.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">signum</span> <span class="param-type">(Num)</span> - Signal number or constant</li>
|
||||
<li><span class="param-name">fn</span> <span class="param-type">(Fn)</span> - Handler function (no arguments)</li>
|
||||
</ul>
|
||||
<pre><code>Signal.trap(Signal.SIGINT) {
|
||||
System.print("Caught Ctrl+C!")
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Signal.ignore</span>(<span class="param">signum</span>)
|
||||
</div>
|
||||
<p>Ignores the specified signal. The signal will be delivered but have no effect.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">signum</span> <span class="param-type">(Num)</span> - Signal number to ignore</li>
|
||||
</ul>
|
||||
<pre><code>Signal.ignore(Signal.SIGHUP)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Signal.reset</span>(<span class="param">signum</span>)
|
||||
</div>
|
||||
<p>Resets the signal handler to the default behavior.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">signum</span> <span class="param-type">(Num)</span> - Signal number to reset</li>
|
||||
</ul>
|
||||
<pre><code>Signal.reset(Signal.SIGINT)</code></pre>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Graceful Shutdown</h3>
|
||||
<pre><code>import "signal" for Signal
|
||||
import "timer" for Timer
|
||||
|
||||
var running = true
|
||||
|
||||
Signal.trap(Signal.SIGINT) {
|
||||
System.print("\nShutting down gracefully...")
|
||||
running = false
|
||||
}
|
||||
|
||||
Signal.trap(Signal.SIGTERM) {
|
||||
System.print("\nReceived SIGTERM, shutting down...")
|
||||
running = false
|
||||
}
|
||||
|
||||
System.print("Server running. Press Ctrl+C to stop.")
|
||||
while (running) {
|
||||
Timer.sleep(1000)
|
||||
}
|
||||
|
||||
System.print("Cleanup complete. Goodbye!")</code></pre>
|
||||
|
||||
<h3>Configuration Reload on SIGHUP</h3>
|
||||
<pre><code>import "signal" for Signal
|
||||
import "timer" for Timer
|
||||
import "io" for File
|
||||
|
||||
var config = {}
|
||||
|
||||
var loadConfig = Fn.new {
|
||||
System.print("Loading configuration...")
|
||||
if (File.exists("config.json")) {
|
||||
var content = File.read("config.json")
|
||||
System.print("Config loaded: %(content)")
|
||||
}
|
||||
}
|
||||
|
||||
loadConfig.call()
|
||||
|
||||
Signal.trap(Signal.SIGHUP) {
|
||||
System.print("Received SIGHUP, reloading config...")
|
||||
loadConfig.call()
|
||||
}
|
||||
|
||||
System.print("Running. Send SIGHUP to reload config.")
|
||||
while (true) {
|
||||
Timer.sleep(1000)
|
||||
}</code></pre>
|
||||
|
||||
<h3>Custom Signal for Debug Dump</h3>
|
||||
<pre><code>import "signal" for Signal
|
||||
import "timer" for Timer
|
||||
import "datetime" for DateTime
|
||||
|
||||
var requestCount = 0
|
||||
var startTime = DateTime.now()
|
||||
|
||||
Signal.trap(Signal.SIGUSR1) {
|
||||
System.print("=== Debug Dump ===")
|
||||
System.print("Requests handled: %(requestCount)")
|
||||
var uptime = DateTime.now() - startTime
|
||||
System.print("Uptime: %(uptime.hours) hours")
|
||||
System.print("==================")
|
||||
}
|
||||
|
||||
System.print("Send SIGUSR1 for debug info (kill -USR1 pid)")
|
||||
while (true) {
|
||||
requestCount = requestCount + 1
|
||||
Timer.sleep(100)
|
||||
}</code></pre>
|
||||
|
||||
<h3>Ignoring Signals</h3>
|
||||
<pre><code>import "signal" for Signal
|
||||
import "timer" for Timer
|
||||
|
||||
Signal.ignore(Signal.SIGINT)
|
||||
System.print("SIGINT is now ignored. Ctrl+C will not stop this script.")
|
||||
System.print("Use 'kill -9 pid' to force stop.")
|
||||
|
||||
for (i in 1..10) {
|
||||
System.print("Still running... %(i)")
|
||||
Timer.sleep(1000)
|
||||
}
|
||||
|
||||
Signal.reset(Signal.SIGINT)
|
||||
System.print("SIGINT handler restored. Ctrl+C works again.")</code></pre>
|
||||
|
||||
<h3>Multiple Signal Handlers</h3>
|
||||
<pre><code>import "signal" for Signal
|
||||
import "timer" for Timer
|
||||
|
||||
var shutdownRequested = false
|
||||
var forceShutdown = false
|
||||
|
||||
Signal.trap(Signal.SIGINT) {
|
||||
if (shutdownRequested) {
|
||||
System.print("\nForce shutdown!")
|
||||
forceShutdown = true
|
||||
} else {
|
||||
System.print("\nGraceful shutdown requested. Press Ctrl+C again to force.")
|
||||
shutdownRequested = true
|
||||
}
|
||||
}
|
||||
|
||||
Signal.trap(Signal.SIGTERM) {
|
||||
System.print("\nSIGTERM received, shutting down immediately.")
|
||||
forceShutdown = true
|
||||
}
|
||||
|
||||
System.print("Running... Press Ctrl+C to stop.")
|
||||
while (!forceShutdown) {
|
||||
if (shutdownRequested) {
|
||||
System.print("Cleaning up...")
|
||||
Timer.sleep(500)
|
||||
break
|
||||
}
|
||||
Timer.sleep(100)
|
||||
}
|
||||
|
||||
System.print("Exited.")</code></pre>
|
||||
|
||||
<h3>Worker Process Control</h3>
|
||||
<pre><code>import "signal" for Signal
|
||||
import "timer" for Timer
|
||||
|
||||
var paused = false
|
||||
|
||||
Signal.trap(Signal.SIGUSR1) {
|
||||
paused = true
|
||||
System.print("Worker paused")
|
||||
}
|
||||
|
||||
Signal.trap(Signal.SIGUSR2) {
|
||||
paused = false
|
||||
System.print("Worker resumed")
|
||||
}
|
||||
|
||||
System.print("Worker running. SIGUSR1 to pause, SIGUSR2 to resume.")
|
||||
|
||||
var count = 0
|
||||
while (true) {
|
||||
if (!paused) {
|
||||
count = count + 1
|
||||
System.print("Working... %(count)")
|
||||
}
|
||||
Timer.sleep(1000)
|
||||
}</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Signal handling is only available on POSIX systems (Linux, macOS, BSD). On Windows, signal support is limited.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Some signals like SIGKILL (9) and SIGSTOP (19) cannot be trapped or ignored. Attempting to do so will have no effect.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Always implement graceful shutdown handlers for long-running services. Use SIGTERM for clean shutdowns and reserve SIGINT for interactive termination.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="env.html" class="prev">env</a>
|
||||
<a href="subprocess.html" class="next">subprocess</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
259
manual/api/sqlite.html
Normal file
259
manual/api/sqlite.html
Normal file
@ -0,0 +1,259 @@
|
||||
<!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>sqlite - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html" class="active">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>sqlite</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>sqlite</h1>
|
||||
|
||||
<p>The <code>sqlite</code> module provides SQLite database functionality for persistent data storage.</p>
|
||||
|
||||
<pre><code>import "sqlite" for Sqlite</code></pre>
|
||||
|
||||
<h2>Sqlite Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Sqlite</h3>
|
||||
<p>SQLite database connection</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructors</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Sqlite.open</span>(<span class="param">path</span>) → <span class="type">Sqlite</span>
|
||||
</div>
|
||||
<p>Opens or creates an SQLite database file.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">path</span> <span class="param-type">(String)</span> - Path to the database file</li>
|
||||
<li><span class="returns">Returns:</span> Database connection</li>
|
||||
</ul>
|
||||
<pre><code>var db = Sqlite.open("data.db")</code></pre>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">execute</span>(<span class="param">sql</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Executes an SQL statement and returns the results.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">sql</span> <span class="param-type">(String)</span> - SQL statement</li>
|
||||
<li><span class="returns">Returns:</span> List of result rows (each row is a Map)</li>
|
||||
</ul>
|
||||
<pre><code>var rows = db.execute("SELECT * FROM users")
|
||||
for (row in rows) {
|
||||
System.print(row["name"])
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">execute</span>(<span class="param">sql</span>, <span class="param">params</span>) → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Executes a parameterized SQL statement (prevents SQL injection).</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">sql</span> <span class="param-type">(String)</span> - SQL with ? placeholders</li>
|
||||
<li><span class="param-name">params</span> <span class="param-type">(List)</span> - Parameter values</li>
|
||||
</ul>
|
||||
<pre><code>var rows = db.execute("SELECT * FROM users WHERE age > ?", [18])</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">lastInsertId</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the row ID of the last INSERT operation.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">close</span>()
|
||||
</div>
|
||||
<p>Closes the database connection.</p>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Creating Tables</h3>
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
|
||||
var db = Sqlite.open("app.db")
|
||||
|
||||
db.execute("
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
")
|
||||
|
||||
db.close()</code></pre>
|
||||
|
||||
<h3>Inserting Data</h3>
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
|
||||
var db = Sqlite.open("app.db")
|
||||
|
||||
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Alice", "alice@example.com"])
|
||||
System.print("Inserted user with ID: %(db.lastInsertId)")
|
||||
|
||||
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Bob", "bob@example.com"])
|
||||
|
||||
db.close()</code></pre>
|
||||
|
||||
<h3>Querying Data</h3>
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
|
||||
var db = Sqlite.open("app.db")
|
||||
|
||||
var users = db.execute("SELECT * FROM users ORDER BY name")
|
||||
for (user in users) {
|
||||
System.print("%(user["id"]): %(user["name"]) <%(user["email"])>")
|
||||
}
|
||||
|
||||
var user = db.execute("SELECT * FROM users WHERE id = ?", [1])
|
||||
if (user.count > 0) {
|
||||
System.print("Found: %(user[0]["name"])")
|
||||
}
|
||||
|
||||
db.close()</code></pre>
|
||||
|
||||
<h3>Updating Data</h3>
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
|
||||
var db = Sqlite.open("app.db")
|
||||
|
||||
db.execute("UPDATE users SET email = ? WHERE id = ?", ["newemail@example.com", 1])
|
||||
|
||||
db.close()</code></pre>
|
||||
|
||||
<h3>Deleting Data</h3>
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
|
||||
var db = Sqlite.open("app.db")
|
||||
|
||||
db.execute("DELETE FROM users WHERE id = ?", [2])
|
||||
|
||||
db.close()</code></pre>
|
||||
|
||||
<h3>Transactions</h3>
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
|
||||
var db = Sqlite.open("app.db")
|
||||
|
||||
db.execute("BEGIN TRANSACTION")
|
||||
|
||||
var fiber = Fiber.new {
|
||||
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Charlie", "charlie@example.com"])
|
||||
db.execute("INSERT INTO users (name, email) VALUES (?, ?)", ["Diana", "diana@example.com"])
|
||||
}
|
||||
|
||||
var error = fiber.try()
|
||||
if (error) {
|
||||
db.execute("ROLLBACK")
|
||||
System.print("Error: %(error)")
|
||||
} else {
|
||||
db.execute("COMMIT")
|
||||
System.print("Transaction committed")
|
||||
}
|
||||
|
||||
db.close()</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Always use parameterized queries with <code>?</code> placeholders to prevent SQL injection attacks. Never concatenate user input directly into SQL strings.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Use <code>":memory:"</code> as the path for an in-memory database that does not persist to disk.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="subprocess.html" class="prev">subprocess</a>
|
||||
<a href="datetime.html" class="next">datetime</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
454
manual/api/string.html
Normal file
454
manual/api/string.html
Normal file
@ -0,0 +1,454 @@
|
||||
<!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>String - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html" class="active">String</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</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>
|
||||
</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>
|
||||
</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>String</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>String</h1>
|
||||
|
||||
<p>The <code>String</code> class is a core type available globally without imports. Strings in Wren are immutable sequences of bytes, typically representing UTF-8 encoded text. All methods return new strings rather than modifying the original.</p>
|
||||
|
||||
<pre><code>var s = "Hello World"
|
||||
System.print(s.lower) // hello world
|
||||
System.print(s.upper) // HELLO WORLD
|
||||
System.print(s.title) // Hello World
|
||||
System.print(s.reverse) // dlroW olleH</code></pre>
|
||||
|
||||
<h2>Case Conversion</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">lower</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the string with all ASCII uppercase characters converted to lowercase.</p>
|
||||
<pre><code>"Hello World".lower // "hello world"
|
||||
"ABC 123".lower // "abc 123"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">upper</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the string with all ASCII lowercase characters converted to uppercase.</p>
|
||||
<pre><code>"Hello World".upper // "HELLO WORLD"
|
||||
"abc 123".upper // "ABC 123"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">capitalize</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the string with the first character uppercased and the rest lowercased.</p>
|
||||
<pre><code>"hello world".capitalize // "Hello world"
|
||||
"HELLO".capitalize // "Hello"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">title</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the string with the first character of each word capitalized and the rest lowercased. Words are separated by whitespace.</p>
|
||||
<pre><code>"hello world".title // "Hello World"
|
||||
"HELLO WORLD".title // "Hello World"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">swapCase</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the string with uppercase characters converted to lowercase and vice versa.</p>
|
||||
<pre><code>"Hello World".swapCase // "hELLO wORLD"</code></pre>
|
||||
|
||||
<h2>Character Testing</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isLower</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if the string contains at least one alphabetic character and all alphabetic characters are lowercase.</p>
|
||||
<pre><code>"hello".isLower // true
|
||||
"hello1".isLower // true
|
||||
"Hello".isLower // false
|
||||
"123".isLower // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isUpper</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if the string contains at least one alphabetic character and all alphabetic characters are uppercase.</p>
|
||||
<pre><code>"HELLO".isUpper // true
|
||||
"HELLO1".isUpper // true
|
||||
"Hello".isUpper // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isDigit</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if the string is non-empty and all bytes are ASCII digits (0-9).</p>
|
||||
<pre><code>"12345".isDigit // true
|
||||
"123a5".isDigit // false
|
||||
"".isDigit // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isAlpha</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if the string is non-empty and all bytes are ASCII letters (a-z, A-Z).</p>
|
||||
<pre><code>"hello".isAlpha // true
|
||||
"hello1".isAlpha // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isAlphaNumeric</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if the string is non-empty and all bytes are ASCII letters or digits.</p>
|
||||
<pre><code>"hello123".isAlphaNumeric // true
|
||||
"hello!".isAlphaNumeric // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isSpace</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if the string is non-empty and all bytes are ASCII whitespace (space, tab, newline, carriage return, form feed, vertical tab).</p>
|
||||
<pre><code>" ".isSpace // true
|
||||
" \t\n".isSpace // true
|
||||
"hello".isSpace // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isAscii</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if all bytes in the string are less than 128. Returns <code>true</code> for empty strings.</p>
|
||||
<pre><code>"hello".isAscii // true
|
||||
"".isAscii // true</code></pre>
|
||||
|
||||
<h2>Search</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">lastIndexOf</span>(<span class="param">search</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the byte index of the last occurrence of <code>search</code> in the string, or <code>-1</code> if not found.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">search</span> <span class="param-type">(String)</span> - The substring to search for</li>
|
||||
<li><span class="returns">Returns:</span> Byte index or -1</li>
|
||||
</ul>
|
||||
<pre><code>"hello world hello".lastIndexOf("hello") // 12
|
||||
"hello".lastIndexOf("x") // -1</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">lastIndexOf</span>(<span class="param">search</span>, <span class="param">start</span>) → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Returns the byte index of the last occurrence of <code>search</code> at or before <code>start</code>, or <code>-1</code> if not found.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">search</span> <span class="param-type">(String)</span> - The substring to search for</li>
|
||||
<li><span class="param-name">start</span> <span class="param-type">(Num)</span> - Maximum byte index to search from</li>
|
||||
<li><span class="returns">Returns:</span> Byte index or -1</li>
|
||||
</ul>
|
||||
<pre><code>"hello world hello".lastIndexOf("hello", 11) // 0
|
||||
"hello world hello".lastIndexOf("hello", 12) // 12</code></pre>
|
||||
|
||||
<h2>Transformation</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">reverse</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the string with characters in reverse order. Code point aware for UTF-8 strings.</p>
|
||||
<pre><code>"hello".reverse // "olleh"
|
||||
"abcde".reverse // "edcba"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">center</span>(<span class="param">width</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Centers the string in a field of <code>width</code> characters, padded with spaces.</p>
|
||||
<pre><code>"hi".center(10) // " hi "</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">center</span>(<span class="param">width</span>, <span class="param">char</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Centers the string in a field of <code>width</code> characters, padded with the given fill character.</p>
|
||||
<pre><code>"hi".center(10, "-") // "----hi----"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">lpad</span>(<span class="param">width</span>, <span class="param">char</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Left-pads the string to <code>width</code> characters using the given fill character.</p>
|
||||
<pre><code>"42".lpad(5, "0") // "00042"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">rpad</span>(<span class="param">width</span>, <span class="param">char</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Right-pads the string to <code>width</code> characters using the given fill character.</p>
|
||||
<pre><code>"hi".rpad(5, ".") // "hi..."</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">zfill</span>(<span class="param">width</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Pads the string with leading zeros to <code>width</code> characters. Preserves a leading <code>-</code> or <code>+</code> sign.</p>
|
||||
<pre><code>"42".zfill(5) // "00042"
|
||||
"-42".zfill(6) // "-00042"
|
||||
"+42".zfill(6) // "+00042"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">removePrefix</span>(<span class="param">prefix</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>If the string starts with <code>prefix</code>, returns the string with the prefix removed. Otherwise returns the original string.</p>
|
||||
<pre><code>"HelloWorld".removePrefix("Hello") // "World"
|
||||
"HelloWorld".removePrefix("World") // "HelloWorld"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">removeSuffix</span>(<span class="param">suffix</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>If the string ends with <code>suffix</code>, returns the string with the suffix removed. Otherwise returns the original string.</p>
|
||||
<pre><code>"HelloWorld".removeSuffix("World") // "Hello"
|
||||
"HelloWorld".removeSuffix("Hello") // "HelloWorld"</code></pre>
|
||||
|
||||
<h2>Splitting & Conversion</h2>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">splitLines</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Splits the string by line endings (<code>\n</code>, <code>\r\n</code>, <code>\r</code>) and returns a list of strings.</p>
|
||||
<pre><code>"line1\nline2\nline3".splitLines // ["line1", "line2", "line3"]
|
||||
"a\r\nb\r\nc".splitLines // ["a", "b", "c"]</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">chars</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>Returns a list of individual characters (code points) in the string.</p>
|
||||
<pre><code>"abc".chars // ["a", "b", "c"]
|
||||
"".chars // []</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">toNum</span> → <span class="type">Num</span> | <span class="type">Null</span>
|
||||
</div>
|
||||
<p>Parses the string as a number. Returns <code>null</code> if the string cannot be parsed.</p>
|
||||
<pre><code>"42".toNum // 42
|
||||
"3.14".toNum // 3.14
|
||||
"-7".toNum // -7
|
||||
"abc".toNum // null</code></pre>
|
||||
|
||||
<h2>Comparison</h2>
|
||||
|
||||
<p>Strings support lexicographic comparison using the standard comparison operators. Comparison is byte-by-byte using the raw byte values, which produces correct results for ASCII and UTF-8 encoded text.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name"><</span>(<span class="param">other</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if this string is lexicographically less than <code>other</code>.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">other</span> <span class="param-type">(String)</span> - The string to compare against</li>
|
||||
</ul>
|
||||
<pre><code>"apple" < "banana" // true
|
||||
"abc" < "abd" // true
|
||||
"abc" < "abcd" // true (prefix is less than longer string)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">></span>(<span class="param">other</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if this string is lexicographically greater than <code>other</code>.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">other</span> <span class="param-type">(String)</span> - The string to compare against</li>
|
||||
</ul>
|
||||
<pre><code>"banana" > "apple" // true
|
||||
"abd" > "abc" // true
|
||||
"abcd" > "abc" // true</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name"><=</span>(<span class="param">other</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if this string is lexicographically less than or equal to <code>other</code>.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">other</span> <span class="param-type">(String)</span> - The string to compare against</li>
|
||||
</ul>
|
||||
<pre><code>"abc" <= "abc" // true
|
||||
"abc" <= "abd" // true
|
||||
"abd" <= "abc" // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">>=</span>(<span class="param">other</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Returns <code>true</code> if this string is lexicographically greater than or equal to <code>other</code>.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">other</span> <span class="param-type">(String)</span> - The string to compare against</li>
|
||||
</ul>
|
||||
<pre><code>"abc" >= "abc" // true
|
||||
"abd" >= "abc" // true
|
||||
"abc" >= "abd" // false</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>String comparison is byte-based, so uppercase letters sort before lowercase letters in ASCII order (e.g., <code>"Z" < "a"</code> is <code>true</code>). If you need case-insensitive comparison, convert both strings to the same case first using <code>lower</code> or <code>upper</code>.</p>
|
||||
</div>
|
||||
|
||||
<h2>Existing Methods</h2>
|
||||
|
||||
<p>The following methods are also available on all strings as part of the core VM.</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>contains(s)</code></td>
|
||||
<td>Returns true if the string contains <code>s</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>startsWith(s)</code></td>
|
||||
<td>Returns true if the string starts with <code>s</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>endsWith(s)</code></td>
|
||||
<td>Returns true if the string ends with <code>s</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>indexOf(s)</code></td>
|
||||
<td>Returns the byte index of <code>s</code>, or -1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>indexOf(s, start)</code></td>
|
||||
<td>Returns the byte index of <code>s</code> starting from <code>start</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>split(delim)</code></td>
|
||||
<td>Splits by delimiter, returns a list</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>replace(from, to)</code></td>
|
||||
<td>Replaces all occurrences of <code>from</code> with <code>to</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>trim()</code></td>
|
||||
<td>Removes leading and trailing whitespace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>trimStart()</code></td>
|
||||
<td>Removes leading whitespace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>trimEnd()</code></td>
|
||||
<td>Removes trailing whitespace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>bytes</code></td>
|
||||
<td>Returns a sequence of byte values</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>codePoints</code></td>
|
||||
<td>Returns a sequence of code point values</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>count</code></td>
|
||||
<td>Number of code points in the string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>isEmpty</code></td>
|
||||
<td>Returns true if the string has no characters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>*(n)</code></td>
|
||||
<td>Repeats the string <code>n</code> times</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>String is a core type available globally. No <code>import</code> statement is needed. All string methods operate on ASCII characters. Multi-byte UTF-8 characters are preserved but case conversion applies only to ASCII letters (a-z, A-Z).</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="index.html" class="prev">Overview</a>
|
||||
<a href="http.html" class="next">http</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
182
manual/api/subprocess.html
Normal file
182
manual/api/subprocess.html
Normal file
@ -0,0 +1,182 @@
|
||||
<!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>subprocess - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html" class="active">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>subprocess</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>subprocess</h1>
|
||||
|
||||
<p>The <code>subprocess</code> module allows running external commands and capturing their output.</p>
|
||||
|
||||
<pre><code>import "subprocess" for Subprocess</code></pre>
|
||||
|
||||
<h2>Subprocess Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Subprocess</h3>
|
||||
<p>External process execution</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Subprocess.run</span>(<span class="param">command</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Runs a command and waits for it to complete.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">command</span> <span class="param-type">(List)</span> - Command and arguments as a list</li>
|
||||
<li><span class="returns">Returns:</span> Map with "stdout", "stderr", and "exitCode"</li>
|
||||
</ul>
|
||||
<pre><code>var result = Subprocess.run(["ls", "-la"])
|
||||
System.print("Exit code: %(result["exitCode"])")
|
||||
System.print("Output: %(result["stdout"])")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Subprocess.run</span>(<span class="param">command</span>, <span class="param">input</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Runs a command with input data provided to stdin.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">command</span> <span class="param-type">(List)</span> - Command and arguments</li>
|
||||
<li><span class="param-name">input</span> <span class="param-type">(String)</span> - Input to send to stdin</li>
|
||||
</ul>
|
||||
<pre><code>var result = Subprocess.run(["cat"], "Hello, World!")
|
||||
System.print(result["stdout"]) // Hello, World!</code></pre>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Running Commands</h3>
|
||||
<pre><code>import "subprocess" for Subprocess
|
||||
|
||||
var result = Subprocess.run(["echo", "Hello"])
|
||||
System.print(result["stdout"]) // Hello
|
||||
|
||||
var files = Subprocess.run(["ls", "-la", "/tmp"])
|
||||
System.print(files["stdout"])</code></pre>
|
||||
|
||||
<h3>Checking Exit Codes</h3>
|
||||
<pre><code>import "subprocess" for Subprocess
|
||||
|
||||
var result = Subprocess.run(["grep", "pattern", "file.txt"])
|
||||
if (result["exitCode"] == 0) {
|
||||
System.print("Found: %(result["stdout"])")
|
||||
} else {
|
||||
System.print("Not found or error")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Processing Data</h3>
|
||||
<pre><code>import "subprocess" for Subprocess
|
||||
import "json" for Json
|
||||
|
||||
var result = Subprocess.run(["curl", "-s", "https://api.example.com/data"])
|
||||
if (result["exitCode"] == 0) {
|
||||
var data = Json.parse(result["stdout"])
|
||||
System.print(data)
|
||||
}</code></pre>
|
||||
|
||||
<h3>Piping Data</h3>
|
||||
<pre><code>import "subprocess" for Subprocess
|
||||
|
||||
var input = "line1\nline2\nline3"
|
||||
var result = Subprocess.run(["wc", "-l"], input)
|
||||
System.print("Lines: %(result["stdout"].trim())")</code></pre>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Commands are not executed through a shell. Use explicit command and argument lists, not shell command strings.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="signal.html" class="prev">signal</a>
|
||||
<a href="sqlite.html" class="next">sqlite</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
370
manual/api/tempfile.html
Normal file
370
manual/api/tempfile.html
Normal file
@ -0,0 +1,370 @@
|
||||
<!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>tempfile - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="tempfile.html" class="active">tempfile</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>tempfile</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>tempfile</h1>
|
||||
|
||||
<p>The <code>tempfile</code> module provides utilities for creating temporary files and directories. It mirrors Python's <code>tempfile</code> API with three classes: <code>TempFile</code> for low-level operations, <code>NamedTemporaryFile</code> for temporary files with automatic cleanup, and <code>TemporaryDirectory</code> for temporary directories with recursive cleanup.</p>
|
||||
|
||||
<pre><code>import "tempfile" for TempFile, NamedTemporaryFile, TemporaryDirectory</code></pre>
|
||||
|
||||
<h2>TempFile Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>TempFile</h3>
|
||||
<p>Low-level temporary file and directory utilities</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">TempFile.tempdir</span> → <span class="type">String|Null</span>
|
||||
</div>
|
||||
<p>Gets or sets a global override for the temporary directory. When set, <code>gettempdir()</code> returns this value instead of checking environment variables.</p>
|
||||
<pre><code>TempFile.tempdir = "/my/custom/tmp"
|
||||
System.print(TempFile.gettempdir()) // /my/custom/tmp
|
||||
TempFile.tempdir = null // Reset to default</code></pre>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">TempFile.gettempdir</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the resolved temporary directory path. Checks in order: the <code>tempdir</code> override, then <code>TMPDIR</code>, <code>TEMP</code>, <code>TMP</code> environment variables, and falls back to <code>/tmp</code> on POSIX or <code>C:\Temp</code> on Windows.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">TempFile.gettempprefix</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Returns the default prefix for temporary file names: <code>"tmp"</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">TempFile.mktemp</span>() → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mktemp</span>(<span class="param">suffix</span>) → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mktemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mktemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Generates a unique temporary file path without creating any file. The generated name consists of the prefix, 8 random hexadecimal characters, and the suffix.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the filename (default: <code>""</code>)</li>
|
||||
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the filename (default: <code>"tmp"</code>)</li>
|
||||
<li><span class="param-name">dir</span> <span class="param-type">(String)</span> - Directory for the path (default: <code>gettempdir()</code>)</li>
|
||||
<li><span class="returns">Returns:</span> A file path string (file is not created)</li>
|
||||
</ul>
|
||||
<pre><code>var name = TempFile.mktemp(".txt", "data_")
|
||||
System.print(name) // e.g., /tmp/data_a1b2c3d4.txt</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">TempFile.mkstemp</span>() → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mkstemp</span>(<span class="param">suffix</span>) → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mkstemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mkstemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Creates a temporary file atomically using exclusive creation flags (<code>O_EXCL</code>), preventing race conditions. Retries up to 100 times on name collisions.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the filename (default: <code>""</code>)</li>
|
||||
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the filename (default: <code>"tmp"</code>)</li>
|
||||
<li><span class="param-name">dir</span> <span class="param-type">(String)</span> - Directory for the file (default: <code>gettempdir()</code>)</li>
|
||||
<li><span class="returns">Returns:</span> The path of the newly created file</li>
|
||||
</ul>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var path = TempFile.mkstemp(".dat", "work_")
|
||||
System.print(path) // e.g., /tmp/work_e5f6a7b8.dat
|
||||
File.delete(path) // Caller must clean up</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">TempFile.mkdtemp</span>() → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mkdtemp</span>(<span class="param">suffix</span>) → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mkdtemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">String</span><br>
|
||||
<span class="method-name">TempFile.mkdtemp</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Creates a temporary directory. Retries up to 100 times on name collisions.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the directory name (default: <code>""</code>)</li>
|
||||
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the directory name (default: <code>"tmp"</code>)</li>
|
||||
<li><span class="param-name">dir</span> <span class="param-type">(String)</span> - Parent directory (default: <code>gettempdir()</code>)</li>
|
||||
<li><span class="returns">Returns:</span> The path of the newly created directory</li>
|
||||
</ul>
|
||||
<pre><code>import "io" for Directory
|
||||
|
||||
var dir = TempFile.mkdtemp("_work")
|
||||
System.print(dir) // e.g., /tmp/tmp9c0d1e2f_work
|
||||
Directory.delete(dir) // Caller must clean up</code></pre>
|
||||
|
||||
<h2>NamedTemporaryFile Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>NamedTemporaryFile</h3>
|
||||
<p>A temporary file with a visible name that is optionally deleted on close</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructors</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">NamedTemporaryFile.new</span>() → <span class="type">NamedTemporaryFile</span><br>
|
||||
<span class="method-name">NamedTemporaryFile.new</span>(<span class="param">suffix</span>) → <span class="type">NamedTemporaryFile</span><br>
|
||||
<span class="method-name">NamedTemporaryFile.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">NamedTemporaryFile</span><br>
|
||||
<span class="method-name">NamedTemporaryFile.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">NamedTemporaryFile</span><br>
|
||||
<span class="method-name">NamedTemporaryFile.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>, <span class="param">delete</span>) → <span class="type">NamedTemporaryFile</span>
|
||||
</div>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the filename (default: <code>""</code>)</li>
|
||||
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the filename (default: <code>"tmp"</code>)</li>
|
||||
<li><span class="param-name">dir</span> <span class="param-type">(String|Null)</span> - Directory for the file (default: <code>null</code>, uses <code>TempFile.gettempdir()</code>)</li>
|
||||
<li><span class="param-name">delete</span> <span class="param-type">(Bool)</span> - Whether to delete the file on close (default: <code>true</code>)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.name</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The full path of the temporary file.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.path</span> → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>A <code>Path</code> object for the temporary file.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.delete</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the file will be deleted on close. Can be set.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.closed</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the file has been closed.</p>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.write</span>(<span class="param">content</span>)
|
||||
</div>
|
||||
<p>Writes string content to the file, replacing any existing content.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.read</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Reads and returns the file content as a string.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.close</span>()
|
||||
</div>
|
||||
<p>Closes the file. If <code>delete</code> is <code>true</code>, the file is deleted from disk.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.use</span>(<span class="param">fn</span>) → <span class="type">*</span>
|
||||
</div>
|
||||
<p>Executes <code>fn</code> with the file as argument and guarantees cleanup afterwards, even if an error occurs. Equivalent to Python's <code>with</code> statement.</p>
|
||||
<pre><code>NamedTemporaryFile.new(".txt").use {|f|
|
||||
f.write("temporary data")
|
||||
System.print(f.read())
|
||||
}
|
||||
// File is automatically deleted here</code></pre>
|
||||
|
||||
<h2>TemporaryDirectory Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>TemporaryDirectory</h3>
|
||||
<p>A temporary directory with recursive cleanup support</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructors</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">TemporaryDirectory.new</span>() → <span class="type">TemporaryDirectory</span><br>
|
||||
<span class="method-name">TemporaryDirectory.new</span>(<span class="param">suffix</span>) → <span class="type">TemporaryDirectory</span><br>
|
||||
<span class="method-name">TemporaryDirectory.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>) → <span class="type">TemporaryDirectory</span><br>
|
||||
<span class="method-name">TemporaryDirectory.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>) → <span class="type">TemporaryDirectory</span><br>
|
||||
<span class="method-name">TemporaryDirectory.new</span>(<span class="param">suffix</span>, <span class="param">prefix</span>, <span class="param">dir</span>, <span class="param">delete</span>) → <span class="type">TemporaryDirectory</span>
|
||||
</div>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">suffix</span> <span class="param-type">(String)</span> - Suffix for the directory name (default: <code>""</code>)</li>
|
||||
<li><span class="param-name">prefix</span> <span class="param-type">(String)</span> - Prefix for the directory name (default: <code>"tmp"</code>)</li>
|
||||
<li><span class="param-name">dir</span> <span class="param-type">(String|Null)</span> - Parent directory (default: <code>null</code>, uses <code>TempFile.gettempdir()</code>)</li>
|
||||
<li><span class="param-name">delete</span> <span class="param-type">(Bool)</span> - Whether to delete the directory on cleanup (default: <code>true</code>)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.name</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The full path of the temporary directory.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.path</span> → <span class="type">Path</span>
|
||||
</div>
|
||||
<p>A <code>Path</code> object for the temporary directory.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.delete</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the directory will be deleted on cleanup. Can be set.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.closed</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether the directory has been cleaned up.</p>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.cleanup</span>()
|
||||
</div>
|
||||
<p>Removes the directory and all its contents recursively (using <code>Path.rmtree()</code>). If <code>delete</code> is <code>false</code>, the directory is not removed.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">.use</span>(<span class="param">fn</span>) → <span class="type">*</span>
|
||||
</div>
|
||||
<p>Executes <code>fn</code> with the directory as argument and guarantees cleanup afterwards, even if an error occurs.</p>
|
||||
<pre><code>TemporaryDirectory.new().use {|d|
|
||||
var path = d.name + "/data.txt"
|
||||
// Create files, do work...
|
||||
}
|
||||
// Directory and all contents automatically removed</code></pre>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Processing Files Safely</h3>
|
||||
<pre><code>import "tempfile" for NamedTemporaryFile
|
||||
import "pathlib" for Path
|
||||
|
||||
NamedTemporaryFile.new(".csv", "report_").use {|tmp|
|
||||
tmp.write("name,value\nalice,42\nbob,17")
|
||||
var data = tmp.read()
|
||||
System.print("Processing %(tmp.name)")
|
||||
System.print(data)
|
||||
}</code></pre>
|
||||
|
||||
<h3>Working Directory for Build Output</h3>
|
||||
<pre><code>import "tempfile" for TemporaryDirectory
|
||||
import "pathlib" for Path
|
||||
|
||||
TemporaryDirectory.new("_build").use {|build|
|
||||
var src = Path.new(build.name) / "output.txt"
|
||||
src.writeText("Build artifact")
|
||||
System.print("Build dir: %(build.name)")
|
||||
}
|
||||
// Build directory cleaned up automatically</code></pre>
|
||||
|
||||
<h3>Persistent Temporary Files</h3>
|
||||
<pre><code>import "tempfile" for NamedTemporaryFile
|
||||
|
||||
var tmp = NamedTemporaryFile.new(".log", "app_", null, false)
|
||||
tmp.write("Application started")
|
||||
tmp.close()
|
||||
// File persists at tmp.name after close
|
||||
System.print("Log file: %(tmp.name)")</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>File creation via <code>mkstemp</code> uses exclusive creation flags (<code>O_EXCL</code>) for atomic file creation, preventing race conditions between checking for existence and creating the file. Random suffixes are generated using <code>Crypto.randomBytes</code> for cryptographic quality randomness.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="pathlib.html" class="prev">pathlib</a>
|
||||
<a href="scheduler.html" class="next">scheduler</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
204
manual/api/timer.html
Normal file
204
manual/api/timer.html
Normal file
@ -0,0 +1,204 @@
|
||||
<!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>timer - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html" class="active">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>timer</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>timer</h1>
|
||||
|
||||
<p>The <code>timer</code> module provides timing functionality for delays and intervals.</p>
|
||||
|
||||
<pre><code>import "timer" for Timer</code></pre>
|
||||
|
||||
<h2>Timer Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Timer</h3>
|
||||
<p>Timer and delay functionality</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Timer.sleep</span>(<span class="param">milliseconds</span>)
|
||||
</div>
|
||||
<p>Pauses execution for the specified duration. The fiber suspends and other operations can proceed.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">milliseconds</span> <span class="param-type">(Num)</span> - Duration to sleep in milliseconds</li>
|
||||
</ul>
|
||||
<pre><code>System.print("Starting...")
|
||||
Timer.sleep(1000) // Wait 1 second
|
||||
System.print("Done!")</code></pre>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Basic Delay</h3>
|
||||
<pre><code>import "timer" for Timer
|
||||
|
||||
System.print("Starting...")
|
||||
Timer.sleep(2000)
|
||||
System.print("2 seconds later...")</code></pre>
|
||||
|
||||
<h3>Polling Loop</h3>
|
||||
<pre><code>import "timer" for Timer
|
||||
import "http" for Http
|
||||
|
||||
var checkStatus = Fn.new {
|
||||
var response = Http.get("https://api.example.com/status")
|
||||
return response.json["ready"]
|
||||
}
|
||||
|
||||
while (!checkStatus.call()) {
|
||||
System.print("Waiting...")
|
||||
Timer.sleep(5000) // Check every 5 seconds
|
||||
}
|
||||
|
||||
System.print("Ready!")</code></pre>
|
||||
|
||||
<h3>Rate Limiting</h3>
|
||||
<pre><code>import "timer" for Timer
|
||||
import "http" for Http
|
||||
|
||||
var urls = [
|
||||
"https://api.example.com/1",
|
||||
"https://api.example.com/2",
|
||||
"https://api.example.com/3"
|
||||
]
|
||||
|
||||
for (url in urls) {
|
||||
var response = Http.get(url)
|
||||
System.print("%(url): %(response.statusCode)")
|
||||
Timer.sleep(1000) // 1 second between requests
|
||||
}</code></pre>
|
||||
|
||||
<h3>Timeout Pattern</h3>
|
||||
<pre><code>import "timer" for Timer
|
||||
import "datetime" for DateTime, Duration
|
||||
|
||||
var start = DateTime.now()
|
||||
var timeout = Duration.fromSeconds(30)
|
||||
|
||||
while (true) {
|
||||
var elapsed = DateTime.now() - start
|
||||
if (elapsed.seconds >= timeout.seconds) {
|
||||
System.print("Timeout!")
|
||||
break
|
||||
}
|
||||
|
||||
// Do work...
|
||||
Timer.sleep(100)
|
||||
}</code></pre>
|
||||
|
||||
<h3>Animation/Progress</h3>
|
||||
<pre><code>import "timer" for Timer
|
||||
|
||||
var frames = ["-", "\\", "|", "/"]
|
||||
var i = 0
|
||||
|
||||
for (step in 1..20) {
|
||||
System.write("\rProcessing %(frames[i]) ")
|
||||
i = (i + 1) % 4
|
||||
Timer.sleep(100)
|
||||
}
|
||||
System.print("\rDone! ")</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Timer.sleep is non-blocking at the event loop level. The current fiber suspends, but other scheduled operations can continue.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="datetime.html" class="prev">datetime</a>
|
||||
<a href="io.html" class="next">io</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
245
manual/api/tls.html
Normal file
245
manual/api/tls.html
Normal file
@ -0,0 +1,245 @@
|
||||
<!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>tls - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html" class="active">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>tls</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>tls</h1>
|
||||
|
||||
<p>The <code>tls</code> module provides SSL/TLS socket support for secure network connections. It uses OpenSSL for encryption and is used internally by the <code>http</code> module for HTTPS connections.</p>
|
||||
|
||||
<pre><code>import "tls" for TlsSocket</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#tlssocket-class">TlsSocket Class</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="tlssocket-class">TlsSocket Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>TlsSocket</h3>
|
||||
<p>SSL/TLS encrypted socket connection</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">TlsSocket.connect</span>(<span class="param">host</span>, <span class="param">port</span>, <span class="param">hostname</span>) → <span class="type">TlsSocket</span>
|
||||
</div>
|
||||
<p>Establishes a TLS connection to a remote server.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - IP address to connect to</li>
|
||||
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number (typically 443 for HTTPS)</li>
|
||||
<li><span class="param-name">hostname</span> <span class="param-type">(String)</span> - Server hostname for SNI (Server Name Indication)</li>
|
||||
<li><span class="returns">Returns:</span> Connected TlsSocket instance</li>
|
||||
</ul>
|
||||
<pre><code>var socket = TlsSocket.connect("93.184.216.34", 443, "example.com")</code></pre>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">write</span>(<span class="param">text</span>)
|
||||
</div>
|
||||
<p>Writes data to the TLS connection. This is an async operation that blocks until the data is sent.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Data to send</li>
|
||||
</ul>
|
||||
<pre><code>socket.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">read</span>() → <span class="type">String|null</span>
|
||||
</div>
|
||||
<p>Reads data from the TLS connection. Blocks until data is available. Returns null when the connection is closed.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="returns">Returns:</span> Data received as a string, or null if connection closed</li>
|
||||
</ul>
|
||||
<pre><code>var data = socket.read()
|
||||
if (data != null) {
|
||||
System.print(data)
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">close</span>()
|
||||
</div>
|
||||
<p>Closes the TLS connection and releases resources.</p>
|
||||
<pre><code>socket.close()</code></pre>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Basic HTTPS Request</h3>
|
||||
<pre><code>import "tls" for TlsSocket
|
||||
import "dns" for Dns
|
||||
|
||||
var host = "example.com"
|
||||
var ip = Dns.lookup(host)
|
||||
var socket = TlsSocket.connect(ip, 443, host)
|
||||
|
||||
socket.write("GET / HTTP/1.1\r\n")
|
||||
socket.write("Host: %(host)\r\n")
|
||||
socket.write("Connection: close\r\n")
|
||||
socket.write("\r\n")
|
||||
|
||||
var response = ""
|
||||
while (true) {
|
||||
var chunk = socket.read()
|
||||
if (chunk == null) break
|
||||
response = response + chunk
|
||||
}
|
||||
|
||||
socket.close()
|
||||
System.print(response)</code></pre>
|
||||
|
||||
<h3>Reading Until Complete</h3>
|
||||
<pre><code>import "tls" for TlsSocket
|
||||
import "dns" for Dns
|
||||
|
||||
var host = "api.example.com"
|
||||
var ip = Dns.lookup(host)
|
||||
var socket = TlsSocket.connect(ip, 443, host)
|
||||
|
||||
socket.write("GET /data HTTP/1.1\r\n")
|
||||
socket.write("Host: %(host)\r\n")
|
||||
socket.write("Accept: application/json\r\n")
|
||||
socket.write("Connection: close\r\n\r\n")
|
||||
|
||||
var buffer = ""
|
||||
while (true) {
|
||||
var data = socket.read()
|
||||
if (data == null) break
|
||||
buffer = buffer + data
|
||||
}
|
||||
|
||||
socket.close()
|
||||
|
||||
var bodyStart = buffer.indexOf("\r\n\r\n")
|
||||
if (bodyStart != -1) {
|
||||
var body = buffer[bodyStart + 4..-1]
|
||||
System.print("Response body: %(body)")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Using with DNS Resolution</h3>
|
||||
<pre><code>import "tls" for TlsSocket
|
||||
import "dns" for Dns
|
||||
|
||||
var hostname = "secure.example.com"
|
||||
|
||||
var ip = Dns.lookup(hostname, 4)
|
||||
System.print("Connecting to %(ip)")
|
||||
|
||||
var socket = TlsSocket.connect(ip, 443, hostname)
|
||||
socket.write("GET /secure-endpoint HTTP/1.1\r\nHost: %(hostname)\r\n\r\n")
|
||||
|
||||
var response = socket.read()
|
||||
System.print(response)
|
||||
|
||||
socket.close()</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>The <code>hostname</code> parameter is used for SNI (Server Name Indication), which is required when connecting to servers that host multiple domains on the same IP address. It should match the domain name in the server's certificate.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>For most use cases, consider using the higher-level <code>http</code> module which handles TLS connections, HTTP protocol details, and response parsing automatically.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="websocket.html" class="prev">websocket</a>
|
||||
<a href="net.html" class="next">net</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
226
manual/api/uuid.html
Normal file
226
manual/api/uuid.html
Normal file
@ -0,0 +1,226 @@
|
||||
<!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>uuid - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html" class="active">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>uuid</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>uuid</h1>
|
||||
|
||||
<p>The <code>uuid</code> module provides UUID (Universally Unique Identifier) generation and validation. It supports version 4 UUIDs which are randomly generated.</p>
|
||||
|
||||
<pre><code>import "uuid" for Uuid</code></pre>
|
||||
|
||||
<h2>Uuid Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Uuid</h3>
|
||||
<p>UUID generation and validation</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Uuid.v4</span>() → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Generates a new random version 4 UUID.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="returns">Returns:</span> A 36-character UUID string in the format <code>xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx</code></li>
|
||||
</ul>
|
||||
<pre><code>var id = Uuid.v4()
|
||||
System.print(id) // e.g., "550e8400-e29b-41d4-a716-446655440000"</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Uuid.isValid</span>(<span class="param">string</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks if a string is a valid UUID format (any version).</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to validate</li>
|
||||
<li><span class="returns">Returns:</span> <code>true</code> if the string is a valid UUID format, <code>false</code> otherwise</li>
|
||||
</ul>
|
||||
<pre><code>System.print(Uuid.isValid("550e8400-e29b-41d4-a716-446655440000")) // true
|
||||
System.print(Uuid.isValid("not-a-uuid")) // false
|
||||
System.print(Uuid.isValid("550e8400e29b41d4a716446655440000")) // false (missing hyphens)</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Uuid.isV4</span>(<span class="param">string</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks if a string is a valid version 4 UUID.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to validate</li>
|
||||
<li><span class="returns">Returns:</span> <code>true</code> if the string is a valid v4 UUID, <code>false</code> otherwise</li>
|
||||
</ul>
|
||||
<pre><code>var id = Uuid.v4()
|
||||
System.print(Uuid.isV4(id)) // true
|
||||
|
||||
System.print(Uuid.isV4("550e8400-e29b-11d4-a716-446655440000")) // false (version 1)</code></pre>
|
||||
|
||||
<h2>UUID Format</h2>
|
||||
|
||||
<p>UUIDs are 128-bit identifiers represented as 32 hexadecimal characters separated by hyphens into five groups:</p>
|
||||
|
||||
<pre><code>xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
|
||||
8 4 4 4 12 = 32 hex chars + 4 hyphens = 36 chars</code></pre>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Position</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>M</td>
|
||||
<td>Version number (4 for v4 UUIDs)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>N</td>
|
||||
<td>Variant (8, 9, a, or b for RFC 4122 UUIDs)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Generating Unique Identifiers</h3>
|
||||
<pre><code>import "uuid" for Uuid
|
||||
|
||||
var userId = Uuid.v4()
|
||||
var sessionId = Uuid.v4()
|
||||
|
||||
System.print("User ID: %(userId)")
|
||||
System.print("Session ID: %(sessionId)")</code></pre>
|
||||
|
||||
<h3>Validating User Input</h3>
|
||||
<pre><code>import "uuid" for Uuid
|
||||
|
||||
var input = "550e8400-e29b-41d4-a716-446655440000"
|
||||
|
||||
if (Uuid.isValid(input)) {
|
||||
System.print("Valid UUID")
|
||||
if (Uuid.isV4(input)) {
|
||||
System.print("This is a v4 UUID")
|
||||
}
|
||||
} else {
|
||||
System.print("Invalid UUID format")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Using with Database Records</h3>
|
||||
<pre><code>import "uuid" for Uuid
|
||||
|
||||
class User {
|
||||
construct new(name) {
|
||||
_id = Uuid.v4()
|
||||
_name = name
|
||||
}
|
||||
|
||||
id { _id }
|
||||
name { _name }
|
||||
}
|
||||
|
||||
var user = User.new("Alice")
|
||||
System.print("Created user %(user.name) with ID %(user.id)")</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Version 4 UUIDs are generated using cryptographically secure random bytes from the <code>crypto</code> module. The probability of generating duplicate UUIDs is astronomically low.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="math.html" class="prev">math</a>
|
||||
<a href="html.html" class="next">html</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
413
manual/api/wdantic.html
Normal file
413
manual/api/wdantic.html
Normal file
@ -0,0 +1,413 @@
|
||||
<!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>wdantic - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html" class="active">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html">web</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>wdantic</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>wdantic</h1>
|
||||
|
||||
<p>The <code>wdantic</code> module provides data validation similar to Python's Pydantic. It includes standalone validators and a schema-based validation system for structured data.</p>
|
||||
|
||||
<pre><code>import "wdantic" for Validator, Field, Schema, ValidationResult</code></pre>
|
||||
|
||||
<h2>Validator Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Validator</h3>
|
||||
<p>Standalone validation functions</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.email</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Validates email address format.</p>
|
||||
<pre><code>Validator.email("user@example.com") // true
|
||||
Validator.email("invalid") // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.domain</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Validates domain name format.</p>
|
||||
<pre><code>Validator.domain("example.com") // true
|
||||
Validator.domain("localhost") // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.url</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Validates HTTP/HTTPS URL format.</p>
|
||||
<pre><code>Validator.url("https://example.com/path") // true
|
||||
Validator.url("ftp://example.com") // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.uuid</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Validates UUID format.</p>
|
||||
<pre><code>Validator.uuid("550e8400-e29b-41d4-a716-446655440000") // true</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.safeStr</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks if string contains only printable ASCII characters (32-126).</p>
|
||||
<pre><code>Validator.safeStr("Hello World") // true
|
||||
Validator.safeStr("Hello\x00") // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.base64</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Validates Base64 encoding format.</p>
|
||||
<pre><code>Validator.base64("SGVsbG8=") // true
|
||||
Validator.base64("invalid!") // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.json</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks if string is valid JSON.</p>
|
||||
<pre><code>Validator.json("{\"key\": \"value\"}") // true
|
||||
Validator.json("{invalid}") // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.ipv4</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Validates IPv4 address format.</p>
|
||||
<pre><code>Validator.ipv4("192.168.1.1") // true
|
||||
Validator.ipv4("256.0.0.1") // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.minLength</span>(<span class="param">value</span>, <span class="param">min</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks minimum length of string or list.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.maxLength</span>(<span class="param">value</span>, <span class="param">max</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks maximum length of string or list.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.range</span>(<span class="param">value</span>, <span class="param">min</span>, <span class="param">max</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks if number is within range (inclusive).</p>
|
||||
<pre><code>Validator.range(5, 1, 10) // true
|
||||
Validator.range(15, 1, 10) // false</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.positive</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks if number is positive (greater than 0).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.negative</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks if number is negative (less than 0).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.integer</span>(<span class="param">value</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Checks if number is an integer (no decimal part).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Validator.regex</span>(<span class="param">value</span>, <span class="param">pattern</span>) → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Tests if value matches a regular expression pattern.</p>
|
||||
<pre><code>Validator.regex("abc123", "^[a-z]+[0-9]+$") // true</code></pre>
|
||||
|
||||
<h2>Field Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Field</h3>
|
||||
<p>Factory for creating field definitions</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.string</span>() → <span class="type">StringField</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.string</span>(<span class="param">options</span>) → <span class="type">StringField</span>
|
||||
</div>
|
||||
<p>Creates a string field. Options: <code>minLength</code>, <code>maxLength</code>, <code>pattern</code>, <code>required</code>, <code>default</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.integer</span>() → <span class="type">IntegerField</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.integer</span>(<span class="param">options</span>) → <span class="type">IntegerField</span>
|
||||
</div>
|
||||
<p>Creates an integer field. Options: <code>min</code>, <code>max</code>, <code>required</code>, <code>default</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.number</span>() → <span class="type">NumberField</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.number</span>(<span class="param">options</span>) → <span class="type">NumberField</span>
|
||||
</div>
|
||||
<p>Creates a number field (integer or float). Options: <code>min</code>, <code>max</code>, <code>required</code>, <code>default</code>.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.email</span>() → <span class="type">EmailField</span>
|
||||
</div>
|
||||
<p>Creates a field that validates email format.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.boolean</span>() → <span class="type">BooleanField</span>
|
||||
</div>
|
||||
<p>Creates a boolean field.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.list</span>(<span class="param">itemType</span>) → <span class="type">ListField</span>
|
||||
</div>
|
||||
<p>Creates a list field with typed items.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.map</span>() → <span class="type">MapField</span>
|
||||
</div>
|
||||
<p>Creates a map/object field.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Field.optional</span>(<span class="param">fieldDef</span>) → <span class="type">OptionalField</span>
|
||||
</div>
|
||||
<p>Makes any field optional (not required).</p>
|
||||
|
||||
<h2>Schema Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Schema</h3>
|
||||
<p>Validates data against a field definition map</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructor</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Schema.new</span>(<span class="param">definition</span>) → <span class="type">Schema</span>
|
||||
</div>
|
||||
<p>Creates a schema from a map of field names to field definitions.</p>
|
||||
|
||||
<h3>Instance Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">validate</span>(<span class="param">data</span>) → <span class="type">ValidationResult</span>
|
||||
</div>
|
||||
<p>Validates data against the schema.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">validateOrAbort</span>(<span class="param">data</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Validates data and aborts on failure. Returns validated data on success.</p>
|
||||
|
||||
<h2>ValidationResult Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>ValidationResult</h3>
|
||||
<p>Result of schema validation</p>
|
||||
</div>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isValid</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>Whether validation passed.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">errors</span> → <span class="type">List</span>
|
||||
</div>
|
||||
<p>List of ValidationError objects.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">data</span> → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Validated and coerced data (null if invalid).</p>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Basic Schema Validation</h3>
|
||||
<pre><code>import "wdantic" for Field, Schema
|
||||
|
||||
var userSchema = Schema.new({
|
||||
"name": Field.string({"minLength": 1, "maxLength": 100}),
|
||||
"email": Field.email(),
|
||||
"age": Field.integer({"min": 0, "max": 150})
|
||||
})
|
||||
|
||||
var result = userSchema.validate({
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com",
|
||||
"age": 30
|
||||
})
|
||||
|
||||
if (result.isValid) {
|
||||
System.print("Valid: %(result.data)")
|
||||
} else {
|
||||
for (error in result.errors) {
|
||||
System.print("Error: %(error)")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Optional Fields</h3>
|
||||
<pre><code>import "wdantic" for Field, Schema
|
||||
|
||||
var schema = Schema.new({
|
||||
"title": Field.string(),
|
||||
"description": Field.optional(Field.string()),
|
||||
"tags": Field.optional(Field.list(Field.string()))
|
||||
})
|
||||
|
||||
var result = schema.validate({
|
||||
"title": "My Post"
|
||||
})
|
||||
|
||||
System.print(result.isValid) // true</code></pre>
|
||||
|
||||
<h3>Nested Lists</h3>
|
||||
<pre><code>import "wdantic" for Field, Schema
|
||||
|
||||
var schema = Schema.new({
|
||||
"numbers": Field.list(Field.integer({"min": 0}))
|
||||
})
|
||||
|
||||
var result = schema.validate({
|
||||
"numbers": [1, 2, 3, -1] // -1 will fail
|
||||
})
|
||||
|
||||
System.print(result.isValid) // false</code></pre>
|
||||
|
||||
<h3>Using validateOrAbort</h3>
|
||||
<pre><code>import "wdantic" for Field, Schema
|
||||
|
||||
var schema = Schema.new({
|
||||
"username": Field.string({"minLength": 3}),
|
||||
"password": Field.string({"minLength": 8})
|
||||
})
|
||||
|
||||
var data = schema.validateOrAbort({
|
||||
"username": "admin",
|
||||
"password": "secret123"
|
||||
})</code></pre>
|
||||
|
||||
<h3>Standalone Validators</h3>
|
||||
<pre><code>import "wdantic" for Validator
|
||||
|
||||
var email = "user@example.com"
|
||||
if (Validator.email(email)) {
|
||||
System.print("Valid email")
|
||||
}
|
||||
|
||||
var ip = "192.168.1.1"
|
||||
if (Validator.ipv4(ip)) {
|
||||
System.print("Valid IP address")
|
||||
}
|
||||
|
||||
var jsonStr = "{\"key\": 123}"
|
||||
if (Validator.json(jsonStr)) {
|
||||
System.print("Valid JSON")
|
||||
}</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Field types automatically coerce values when possible. For example, <code>IntegerField</code> will convert the string <code>"42"</code> to the number <code>42</code>.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="argparse.html" class="prev">argparse</a>
|
||||
<a href="dataset.html" class="next">dataset</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
553
manual/api/web.html
Normal file
553
manual/api/web.html
Normal file
@ -0,0 +1,553 @@
|
||||
<!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>web - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</a></li>
|
||||
<li><a href="uuid.html">uuid</a></li>
|
||||
<li><a href="html.html">html</a></li>
|
||||
<li><a href="argparse.html">argparse</a></li>
|
||||
<li><a href="wdantic.html">wdantic</a></li>
|
||||
<li><a href="dataset.html">dataset</a></li>
|
||||
<li><a href="markdown.html">markdown</a></li>
|
||||
<li><a href="web.html" class="active">web</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="../tutorials/index.html">Tutorial List</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>
|
||||
</ul>
|
||||
</div>
|
||||
</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>web</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>web</h1>
|
||||
|
||||
<p>The <code>web</code> module provides a web framework for building HTTP servers and a client for making HTTP requests. It includes routing, middleware, sessions, static file serving, and class-based views.</p>
|
||||
|
||||
<pre><code>import "web" for Application, Router, Request, Response, View, Session, Client</code></pre>
|
||||
|
||||
<h2>Application Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Application</h3>
|
||||
<p>HTTP server with routing and middleware</p>
|
||||
</div>
|
||||
|
||||
<h3>Constructor</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Application.new</span>() → <span class="type">Application</span>
|
||||
</div>
|
||||
<p>Creates a new web application.</p>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">router</span> → <span class="type">Router</span>
|
||||
</div>
|
||||
<p>Access to the underlying router.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">sessionStore</span> → <span class="type">SessionStore</span>
|
||||
</div>
|
||||
<p>Access to the session storage.</p>
|
||||
|
||||
<h3>Routing Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">get</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">post</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">put</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">delete</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">patch</span>(<span class="param">path</span>, <span class="param">handler</span>) → <span class="type">Application</span>
|
||||
</div>
|
||||
<p>Register route handlers. Handler receives <code>Request</code> and returns <code>Response</code>.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">path</span> <span class="param-type">(String)</span> - URL path with optional parameters (<code>:param</code>) or wildcards (<code>*</code>)</li>
|
||||
<li><span class="param-name">handler</span> <span class="param-type">(Fn)</span> - Function receiving Request, returning Response</li>
|
||||
</ul>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">addView</span>(<span class="param">path</span>, <span class="param">viewClass</span>) → <span class="type">Application</span>
|
||||
</div>
|
||||
<p>Registers a class-based view for all HTTP methods.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">static_</span>(<span class="param">prefix</span>, <span class="param">directory</span>) → <span class="type">Application</span>
|
||||
</div>
|
||||
<p>Serves static files from a directory.</p>
|
||||
<pre><code>app.static_("/assets", "./public")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">use</span>(<span class="param">middleware</span>) → <span class="type">Application</span>
|
||||
</div>
|
||||
<p>Adds middleware function. Middleware receives Request, returns Response or null to continue.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">run</span>(<span class="param">host</span>, <span class="param">port</span>)
|
||||
</div>
|
||||
<p>Starts the server (blocking).</p>
|
||||
|
||||
<h2>Request Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Request</h3>
|
||||
<p>Incoming HTTP request</p>
|
||||
</div>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>method</code></td>
|
||||
<td>String</td>
|
||||
<td>HTTP method (GET, POST, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>path</code></td>
|
||||
<td>String</td>
|
||||
<td>Request path without query string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>query</code></td>
|
||||
<td>Map</td>
|
||||
<td>Parsed query parameters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>headers</code></td>
|
||||
<td>Map</td>
|
||||
<td>Request headers</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>body</code></td>
|
||||
<td>String</td>
|
||||
<td>Raw request body</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>params</code></td>
|
||||
<td>Map</td>
|
||||
<td>Route parameters from URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>cookies</code></td>
|
||||
<td>Map</td>
|
||||
<td>Parsed cookies</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>session</code></td>
|
||||
<td>Session</td>
|
||||
<td>Session data</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">header</span>(<span class="param">name</span>) → <span class="type">String|null</span>
|
||||
</div>
|
||||
<p>Gets header value (case-insensitive).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">json</span> → <span class="type">Map|List</span>
|
||||
</div>
|
||||
<p>Parses body as JSON (cached).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">form</span> → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Parses body as form data (cached).</p>
|
||||
|
||||
<h2>Response Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Response</h3>
|
||||
<p>HTTP response builder</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Factory Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Response.text</span>(<span class="param">content</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<p>Creates plain text response.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Response.html</span>(<span class="param">content</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<p>Creates HTML response.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Response.json</span>(<span class="param">data</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<p>Creates JSON response (automatically stringifies).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Response.redirect</span>(<span class="param">url</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Response.redirect</span>(<span class="param">url</span>, <span class="param">status</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<p>Creates redirect response (default 302).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Response.file</span>(<span class="param">path</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Response.file</span>(<span class="param">path</span>, <span class="param">contentType</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<p>Serves a file. Auto-detects content type if not specified.</p>
|
||||
|
||||
<h3>Instance Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">header</span>(<span class="param">name</span>, <span class="param">value</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<p>Sets a response header.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">cookie</span>(<span class="param">name</span>, <span class="param">value</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">cookie</span>(<span class="param">name</span>, <span class="param">value</span>, <span class="param">options</span>) → <span class="type">Response</span>
|
||||
</div>
|
||||
<p>Sets a cookie. Options: <code>path</code>, <code>maxAge</code>, <code>httpOnly</code>, <code>secure</code>.</p>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">status</span> → <span class="type">Num</span>
|
||||
</div>
|
||||
<p>Gets or sets HTTP status code.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">body</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>Gets or sets response body.</p>
|
||||
|
||||
<h2>View Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>View</h3>
|
||||
<p>Base class for class-based views</p>
|
||||
</div>
|
||||
|
||||
<p>Subclass and override methods for each HTTP method:</p>
|
||||
<pre><code>class UserView is View {
|
||||
get(request) {
|
||||
return Response.json({"users": []})
|
||||
}
|
||||
|
||||
post(request) {
|
||||
var data = request.json
|
||||
return Response.json({"created": data})
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Session Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Session</h3>
|
||||
<p>Session data storage</p>
|
||||
</div>
|
||||
|
||||
<h3>Properties and Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">id</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The session ID.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">[key]</span> → <span class="type">any</span>
|
||||
</div>
|
||||
<p>Gets session value.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">[key]=</span>(<span class="param">value</span>)
|
||||
</div>
|
||||
<p>Sets session value.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">remove</span>(<span class="param">key</span>)
|
||||
</div>
|
||||
<p>Removes a session key.</p>
|
||||
|
||||
<h2>Client Class</h2>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>Client</h3>
|
||||
<p>HTTP client for making requests</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Client.get</span>(<span class="param">url</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Client.get</span>(<span class="param">url</span>, <span class="param">options</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Makes GET request.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Client.post</span>(<span class="param">url</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Client.post</span>(<span class="param">url</span>, <span class="param">options</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Makes POST request.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Client.put</span>(<span class="param">url</span>, <span class="param">options</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Makes PUT request.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">Client.delete</span>(<span class="param">url</span>, <span class="param">options</span>) → <span class="type">Map</span>
|
||||
</div>
|
||||
<p>Makes DELETE request.</p>
|
||||
|
||||
<h3>Client Options</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>headers</code></td>
|
||||
<td>Map</td>
|
||||
<td>Request headers</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>body</code></td>
|
||||
<td>String</td>
|
||||
<td>Request body</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>json</code></td>
|
||||
<td>Map/List</td>
|
||||
<td>JSON body (auto-stringifies and sets Content-Type)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Response Format</h3>
|
||||
<pre><code>{
|
||||
"status": 200,
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
"body": "{...}"
|
||||
}</code></pre>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Basic Server</h3>
|
||||
<pre><code>import "web" for Application, Response
|
||||
|
||||
var app = Application.new()
|
||||
|
||||
app.get("/", Fn.new { |req|
|
||||
return Response.html("<h1>Hello World</h1>")
|
||||
})
|
||||
|
||||
app.get("/api/users", Fn.new { |req|
|
||||
return Response.json({"users": ["Alice", "Bob"]})
|
||||
})
|
||||
|
||||
app.run("0.0.0.0", 8080)</code></pre>
|
||||
|
||||
<h3>Route Parameters</h3>
|
||||
<pre><code>import "web" for Application, Response
|
||||
|
||||
var app = Application.new()
|
||||
|
||||
app.get("/users/:id", Fn.new { |req|
|
||||
var userId = req.params["id"]
|
||||
return Response.json({"id": userId})
|
||||
})
|
||||
|
||||
app.get("/files/*", Fn.new { |req|
|
||||
return Response.text("Wildcard route")
|
||||
})
|
||||
|
||||
app.run("0.0.0.0", 8080)</code></pre>
|
||||
|
||||
<h3>Class-Based Views</h3>
|
||||
<pre><code>import "web" for Application, Response, View
|
||||
|
||||
class ArticleView is View {
|
||||
get(request) {
|
||||
return Response.json({"articles": []})
|
||||
}
|
||||
|
||||
post(request) {
|
||||
var data = request.json
|
||||
return Response.json({"created": data})
|
||||
}
|
||||
}
|
||||
|
||||
var app = Application.new()
|
||||
app.addView("/articles", ArticleView)
|
||||
app.run("0.0.0.0", 8080)</code></pre>
|
||||
|
||||
<h3>Sessions</h3>
|
||||
<pre><code>import "web" for Application, Response
|
||||
|
||||
var app = Application.new()
|
||||
|
||||
app.get("/login", Fn.new { |req|
|
||||
req.session["user"] = "Alice"
|
||||
return Response.text("Logged in")
|
||||
})
|
||||
|
||||
app.get("/profile", Fn.new { |req|
|
||||
var user = req.session["user"]
|
||||
if (user == null) {
|
||||
return Response.redirect("/login")
|
||||
}
|
||||
return Response.text("Hello, %(user)")
|
||||
})
|
||||
|
||||
app.run("0.0.0.0", 8080)</code></pre>
|
||||
|
||||
<h3>Middleware</h3>
|
||||
<pre><code>import "web" for Application, Response
|
||||
|
||||
var app = Application.new()
|
||||
|
||||
app.use(Fn.new { |req|
|
||||
System.print("%(req.method) %(req.path)")
|
||||
return null
|
||||
})
|
||||
|
||||
app.use(Fn.new { |req|
|
||||
if (req.path.startsWith("/admin") && !req.session["isAdmin"]) {
|
||||
return Response.redirect("/login")
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
app.get("/", Fn.new { |req|
|
||||
return Response.text("Home")
|
||||
})
|
||||
|
||||
app.run("0.0.0.0", 8080)</code></pre>
|
||||
|
||||
<h3>HTTP Client</h3>
|
||||
<pre><code>import "web" for Client
|
||||
import "json" for Json
|
||||
|
||||
var response = Client.get("https://api.example.com/data")
|
||||
System.print("Status: %(response["status"])")
|
||||
System.print("Body: %(response["body"])")
|
||||
|
||||
var postResponse = Client.post("https://api.example.com/users", {
|
||||
"json": {"name": "Alice", "email": "alice@example.com"}
|
||||
})
|
||||
|
||||
var data = Json.parse(postResponse["body"])</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Sessions are stored in memory by default. Data is lost when the server restarts. For production, consider persisting session data to a database.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="markdown.html" class="prev">markdown</a>
|
||||
<a href="index.html" class="next">Overview</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
511
manual/api/websocket.html
Normal file
511
manual/api/websocket.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>websocket - 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="index.html">Overview</a></li>
|
||||
<li><a href="string.html">String</a></li>
|
||||
<li><a href="number.html">Num</a></li>
|
||||
<li><a href="http.html">http</a></li>
|
||||
<li><a href="websocket.html" class="active">websocket</a></li>
|
||||
<li><a href="tls.html">tls</a></li>
|
||||
<li><a href="net.html">net</a></li>
|
||||
<li><a href="dns.html">dns</a></li>
|
||||
<li><a href="json.html">json</a></li>
|
||||
<li><a href="base64.html">base64</a></li>
|
||||
<li><a href="regex.html">regex</a></li>
|
||||
<li><a href="jinja.html">jinja</a></li>
|
||||
<li><a href="crypto.html">crypto</a></li>
|
||||
<li><a href="os.html">os</a></li>
|
||||
<li><a href="env.html">env</a></li>
|
||||
<li><a href="signal.html">signal</a></li>
|
||||
<li><a href="subprocess.html">subprocess</a></li>
|
||||
<li><a href="sqlite.html">sqlite</a></li>
|
||||
<li><a href="datetime.html">datetime</a></li>
|
||||
<li><a href="timer.html">timer</a></li>
|
||||
<li><a href="io.html">io</a></li>
|
||||
<li><a href="pathlib.html">pathlib</a></li>
|
||||
<li><a href="scheduler.html">scheduler</a></li>
|
||||
<li><a href="math.html">math</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>
|
||||
</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>
|
||||
</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>websocket</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>websocket</h1>
|
||||
|
||||
<p>The <code>websocket</code> module provides WebSocket client and server functionality implementing the RFC 6455 protocol. It supports both <code>ws://</code> and <code>wss://</code> (secure) connections.</p>
|
||||
|
||||
<pre><code>import "websocket" for WebSocket, WebSocketServer, WebSocketMessage</code></pre>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#websocket-class">WebSocket Class</a></li>
|
||||
<li><a href="#websocketserver-class">WebSocketServer Class</a></li>
|
||||
<li><a href="#websocketmessage-class">WebSocketMessage Class</a></li>
|
||||
<li><a href="#opcodes">Frame Opcodes</a></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="websocket-class">WebSocket Class</h2>
|
||||
<p>WebSocket client for connecting to WebSocket servers.</p>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>WebSocket</h3>
|
||||
<p>WebSocket client connection</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">WebSocket.connect</span>(<span class="param">url</span>) → <span class="type">WebSocket</span>
|
||||
</div>
|
||||
<p>Connects to a WebSocket server.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - WebSocket URL (ws:// or wss://)</li>
|
||||
<li><span class="returns">Returns:</span> Connected WebSocket instance</li>
|
||||
</ul>
|
||||
<pre><code>var ws = WebSocket.connect("ws://echo.websocket.org")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">WebSocket.connect</span>(<span class="param">url</span>, <span class="param">headers</span>) → <span class="type">WebSocket</span>
|
||||
</div>
|
||||
<p>Connects with custom headers (useful for authentication).</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">url</span> <span class="param-type">(String)</span> - WebSocket URL</li>
|
||||
<li><span class="param-name">headers</span> <span class="param-type">(Map)</span> - Custom HTTP headers for handshake</li>
|
||||
</ul>
|
||||
<pre><code>var headers = {"Authorization": "Bearer token123"}
|
||||
var ws = WebSocket.connect("wss://api.example.com/ws", headers)</code></pre>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">url</span> → <span class="type">String</span>
|
||||
</div>
|
||||
<p>The URL this WebSocket is connected to.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">isOpen</span> → <span class="type">Bool</span>
|
||||
</div>
|
||||
<p>True if the connection is still open.</p>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">send</span>(<span class="param">text</span>)
|
||||
</div>
|
||||
<p>Sends a text message to the server.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - Text message to send</li>
|
||||
</ul>
|
||||
<pre><code>ws.send("Hello, server!")</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">sendBinary</span>(<span class="param">bytes</span>)
|
||||
</div>
|
||||
<p>Sends a binary message to the server.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">bytes</span> <span class="param-type">(List)</span> - List of bytes to send</li>
|
||||
</ul>
|
||||
<pre><code>ws.sendBinary([0x01, 0x02, 0x03, 0x04])</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">receive</span>() → <span class="type">WebSocketMessage|null</span>
|
||||
</div>
|
||||
<p>Receives the next message from the server. Blocks until a message arrives. Returns null if the connection is closed.</p>
|
||||
<pre><code>var msg = ws.receive()
|
||||
if (msg != null && msg.isText) {
|
||||
System.print("Got: %(msg.text)")
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">ping</span>()
|
||||
</div>
|
||||
<p>Sends a ping frame to the server.</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">ping</span>(<span class="param">data</span>)
|
||||
</div>
|
||||
<p>Sends a ping frame with payload data (max 125 bytes).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">pong</span>(<span class="param">data</span>)
|
||||
</div>
|
||||
<p>Sends a pong frame (usually automatic in response to ping).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">close</span>()
|
||||
</div>
|
||||
<p>Closes the connection with status code 1000 (normal closure).</p>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">close</span>(<span class="param">code</span>, <span class="param">reason</span>)
|
||||
</div>
|
||||
<p>Closes the connection with a custom status code and reason.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">code</span> <span class="param-type">(Num)</span> - Close status code (1000-4999)</li>
|
||||
<li><span class="param-name">reason</span> <span class="param-type">(String)</span> - Human-readable close reason</li>
|
||||
</ul>
|
||||
<pre><code>ws.close(1000, "Goodbye")</code></pre>
|
||||
|
||||
<h2 id="websocketserver-class">WebSocketServer Class</h2>
|
||||
<p>WebSocket server for accepting client connections.</p>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>WebSocketServer</h3>
|
||||
<p>WebSocket server</p>
|
||||
</div>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">WebSocketServer.bind</span>(<span class="param">host</span>, <span class="param">port</span>) → <span class="type">WebSocketServer</span>
|
||||
</div>
|
||||
<p>Creates a WebSocket server listening on the specified host and port.</p>
|
||||
<ul class="param-list">
|
||||
<li><span class="param-name">host</span> <span class="param-type">(String)</span> - Host to bind to (e.g., "0.0.0.0", "127.0.0.1")</li>
|
||||
<li><span class="param-name">port</span> <span class="param-type">(Num)</span> - Port number</li>
|
||||
</ul>
|
||||
<pre><code>var server = WebSocketServer.bind("0.0.0.0", 8080)</code></pre>
|
||||
|
||||
<h3>Methods</h3>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">accept</span>() → <span class="type">WebSocket|null</span>
|
||||
</div>
|
||||
<p>Accepts and upgrades an incoming connection. Blocks until a client connects. Returns a WebSocket instance for the connected client.</p>
|
||||
<pre><code>var client = server.accept()
|
||||
if (client != null) {
|
||||
System.print("Client connected!")
|
||||
}</code></pre>
|
||||
|
||||
<div class="method-signature">
|
||||
<span class="method-name">close</span>()
|
||||
</div>
|
||||
<p>Stops the server and closes the listening socket.</p>
|
||||
|
||||
<h2 id="websocketmessage-class">WebSocketMessage Class</h2>
|
||||
<p>Represents a WebSocket frame received from a connection.</p>
|
||||
|
||||
<div class="class-header">
|
||||
<h3>WebSocketMessage</h3>
|
||||
<p>WebSocket message/frame</p>
|
||||
</div>
|
||||
|
||||
<h3>Properties</h3>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>opcode</code></td>
|
||||
<td>Num</td>
|
||||
<td>Frame opcode (1=text, 2=binary, 8=close, 9=ping, 10=pong)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>payload</code></td>
|
||||
<td>List</td>
|
||||
<td>Raw payload bytes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fin</code></td>
|
||||
<td>Bool</td>
|
||||
<td>True if this is the final fragment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>isText</code></td>
|
||||
<td>Bool</td>
|
||||
<td>True if this is a text frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>isBinary</code></td>
|
||||
<td>Bool</td>
|
||||
<td>True if this is a binary frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>isClose</code></td>
|
||||
<td>Bool</td>
|
||||
<td>True if this is a close frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>isPing</code></td>
|
||||
<td>Bool</td>
|
||||
<td>True if this is a ping frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>isPong</code></td>
|
||||
<td>Bool</td>
|
||||
<td>True if this is a pong frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>text</code></td>
|
||||
<td>String</td>
|
||||
<td>Payload as string (only for text frames)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>bytes</code></td>
|
||||
<td>List</td>
|
||||
<td>Payload as byte list</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>closeCode</code></td>
|
||||
<td>Num</td>
|
||||
<td>Close status code (only for close frames)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>closeReason</code></td>
|
||||
<td>String</td>
|
||||
<td>Close reason text (only for close frames)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2 id="opcodes">Frame Opcodes</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Opcode</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>Continuation</td>
|
||||
<td>Continuation of fragmented message</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>Text</td>
|
||||
<td>UTF-8 text data</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Binary</td>
|
||||
<td>Binary data</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>Close</td>
|
||||
<td>Connection close</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Ping</td>
|
||||
<td>Ping (keepalive)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Pong</td>
|
||||
<td>Pong response</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Common Close Codes</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1000</td>
|
||||
<td>Normal closure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1001</td>
|
||||
<td>Going away (server shutdown)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1002</td>
|
||||
<td>Protocol error</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1003</td>
|
||||
<td>Unsupported data type</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1005</td>
|
||||
<td>No status received</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1006</td>
|
||||
<td>Abnormal closure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1011</td>
|
||||
<td>Server error</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2 id="examples">Examples</h2>
|
||||
|
||||
<h3>Echo Client</h3>
|
||||
<pre><code>import "websocket" for WebSocket
|
||||
|
||||
var ws = WebSocket.connect("ws://echo.websocket.org")
|
||||
|
||||
ws.send("Hello, WebSocket!")
|
||||
|
||||
var msg = ws.receive()
|
||||
if (msg != null && msg.isText) {
|
||||
System.print("Echo: %(msg.text)")
|
||||
}
|
||||
|
||||
ws.close()</code></pre>
|
||||
|
||||
<h3>Chat Client</h3>
|
||||
<pre><code>import "websocket" for WebSocket
|
||||
import "json" for Json
|
||||
|
||||
var ws = WebSocket.connect("wss://chat.example.com/socket")
|
||||
|
||||
ws.send(Json.stringify({
|
||||
"type": "join",
|
||||
"room": "general",
|
||||
"username": "alice"
|
||||
}))
|
||||
|
||||
while (ws.isOpen) {
|
||||
var msg = ws.receive()
|
||||
if (msg == null) break
|
||||
|
||||
if (msg.isClose) {
|
||||
System.print("Server closed: %(msg.closeCode) %(msg.closeReason)")
|
||||
break
|
||||
}
|
||||
|
||||
if (msg.isText) {
|
||||
var data = Json.parse(msg.text)
|
||||
if (data["type"] == "message") {
|
||||
System.print("%(data["from"]): %(data["text"])")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ws.close()</code></pre>
|
||||
|
||||
<h3>WebSocket Server</h3>
|
||||
<pre><code>import "websocket" for WebSocketServer
|
||||
|
||||
var server = WebSocketServer.bind("0.0.0.0", 8080)
|
||||
System.print("WebSocket server listening on port 8080")
|
||||
|
||||
while (true) {
|
||||
var client = server.accept()
|
||||
if (client == null) continue
|
||||
|
||||
System.print("Client connected")
|
||||
|
||||
while (client.isOpen) {
|
||||
var msg = client.receive()
|
||||
if (msg == null) break
|
||||
|
||||
if (msg.isClose) {
|
||||
System.print("Client disconnected")
|
||||
break
|
||||
}
|
||||
|
||||
if (msg.isText) {
|
||||
System.print("Received: %(msg.text)")
|
||||
client.send("Echo: %(msg.text)")
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Secure WebSocket (WSS)</h3>
|
||||
<pre><code>import "websocket" for WebSocket
|
||||
|
||||
var ws = WebSocket.connect("wss://secure.example.com/socket")
|
||||
|
||||
ws.send("Secure message")
|
||||
var response = ws.receive()
|
||||
System.print(response.text)
|
||||
|
||||
ws.close()</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Ping frames are automatically responded to with pong frames. You typically do not need to handle ping/pong manually unless implementing custom keepalive logic.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="http.html" class="prev">http</a>
|
||||
<a href="tls.html" class="next">tls</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
727
manual/css/style.css
Normal file
727
manual/css/style.css
Normal file
@ -0,0 +1,727 @@
|
||||
/* retoor <retoor@molodetz.nl> */
|
||||
|
||||
:root {
|
||||
--sidebar-bg: #343131;
|
||||
--sidebar-text: #d9d9d9;
|
||||
--sidebar-link: #b3b3b3;
|
||||
--sidebar-link-hover: #ffffff;
|
||||
--sidebar-active: #2980b9;
|
||||
--primary: #2980b9;
|
||||
--primary-dark: #1a5276;
|
||||
--text: #404040;
|
||||
--heading: #2c3e50;
|
||||
--code-bg: #f4f4f4;
|
||||
--code-border: #e1e4e5;
|
||||
--border: #e1e4e5;
|
||||
--sidebar-width: 280px;
|
||||
--content-max-width: 900px;
|
||||
--link: #2980b9;
|
||||
--link-hover: #1a5276;
|
||||
--warning-bg: #fff3cd;
|
||||
--warning-border: #ffc107;
|
||||
--note-bg: #e7f2fa;
|
||||
--note-border: #6ab0de;
|
||||
--tip-bg: #dff0d8;
|
||||
--tip-border: #3c763d;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: var(--sidebar-width);
|
||||
height: 100vh;
|
||||
background: var(--sidebar-bg);
|
||||
color: var(--sidebar-text);
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sidebar-header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sidebar-header h1 a {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-header .version {
|
||||
font-size: 0.85rem;
|
||||
color: var(--sidebar-link);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.sidebar-nav .section {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sidebar-nav .section-title {
|
||||
display: block;
|
||||
padding: 8px 20px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
color: var(--sidebar-text);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.sidebar-nav ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sidebar-nav li a {
|
||||
display: block;
|
||||
padding: 6px 20px 6px 30px;
|
||||
color: var(--sidebar-link);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.sidebar-nav li a:hover {
|
||||
color: var(--sidebar-link-hover);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.sidebar-nav li a.active {
|
||||
color: var(--sidebar-active);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-left: 3px solid var(--sidebar-active);
|
||||
padding-left: 27px;
|
||||
}
|
||||
|
||||
.sidebar-nav li.nested a {
|
||||
padding-left: 45px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
margin-left: var(--sidebar-width);
|
||||
padding: 40px 50px;
|
||||
max-width: calc(var(--content-max-width) + var(--sidebar-width) + 100px);
|
||||
}
|
||||
|
||||
article {
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: var(--heading);
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-top: 0;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--link-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Monaco, monospace;
|
||||
font-size: 0.9em;
|
||||
background: var(--code-bg);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--code-border);
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--code-border);
|
||||
border-radius: 4px;
|
||||
padding: 15px 20px;
|
||||
overflow-x: auto;
|
||||
margin: 1em 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.code-block .copy-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
padding: 4px 10px;
|
||||
font-size: 0.75rem;
|
||||
background: var(--primary);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.code-block:hover .copy-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.copy-btn.copied {
|
||||
background: var(--tip-border);
|
||||
}
|
||||
|
||||
.method-signature {
|
||||
background: #f8f8f8;
|
||||
border-left: 4px solid var(--primary);
|
||||
padding: 12px 16px;
|
||||
margin: 1em 0;
|
||||
font-family: "SFMono-Regular", Consolas, monospace;
|
||||
font-size: 0.9rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.method-signature .method-name {
|
||||
color: var(--primary-dark);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.method-signature .param {
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
.method-signature .type {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 1em 0;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
ul.param-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin: 0.5em 0 1em;
|
||||
}
|
||||
|
||||
ul.param-list li {
|
||||
padding: 4px 0;
|
||||
padding-left: 1.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ul.param-list li::before {
|
||||
content: "•";
|
||||
position: absolute;
|
||||
left: 0.5em;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.param-name {
|
||||
font-family: monospace;
|
||||
background: var(--code-bg);
|
||||
padding: 1px 5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.param-type {
|
||||
color: #27ae60;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.returns {
|
||||
color: #8e44ad;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--code-bg);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.admonition {
|
||||
padding: 15px 20px;
|
||||
margin: 1.5em 0;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
.admonition-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.admonition.note {
|
||||
background: var(--note-bg);
|
||||
border-color: var(--note-border);
|
||||
}
|
||||
|
||||
.admonition.warning {
|
||||
background: var(--warning-bg);
|
||||
border-color: var(--warning-border);
|
||||
}
|
||||
|
||||
.admonition.tip {
|
||||
background: var(--tip-bg);
|
||||
border-color: var(--tip-border);
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 3em;
|
||||
padding-top: 1.5em;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.page-footer a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: var(--code-bg);
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.page-footer a:hover {
|
||||
background: var(--border);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.page-footer .prev::before {
|
||||
content: "← ";
|
||||
}
|
||||
|
||||
.page-footer .next::after {
|
||||
content: " →";
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
margin-bottom: 1.5em;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.breadcrumb a:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.breadcrumb .separator {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
z-index: 200;
|
||||
background: var(--sidebar-bg);
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
background: linear-gradient(135deg, var(--heading) 0%, var(--primary) 100%);
|
||||
color: #ffffff;
|
||||
margin: -40px -50px 40px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 1.5em;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-buttons a {
|
||||
padding: 12px 24px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.hero-buttons .primary-btn {
|
||||
background: #ffffff;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.hero-buttons .secondary-btn {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: #ffffff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.hero-buttons a:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
transition: box-shadow 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.card h3 a {
|
||||
color: var(--heading);
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.module-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.module-card {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
color: var(--heading);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.module-card:hover {
|
||||
background: var(--primary);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.toc {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.toc h4 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.toc ul {
|
||||
margin: 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.toc li {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
.api-section {
|
||||
margin: 2em 0;
|
||||
padding: 1.5em;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.api-section h3 {
|
||||
margin-top: 0;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.class-header {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 1.5em 0 1em;
|
||||
border-left: 4px solid var(--primary);
|
||||
}
|
||||
|
||||
.class-header h3 {
|
||||
margin: 0;
|
||||
color: var(--heading);
|
||||
}
|
||||
|
||||
.class-header p {
|
||||
margin: 0.5em 0 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.mobile-menu-toggle {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 0;
|
||||
padding: 70px 20px 40px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
margin: -70px -20px 30px;
|
||||
padding: 80px 20px 40px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-footer a {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.sidebar-overlay.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.search-box input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.example-output {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
margin: 1em 0;
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.example-output::before {
|
||||
content: "Output:";
|
||||
display: block;
|
||||
color: #888;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.deprecated {
|
||||
opacity: 0.7;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
border-radius: 3px;
|
||||
margin-left: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.badge-new {
|
||||
background: #27ae60;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.badge-experimental {
|
||||
background: #f39c12;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.badge-async {
|
||||
background: #9b59b6;
|
||||
color: #ffffff;
|
||||
}
|
||||
306
manual/getting-started/first-script.html
Normal file
306
manual/getting-started/first-script.html
Normal file
@ -0,0 +1,306 @@
|
||||
<!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>First Script - 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="index.html">Overview</a></li>
|
||||
<li><a href="installation.html">Installation</a></li>
|
||||
<li><a href="first-script.html" class="active">First Script</a></li>
|
||||
<li><a href="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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Getting Started</a>
|
||||
<span class="separator">/</span>
|
||||
<span>First Script</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>First Script</h1>
|
||||
|
||||
<p>This guide walks you through creating and running your first Wren script. You will learn the basics of printing output, using variables, and importing modules.</p>
|
||||
|
||||
<h2>Hello World</h2>
|
||||
<p>Create a file named <code>hello.wren</code> with the following content:</p>
|
||||
<pre><code>System.print("Hello, World!")</code></pre>
|
||||
|
||||
<p>Run it:</p>
|
||||
<pre><code>bin/wren_cli hello.wren</code></pre>
|
||||
|
||||
<div class="example-output">Hello, World!</div>
|
||||
|
||||
<p>The <code>System.print</code> method outputs text to the console followed by a newline.</p>
|
||||
|
||||
<h2>Variables and Types</h2>
|
||||
<p>Wren is dynamically typed. Use <code>var</code> to declare variables:</p>
|
||||
<pre><code>var name = "Wren"
|
||||
var version = 0.4
|
||||
var features = ["fast", "small", "class-based"]
|
||||
var active = true
|
||||
|
||||
System.print("Language: %(name)")
|
||||
System.print("Version: %(version)")
|
||||
System.print("Features: %(features)")
|
||||
System.print("Active: %(active)")</code></pre>
|
||||
|
||||
<div class="example-output">Language: Wren
|
||||
Version: 0.4
|
||||
Features: [fast, small, class-based]
|
||||
Active: true</div>
|
||||
|
||||
<p>The <code>%(expression)</code> syntax is string interpolation. It evaluates the expression and inserts the result into the string.</p>
|
||||
|
||||
<h2>Working with Lists and Maps</h2>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5]
|
||||
System.print("Count: %(numbers.count)")
|
||||
System.print("First: %(numbers[0])")
|
||||
System.print("Last: %(numbers[-1])")
|
||||
|
||||
var person = {
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
"city": "Amsterdam"
|
||||
}
|
||||
System.print("Name: %(person["name"])")</code></pre>
|
||||
|
||||
<div class="example-output">Count: 5
|
||||
First: 1
|
||||
Last: 5
|
||||
Name: Alice</div>
|
||||
|
||||
<h2>Control Flow</h2>
|
||||
<pre><code>var score = 85
|
||||
|
||||
if (score >= 90) {
|
||||
System.print("Grade: A")
|
||||
} else if (score >= 80) {
|
||||
System.print("Grade: B")
|
||||
} else {
|
||||
System.print("Grade: C")
|
||||
}
|
||||
|
||||
for (i in 1..5) {
|
||||
System.print("Count: %(i)")
|
||||
}</code></pre>
|
||||
|
||||
<div class="example-output">Grade: B
|
||||
Count: 1
|
||||
Count: 2
|
||||
Count: 3
|
||||
Count: 4
|
||||
Count: 5</div>
|
||||
|
||||
<h2>Functions</h2>
|
||||
<p>Functions in Wren are created using block syntax:</p>
|
||||
<pre><code>var greet = Fn.new { |name|
|
||||
return "Hello, %(name)!"
|
||||
}
|
||||
|
||||
System.print(greet.call("World"))
|
||||
|
||||
var add = Fn.new { |a, b| a + b }
|
||||
System.print(add.call(3, 4))</code></pre>
|
||||
|
||||
<div class="example-output">Hello, World!
|
||||
7</div>
|
||||
|
||||
<h2>Classes</h2>
|
||||
<pre><code>class Person {
|
||||
construct new(name, age) {
|
||||
_name = name
|
||||
_age = age
|
||||
}
|
||||
|
||||
name { _name }
|
||||
age { _age }
|
||||
|
||||
greet() {
|
||||
System.print("Hello, I'm %(_name)")
|
||||
}
|
||||
}
|
||||
|
||||
var alice = Person.new("Alice", 30)
|
||||
alice.greet()
|
||||
System.print("Age: %(alice.age)")</code></pre>
|
||||
|
||||
<div class="example-output">Hello, I'm Alice
|
||||
Age: 30</div>
|
||||
|
||||
<h2>Using Modules</h2>
|
||||
<p>Wren-CLI provides many built-in modules. Import them to use their functionality:</p>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var content = File.read("hello.wren")
|
||||
System.print("File contents:")
|
||||
System.print(content)</code></pre>
|
||||
|
||||
<h3>Making HTTP Requests</h3>
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var response = Http.get("https://httpbin.org/get")
|
||||
System.print("Status: %(response.status)")
|
||||
|
||||
var data = Json.parse(response.body)
|
||||
System.print("Origin: %(data["origin"])")</code></pre>
|
||||
|
||||
<h3>Working with JSON</h3>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = {
|
||||
"name": "Wren",
|
||||
"version": 0.4,
|
||||
"features": ["fast", "small"]
|
||||
}
|
||||
|
||||
var jsonString = Json.stringify(data)
|
||||
System.print(jsonString)
|
||||
|
||||
var parsed = Json.parse(jsonString)
|
||||
System.print("Name: %(parsed["name"])")</code></pre>
|
||||
|
||||
<h2>Error Handling</h2>
|
||||
<p>Use <code>Fiber.try</code> to catch runtime errors:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
var x = 1 / 0
|
||||
}
|
||||
|
||||
var error = fiber.try()
|
||||
if (error) {
|
||||
System.print("Error: %(error)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Script Arguments</h2>
|
||||
<p>Access command-line arguments via <code>Process.arguments</code>:</p>
|
||||
<pre><code>import "os" for Process
|
||||
|
||||
System.print("Script arguments:")
|
||||
for (arg in Process.arguments) {
|
||||
System.print(" %(arg)")
|
||||
}</code></pre>
|
||||
|
||||
<p>Run with arguments:</p>
|
||||
<pre><code>bin/wren_cli script.wren arg1 arg2 arg3</code></pre>
|
||||
|
||||
<h2>Exit Codes</h2>
|
||||
<p>Wren-CLI uses these exit codes:</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>Success</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>65</td>
|
||||
<td>Compile error (syntax error)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>70</td>
|
||||
<td>Runtime error</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a href="repl.html">Using the REPL</a> - Interactive experimentation</li>
|
||||
<li><a href="../language/index.html">Language Reference</a> - Deep dive into Wren syntax</li>
|
||||
<li><a href="../tutorials/index.html">Tutorials</a> - Build real applications</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="installation.html" class="prev">Installation</a>
|
||||
<a href="repl.html" class="next">Using the REPL</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
205
manual/getting-started/index.html
Normal file
205
manual/getting-started/index.html
Normal file
@ -0,0 +1,205 @@
|
||||
<!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>Getting Started - 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="index.html" class="active">Overview</a></li>
|
||||
<li><a href="installation.html">Installation</a></li>
|
||||
<li><a href="first-script.html">First Script</a></li>
|
||||
<li><a href="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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Getting Started</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Getting Started</h1>
|
||||
|
||||
<p>Welcome to Wren-CLI, a command-line interface for the Wren programming language extended with powerful modules for networking, file I/O, databases, and more.</p>
|
||||
|
||||
<div class="toc">
|
||||
<h4>In This Section</h4>
|
||||
<ul>
|
||||
<li><a href="installation.html">Installation</a> - Build from source on Linux, macOS, or FreeBSD</li>
|
||||
<li><a href="first-script.html">First Script</a> - Write and run your first Wren program</li>
|
||||
<li><a href="repl.html">Using the REPL</a> - Interactive experimentation</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>What is Wren?</h2>
|
||||
<p>Wren is a small, fast, class-based scripting language. It has a familiar syntax, first-class functions, and a fiber-based concurrency model. Wren-CLI extends the core language with modules for:</p>
|
||||
<ul>
|
||||
<li>HTTP and WebSocket networking</li>
|
||||
<li>File and directory operations</li>
|
||||
<li>SQLite database access</li>
|
||||
<li>JSON and regex processing</li>
|
||||
<li>Template rendering (Jinja2-compatible)</li>
|
||||
<li>Cryptographic operations</li>
|
||||
<li>Process and signal handling</li>
|
||||
</ul>
|
||||
|
||||
<h2>Quick Start</h2>
|
||||
<p>If you want to dive right in, here is the fastest path to running your first script:</p>
|
||||
|
||||
<h3>1. Build</h3>
|
||||
<pre><code>git clone https://github.com/wren-lang/wren-cli.git
|
||||
cd wren-cli
|
||||
cd projects/make && make</code></pre>
|
||||
|
||||
<h3>2. Create a Script</h3>
|
||||
<p>Create a file named <code>hello.wren</code>:</p>
|
||||
<pre><code>System.print("Hello, Wren!")</code></pre>
|
||||
|
||||
<h3>3. Run</h3>
|
||||
<pre><code>bin/wren_cli hello.wren</code></pre>
|
||||
|
||||
<div class="example-output">Hello, Wren!</div>
|
||||
|
||||
<h2>Key Concepts</h2>
|
||||
<p>Before diving deeper, understand these fundamental Wren concepts:</p>
|
||||
|
||||
<h3>Everything is an Object</h3>
|
||||
<p>In Wren, everything is an object, including numbers, strings, and functions. Every object is an instance of a class.</p>
|
||||
<pre><code>var name = "Wren"
|
||||
System.print(name.count) // 4
|
||||
System.print(name.bytes[0]) // 87 (ASCII for 'W')</code></pre>
|
||||
|
||||
<h3>Fibers for Concurrency</h3>
|
||||
<p>Wren uses fibers (cooperative threads) for concurrency. The scheduler module manages async operations.</p>
|
||||
<pre><code>import "timer" for Timer
|
||||
|
||||
Timer.sleep(1000) // Pauses for 1 second
|
||||
System.print("Done waiting")</code></pre>
|
||||
|
||||
<h3>Module System</h3>
|
||||
<p>Functionality is organized into modules that you import as needed:</p>
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var response = Http.get("https://api.example.com/data")
|
||||
var data = Json.parse(response.body)</code></pre>
|
||||
|
||||
<h2>System Requirements</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Requirements</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux</td>
|
||||
<td>GCC, Make, OpenSSL development libraries</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>macOS</td>
|
||||
<td>Xcode Command Line Tools, OpenSSL (via Homebrew)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>FreeBSD</td>
|
||||
<td>GCC or Clang, gmake, OpenSSL</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<p>Continue with the following sections to learn more:</p>
|
||||
<ul>
|
||||
<li><a href="installation.html">Installation</a> - Detailed build instructions for all platforms</li>
|
||||
<li><a href="first-script.html">First Script</a> - A more detailed walkthrough of your first program</li>
|
||||
<li><a href="../language/index.html">Language Reference</a> - Learn the Wren language syntax</li>
|
||||
<li><a href="../api/index.html">API Reference</a> - Explore all available modules</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="../index.html" class="prev">Home</a>
|
||||
<a href="installation.html" class="next">Installation</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
263
manual/getting-started/installation.html
Normal file
263
manual/getting-started/installation.html
Normal file
@ -0,0 +1,263 @@
|
||||
<!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>Installation - 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="index.html">Overview</a></li>
|
||||
<li><a href="installation.html" class="active">Installation</a></li>
|
||||
<li><a href="first-script.html">First Script</a></li>
|
||||
<li><a href="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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Getting Started</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Installation</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Installation</h1>
|
||||
|
||||
<p>Wren-CLI must be built from source. This page covers the build process for Linux, macOS, and FreeBSD.</p>
|
||||
|
||||
<div class="toc">
|
||||
<h4>On This Page</h4>
|
||||
<ul>
|
||||
<li><a href="#prerequisites">Prerequisites</a></li>
|
||||
<li><a href="#linux">Building on Linux</a></li>
|
||||
<li><a href="#macos">Building on macOS</a></li>
|
||||
<li><a href="#freebsd">Building on FreeBSD</a></li>
|
||||
<li><a href="#configurations">Build Configurations</a></li>
|
||||
<li><a href="#verification">Verifying Installation</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="prerequisites">Prerequisites</h2>
|
||||
<p>All platforms require:</p>
|
||||
<ul>
|
||||
<li>Git</li>
|
||||
<li>C compiler (GCC or Clang)</li>
|
||||
<li>Make</li>
|
||||
<li>OpenSSL development libraries (for TLS/HTTPS support)</li>
|
||||
<li>Python 3 (for running tests)</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="linux">Building on Linux</h2>
|
||||
|
||||
<h3>Install Dependencies</h3>
|
||||
<p>On Debian/Ubuntu:</p>
|
||||
<pre><code>sudo apt-get update
|
||||
sudo apt-get install build-essential libssl-dev git python3</code></pre>
|
||||
|
||||
<p>On Fedora/RHEL:</p>
|
||||
<pre><code>sudo dnf install gcc make openssl-devel git python3</code></pre>
|
||||
|
||||
<p>On Arch Linux:</p>
|
||||
<pre><code>sudo pacman -S base-devel openssl git python</code></pre>
|
||||
|
||||
<h3>Build</h3>
|
||||
<pre><code>git clone https://github.com/wren-lang/wren-cli.git
|
||||
cd wren-cli
|
||||
make build</code></pre>
|
||||
|
||||
<p>The binary will be created at <code>bin/wren_cli</code>.</p>
|
||||
|
||||
<h3>Install System-Wide (Optional)</h3>
|
||||
<pre><code>sudo cp bin/wren_cli /usr/local/bin/wren</code></pre>
|
||||
|
||||
<h2 id="macos">Building on macOS</h2>
|
||||
|
||||
<h3>Install Dependencies</h3>
|
||||
<p>Install Xcode Command Line Tools:</p>
|
||||
<pre><code>xcode-select --install</code></pre>
|
||||
|
||||
<p>Install OpenSSL via Homebrew:</p>
|
||||
<pre><code>brew install openssl</code></pre>
|
||||
|
||||
<h3>Build</h3>
|
||||
<pre><code>git clone https://github.com/wren-lang/wren-cli.git
|
||||
cd wren-cli
|
||||
cd projects/make.mac && make</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>If OpenSSL is not found, you may need to set the library path:</p>
|
||||
<pre><code>export LDFLAGS="-L/opt/homebrew/opt/openssl/lib"
|
||||
export CPPFLAGS="-I/opt/homebrew/opt/openssl/include"</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="freebsd">Building on FreeBSD</h2>
|
||||
|
||||
<h3>Install Dependencies</h3>
|
||||
<pre><code>sudo pkg install gmake git python3</code></pre>
|
||||
|
||||
<h3>Build</h3>
|
||||
<pre><code>git clone https://github.com/wren-lang/wren-cli.git
|
||||
cd wren-cli
|
||||
cd projects/make.bsd && gmake</code></pre>
|
||||
|
||||
<h2 id="configurations">Build Configurations</h2>
|
||||
<p>Several build configurations are available:</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Configuration</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>release_64bit</code></td>
|
||||
<td>Default optimized 64-bit build</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>release_32bit</code></td>
|
||||
<td>Optimized 32-bit build</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>debug_64bit</code></td>
|
||||
<td>Debug build with symbols</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>debug_32bit</code></td>
|
||||
<td>32-bit debug build</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>release_64bit-no-nan-tagging</code></td>
|
||||
<td>Without NaN tagging optimization</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>debug_64bit-no-nan-tagging</code></td>
|
||||
<td>Debug without NaN tagging</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>To use a specific configuration:</p>
|
||||
<pre><code>make config=debug_64bit</code></pre>
|
||||
|
||||
<p>Debug builds output to <code>bin/wren_cli_d</code>.</p>
|
||||
|
||||
<h2 id="verification">Verifying Installation</h2>
|
||||
<p>Verify the build was successful:</p>
|
||||
<pre><code>bin/wren_cli --version</code></pre>
|
||||
|
||||
<p>Run the test suite:</p>
|
||||
<pre><code>make tests</code></pre>
|
||||
|
||||
<p>Start the REPL:</p>
|
||||
<pre><code>bin/wren_cli</code></pre>
|
||||
|
||||
<div class="example-output">> </div>
|
||||
|
||||
<p>Type <code>System.print("Hello")</code> and press Enter to verify the REPL works.</p>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>OpenSSL Not Found</h3>
|
||||
<p>If you see errors about missing OpenSSL headers:</p>
|
||||
<ul>
|
||||
<li>Verify OpenSSL development packages are installed</li>
|
||||
<li>On macOS, ensure Homebrew OpenSSL path is in your environment</li>
|
||||
</ul>
|
||||
|
||||
<h3>Build Errors</h3>
|
||||
<p>Try cleaning and rebuilding:</p>
|
||||
<pre><code>make clean
|
||||
make build</code></pre>
|
||||
|
||||
<h3>Test Failures</h3>
|
||||
<p>Run a specific test module to isolate issues:</p>
|
||||
<pre><code>python3 util/test.py json</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="index.html" class="prev">Overview</a>
|
||||
<a href="first-script.html" class="next">First Script</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
254
manual/getting-started/repl.html
Normal file
254
manual/getting-started/repl.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>Using the REPL - 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="index.html">Overview</a></li>
|
||||
<li><a href="installation.html">Installation</a></li>
|
||||
<li><a href="first-script.html">First Script</a></li>
|
||||
<li><a href="repl.html" class="active">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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Getting Started</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Using the REPL</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Using the REPL</h1>
|
||||
|
||||
<p>The REPL (Read-Eval-Print Loop) is an interactive environment for experimenting with Wren code. It is useful for testing ideas, exploring APIs, and learning the language.</p>
|
||||
|
||||
<h2>Starting the REPL</h2>
|
||||
<p>Run <code>wren_cli</code> without arguments to start the REPL:</p>
|
||||
<pre><code>bin/wren_cli</code></pre>
|
||||
|
||||
<p>You will see a prompt:</p>
|
||||
<div class="example-output">> </div>
|
||||
|
||||
<h2>Basic Usage</h2>
|
||||
<p>Type expressions and press Enter to evaluate them:</p>
|
||||
<pre><code>> 1 + 2
|
||||
3
|
||||
> "hello".count
|
||||
5
|
||||
> [1, 2, 3].map { |x| x * 2 }
|
||||
[2, 4, 6]</code></pre>
|
||||
|
||||
<h2>Multi-line Input</h2>
|
||||
<p>The REPL automatically detects incomplete expressions and waits for more input:</p>
|
||||
<pre><code>> class Person {
|
||||
| construct new(name) {
|
||||
| _name = name
|
||||
| }
|
||||
| name { _name }
|
||||
| }
|
||||
null
|
||||
> var p = Person.new("Alice")
|
||||
null
|
||||
> p.name
|
||||
Alice</code></pre>
|
||||
|
||||
<p>The <code>|</code> prompt indicates the REPL is waiting for more input.</p>
|
||||
|
||||
<h2>Importing Modules</h2>
|
||||
<p>Modules can be imported in the REPL just like in scripts:</p>
|
||||
<pre><code>> import "json" for Json
|
||||
null
|
||||
> Json.stringify({"name": "Wren"})
|
||||
{"name":"Wren"}
|
||||
> import "math" for Math
|
||||
null
|
||||
> Math.sqrt(16)
|
||||
4</code></pre>
|
||||
|
||||
<h2>Variables Persist</h2>
|
||||
<p>Variables defined in the REPL persist across lines:</p>
|
||||
<pre><code>> var x = 10
|
||||
null
|
||||
> var y = 20
|
||||
null
|
||||
> x + y
|
||||
30</code></pre>
|
||||
|
||||
<h2>Examining Values</h2>
|
||||
<p>Use <code>System.print</code> for formatted output:</p>
|
||||
<pre><code>> var data = {"name": "Wren", "version": 0.4}
|
||||
null
|
||||
> System.print(data)
|
||||
{name: Wren, version: 0.4}</code></pre>
|
||||
|
||||
<p>Check the type of a value:</p>
|
||||
<pre><code>> 42.type
|
||||
Num
|
||||
> "hello".type
|
||||
String
|
||||
> [1, 2, 3].type
|
||||
List</code></pre>
|
||||
|
||||
<h2>Exploring Classes</h2>
|
||||
<p>Examine what methods a class provides:</p>
|
||||
<pre><code>> String
|
||||
String
|
||||
> "test".bytes
|
||||
[116, 101, 115, 116]
|
||||
> "test".codePoints.toList
|
||||
[116, 101, 115, 116]</code></pre>
|
||||
|
||||
<h2>Error Handling</h2>
|
||||
<p>Errors are displayed but do not crash the REPL:</p>
|
||||
<pre><code>> 1 / 0
|
||||
infinity
|
||||
> "test"[10]
|
||||
Subscript out of bounds.
|
||||
> undefined_variable
|
||||
[repl line 1] Error at 'undefined_variable': Variable is used but not defined.</code></pre>
|
||||
|
||||
<p>You can continue using the REPL after errors.</p>
|
||||
|
||||
<h2>REPL Tips</h2>
|
||||
|
||||
<h3>Quick Testing</h3>
|
||||
<p>Use the REPL to quickly test regular expressions:</p>
|
||||
<pre><code>> import "regex" for Regex
|
||||
null
|
||||
> Regex.new("\\d+").test("abc123")
|
||||
true
|
||||
> Regex.new("\\d+").match("abc123def").text
|
||||
123</code></pre>
|
||||
|
||||
<h3>Exploring APIs</h3>
|
||||
<p>Test module functionality before using it in scripts:</p>
|
||||
<pre><code>> import "base64" for Base64
|
||||
null
|
||||
> Base64.encode("Hello, World!")
|
||||
SGVsbG8sIFdvcmxkIQ==
|
||||
> Base64.decode("SGVsbG8sIFdvcmxkIQ==")
|
||||
Hello, World!</code></pre>
|
||||
|
||||
<h3>Date and Time</h3>
|
||||
<pre><code>> import "datetime" for DateTime
|
||||
null
|
||||
> DateTime.now
|
||||
2024-01-15T10:30:45
|
||||
> DateTime.now.year
|
||||
2024</code></pre>
|
||||
|
||||
<h2>Exiting the REPL</h2>
|
||||
<p>Exit the REPL by pressing <code>Ctrl+D</code> (Unix) or <code>Ctrl+Z</code> followed by Enter (Windows).</p>
|
||||
|
||||
<h2>Limitations</h2>
|
||||
<ul>
|
||||
<li>No command history navigation (arrow keys)</li>
|
||||
<li>No tab completion</li>
|
||||
<li>No line editing beyond basic backspace</li>
|
||||
<li>Cannot redefine classes once defined</li>
|
||||
</ul>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>For complex experimentation, write code in a file and run it with <code>wren_cli script.wren</code>. The REPL is best for quick tests and exploration.</p>
|
||||
</div>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a href="../language/index.html">Language Reference</a> - Learn Wren syntax in detail</li>
|
||||
<li><a href="../api/index.html">API Reference</a> - Explore available modules</li>
|
||||
<li><a href="../tutorials/index.html">Tutorials</a> - Build real applications</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="first-script.html" class="prev">First Script</a>
|
||||
<a href="../language/index.html" class="next">Language Reference</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
424
manual/howto/async-operations.html
Normal file
424
manual/howto/async-operations.html
Normal file
@ -0,0 +1,424 @@
|
||||
<!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 Programming - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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">Template Rendering</a></li>
|
||||
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">How-To Guides</span>
|
||||
<ul>
|
||||
<li><a href="index.html">How-To List</a></li>
|
||||
<li><a href="http-requests.html">HTTP Requests</a></li>
|
||||
<li><a href="json-parsing.html">JSON Parsing</a></li>
|
||||
<li><a href="regex-patterns.html">Regex Patterns</a></li>
|
||||
<li><a href="file-operations.html">File Operations</a></li>
|
||||
<li><a href="async-operations.html" class="active">Async Operations</a></li>
|
||||
<li><a href="error-handling.html">Error Handling</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">How-To Guides</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Async Operations</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Async Programming</h1>
|
||||
|
||||
<p>Wren-CLI uses fibers for concurrent operations. Understanding fibers is key to writing efficient async code.</p>
|
||||
|
||||
<h2>Create a Fiber</h2>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
System.print("Hello from fiber!")
|
||||
}
|
||||
|
||||
fiber.call()</code></pre>
|
||||
|
||||
<h2>Fibers with Return Values</h2>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
return 42
|
||||
}
|
||||
|
||||
var result = fiber.call()
|
||||
System.print("Result: %(result)") // 42</code></pre>
|
||||
|
||||
<h2>Sleep/Delay</h2>
|
||||
<pre><code>import "timer" for Timer
|
||||
|
||||
System.print("Starting...")
|
||||
Timer.sleep(1000) // Wait 1 second
|
||||
System.print("Done!")</code></pre>
|
||||
|
||||
<h2>Sequential HTTP Requests</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
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) })
|
||||
}
|
||||
|
||||
for (fiber in fibers) {
|
||||
fiber.call()
|
||||
}
|
||||
|
||||
for (fiber in fibers) {
|
||||
System.print(fiber.value)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Run Task in Background</h2>
|
||||
<pre><code>import "timer" for Timer
|
||||
|
||||
var backgroundTask = Fiber.new {
|
||||
for (i in 1..5) {
|
||||
System.print("Background: %(i)")
|
||||
Timer.sleep(500)
|
||||
}
|
||||
}
|
||||
|
||||
backgroundTask.call()
|
||||
|
||||
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")
|
||||
}
|
||||
Timer.sleep(10)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var result = withTimeout.call(Fn.new {
|
||||
Timer.sleep(500)
|
||||
return "completed"
|
||||
}, 1000)
|
||||
|
||||
System.print(result)</code></pre>
|
||||
|
||||
<h2>Polling Loop</h2>
|
||||
<pre><code>import "timer" for Timer
|
||||
import "http" for Http
|
||||
|
||||
var pollUntilReady = Fn.new { |url, maxAttempts|
|
||||
var attempts = 0
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
attempts = attempts + 1
|
||||
System.print("Attempt %(attempts)...")
|
||||
|
||||
var response = Http.get(url)
|
||||
if (response.json["ready"]) {
|
||||
return response.json
|
||||
}
|
||||
|
||||
Timer.sleep(2000)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
var result = pollUntilReady.call("https://api.example.com/status", 10)
|
||||
if (result) {
|
||||
System.print("Ready: %(result)")
|
||||
} else {
|
||||
System.print("Timed out waiting for ready state")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Rate Limiting</h2>
|
||||
<pre><code>import "timer" for Timer
|
||||
import "http" for Http
|
||||
|
||||
var rateLimitedFetch = Fn.new { |urls, delayMs|
|
||||
var results = []
|
||||
|
||||
for (url in urls) {
|
||||
var response = Http.get(url)
|
||||
results.add(response)
|
||||
Timer.sleep(delayMs)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
var responses = rateLimitedFetch.call([
|
||||
"https://api.example.com/1",
|
||||
"https://api.example.com/2",
|
||||
"https://api.example.com/3"
|
||||
], 1000)
|
||||
|
||||
for (r in responses) {
|
||||
System.print(r.statusCode)
|
||||
}</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
|
||||
|
||||
var running = true
|
||||
|
||||
Signal.handle("SIGINT", Fn.new {
|
||||
System.print("\nShutting down gracefully...")
|
||||
running = false
|
||||
})
|
||||
|
||||
System.print("Press Ctrl+C to stop")
|
||||
|
||||
while (running) {
|
||||
System.print("Working...")
|
||||
Timer.sleep(1000)
|
||||
}
|
||||
|
||||
System.print("Cleanup complete, exiting.")</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>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>
|
||||
</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>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="file-operations.html" class="prev">File Operations</a>
|
||||
<a href="error-handling.html" class="next">Error Handling</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
481
manual/howto/error-handling.html
Normal file
481
manual/howto/error-handling.html
Normal file
@ -0,0 +1,481 @@
|
||||
<!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>Error Handling - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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">Template Rendering</a></li>
|
||||
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">How-To Guides</span>
|
||||
<ul>
|
||||
<li><a href="index.html">How-To List</a></li>
|
||||
<li><a href="http-requests.html">HTTP Requests</a></li>
|
||||
<li><a href="json-parsing.html">JSON Parsing</a></li>
|
||||
<li><a href="regex-patterns.html">Regex Patterns</a></li>
|
||||
<li><a href="file-operations.html">File Operations</a></li>
|
||||
<li><a href="async-operations.html">Async Operations</a></li>
|
||||
<li><a href="error-handling.html" class="active">Error Handling</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">How-To Guides</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Error Handling</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Error Handling</h1>
|
||||
|
||||
<p>Wren uses fibers for error handling. The <code>fiber.try()</code> method catches errors without crashing your program.</p>
|
||||
|
||||
<h2>Basic Try/Catch Pattern</h2>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
Fiber.abort("Something went wrong!")
|
||||
}
|
||||
|
||||
var result = fiber.try()
|
||||
|
||||
if (fiber.error) {
|
||||
System.print("Error: %(fiber.error)")
|
||||
} else {
|
||||
System.print("Result: %(result)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Throw an Error</h2>
|
||||
<pre><code>Fiber.abort("This is an error message")</code></pre>
|
||||
|
||||
<h2>Catch Specific Error Types</h2>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
var x = null
|
||||
return x.count // Error: null has no method 'count'
|
||||
}
|
||||
|
||||
var result = fiber.try()
|
||||
|
||||
if (fiber.error) {
|
||||
if (fiber.error.contains("null")) {
|
||||
System.print("Null reference error")
|
||||
} else {
|
||||
System.print("Other error: %(fiber.error)")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Safe File Read</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var safeRead = Fn.new { |path|
|
||||
var fiber = Fiber.new { File.read(path) }
|
||||
var content = fiber.try()
|
||||
|
||||
if (fiber.error) {
|
||||
return null
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
var content = safeRead.call("config.txt")
|
||||
if (content) {
|
||||
System.print(content)
|
||||
} else {
|
||||
System.print("Could not read file")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Safe JSON Parse</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var safeParse = Fn.new { |jsonStr|
|
||||
var fiber = Fiber.new { Json.parse(jsonStr) }
|
||||
var data = fiber.try()
|
||||
|
||||
if (fiber.error) {
|
||||
System.print("Invalid JSON: %(fiber.error)")
|
||||
return null
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
var data = safeParse.call('{"valid": true}') // Works
|
||||
var bad = safeParse.call('not json') // Returns null</code></pre>
|
||||
|
||||
<h2>Safe HTTP Request</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var safeFetch = Fn.new { |url|
|
||||
var fiber = Fiber.new { Http.get(url) }
|
||||
var response = fiber.try()
|
||||
|
||||
if (fiber.error) {
|
||||
return {"error": fiber.error, "ok": false}
|
||||
}
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
return {"error": "HTTP %(response.statusCode)", "ok": false}
|
||||
}
|
||||
|
||||
return {"data": response.json, "ok": true}
|
||||
}
|
||||
|
||||
var result = safeFetch.call("https://api.example.com/data")
|
||||
if (result["ok"]) {
|
||||
System.print(result["data"])
|
||||
} else {
|
||||
System.print("Error: %(result["error"])")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Input Validation</h2>
|
||||
<pre><code>var validateEmail = Fn.new { |email|
|
||||
if (email == null) {
|
||||
Fiber.abort("Email is required")
|
||||
}
|
||||
if (!email.contains("@")) {
|
||||
Fiber.abort("Invalid email format")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var validate = Fn.new { |email|
|
||||
var fiber = Fiber.new { validateEmail.call(email) }
|
||||
fiber.try()
|
||||
return fiber.error
|
||||
}
|
||||
|
||||
System.print(validate.call(null)) // Email is required
|
||||
System.print(validate.call("invalid")) // Invalid email format
|
||||
System.print(validate.call("a@b.com")) // null (no error)</code></pre>
|
||||
|
||||
<h2>Result Type Pattern</h2>
|
||||
<pre><code>class Result {
|
||||
construct ok(value) {
|
||||
_value = value
|
||||
_error = null
|
||||
}
|
||||
|
||||
construct error(message) {
|
||||
_value = null
|
||||
_error = message
|
||||
}
|
||||
|
||||
isOk { _error == null }
|
||||
isError { _error != null }
|
||||
value { _value }
|
||||
error { _error }
|
||||
|
||||
unwrap {
|
||||
if (isError) Fiber.abort(_error)
|
||||
return _value
|
||||
}
|
||||
|
||||
unwrapOr(default) { isOk ? _value : default }
|
||||
}
|
||||
|
||||
var divide = Fn.new { |a, b|
|
||||
if (b == 0) {
|
||||
return Result.error("Division by zero")
|
||||
}
|
||||
return Result.ok(a / b)
|
||||
}
|
||||
|
||||
var result = divide.call(10, 2)
|
||||
if (result.isOk) {
|
||||
System.print("Result: %(result.value)")
|
||||
}
|
||||
|
||||
var bad = divide.call(10, 0)
|
||||
if (bad.isError) {
|
||||
System.print("Error: %(bad.error)")
|
||||
}
|
||||
|
||||
System.print(divide.call(10, 5).unwrapOr(0)) // 2
|
||||
System.print(divide.call(10, 0).unwrapOr(0)) // 0</code></pre>
|
||||
|
||||
<h2>Assert Function</h2>
|
||||
<pre><code>var assert = Fn.new { |condition, message|
|
||||
if (!condition) {
|
||||
Fiber.abort("Assertion failed: %(message)")
|
||||
}
|
||||
}
|
||||
|
||||
var processUser = Fn.new { |user|
|
||||
assert.call(user != null, "User is required")
|
||||
assert.call(user["name"] != null, "User name is required")
|
||||
assert.call(user["age"] >= 0, "Age must be non-negative")
|
||||
|
||||
System.print("Processing %(user["name"])...")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Multiple Error Sources</h2>
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var fetchAndParse = Fn.new { |url|
|
||||
var httpFiber = Fiber.new { Http.get(url) }
|
||||
var response = httpFiber.try()
|
||||
|
||||
if (httpFiber.error) {
|
||||
return {"error": "Network error: %(httpFiber.error)"}
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return {"error": "HTTP error: %(response.statusCode)"}
|
||||
}
|
||||
|
||||
var jsonFiber = Fiber.new { Json.parse(response.body) }
|
||||
var data = jsonFiber.try()
|
||||
|
||||
if (jsonFiber.error) {
|
||||
return {"error": "Parse error: %(jsonFiber.error)"}
|
||||
}
|
||||
|
||||
return {"data": data}
|
||||
}
|
||||
|
||||
var result = fetchAndParse.call("https://api.example.com/data")
|
||||
if (result.containsKey("error")) {
|
||||
System.print(result["error"])
|
||||
} else {
|
||||
System.print(result["data"])
|
||||
}</code></pre>
|
||||
|
||||
<h2>Error Logging</h2>
|
||||
<pre><code>import "datetime" for DateTime
|
||||
import "io" for File
|
||||
|
||||
class Logger {
|
||||
construct new(logFile) {
|
||||
_logFile = logFile
|
||||
}
|
||||
|
||||
log(level, message) {
|
||||
var timestamp = DateTime.now().toString
|
||||
var entry = "[%(timestamp)] [%(level)] %(message)\n"
|
||||
|
||||
var existing = ""
|
||||
var fiber = Fiber.new { File.read(_logFile) }
|
||||
existing = fiber.try() || ""
|
||||
|
||||
File.write(_logFile, existing + entry)
|
||||
|
||||
if (level == "ERROR") {
|
||||
System.print("ERROR: %(message)")
|
||||
}
|
||||
}
|
||||
|
||||
error(message) { log("ERROR", message) }
|
||||
warn(message) { log("WARN", message) }
|
||||
info(message) { log("INFO", message) }
|
||||
}
|
||||
|
||||
var logger = Logger.new("app.log")
|
||||
|
||||
var safeDivide = Fn.new { |a, b|
|
||||
if (b == 0) {
|
||||
logger.error("Division by zero: %(a) / %(b)")
|
||||
return null
|
||||
}
|
||||
return a / b
|
||||
}
|
||||
|
||||
var result = safeDivide.call(10, 0)</code></pre>
|
||||
|
||||
<h2>Cleanup with Finally Pattern</h2>
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
|
||||
var withDatabase = Fn.new { |dbPath, operation|
|
||||
var db = Sqlite.open(dbPath)
|
||||
var result = null
|
||||
var error = null
|
||||
|
||||
var fiber = Fiber.new { operation.call(db) }
|
||||
result = fiber.try()
|
||||
error = fiber.error
|
||||
|
||||
db.close()
|
||||
|
||||
if (error) {
|
||||
Fiber.abort(error)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var users = withDatabase.call("app.db", Fn.new { |db|
|
||||
return db.execute("SELECT * FROM users")
|
||||
})
|
||||
|
||||
System.print(users)</code></pre>
|
||||
|
||||
<h2>Custom Error Class</h2>
|
||||
<pre><code>class AppError {
|
||||
construct new(code, message) {
|
||||
_code = code
|
||||
_message = message
|
||||
}
|
||||
|
||||
code { _code }
|
||||
message { _message }
|
||||
toString { "[%(code)] %(message)" }
|
||||
|
||||
static notFound(resource) {
|
||||
return AppError.new("NOT_FOUND", "%(resource) not found")
|
||||
}
|
||||
|
||||
static validation(field, reason) {
|
||||
return AppError.new("VALIDATION", "%(field): %(reason)")
|
||||
}
|
||||
|
||||
static unauthorized() {
|
||||
return AppError.new("UNAUTHORIZED", "Authentication required")
|
||||
}
|
||||
}
|
||||
|
||||
var findUser = Fn.new { |id|
|
||||
if (id == null) {
|
||||
Fiber.abort(AppError.validation("id", "is required").toString)
|
||||
}
|
||||
|
||||
var user = null
|
||||
|
||||
if (user == null) {
|
||||
Fiber.abort(AppError.notFound("User %(id)").toString)
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
var fiber = Fiber.new { findUser.call(null) }
|
||||
fiber.try()
|
||||
System.print(fiber.error) // [VALIDATION] id: is required</code></pre>
|
||||
|
||||
<h2>Retry on Error</h2>
|
||||
<pre><code>import "timer" for Timer
|
||||
|
||||
var retry = Fn.new { |operation, maxAttempts, delayMs|
|
||||
var lastError = null
|
||||
|
||||
for (i in 1..maxAttempts) {
|
||||
var fiber = Fiber.new { operation.call() }
|
||||
var result = fiber.try()
|
||||
|
||||
if (!fiber.error) {
|
||||
return result
|
||||
}
|
||||
|
||||
lastError = fiber.error
|
||||
System.print("Attempt %(i) failed: %(lastError)")
|
||||
|
||||
if (i < maxAttempts) {
|
||||
Timer.sleep(delayMs)
|
||||
}
|
||||
}
|
||||
|
||||
Fiber.abort("All %(maxAttempts) attempts failed. Last error: %(lastError)")
|
||||
}
|
||||
|
||||
var result = retry.call(Fn.new {
|
||||
return "success"
|
||||
}, 3, 1000)</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Best Practices</div>
|
||||
<ul>
|
||||
<li>Always wrap external calls (HTTP, file I/O) in fibers</li>
|
||||
<li>Provide meaningful error messages</li>
|
||||
<li>Log errors for debugging</li>
|
||||
<li>Fail fast on programming errors, recover from user errors</li>
|
||||
<li>Clean up resources (close files, connections) even on error</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">See Also</div>
|
||||
<p>For more on fibers, see the <a href="../language/fibers.html">Fibers language guide</a>.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="async-operations.html" class="prev">Async Operations</a>
|
||||
<a href="../index.html" class="next">Home</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
409
manual/howto/file-operations.html
Normal file
409
manual/howto/file-operations.html
Normal file
@ -0,0 +1,409 @@
|
||||
<!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>File Operations - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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">Template Rendering</a></li>
|
||||
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">How-To Guides</span>
|
||||
<ul>
|
||||
<li><a href="index.html">How-To List</a></li>
|
||||
<li><a href="http-requests.html">HTTP Requests</a></li>
|
||||
<li><a href="json-parsing.html">JSON Parsing</a></li>
|
||||
<li><a href="regex-patterns.html">Regex Patterns</a></li>
|
||||
<li><a href="file-operations.html" class="active">File Operations</a></li>
|
||||
<li><a href="async-operations.html">Async Operations</a></li>
|
||||
<li><a href="error-handling.html">Error Handling</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">How-To Guides</a>
|
||||
<span class="separator">/</span>
|
||||
<span>File Operations</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>File Operations</h1>
|
||||
|
||||
<h2>Read Entire File</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var content = File.read("document.txt")
|
||||
System.print(content)</code></pre>
|
||||
|
||||
<h2>Write to File</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
File.write("output.txt", "Hello, World!")
|
||||
System.print("File written!")</code></pre>
|
||||
|
||||
<h2>Append to File</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var existing = File.exists("log.txt") ? File.read("log.txt") : ""
|
||||
File.write("log.txt", existing + "New line\n")</code></pre>
|
||||
|
||||
<h2>Check if File Exists</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
if (File.exists("config.txt")) {
|
||||
System.print("Config found")
|
||||
var content = File.read("config.txt")
|
||||
} else {
|
||||
System.print("Config not found, using defaults")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Get File Size</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var size = File.size("data.bin")
|
||||
System.print("File size: %(size) bytes")</code></pre>
|
||||
|
||||
<h2>Copy File</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
File.copy("source.txt", "destination.txt")
|
||||
System.print("File copied!")</code></pre>
|
||||
|
||||
<h2>Rename/Move File</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
File.rename("old_name.txt", "new_name.txt")
|
||||
System.print("File renamed!")
|
||||
|
||||
File.rename("file.txt", "subdir/file.txt")
|
||||
System.print("File moved!")</code></pre>
|
||||
|
||||
<h2>Delete File</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
if (File.exists("temp.txt")) {
|
||||
File.delete("temp.txt")
|
||||
System.print("File deleted!")
|
||||
}</code></pre>
|
||||
|
||||
<h2>List Directory Contents</h2>
|
||||
<pre><code>import "io" for Directory
|
||||
|
||||
var files = Directory.list(".")
|
||||
|
||||
for (file in files) {
|
||||
System.print(file)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Check if Directory Exists</h2>
|
||||
<pre><code>import "io" for Directory
|
||||
|
||||
if (Directory.exists("data")) {
|
||||
System.print("Directory found")
|
||||
} else {
|
||||
System.print("Directory not found")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Create Directory</h2>
|
||||
<pre><code>import "io" for Directory
|
||||
|
||||
if (!Directory.exists("output")) {
|
||||
Directory.create("output")
|
||||
System.print("Directory created!")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Delete Empty Directory</h2>
|
||||
<pre><code>import "io" for Directory
|
||||
|
||||
Directory.delete("empty_folder")
|
||||
System.print("Directory deleted!")</code></pre>
|
||||
|
||||
<h2>Read File Line by Line</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var content = File.read("data.txt")
|
||||
var lines = content.split("\n")
|
||||
|
||||
for (line in lines) {
|
||||
if (line.count > 0) {
|
||||
System.print(line)
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Process Files in Directory</h2>
|
||||
<pre><code>import "io" for File, Directory
|
||||
|
||||
var files = Directory.list("./data")
|
||||
|
||||
for (filename in files) {
|
||||
if (filename.endsWith(".txt")) {
|
||||
var path = "./data/%(filename)"
|
||||
var content = File.read(path)
|
||||
System.print("%(filename): %(content.count) chars")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Recursive Directory Listing</h2>
|
||||
<pre><code>import "io" for File, Directory
|
||||
|
||||
var listRecursive = Fn.new { |path, indent|
|
||||
var items = Directory.list(path)
|
||||
|
||||
for (item in items) {
|
||||
var fullPath = "%(path)/%(item)"
|
||||
System.print("%(indent)%(item)")
|
||||
|
||||
if (Directory.exists(fullPath)) {
|
||||
listRecursive.call(fullPath, indent + " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listRecursive.call(".", "")</code></pre>
|
||||
|
||||
<h2>Find Files by Extension</h2>
|
||||
<pre><code>import "io" for File, Directory
|
||||
|
||||
var findByExtension = Fn.new { |path, ext|
|
||||
var results = []
|
||||
var items = Directory.list(path)
|
||||
|
||||
for (item in items) {
|
||||
var fullPath = "%(path)/%(item)"
|
||||
|
||||
if (Directory.exists(fullPath)) {
|
||||
var subResults = findByExtension.call(fullPath, ext)
|
||||
for (r in subResults) results.add(r)
|
||||
} else if (item.endsWith(ext)) {
|
||||
results.add(fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
var wrenFiles = findByExtension.call(".", ".wren")
|
||||
for (file in wrenFiles) {
|
||||
System.print(file)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Read JSON Configuration</h2>
|
||||
<pre><code>import "io" for File
|
||||
import "json" for Json
|
||||
|
||||
var loadConfig = Fn.new { |path, defaults|
|
||||
if (!File.exists(path)) {
|
||||
return defaults
|
||||
}
|
||||
|
||||
var content = File.read(path)
|
||||
var config = Json.parse(content)
|
||||
|
||||
for (key in defaults.keys) {
|
||||
if (!config.containsKey(key)) {
|
||||
config[key] = defaults[key]
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
var config = loadConfig.call("config.json", {
|
||||
"port": 8080,
|
||||
"debug": false
|
||||
})
|
||||
|
||||
System.print("Port: %(config["port"])")</code></pre>
|
||||
|
||||
<h2>Save JSON Configuration</h2>
|
||||
<pre><code>import "io" for File
|
||||
import "json" for Json
|
||||
|
||||
var config = {
|
||||
"database": "app.db",
|
||||
"port": 8080,
|
||||
"debug": true
|
||||
}
|
||||
|
||||
File.write("config.json", Json.stringify(config, 2))
|
||||
System.print("Configuration saved!")</code></pre>
|
||||
|
||||
<h2>Create Backup Copy</h2>
|
||||
<pre><code>import "io" for File
|
||||
import "datetime" for DateTime
|
||||
|
||||
var backup = Fn.new { |path|
|
||||
if (!File.exists(path)) {
|
||||
System.print("File not found: %(path)")
|
||||
return null
|
||||
}
|
||||
|
||||
var timestamp = DateTime.now().format("\%Y\%m\%d_\%H\%M\%S")
|
||||
var backupPath = "%(path).%(timestamp).bak"
|
||||
|
||||
File.copy(path, backupPath)
|
||||
System.print("Backup created: %(backupPath)")
|
||||
|
||||
return backupPath
|
||||
}
|
||||
|
||||
backup.call("important.txt")</code></pre>
|
||||
|
||||
<h2>Read User Input</h2>
|
||||
<pre><code>import "io" for Stdin
|
||||
|
||||
System.write("Enter your name: ")
|
||||
var name = Stdin.readLine()
|
||||
|
||||
System.print("Hello, %(name)!")</code></pre>
|
||||
|
||||
<h2>Interactive Menu</h2>
|
||||
<pre><code>import "io" for Stdin
|
||||
|
||||
System.print("Select an option:")
|
||||
System.print("1. Option A")
|
||||
System.print("2. Option B")
|
||||
System.print("3. Exit")
|
||||
|
||||
System.write("Choice: ")
|
||||
var choice = Stdin.readLine()
|
||||
|
||||
if (choice == "1") {
|
||||
System.print("You selected Option A")
|
||||
} else if (choice == "2") {
|
||||
System.print("You selected Option B")
|
||||
} else if (choice == "3") {
|
||||
System.print("Goodbye!")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Safe File Operations with Error Handling</h2>
|
||||
<pre><code>import "io" for File
|
||||
|
||||
var safeRead = Fn.new { |path|
|
||||
var fiber = Fiber.new { File.read(path) }
|
||||
var result = fiber.try()
|
||||
|
||||
if (fiber.error) {
|
||||
System.print("Error reading %(path): %(fiber.error)")
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var content = safeRead.call("maybe_exists.txt")
|
||||
if (content) {
|
||||
System.print("Content: %(content)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Calculate Directory Size</h2>
|
||||
<pre><code>import "io" for File, Directory
|
||||
|
||||
var dirSize = Fn.new { |path|
|
||||
var total = 0
|
||||
var items = Directory.list(path)
|
||||
|
||||
for (item in items) {
|
||||
var fullPath = "%(path)/%(item)"
|
||||
|
||||
if (Directory.exists(fullPath)) {
|
||||
total = total + dirSize.call(fullPath)
|
||||
} else if (File.exists(fullPath)) {
|
||||
total = total + File.size(fullPath)
|
||||
}
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
var size = dirSize.call(".")
|
||||
System.print("Total size: %(size) bytes")</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">See Also</div>
|
||||
<p>For full API documentation, see the <a href="../api/io.html">IO module reference</a>. For object-oriented path manipulation with glob, walk, and tree operations, see the <a href="../api/pathlib.html">pathlib module reference</a>.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="regex-patterns.html" class="prev">Regex Patterns</a>
|
||||
<a href="async-operations.html" class="next">Async Operations</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
337
manual/howto/http-requests.html
Normal file
337
manual/howto/http-requests.html
Normal file
@ -0,0 +1,337 @@
|
||||
<!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>Making HTTP Requests - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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">Template Rendering</a></li>
|
||||
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">How-To Guides</span>
|
||||
<ul>
|
||||
<li><a href="index.html">How-To List</a></li>
|
||||
<li><a href="http-requests.html" class="active">HTTP Requests</a></li>
|
||||
<li><a href="json-parsing.html">JSON Parsing</a></li>
|
||||
<li><a href="regex-patterns.html">Regex Patterns</a></li>
|
||||
<li><a href="file-operations.html">File Operations</a></li>
|
||||
<li><a href="async-operations.html">Async Operations</a></li>
|
||||
<li><a href="error-handling.html">Error Handling</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">How-To Guides</a>
|
||||
<span class="separator">/</span>
|
||||
<span>HTTP Requests</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Making HTTP Requests</h1>
|
||||
|
||||
<h2>Basic GET Request</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://api.example.com/data")
|
||||
System.print(response.body)</code></pre>
|
||||
|
||||
<h2>GET Request with JSON Response</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://jsonplaceholder.typicode.com/posts/1")
|
||||
var data = response.json
|
||||
|
||||
System.print("Title: %(data["title"])")</code></pre>
|
||||
|
||||
<h2>GET Request with Headers</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://api.example.com/data", {
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "Wren-CLI/1.0"
|
||||
})
|
||||
|
||||
System.print(response.body)</code></pre>
|
||||
|
||||
<h2>POST Request with JSON Body</h2>
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var data = {
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com"
|
||||
}
|
||||
|
||||
var response = Http.post(
|
||||
"https://api.example.com/users",
|
||||
Json.stringify(data),
|
||||
{"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
System.print("Status: %(response.statusCode)")
|
||||
System.print("Created: %(response.json)")</code></pre>
|
||||
|
||||
<h2>PUT Request</h2>
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var data = {
|
||||
"id": 1,
|
||||
"name": "Jane Doe",
|
||||
"email": "jane@example.com"
|
||||
}
|
||||
|
||||
var response = Http.put(
|
||||
"https://api.example.com/users/1",
|
||||
Json.stringify(data),
|
||||
{"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
System.print("Updated: %(response.statusCode == 200)")</code></pre>
|
||||
|
||||
<h2>DELETE Request</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.delete("https://api.example.com/users/1")
|
||||
System.print("Deleted: %(response.statusCode == 204)")</code></pre>
|
||||
|
||||
<h2>PATCH Request</h2>
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var response = Http.patch(
|
||||
"https://api.example.com/users/1",
|
||||
Json.stringify({"email": "newemail@example.com"}),
|
||||
{"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
System.print("Patched: %(response.statusCode)")</code></pre>
|
||||
|
||||
<h2>Bearer Token Authentication</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var token = "your-api-token"
|
||||
|
||||
var response = Http.get("https://api.example.com/protected", {
|
||||
"Authorization": "Bearer %(token)"
|
||||
})
|
||||
|
||||
System.print(response.json)</code></pre>
|
||||
|
||||
<h2>Basic Authentication</h2>
|
||||
<pre><code>import "http" for Http
|
||||
import "base64" for Base64
|
||||
|
||||
var username = "user"
|
||||
var password = "pass"
|
||||
var credentials = Base64.encode("%(username):%(password)")
|
||||
|
||||
var response = Http.get("https://api.example.com/protected", {
|
||||
"Authorization": "Basic %(credentials)"
|
||||
})
|
||||
|
||||
System.print(response.body)</code></pre>
|
||||
|
||||
<h2>API Key Authentication</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://api.example.com/data", {
|
||||
"X-API-Key": "your-api-key"
|
||||
})
|
||||
|
||||
System.print(response.body)</code></pre>
|
||||
|
||||
<h2>Check Response Status</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://api.example.com/data")
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
System.print("Success: %(response.json)")
|
||||
} else if (response.statusCode == 404) {
|
||||
System.print("Not found")
|
||||
} else if (response.statusCode >= 500) {
|
||||
System.print("Server error: %(response.statusCode)")
|
||||
} else {
|
||||
System.print("Error: %(response.statusCode)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Access Response Headers</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://api.example.com/data")
|
||||
|
||||
System.print("Content-Type: %(response.headers["content-type"])")
|
||||
System.print("All headers: %(response.headers)")</code></pre>
|
||||
|
||||
<h2>URL Query Parameters</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://api.example.com/search?q=wren&limit=10")
|
||||
|
||||
System.print(response.json)</code></pre>
|
||||
|
||||
<h2>Form URL Encoded POST</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var body = "username=john&password=secret"
|
||||
|
||||
var response = Http.post(
|
||||
"https://api.example.com/login",
|
||||
body,
|
||||
{"Content-Type": "application/x-www-form-urlencoded"}
|
||||
)
|
||||
|
||||
System.print(response.json)</code></pre>
|
||||
|
||||
<h2>Download File</h2>
|
||||
<pre><code>import "http" for Http
|
||||
import "io" for File
|
||||
|
||||
var response = Http.get("https://example.com/file.txt")
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
File.write("downloaded.txt", response.body)
|
||||
System.print("File downloaded!")
|
||||
}</code></pre>
|
||||
|
||||
<h2>HTTPS Request</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://secure.example.com/api")
|
||||
System.print(response.body)</code></pre>
|
||||
|
||||
<h2>Error Handling</h2>
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var fiber = Fiber.new {
|
||||
return Http.get("https://api.example.com/data")
|
||||
}
|
||||
|
||||
var response = fiber.try()
|
||||
|
||||
if (fiber.error) {
|
||||
System.print("Request failed: %(fiber.error)")
|
||||
} else if (response.statusCode >= 400) {
|
||||
System.print("HTTP error: %(response.statusCode)")
|
||||
} else {
|
||||
System.print("Success: %(response.json)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Retry on Failure</h2>
|
||||
<pre><code>import "http" for Http
|
||||
import "timer" for Timer
|
||||
|
||||
var fetchWithRetry = Fn.new { |url, maxRetries|
|
||||
var attempt = 0
|
||||
while (attempt < maxRetries) {
|
||||
var fiber = Fiber.new { Http.get(url) }
|
||||
var response = fiber.try()
|
||||
|
||||
if (!fiber.error && response.statusCode == 200) {
|
||||
return response
|
||||
}
|
||||
|
||||
attempt = attempt + 1
|
||||
if (attempt < maxRetries) {
|
||||
Timer.sleep(1000 * attempt)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
var response = fetchWithRetry.call("https://api.example.com/data", 3)
|
||||
if (response) {
|
||||
System.print(response.json)
|
||||
} else {
|
||||
System.print("Failed after 3 retries")
|
||||
}</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">See Also</div>
|
||||
<p>For a complete API client example, see the <a href="../tutorials/http-client.html">HTTP Client Tutorial</a>. For full API documentation, see the <a href="../api/http.html">HTTP module reference</a>.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="index.html" class="prev">How-To List</a>
|
||||
<a href="json-parsing.html" class="next">JSON Parsing</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
211
manual/howto/index.html
Normal file
211
manual/howto/index.html
Normal file
@ -0,0 +1,211 @@
|
||||
<!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>How-To Guides - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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">Template Rendering</a></li>
|
||||
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">How-To Guides</span>
|
||||
<ul>
|
||||
<li><a href="index.html" class="active">How-To List</a></li>
|
||||
<li><a href="http-requests.html">HTTP Requests</a></li>
|
||||
<li><a href="json-parsing.html">JSON Parsing</a></li>
|
||||
<li><a href="regex-patterns.html">Regex Patterns</a></li>
|
||||
<li><a href="file-operations.html">File Operations</a></li>
|
||||
<li><a href="async-operations.html">Async Operations</a></li>
|
||||
<li><a href="error-handling.html">Error Handling</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<span>How-To Guides</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>How-To Guides</h1>
|
||||
|
||||
<p>Quick, focused guides that show you how to accomplish specific tasks. Each guide provides working code examples you can copy and adapt for your projects.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="http-requests.html">Making HTTP Requests</a></h3>
|
||||
<p>GET, POST, PUT, DELETE requests with headers, authentication, and error handling.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">http</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="json-parsing.html">Working with JSON</a></h3>
|
||||
<p>Parse JSON strings, access nested data, create JSON output, and handle errors.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">json</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="regex-patterns.html">Using Regular Expressions</a></h3>
|
||||
<p>Match, search, replace, and split text with regex patterns.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">regex</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="file-operations.html">File Operations</a></h3>
|
||||
<p>Read, write, copy, and delete files. Work with directories and paths.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">io</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="async-operations.html">Async Programming</a></h3>
|
||||
<p>Use fibers for concurrent operations, parallel requests, and timeouts.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">fibers</span>
|
||||
<span class="tag">scheduler</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="error-handling.html">Error Handling</a></h3>
|
||||
<p>Catch errors with fibers, validate input, and handle edge cases gracefully.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">fibers</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>How-To vs Tutorials</h2>
|
||||
|
||||
<p><strong>Tutorials</strong> are learning-oriented. They walk you through building complete applications step by step, introducing concepts gradually.</p>
|
||||
|
||||
<p><strong>How-To Guides</strong> are goal-oriented. They assume you know the basics and need to accomplish a specific task quickly. Each guide focuses on one topic with copy-paste examples.</p>
|
||||
|
||||
<h2>Quick Reference</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Task</th>
|
||||
<th>Guide</th>
|
||||
<th>Key Functions</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fetch data from API</td>
|
||||
<td><a href="http-requests.html">HTTP Requests</a></td>
|
||||
<td><code>Http.get()</code>, <code>response.json</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Parse JSON string</td>
|
||||
<td><a href="json-parsing.html">JSON Parsing</a></td>
|
||||
<td><code>Json.parse()</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Validate email format</td>
|
||||
<td><a href="regex-patterns.html">Regex Patterns</a></td>
|
||||
<td><code>Regex.new().test()</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Read file contents</td>
|
||||
<td><a href="file-operations.html">File Operations</a></td>
|
||||
<td><code>File.read()</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Run tasks in parallel</td>
|
||||
<td><a href="async-operations.html">Async Operations</a></td>
|
||||
<td><code>Fiber.new { }</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Handle runtime errors</td>
|
||||
<td><a href="error-handling.html">Error Handling</a></td>
|
||||
<td><code>fiber.try()</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="../tutorials/cli-tool.html" class="prev">CLI Tool</a>
|
||||
<a href="http-requests.html" class="next">HTTP Requests</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
346
manual/howto/json-parsing.html
Normal file
346
manual/howto/json-parsing.html
Normal file
@ -0,0 +1,346 @@
|
||||
<!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>Working with JSON - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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">Template Rendering</a></li>
|
||||
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">How-To Guides</span>
|
||||
<ul>
|
||||
<li><a href="index.html">How-To List</a></li>
|
||||
<li><a href="http-requests.html">HTTP Requests</a></li>
|
||||
<li><a href="json-parsing.html" class="active">JSON Parsing</a></li>
|
||||
<li><a href="regex-patterns.html">Regex Patterns</a></li>
|
||||
<li><a href="file-operations.html">File Operations</a></li>
|
||||
<li><a href="async-operations.html">Async Operations</a></li>
|
||||
<li><a href="error-handling.html">Error Handling</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">How-To Guides</a>
|
||||
<span class="separator">/</span>
|
||||
<span>JSON Parsing</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Working with JSON</h1>
|
||||
|
||||
<h2>Parse JSON String</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var jsonStr = '{"name": "Alice", "age": 30}'
|
||||
var data = Json.parse(jsonStr)
|
||||
|
||||
System.print(data["name"]) // Alice
|
||||
System.print(data["age"]) // 30</code></pre>
|
||||
|
||||
<h2>Parse JSON Array</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var jsonStr = '[1, 2, 3, "four", true, null]'
|
||||
var items = Json.parse(jsonStr)
|
||||
|
||||
for (item in items) {
|
||||
System.print(item)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Access Nested Objects</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var jsonStr = '{"user": {"name": "Bob", "address": {"city": "NYC"}}}'
|
||||
var data = Json.parse(jsonStr)
|
||||
|
||||
System.print(data["user"]["name"]) // Bob
|
||||
System.print(data["user"]["address"]["city"]) // NYC</code></pre>
|
||||
|
||||
<h2>Convert Wren Object to JSON</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = {
|
||||
"name": "Charlie",
|
||||
"age": 25,
|
||||
"active": true
|
||||
}
|
||||
|
||||
var jsonStr = Json.stringify(data)
|
||||
System.print(jsonStr) // {"name":"Charlie","age":25,"active":true}</code></pre>
|
||||
|
||||
<h2>Pretty Print JSON</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = {
|
||||
"users": [
|
||||
{"name": "Alice", "age": 30},
|
||||
{"name": "Bob", "age": 25}
|
||||
]
|
||||
}
|
||||
|
||||
var pretty = Json.stringify(data, 2)
|
||||
System.print(pretty)</code></pre>
|
||||
|
||||
<p>Output:</p>
|
||||
<pre><code>{
|
||||
"users": [
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 30
|
||||
},
|
||||
{
|
||||
"name": "Bob",
|
||||
"age": 25
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
|
||||
<h2>Check if Key Exists</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = Json.parse('{"name": "Alice"}')
|
||||
|
||||
if (data.containsKey("name")) {
|
||||
System.print("Name: %(data["name"])")
|
||||
}
|
||||
|
||||
if (!data.containsKey("age")) {
|
||||
System.print("Age not specified")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Provide Default Values</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = Json.parse('{"name": "Alice"}')
|
||||
|
||||
var name = data["name"]
|
||||
var age = data.containsKey("age") ? data["age"] : 0
|
||||
var city = data.containsKey("city") ? data["city"] : "Unknown"
|
||||
|
||||
System.print("%(name), %(age), %(city)")</code></pre>
|
||||
|
||||
<h2>Iterate Over Object Keys</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = Json.parse('{"a": 1, "b": 2, "c": 3}')
|
||||
|
||||
for (key in data.keys) {
|
||||
System.print("%(key): %(data[key])")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Iterate Over Array</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var users = Json.parse('[{"name": "Alice"}, {"name": "Bob"}]')
|
||||
|
||||
for (i in 0...users.count) {
|
||||
System.print("%(i + 1). %(users[i]["name"])")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Modify JSON Data</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = Json.parse('{"name": "Alice", "age": 30}')
|
||||
|
||||
data["age"] = 31
|
||||
data["email"] = "alice@example.com"
|
||||
data.remove("name")
|
||||
|
||||
System.print(Json.stringify(data))</code></pre>
|
||||
|
||||
<h2>Parse JSON from File</h2>
|
||||
<pre><code>import "json" for Json
|
||||
import "io" for File
|
||||
|
||||
var content = File.read("config.json")
|
||||
var config = Json.parse(content)
|
||||
|
||||
System.print(config["setting"])</code></pre>
|
||||
|
||||
<h2>Write JSON to File</h2>
|
||||
<pre><code>import "json" for Json
|
||||
import "io" for File
|
||||
|
||||
var data = {
|
||||
"database": "myapp.db",
|
||||
"port": 8080,
|
||||
"debug": true
|
||||
}
|
||||
|
||||
File.write("config.json", Json.stringify(data, 2))</code></pre>
|
||||
|
||||
<h2>Handle Parse Errors</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var jsonStr = "invalid json {"
|
||||
|
||||
var fiber = Fiber.new { Json.parse(jsonStr) }
|
||||
var result = fiber.try()
|
||||
|
||||
if (fiber.error) {
|
||||
System.print("Parse error: %(fiber.error)")
|
||||
} else {
|
||||
System.print(result)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Work with Null Values</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = Json.parse('{"name": "Alice", "address": null}')
|
||||
|
||||
if (data["address"] == null) {
|
||||
System.print("No address provided")
|
||||
}
|
||||
|
||||
var output = {"value": null}
|
||||
System.print(Json.stringify(output)) // {"value":null}</code></pre>
|
||||
|
||||
<h2>Build JSON Array Dynamically</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var users = []
|
||||
|
||||
users.add({"name": "Alice", "role": "admin"})
|
||||
users.add({"name": "Bob", "role": "user"})
|
||||
users.add({"name": "Charlie", "role": "user"})
|
||||
|
||||
System.print(Json.stringify(users, 2))</code></pre>
|
||||
|
||||
<h2>Filter JSON Array</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var users = Json.parse('[
|
||||
{"name": "Alice", "age": 30},
|
||||
{"name": "Bob", "age": 17},
|
||||
{"name": "Charlie", "age": 25}
|
||||
]')
|
||||
|
||||
var adults = []
|
||||
for (user in users) {
|
||||
if (user["age"] >= 18) {
|
||||
adults.add(user)
|
||||
}
|
||||
}
|
||||
|
||||
System.print("Adults: %(Json.stringify(adults))")</code></pre>
|
||||
|
||||
<h2>Transform JSON Data</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var users = Json.parse('[
|
||||
{"firstName": "Alice", "lastName": "Smith"},
|
||||
{"firstName": "Bob", "lastName": "Jones"}
|
||||
]')
|
||||
|
||||
var names = []
|
||||
for (user in users) {
|
||||
names.add("%(user["firstName"]) %(user["lastName"])")
|
||||
}
|
||||
|
||||
System.print(names.join(", "))</code></pre>
|
||||
|
||||
<h2>Merge JSON Objects</h2>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var defaults = {"theme": "light", "language": "en", "timeout": 30}
|
||||
var userPrefs = {"theme": "dark"}
|
||||
|
||||
var config = {}
|
||||
for (key in defaults.keys) {
|
||||
config[key] = defaults[key]
|
||||
}
|
||||
for (key in userPrefs.keys) {
|
||||
config[key] = userPrefs[key]
|
||||
}
|
||||
|
||||
System.print(Json.stringify(config, 2))</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">See Also</div>
|
||||
<p>For full API documentation, see the <a href="../api/json.html">JSON module reference</a>.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="http-requests.html" class="prev">HTTP Requests</a>
|
||||
<a href="regex-patterns.html" class="next">Regex Patterns</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
340
manual/howto/regex-patterns.html
Normal file
340
manual/howto/regex-patterns.html
Normal file
@ -0,0 +1,340 @@
|
||||
<!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>Using Regular Expressions - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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">Template Rendering</a></li>
|
||||
<li><a href="../tutorials/cli-tool.html">CLI Tool</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">How-To Guides</span>
|
||||
<ul>
|
||||
<li><a href="index.html">How-To List</a></li>
|
||||
<li><a href="http-requests.html">HTTP Requests</a></li>
|
||||
<li><a href="json-parsing.html">JSON Parsing</a></li>
|
||||
<li><a href="regex-patterns.html" class="active">Regex Patterns</a></li>
|
||||
<li><a href="file-operations.html">File Operations</a></li>
|
||||
<li><a href="async-operations.html">Async Operations</a></li>
|
||||
<li><a href="error-handling.html">Error Handling</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">How-To Guides</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Regex Patterns</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Using Regular Expressions</h1>
|
||||
|
||||
<h2>Test if String Matches Pattern</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var pattern = Regex.new("^hello")
|
||||
|
||||
System.print(pattern.test("hello world")) // true
|
||||
System.print(pattern.test("say hello")) // false</code></pre>
|
||||
|
||||
<h2>Find First Match</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var pattern = Regex.new("\\d+")
|
||||
var match = pattern.match("Order 12345 shipped")
|
||||
|
||||
if (match) {
|
||||
System.print(match.text) // 12345
|
||||
System.print(match.start) // 6
|
||||
System.print(match.end) // 11
|
||||
}</code></pre>
|
||||
|
||||
<h2>Find All Matches</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var pattern = Regex.new("\\d+")
|
||||
var matches = pattern.matchAll("Items: 10, 20, 30")
|
||||
|
||||
for (match in matches) {
|
||||
System.print(match.text)
|
||||
}
|
||||
// 10
|
||||
// 20
|
||||
// 30</code></pre>
|
||||
|
||||
<h2>Capture Groups</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var pattern = Regex.new("(\\w+)@(\\w+\\.\\w+)")
|
||||
var match = pattern.match("Contact: alice@example.com")
|
||||
|
||||
if (match) {
|
||||
System.print(match.group(0)) // alice@example.com
|
||||
System.print(match.group(1)) // alice
|
||||
System.print(match.group(2)) // example.com
|
||||
}</code></pre>
|
||||
|
||||
<h2>Replace Matches</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var pattern = Regex.new("\\bcat\\b")
|
||||
var result = pattern.replace("The cat sat on the cat mat", "dog")
|
||||
|
||||
System.print(result) // The dog sat on the dog mat</code></pre>
|
||||
|
||||
<h2>Replace with Callback</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var pattern = Regex.new("\\d+")
|
||||
var result = pattern.replace("a1b2c3", Fn.new { |match|
|
||||
return "[%(match.text)]"
|
||||
})
|
||||
|
||||
System.print(result) // a[1]b[2]c[3]</code></pre>
|
||||
|
||||
<h2>Split String</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var pattern = Regex.new("[,;\\s]+")
|
||||
var parts = pattern.split("apple, banana; cherry date")
|
||||
|
||||
for (part in parts) {
|
||||
System.print(part)
|
||||
}
|
||||
// apple
|
||||
// banana
|
||||
// cherry
|
||||
// date</code></pre>
|
||||
|
||||
<h2>Case Insensitive Matching</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var pattern = Regex.new("hello", "i")
|
||||
|
||||
System.print(pattern.test("Hello World")) // true
|
||||
System.print(pattern.test("HELLO")) // true</code></pre>
|
||||
|
||||
<h2>Multiline Matching</h2>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var text = "Line 1\nLine 2\nLine 3"
|
||||
var pattern = Regex.new("^Line", "m")
|
||||
|
||||
var matches = pattern.matchAll(text)
|
||||
System.print(matches.count) // 3</code></pre>
|
||||
|
||||
<h2>Common Patterns</h2>
|
||||
|
||||
<h3>Validate Email</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var emailPattern = Regex.new("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
|
||||
|
||||
System.print(emailPattern.test("user@example.com")) // true
|
||||
System.print(emailPattern.test("invalid-email")) // false</code></pre>
|
||||
|
||||
<h3>Validate URL</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var urlPattern = Regex.new("^https?://[a-zA-Z0-9.-]+(/.*)?$")
|
||||
|
||||
System.print(urlPattern.test("https://example.com")) // true
|
||||
System.print(urlPattern.test("http://example.com/path")) // true
|
||||
System.print(urlPattern.test("ftp://invalid")) // false</code></pre>
|
||||
|
||||
<h3>Validate Phone Number</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var phonePattern = Regex.new("^\\+?\\d{1,3}[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$")
|
||||
|
||||
System.print(phonePattern.test("+1-555-123-4567")) // true
|
||||
System.print(phonePattern.test("(555) 123-4567")) // true</code></pre>
|
||||
|
||||
<h3>Extract Numbers</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var numberPattern = Regex.new("-?\\d+\\.?\\d*")
|
||||
var text = "Temperature: -5.5 to 32.0 degrees"
|
||||
|
||||
var matches = numberPattern.matchAll(text)
|
||||
for (match in matches) {
|
||||
System.print(match.text)
|
||||
}
|
||||
// -5.5
|
||||
// 32.0</code></pre>
|
||||
|
||||
<h3>Extract Hashtags</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var hashtagPattern = Regex.new("#\\w+")
|
||||
var text = "Check out #wren and #programming!"
|
||||
|
||||
var matches = hashtagPattern.matchAll(text)
|
||||
for (match in matches) {
|
||||
System.print(match.text)
|
||||
}
|
||||
// #wren
|
||||
// #programming</code></pre>
|
||||
|
||||
<h3>Remove HTML Tags</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var tagPattern = Regex.new("<[^>]+>")
|
||||
var html = "<p>Hello <b>World</b>!</p>"
|
||||
|
||||
var text = tagPattern.replace(html, "")
|
||||
System.print(text) // Hello World!</code></pre>
|
||||
|
||||
<h3>Validate Password Strength</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var hasUpper = Regex.new("[A-Z]")
|
||||
var hasLower = Regex.new("[a-z]")
|
||||
var hasDigit = Regex.new("\\d")
|
||||
var hasSpecial = Regex.new("[!@#$%^&*]")
|
||||
var minLength = 8
|
||||
|
||||
var validatePassword = Fn.new { |password|
|
||||
if (password.count < minLength) return false
|
||||
if (!hasUpper.test(password)) return false
|
||||
if (!hasLower.test(password)) return false
|
||||
if (!hasDigit.test(password)) return false
|
||||
if (!hasSpecial.test(password)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
System.print(validatePassword.call("Weak")) // false
|
||||
System.print(validatePassword.call("Strong@Pass1")) // true</code></pre>
|
||||
|
||||
<h3>Parse Log Lines</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var logPattern = Regex.new("\\[(\\d{4}-\\d{2}-\\d{2})\\]\\s+(\\w+):\\s+(.+)")
|
||||
|
||||
var line = "[2024-01-15] ERROR: Connection failed"
|
||||
var match = logPattern.match(line)
|
||||
|
||||
if (match) {
|
||||
System.print("Date: %(match.group(1))") // 2024-01-15
|
||||
System.print("Level: %(match.group(2))") // ERROR
|
||||
System.print("Message: %(match.group(3))") // Connection failed
|
||||
}</code></pre>
|
||||
|
||||
<h3>Validate IP Address</h3>
|
||||
<pre><code>import "regex" for Regex
|
||||
|
||||
var ipPattern = Regex.new("^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$")
|
||||
|
||||
System.print(ipPattern.test("192.168.1.1")) // true
|
||||
System.print(ipPattern.test("256.1.1.1")) // false
|
||||
System.print(ipPattern.test("10.0.0.255")) // true</code></pre>
|
||||
|
||||
<h2>Pattern Syntax Quick Reference</h2>
|
||||
|
||||
<table>
|
||||
<tr><th>Pattern</th><th>Description</th></tr>
|
||||
<tr><td><code>.</code></td><td>Any character except newline</td></tr>
|
||||
<tr><td><code>\\d</code></td><td>Digit (0-9)</td></tr>
|
||||
<tr><td><code>\\w</code></td><td>Word character (a-z, A-Z, 0-9, _)</td></tr>
|
||||
<tr><td><code>\\s</code></td><td>Whitespace</td></tr>
|
||||
<tr><td><code>^</code></td><td>Start of string/line</td></tr>
|
||||
<tr><td><code>$</code></td><td>End of string/line</td></tr>
|
||||
<tr><td><code>*</code></td><td>Zero or more</td></tr>
|
||||
<tr><td><code>+</code></td><td>One or more</td></tr>
|
||||
<tr><td><code>?</code></td><td>Zero or one</td></tr>
|
||||
<tr><td><code>{n,m}</code></td><td>Between n and m times</td></tr>
|
||||
<tr><td><code>[abc]</code></td><td>Character class</td></tr>
|
||||
<tr><td><code>[^abc]</code></td><td>Negated character class</td></tr>
|
||||
<tr><td><code>(group)</code></td><td>Capture group</td></tr>
|
||||
<tr><td><code>a|b</code></td><td>Alternation (a or b)</td></tr>
|
||||
<tr><td><code>\\b</code></td><td>Word boundary</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">See Also</div>
|
||||
<p>For full API documentation, see the <a href="../api/regex.html">Regex module reference</a>.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="json-parsing.html" class="prev">JSON Parsing</a>
|
||||
<a href="file-operations.html" class="next">File Operations</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
194
manual/index.html
Normal file
194
manual/index.html
Normal file
@ -0,0 +1,194 @@
|
||||
<!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>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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<div class="hero">
|
||||
<h1>Wren-CLI Manual</h1>
|
||||
<p>A complete reference for the Wren scripting language command-line interface with networking, async I/O, and extended module support.</p>
|
||||
<div class="hero-buttons">
|
||||
<a href="getting-started/index.html" class="primary-btn">Get Started</a>
|
||||
<a href="api/index.html" class="secondary-btn">API Reference</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article>
|
||||
<h2>What is Wren-CLI?</h2>
|
||||
<p>Wren-CLI is a command-line interface for the <a href="https://wren.io">Wren programming language</a>, extended with powerful modules for networking, file I/O, databases, and more. It provides an async event loop powered by libuv, making it suitable for building servers, automation scripts, and command-line tools.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="getting-started/index.html">Getting Started</a></h3>
|
||||
<p>Install Wren-CLI, write your first script, and learn the basics of the language.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="language/index.html">Language Reference</a></h3>
|
||||
<p>Learn about classes, methods, control flow, fibers, and the module system.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="api/index.html">API Reference</a></h3>
|
||||
<p>Complete documentation for all 21 built-in modules including HTTP, WebSocket, and SQLite.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><a href="tutorials/index.html">Tutorials</a></h3>
|
||||
<p>Step-by-step guides for building real applications with Wren-CLI.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Available Modules</h2>
|
||||
<p>Wren-CLI provides a rich set of modules for common programming tasks:</p>
|
||||
|
||||
<h3>Networking</h3>
|
||||
<div class="module-grid">
|
||||
<a href="api/http.html" class="module-card">http</a>
|
||||
<a href="api/websocket.html" class="module-card">websocket</a>
|
||||
<a href="api/tls.html" class="module-card">tls</a>
|
||||
<a href="api/net.html" class="module-card">net</a>
|
||||
<a href="api/dns.html" class="module-card">dns</a>
|
||||
</div>
|
||||
|
||||
<h3>Data Processing</h3>
|
||||
<div class="module-grid">
|
||||
<a href="api/json.html" class="module-card">json</a>
|
||||
<a href="api/base64.html" class="module-card">base64</a>
|
||||
<a href="api/regex.html" class="module-card">regex</a>
|
||||
<a href="api/jinja.html" class="module-card">jinja</a>
|
||||
<a href="api/crypto.html" class="module-card">crypto</a>
|
||||
</div>
|
||||
|
||||
<h3>System</h3>
|
||||
<div class="module-grid">
|
||||
<a href="api/os.html" class="module-card">os</a>
|
||||
<a href="api/env.html" class="module-card">env</a>
|
||||
<a href="api/signal.html" class="module-card">signal</a>
|
||||
<a href="api/subprocess.html" class="module-card">subprocess</a>
|
||||
<a href="api/io.html" class="module-card">io</a>
|
||||
<a href="api/pathlib.html" class="module-card">pathlib</a>
|
||||
</div>
|
||||
|
||||
<h3>Data & Time</h3>
|
||||
<div class="module-grid">
|
||||
<a href="api/sqlite.html" class="module-card">sqlite</a>
|
||||
<a href="api/datetime.html" class="module-card">datetime</a>
|
||||
<a href="api/timer.html" class="module-card">timer</a>
|
||||
<a href="api/math.html" class="module-card">math</a>
|
||||
<a href="api/scheduler.html" class="module-card">scheduler</a>
|
||||
</div>
|
||||
|
||||
<h2>Quick Example</h2>
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var response = Http.get("https://api.github.com/users/wren-lang")
|
||||
var data = Json.parse(response.body)
|
||||
|
||||
System.print("User: %(data["login"])")
|
||||
System.print("Repos: %(data["public_repos"])")</code></pre>
|
||||
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li><strong>Async I/O</strong> - Non-blocking operations powered by libuv</li>
|
||||
<li><strong>HTTP/HTTPS</strong> - Full HTTP client with TLS support</li>
|
||||
<li><strong>WebSocket</strong> - Client and server WebSocket support</li>
|
||||
<li><strong>SQLite</strong> - Embedded database for persistent storage</li>
|
||||
<li><strong>Templates</strong> - Jinja2-compatible template engine</li>
|
||||
<li><strong>Regex</strong> - Full regular expression support</li>
|
||||
<li><strong>Cross-platform</strong> - Runs on Linux, macOS, and FreeBSD</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<span></span>
|
||||
<a href="getting-started/index.html" class="next">Getting Started</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
112
manual/js/main.js
Normal file
112
manual/js/main.js
Normal file
@ -0,0 +1,112 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function initSidebar() {
|
||||
var toggle = document.querySelector('.mobile-menu-toggle');
|
||||
var sidebar = document.querySelector('.sidebar');
|
||||
var overlay = document.createElement('div');
|
||||
overlay.className = 'sidebar-overlay';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
if (toggle) {
|
||||
toggle.addEventListener('click', function() {
|
||||
sidebar.classList.toggle('open');
|
||||
overlay.classList.toggle('visible');
|
||||
});
|
||||
}
|
||||
|
||||
overlay.addEventListener('click', function() {
|
||||
sidebar.classList.remove('open');
|
||||
overlay.classList.remove('visible');
|
||||
});
|
||||
|
||||
var currentPath = window.location.pathname;
|
||||
var links = document.querySelectorAll('.sidebar-nav a');
|
||||
links.forEach(function(link) {
|
||||
var href = link.getAttribute('href');
|
||||
if (href && currentPath.endsWith(href.replace(/^\.\.\//, '').replace(/^\.\//, ''))) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initCopyButtons() {
|
||||
var codeBlocks = document.querySelectorAll('pre');
|
||||
codeBlocks.forEach(function(pre) {
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.className = 'code-block';
|
||||
pre.parentNode.insertBefore(wrapper, pre);
|
||||
wrapper.appendChild(pre);
|
||||
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'copy-btn';
|
||||
btn.textContent = 'Copy';
|
||||
wrapper.appendChild(btn);
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
var code = pre.querySelector('code');
|
||||
var text = code ? code.textContent : pre.textContent;
|
||||
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
btn.textContent = 'Copied!';
|
||||
btn.classList.add('copied');
|
||||
setTimeout(function() {
|
||||
btn.textContent = 'Copy';
|
||||
btn.classList.remove('copied');
|
||||
}, 2000);
|
||||
}).catch(function() {
|
||||
btn.textContent = 'Failed';
|
||||
setTimeout(function() {
|
||||
btn.textContent = 'Copy';
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initSmoothScroll() {
|
||||
document.querySelectorAll('a[href^="#"]').forEach(function(anchor) {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
var targetId = this.getAttribute('href').slice(1);
|
||||
var target = document.getElementById(targetId);
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
target.scrollIntoView({ behavior: 'smooth' });
|
||||
history.pushState(null, null, '#' + targetId);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function highlightCode() {
|
||||
var keywords = ['import', 'for', 'class', 'static', 'var', 'if', 'else', 'while',
|
||||
'return', 'true', 'false', 'null', 'this', 'super', 'is', 'new',
|
||||
'foreign', 'construct', 'break', 'continue', 'in'];
|
||||
|
||||
document.querySelectorAll('pre code').forEach(function(block) {
|
||||
if (block.classList.contains('highlighted')) return;
|
||||
block.classList.add('highlighted');
|
||||
|
||||
var html = block.innerHTML;
|
||||
|
||||
html = html.replace(/(\/\/[^\n]*)/g, '<span style="color:#6a9955">$1</span>');
|
||||
html = html.replace(/("(?:[^"\\]|\\.)*")/g, '<span style="color:#ce9178">$1</span>');
|
||||
|
||||
keywords.forEach(function(kw) {
|
||||
var regex = new RegExp('\\b(' + kw + ')\\b', 'g');
|
||||
html = html.replace(regex, '<span style="color:#569cd6">$1</span>');
|
||||
});
|
||||
|
||||
block.innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initSidebar();
|
||||
initCopyButtons();
|
||||
initSmoothScroll();
|
||||
highlightCode();
|
||||
});
|
||||
})();
|
||||
425
manual/language/classes.html
Normal file
425
manual/language/classes.html
Normal file
@ -0,0 +1,425 @@
|
||||
<!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>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="index.html">Syntax Overview</a></li>
|
||||
<li><a href="classes.html" class="active">Classes</a></li>
|
||||
<li><a href="methods.html">Methods</a></li>
|
||||
<li><a href="control-flow.html">Control Flow</a></li>
|
||||
<li><a href="fibers.html">Fibers</a></li>
|
||||
<li><a href="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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Language</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Classes</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Classes</h1>
|
||||
|
||||
<p>Wren is a class-based object-oriented language. Everything in Wren is an object, and every object is an instance of a class.</p>
|
||||
|
||||
<h2>Defining Classes</h2>
|
||||
<p>Define a class with the <code>class</code> keyword:</p>
|
||||
<pre><code>class Animal {
|
||||
}</code></pre>
|
||||
|
||||
<p>This creates a class named <code>Animal</code> with no methods or fields.</p>
|
||||
|
||||
<h2>Constructors</h2>
|
||||
<p>Constructors create new instances. Define them with <code>construct</code>:</p>
|
||||
<pre><code>class Person {
|
||||
construct new(name, age) {
|
||||
_name = name
|
||||
_age = age
|
||||
}
|
||||
}
|
||||
|
||||
var alice = Person.new("Alice", 30)</code></pre>
|
||||
|
||||
<p>A class can have multiple named constructors:</p>
|
||||
<pre><code>class Point {
|
||||
construct new(x, y) {
|
||||
_x = x
|
||||
_y = y
|
||||
}
|
||||
|
||||
construct origin() {
|
||||
_x = 0
|
||||
_y = 0
|
||||
}
|
||||
|
||||
construct fromList(list) {
|
||||
_x = list[0]
|
||||
_y = list[1]
|
||||
}
|
||||
}
|
||||
|
||||
var p1 = Point.new(3, 4)
|
||||
var p2 = Point.origin()
|
||||
var p3 = Point.fromList([5, 6])</code></pre>
|
||||
|
||||
<h2>Fields</h2>
|
||||
<p>Instance fields are prefixed with <code>_</code>. They are private to the class:</p>
|
||||
<pre><code>class Counter {
|
||||
construct new() {
|
||||
_count = 0
|
||||
}
|
||||
|
||||
increment() {
|
||||
_count = _count + 1
|
||||
}
|
||||
|
||||
count { _count }
|
||||
}</code></pre>
|
||||
|
||||
<p>Fields are not declared; they are created when first assigned.</p>
|
||||
|
||||
<h2>Getters and Setters</h2>
|
||||
<p>Getters are methods without parentheses:</p>
|
||||
<pre><code>class Circle {
|
||||
construct new(radius) {
|
||||
_radius = radius
|
||||
}
|
||||
|
||||
radius { _radius }
|
||||
area { 3.14159 * _radius * _radius }
|
||||
}
|
||||
|
||||
var c = Circle.new(5)
|
||||
System.print(c.radius) // 5
|
||||
System.print(c.area) // 78.53975</code></pre>
|
||||
|
||||
<p>Setters use <code>=</code> suffix:</p>
|
||||
<pre><code>class Circle {
|
||||
construct new(radius) {
|
||||
_radius = radius
|
||||
}
|
||||
|
||||
radius { _radius }
|
||||
radius=(value) { _radius = value }
|
||||
}
|
||||
|
||||
var c = Circle.new(5)
|
||||
c.radius = 10
|
||||
System.print(c.radius) // 10</code></pre>
|
||||
|
||||
<h2>Methods</h2>
|
||||
<p>Methods are defined inside the class body:</p>
|
||||
<pre><code>class Rectangle {
|
||||
construct new(width, height) {
|
||||
_width = width
|
||||
_height = height
|
||||
}
|
||||
|
||||
area() {
|
||||
return _width * _height
|
||||
}
|
||||
|
||||
perimeter() {
|
||||
return 2 * (_width + _height)
|
||||
}
|
||||
}
|
||||
|
||||
var rect = Rectangle.new(4, 5)
|
||||
System.print(rect.area()) // 20
|
||||
System.print(rect.perimeter()) // 18</code></pre>
|
||||
|
||||
<h2>Static Members</h2>
|
||||
<p>Static methods and fields belong to the class, not instances:</p>
|
||||
<pre><code>class Math {
|
||||
static pi { 3.14159 }
|
||||
|
||||
static square(x) {
|
||||
return x * x
|
||||
}
|
||||
|
||||
static cube(x) {
|
||||
return x * x * x
|
||||
}
|
||||
}
|
||||
|
||||
System.print(Math.pi) // 3.14159
|
||||
System.print(Math.square(4)) // 16
|
||||
System.print(Math.cube(3)) // 27</code></pre>
|
||||
|
||||
<p>Static fields use double underscore:</p>
|
||||
<pre><code>class Counter {
|
||||
static count { __count }
|
||||
|
||||
static increment() {
|
||||
if (__count == null) __count = 0
|
||||
__count = __count + 1
|
||||
}
|
||||
}
|
||||
|
||||
Counter.increment()
|
||||
Counter.increment()
|
||||
System.print(Counter.count) // 2</code></pre>
|
||||
|
||||
<h2>Inheritance</h2>
|
||||
<p>Classes can inherit from a single superclass using <code>is</code>:</p>
|
||||
<pre><code>class Animal {
|
||||
construct new(name) {
|
||||
_name = name
|
||||
}
|
||||
|
||||
name { _name }
|
||||
|
||||
speak() {
|
||||
System.print("...")
|
||||
}
|
||||
}
|
||||
|
||||
class Dog is Animal {
|
||||
construct new(name, breed) {
|
||||
super(name)
|
||||
_breed = breed
|
||||
}
|
||||
|
||||
breed { _breed }
|
||||
|
||||
speak() {
|
||||
System.print("Woof!")
|
||||
}
|
||||
}
|
||||
|
||||
var dog = Dog.new("Rex", "German Shepherd")
|
||||
System.print(dog.name) // Rex
|
||||
System.print(dog.breed) // German Shepherd
|
||||
dog.speak() // Woof!</code></pre>
|
||||
|
||||
<h3>Calling Super</h3>
|
||||
<p>Use <code>super</code> to call the superclass constructor or methods:</p>
|
||||
<pre><code>class Parent {
|
||||
construct new() {
|
||||
_value = 10
|
||||
}
|
||||
|
||||
value { _value }
|
||||
|
||||
describe() {
|
||||
System.print("Parent value: %(_value)")
|
||||
}
|
||||
}
|
||||
|
||||
class Child is Parent {
|
||||
construct new() {
|
||||
super()
|
||||
_extra = 20
|
||||
}
|
||||
|
||||
describe() {
|
||||
super.describe()
|
||||
System.print("Child extra: %(_extra)")
|
||||
}
|
||||
}
|
||||
|
||||
var child = Child.new()
|
||||
child.describe()
|
||||
// Output:
|
||||
// Parent value: 10
|
||||
// Child extra: 20</code></pre>
|
||||
|
||||
<h2>This</h2>
|
||||
<p>Use <code>this</code> to refer to the current instance:</p>
|
||||
<pre><code>class Node {
|
||||
construct new(value) {
|
||||
_value = value
|
||||
_next = null
|
||||
}
|
||||
|
||||
value { _value }
|
||||
next { _next }
|
||||
|
||||
append(value) {
|
||||
_next = Node.new(value)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
var n = Node.new(1).append(2).append(3)</code></pre>
|
||||
|
||||
<h2>Object Class</h2>
|
||||
<p>All classes implicitly inherit from <code>Object</code>:</p>
|
||||
<pre><code>class Foo {}
|
||||
|
||||
System.print(Foo is Class) // true
|
||||
System.print(Foo.supertype) // Object</code></pre>
|
||||
|
||||
<h2>Type Checking</h2>
|
||||
<p>Use <code>is</code> to check if an object is an instance of a class:</p>
|
||||
<pre><code>var dog = Dog.new("Rex", "Shepherd")
|
||||
|
||||
System.print(dog is Dog) // true
|
||||
System.print(dog is Animal) // true
|
||||
System.print(dog is Object) // true
|
||||
System.print(dog is String) // false</code></pre>
|
||||
|
||||
<p>Get the class of an object with <code>type</code>:</p>
|
||||
<pre><code>System.print(dog.type) // Dog
|
||||
System.print(dog.type.name) // Dog
|
||||
System.print(dog.type.supertype) // Animal</code></pre>
|
||||
|
||||
<h2>Foreign Classes</h2>
|
||||
<p>Foreign classes are implemented in C. They can hold native data:</p>
|
||||
<pre><code>foreign class Socket {
|
||||
construct new() {}
|
||||
foreign connect(host, port)
|
||||
foreign send(data)
|
||||
foreign receive()
|
||||
foreign close()
|
||||
}</code></pre>
|
||||
|
||||
<p>Foreign classes are used by built-in modules to provide native functionality.</p>
|
||||
|
||||
<h2>Complete Example</h2>
|
||||
<pre><code>class Shape {
|
||||
construct new() {}
|
||||
|
||||
area { 0 }
|
||||
perimeter { 0 }
|
||||
|
||||
describe() {
|
||||
System.print("Area: %(area)")
|
||||
System.print("Perimeter: %(perimeter)")
|
||||
}
|
||||
}
|
||||
|
||||
class Rectangle is Shape {
|
||||
construct new(width, height) {
|
||||
_width = width
|
||||
_height = height
|
||||
}
|
||||
|
||||
width { _width }
|
||||
height { _height }
|
||||
area { _width * _height }
|
||||
perimeter { 2 * (_width + _height) }
|
||||
}
|
||||
|
||||
class Square is Rectangle {
|
||||
construct new(side) {
|
||||
super(side, side)
|
||||
}
|
||||
}
|
||||
|
||||
class Circle is Shape {
|
||||
construct new(radius) {
|
||||
_radius = radius
|
||||
}
|
||||
|
||||
static pi { 3.14159 }
|
||||
|
||||
radius { _radius }
|
||||
area { Circle.pi * _radius * _radius }
|
||||
perimeter { 2 * Circle.pi * _radius }
|
||||
}
|
||||
|
||||
var shapes = [
|
||||
Rectangle.new(4, 5),
|
||||
Square.new(3),
|
||||
Circle.new(2)
|
||||
]
|
||||
|
||||
for (shape in shapes) {
|
||||
System.print("%(shape.type.name):")
|
||||
shape.describe()
|
||||
System.print("")
|
||||
}</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="index.html" class="prev">Syntax Overview</a>
|
||||
<a href="methods.html" class="next">Methods</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
335
manual/language/control-flow.html
Normal file
335
manual/language/control-flow.html
Normal file
@ -0,0 +1,335 @@
|
||||
<!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>Control Flow - 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="index.html">Syntax Overview</a></li>
|
||||
<li><a href="classes.html">Classes</a></li>
|
||||
<li><a href="methods.html">Methods</a></li>
|
||||
<li><a href="control-flow.html" class="active">Control Flow</a></li>
|
||||
<li><a href="fibers.html">Fibers</a></li>
|
||||
<li><a href="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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Language</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Control Flow</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Control Flow</h1>
|
||||
|
||||
<p>Wren provides standard control flow constructs for conditionals, loops, and early exit.</p>
|
||||
|
||||
<h2>Truthiness</h2>
|
||||
<p>Before covering control flow, understand how Wren evaluates truthiness:</p>
|
||||
<ul>
|
||||
<li><code>false</code> is falsy</li>
|
||||
<li><code>null</code> is falsy</li>
|
||||
<li>Everything else is truthy (including <code>0</code>, <code>""</code>, <code>[]</code>)</li>
|
||||
</ul>
|
||||
<pre><code>if (0) System.print("0 is truthy")
|
||||
if ("") System.print("empty string is truthy")
|
||||
if ([]) System.print("empty list is truthy")
|
||||
if (false) System.print("false is falsy") // Not printed
|
||||
if (null) System.print("null is falsy") // Not printed</code></pre>
|
||||
|
||||
<h2>If Statements</h2>
|
||||
<p>Basic conditional execution:</p>
|
||||
<pre><code>if (condition) {
|
||||
System.print("condition is true")
|
||||
}</code></pre>
|
||||
|
||||
<h3>If-Else</h3>
|
||||
<pre><code>if (score >= 90) {
|
||||
System.print("A")
|
||||
} else {
|
||||
System.print("Not A")
|
||||
}</code></pre>
|
||||
|
||||
<h3>If-Else If-Else</h3>
|
||||
<pre><code>if (score >= 90) {
|
||||
System.print("A")
|
||||
} else if (score >= 80) {
|
||||
System.print("B")
|
||||
} else if (score >= 70) {
|
||||
System.print("C")
|
||||
} else {
|
||||
System.print("F")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Single Expression</h3>
|
||||
<p>For single expressions, braces are optional:</p>
|
||||
<pre><code>if (x > 0) System.print("positive")</code></pre>
|
||||
|
||||
<h2>Ternary Operator</h2>
|
||||
<p>For inline conditionals:</p>
|
||||
<pre><code>var status = age >= 18 ? "adult" : "minor"
|
||||
var max = a > b ? a : b</code></pre>
|
||||
|
||||
<h2>Logical Operators</h2>
|
||||
|
||||
<h3>And (&&)</h3>
|
||||
<p>Returns the first falsy value or the last value:</p>
|
||||
<pre><code>System.print(true && false) // false
|
||||
System.print(true && true) // true
|
||||
System.print(1 && 2) // 2
|
||||
System.print(null && 1) // null</code></pre>
|
||||
|
||||
<h3>Or (||)</h3>
|
||||
<p>Returns the first truthy value or the last value:</p>
|
||||
<pre><code>System.print(false || true) // true
|
||||
System.print(false || false) // false
|
||||
System.print(null || "default") // default
|
||||
System.print(1 || 2) // 1</code></pre>
|
||||
|
||||
<p>Use <code>||</code> for default values:</p>
|
||||
<pre><code>var name = providedName || "Anonymous"</code></pre>
|
||||
|
||||
<h2>While Loops</h2>
|
||||
<p>Repeat while a condition is true:</p>
|
||||
<pre><code>var i = 0
|
||||
while (i < 5) {
|
||||
System.print(i)
|
||||
i = i + 1
|
||||
}</code></pre>
|
||||
|
||||
<div class="example-output">0
|
||||
1
|
||||
2
|
||||
3
|
||||
4</div>
|
||||
|
||||
<h2>For Loops</h2>
|
||||
<p>Iterate over any sequence:</p>
|
||||
<pre><code>for (item in [1, 2, 3]) {
|
||||
System.print(item)
|
||||
}</code></pre>
|
||||
|
||||
<h3>Range Iteration</h3>
|
||||
<pre><code>for (i in 1..5) {
|
||||
System.print(i)
|
||||
}
|
||||
// Prints: 1 2 3 4 5
|
||||
|
||||
for (i in 1...5) {
|
||||
System.print(i)
|
||||
}
|
||||
// Prints: 1 2 3 4 (exclusive)</code></pre>
|
||||
|
||||
<h3>String Iteration</h3>
|
||||
<pre><code>for (char in "hello") {
|
||||
System.print(char)
|
||||
}
|
||||
// Prints each character</code></pre>
|
||||
|
||||
<h3>Map Iteration</h3>
|
||||
<pre><code>var person = {"name": "Alice", "age": 30}
|
||||
for (key in person.keys) {
|
||||
System.print("%(key): %(person[key])")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Break</h2>
|
||||
<p>Exit a loop early:</p>
|
||||
<pre><code>for (i in 1..100) {
|
||||
if (i > 5) break
|
||||
System.print(i)
|
||||
}
|
||||
// Prints: 1 2 3 4 5</code></pre>
|
||||
|
||||
<h2>Continue</h2>
|
||||
<p>Skip to the next iteration:</p>
|
||||
<pre><code>for (i in 1..10) {
|
||||
if (i % 2 == 0) continue
|
||||
System.print(i)
|
||||
}
|
||||
// Prints: 1 3 5 7 9 (odd numbers only)</code></pre>
|
||||
|
||||
<h2>Block Scoping</h2>
|
||||
<p>Blocks create new scopes:</p>
|
||||
<pre><code>var x = "outer"
|
||||
{
|
||||
var x = "inner"
|
||||
System.print(x) // inner
|
||||
}
|
||||
System.print(x) // outer</code></pre>
|
||||
|
||||
<p>Variables declared in a block are not visible outside:</p>
|
||||
<pre><code>if (true) {
|
||||
var temp = "temporary"
|
||||
}
|
||||
// temp is not accessible here</code></pre>
|
||||
|
||||
<h2>Iterating with Index</h2>
|
||||
<p>Use range to get indices:</p>
|
||||
<pre><code>var list = ["a", "b", "c"]
|
||||
for (i in 0...list.count) {
|
||||
System.print("%(i): %(list[i])")
|
||||
}</code></pre>
|
||||
|
||||
<div class="example-output">0: a
|
||||
1: b
|
||||
2: c</div>
|
||||
|
||||
<h2>Infinite Loops</h2>
|
||||
<p>Create with <code>while (true)</code>:</p>
|
||||
<pre><code>var count = 0
|
||||
while (true) {
|
||||
count = count + 1
|
||||
if (count >= 5) break
|
||||
System.print(count)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Nested Loops</h2>
|
||||
<pre><code>for (i in 1..3) {
|
||||
for (j in 1..3) {
|
||||
System.print("%(i), %(j)")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<p>Break only exits the innermost loop:</p>
|
||||
<pre><code>for (i in 1..3) {
|
||||
for (j in 1..10) {
|
||||
if (j > 2) break // Only breaks inner loop
|
||||
System.print("%(i), %(j)")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Iteration Patterns</h2>
|
||||
|
||||
<h3>Filtering</h3>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
var evens = []
|
||||
for (n in numbers) {
|
||||
if (n % 2 == 0) evens.add(n)
|
||||
}
|
||||
System.print(evens) // [2, 4, 6, 8, 10]</code></pre>
|
||||
|
||||
<h3>Mapping</h3>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5]
|
||||
var squared = []
|
||||
for (n in numbers) {
|
||||
squared.add(n * n)
|
||||
}
|
||||
System.print(squared) // [1, 4, 9, 16, 25]</code></pre>
|
||||
|
||||
<h3>Finding</h3>
|
||||
<pre><code>var numbers = [1, 3, 5, 8, 9, 11]
|
||||
var firstEven = null
|
||||
for (n in numbers) {
|
||||
if (n % 2 == 0) {
|
||||
firstEven = n
|
||||
break
|
||||
}
|
||||
}
|
||||
System.print(firstEven) // 8</code></pre>
|
||||
|
||||
<h3>Reducing</h3>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5]
|
||||
var sum = 0
|
||||
for (n in numbers) {
|
||||
sum = sum + n
|
||||
}
|
||||
System.print(sum) // 15</code></pre>
|
||||
|
||||
<h2>Functional Alternatives</h2>
|
||||
<p>Lists provide functional methods that are often cleaner:</p>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5]
|
||||
|
||||
var evens = numbers.where { |n| n % 2 == 0 }.toList
|
||||
var squared = numbers.map { |n| n * n }.toList
|
||||
var sum = numbers.reduce(0) { |acc, n| acc + n }</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="methods.html" class="prev">Methods</a>
|
||||
<a href="fibers.html" class="next">Fibers</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
387
manual/language/fibers.html
Normal file
387
manual/language/fibers.html
Normal file
@ -0,0 +1,387 @@
|
||||
<!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>Fibers - 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="index.html">Syntax Overview</a></li>
|
||||
<li><a href="classes.html">Classes</a></li>
|
||||
<li><a href="methods.html">Methods</a></li>
|
||||
<li><a href="control-flow.html">Control Flow</a></li>
|
||||
<li><a href="fibers.html" class="active">Fibers</a></li>
|
||||
<li><a href="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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Language</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Fibers</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Fibers</h1>
|
||||
|
||||
<p>Fibers are Wren's mechanism for cooperative concurrency. They are lightweight threads of execution that you explicitly control. Unlike OS threads, only one fiber runs at a time, and switching between them is explicit.</p>
|
||||
|
||||
<h2>Creating Fibers</h2>
|
||||
<p>Create a fiber with <code>Fiber.new</code>:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
System.print("Inside fiber")
|
||||
}</code></pre>
|
||||
|
||||
<p>The fiber does not run immediately. It is suspended until you start it.</p>
|
||||
|
||||
<h2>Running Fibers</h2>
|
||||
|
||||
<h3>call()</h3>
|
||||
<p>Start a fiber and wait for it to complete or yield:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
System.print("Running")
|
||||
}
|
||||
|
||||
fiber.call() // Prints "Running"</code></pre>
|
||||
|
||||
<h3>try()</h3>
|
||||
<p>Start a fiber and catch any runtime errors:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
Fiber.abort("Something went wrong")
|
||||
}
|
||||
|
||||
var error = fiber.try()
|
||||
System.print("Error: %(error)") // Error: Something went wrong</code></pre>
|
||||
|
||||
<h2>Yielding</h2>
|
||||
<p>Fibers can pause execution and return control to the caller:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
System.print("First")
|
||||
Fiber.yield()
|
||||
System.print("Second")
|
||||
Fiber.yield()
|
||||
System.print("Third")
|
||||
}
|
||||
|
||||
fiber.call() // Prints "First"
|
||||
fiber.call() // Prints "Second"
|
||||
fiber.call() // Prints "Third"</code></pre>
|
||||
|
||||
<h3>Yielding Values</h3>
|
||||
<pre><code>var counter = Fiber.new {
|
||||
Fiber.yield(1)
|
||||
Fiber.yield(2)
|
||||
Fiber.yield(3)
|
||||
}
|
||||
|
||||
System.print(counter.call()) // 1
|
||||
System.print(counter.call()) // 2
|
||||
System.print(counter.call()) // 3</code></pre>
|
||||
|
||||
<h3>Passing Values In</h3>
|
||||
<pre><code>var adder = Fiber.new {
|
||||
var total = 0
|
||||
while (true) {
|
||||
var value = Fiber.yield(total)
|
||||
total = total + value
|
||||
}
|
||||
}
|
||||
|
||||
adder.call() // Start the fiber
|
||||
System.print(adder.call(5)) // 5
|
||||
System.print(adder.call(10)) // 15
|
||||
System.print(adder.call(3)) // 18</code></pre>
|
||||
|
||||
<h2>Fiber State</h2>
|
||||
<p>Check the state of a fiber:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
Fiber.yield()
|
||||
}
|
||||
|
||||
System.print(fiber.isDone) // false
|
||||
|
||||
fiber.call()
|
||||
System.print(fiber.isDone) // false (yielded)
|
||||
|
||||
fiber.call()
|
||||
System.print(fiber.isDone) // true (completed)</code></pre>
|
||||
|
||||
<h2>Error Handling</h2>
|
||||
<p>Fibers can abort with an error:</p>
|
||||
<pre><code>Fiber.abort("Error message")</code></pre>
|
||||
|
||||
<p>Use <code>try()</code> to catch errors:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
var x = 1 / 0 // Will cause infinity, not error
|
||||
[1, 2, 3][10] // This will cause an error
|
||||
}
|
||||
|
||||
var error = fiber.try()
|
||||
if (error != null) {
|
||||
System.print("Caught: %(error)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Current Fiber</h2>
|
||||
<p>Get the currently executing fiber:</p>
|
||||
<pre><code>var current = Fiber.current
|
||||
System.print(current) // Fiber instance</code></pre>
|
||||
|
||||
<h2>Generator Pattern</h2>
|
||||
<p>Fibers naturally implement generators:</p>
|
||||
<pre><code>var range = Fn.new { |start, end|
|
||||
return Fiber.new {
|
||||
var i = start
|
||||
while (i <= end) {
|
||||
Fiber.yield(i)
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var nums = range.call(1, 5)
|
||||
while (!nums.isDone) {
|
||||
var value = nums.call()
|
||||
if (value != null) System.print(value)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Coroutine Pattern</h2>
|
||||
<p>Two fibers can communicate back and forth:</p>
|
||||
<pre><code>var producer = Fiber.new {
|
||||
for (i in 1..5) {
|
||||
System.print("Producing %(i)")
|
||||
Fiber.yield(i)
|
||||
}
|
||||
}
|
||||
|
||||
var consumer = Fiber.new {
|
||||
while (!producer.isDone) {
|
||||
var value = producer.call()
|
||||
if (value != null) {
|
||||
System.print("Consuming %(value)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
consumer.call()</code></pre>
|
||||
|
||||
<h2>Async Operations with Scheduler</h2>
|
||||
<p>Wren-CLI uses fibers for async I/O. The scheduler suspends fibers during I/O and resumes them when the operation completes:</p>
|
||||
<pre><code>import "timer" for Timer
|
||||
|
||||
System.print("Before sleep")
|
||||
Timer.sleep(1000) // Fiber suspends here
|
||||
System.print("After sleep")</code></pre>
|
||||
|
||||
<p>The scheduler pattern internally looks like:</p>
|
||||
<pre><code>import "scheduler" for Scheduler
|
||||
|
||||
Scheduler.await_ {
|
||||
Timer.sleep_(1000, Fiber.current)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Fiber Methods</h2>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.new { block }</code></td>
|
||||
<td>Create a new fiber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.current</code></td>
|
||||
<td>Get the current fiber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.yield()</code></td>
|
||||
<td>Pause and return null</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.yield(value)</code></td>
|
||||
<td>Pause and return value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.abort(message)</code></td>
|
||||
<td>Abort with error</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.suspend()</code></td>
|
||||
<td>Suspend the current fiber</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Instance Methods</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.call()</code></td>
|
||||
<td>Run fiber, wait for yield/complete</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.call(value)</code></td>
|
||||
<td>Run with value passed to yield</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.try()</code></td>
|
||||
<td>Run and catch errors</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.isDone</code></td>
|
||||
<td>True if completed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.error</code></td>
|
||||
<td>Error message if aborted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.transfer()</code></td>
|
||||
<td>Switch to this fiber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.transfer(value)</code></td>
|
||||
<td>Switch with value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.transferError(msg)</code></td>
|
||||
<td>Switch and raise error</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Transfer vs Call</h2>
|
||||
<p><code>call()</code> maintains a call stack and returns when the fiber yields:</p>
|
||||
<pre><code>var a = Fiber.new {
|
||||
System.print("a: before yield")
|
||||
Fiber.yield()
|
||||
System.print("a: after yield")
|
||||
}
|
||||
|
||||
a.call()
|
||||
System.print("back in main")
|
||||
a.call()
|
||||
|
||||
// Output:
|
||||
// a: before yield
|
||||
// back in main
|
||||
// a: after yield</code></pre>
|
||||
|
||||
<p><code>transfer()</code> does not maintain a call stack:</p>
|
||||
<pre><code>var main = Fiber.current
|
||||
var a = null
|
||||
var b = null
|
||||
|
||||
a = Fiber.new {
|
||||
System.print("in a")
|
||||
b.transfer()
|
||||
System.print("back in a")
|
||||
main.transfer()
|
||||
}
|
||||
|
||||
b = Fiber.new {
|
||||
System.print("in b")
|
||||
a.transfer()
|
||||
System.print("back in b")
|
||||
}
|
||||
|
||||
a.transfer()
|
||||
System.print("done")
|
||||
|
||||
// Output:
|
||||
// in a
|
||||
// in b
|
||||
// back in a
|
||||
// done</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="control-flow.html" class="prev">Control Flow</a>
|
||||
<a href="modules.html" class="next">Modules</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
305
manual/language/index.html
Normal file
305
manual/language/index.html
Normal file
@ -0,0 +1,305 @@
|
||||
<!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>Syntax Overview - 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="index.html" class="active">Syntax Overview</a></li>
|
||||
<li><a href="classes.html">Classes</a></li>
|
||||
<li><a href="methods.html">Methods</a></li>
|
||||
<li><a href="control-flow.html">Control Flow</a></li>
|
||||
<li><a href="fibers.html">Fibers</a></li>
|
||||
<li><a href="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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Language Reference</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Syntax Overview</h1>
|
||||
|
||||
<p>Wren is a small, fast, class-based scripting language with a clean syntax inspired by languages like Dart, Lua, and Smalltalk. This section covers the core language features.</p>
|
||||
|
||||
<div class="toc">
|
||||
<h4>Language Topics</h4>
|
||||
<ul>
|
||||
<li><a href="classes.html">Classes</a> - Object-oriented programming</li>
|
||||
<li><a href="methods.html">Methods</a> - Method definition and operators</li>
|
||||
<li><a href="control-flow.html">Control Flow</a> - Conditionals and loops</li>
|
||||
<li><a href="fibers.html">Fibers</a> - Cooperative concurrency</li>
|
||||
<li><a href="modules.html">Modules</a> - Import system</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Comments</h2>
|
||||
<p>Single-line comments start with <code>//</code>:</p>
|
||||
<pre><code>// This is a comment
|
||||
var x = 42 // Inline comment</code></pre>
|
||||
|
||||
<p>Block comments use <code>/* */</code> and can nest:</p>
|
||||
<pre><code>/* This is a
|
||||
multi-line comment */
|
||||
|
||||
/* Outer /* nested */ comment */</code></pre>
|
||||
|
||||
<h2>Variables</h2>
|
||||
<p>Declare variables with <code>var</code>:</p>
|
||||
<pre><code>var name = "Wren"
|
||||
var count = 42
|
||||
var active = true
|
||||
var nothing = null</code></pre>
|
||||
|
||||
<p>Variables must be initialized when declared. They are lexically scoped:</p>
|
||||
<pre><code>var outer = "outside"
|
||||
{
|
||||
var inner = "inside"
|
||||
System.print(outer) // Works
|
||||
}
|
||||
// inner is not accessible here</code></pre>
|
||||
|
||||
<h2>Data Types</h2>
|
||||
|
||||
<h3>Numbers</h3>
|
||||
<p>All numbers are 64-bit floating point:</p>
|
||||
<pre><code>var integer = 42
|
||||
var decimal = 3.14159
|
||||
var negative = -100
|
||||
var scientific = 1.5e10
|
||||
var hex = 0xFF
|
||||
var binary = 0b1010</code></pre>
|
||||
|
||||
<h3>Strings</h3>
|
||||
<p>Strings are immutable sequences of bytes:</p>
|
||||
<pre><code>var single = "Hello"
|
||||
var escape = "Line 1\nLine 2"
|
||||
var interpolation = "Value: %(1 + 2)"</code></pre>
|
||||
|
||||
<p>Raw strings avoid escape processing:</p>
|
||||
<pre><code>var raw = """
|
||||
This is a raw string.
|
||||
Backslashes \ are literal.
|
||||
"""</code></pre>
|
||||
|
||||
<h3>Booleans</h3>
|
||||
<pre><code>var yes = true
|
||||
var no = false</code></pre>
|
||||
|
||||
<p>Only <code>false</code> and <code>null</code> are falsy. All other values, including <code>0</code> and empty strings, are truthy.</p>
|
||||
|
||||
<h3>Null</h3>
|
||||
<pre><code>var nothing = null</code></pre>
|
||||
|
||||
<h3>Ranges</h3>
|
||||
<p>Ranges represent sequences of numbers:</p>
|
||||
<pre><code>var inclusive = 1..5 // 1, 2, 3, 4, 5
|
||||
var exclusive = 1...5 // 1, 2, 3, 4</code></pre>
|
||||
|
||||
<h3>Lists</h3>
|
||||
<p>Ordered, indexable collections:</p>
|
||||
<pre><code>var empty = []
|
||||
var numbers = [1, 2, 3, 4, 5]
|
||||
var mixed = [1, "two", true, null]
|
||||
|
||||
System.print(numbers[0]) // 1
|
||||
System.print(numbers[-1]) // 5 (last element)
|
||||
numbers[0] = 10
|
||||
numbers.add(6)</code></pre>
|
||||
|
||||
<h3>Maps</h3>
|
||||
<p>Key-value collections:</p>
|
||||
<pre><code>var empty = {}
|
||||
var person = {
|
||||
"name": "Alice",
|
||||
"age": 30
|
||||
}
|
||||
|
||||
System.print(person["name"]) // Alice
|
||||
person["city"] = "Amsterdam"</code></pre>
|
||||
|
||||
<h2>Operators</h2>
|
||||
|
||||
<h3>Arithmetic</h3>
|
||||
<pre><code>1 + 2 // 3
|
||||
5 - 3 // 2
|
||||
4 * 3 // 12
|
||||
10 / 4 // 2.5
|
||||
10 % 3 // 1 (modulo)</code></pre>
|
||||
|
||||
<h3>Comparison</h3>
|
||||
<pre><code>1 == 1 // true
|
||||
1 != 2 // true
|
||||
1 < 2 // true
|
||||
1 <= 1 // true
|
||||
2 > 1 // true
|
||||
2 >= 2 // true</code></pre>
|
||||
|
||||
<h3>Logical</h3>
|
||||
<pre><code>true && false // false
|
||||
true || false // true
|
||||
!true // false</code></pre>
|
||||
|
||||
<p>Logical operators short-circuit:</p>
|
||||
<pre><code>false && expensive() // expensive() not called
|
||||
true || expensive() // expensive() not called</code></pre>
|
||||
|
||||
<h3>Bitwise</h3>
|
||||
<pre><code>5 & 3 // 1 (AND)
|
||||
5 | 3 // 7 (OR)
|
||||
5 ^ 3 // 6 (XOR)
|
||||
~5 // -6 (NOT)
|
||||
8 << 2 // 32 (left shift)
|
||||
8 >> 2 // 2 (right shift)</code></pre>
|
||||
|
||||
<h3>Ternary</h3>
|
||||
<pre><code>var result = condition ? valueIfTrue : valueIfFalse</code></pre>
|
||||
|
||||
<h2>String Interpolation</h2>
|
||||
<p>Embed expressions in strings with <code>%()</code>:</p>
|
||||
<pre><code>var name = "World"
|
||||
System.print("Hello, %(name)!")
|
||||
|
||||
var a = 3
|
||||
var b = 4
|
||||
System.print("%(a) + %(b) = %(a + b)")</code></pre>
|
||||
|
||||
<p>Any expression can be interpolated:</p>
|
||||
<pre><code>System.print("Random: %(Random.new().float())")
|
||||
System.print("List: %([1, 2, 3].map { |x| x * 2 })")</code></pre>
|
||||
|
||||
<h2>Blocks</h2>
|
||||
<p>Blocks are anonymous functions. They use curly braces:</p>
|
||||
<pre><code>var block = { System.print("Hello") }
|
||||
block.call()
|
||||
|
||||
var add = { |a, b| a + b }
|
||||
System.print(add.call(1, 2)) // 3</code></pre>
|
||||
|
||||
<p>Blocks with a single expression return that value:</p>
|
||||
<pre><code>var square = { |x| x * x }
|
||||
System.print(square.call(5)) // 25</code></pre>
|
||||
|
||||
<h2>Functions</h2>
|
||||
<p>Use <code>Fn.new</code> for functions stored in variables:</p>
|
||||
<pre><code>var greet = Fn.new { |name|
|
||||
return "Hello, %(name)!"
|
||||
}
|
||||
System.print(greet.call("World"))</code></pre>
|
||||
|
||||
<p>Functions can have multiple statements:</p>
|
||||
<pre><code>var factorial = Fn.new { |n|
|
||||
if (n <= 1) return 1
|
||||
return n * factorial.call(n - 1)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Is Operator</h2>
|
||||
<p>Check if an object is an instance of a class:</p>
|
||||
<pre><code>"hello" is String // true
|
||||
42 is Num // true
|
||||
[1, 2] is List // true</code></pre>
|
||||
|
||||
<h2>Reserved Words</h2>
|
||||
<p>The following are reserved and cannot be used as identifiers:</p>
|
||||
<pre><code>break class construct else false for foreign if import
|
||||
in is null return static super this true var while</code></pre>
|
||||
|
||||
<h2>Identifiers</h2>
|
||||
<p>Identifiers follow these conventions:</p>
|
||||
<ul>
|
||||
<li><code>camelCase</code> for variables and methods</li>
|
||||
<li><code>PascalCase</code> for class names</li>
|
||||
<li><code>_underscore</code> prefix for private fields</li>
|
||||
<li><code>UPPER_CASE</code> for constants (by convention)</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="../getting-started/repl.html" class="prev">Using the REPL</a>
|
||||
<a href="classes.html" class="next">Classes</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
379
manual/language/methods.html
Normal file
379
manual/language/methods.html
Normal file
@ -0,0 +1,379 @@
|
||||
<!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>Methods - 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="index.html">Syntax Overview</a></li>
|
||||
<li><a href="classes.html">Classes</a></li>
|
||||
<li><a href="methods.html" class="active">Methods</a></li>
|
||||
<li><a href="control-flow.html">Control Flow</a></li>
|
||||
<li><a href="fibers.html">Fibers</a></li>
|
||||
<li><a href="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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Language</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Methods</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Methods</h1>
|
||||
|
||||
<p>Methods are the primary way to define behavior in Wren. They can be instance methods, static methods, getters, setters, or operators.</p>
|
||||
|
||||
<h2>Instance Methods</h2>
|
||||
<p>Instance methods operate on a specific object:</p>
|
||||
<pre><code>class Greeter {
|
||||
construct new(name) {
|
||||
_name = name
|
||||
}
|
||||
|
||||
greet() {
|
||||
return "Hello, %(_name)!"
|
||||
}
|
||||
|
||||
greetWith(greeting) {
|
||||
return "%(greeting), %(_name)!"
|
||||
}
|
||||
}
|
||||
|
||||
var g = Greeter.new("World")
|
||||
System.print(g.greet()) // Hello, World!
|
||||
System.print(g.greetWith("Hi")) // Hi, World!</code></pre>
|
||||
|
||||
<h2>Static Methods</h2>
|
||||
<p>Static methods belong to the class rather than instances:</p>
|
||||
<pre><code>class StringUtils {
|
||||
static reverse(s) {
|
||||
var result = ""
|
||||
for (i in (s.count - 1)..0) {
|
||||
result = result + s[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static capitalize(s) {
|
||||
if (s.count == 0) return s
|
||||
return s[0].toString.toUpperCase + s[1..-1]
|
||||
}
|
||||
}
|
||||
|
||||
System.print(StringUtils.reverse("hello")) // olleh
|
||||
System.print(StringUtils.capitalize("hello")) // Hello</code></pre>
|
||||
|
||||
<h2>Getters</h2>
|
||||
<p>Getters are methods without parentheses that act like properties:</p>
|
||||
<pre><code>class Temperature {
|
||||
construct celsius(c) {
|
||||
_celsius = c
|
||||
}
|
||||
|
||||
celsius { _celsius }
|
||||
fahrenheit { _celsius * 9 / 5 + 32 }
|
||||
kelvin { _celsius + 273.15 }
|
||||
}
|
||||
|
||||
var temp = Temperature.celsius(100)
|
||||
System.print(temp.celsius) // 100
|
||||
System.print(temp.fahrenheit) // 212
|
||||
System.print(temp.kelvin) // 373.15</code></pre>
|
||||
|
||||
<h2>Setters</h2>
|
||||
<p>Setters use the <code>=</code> suffix:</p>
|
||||
<pre><code>class Box {
|
||||
construct new(value) {
|
||||
_value = value
|
||||
}
|
||||
|
||||
value { _value }
|
||||
|
||||
value=(v) {
|
||||
if (v < 0) Fiber.abort("Value must be non-negative")
|
||||
_value = v
|
||||
}
|
||||
}
|
||||
|
||||
var box = Box.new(10)
|
||||
box.value = 20
|
||||
System.print(box.value) // 20</code></pre>
|
||||
|
||||
<h2>Method Signatures</h2>
|
||||
<p>Wren distinguishes methods by their signature (name + arity):</p>
|
||||
<pre><code>class Example {
|
||||
method { "no args" }
|
||||
method() { "zero args with parens" }
|
||||
method(a) { "one arg" }
|
||||
method(a, b) { "two args" }
|
||||
}
|
||||
|
||||
var e = Example.new()
|
||||
System.print(e.method) // no args
|
||||
System.print(e.method()) // zero args with parens
|
||||
System.print(e.method(1)) // one arg
|
||||
System.print(e.method(1, 2)) // two args</code></pre>
|
||||
|
||||
<h2>Block Arguments</h2>
|
||||
<p>Methods can take a block as the last argument:</p>
|
||||
<pre><code>class List {
|
||||
static each(list, fn) {
|
||||
for (item in list) {
|
||||
fn.call(item)
|
||||
}
|
||||
}
|
||||
|
||||
static map(list, fn) {
|
||||
var result = []
|
||||
for (item in list) {
|
||||
result.add(fn.call(item))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
var numbers = [1, 2, 3, 4, 5]
|
||||
|
||||
List.each(numbers) { |n|
|
||||
System.print(n)
|
||||
}
|
||||
|
||||
var doubled = List.map(numbers) { |n| n * 2 }
|
||||
System.print(doubled) // [2, 4, 6, 8, 10]</code></pre>
|
||||
|
||||
<h2>Operator Overloading</h2>
|
||||
<p>Classes can define custom behavior for operators:</p>
|
||||
|
||||
<h3>Binary Operators</h3>
|
||||
<pre><code>class Vector {
|
||||
construct new(x, y) {
|
||||
_x = x
|
||||
_y = y
|
||||
}
|
||||
|
||||
x { _x }
|
||||
y { _y }
|
||||
|
||||
+(other) { Vector.new(_x + other.x, _y + other.y) }
|
||||
-(other) { Vector.new(_x - other.x, _y - other.y) }
|
||||
*(scalar) { Vector.new(_x * scalar, _y * scalar) }
|
||||
/(scalar) { Vector.new(_x / scalar, _y / scalar) }
|
||||
|
||||
==(other) {
|
||||
return _x == other.x && _y == other.y
|
||||
}
|
||||
|
||||
toString { "(%(_x), %(_y))" }
|
||||
}
|
||||
|
||||
var a = Vector.new(1, 2)
|
||||
var b = Vector.new(3, 4)
|
||||
|
||||
System.print((a + b).toString) // (4, 6)
|
||||
System.print((a * 2).toString) // (2, 4)
|
||||
System.print(a == b) // false</code></pre>
|
||||
|
||||
<h3>Available Operators</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Operator</th>
|
||||
<th>Signature</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr><td><code>+</code></td><td><code>+(other)</code></td><td>Addition</td></tr>
|
||||
<tr><td><code>-</code></td><td><code>-(other)</code></td><td>Subtraction</td></tr>
|
||||
<tr><td><code>*</code></td><td><code>*(other)</code></td><td>Multiplication</td></tr>
|
||||
<tr><td><code>/</code></td><td><code>/(other)</code></td><td>Division</td></tr>
|
||||
<tr><td><code>%</code></td><td><code>%(other)</code></td><td>Modulo</td></tr>
|
||||
<tr><td><code><</code></td><td><code><(other)</code></td><td>Less than</td></tr>
|
||||
<tr><td><code>></code></td><td><code>>(other)</code></td><td>Greater than</td></tr>
|
||||
<tr><td><code><=</code></td><td><code><=(other)</code></td><td>Less or equal</td></tr>
|
||||
<tr><td><code>>=</code></td><td><code>>=(other)</code></td><td>Greater or equal</td></tr>
|
||||
<tr><td><code>==</code></td><td><code>==(other)</code></td><td>Equality</td></tr>
|
||||
<tr><td><code>!=</code></td><td><code>!=(other)</code></td><td>Inequality</td></tr>
|
||||
<tr><td><code>&</code></td><td><code>&(other)</code></td><td>Bitwise AND</td></tr>
|
||||
<tr><td><code>|</code></td><td><code>|(other)</code></td><td>Bitwise OR</td></tr>
|
||||
<tr><td><code>^</code></td><td><code>^(other)</code></td><td>Bitwise XOR</td></tr>
|
||||
<tr><td><code><<</code></td><td><code><<(other)</code></td><td>Left shift</td></tr>
|
||||
<tr><td><code>>></code></td><td><code>>>(other)</code></td><td>Right shift</td></tr>
|
||||
<tr><td><code>..</code></td><td><code>..(other)</code></td><td>Inclusive range</td></tr>
|
||||
<tr><td><code>...</code></td><td><code>...(other)</code></td><td>Exclusive range</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Unary Operators</h3>
|
||||
<pre><code>class Vector {
|
||||
construct new(x, y) {
|
||||
_x = x
|
||||
_y = y
|
||||
}
|
||||
|
||||
- { Vector.new(-_x, -_y) }
|
||||
! { Vector.new(_y, _x) } // Perpendicular
|
||||
|
||||
toString { "(%(_x), %(_y))" }
|
||||
}
|
||||
|
||||
var v = Vector.new(3, 4)
|
||||
System.print((-v).toString) // (-3, -4)</code></pre>
|
||||
|
||||
<h3>Subscript Operators</h3>
|
||||
<pre><code>class Grid {
|
||||
construct new(width, height) {
|
||||
_width = width
|
||||
_height = height
|
||||
_cells = List.filled(width * height, 0)
|
||||
}
|
||||
|
||||
[x, y] { _cells[y * _width + x] }
|
||||
[x, y]=(value) { _cells[y * _width + x] = value }
|
||||
}
|
||||
|
||||
var grid = Grid.new(10, 10)
|
||||
grid[5, 3] = 42
|
||||
System.print(grid[5, 3]) // 42</code></pre>
|
||||
|
||||
<h2>Calling Methods</h2>
|
||||
|
||||
<h3>With Parentheses</h3>
|
||||
<pre><code>object.method()
|
||||
object.method(arg1)
|
||||
object.method(arg1, arg2)</code></pre>
|
||||
|
||||
<h3>Without Parentheses (Getters)</h3>
|
||||
<pre><code>object.property
|
||||
object.count
|
||||
string.bytes</code></pre>
|
||||
|
||||
<h3>With Block Argument</h3>
|
||||
<pre><code>list.map { |x| x * 2 }
|
||||
list.where { |x| x > 5 }
|
||||
list.each { |x| System.print(x) }</code></pre>
|
||||
|
||||
<h3>Chaining</h3>
|
||||
<pre><code>var result = list
|
||||
.where { |x| x > 0 }
|
||||
.map { |x| x * 2 }
|
||||
.toList</code></pre>
|
||||
|
||||
<h2>Return Values</h2>
|
||||
<p>Methods return the last expression or use <code>return</code>:</p>
|
||||
<pre><code>class Example {
|
||||
implicit() {
|
||||
42 // Implicit return
|
||||
}
|
||||
|
||||
explicit() {
|
||||
return 42 // Explicit return
|
||||
}
|
||||
|
||||
early(x) {
|
||||
if (x < 0) return -1
|
||||
if (x > 0) return 1
|
||||
return 0
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<p>Methods without a return statement return <code>null</code>.</p>
|
||||
|
||||
<h2>Method References</h2>
|
||||
<p>You cannot directly reference a method as a value. Use a block wrapper:</p>
|
||||
<pre><code>class Printer {
|
||||
static print(value) {
|
||||
System.print(value)
|
||||
}
|
||||
}
|
||||
|
||||
var fn = Fn.new { |x| Printer.print(x) }
|
||||
fn.call("Hello") // Hello</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="classes.html" class="prev">Classes</a>
|
||||
<a href="control-flow.html" class="next">Control Flow</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
405
manual/language/modules.html
Normal file
405
manual/language/modules.html
Normal file
@ -0,0 +1,405 @@
|
||||
<!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>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="index.html">Syntax Overview</a></li>
|
||||
<li><a href="classes.html">Classes</a></li>
|
||||
<li><a href="methods.html">Methods</a></li>
|
||||
<li><a href="control-flow.html">Control Flow</a></li>
|
||||
<li><a href="fibers.html">Fibers</a></li>
|
||||
<li><a href="modules.html" class="active">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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</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>
|
||||
</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>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Language</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Modules</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Modules</h1>
|
||||
|
||||
<p>Modules organize code into separate, reusable files. Wren-CLI provides built-in modules and supports user-defined modules.</p>
|
||||
|
||||
<h2>Importing</h2>
|
||||
<p>Use <code>import</code> to load a module and access its classes:</p>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = Json.parse('{"name": "Wren"}')
|
||||
System.print(data["name"])</code></pre>
|
||||
|
||||
<h3>Multiple Imports</h3>
|
||||
<p>Import multiple classes from one module:</p>
|
||||
<pre><code>import "io" for File, Directory, Stdin</code></pre>
|
||||
|
||||
<h3>Import All</h3>
|
||||
<p>Some modules export many classes. Import what you need:</p>
|
||||
<pre><code>import "os" for Process, Platform</code></pre>
|
||||
|
||||
<h2>Built-in Modules</h2>
|
||||
<p>Wren-CLI provides these modules:</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Module</th>
|
||||
<th>Description</th>
|
||||
<th>Main Classes</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/http.html">http</a></td>
|
||||
<td>HTTP client</td>
|
||||
<td>Http, HttpResponse, Url</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/websocket.html">websocket</a></td>
|
||||
<td>WebSocket client/server</td>
|
||||
<td>WebSocket, WebSocketServer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/tls.html">tls</a></td>
|
||||
<td>TLS/SSL sockets</td>
|
||||
<td>TlsSocket</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/net.html">net</a></td>
|
||||
<td>TCP networking</td>
|
||||
<td>Socket, Server</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/dns.html">dns</a></td>
|
||||
<td>DNS resolution</td>
|
||||
<td>Dns</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/json.html">json</a></td>
|
||||
<td>JSON parsing</td>
|
||||
<td>Json</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/base64.html">base64</a></td>
|
||||
<td>Base64 encoding</td>
|
||||
<td>Base64</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/regex.html">regex</a></td>
|
||||
<td>Regular expressions</td>
|
||||
<td>Regex, Match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/jinja.html">jinja</a></td>
|
||||
<td>Template engine</td>
|
||||
<td>Environment, Template</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/crypto.html">crypto</a></td>
|
||||
<td>Cryptography</td>
|
||||
<td>Hash</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/os.html">os</a></td>
|
||||
<td>OS information</td>
|
||||
<td>Process, Platform</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/env.html">env</a></td>
|
||||
<td>Environment variables</td>
|
||||
<td>Env</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/signal.html">signal</a></td>
|
||||
<td>Unix signals</td>
|
||||
<td>Signal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/subprocess.html">subprocess</a></td>
|
||||
<td>Run processes</td>
|
||||
<td>Subprocess</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/sqlite.html">sqlite</a></td>
|
||||
<td>SQLite database</td>
|
||||
<td>Sqlite</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/datetime.html">datetime</a></td>
|
||||
<td>Date/time handling</td>
|
||||
<td>DateTime, Duration</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/timer.html">timer</a></td>
|
||||
<td>Timers and delays</td>
|
||||
<td>Timer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/io.html">io</a></td>
|
||||
<td>File I/O</td>
|
||||
<td>File, Directory, Stdin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/pathlib.html">pathlib</a></td>
|
||||
<td>Filesystem paths</td>
|
||||
<td>Path, PurePath</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/scheduler.html">scheduler</a></td>
|
||||
<td>Async scheduling</td>
|
||||
<td>Scheduler</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/math.html">math</a></td>
|
||||
<td>Math functions</td>
|
||||
<td>Math</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>User Modules</h2>
|
||||
<p>Create your own modules by putting code in <code>.wren</code> files.</p>
|
||||
|
||||
<h3>Creating a Module</h3>
|
||||
<p>Create <code>utils.wren</code>:</p>
|
||||
<pre><code>class StringUtils {
|
||||
static reverse(s) {
|
||||
var result = ""
|
||||
for (i in (s.count - 1)..0) {
|
||||
result = result + s[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static capitalize(s) {
|
||||
if (s.count == 0) return s
|
||||
return s[0].toString.toUpperCase + s[1..-1]
|
||||
}
|
||||
}
|
||||
|
||||
class MathUtils {
|
||||
static clamp(value, min, max) {
|
||||
if (value < min) return min
|
||||
if (value > max) return max
|
||||
return value
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Using a Module</h3>
|
||||
<p>Import with a relative path:</p>
|
||||
<pre><code>import "./utils" for StringUtils, MathUtils
|
||||
|
||||
System.print(StringUtils.reverse("hello")) // olleh
|
||||
System.print(StringUtils.capitalize("world")) // World
|
||||
System.print(MathUtils.clamp(15, 0, 10)) // 10</code></pre>
|
||||
|
||||
<h2>Module Resolution</h2>
|
||||
|
||||
<h3>Built-in Modules</h3>
|
||||
<p>Names without paths are built-in modules:</p>
|
||||
<pre><code>import "json" for Json // Built-in
|
||||
import "http" for Http // Built-in</code></pre>
|
||||
|
||||
<h3>Relative Paths</h3>
|
||||
<p>Paths starting with <code>./</code> or <code>../</code> are relative to the current file:</p>
|
||||
<pre><code>import "./helpers" for Helper // Same directory
|
||||
import "../utils/string" for StringUtil // Parent directory</code></pre>
|
||||
|
||||
<h3>Absolute Paths</h3>
|
||||
<p>Paths starting with <code>/</code> are absolute:</p>
|
||||
<pre><code>import "/home/user/libs/mylib" for MyClass</code></pre>
|
||||
|
||||
<h2>Module Structure</h2>
|
||||
<p>A typical project structure:</p>
|
||||
<pre><code>project/
|
||||
├── main.wren
|
||||
├── lib/
|
||||
│ ├── http_client.wren
|
||||
│ ├── database.wren
|
||||
│ └── templates.wren
|
||||
└── tests/
|
||||
└── test_http.wren</code></pre>
|
||||
|
||||
<p>In <code>main.wren</code>:</p>
|
||||
<pre><code>import "./lib/http_client" for HttpClient
|
||||
import "./lib/database" for Database
|
||||
import "./lib/templates" for TemplateEngine
|
||||
|
||||
var client = HttpClient.new()
|
||||
var db = Database.new("data.db")
|
||||
var tmpl = TemplateEngine.new()</code></pre>
|
||||
|
||||
<h2>Module Top-Level Code</h2>
|
||||
<p>Code outside classes runs when the module is first imported:</p>
|
||||
<pre><code>// config.wren
|
||||
System.print("Config module loading...")
|
||||
|
||||
class Config {
|
||||
static port { 8080 }
|
||||
static host { "localhost" }
|
||||
}
|
||||
|
||||
System.print("Config ready")</code></pre>
|
||||
|
||||
<pre><code>// main.wren
|
||||
System.print("Before import")
|
||||
import "./config" for Config
|
||||
System.print("After import")
|
||||
System.print("Port: %(Config.port)")
|
||||
|
||||
// Output:
|
||||
// Before import
|
||||
// Config module loading...
|
||||
// Config ready
|
||||
// After import
|
||||
// Port: 8080</code></pre>
|
||||
|
||||
<h2>Module Variables</h2>
|
||||
<p>Top-level variables are module-private by default:</p>
|
||||
<pre><code>// counter.wren
|
||||
var _count = 0 // Private to module
|
||||
|
||||
class Counter {
|
||||
static increment() { _count = _count + 1 }
|
||||
static count { _count }
|
||||
}</code></pre>
|
||||
|
||||
<pre><code>// main.wren
|
||||
import "./counter" for Counter
|
||||
|
||||
Counter.increment()
|
||||
Counter.increment()
|
||||
System.print(Counter.count) // 2
|
||||
// _count is not accessible here</code></pre>
|
||||
|
||||
<h2>Circular Imports</h2>
|
||||
<p>Wren handles circular imports by completing partial modules:</p>
|
||||
<pre><code>// a.wren
|
||||
import "./b" for B
|
||||
|
||||
class A {
|
||||
static greet() { "Hello from A" }
|
||||
static callB() { B.greet() }
|
||||
}
|
||||
|
||||
// b.wren
|
||||
import "./a" for A
|
||||
|
||||
class B {
|
||||
static greet() { "Hello from B" }
|
||||
static callA() { A.greet() }
|
||||
}</code></pre>
|
||||
|
||||
<p>This works because class definitions are hoisted.</p>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Avoid calling imported classes in top-level code during circular imports, as they may not be fully initialized.</p>
|
||||
</div>
|
||||
|
||||
<h2>Re-exporting</h2>
|
||||
<p>Create a facade module that re-exports from multiple modules:</p>
|
||||
<pre><code>// lib/index.wren
|
||||
import "./http_client" for HttpClient
|
||||
import "./database" for Database
|
||||
import "./templates" for TemplateEngine
|
||||
|
||||
class Lib {
|
||||
static httpClient { HttpClient }
|
||||
static database { Database }
|
||||
static templates { TemplateEngine }
|
||||
}</code></pre>
|
||||
|
||||
<pre><code>// main.wren
|
||||
import "./lib/index" for Lib
|
||||
|
||||
var client = Lib.httpClient.new()</code></pre>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="fibers.html" class="prev">Fibers</a>
|
||||
<a href="../api/index.html" class="next">API Reference</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
742
manual/tutorials/cli-tool.html
Normal file
742
manual/tutorials/cli-tool.html
Normal file
@ -0,0 +1,742 @@
|
||||
<!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>Building a CLI Tool - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial List</a></li>
|
||||
<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="cli-tool.html" class="active">CLI Tool</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>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Tutorials</a>
|
||||
<span class="separator">/</span>
|
||||
<span>CLI Tool</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Building a CLI Tool</h1>
|
||||
|
||||
<p>In this tutorial, you will build a complete command-line application. You will learn to parse arguments, handle user input, run subprocesses, and create a professional CLI experience.</p>
|
||||
|
||||
<h2>What You Will Learn</h2>
|
||||
|
||||
<ul>
|
||||
<li>Parsing command-line arguments</li>
|
||||
<li>Handling user input interactively</li>
|
||||
<li>Running external commands</li>
|
||||
<li>Working with environment variables</li>
|
||||
<li>Signal handling for graceful shutdown</li>
|
||||
<li>Creating subcommands</li>
|
||||
</ul>
|
||||
|
||||
<h2>Step 1: Accessing Command-Line Arguments</h2>
|
||||
|
||||
<p>Create a file called <code>cli_tool.wren</code>:</p>
|
||||
|
||||
<pre><code>import "os" for Process
|
||||
|
||||
var args = Process.arguments
|
||||
|
||||
System.print("Script: %(Process.arguments[0])")
|
||||
System.print("Arguments: %(args.count - 1)")
|
||||
|
||||
for (i in 1...args.count) {
|
||||
System.print(" arg[%(i)]: %(args[i])")
|
||||
}</code></pre>
|
||||
|
||||
<p>Run it:</p>
|
||||
<pre><code>$ wren_cli cli_tool.wren hello world --verbose
|
||||
Script: cli_tool.wren
|
||||
Arguments: 3
|
||||
arg[1]: hello
|
||||
arg[2]: world
|
||||
arg[3]: --verbose</code></pre>
|
||||
|
||||
<h2>Step 2: Building an Argument Parser</h2>
|
||||
|
||||
<pre><code>import "os" for Process
|
||||
|
||||
class ArgParser {
|
||||
construct new() {
|
||||
_commands = {}
|
||||
_options = {}
|
||||
_flags = {}
|
||||
_positional = []
|
||||
}
|
||||
|
||||
parse(args) {
|
||||
var i = 1
|
||||
while (i < args.count) {
|
||||
var arg = args[i]
|
||||
|
||||
if (arg.startsWith("--")) {
|
||||
var key = arg[2..-1]
|
||||
if (key.contains("=")) {
|
||||
var parts = key.split("=")
|
||||
_options[parts[0]] = parts[1]
|
||||
} else if (i + 1 < args.count && !args[i + 1].startsWith("-")) {
|
||||
_options[key] = args[i + 1]
|
||||
i = i + 1
|
||||
} else {
|
||||
_flags[key] = true
|
||||
}
|
||||
} else if (arg.startsWith("-")) {
|
||||
for (char in arg[1..-1]) {
|
||||
_flags[char] = true
|
||||
}
|
||||
} else {
|
||||
_positional.add(arg)
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
option(name) { _options[name] }
|
||||
flag(name) { _flags.containsKey(name) && _flags[name] }
|
||||
positional { _positional }
|
||||
hasOption(name) { _options.containsKey(name) }
|
||||
}
|
||||
|
||||
var parser = ArgParser.new()
|
||||
parser.parse(Process.arguments)
|
||||
|
||||
System.print("Positional: %(parser.positional)")
|
||||
System.print("Verbose: %(parser.flag("verbose") || parser.flag("v"))")
|
||||
System.print("Output: %(parser.option("output") || "stdout")")</code></pre>
|
||||
|
||||
<p>Run it:</p>
|
||||
<pre><code>$ wren_cli cli_tool.wren file1.txt file2.txt -v --output=result.txt
|
||||
Positional: [file1.txt, file2.txt]
|
||||
Verbose: true
|
||||
Output: result.txt</code></pre>
|
||||
|
||||
<h2>Step 3: Creating Subcommands</h2>
|
||||
|
||||
<pre><code>import "os" for Process
|
||||
import "io" for File, Directory, Stdin
|
||||
|
||||
class CLI {
|
||||
construct new(name, version) {
|
||||
_name = name
|
||||
_version = version
|
||||
_commands = {}
|
||||
}
|
||||
|
||||
command(name, description, handler) {
|
||||
_commands[name] = {"description": description, "handler": handler}
|
||||
}
|
||||
|
||||
run(args) {
|
||||
if (args.count < 2) {
|
||||
showHelp()
|
||||
return
|
||||
}
|
||||
|
||||
var cmd = args[1]
|
||||
|
||||
if (cmd == "--help" || cmd == "-h") {
|
||||
showHelp()
|
||||
} else if (cmd == "--version" || cmd == "-V") {
|
||||
System.print("%(_name) %(_version)")
|
||||
} else if (_commands.containsKey(cmd)) {
|
||||
_commands[cmd]["handler"].call(args[2..-1])
|
||||
} else {
|
||||
System.print("Unknown command: %(cmd)")
|
||||
System.print("Run '%(_name) --help' for usage.")
|
||||
}
|
||||
}
|
||||
|
||||
showHelp() {
|
||||
System.print("%(_name) %(_version)")
|
||||
System.print("")
|
||||
System.print("Usage: %(_name) <command> [options]")
|
||||
System.print("")
|
||||
System.print("Commands:")
|
||||
for (name in _commands.keys) {
|
||||
var desc = _commands[name]["description"]
|
||||
System.print(" %(name.padRight(15)) %(desc)")
|
||||
}
|
||||
System.print("")
|
||||
System.print("Options:")
|
||||
System.print(" --help, -h Show this help")
|
||||
System.print(" --version, -V Show version")
|
||||
}
|
||||
}
|
||||
|
||||
var cli = CLI.new("mytool", "1.0.0")
|
||||
|
||||
cli.command("list", "List files in directory", Fn.new { |args|
|
||||
var path = args.count > 0 ? args[0] : "."
|
||||
var files = Directory.list(path)
|
||||
for (file in files) {
|
||||
System.print(file)
|
||||
}
|
||||
})
|
||||
|
||||
cli.command("read", "Read and display a file", Fn.new { |args|
|
||||
if (args.count == 0) {
|
||||
System.print("Usage: mytool read <file>")
|
||||
return
|
||||
}
|
||||
System.print(File.read(args[0]))
|
||||
})
|
||||
|
||||
cli.command("info", "Show file information", Fn.new { |args|
|
||||
if (args.count == 0) {
|
||||
System.print("Usage: mytool info <file>")
|
||||
return
|
||||
}
|
||||
var path = args[0]
|
||||
if (File.exists(path)) {
|
||||
System.print("File: %(path)")
|
||||
System.print("Size: %(File.size(path)) bytes")
|
||||
} else {
|
||||
System.print("File not found: %(path)")
|
||||
}
|
||||
})
|
||||
|
||||
cli.run(Process.arguments)</code></pre>
|
||||
|
||||
<h2>Step 4: Interactive Input</h2>
|
||||
|
||||
<pre><code>import "io" for Stdin
|
||||
|
||||
class Prompt {
|
||||
static ask(question) {
|
||||
System.write("%(question) ")
|
||||
return Stdin.readLine()
|
||||
}
|
||||
|
||||
static confirm(question) {
|
||||
System.write("%(question) (y/n) ")
|
||||
var answer = Stdin.readLine().lower
|
||||
return answer == "y" || answer == "yes"
|
||||
}
|
||||
|
||||
static choose(question, options) {
|
||||
System.print(question)
|
||||
var i = 1
|
||||
for (opt in options) {
|
||||
System.print(" %(i). %(opt)")
|
||||
i = i + 1
|
||||
}
|
||||
System.write("Choice: ")
|
||||
var choice = Num.fromString(Stdin.readLine())
|
||||
if (choice && choice >= 1 && choice <= options.count) {
|
||||
return options[choice - 1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
static password(question) {
|
||||
System.write("%(question) ")
|
||||
return Stdin.readLine()
|
||||
}
|
||||
}
|
||||
|
||||
var name = Prompt.ask("What is your name?")
|
||||
System.print("Hello, %(name)!")
|
||||
|
||||
if (Prompt.confirm("Do you want to continue?")) {
|
||||
var color = Prompt.choose("Pick a color:", ["Red", "Green", "Blue"])
|
||||
System.print("You chose: %(color)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Step 5: Running External Commands</h2>
|
||||
|
||||
<pre><code>import "subprocess" for Subprocess
|
||||
import "os" for Process
|
||||
|
||||
class Shell {
|
||||
static run(command) {
|
||||
var result = Subprocess.exec(command)
|
||||
return {
|
||||
"output": result.stdout,
|
||||
"error": result.stderr,
|
||||
"code": result.exitCode
|
||||
}
|
||||
}
|
||||
|
||||
static runOrFail(command) {
|
||||
var result = run(command)
|
||||
if (result["code"] != 0) {
|
||||
Fiber.abort("Command failed: %(command)\n%(result["error"])")
|
||||
}
|
||||
return result["output"]
|
||||
}
|
||||
|
||||
static which(program) {
|
||||
var result = run("which %(program)")
|
||||
return result["code"] == 0 ? result["output"].trim() : null
|
||||
}
|
||||
}
|
||||
|
||||
System.print("Git status:")
|
||||
var result = Shell.run("git status --porcelain")
|
||||
if (result["code"] == 0) {
|
||||
if (result["output"].count == 0) {
|
||||
System.print(" Working directory clean")
|
||||
} else {
|
||||
System.print(result["output"])
|
||||
}
|
||||
} else {
|
||||
System.print(" Not a git repository")
|
||||
}
|
||||
|
||||
var gitPath = Shell.which("git")
|
||||
if (gitPath) {
|
||||
System.print("Git found at: %(gitPath)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Step 6: Environment Variables</h2>
|
||||
|
||||
<pre><code>import "env" for Env
|
||||
import "os" for Process
|
||||
|
||||
class Config {
|
||||
static get(key, defaultValue) {
|
||||
return Env.get(key) || defaultValue
|
||||
}
|
||||
|
||||
static require(key) {
|
||||
var value = Env.get(key)
|
||||
if (!value) {
|
||||
Fiber.abort("Required environment variable not set: %(key)")
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
var home = Config.get("HOME", "/tmp")
|
||||
var editor = Config.get("EDITOR", "vim")
|
||||
var debug = Config.get("DEBUG", "false") == "true"
|
||||
|
||||
System.print("Home: %(home)")
|
||||
System.print("Editor: %(editor)")
|
||||
System.print("Debug mode: %(debug)")
|
||||
|
||||
System.print("\nAll environment variables:")
|
||||
for (key in Env.keys()) {
|
||||
System.print(" %(key)=%(Env.get(key))")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Step 7: Signal Handling</h2>
|
||||
|
||||
<pre><code>import "signal" for Signal
|
||||
import "timer" for Timer
|
||||
|
||||
var running = true
|
||||
|
||||
Signal.handle("SIGINT", Fn.new {
|
||||
System.print("\nReceived SIGINT, shutting down...")
|
||||
running = false
|
||||
})
|
||||
|
||||
Signal.handle("SIGTERM", Fn.new {
|
||||
System.print("\nReceived SIGTERM, shutting down...")
|
||||
running = false
|
||||
})
|
||||
|
||||
System.print("Running... Press Ctrl+C to stop")
|
||||
|
||||
var counter = 0
|
||||
while (running) {
|
||||
counter = counter + 1
|
||||
System.write("\rProcessed %(counter) items...")
|
||||
Timer.sleep(100)
|
||||
}
|
||||
|
||||
System.print("\nGraceful shutdown complete. Processed %(counter) items.")</code></pre>
|
||||
|
||||
<h2>Step 8: Complete CLI Application</h2>
|
||||
|
||||
<p>Let's build a complete file utility tool:</p>
|
||||
|
||||
<pre><code>import "os" for Process
|
||||
import "io" for File, Directory, Stdin
|
||||
import "json" for Json
|
||||
import "crypto" for Crypto
|
||||
import "subprocess" for Subprocess
|
||||
import "signal" for Signal
|
||||
import "env" for Env
|
||||
|
||||
class FileUtil {
|
||||
construct new() {
|
||||
_verbose = false
|
||||
}
|
||||
|
||||
verbose=(value) { _verbose = value }
|
||||
|
||||
log(message) {
|
||||
if (_verbose) System.print("[INFO] %(message)")
|
||||
}
|
||||
|
||||
list(path, recursive) {
|
||||
var files = Directory.list(path)
|
||||
var results = []
|
||||
|
||||
for (file in files) {
|
||||
var fullPath = "%(path)/%(file)"
|
||||
results.add(fullPath)
|
||||
|
||||
if (recursive && Directory.exists(fullPath)) {
|
||||
var subFiles = list(fullPath, true)
|
||||
for (sub in subFiles) {
|
||||
results.add(sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
copy(source, dest) {
|
||||
log("Copying %(source) to %(dest)")
|
||||
File.copy(source, dest)
|
||||
}
|
||||
|
||||
hash(path, algorithm) {
|
||||
var content = File.read(path)
|
||||
if (algorithm == "md5") {
|
||||
return Crypto.md5(content)
|
||||
} else if (algorithm == "sha256") {
|
||||
return Crypto.sha256(content)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
search(path, pattern) {
|
||||
var files = list(path, true)
|
||||
var matches = []
|
||||
|
||||
for (file in files) {
|
||||
if (file.contains(pattern)) {
|
||||
matches.add(file)
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
stats(path) {
|
||||
var files = list(path, true)
|
||||
var totalSize = 0
|
||||
var fileCount = 0
|
||||
var dirCount = 0
|
||||
|
||||
for (file in files) {
|
||||
if (Directory.exists(file)) {
|
||||
dirCount = dirCount + 1
|
||||
} else if (File.exists(file)) {
|
||||
fileCount = fileCount + 1
|
||||
totalSize = totalSize + File.size(file)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"files": fileCount,
|
||||
"directories": dirCount,
|
||||
"totalSize": totalSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CLI {
|
||||
construct new() {
|
||||
_name = "fileutil"
|
||||
_version = "1.0.0"
|
||||
_util = FileUtil.new()
|
||||
}
|
||||
|
||||
run(args) {
|
||||
if (args.count < 2) {
|
||||
showHelp()
|
||||
return
|
||||
}
|
||||
|
||||
var verbose = args.contains("-v") || args.contains("--verbose")
|
||||
_util.verbose = verbose
|
||||
|
||||
var cmd = args[1]
|
||||
|
||||
if (cmd == "list" || cmd == "ls") {
|
||||
cmdList(args)
|
||||
} else if (cmd == "copy" || cmd == "cp") {
|
||||
cmdCopy(args)
|
||||
} else if (cmd == "hash") {
|
||||
cmdHash(args)
|
||||
} else if (cmd == "search" || cmd == "find") {
|
||||
cmdSearch(args)
|
||||
} else if (cmd == "stats") {
|
||||
cmdStats(args)
|
||||
} else if (cmd == "--help" || cmd == "-h") {
|
||||
showHelp()
|
||||
} else if (cmd == "--version" || cmd == "-V") {
|
||||
System.print("%(_name) %(_version)")
|
||||
} else {
|
||||
System.print("Unknown command: %(cmd)")
|
||||
System.print("Run '%(_name) --help' for usage.")
|
||||
}
|
||||
}
|
||||
|
||||
cmdList(args) {
|
||||
var path = "."
|
||||
var recursive = args.contains("-r") || args.contains("--recursive")
|
||||
|
||||
for (arg in args[2..-1]) {
|
||||
if (!arg.startsWith("-")) {
|
||||
path = arg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var files = _util.list(path, recursive)
|
||||
for (file in files) {
|
||||
if (File.exists(file)) {
|
||||
var size = File.size(file)
|
||||
System.print("%(formatSize(size).padLeft(10)) %(file)")
|
||||
} else {
|
||||
System.print(" DIR %(file)/")
|
||||
}
|
||||
}
|
||||
|
||||
System.print("\n%(files.count) items")
|
||||
}
|
||||
|
||||
cmdCopy(args) {
|
||||
if (args.count < 4) {
|
||||
System.print("Usage: %(_name) copy <source> <dest>")
|
||||
return
|
||||
}
|
||||
|
||||
var source = args[2]
|
||||
var dest = args[3]
|
||||
|
||||
if (!File.exists(source)) {
|
||||
System.print("Source file not found: %(source)")
|
||||
return
|
||||
}
|
||||
|
||||
_util.copy(source, dest)
|
||||
System.print("Copied %(source) to %(dest)")
|
||||
}
|
||||
|
||||
cmdHash(args) {
|
||||
if (args.count < 3) {
|
||||
System.print("Usage: %(_name) hash <file> [--algorithm=sha256|md5]")
|
||||
return
|
||||
}
|
||||
|
||||
var file = args[2]
|
||||
var algorithm = "sha256"
|
||||
|
||||
for (arg in args) {
|
||||
if (arg.startsWith("--algorithm=")) {
|
||||
algorithm = arg[12..-1]
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.exists(file)) {
|
||||
System.print("File not found: %(file)")
|
||||
return
|
||||
}
|
||||
|
||||
var hash = _util.hash(file, algorithm)
|
||||
System.print("%(algorithm.upper): %(hash)")
|
||||
}
|
||||
|
||||
cmdSearch(args) {
|
||||
if (args.count < 3) {
|
||||
System.print("Usage: %(_name) search <pattern> [path]")
|
||||
return
|
||||
}
|
||||
|
||||
var pattern = args[2]
|
||||
var path = args.count > 3 ? args[3] : "."
|
||||
|
||||
var matches = _util.search(path, pattern)
|
||||
|
||||
if (matches.count == 0) {
|
||||
System.print("No matches found for '%(pattern)'")
|
||||
} else {
|
||||
System.print("Found %(matches.count) matches:")
|
||||
for (match in matches) {
|
||||
System.print(" %(match)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmdStats(args) {
|
||||
var path = args.count > 2 ? args[2] : "."
|
||||
var stats = _util.stats(path)
|
||||
|
||||
System.print("Statistics for %(path):")
|
||||
System.print(" Files: %(stats["files"])")
|
||||
System.print(" Directories: %(stats["directories"])")
|
||||
System.print(" Total size: %(formatSize(stats["totalSize"]))")
|
||||
}
|
||||
|
||||
formatSize(bytes) {
|
||||
if (bytes < 1024) return "%(bytes) B"
|
||||
if (bytes < 1024 * 1024) return "%(((bytes / 1024) * 10).round / 10) KB"
|
||||
if (bytes < 1024 * 1024 * 1024) return "%(((bytes / 1024 / 1024) * 10).round / 10) MB"
|
||||
return "%(((bytes / 1024 / 1024 / 1024) * 10).round / 10) GB"
|
||||
}
|
||||
|
||||
showHelp() {
|
||||
System.print("%(_name) %(_version) - File utility tool")
|
||||
System.print("")
|
||||
System.print("Usage: %(_name) <command> [options] [arguments]")
|
||||
System.print("")
|
||||
System.print("Commands:")
|
||||
System.print(" list, ls List files in directory")
|
||||
System.print(" -r, --recursive Include subdirectories")
|
||||
System.print("")
|
||||
System.print(" copy, cp Copy a file")
|
||||
System.print(" <source> <dest>")
|
||||
System.print("")
|
||||
System.print(" hash Calculate file hash")
|
||||
System.print(" --algorithm=sha256|md5")
|
||||
System.print("")
|
||||
System.print(" search Search for files by name")
|
||||
System.print(" <pattern> [path]")
|
||||
System.print("")
|
||||
System.print(" stats Show directory statistics")
|
||||
System.print(" [path]")
|
||||
System.print("")
|
||||
System.print("Global Options:")
|
||||
System.print(" -v, --verbose Verbose output")
|
||||
System.print(" -h, --help Show this help")
|
||||
System.print(" -V, --version Show version")
|
||||
}
|
||||
}
|
||||
|
||||
var cli = CLI.new()
|
||||
cli.run(Process.arguments)</code></pre>
|
||||
|
||||
<h2>Running the Tool</h2>
|
||||
|
||||
<pre><code>$ wren_cli fileutil.wren --help
|
||||
fileutil 1.0.0 - File utility tool
|
||||
|
||||
Usage: fileutil <command> [options] [arguments]
|
||||
|
||||
Commands:
|
||||
list, ls List files in directory
|
||||
...
|
||||
|
||||
$ wren_cli fileutil.wren list -r src/
|
||||
1.2 KB src/main.wren
|
||||
3.4 KB src/utils.wren
|
||||
DIR src/lib/
|
||||
2.1 KB src/lib/helper.wren
|
||||
|
||||
4 items
|
||||
|
||||
$ wren_cli fileutil.wren hash README.md
|
||||
SHA256: a3f2e8b9c4d5...</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Create a shell alias or wrapper script to run your Wren CLI tools more conveniently: <code>alias fileutil='wren_cli /path/to/fileutil.wren'</code></p>
|
||||
</div>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
|
||||
<ul>
|
||||
<li>Add configuration file support with <a href="../api/json.html">JSON</a></li>
|
||||
<li>Implement colored output for better UX</li>
|
||||
<li>Add tab completion hints</li>
|
||||
<li>See the <a href="../api/os.html">OS</a> and <a href="../api/subprocess.html">Subprocess</a> API references</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<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>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
725
manual/tutorials/database-app.html
Normal file
725
manual/tutorials/database-app.html
Normal file
@ -0,0 +1,725 @@
|
||||
<!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>Database Application - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial List</a></li>
|
||||
<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="cli-tool.html">CLI Tool</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>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Tutorials</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Database App</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Database Application</h1>
|
||||
|
||||
<p>In this tutorial, you will build a complete task management application using SQLite for persistent storage. You will learn to create tables, perform CRUD operations, and build a command-line interface.</p>
|
||||
|
||||
<h2>What You Will Learn</h2>
|
||||
|
||||
<ul>
|
||||
<li>Creating and managing SQLite databases</li>
|
||||
<li>Designing database schemas</li>
|
||||
<li>Performing CRUD operations (Create, Read, Update, Delete)</li>
|
||||
<li>Using parameterized queries to prevent SQL injection</li>
|
||||
<li>Building a command-line interface</li>
|
||||
</ul>
|
||||
|
||||
<h2>Step 1: Setting Up the Database</h2>
|
||||
|
||||
<p>Create a file called <code>task_app.wren</code>:</p>
|
||||
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
|
||||
var db = Sqlite.open("tasks.db")
|
||||
|
||||
db.execute("
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
priority INTEGER DEFAULT 1,
|
||||
completed INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
due_date DATE
|
||||
)
|
||||
")
|
||||
|
||||
System.print("Database initialized!")
|
||||
db.close()</code></pre>
|
||||
|
||||
<h2>Step 2: Creating a Task Model</h2>
|
||||
|
||||
<p>Let's create a class to manage task operations:</p>
|
||||
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
import "datetime" for DateTime
|
||||
|
||||
class TaskManager {
|
||||
construct new(dbPath) {
|
||||
_db = Sqlite.open(dbPath)
|
||||
initDatabase()
|
||||
}
|
||||
|
||||
initDatabase() {
|
||||
_db.execute("
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
priority INTEGER DEFAULT 1,
|
||||
completed INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
due_date DATE
|
||||
)
|
||||
")
|
||||
}
|
||||
|
||||
create(title, description, priority, dueDate) {
|
||||
_db.execute(
|
||||
"INSERT INTO tasks (title, description, priority, due_date) VALUES (?, ?, ?, ?)",
|
||||
[title, description, priority, dueDate]
|
||||
)
|
||||
return _db.lastInsertId
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return _db.execute("SELECT * FROM tasks ORDER BY priority DESC, due_date ASC")
|
||||
}
|
||||
|
||||
findById(id) {
|
||||
var results = _db.execute("SELECT * FROM tasks WHERE id = ?", [id])
|
||||
return results.count > 0 ? results[0] : null
|
||||
}
|
||||
|
||||
findPending() {
|
||||
return _db.execute("SELECT * FROM tasks WHERE completed = 0 ORDER BY priority DESC")
|
||||
}
|
||||
|
||||
findCompleted() {
|
||||
return _db.execute("SELECT * FROM tasks WHERE completed = 1 ORDER BY created_at DESC")
|
||||
}
|
||||
|
||||
update(id, title, description, priority, dueDate) {
|
||||
_db.execute(
|
||||
"UPDATE tasks SET title = ?, description = ?, priority = ?, due_date = ? WHERE id = ?",
|
||||
[title, description, priority, dueDate, id]
|
||||
)
|
||||
}
|
||||
|
||||
complete(id) {
|
||||
_db.execute("UPDATE tasks SET completed = 1 WHERE id = ?", [id])
|
||||
}
|
||||
|
||||
uncomplete(id) {
|
||||
_db.execute("UPDATE tasks SET completed = 0 WHERE id = ?", [id])
|
||||
}
|
||||
|
||||
delete(id) {
|
||||
_db.execute("DELETE FROM tasks WHERE id = ?", [id])
|
||||
}
|
||||
|
||||
search(query) {
|
||||
return _db.execute(
|
||||
"SELECT * FROM tasks WHERE title LIKE ? OR description LIKE ?",
|
||||
["\%%(query)\%", "\%%(query)\%"]
|
||||
)
|
||||
}
|
||||
|
||||
close() {
|
||||
_db.close()
|
||||
}
|
||||
}
|
||||
|
||||
var tasks = TaskManager.new("tasks.db")
|
||||
|
||||
var id = tasks.create("Learn Wren-CLI", "Complete the database tutorial", 3, "2024-12-31")
|
||||
System.print("Created task with ID: %(id)")
|
||||
|
||||
tasks.close()</code></pre>
|
||||
|
||||
<h2>Step 3: Building the CLI Interface</h2>
|
||||
|
||||
<p>Now let's create an interactive command-line interface:</p>
|
||||
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
import "io" for Stdin
|
||||
|
||||
class TaskManager {
|
||||
construct new(dbPath) {
|
||||
_db = Sqlite.open(dbPath)
|
||||
initDatabase()
|
||||
}
|
||||
|
||||
initDatabase() {
|
||||
_db.execute("
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
priority INTEGER DEFAULT 1,
|
||||
completed INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
")
|
||||
}
|
||||
|
||||
create(title, description, priority) {
|
||||
_db.execute(
|
||||
"INSERT INTO tasks (title, description, priority) VALUES (?, ?, ?)",
|
||||
[title, description, priority]
|
||||
)
|
||||
return _db.lastInsertId
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return _db.execute("SELECT * FROM tasks ORDER BY completed ASC, priority DESC")
|
||||
}
|
||||
|
||||
complete(id) {
|
||||
_db.execute("UPDATE tasks SET completed = 1 WHERE id = ?", [id])
|
||||
}
|
||||
|
||||
delete(id) {
|
||||
_db.execute("DELETE FROM tasks WHERE id = ?", [id])
|
||||
}
|
||||
|
||||
close() {
|
||||
_db.close()
|
||||
}
|
||||
}
|
||||
|
||||
class TaskApp {
|
||||
construct new() {
|
||||
_tasks = TaskManager.new("tasks.db")
|
||||
_running = true
|
||||
}
|
||||
|
||||
run() {
|
||||
System.print("=== Task Manager ===\n")
|
||||
showHelp()
|
||||
|
||||
while (_running) {
|
||||
System.write("\n> ")
|
||||
var input = Stdin.readLine()
|
||||
|
||||
if (input == null) break
|
||||
|
||||
var parts = input.split(" ")
|
||||
var command = parts.count > 0 ? parts[0] : ""
|
||||
|
||||
if (command == "list") {
|
||||
listTasks()
|
||||
} else if (command == "add") {
|
||||
addTask()
|
||||
} else if (command == "done") {
|
||||
if (parts.count > 1) {
|
||||
completeTask(Num.fromString(parts[1]))
|
||||
} else {
|
||||
System.print("Usage: done <id>")
|
||||
}
|
||||
} else if (command == "delete") {
|
||||
if (parts.count > 1) {
|
||||
deleteTask(Num.fromString(parts[1]))
|
||||
} else {
|
||||
System.print("Usage: delete <id>")
|
||||
}
|
||||
} else if (command == "help") {
|
||||
showHelp()
|
||||
} else if (command == "quit" || command == "exit") {
|
||||
_running = false
|
||||
} else if (command.count > 0) {
|
||||
System.print("Unknown command: %(command)")
|
||||
}
|
||||
}
|
||||
|
||||
_tasks.close()
|
||||
System.print("Goodbye!")
|
||||
}
|
||||
|
||||
showHelp() {
|
||||
System.print("Commands:")
|
||||
System.print(" list - Show all tasks")
|
||||
System.print(" add - Add a new task")
|
||||
System.print(" done <id> - Mark task as complete")
|
||||
System.print(" delete <id> - Delete a task")
|
||||
System.print(" help - Show this help")
|
||||
System.print(" quit - Exit the application")
|
||||
}
|
||||
|
||||
listTasks() {
|
||||
var tasks = _tasks.findAll()
|
||||
|
||||
if (tasks.count == 0) {
|
||||
System.print("No tasks found.")
|
||||
return
|
||||
}
|
||||
|
||||
System.print("\nID | Pri | Status | Title")
|
||||
System.print("----|-----|--------|------")
|
||||
|
||||
for (task in tasks) {
|
||||
var status = task["completed"] == 1 ? "[X]" : "[ ]"
|
||||
var priority = ["Low", "Med", "High"][task["priority"] - 1]
|
||||
System.print("%(task["id"]) | %(priority) | %(status) | %(task["title"])")
|
||||
}
|
||||
}
|
||||
|
||||
addTask() {
|
||||
System.write("Title: ")
|
||||
var title = Stdin.readLine()
|
||||
|
||||
System.write("Description (optional): ")
|
||||
var description = Stdin.readLine()
|
||||
|
||||
System.write("Priority (1=Low, 2=Medium, 3=High): ")
|
||||
var priorityStr = Stdin.readLine()
|
||||
var priority = Num.fromString(priorityStr) || 1
|
||||
|
||||
if (priority < 1) priority = 1
|
||||
if (priority > 3) priority = 3
|
||||
|
||||
var id = _tasks.create(title, description, priority)
|
||||
System.print("Task created with ID: %(id)")
|
||||
}
|
||||
|
||||
completeTask(id) {
|
||||
_tasks.complete(id)
|
||||
System.print("Task %(id) marked as complete.")
|
||||
}
|
||||
|
||||
deleteTask(id) {
|
||||
_tasks.delete(id)
|
||||
System.print("Task %(id) deleted.")
|
||||
}
|
||||
}
|
||||
|
||||
var app = TaskApp.new()
|
||||
app.run()</code></pre>
|
||||
|
||||
<h2>Step 4: Adding Advanced Features</h2>
|
||||
|
||||
<p>Let's enhance our application with categories and due dates:</p>
|
||||
|
||||
<pre><code>import "sqlite" for Sqlite
|
||||
import "io" for Stdin
|
||||
import "datetime" for DateTime
|
||||
|
||||
class TaskManager {
|
||||
construct new(dbPath) {
|
||||
_db = Sqlite.open(dbPath)
|
||||
initDatabase()
|
||||
}
|
||||
|
||||
initDatabase() {
|
||||
_db.execute("
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
color TEXT DEFAULT '#808080'
|
||||
)
|
||||
")
|
||||
|
||||
_db.execute("
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
category_id INTEGER,
|
||||
priority INTEGER DEFAULT 1,
|
||||
completed INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
due_date DATE,
|
||||
FOREIGN KEY (category_id) REFERENCES categories(id)
|
||||
)
|
||||
")
|
||||
|
||||
var categories = _db.execute("SELECT COUNT(*) as count FROM categories")
|
||||
if (categories[0]["count"] == 0) {
|
||||
_db.execute("INSERT INTO categories (name, color) VALUES ('Work', '#FF6B6B')")
|
||||
_db.execute("INSERT INTO categories (name, color) VALUES ('Personal', '#4ECDC4')")
|
||||
_db.execute("INSERT INTO categories (name, color) VALUES ('Shopping', '#45B7D1')")
|
||||
}
|
||||
}
|
||||
|
||||
createTask(title, description, categoryId, priority, dueDate) {
|
||||
_db.execute(
|
||||
"INSERT INTO tasks (title, description, category_id, priority, due_date) VALUES (?, ?, ?, ?, ?)",
|
||||
[title, description, categoryId, priority, dueDate]
|
||||
)
|
||||
return _db.lastInsertId
|
||||
}
|
||||
|
||||
findAllTasks() {
|
||||
return _db.execute("
|
||||
SELECT t.*, c.name as category_name
|
||||
FROM tasks t
|
||||
LEFT JOIN categories c ON t.category_id = c.id
|
||||
ORDER BY t.completed ASC, t.priority DESC, t.due_date ASC
|
||||
")
|
||||
}
|
||||
|
||||
findTasksByCategory(categoryId) {
|
||||
return _db.execute("
|
||||
SELECT t.*, c.name as category_name
|
||||
FROM tasks t
|
||||
LEFT JOIN categories c ON t.category_id = c.id
|
||||
WHERE t.category_id = ?
|
||||
ORDER BY t.completed ASC, t.priority DESC
|
||||
", [categoryId])
|
||||
}
|
||||
|
||||
findOverdueTasks() {
|
||||
return _db.execute("
|
||||
SELECT t.*, c.name as category_name
|
||||
FROM tasks t
|
||||
LEFT JOIN categories c ON t.category_id = c.id
|
||||
WHERE t.completed = 0 AND t.due_date < DATE('now')
|
||||
ORDER BY t.due_date ASC
|
||||
")
|
||||
}
|
||||
|
||||
findDueToday() {
|
||||
return _db.execute("
|
||||
SELECT t.*, c.name as category_name
|
||||
FROM tasks t
|
||||
LEFT JOIN categories c ON t.category_id = c.id
|
||||
WHERE t.completed = 0 AND t.due_date = DATE('now')
|
||||
ORDER BY t.priority DESC
|
||||
")
|
||||
}
|
||||
|
||||
findCategories() {
|
||||
return _db.execute("SELECT * FROM categories ORDER BY name")
|
||||
}
|
||||
|
||||
createCategory(name, color) {
|
||||
_db.execute("INSERT INTO categories (name, color) VALUES (?, ?)", [name, color])
|
||||
return _db.lastInsertId
|
||||
}
|
||||
|
||||
completeTask(id) {
|
||||
_db.execute("UPDATE tasks SET completed = 1 WHERE id = ?", [id])
|
||||
}
|
||||
|
||||
deleteTask(id) {
|
||||
_db.execute("DELETE FROM tasks WHERE id = ?", [id])
|
||||
}
|
||||
|
||||
getStats() {
|
||||
var total = _db.execute("SELECT COUNT(*) as count FROM tasks")[0]["count"]
|
||||
var completed = _db.execute("SELECT COUNT(*) as count FROM tasks WHERE completed = 1")[0]["count"]
|
||||
var pending = total - completed
|
||||
var overdue = _db.execute("SELECT COUNT(*) as count FROM tasks WHERE completed = 0 AND due_date < DATE('now')")[0]["count"]
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"completed": completed,
|
||||
"pending": pending,
|
||||
"overdue": overdue
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
_db.close()
|
||||
}
|
||||
}
|
||||
|
||||
class TaskApp {
|
||||
construct new() {
|
||||
_tasks = TaskManager.new("tasks.db")
|
||||
_running = true
|
||||
}
|
||||
|
||||
run() {
|
||||
System.print("=== Task Manager v2 ===\n")
|
||||
|
||||
while (_running) {
|
||||
showMenu()
|
||||
System.write("\nChoice: ")
|
||||
var choice = Stdin.readLine()
|
||||
|
||||
if (choice == "1") {
|
||||
listTasks()
|
||||
} else if (choice == "2") {
|
||||
addTask()
|
||||
} else if (choice == "3") {
|
||||
completeTask()
|
||||
} else if (choice == "4") {
|
||||
showOverdue()
|
||||
} else if (choice == "5") {
|
||||
showStats()
|
||||
} else if (choice == "6") {
|
||||
manageCategories()
|
||||
} else if (choice == "0") {
|
||||
_running = false
|
||||
}
|
||||
}
|
||||
|
||||
_tasks.close()
|
||||
System.print("\nGoodbye!")
|
||||
}
|
||||
|
||||
showMenu() {
|
||||
System.print("\n--- Main Menu ---")
|
||||
System.print("1. List all tasks")
|
||||
System.print("2. Add new task")
|
||||
System.print("3. Complete task")
|
||||
System.print("4. Show overdue")
|
||||
System.print("5. Statistics")
|
||||
System.print("6. Categories")
|
||||
System.print("0. Exit")
|
||||
}
|
||||
|
||||
listTasks() {
|
||||
var tasks = _tasks.findAllTasks()
|
||||
|
||||
if (tasks.count == 0) {
|
||||
System.print("\nNo tasks found.")
|
||||
return
|
||||
}
|
||||
|
||||
System.print("\n--- All Tasks ---")
|
||||
for (task in tasks) {
|
||||
var status = task["completed"] == 1 ? "[X]" : "[ ]"
|
||||
var category = task["category_name"] || "None"
|
||||
var due = task["due_date"] || "No due date"
|
||||
|
||||
System.print("%(task["id"]). %(status) %(task["title"])")
|
||||
System.print(" Category: %(category) | Due: %(due)")
|
||||
}
|
||||
}
|
||||
|
||||
addTask() {
|
||||
System.print("\n--- Add Task ---")
|
||||
|
||||
System.write("Title: ")
|
||||
var title = Stdin.readLine()
|
||||
if (title.count == 0) return
|
||||
|
||||
System.write("Description: ")
|
||||
var description = Stdin.readLine()
|
||||
|
||||
var categories = _tasks.findCategories()
|
||||
System.print("\nCategories:")
|
||||
for (cat in categories) {
|
||||
System.print(" %(cat["id"]). %(cat["name"])")
|
||||
}
|
||||
System.write("Category ID (or 0 for none): ")
|
||||
var categoryId = Num.fromString(Stdin.readLine())
|
||||
if (categoryId == 0) categoryId = null
|
||||
|
||||
System.write("Priority (1-3): ")
|
||||
var priority = Num.fromString(Stdin.readLine()) || 1
|
||||
|
||||
System.write("Due date (YYYY-MM-DD or empty): ")
|
||||
var dueDate = Stdin.readLine()
|
||||
if (dueDate.count == 0) dueDate = null
|
||||
|
||||
var id = _tasks.createTask(title, description, categoryId, priority, dueDate)
|
||||
System.print("\nTask created with ID: %(id)")
|
||||
}
|
||||
|
||||
completeTask() {
|
||||
System.write("\nTask ID to complete: ")
|
||||
var id = Num.fromString(Stdin.readLine())
|
||||
if (id) {
|
||||
_tasks.completeTask(id)
|
||||
System.print("Task %(id) marked as complete!")
|
||||
}
|
||||
}
|
||||
|
||||
showOverdue() {
|
||||
var tasks = _tasks.findOverdueTasks()
|
||||
|
||||
if (tasks.count == 0) {
|
||||
System.print("\nNo overdue tasks!")
|
||||
return
|
||||
}
|
||||
|
||||
System.print("\n--- Overdue Tasks ---")
|
||||
for (task in tasks) {
|
||||
System.print("%(task["id"]). %(task["title"]) (Due: %(task["due_date"]))")
|
||||
}
|
||||
}
|
||||
|
||||
showStats() {
|
||||
var stats = _tasks.getStats()
|
||||
|
||||
System.print("\n--- Statistics ---")
|
||||
System.print("Total tasks: %(stats["total"])")
|
||||
System.print("Completed: %(stats["completed"])")
|
||||
System.print("Pending: %(stats["pending"])")
|
||||
System.print("Overdue: %(stats["overdue"])")
|
||||
|
||||
if (stats["total"] > 0) {
|
||||
var pct = (stats["completed"] / stats["total"] * 100).round
|
||||
System.print("Completion rate: %(pct)\%")
|
||||
}
|
||||
}
|
||||
|
||||
manageCategories() {
|
||||
var categories = _tasks.findCategories()
|
||||
|
||||
System.print("\n--- Categories ---")
|
||||
for (cat in categories) {
|
||||
System.print("%(cat["id"]). %(cat["name"])")
|
||||
}
|
||||
|
||||
System.write("\nAdd new category? (y/n): ")
|
||||
if (Stdin.readLine().lower == "y") {
|
||||
System.write("Category name: ")
|
||||
var name = Stdin.readLine()
|
||||
if (name.count > 0) {
|
||||
_tasks.createCategory(name, "#808080")
|
||||
System.print("Category created!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var app = TaskApp.new()
|
||||
app.run()</code></pre>
|
||||
|
||||
<h2>Running the Application</h2>
|
||||
|
||||
<pre><code>$ wren_cli task_app.wren
|
||||
=== Task Manager v2 ===
|
||||
|
||||
--- Main Menu ---
|
||||
1. List all tasks
|
||||
2. Add new task
|
||||
3. Complete task
|
||||
4. Show overdue
|
||||
5. Statistics
|
||||
6. Categories
|
||||
0. Exit
|
||||
|
||||
Choice: 2
|
||||
|
||||
--- Add Task ---
|
||||
Title: Complete database tutorial
|
||||
Description: Learn SQLite with Wren-CLI
|
||||
|
||||
Categories:
|
||||
1. Work
|
||||
2. Personal
|
||||
3. Shopping
|
||||
Category ID (or 0 for none): 1
|
||||
Priority (1-3): 3
|
||||
Due date (YYYY-MM-DD or empty): 2024-12-31
|
||||
|
||||
Task created with ID: 1</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Always use parameterized queries with <code>?</code> placeholders instead of string concatenation. This prevents SQL injection vulnerabilities.</p>
|
||||
</div>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Use <code>":memory:"</code> as the database path for testing without creating a file on disk.</p>
|
||||
</div>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
|
||||
<ul>
|
||||
<li>Add data export to JSON or CSV</li>
|
||||
<li>Implement task reminders with <a href="../api/timer.html">Timer</a></li>
|
||||
<li>Generate HTML reports with <a href="template-rendering.html">Jinja templates</a></li>
|
||||
<li>See the <a href="../api/sqlite.html">SQLite API reference</a></li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="websocket-chat.html" class="prev">WebSocket Chat</a>
|
||||
<a href="template-rendering.html" class="next">Template Rendering</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
530
manual/tutorials/http-client.html
Normal file
530
manual/tutorials/http-client.html
Normal file
@ -0,0 +1,530 @@
|
||||
<!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>Building an HTTP Client - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial List</a></li>
|
||||
<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="cli-tool.html">CLI Tool</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>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Tutorials</a>
|
||||
<span class="separator">/</span>
|
||||
<span>HTTP Client</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Building an HTTP Client</h1>
|
||||
|
||||
<p>In this tutorial, you will learn how to build a REST API client using Wren-CLI's <code>http</code> and <code>json</code> modules. By the end, you will have a reusable API client class that can interact with any JSON REST API.</p>
|
||||
|
||||
<h2>What You Will Learn</h2>
|
||||
|
||||
<ul>
|
||||
<li>Making GET, POST, PUT, and DELETE requests</li>
|
||||
<li>Parsing JSON responses</li>
|
||||
<li>Handling HTTP errors</li>
|
||||
<li>Working with request headers</li>
|
||||
<li>Building a reusable API client class</li>
|
||||
</ul>
|
||||
|
||||
<h2>Step 1: Your First HTTP Request</h2>
|
||||
|
||||
<p>Let's start with a simple GET request. Create a file called <code>http_client.wren</code>:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://jsonplaceholder.typicode.com/posts/1")
|
||||
|
||||
System.print("Status: %(response.statusCode)")
|
||||
System.print("Body: %(response.body)")</code></pre>
|
||||
|
||||
<p>Run it:</p>
|
||||
|
||||
<pre><code>$ wren_cli http_client.wren
|
||||
Status: 200
|
||||
Body: {
|
||||
"userId": 1,
|
||||
"id": 1,
|
||||
"title": "sunt aut facere...",
|
||||
"body": "quia et suscipit..."
|
||||
}</code></pre>
|
||||
|
||||
<h2>Step 2: Parsing JSON Responses</h2>
|
||||
|
||||
<p>Raw JSON strings are not very useful. Let's parse them into Wren objects:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var response = Http.get("https://jsonplaceholder.typicode.com/posts/1")
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var post = Json.parse(response.body)
|
||||
System.print("Title: %(post["title"])")
|
||||
System.print("User ID: %(post["userId"])")
|
||||
} else {
|
||||
System.print("Error: %(response.statusCode)")
|
||||
}</code></pre>
|
||||
|
||||
<p>The <code>HttpResponse</code> class also provides a convenient <code>json</code> property:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://jsonplaceholder.typicode.com/posts/1")
|
||||
var post = response.json
|
||||
|
||||
System.print("Title: %(post["title"])")</code></pre>
|
||||
|
||||
<h2>Step 3: Fetching Lists</h2>
|
||||
|
||||
<p>APIs often return arrays of objects. Let's fetch multiple posts:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
|
||||
var response = Http.get("https://jsonplaceholder.typicode.com/posts")
|
||||
var posts = response.json
|
||||
|
||||
System.print("Found %(posts.count) posts\n")
|
||||
|
||||
for (i in 0...5) {
|
||||
var post = posts[i]
|
||||
System.print("%(post["id"]). %(post["title"])")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Step 4: Making POST Requests</h2>
|
||||
|
||||
<p>To create resources, use POST with a JSON body:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var newPost = {
|
||||
"title": "My New Post",
|
||||
"body": "This is the content of my post.",
|
||||
"userId": 1
|
||||
}
|
||||
|
||||
var response = Http.post(
|
||||
"https://jsonplaceholder.typicode.com/posts",
|
||||
Json.stringify(newPost),
|
||||
{"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
System.print("Status: %(response.statusCode)")
|
||||
System.print("Created post with ID: %(response.json["id"])")</code></pre>
|
||||
|
||||
<h2>Step 5: PUT and DELETE Requests</h2>
|
||||
|
||||
<p>Update and delete resources with PUT and DELETE:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
var updatedPost = {
|
||||
"id": 1,
|
||||
"title": "Updated Title",
|
||||
"body": "Updated content.",
|
||||
"userId": 1
|
||||
}
|
||||
|
||||
var putResponse = Http.put(
|
||||
"https://jsonplaceholder.typicode.com/posts/1",
|
||||
Json.stringify(updatedPost),
|
||||
{"Content-Type": "application/json"}
|
||||
)
|
||||
System.print("PUT Status: %(putResponse.statusCode)")
|
||||
|
||||
var deleteResponse = Http.delete("https://jsonplaceholder.typicode.com/posts/1")
|
||||
System.print("DELETE Status: %(deleteResponse.statusCode)")</code></pre>
|
||||
|
||||
<h2>Step 6: Building an API Client Class</h2>
|
||||
|
||||
<p>Let's create a reusable API client that encapsulates all these patterns:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
class ApiClient {
|
||||
construct new(baseUrl) {
|
||||
_baseUrl = baseUrl
|
||||
_headers = {"Content-Type": "application/json"}
|
||||
}
|
||||
|
||||
headers { _headers }
|
||||
headers=(value) { _headers = value }
|
||||
|
||||
setHeader(name, value) {
|
||||
_headers[name] = value
|
||||
}
|
||||
|
||||
get(path) {
|
||||
var response = Http.get(_baseUrl + path, _headers)
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
post(path, data) {
|
||||
var body = Json.stringify(data)
|
||||
var response = Http.post(_baseUrl + path, body, _headers)
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
put(path, data) {
|
||||
var body = Json.stringify(data)
|
||||
var response = Http.put(_baseUrl + path, body, _headers)
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
delete(path) {
|
||||
var response = Http.delete(_baseUrl + path, _headers)
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
handleResponse(response) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
if (response.body.count > 0) {
|
||||
return response.json
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
Fiber.abort("API Error: %(response.statusCode)")
|
||||
}
|
||||
}
|
||||
|
||||
var api = ApiClient.new("https://jsonplaceholder.typicode.com")
|
||||
|
||||
var posts = api.get("/posts")
|
||||
System.print("Fetched %(posts.count) posts")
|
||||
|
||||
var newPost = api.post("/posts", {
|
||||
"title": "Hello from Wren",
|
||||
"body": "Created with ApiClient",
|
||||
"userId": 1
|
||||
})
|
||||
System.print("Created post: %(newPost["id"])")</code></pre>
|
||||
|
||||
<h2>Step 7: Adding Authentication</h2>
|
||||
|
||||
<p>Many APIs require authentication. Add support for API keys and bearer tokens:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
import "base64" for Base64
|
||||
|
||||
class ApiClient {
|
||||
construct new(baseUrl) {
|
||||
_baseUrl = baseUrl
|
||||
_headers = {"Content-Type": "application/json"}
|
||||
}
|
||||
|
||||
setApiKey(key) {
|
||||
_headers["X-API-Key"] = key
|
||||
}
|
||||
|
||||
setBearerToken(token) {
|
||||
_headers["Authorization"] = "Bearer %(token)"
|
||||
}
|
||||
|
||||
setBasicAuth(username, password) {
|
||||
var credentials = Base64.encode("%(username):%(password)")
|
||||
_headers["Authorization"] = "Basic %(credentials)"
|
||||
}
|
||||
|
||||
get(path) {
|
||||
var response = Http.get(_baseUrl + path, _headers)
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
handleResponse(response) {
|
||||
if (response.statusCode == 401) {
|
||||
Fiber.abort("Authentication failed")
|
||||
}
|
||||
if (response.statusCode == 403) {
|
||||
Fiber.abort("Access forbidden")
|
||||
}
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return response.json
|
||||
}
|
||||
Fiber.abort("API Error: %(response.statusCode)")
|
||||
}
|
||||
}
|
||||
|
||||
var api = ApiClient.new("https://api.example.com")
|
||||
api.setBearerToken("your-auth-token")
|
||||
|
||||
var data = api.get("/protected/resource")</code></pre>
|
||||
|
||||
<h2>Step 8: Error Handling</h2>
|
||||
|
||||
<p>Proper error handling makes your client robust:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
|
||||
class ApiError {
|
||||
construct new(statusCode, message) {
|
||||
_statusCode = statusCode
|
||||
_message = message
|
||||
}
|
||||
|
||||
statusCode { _statusCode }
|
||||
message { _message }
|
||||
|
||||
toString { "ApiError %(statusCode): %(message)" }
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
construct new(baseUrl) {
|
||||
_baseUrl = baseUrl
|
||||
_headers = {"Content-Type": "application/json"}
|
||||
}
|
||||
|
||||
get(path) {
|
||||
var fiber = Fiber.new {
|
||||
return Http.get(_baseUrl + path, _headers)
|
||||
}
|
||||
|
||||
var response = fiber.try()
|
||||
if (fiber.error) {
|
||||
return {"error": ApiError.new(0, fiber.error)}
|
||||
}
|
||||
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
handleResponse(response) {
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
return {"data": response.json}
|
||||
}
|
||||
|
||||
var message = "Unknown error"
|
||||
var fiber = Fiber.new { response.json["message"] }
|
||||
var apiMessage = fiber.try()
|
||||
if (!fiber.error && apiMessage) {
|
||||
message = apiMessage
|
||||
}
|
||||
|
||||
return {"error": ApiError.new(response.statusCode, message)}
|
||||
}
|
||||
}
|
||||
|
||||
var api = ApiClient.new("https://jsonplaceholder.typicode.com")
|
||||
var result = api.get("/posts/1")
|
||||
|
||||
if (result["error"]) {
|
||||
System.print("Error: %(result["error"])")
|
||||
} else {
|
||||
System.print("Success: %(result["data"]["title"])")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Complete Example</h2>
|
||||
|
||||
<p>Here is a complete, production-ready API client:</p>
|
||||
|
||||
<pre><code>import "http" for Http
|
||||
import "json" for Json
|
||||
import "base64" for Base64
|
||||
|
||||
class ApiClient {
|
||||
construct new(baseUrl) {
|
||||
_baseUrl = baseUrl
|
||||
_headers = {"Content-Type": "application/json"}
|
||||
_timeout = 30000
|
||||
}
|
||||
|
||||
baseUrl { _baseUrl }
|
||||
headers { _headers }
|
||||
|
||||
setHeader(name, value) { _headers[name] = value }
|
||||
removeHeader(name) { _headers.remove(name) }
|
||||
|
||||
setBearerToken(token) {
|
||||
_headers["Authorization"] = "Bearer %(token)"
|
||||
}
|
||||
|
||||
setBasicAuth(username, password) {
|
||||
var credentials = Base64.encode("%(username):%(password)")
|
||||
_headers["Authorization"] = "Basic %(credentials)"
|
||||
}
|
||||
|
||||
get(path) { request("GET", path, null) }
|
||||
post(path, data) { request("POST", path, data) }
|
||||
put(path, data) { request("PUT", path, data) }
|
||||
patch(path, data) { request("PATCH", path, data) }
|
||||
delete(path) { request("DELETE", path, null) }
|
||||
|
||||
request(method, path, data) {
|
||||
var url = _baseUrl + path
|
||||
var body = data ? Json.stringify(data) : ""
|
||||
|
||||
var response
|
||||
if (method == "GET") {
|
||||
response = Http.get(url, _headers)
|
||||
} else if (method == "POST") {
|
||||
response = Http.post(url, body, _headers)
|
||||
} else if (method == "PUT") {
|
||||
response = Http.put(url, body, _headers)
|
||||
} else if (method == "PATCH") {
|
||||
response = Http.patch(url, body, _headers)
|
||||
} else if (method == "DELETE") {
|
||||
response = Http.delete(url, _headers)
|
||||
}
|
||||
|
||||
return parseResponse(response)
|
||||
}
|
||||
|
||||
parseResponse(response) {
|
||||
var result = {
|
||||
"status": response.statusCode,
|
||||
"headers": response.headers,
|
||||
"ok": response.statusCode >= 200 && response.statusCode < 300
|
||||
}
|
||||
|
||||
if (response.body.count > 0) {
|
||||
var fiber = Fiber.new { Json.parse(response.body) }
|
||||
var data = fiber.try()
|
||||
result["data"] = fiber.error ? response.body : data
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
System.print("=== API Client Demo ===\n")
|
||||
|
||||
var api = ApiClient.new("https://jsonplaceholder.typicode.com")
|
||||
|
||||
System.print("--- Fetching posts ---")
|
||||
var result = api.get("/posts")
|
||||
if (result["ok"]) {
|
||||
System.print("Found %(result["data"].count) posts")
|
||||
System.print("First post: %(result["data"][0]["title"])")
|
||||
}
|
||||
|
||||
System.print("\n--- Creating post ---")
|
||||
result = api.post("/posts", {
|
||||
"title": "Created with Wren-CLI",
|
||||
"body": "This is a test post",
|
||||
"userId": 1
|
||||
})
|
||||
if (result["ok"]) {
|
||||
System.print("Created post ID: %(result["data"]["id"])")
|
||||
}
|
||||
|
||||
System.print("\n--- Updating post ---")
|
||||
result = api.put("/posts/1", {
|
||||
"id": 1,
|
||||
"title": "Updated Title",
|
||||
"body": "Updated body",
|
||||
"userId": 1
|
||||
})
|
||||
System.print("Update status: %(result["status"])")
|
||||
|
||||
System.print("\n--- Deleting post ---")
|
||||
result = api.delete("/posts/1")
|
||||
System.print("Delete status: %(result["status"])")</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Save the <code>ApiClient</code> class in a separate file like <code>api_client.wren</code> and import it in your projects for reuse.</p>
|
||||
</div>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
|
||||
<ul>
|
||||
<li>Learn about <a href="../api/http.html">HTTP module</a> features in detail</li>
|
||||
<li>Explore <a href="../api/json.html">JSON module</a> for advanced parsing</li>
|
||||
<li>Build a <a href="websocket-chat.html">WebSocket chat application</a></li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="index.html" class="prev">Tutorials</a>
|
||||
<a href="websocket-chat.html" class="next">WebSocket Chat</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
177
manual/tutorials/index.html
Normal file
177
manual/tutorials/index.html
Normal file
@ -0,0 +1,177 @@
|
||||
<!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>Tutorials - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="index.html" class="active">Tutorial List</a></li>
|
||||
<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="cli-tool.html">CLI Tool</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>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Tutorials</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Tutorials</h1>
|
||||
|
||||
<p>Step-by-step tutorials that guide you through building complete applications with Wren-CLI. Each tutorial introduces new concepts and builds upon previous knowledge.</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h3><a href="http-client.html">Building an HTTP Client</a></h3>
|
||||
<p>Learn to make HTTP requests, parse JSON responses, and handle errors while building a REST API client.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">http</span>
|
||||
<span class="tag">json</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="websocket-chat.html">WebSocket Chat Application</a></h3>
|
||||
<p>Build a real-time chat application using WebSockets with both client and server components.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">websocket</span>
|
||||
<span class="tag">fibers</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="database-app.html">Database Application</a></h3>
|
||||
<p>Create a complete CRUD application using SQLite for persistent data storage.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">sqlite</span>
|
||||
<span class="tag">io</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="template-rendering.html">Template Rendering</a></h3>
|
||||
<p>Use Jinja templates to generate HTML pages, reports, and configuration files.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">jinja</span>
|
||||
<span class="tag">io</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><a href="cli-tool.html">Building a CLI Tool</a></h3>
|
||||
<p>Create a command-line application with argument parsing, user input, and subprocess management.</p>
|
||||
<div class="card-meta">
|
||||
<span class="tag">os</span>
|
||||
<span class="tag">subprocess</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Learning Path</h2>
|
||||
|
||||
<p>If you are new to Wren-CLI, we recommend following the tutorials in this order:</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>HTTP Client</strong> - Introduces async operations and JSON handling</li>
|
||||
<li><strong>Database Application</strong> - Covers data persistence and file I/O</li>
|
||||
<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>
|
||||
</ol>
|
||||
|
||||
<h2>Prerequisites</h2>
|
||||
|
||||
<p>Before starting the tutorials, you should:</p>
|
||||
|
||||
<ul>
|
||||
<li>Have Wren-CLI <a href="../getting-started/installation.html">installed</a></li>
|
||||
<li>Understand the <a href="../language/index.html">basic syntax</a></li>
|
||||
<li>Be familiar with <a href="../language/classes.html">classes</a> and <a href="../language/methods.html">methods</a></li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="../api/math.html" class="prev">math</a>
|
||||
<a href="http-client.html" class="next">HTTP Client</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
670
manual/tutorials/template-rendering.html
Normal file
670
manual/tutorials/template-rendering.html
Normal file
@ -0,0 +1,670 @@
|
||||
<!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>Template Rendering - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial List</a></li>
|
||||
<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="cli-tool.html">CLI Tool</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>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Tutorials</a>
|
||||
<span class="separator">/</span>
|
||||
<span>Template Rendering</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>Template Rendering</h1>
|
||||
|
||||
<p>In this tutorial, you will learn to use Jinja templates to generate HTML pages, reports, and configuration files. Jinja is a powerful templating engine that separates your presentation logic from your data.</p>
|
||||
|
||||
<h2>What You Will Learn</h2>
|
||||
|
||||
<ul>
|
||||
<li>Basic template syntax (variables, expressions)</li>
|
||||
<li>Control structures (if, for)</li>
|
||||
<li>Template inheritance</li>
|
||||
<li>Filters and macros</li>
|
||||
<li>Loading templates from files</li>
|
||||
</ul>
|
||||
|
||||
<h2>Step 1: Basic Templates</h2>
|
||||
|
||||
<p>Create a file called <code>template_demo.wren</code>:</p>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
var env = Environment.new(DictLoader.new({
|
||||
"greeting": "Hello, {{ name }}!"
|
||||
}))
|
||||
|
||||
var template = env.getTemplate("greeting")
|
||||
var result = template.render({"name": "World"})
|
||||
|
||||
System.print(result) // Hello, World!</code></pre>
|
||||
|
||||
<h2>Step 2: Variables and Expressions</h2>
|
||||
|
||||
<p>Jinja supports various expressions inside <code>{{ }}</code>:</p>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
var templates = {
|
||||
"expressions": "
|
||||
Name: {{ user.name }}
|
||||
Age: {{ user.age }}
|
||||
Adult: {{ user.age >= 18 }}
|
||||
Items: {{ items | length }}
|
||||
First: {{ items[0] }}
|
||||
Upper: {{ user.name | upper }}
|
||||
"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
var template = env.getTemplate("expressions")
|
||||
|
||||
var result = template.render({
|
||||
"user": {"name": "Alice", "age": 25},
|
||||
"items": ["apple", "banana", "cherry"]
|
||||
})
|
||||
|
||||
System.print(result)</code></pre>
|
||||
|
||||
<p>Output:</p>
|
||||
<pre><code>Name: Alice
|
||||
Age: 25
|
||||
Adult: true
|
||||
Items: 3
|
||||
First: apple
|
||||
Upper: ALICE</code></pre>
|
||||
|
||||
<h2>Step 3: Control Structures</h2>
|
||||
|
||||
<h3>Conditionals</h3>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
var templates = {
|
||||
"status": "
|
||||
{% if user.active %}
|
||||
User {{ user.name }} is active.
|
||||
{% elif user.pending %}
|
||||
User {{ user.name }} is pending approval.
|
||||
{% else %}
|
||||
User {{ user.name }} is inactive.
|
||||
{% endif %}
|
||||
"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
var template = env.getTemplate("status")
|
||||
|
||||
System.print(template.render({"user": {"name": "Bob", "active": true}}))
|
||||
System.print(template.render({"user": {"name": "Carol", "pending": true}}))
|
||||
System.print(template.render({"user": {"name": "Dave", "active": false}}))</code></pre>
|
||||
|
||||
<h3>Loops</h3>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
var templates = {
|
||||
"list": "
|
||||
Shopping List:
|
||||
{% for item in items %}
|
||||
- {{ item.name }}: ${{ item.price }}
|
||||
{% endfor %}
|
||||
|
||||
Total items: {{ items | length }}
|
||||
"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
var template = env.getTemplate("list")
|
||||
|
||||
var result = template.render({
|
||||
"items": [
|
||||
{"name": "Apples", "price": 2.99},
|
||||
{"name": "Bread", "price": 3.50},
|
||||
{"name": "Milk", "price": 4.25}
|
||||
]
|
||||
})
|
||||
|
||||
System.print(result)</code></pre>
|
||||
|
||||
<h3>Loop Variables</h3>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
var templates = {
|
||||
"numbered": "
|
||||
{% for item in items %}
|
||||
{{ loop.index }}. {{ item }}{% if loop.first %} (first){% endif %}{% if loop.last %} (last){% endif %}
|
||||
{% endfor %}
|
||||
"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
var template = env.getTemplate("numbered")
|
||||
|
||||
var result = template.render({
|
||||
"items": ["Red", "Green", "Blue"]
|
||||
})
|
||||
|
||||
System.print(result)</code></pre>
|
||||
|
||||
<p>Output:</p>
|
||||
<pre><code>1. Red (first)
|
||||
2. Green
|
||||
3. Blue (last)</code></pre>
|
||||
|
||||
<h2>Step 4: Filters</h2>
|
||||
|
||||
<p>Filters transform values using the pipe (<code>|</code>) syntax:</p>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
var templates = {
|
||||
"filters": "
|
||||
{{ name | upper }}
|
||||
{{ name | lower }}
|
||||
{{ name | capitalize }}
|
||||
{{ name | title }}
|
||||
{{ price | round(2) }}
|
||||
{{ items | join(', ') }}
|
||||
{{ text | truncate(20) }}
|
||||
{{ html | escape }}
|
||||
{{ value | default('N/A') }}
|
||||
"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
var template = env.getTemplate("filters")
|
||||
|
||||
var result = template.render({
|
||||
"name": "hELLo WoRLD",
|
||||
"price": 19.99567,
|
||||
"items": ["a", "b", "c"],
|
||||
"text": "This is a very long string that should be truncated",
|
||||
"html": "<script>alert('xss')</script>",
|
||||
"value": null
|
||||
})
|
||||
|
||||
System.print(result)</code></pre>
|
||||
|
||||
<h2>Step 5: Template Inheritance</h2>
|
||||
|
||||
<p>Template inheritance allows you to build a base template with common structure:</p>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
var templates = {
|
||||
"base.html": "
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}My Site{% endblock %}</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav>Home | About | Contact</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Copyright 2024
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
",
|
||||
|
||||
"home.html": "
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Home - My Site{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Welcome!</h1>
|
||||
<p>This is the home page.</p>
|
||||
{% endblock %}
|
||||
",
|
||||
|
||||
"about.html": "
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}About - My Site{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>About Us</h1>
|
||||
<p>{{ description }}</p>
|
||||
{% endblock %}
|
||||
"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
|
||||
System.print("=== Home Page ===")
|
||||
System.print(env.getTemplate("home.html").render({}))
|
||||
|
||||
System.print("\n=== About Page ===")
|
||||
System.print(env.getTemplate("about.html").render({
|
||||
"description": "We are a software company."
|
||||
}))</code></pre>
|
||||
|
||||
<h2>Step 6: Macros</h2>
|
||||
|
||||
<p>Macros are reusable template functions:</p>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
var templates = {
|
||||
"forms": "
|
||||
{% macro input(name, type='text', value='', placeholder='') %}
|
||||
<input type=\"{{ type }}\" name=\"{{ name }}\" value=\"{{ value }}\" placeholder=\"{{ placeholder }}\">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro button(text, type='button', class='btn') %}
|
||||
<button type=\"{{ type }}\" class=\"{{ class }}\">{{ text }}</button>
|
||||
{% endmacro %}
|
||||
|
||||
<form>
|
||||
{{ input('username', placeholder='Enter username') }}
|
||||
{{ input('password', type='password', placeholder='Enter password') }}
|
||||
{{ button('Login', type='submit', class='btn btn-primary') }}
|
||||
</form>
|
||||
"
|
||||
}
|
||||
|
||||
var env = Environment.new(DictLoader.new(templates))
|
||||
var result = env.getTemplate("forms").render({})
|
||||
|
||||
System.print(result)</code></pre>
|
||||
|
||||
<h2>Step 7: Loading Templates from Files</h2>
|
||||
|
||||
<p>For larger projects, store templates in files:</p>
|
||||
|
||||
<pre><code>import "jinja" for Environment, FileSystemLoader
|
||||
import "io" for File
|
||||
|
||||
File.write("templates/base.html", "
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
")
|
||||
|
||||
File.write("templates/page.html", "
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ title }}</h1>
|
||||
{{ content }}
|
||||
{% endblock %}
|
||||
")
|
||||
|
||||
var env = Environment.new(FileSystemLoader.new("templates"))
|
||||
var template = env.getTemplate("page.html")
|
||||
|
||||
var html = template.render({
|
||||
"title": "My Page",
|
||||
"content": "<p>Hello from a file-based template!</p>"
|
||||
})
|
||||
|
||||
System.print(html)</code></pre>
|
||||
|
||||
<h2>Step 8: Building a Report Generator</h2>
|
||||
|
||||
<p>Let's build a practical example - generating HTML reports:</p>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
import "io" for File
|
||||
import "sqlite" for Sqlite
|
||||
import "datetime" for DateTime
|
||||
|
||||
class ReportGenerator {
|
||||
construct new() {
|
||||
_env = Environment.new(DictLoader.new({
|
||||
"report": "
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { color: #333; }
|
||||
table { border-collapse: collapse; width: 100\%; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||
th { background-color: #4CAF50; color: white; }
|
||||
tr:nth-child(even) { background-color: #f2f2f2; }
|
||||
.summary { background: #f9f9f9; padding: 20px; margin: 20px 0; }
|
||||
.footer { margin-top: 40px; color: #666; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ title }}</h1>
|
||||
<p>Generated: {{ generated_at }}</p>
|
||||
|
||||
<div class=\"summary\">
|
||||
<h2>Summary</h2>
|
||||
<p>Total Records: {{ data | length }}</p>
|
||||
{% if total %}
|
||||
<p>Total Amount: ${{ total | round(2) }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2>Details</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% for header in headers %}
|
||||
<th>{{ header }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data %}
|
||||
<tr>
|
||||
{% for header in headers %}
|
||||
<td>{{ row[header] }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class=\"footer\">
|
||||
Report generated by Wren-CLI Report Generator
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"
|
||||
}))
|
||||
}
|
||||
|
||||
generate(title, headers, data, options) {
|
||||
var template = _env.getTemplate("report")
|
||||
|
||||
var total = null
|
||||
if (options && options["sumColumn"]) {
|
||||
total = 0
|
||||
for (row in data) {
|
||||
total = total + (row[options["sumColumn"]] || 0)
|
||||
}
|
||||
}
|
||||
|
||||
return template.render({
|
||||
"title": title,
|
||||
"headers": headers,
|
||||
"data": data,
|
||||
"total": total,
|
||||
"generated_at": DateTime.now().toString
|
||||
})
|
||||
}
|
||||
|
||||
save(filename, html) {
|
||||
File.write(filename, html)
|
||||
System.print("Report saved to %(filename)")
|
||||
}
|
||||
}
|
||||
|
||||
var generator = ReportGenerator.new()
|
||||
|
||||
var salesData = [
|
||||
{"Product": "Widget A", "Quantity": 150, "Price": 29.99, "Total": 4498.50},
|
||||
{"Product": "Widget B", "Quantity": 75, "Price": 49.99, "Total": 3749.25},
|
||||
{"Product": "Widget C", "Quantity": 200, "Price": 19.99, "Total": 3998.00},
|
||||
{"Product": "Widget D", "Quantity": 50, "Price": 99.99, "Total": 4999.50}
|
||||
]
|
||||
|
||||
var html = generator.generate(
|
||||
"Sales Report Q4 2024",
|
||||
["Product", "Quantity", "Price", "Total"],
|
||||
salesData,
|
||||
{"sumColumn": "Total"}
|
||||
)
|
||||
|
||||
generator.save("sales_report.html", html)
|
||||
System.print("Report generated successfully!")</code></pre>
|
||||
|
||||
<h2>Step 9: Email Templates</h2>
|
||||
|
||||
<p>Create personalized emails with templates:</p>
|
||||
|
||||
<pre><code>import "jinja" for Environment, DictLoader
|
||||
|
||||
class EmailGenerator {
|
||||
construct new() {
|
||||
_env = Environment.new(DictLoader.new({
|
||||
"welcome": "
|
||||
Subject: Welcome to {{ company }}, {{ user.name }}!
|
||||
|
||||
Dear {{ user.name }},
|
||||
|
||||
Thank you for joining {{ company }}! We are excited to have you.
|
||||
|
||||
Your account details:
|
||||
- Username: {{ user.username }}
|
||||
- Email: {{ user.email }}
|
||||
- Plan: {{ user.plan | default('Free') }}
|
||||
|
||||
{% if user.plan == 'Premium' %}
|
||||
As a Premium member, you have access to:
|
||||
{% for feature in premium_features %}
|
||||
- {{ feature }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
If you have any questions, please contact us at {{ support_email }}.
|
||||
|
||||
Best regards,
|
||||
The {{ company }} Team
|
||||
",
|
||||
|
||||
"order_confirmation": "
|
||||
Subject: Order #{{ order.id }} Confirmed
|
||||
|
||||
Dear {{ customer.name }},
|
||||
|
||||
Thank you for your order!
|
||||
|
||||
Order Details:
|
||||
{% for item in order.items %}
|
||||
- {{ item.name }} x {{ item.quantity }} @ ${{ item.price }} = ${{ item.total }}
|
||||
{% endfor %}
|
||||
|
||||
Subtotal: ${{ order.subtotal | round(2) }}
|
||||
Tax: ${{ order.tax | round(2) }}
|
||||
Total: ${{ order.total | round(2) }}
|
||||
|
||||
Shipping to:
|
||||
{{ customer.address.street }}
|
||||
{{ customer.address.city }}, {{ customer.address.state }} {{ customer.address.zip }}
|
||||
|
||||
Estimated delivery: {{ delivery_date }}
|
||||
|
||||
Thank you for shopping with us!
|
||||
"
|
||||
}))
|
||||
}
|
||||
|
||||
welcome(user, company) {
|
||||
return _env.getTemplate("welcome").render({
|
||||
"user": user,
|
||||
"company": company,
|
||||
"support_email": "support@%(company.lower).com",
|
||||
"premium_features": [
|
||||
"Unlimited storage",
|
||||
"Priority support",
|
||||
"Advanced analytics",
|
||||
"Custom integrations"
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
orderConfirmation(customer, order) {
|
||||
return _env.getTemplate("order_confirmation").render({
|
||||
"customer": customer,
|
||||
"order": order,
|
||||
"delivery_date": "3-5 business days"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var emails = EmailGenerator.new()
|
||||
|
||||
var welcomeEmail = emails.welcome(
|
||||
{
|
||||
"name": "Alice Smith",
|
||||
"username": "alice",
|
||||
"email": "alice@example.com",
|
||||
"plan": "Premium"
|
||||
},
|
||||
"Acme Corp"
|
||||
)
|
||||
|
||||
System.print(welcomeEmail)
|
||||
|
||||
System.print("\n---\n")
|
||||
|
||||
var orderEmail = emails.orderConfirmation(
|
||||
{
|
||||
"name": "Bob Jones",
|
||||
"address": {
|
||||
"street": "123 Main St",
|
||||
"city": "Springfield",
|
||||
"state": "IL",
|
||||
"zip": "62701"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ORD-12345",
|
||||
"items": [
|
||||
{"name": "Blue Widget", "quantity": 2, "price": 29.99, "total": 59.98},
|
||||
{"name": "Red Gadget", "quantity": 1, "price": 49.99, "total": 49.99}
|
||||
],
|
||||
"subtotal": 109.97,
|
||||
"tax": 9.90,
|
||||
"total": 119.87
|
||||
}
|
||||
)
|
||||
|
||||
System.print(orderEmail)</code></pre>
|
||||
|
||||
<div class="admonition tip">
|
||||
<div class="admonition-title">Tip</div>
|
||||
<p>Use the <code>escape</code> filter on user-provided content to prevent XSS attacks in HTML output: <code>{{ user_input | escape }}</code></p>
|
||||
</div>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>Jinja whitespace can be controlled with <code>{%-</code> and <code>-%}</code> to strip whitespace before or after tags.</p>
|
||||
</div>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
|
||||
<ul>
|
||||
<li>Explore all <a href="../api/jinja.html">Jinja filters and features</a></li>
|
||||
<li>Combine with <a href="database-app.html">database queries</a> for dynamic reports</li>
|
||||
<li>Build a <a href="cli-tool.html">CLI tool</a> for template processing</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="database-app.html" class="prev">Database App</a>
|
||||
<a href="cli-tool.html" class="next">CLI Tool</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
623
manual/tutorials/websocket-chat.html
Normal file
623
manual/tutorials/websocket-chat.html
Normal file
@ -0,0 +1,623 @@
|
||||
<!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>WebSocket Chat Application - 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/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/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/datetime.html">datetime</a></li>
|
||||
<li><a href="../api/timer.html">timer</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/math.html">math</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<span class="section-title">Tutorials</span>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial List</a></li>
|
||||
<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="cli-tool.html">CLI Tool</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>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<nav class="breadcrumb">
|
||||
<a href="../index.html">Home</a>
|
||||
<span class="separator">/</span>
|
||||
<a href="index.html">Tutorials</a>
|
||||
<span class="separator">/</span>
|
||||
<span>WebSocket Chat</span>
|
||||
</nav>
|
||||
|
||||
<article>
|
||||
<h1>WebSocket Chat Application</h1>
|
||||
|
||||
<p>In this tutorial, you will build a real-time chat application using WebSockets. You will create both a server that handles multiple clients and a client that can send and receive messages.</p>
|
||||
|
||||
<h2>What You Will Learn</h2>
|
||||
|
||||
<ul>
|
||||
<li>Creating a WebSocket server</li>
|
||||
<li>Handling multiple client connections</li>
|
||||
<li>Broadcasting messages to all clients</li>
|
||||
<li>Building a WebSocket client</li>
|
||||
<li>Working with fibers for concurrent operations</li>
|
||||
</ul>
|
||||
|
||||
<h2>Part 1: The Chat Server</h2>
|
||||
|
||||
<h3>Step 1: Basic Server Setup</h3>
|
||||
|
||||
<p>Create a file called <code>chat_server.wren</code>:</p>
|
||||
|
||||
<pre><code>import "websocket" for WebSocketServer
|
||||
import "json" for Json
|
||||
|
||||
System.print("Starting chat server on port 8080...")
|
||||
|
||||
var server = WebSocketServer.new("0.0.0.0", 8080)
|
||||
var clients = []
|
||||
|
||||
while (true) {
|
||||
var client = server.accept()
|
||||
System.print("Client connected!")
|
||||
clients.add(client)
|
||||
}</code></pre>
|
||||
|
||||
<h3>Step 2: Handling Client Messages</h3>
|
||||
|
||||
<p>Now let's process messages from clients using fibers:</p>
|
||||
|
||||
<pre><code>import "websocket" for WebSocketServer, WebSocketMessage
|
||||
import "json" for Json
|
||||
|
||||
System.print("Starting chat server on port 8080...")
|
||||
|
||||
var server = WebSocketServer.new("0.0.0.0", 8080)
|
||||
var clients = []
|
||||
|
||||
var handleClient = Fn.new { |client|
|
||||
while (true) {
|
||||
var message = client.receive()
|
||||
|
||||
if (message == null) {
|
||||
System.print("Client disconnected")
|
||||
break
|
||||
}
|
||||
|
||||
if (message.opcode == WebSocketMessage.TEXT) {
|
||||
System.print("Received: %(message.payload)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var client = server.accept()
|
||||
System.print("Client connected!")
|
||||
clients.add(client)
|
||||
|
||||
var fiber = Fiber.new { handleClient.call(client) }
|
||||
fiber.call()
|
||||
}</code></pre>
|
||||
|
||||
<h3>Step 3: Broadcasting Messages</h3>
|
||||
|
||||
<p>Let's broadcast messages to all connected clients:</p>
|
||||
|
||||
<pre><code>import "websocket" for WebSocketServer, WebSocketMessage
|
||||
import "json" for Json
|
||||
|
||||
System.print("=== Chat Server ===")
|
||||
System.print("Listening on ws://0.0.0.0:8080")
|
||||
|
||||
var server = WebSocketServer.new("0.0.0.0", 8080)
|
||||
var clients = []
|
||||
|
||||
var broadcast = Fn.new { |message, sender|
|
||||
var data = Json.stringify({
|
||||
"type": "message",
|
||||
"from": sender,
|
||||
"text": message
|
||||
})
|
||||
|
||||
for (client in clients) {
|
||||
var fiber = Fiber.new { client.send(data) }
|
||||
fiber.try()
|
||||
}
|
||||
}
|
||||
|
||||
var removeClient = Fn.new { |client|
|
||||
var index = clients.indexOf(client)
|
||||
if (index >= 0) {
|
||||
clients.removeAt(index)
|
||||
}
|
||||
}
|
||||
|
||||
var handleClient = Fn.new { |client, clientId|
|
||||
client.send(Json.stringify({
|
||||
"type": "welcome",
|
||||
"message": "Welcome to the chat!",
|
||||
"clientId": clientId
|
||||
}))
|
||||
|
||||
broadcast.call("%(clientId) joined the chat", "System")
|
||||
|
||||
while (true) {
|
||||
var message = client.receive()
|
||||
|
||||
if (message == null) {
|
||||
System.print("Client %(clientId) disconnected")
|
||||
removeClient.call(client)
|
||||
broadcast.call("%(clientId) left the chat", "System")
|
||||
break
|
||||
}
|
||||
|
||||
if (message.opcode == WebSocketMessage.TEXT) {
|
||||
var data = Json.parse(message.payload)
|
||||
System.print("[%(clientId)] %(data["text"])")
|
||||
broadcast.call(data["text"], clientId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var clientCounter = 0
|
||||
|
||||
while (true) {
|
||||
var client = server.accept()
|
||||
clientCounter = clientCounter + 1
|
||||
var clientId = "User%(clientCounter)"
|
||||
|
||||
System.print("%(clientId) connected")
|
||||
clients.add(client)
|
||||
|
||||
Fiber.new { handleClient.call(client, clientId) }.call()
|
||||
}</code></pre>
|
||||
|
||||
<h2>Part 2: The Chat Client</h2>
|
||||
|
||||
<h3>Step 4: Basic Client</h3>
|
||||
|
||||
<p>Create a file called <code>chat_client.wren</code>:</p>
|
||||
|
||||
<pre><code>import "websocket" for WebSocket, WebSocketMessage
|
||||
import "json" for Json
|
||||
import "io" for Stdin
|
||||
|
||||
System.print("Connecting to chat server...")
|
||||
|
||||
var ws = WebSocket.connect("ws://localhost:8080")
|
||||
System.print("Connected!")
|
||||
|
||||
var receiveMessages = Fn.new {
|
||||
while (true) {
|
||||
var message = ws.receive()
|
||||
|
||||
if (message == null) {
|
||||
System.print("Disconnected from server")
|
||||
break
|
||||
}
|
||||
|
||||
if (message.opcode == WebSocketMessage.TEXT) {
|
||||
var data = Json.parse(message.payload)
|
||||
|
||||
if (data["type"] == "welcome") {
|
||||
System.print("\n%(data["message"])")
|
||||
System.print("You are: %(data["clientId"])\n")
|
||||
} else if (data["type"] == "message") {
|
||||
System.print("[%(data["from"])] %(data["text"])")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Fiber.new { receiveMessages.call() }.call()
|
||||
|
||||
System.print("Type messages and press Enter to send. Type 'quit' to exit.\n")
|
||||
|
||||
while (true) {
|
||||
System.write("> ")
|
||||
var input = Stdin.readLine()
|
||||
|
||||
if (input == "quit") {
|
||||
ws.close()
|
||||
break
|
||||
}
|
||||
|
||||
if (input.count > 0) {
|
||||
ws.send(Json.stringify({"text": input}))
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Part 3: Enhanced Features</h2>
|
||||
|
||||
<h3>Step 5: Private Messages</h3>
|
||||
|
||||
<p>Add support for private messages with the <code>/msg</code> command:</p>
|
||||
|
||||
<pre><code>import "websocket" for WebSocketServer, WebSocketMessage
|
||||
import "json" for Json
|
||||
import "regex" for Regex
|
||||
|
||||
System.print("=== Enhanced Chat Server ===")
|
||||
System.print("Listening on ws://0.0.0.0:8080")
|
||||
|
||||
var server = WebSocketServer.new("0.0.0.0", 8080)
|
||||
var clients = {}
|
||||
|
||||
var broadcast = Fn.new { |type, data|
|
||||
var message = Json.stringify({"type": type}.merge(data))
|
||||
for (id in clients.keys) {
|
||||
var fiber = Fiber.new { clients[id].send(message) }
|
||||
fiber.try()
|
||||
}
|
||||
}
|
||||
|
||||
var sendTo = Fn.new { |clientId, type, data|
|
||||
if (clients.containsKey(clientId)) {
|
||||
var message = Json.stringify({"type": type}.merge(data))
|
||||
clients[clientId].send(message)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var handleCommand = Fn.new { |client, clientId, text|
|
||||
var msgMatch = Regex.new("^/msg (\\w+) (.+)$").match(text)
|
||||
if (msgMatch) {
|
||||
var target = msgMatch.group(1)
|
||||
var message = msgMatch.group(2)
|
||||
|
||||
if (sendTo.call(target, "private", {"from": clientId, "text": message})) {
|
||||
sendTo.call(clientId, "private", {"from": "You -> %(target)", "text": message})
|
||||
} else {
|
||||
sendTo.call(clientId, "error", {"message": "User %(target) not found"})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (text == "/users") {
|
||||
var userList = clients.keys.toList.join(", ")
|
||||
sendTo.call(clientId, "info", {"message": "Online users: %(userList)"})
|
||||
return true
|
||||
}
|
||||
|
||||
if (text == "/help") {
|
||||
sendTo.call(clientId, "info", {
|
||||
"message": "Commands: /msg <user> <message>, /users, /help"
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var handleClient = Fn.new { |client, clientId|
|
||||
client.send(Json.stringify({
|
||||
"type": "welcome",
|
||||
"clientId": clientId
|
||||
}))
|
||||
|
||||
broadcast.call("join", {"user": clientId})
|
||||
|
||||
while (true) {
|
||||
var message = client.receive()
|
||||
|
||||
if (message == null) {
|
||||
clients.remove(clientId)
|
||||
broadcast.call("leave", {"user": clientId})
|
||||
System.print("%(clientId) disconnected")
|
||||
break
|
||||
}
|
||||
|
||||
if (message.opcode == WebSocketMessage.TEXT) {
|
||||
var data = Json.parse(message.payload)
|
||||
var text = data["text"]
|
||||
|
||||
if (!handleCommand.call(client, clientId, text)) {
|
||||
broadcast.call("message", {"from": clientId, "text": text})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var clientCounter = 0
|
||||
|
||||
while (true) {
|
||||
var client = server.accept()
|
||||
clientCounter = clientCounter + 1
|
||||
var clientId = "User%(clientCounter)"
|
||||
|
||||
System.print("%(clientId) connected")
|
||||
clients[clientId] = client
|
||||
|
||||
Fiber.new { handleClient.call(client, clientId) }.call()
|
||||
}</code></pre>
|
||||
|
||||
<h3>Step 6: Enhanced Client</h3>
|
||||
|
||||
<p>Update the client to handle new message types:</p>
|
||||
|
||||
<pre><code>import "websocket" for WebSocket, WebSocketMessage
|
||||
import "json" for Json
|
||||
import "io" for Stdin
|
||||
|
||||
System.print("Connecting to chat server...")
|
||||
|
||||
var ws = WebSocket.connect("ws://localhost:8080")
|
||||
var myId = ""
|
||||
|
||||
var receiveMessages = Fn.new {
|
||||
while (true) {
|
||||
var message = ws.receive()
|
||||
|
||||
if (message == null) {
|
||||
System.print("\nDisconnected from server")
|
||||
break
|
||||
}
|
||||
|
||||
if (message.opcode == WebSocketMessage.TEXT) {
|
||||
var data = Json.parse(message.payload)
|
||||
|
||||
if (data["type"] == "welcome") {
|
||||
myId = data["clientId"]
|
||||
System.print("Connected as %(myId)")
|
||||
System.print("Type /help for commands\n")
|
||||
} else if (data["type"] == "message") {
|
||||
System.print("[%(data["from"])] %(data["text"])")
|
||||
} else if (data["type"] == "private") {
|
||||
System.print("[PM %(data["from"])] %(data["text"])")
|
||||
} else if (data["type"] == "join") {
|
||||
System.print("* %(data["user"]) joined the chat")
|
||||
} else if (data["type"] == "leave") {
|
||||
System.print("* %(data["user"]) left the chat")
|
||||
} else if (data["type"] == "info") {
|
||||
System.print("INFO: %(data["message"])")
|
||||
} else if (data["type"] == "error") {
|
||||
System.print("ERROR: %(data["message"])")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Fiber.new { receiveMessages.call() }.call()
|
||||
|
||||
while (true) {
|
||||
var input = Stdin.readLine()
|
||||
|
||||
if (input == null || input == "/quit") {
|
||||
ws.close()
|
||||
break
|
||||
}
|
||||
|
||||
if (input.count > 0) {
|
||||
ws.send(Json.stringify({"text": input}))
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Part 4: Running the Application</h2>
|
||||
|
||||
<h3>Starting the Server</h3>
|
||||
|
||||
<pre><code>$ wren_cli chat_server.wren
|
||||
=== Enhanced Chat Server ===
|
||||
Listening on ws://0.0.0.0:8080</code></pre>
|
||||
|
||||
<h3>Connecting Clients</h3>
|
||||
|
||||
<p>Open multiple terminals and run:</p>
|
||||
|
||||
<pre><code>$ wren_cli chat_client.wren
|
||||
Connecting to chat server...
|
||||
Connected as User1
|
||||
Type /help for commands
|
||||
|
||||
> Hello everyone!
|
||||
[User1] Hello everyone!
|
||||
* User2 joined the chat
|
||||
[User2] Hi there!
|
||||
> /msg User2 This is a private message
|
||||
[PM You -> User2] This is a private message</code></pre>
|
||||
|
||||
<h2>Complete Server Code</h2>
|
||||
|
||||
<pre><code>import "websocket" for WebSocketServer, WebSocketMessage
|
||||
import "json" for Json
|
||||
import "regex" for Regex
|
||||
import "datetime" for DateTime
|
||||
|
||||
class ChatServer {
|
||||
construct new(host, port) {
|
||||
_server = WebSocketServer.new(host, port)
|
||||
_clients = {}
|
||||
_messageHistory = []
|
||||
_maxHistory = 100
|
||||
}
|
||||
|
||||
broadcast(type, data) {
|
||||
var message = Json.stringify({"type": type, "timestamp": DateTime.now().toString}.merge(data))
|
||||
for (id in _clients.keys) {
|
||||
var fiber = Fiber.new { _clients[id].send(message) }
|
||||
fiber.try()
|
||||
}
|
||||
}
|
||||
|
||||
sendTo(clientId, type, data) {
|
||||
if (_clients.containsKey(clientId)) {
|
||||
var message = Json.stringify({"type": type}.merge(data))
|
||||
_clients[clientId].send(message)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
handleCommand(client, clientId, text) {
|
||||
if (text.startsWith("/msg ")) {
|
||||
var parts = text[5..-1].split(" ")
|
||||
if (parts.count >= 2) {
|
||||
var target = parts[0]
|
||||
var message = parts[1..-1].join(" ")
|
||||
|
||||
if (sendTo(target, "private", {"from": clientId, "text": message})) {
|
||||
sendTo(clientId, "private", {"from": "You -> %(target)", "text": message})
|
||||
} else {
|
||||
sendTo(clientId, "error", {"message": "User not found"})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (text == "/users") {
|
||||
sendTo(clientId, "info", {"message": "Online: %(_clients.keys.toList.join(", "))"})
|
||||
return true
|
||||
}
|
||||
|
||||
if (text == "/help") {
|
||||
sendTo(clientId, "info", {"message": "/msg <user> <text>, /users, /help, /quit"})
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
handleClient(client, clientId) {
|
||||
_clients[clientId] = client
|
||||
|
||||
sendTo(clientId, "welcome", {"clientId": clientId, "users": _clients.keys.toList})
|
||||
broadcast("join", {"user": clientId})
|
||||
|
||||
while (true) {
|
||||
var message = client.receive()
|
||||
|
||||
if (message == null) {
|
||||
_clients.remove(clientId)
|
||||
broadcast("leave", {"user": clientId})
|
||||
System.print("[%(DateTime.now())] %(clientId) disconnected")
|
||||
break
|
||||
}
|
||||
|
||||
if (message.opcode == WebSocketMessage.TEXT) {
|
||||
var data = Json.parse(message.payload)
|
||||
var text = data["text"]
|
||||
|
||||
if (!handleCommand(client, clientId, text)) {
|
||||
System.print("[%(DateTime.now())] %(clientId): %(text)")
|
||||
broadcast("message", {"from": clientId, "text": text})
|
||||
|
||||
_messageHistory.add({"from": clientId, "text": text, "time": DateTime.now().toString})
|
||||
if (_messageHistory.count > _maxHistory) {
|
||||
_messageHistory.removeAt(0)
|
||||
}
|
||||
}
|
||||
} else if (message.opcode == WebSocketMessage.PING) {
|
||||
client.pong(message.payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run() {
|
||||
System.print("Chat server running on port 8080")
|
||||
var counter = 0
|
||||
|
||||
while (true) {
|
||||
var client = _server.accept()
|
||||
counter = counter + 1
|
||||
var clientId = "User%(counter)"
|
||||
|
||||
System.print("[%(DateTime.now())] %(clientId) connected")
|
||||
|
||||
Fiber.new { handleClient(client, clientId) }.call()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var server = ChatServer.new("0.0.0.0", 8080)
|
||||
server.run()</code></pre>
|
||||
|
||||
<div class="admonition note">
|
||||
<div class="admonition-title">Note</div>
|
||||
<p>WebSocket connections in Wren-CLI use fibers for concurrent handling. Each client runs in its own fiber, allowing the server to handle multiple connections simultaneously.</p>
|
||||
</div>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
|
||||
<ul>
|
||||
<li>Add user authentication</li>
|
||||
<li>Store message history in <a href="database-app.html">SQLite</a></li>
|
||||
<li>Create chat rooms</li>
|
||||
<li>See the <a href="../api/websocket.html">WebSocket API reference</a></li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer class="page-footer">
|
||||
<a href="http-client.html" class="prev">HTTP Client</a>
|
||||
<a href="database-app.html" class="next">Database App</a>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
<script src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -110,6 +110,7 @@ OBJECTS += $(OBJDIR)/getnameinfo.o
|
||||
OBJECTS += $(OBJDIR)/idna.o
|
||||
OBJECTS += $(OBJDIR)/inet.o
|
||||
OBJECTS += $(OBJDIR)/base64.o
|
||||
OBJECTS += $(OBJDIR)/bytes.o
|
||||
OBJECTS += $(OBJDIR)/cJSON.o
|
||||
OBJECTS += $(OBJDIR)/crypto.o
|
||||
OBJECTS += $(OBJDIR)/datetime.o
|
||||
@ -145,6 +146,8 @@ OBJECTS += $(OBJDIR)/signal_module.o
|
||||
OBJECTS += $(OBJDIR)/sqlite3.o
|
||||
OBJECTS += $(OBJDIR)/sqlite_module.o
|
||||
OBJECTS += $(OBJDIR)/subprocess.o
|
||||
OBJECTS += $(OBJDIR)/strutil.o
|
||||
OBJECTS += $(OBJDIR)/pathlib.o
|
||||
OBJECTS += $(OBJDIR)/tls.o
|
||||
OBJECTS += $(OBJDIR)/stream.o
|
||||
OBJECTS += $(OBJDIR)/strscpy.o
|
||||
@ -380,6 +383,9 @@ $(OBJDIR)/vm.o: ../../src/cli/vm.c
|
||||
$(OBJDIR)/base64.o: ../../src/module/base64.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/bytes.o: ../../src/module/bytes.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/cJSON.o: ../../deps/cjson/cJSON.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
@ -434,6 +440,12 @@ $(OBJDIR)/sqlite3.o: ../../deps/sqlite/sqlite3.c
|
||||
$(OBJDIR)/sqlite_module.o: ../../src/module/sqlite_module.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/strutil.o: ../../src/module/strutil.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/pathlib.o: ../../src/module/pathlib.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
$(OBJDIR)/tls.o: ../../src/module/tls.c
|
||||
@echo $(notdir $<)
|
||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||
|
||||
20
recursion_error.wren
vendored
Normal file
20
recursion_error.wren
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
import "io" for File, Directory
|
||||
|
||||
|
||||
var listWrenFiles
|
||||
|
||||
listWrenFiles = Fn.new { |path|
|
||||
var items = Directory.list(path)
|
||||
|
||||
for (item in items) {
|
||||
var fullPath = "%(path)/%(item)"
|
||||
|
||||
if (Directory.exists(fullPath)) {
|
||||
listWrenFiles.call(fullPath)
|
||||
} else if (item.endsWith(".wren")) {
|
||||
System.print(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listWrenFiles.call(".")
|
||||
@ -3,30 +3,47 @@
|
||||
|
||||
#include "modules.h"
|
||||
|
||||
#include "argparse.wren.inc"
|
||||
#include "base64.wren.inc"
|
||||
#include "strutil.wren.inc"
|
||||
#include "bytes.wren.inc"
|
||||
#include "crypto.wren.inc"
|
||||
#include "dataset.wren.inc"
|
||||
#include "datetime.wren.inc"
|
||||
#include "dns.wren.inc"
|
||||
#include "env.wren.inc"
|
||||
#include "html.wren.inc"
|
||||
#include "http.wren.inc"
|
||||
#include "io.wren.inc"
|
||||
#include "jinja.wren.inc"
|
||||
#include "json.wren.inc"
|
||||
#include "markdown.wren.inc"
|
||||
#include "math.wren.inc"
|
||||
#include "net.wren.inc"
|
||||
#include "os.wren.inc"
|
||||
#include "pathlib.wren.inc"
|
||||
#include "regex.wren.inc"
|
||||
#include "repl.wren.inc"
|
||||
#include "scheduler.wren.inc"
|
||||
#include "signal.wren.inc"
|
||||
#include "sqlite.wren.inc"
|
||||
#include "subprocess.wren.inc"
|
||||
#include "tempfile.wren.inc"
|
||||
#include "timer.wren.inc"
|
||||
#include "tls.wren.inc"
|
||||
#include "uuid.wren.inc"
|
||||
#include "wdantic.wren.inc"
|
||||
#include "web.wren.inc"
|
||||
#include "websocket.wren.inc"
|
||||
|
||||
extern void base64Encode(WrenVM* vm);
|
||||
extern void base64Decode(WrenVM* vm);
|
||||
extern void bytesToList(WrenVM* vm);
|
||||
extern void bytesFromList(WrenVM* vm);
|
||||
extern void bytesXorMask(WrenVM* vm);
|
||||
extern void bytesConcat(WrenVM* vm);
|
||||
extern void bytesSlice(WrenVM* vm);
|
||||
extern void bytesLength(WrenVM* vm);
|
||||
extern void cryptoRandomBytes(WrenVM* vm);
|
||||
extern void cryptoMd5(WrenVM* vm);
|
||||
extern void cryptoSha1(WrenVM* vm);
|
||||
@ -149,6 +166,29 @@ extern void tlsSocketConnect(WrenVM* vm);
|
||||
extern void tlsSocketWrite(WrenVM* vm);
|
||||
extern void tlsSocketRead(WrenVM* vm);
|
||||
extern void tlsSocketClose(WrenVM* vm);
|
||||
extern void strutilToLower(WrenVM* vm);
|
||||
extern void strutilToUpper(WrenVM* vm);
|
||||
extern void strutilHexEncode(WrenVM* vm);
|
||||
extern void strutilHexDecode(WrenVM* vm);
|
||||
extern void strutilRepeat(WrenVM* vm);
|
||||
extern void strutilPadLeft(WrenVM* vm);
|
||||
extern void strutilPadRight(WrenVM* vm);
|
||||
extern void strutilEscapeHtml(WrenVM* vm);
|
||||
extern void strutilEscapeJson(WrenVM* vm);
|
||||
extern void strutilUrlEncode(WrenVM* vm);
|
||||
extern void strutilUrlDecode(WrenVM* vm);
|
||||
extern void pathlibIsSymlink(WrenVM* vm);
|
||||
extern void pathlibLstat(WrenVM* vm);
|
||||
extern void pathlibTouch(WrenVM* vm);
|
||||
extern void pathlibRename(WrenVM* vm);
|
||||
extern void pathlibChmod(WrenVM* vm);
|
||||
extern void pathlibSymlinkTo(WrenVM* vm);
|
||||
extern void pathlibHardlinkTo(WrenVM* vm);
|
||||
extern void pathlibReadlink(WrenVM* vm);
|
||||
extern void pathlibSamefile(WrenVM* vm);
|
||||
extern void pathlibGlob(WrenVM* vm);
|
||||
extern void pathlibOwner(WrenVM* vm);
|
||||
extern void pathlibGroup(WrenVM* vm);
|
||||
|
||||
// The maximum number of foreign methods a single class defines. Ideally, we
|
||||
// would use variable-length arrays for each class in the table below, but
|
||||
@ -218,12 +258,24 @@ typedef struct
|
||||
// The array of built-in modules.
|
||||
static ModuleRegistry modules[] =
|
||||
{
|
||||
MODULE(argparse)
|
||||
END_MODULE
|
||||
MODULE(base64)
|
||||
CLASS(Base64)
|
||||
STATIC_METHOD("encode(_)", base64Encode)
|
||||
STATIC_METHOD("decode(_)", base64Decode)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(bytes)
|
||||
CLASS(Bytes)
|
||||
STATIC_METHOD("toList(_)", bytesToList)
|
||||
STATIC_METHOD("fromList(_)", bytesFromList)
|
||||
STATIC_METHOD("xorMask(_,_)", bytesXorMask)
|
||||
STATIC_METHOD("concat(_,_)", bytesConcat)
|
||||
STATIC_METHOD("slice(_,_,_)", bytesSlice)
|
||||
STATIC_METHOD("length(_)", bytesLength)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(crypto)
|
||||
CLASS(Crypto)
|
||||
STATIC_METHOD("randomBytes_(_,_)", cryptoRandomBytes)
|
||||
@ -234,6 +286,8 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("sha256_(_)", cryptoSha256)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(dataset)
|
||||
END_MODULE
|
||||
MODULE(datetime)
|
||||
CLASS(DateTime)
|
||||
STATIC_METHOD("now_()", datetimeNow)
|
||||
@ -255,6 +309,8 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("all", envAll)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(html)
|
||||
END_MODULE
|
||||
MODULE(http)
|
||||
END_MODULE
|
||||
MODULE(io)
|
||||
@ -313,6 +369,8 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("parse(_)", jsonParse)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(markdown)
|
||||
END_MODULE
|
||||
MODULE(math)
|
||||
CLASS(Math)
|
||||
STATIC_METHOD("sin(_)", mathSin)
|
||||
@ -370,6 +428,22 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("version", processVersion)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(pathlib)
|
||||
CLASS(Path)
|
||||
STATIC_METHOD("isSymlink_(_,_)", pathlibIsSymlink)
|
||||
STATIC_METHOD("lstat_(_,_)", pathlibLstat)
|
||||
STATIC_METHOD("touch_(_,_)", pathlibTouch)
|
||||
STATIC_METHOD("rename_(_,_,_)", pathlibRename)
|
||||
STATIC_METHOD("chmod_(_,_,_)", pathlibChmod)
|
||||
STATIC_METHOD("symlinkTo_(_,_,_)", pathlibSymlinkTo)
|
||||
STATIC_METHOD("hardlinkTo_(_,_,_)", pathlibHardlinkTo)
|
||||
STATIC_METHOD("readlink_(_,_)", pathlibReadlink)
|
||||
STATIC_METHOD("samefile_(_,_,_)", pathlibSamefile)
|
||||
STATIC_METHOD("glob_(_,_,_,_)", pathlibGlob)
|
||||
STATIC_METHOD("owner_(_,_)", pathlibOwner)
|
||||
STATIC_METHOD("group_(_,_)", pathlibGroup)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(regex)
|
||||
CLASS(Regex)
|
||||
ALLOCATE(regexAllocate)
|
||||
@ -398,6 +472,21 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("reset_(_)", signalReset)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(strutil)
|
||||
CLASS(Str)
|
||||
STATIC_METHOD("toLower(_)", strutilToLower)
|
||||
STATIC_METHOD("toUpper(_)", strutilToUpper)
|
||||
STATIC_METHOD("hexEncode(_)", strutilHexEncode)
|
||||
STATIC_METHOD("hexDecode(_)", strutilHexDecode)
|
||||
STATIC_METHOD("repeat(_,_)", strutilRepeat)
|
||||
STATIC_METHOD("padLeft(_,_,_)", strutilPadLeft)
|
||||
STATIC_METHOD("padRight(_,_,_)", strutilPadRight)
|
||||
STATIC_METHOD("escapeHtml(_)", strutilEscapeHtml)
|
||||
STATIC_METHOD("escapeJson(_)", strutilEscapeJson)
|
||||
STATIC_METHOD("urlEncode(_)", strutilUrlEncode)
|
||||
STATIC_METHOD("urlDecode(_)", strutilUrlDecode)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(sqlite)
|
||||
CLASS(Database)
|
||||
ALLOCATE(sqliteAllocate)
|
||||
@ -416,6 +505,8 @@ static ModuleRegistry modules[] =
|
||||
STATIC_METHOD("run_(_,_)", subprocessRun)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(tempfile)
|
||||
END_MODULE
|
||||
MODULE(timer)
|
||||
CLASS(Timer)
|
||||
STATIC_METHOD("startTimer_(_,_)", timerStartTimer)
|
||||
@ -431,6 +522,12 @@ static ModuleRegistry modules[] =
|
||||
METHOD("close_()", tlsSocketClose)
|
||||
END_CLASS
|
||||
END_MODULE
|
||||
MODULE(uuid)
|
||||
END_MODULE
|
||||
MODULE(wdantic)
|
||||
END_MODULE
|
||||
MODULE(web)
|
||||
END_MODULE
|
||||
MODULE(websocket)
|
||||
END_MODULE
|
||||
|
||||
|
||||
274
src/module/argparse.wren
vendored
Normal file
274
src/module/argparse.wren
vendored
Normal file
@ -0,0 +1,274 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "os" for Process
|
||||
import "strutil" for Str
|
||||
|
||||
class ArgumentParser {
|
||||
construct new() {
|
||||
_description = ""
|
||||
_positional = []
|
||||
_optional = []
|
||||
_prog = null
|
||||
}
|
||||
|
||||
construct new(description) {
|
||||
_description = description
|
||||
_positional = []
|
||||
_optional = []
|
||||
_prog = null
|
||||
}
|
||||
|
||||
prog { _prog }
|
||||
prog=(value) { _prog = value }
|
||||
description { _description }
|
||||
description=(value) { _description = value }
|
||||
|
||||
addArgument(name) { addArgument(name, {}) }
|
||||
|
||||
addArgument(name, options) {
|
||||
if (name.startsWith("-")) {
|
||||
var arg = {
|
||||
"name": name,
|
||||
"long": options.containsKey("long") ? options["long"] : null,
|
||||
"type": options.containsKey("type") ? options["type"] : "string",
|
||||
"default": options.containsKey("default") ? options["default"] : null,
|
||||
"required": options.containsKey("required") ? options["required"] : false,
|
||||
"help": options.containsKey("help") ? options["help"] : "",
|
||||
"choices": options.containsKey("choices") ? options["choices"] : null,
|
||||
"action": options.containsKey("action") ? options["action"] : "store",
|
||||
"nargs": options.containsKey("nargs") ? options["nargs"] : null,
|
||||
"dest": options.containsKey("dest") ? options["dest"] : null
|
||||
}
|
||||
_optional.add(arg)
|
||||
} else {
|
||||
var arg = {
|
||||
"name": name,
|
||||
"type": options.containsKey("type") ? options["type"] : "string",
|
||||
"default": options.containsKey("default") ? options["default"] : null,
|
||||
"required": options.containsKey("required") ? options["required"] : true,
|
||||
"help": options.containsKey("help") ? options["help"] : "",
|
||||
"choices": options.containsKey("choices") ? options["choices"] : null,
|
||||
"nargs": options.containsKey("nargs") ? options["nargs"] : null
|
||||
}
|
||||
_positional.add(arg)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
destName_(arg) {
|
||||
if (arg["dest"]) return arg["dest"]
|
||||
var name = arg["long"] ? arg["long"] : arg["name"]
|
||||
while (name.startsWith("-")) name = name[1..-1]
|
||||
return name.replace("-", "_")
|
||||
}
|
||||
|
||||
parseArgs() { parseArgs(Process.arguments) }
|
||||
|
||||
parseArgs(args) {
|
||||
var result = {}
|
||||
|
||||
for (arg in _optional) {
|
||||
var dest = destName_(arg)
|
||||
if (arg["action"] == "storeTrue") {
|
||||
result[dest] = false
|
||||
} else if (arg["action"] == "storeFalse") {
|
||||
result[dest] = true
|
||||
} else if (arg["action"] == "count") {
|
||||
result[dest] = 0
|
||||
} else if (arg["action"] == "append") {
|
||||
result[dest] = []
|
||||
} else if (arg["default"] != null) {
|
||||
result[dest] = arg["default"]
|
||||
} else {
|
||||
result[dest] = null
|
||||
}
|
||||
}
|
||||
|
||||
for (arg in _positional) {
|
||||
if (arg["default"] != null) {
|
||||
result[arg["name"]] = arg["default"]
|
||||
} else {
|
||||
result[arg["name"]] = null
|
||||
}
|
||||
}
|
||||
|
||||
var posIdx = 0
|
||||
var i = 0
|
||||
while (i < args.count) {
|
||||
var token = args[i]
|
||||
|
||||
if (token.startsWith("-")) {
|
||||
var matchedArg = null
|
||||
for (arg in _optional) {
|
||||
if (token == arg["name"] || token == arg["long"]) {
|
||||
matchedArg = arg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedArg == null) {
|
||||
Fiber.abort("Unknown option: %(token)")
|
||||
}
|
||||
|
||||
var dest = destName_(matchedArg)
|
||||
if (matchedArg["action"] == "storeTrue") {
|
||||
result[dest] = true
|
||||
i = i + 1
|
||||
} else if (matchedArg["action"] == "storeFalse") {
|
||||
result[dest] = false
|
||||
i = i + 1
|
||||
} else if (matchedArg["action"] == "count") {
|
||||
result[dest] = result[dest] + 1
|
||||
i = i + 1
|
||||
} else if (matchedArg["action"] == "append") {
|
||||
if (i + 1 >= args.count) {
|
||||
Fiber.abort("Option %(token) requires a value")
|
||||
}
|
||||
i = i + 1
|
||||
result[dest].add(convertValue_(args[i], matchedArg["type"]))
|
||||
i = i + 1
|
||||
} else {
|
||||
if (matchedArg["nargs"] == "*") {
|
||||
var values = []
|
||||
i = i + 1
|
||||
while (i < args.count && !args[i].startsWith("-")) {
|
||||
values.add(convertValue_(args[i], matchedArg["type"]))
|
||||
i = i + 1
|
||||
}
|
||||
result[dest] = values
|
||||
} else if (matchedArg["nargs"] == "+") {
|
||||
var values = []
|
||||
i = i + 1
|
||||
while (i < args.count && !args[i].startsWith("-")) {
|
||||
values.add(convertValue_(args[i], matchedArg["type"]))
|
||||
i = i + 1
|
||||
}
|
||||
if (values.count == 0) {
|
||||
Fiber.abort("Option %(token) requires at least one value")
|
||||
}
|
||||
result[dest] = values
|
||||
} else if (matchedArg["nargs"] is Num) {
|
||||
var values = []
|
||||
var n = matchedArg["nargs"]
|
||||
i = i + 1
|
||||
for (j in 0...n) {
|
||||
if (i >= args.count) {
|
||||
Fiber.abort("Option %(token) requires %(n) values")
|
||||
}
|
||||
values.add(convertValue_(args[i], matchedArg["type"]))
|
||||
i = i + 1
|
||||
}
|
||||
result[dest] = values
|
||||
} else {
|
||||
if (i + 1 >= args.count) {
|
||||
Fiber.abort("Option %(token) requires a value")
|
||||
}
|
||||
i = i + 1
|
||||
var value = convertValue_(args[i], matchedArg["type"])
|
||||
if (matchedArg["choices"] && !matchedArg["choices"].contains(value)) {
|
||||
Fiber.abort("Invalid choice '%(value)' for %(token)")
|
||||
}
|
||||
result[dest] = value
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (posIdx < _positional.count) {
|
||||
var arg = _positional[posIdx]
|
||||
if (arg["nargs"] == "*" || arg["nargs"] == "+") {
|
||||
var values = []
|
||||
while (i < args.count && !args[i].startsWith("-")) {
|
||||
values.add(convertValue_(args[i], arg["type"]))
|
||||
i = i + 1
|
||||
}
|
||||
if (arg["nargs"] == "+" && values.count == 0) {
|
||||
Fiber.abort("Argument %(arg["name"]) requires at least one value")
|
||||
}
|
||||
result[arg["name"]] = values
|
||||
} else {
|
||||
var value = convertValue_(token, arg["type"])
|
||||
if (arg["choices"] && !arg["choices"].contains(value)) {
|
||||
Fiber.abort("Invalid choice '%(value)' for %(arg["name"])")
|
||||
}
|
||||
result[arg["name"]] = value
|
||||
i = i + 1
|
||||
}
|
||||
posIdx = posIdx + 1
|
||||
} else {
|
||||
Fiber.abort("Unexpected argument: %(token)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (arg in _positional) {
|
||||
if (arg["required"] && result[arg["name"]] == null) {
|
||||
Fiber.abort("Missing required argument: %(arg["name"])")
|
||||
}
|
||||
}
|
||||
|
||||
for (arg in _optional) {
|
||||
var dest = destName_(arg)
|
||||
if (arg["required"] && result[dest] == null) {
|
||||
Fiber.abort("Missing required option: %(arg["name"])")
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
convertValue_(value, type) {
|
||||
if (type == "int") {
|
||||
return Num.fromString(value)
|
||||
} else if (type == "float") {
|
||||
return Num.fromString(value)
|
||||
} else if (type == "bool") {
|
||||
if (value == "true" || value == "1" || value == "yes") return true
|
||||
if (value == "false" || value == "0" || value == "no") return false
|
||||
Fiber.abort("Invalid boolean value: %(value)")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
printHelp() {
|
||||
var prog = _prog ? _prog : "program"
|
||||
System.print("usage: %(prog) [options]%(positionalUsage_())")
|
||||
if (_description.count > 0) {
|
||||
System.print("\n%(description)")
|
||||
}
|
||||
if (_positional.count > 0) {
|
||||
System.print("\npositional arguments:")
|
||||
for (arg in _positional) {
|
||||
var helpText = arg["help"].count > 0 ? arg["help"] : ""
|
||||
System.print(" %(pad_(arg["name"], 20)) %(helpText)")
|
||||
}
|
||||
}
|
||||
if (_optional.count > 0) {
|
||||
System.print("\noptional arguments:")
|
||||
for (arg in _optional) {
|
||||
var names = arg["name"]
|
||||
if (arg["long"]) names = names + ", " + arg["long"]
|
||||
var helpText = arg["help"].count > 0 ? arg["help"] : ""
|
||||
System.print(" %(pad_(names, 20)) %(helpText)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
positionalUsage_() {
|
||||
if (_positional.count == 0) return ""
|
||||
var parts = []
|
||||
for (arg in _positional) {
|
||||
if (arg["nargs"] == "*") {
|
||||
parts.add("[%(arg["name"]) ...]")
|
||||
} else if (arg["nargs"] == "+") {
|
||||
parts.add("%(arg["name"]) [...]")
|
||||
} else if (arg["required"]) {
|
||||
parts.add(arg["name"])
|
||||
} else {
|
||||
parts.add("[%(arg["name"])]")
|
||||
}
|
||||
}
|
||||
return " " + parts.join(" ")
|
||||
}
|
||||
|
||||
pad_(str, width) { Str.padRight(str, width, " ") }
|
||||
}
|
||||
278
src/module/argparse.wren.inc
Normal file
278
src/module/argparse.wren.inc
Normal file
@ -0,0 +1,278 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/argparse.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* argparseModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"os\" for Process\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class ArgumentParser {\n"
|
||||
" construct new() {\n"
|
||||
" _description = \"\"\n"
|
||||
" _positional = []\n"
|
||||
" _optional = []\n"
|
||||
" _prog = null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" construct new(description) {\n"
|
||||
" _description = description\n"
|
||||
" _positional = []\n"
|
||||
" _optional = []\n"
|
||||
" _prog = null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" prog { _prog }\n"
|
||||
" prog=(value) { _prog = value }\n"
|
||||
" description { _description }\n"
|
||||
" description=(value) { _description = value }\n"
|
||||
"\n"
|
||||
" addArgument(name) { addArgument(name, {}) }\n"
|
||||
"\n"
|
||||
" addArgument(name, options) {\n"
|
||||
" if (name.startsWith(\"-\")) {\n"
|
||||
" var arg = {\n"
|
||||
" \"name\": name,\n"
|
||||
" \"long\": options.containsKey(\"long\") ? options[\"long\"] : null,\n"
|
||||
" \"type\": options.containsKey(\"type\") ? options[\"type\"] : \"string\",\n"
|
||||
" \"default\": options.containsKey(\"default\") ? options[\"default\"] : null,\n"
|
||||
" \"required\": options.containsKey(\"required\") ? options[\"required\"] : false,\n"
|
||||
" \"help\": options.containsKey(\"help\") ? options[\"help\"] : \"\",\n"
|
||||
" \"choices\": options.containsKey(\"choices\") ? options[\"choices\"] : null,\n"
|
||||
" \"action\": options.containsKey(\"action\") ? options[\"action\"] : \"store\",\n"
|
||||
" \"nargs\": options.containsKey(\"nargs\") ? options[\"nargs\"] : null,\n"
|
||||
" \"dest\": options.containsKey(\"dest\") ? options[\"dest\"] : null\n"
|
||||
" }\n"
|
||||
" _optional.add(arg)\n"
|
||||
" } else {\n"
|
||||
" var arg = {\n"
|
||||
" \"name\": name,\n"
|
||||
" \"type\": options.containsKey(\"type\") ? options[\"type\"] : \"string\",\n"
|
||||
" \"default\": options.containsKey(\"default\") ? options[\"default\"] : null,\n"
|
||||
" \"required\": options.containsKey(\"required\") ? options[\"required\"] : true,\n"
|
||||
" \"help\": options.containsKey(\"help\") ? options[\"help\"] : \"\",\n"
|
||||
" \"choices\": options.containsKey(\"choices\") ? options[\"choices\"] : null,\n"
|
||||
" \"nargs\": options.containsKey(\"nargs\") ? options[\"nargs\"] : null\n"
|
||||
" }\n"
|
||||
" _positional.add(arg)\n"
|
||||
" }\n"
|
||||
" return this\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" destName_(arg) {\n"
|
||||
" if (arg[\"dest\"]) return arg[\"dest\"]\n"
|
||||
" var name = arg[\"long\"] ? arg[\"long\"] : arg[\"name\"]\n"
|
||||
" while (name.startsWith(\"-\")) name = name[1..-1]\n"
|
||||
" return name.replace(\"-\", \"_\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" parseArgs() { parseArgs(Process.arguments) }\n"
|
||||
"\n"
|
||||
" parseArgs(args) {\n"
|
||||
" var result = {}\n"
|
||||
"\n"
|
||||
" for (arg in _optional) {\n"
|
||||
" var dest = destName_(arg)\n"
|
||||
" if (arg[\"action\"] == \"storeTrue\") {\n"
|
||||
" result[dest] = false\n"
|
||||
" } else if (arg[\"action\"] == \"storeFalse\") {\n"
|
||||
" result[dest] = true\n"
|
||||
" } else if (arg[\"action\"] == \"count\") {\n"
|
||||
" result[dest] = 0\n"
|
||||
" } else if (arg[\"action\"] == \"append\") {\n"
|
||||
" result[dest] = []\n"
|
||||
" } else if (arg[\"default\"] != null) {\n"
|
||||
" result[dest] = arg[\"default\"]\n"
|
||||
" } else {\n"
|
||||
" result[dest] = null\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" for (arg in _positional) {\n"
|
||||
" if (arg[\"default\"] != null) {\n"
|
||||
" result[arg[\"name\"]] = arg[\"default\"]\n"
|
||||
" } else {\n"
|
||||
" result[arg[\"name\"]] = null\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var posIdx = 0\n"
|
||||
" var i = 0\n"
|
||||
" while (i < args.count) {\n"
|
||||
" var token = args[i]\n"
|
||||
"\n"
|
||||
" if (token.startsWith(\"-\")) {\n"
|
||||
" var matchedArg = null\n"
|
||||
" for (arg in _optional) {\n"
|
||||
" if (token == arg[\"name\"] || token == arg[\"long\"]) {\n"
|
||||
" matchedArg = arg\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (matchedArg == null) {\n"
|
||||
" Fiber.abort(\"Unknown option: %(token)\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var dest = destName_(matchedArg)\n"
|
||||
" if (matchedArg[\"action\"] == \"storeTrue\") {\n"
|
||||
" result[dest] = true\n"
|
||||
" i = i + 1\n"
|
||||
" } else if (matchedArg[\"action\"] == \"storeFalse\") {\n"
|
||||
" result[dest] = false\n"
|
||||
" i = i + 1\n"
|
||||
" } else if (matchedArg[\"action\"] == \"count\") {\n"
|
||||
" result[dest] = result[dest] + 1\n"
|
||||
" i = i + 1\n"
|
||||
" } else if (matchedArg[\"action\"] == \"append\") {\n"
|
||||
" if (i + 1 >= args.count) {\n"
|
||||
" Fiber.abort(\"Option %(token) requires a value\")\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" result[dest].add(convertValue_(args[i], matchedArg[\"type\"]))\n"
|
||||
" i = i + 1\n"
|
||||
" } else {\n"
|
||||
" if (matchedArg[\"nargs\"] == \"*\") {\n"
|
||||
" var values = []\n"
|
||||
" i = i + 1\n"
|
||||
" while (i < args.count && !args[i].startsWith(\"-\")) {\n"
|
||||
" values.add(convertValue_(args[i], matchedArg[\"type\"]))\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" result[dest] = values\n"
|
||||
" } else if (matchedArg[\"nargs\"] == \"+\") {\n"
|
||||
" var values = []\n"
|
||||
" i = i + 1\n"
|
||||
" while (i < args.count && !args[i].startsWith(\"-\")) {\n"
|
||||
" values.add(convertValue_(args[i], matchedArg[\"type\"]))\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" if (values.count == 0) {\n"
|
||||
" Fiber.abort(\"Option %(token) requires at least one value\")\n"
|
||||
" }\n"
|
||||
" result[dest] = values\n"
|
||||
" } else if (matchedArg[\"nargs\"] is Num) {\n"
|
||||
" var values = []\n"
|
||||
" var n = matchedArg[\"nargs\"]\n"
|
||||
" i = i + 1\n"
|
||||
" for (j in 0...n) {\n"
|
||||
" if (i >= args.count) {\n"
|
||||
" Fiber.abort(\"Option %(token) requires %(n) values\")\n"
|
||||
" }\n"
|
||||
" values.add(convertValue_(args[i], matchedArg[\"type\"]))\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" result[dest] = values\n"
|
||||
" } else {\n"
|
||||
" if (i + 1 >= args.count) {\n"
|
||||
" Fiber.abort(\"Option %(token) requires a value\")\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" var value = convertValue_(args[i], matchedArg[\"type\"])\n"
|
||||
" if (matchedArg[\"choices\"] && !matchedArg[\"choices\"].contains(value)) {\n"
|
||||
" Fiber.abort(\"Invalid choice '%(value)' for %(token)\")\n"
|
||||
" }\n"
|
||||
" result[dest] = value\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" if (posIdx < _positional.count) {\n"
|
||||
" var arg = _positional[posIdx]\n"
|
||||
" if (arg[\"nargs\"] == \"*\" || arg[\"nargs\"] == \"+\") {\n"
|
||||
" var values = []\n"
|
||||
" while (i < args.count && !args[i].startsWith(\"-\")) {\n"
|
||||
" values.add(convertValue_(args[i], arg[\"type\"]))\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" if (arg[\"nargs\"] == \"+\" && values.count == 0) {\n"
|
||||
" Fiber.abort(\"Argument %(arg[\"name\"]) requires at least one value\")\n"
|
||||
" }\n"
|
||||
" result[arg[\"name\"]] = values\n"
|
||||
" } else {\n"
|
||||
" var value = convertValue_(token, arg[\"type\"])\n"
|
||||
" if (arg[\"choices\"] && !arg[\"choices\"].contains(value)) {\n"
|
||||
" Fiber.abort(\"Invalid choice '%(value)' for %(arg[\"name\"])\")\n"
|
||||
" }\n"
|
||||
" result[arg[\"name\"]] = value\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" posIdx = posIdx + 1\n"
|
||||
" } else {\n"
|
||||
" Fiber.abort(\"Unexpected argument: %(token)\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" for (arg in _positional) {\n"
|
||||
" if (arg[\"required\"] && result[arg[\"name\"]] == null) {\n"
|
||||
" Fiber.abort(\"Missing required argument: %(arg[\"name\"])\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" for (arg in _optional) {\n"
|
||||
" var dest = destName_(arg)\n"
|
||||
" if (arg[\"required\"] && result[dest] == null) {\n"
|
||||
" Fiber.abort(\"Missing required option: %(arg[\"name\"])\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" convertValue_(value, type) {\n"
|
||||
" if (type == \"int\") {\n"
|
||||
" return Num.fromString(value)\n"
|
||||
" } else if (type == \"float\") {\n"
|
||||
" return Num.fromString(value)\n"
|
||||
" } else if (type == \"bool\") {\n"
|
||||
" if (value == \"true\" || value == \"1\" || value == \"yes\") return true\n"
|
||||
" if (value == \"false\" || value == \"0\" || value == \"no\") return false\n"
|
||||
" Fiber.abort(\"Invalid boolean value: %(value)\")\n"
|
||||
" }\n"
|
||||
" return value\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" printHelp() {\n"
|
||||
" var prog = _prog ? _prog : \"program\"\n"
|
||||
" System.print(\"usage: %(prog) [options]%(positionalUsage_())\")\n"
|
||||
" if (_description.count > 0) {\n"
|
||||
" System.print(\"\\n%(description)\")\n"
|
||||
" }\n"
|
||||
" if (_positional.count > 0) {\n"
|
||||
" System.print(\"\\npositional arguments:\")\n"
|
||||
" for (arg in _positional) {\n"
|
||||
" var helpText = arg[\"help\"].count > 0 ? arg[\"help\"] : \"\"\n"
|
||||
" System.print(\" %(pad_(arg[\"name\"], 20)) %(helpText)\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (_optional.count > 0) {\n"
|
||||
" System.print(\"\\noptional arguments:\")\n"
|
||||
" for (arg in _optional) {\n"
|
||||
" var names = arg[\"name\"]\n"
|
||||
" if (arg[\"long\"]) names = names + \", \" + arg[\"long\"]\n"
|
||||
" var helpText = arg[\"help\"].count > 0 ? arg[\"help\"] : \"\"\n"
|
||||
" System.print(\" %(pad_(names, 20)) %(helpText)\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" positionalUsage_() {\n"
|
||||
" if (_positional.count == 0) return \"\"\n"
|
||||
" var parts = []\n"
|
||||
" for (arg in _positional) {\n"
|
||||
" if (arg[\"nargs\"] == \"*\") {\n"
|
||||
" parts.add(\"[%(arg[\"name\"]) ...]\")\n"
|
||||
" } else if (arg[\"nargs\"] == \"+\") {\n"
|
||||
" parts.add(\"%(arg[\"name\"]) [...]\")\n"
|
||||
" } else if (arg[\"required\"]) {\n"
|
||||
" parts.add(arg[\"name\"])\n"
|
||||
" } else {\n"
|
||||
" parts.add(\"[%(arg[\"name\"])]\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return \" \" + parts.join(\" \")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" pad_(str, width) { Str.padRight(str, width, \" \") }\n"
|
||||
"}\n";
|
||||
180
src/module/bytes.c
Normal file
180
src/module/bytes.c
Normal file
@ -0,0 +1,180 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include "bytes.h"
|
||||
#include "wren.h"
|
||||
|
||||
void bytesToList(WrenVM* vm) {
|
||||
int length = 0;
|
||||
const char* data = wrenGetSlotBytes(vm, 1, &length);
|
||||
|
||||
if (data == NULL || length == 0) {
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
|
||||
const uint8_t* bytes = (const uint8_t*)data;
|
||||
for (int i = 0; i < length; i++) {
|
||||
wrenSetSlotDouble(vm, 2, (double)bytes[i]);
|
||||
wrenInsertInList(vm, 0, -1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void bytesFromList(WrenVM* vm) {
|
||||
if (wrenGetSlotType(vm, 1) != WREN_TYPE_LIST) {
|
||||
wrenSetSlotString(vm, 0, "Argument must be a list.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = wrenGetListCount(vm, 1);
|
||||
if (count == 0) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
char* buffer = (char*)malloc(count);
|
||||
if (buffer == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenEnsureSlots(vm, 3);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
wrenGetListElement(vm, 1, i, 2);
|
||||
if (wrenGetSlotType(vm, 2) != WREN_TYPE_NUM) {
|
||||
free(buffer);
|
||||
wrenSetSlotString(vm, 0, "List must contain only numbers.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
double value = wrenGetSlotDouble(vm, 2);
|
||||
int byteVal = (int)value;
|
||||
if (byteVal < 0 || byteVal > 255 || value != (double)byteVal) {
|
||||
free(buffer);
|
||||
wrenSetSlotString(vm, 0, "Byte values must be integers 0-255.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
buffer[i] = (char)(uint8_t)byteVal;
|
||||
}
|
||||
|
||||
wrenSetSlotBytes(vm, 0, buffer, count);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void bytesXorMask(WrenVM* vm) {
|
||||
int dataLen = 0;
|
||||
const char* data = wrenGetSlotBytes(vm, 1, &dataLen);
|
||||
int maskLen = 0;
|
||||
const char* mask = wrenGetSlotBytes(vm, 2, &maskLen);
|
||||
|
||||
if (mask == NULL || maskLen == 0) {
|
||||
wrenSetSlotString(vm, 0, "Mask cannot be empty.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data == NULL || dataLen == 0) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(dataLen);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t* src = (const uint8_t*)data;
|
||||
const uint8_t* maskBytes = (const uint8_t*)mask;
|
||||
uint8_t* dst = (uint8_t*)result;
|
||||
|
||||
if (maskLen == 4) {
|
||||
uint8_t m0 = maskBytes[0], m1 = maskBytes[1];
|
||||
uint8_t m2 = maskBytes[2], m3 = maskBytes[3];
|
||||
int i = 0;
|
||||
int fullBlocks = dataLen / 4;
|
||||
for (int block = 0; block < fullBlocks; block++) {
|
||||
dst[i] = src[i] ^ m0;
|
||||
dst[i+1] = src[i+1] ^ m1;
|
||||
dst[i+2] = src[i+2] ^ m2;
|
||||
dst[i+3] = src[i+3] ^ m3;
|
||||
i += 4;
|
||||
}
|
||||
int rem = dataLen % 4;
|
||||
if (rem >= 1) dst[i] = src[i] ^ m0;
|
||||
if (rem >= 2) dst[i+1] = src[i+1] ^ m1;
|
||||
if (rem >= 3) dst[i+2] = src[i+2] ^ m2;
|
||||
} else {
|
||||
for (int i = 0; i < dataLen; i++) {
|
||||
dst[i] = src[i] ^ maskBytes[i % maskLen];
|
||||
}
|
||||
}
|
||||
|
||||
wrenSetSlotBytes(vm, 0, result, dataLen);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void bytesConcat(WrenVM* vm) {
|
||||
int lenA = 0;
|
||||
const char* a = wrenGetSlotBytes(vm, 1, &lenA);
|
||||
int lenB = 0;
|
||||
const char* b = wrenGetSlotBytes(vm, 2, &lenB);
|
||||
|
||||
if (lenA == 0 && lenB == 0) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
if (lenA == 0) {
|
||||
wrenSetSlotBytes(vm, 0, b, lenB);
|
||||
return;
|
||||
}
|
||||
if (lenB == 0) {
|
||||
wrenSetSlotBytes(vm, 0, a, lenA);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t totalLen = (size_t)lenA + (size_t)lenB;
|
||||
char* result = (char*)malloc(totalLen);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "Memory allocation failed.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(result, a, lenA);
|
||||
memcpy(result + lenA, b, lenB);
|
||||
wrenSetSlotBytes(vm, 0, result, (int)totalLen);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void bytesSlice(WrenVM* vm) {
|
||||
int length = 0;
|
||||
const char* data = wrenGetSlotBytes(vm, 1, &length);
|
||||
int start = (int)wrenGetSlotDouble(vm, 2);
|
||||
int end = (int)wrenGetSlotDouble(vm, 3);
|
||||
|
||||
if (start < 0) start = 0;
|
||||
if (end > length) end = length;
|
||||
if (start >= end || start >= length) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
wrenSetSlotBytes(vm, 0, data + start, end - start);
|
||||
}
|
||||
|
||||
void bytesLength(WrenVM* vm) {
|
||||
int length = 0;
|
||||
wrenGetSlotBytes(vm, 1, &length);
|
||||
wrenSetSlotDouble(vm, 0, (double)length);
|
||||
}
|
||||
15
src/module/bytes.h
Normal file
15
src/module/bytes.h
Normal file
@ -0,0 +1,15 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef bytes_h
|
||||
#define bytes_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void bytesToList(WrenVM* vm);
|
||||
void bytesFromList(WrenVM* vm);
|
||||
void bytesXorMask(WrenVM* vm);
|
||||
void bytesConcat(WrenVM* vm);
|
||||
void bytesSlice(WrenVM* vm);
|
||||
void bytesLength(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
10
src/module/bytes.wren
vendored
Normal file
10
src/module/bytes.wren
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class Bytes {
|
||||
foreign static toList(data)
|
||||
foreign static fromList(list)
|
||||
foreign static xorMask(data, mask)
|
||||
foreign static concat(a, b)
|
||||
foreign static slice(data, start, end)
|
||||
foreign static length(data)
|
||||
}
|
||||
14
src/module/bytes.wren.inc
Normal file
14
src/module/bytes.wren.inc
Normal file
@ -0,0 +1,14 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/bytes.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* bytesModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"class Bytes {\n"
|
||||
" foreign static toList(data)\n"
|
||||
" foreign static fromList(list)\n"
|
||||
" foreign static xorMask(data, mask)\n"
|
||||
" foreign static concat(a, b)\n"
|
||||
" foreign static slice(data, start, end)\n"
|
||||
" foreign static length(data)\n"
|
||||
"}\n";
|
||||
390
src/module/create_training_data.wren.inc
Normal file
390
src/module/create_training_data.wren.inc
Normal file
@ -0,0 +1,390 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `create_training_data.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* create_training_dataModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"argparse\" for ArgumentParser\n"
|
||||
"import \"crypto\" for Hash\n"
|
||||
"import \"json\" for Json\n"
|
||||
"import \"regex\" for Regex\n"
|
||||
"import \"io\" for File, Directory\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"var SYSTEM_PROMPT = \"You are an expert assistant for the Wren-CLI language and its extended modules. You answer with correct Wren syntax and semantics, and you understand the Wren-CLI standard library plus the custom modules documented in the provided manual/tests. When asked for expected output, provide exactly the printed output lines in order. When asked about APIs, provide concise explanations and minimal working examples.\"\n"
|
||||
"\n"
|
||||
"class Sample {\n"
|
||||
" construct new(user, assistant) {\n"
|
||||
" _user = user\n"
|
||||
" _assistant = assistant\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" user { _user }\n"
|
||||
" assistant { _assistant }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class PseudoRandom {\n"
|
||||
" construct new(seed) {\n"
|
||||
" _state = seed\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" next() {\n"
|
||||
" _state = (_state * 1103515245 + 12345) & 0x7fffffff\n"
|
||||
" return _state\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" shuffle(list) {\n"
|
||||
" if (list.count < 2) return\n"
|
||||
" var i = list.count - 1\n"
|
||||
" while (i > 0) {\n"
|
||||
" var j = next() % (i + 1)\n"
|
||||
" var temp = list[i]\n"
|
||||
" list[i] = list[j]\n"
|
||||
" list[j] = temp\n"
|
||||
" i = i - 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class TrainingDataGenerator {\n"
|
||||
" static sha1Text(s) {\n"
|
||||
" return Hash.toHex(Hash.sha1(s))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static normalizeNewlines(s) {\n"
|
||||
" var result = s.replace(\"\\r\\n\", \"\\n\")\n"
|
||||
" result = result.replace(\"\\r\", \"\\n\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static safeTrim(s, maxChars) {\n"
|
||||
" if (maxChars <= 0) return s\n"
|
||||
" if (s.count <= maxChars) return s\n"
|
||||
" return s[0...(maxChars - 1)] + \"…\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static writeJsonl(samples, outPath, systemPrompt) {\n"
|
||||
" var path = outPath is String ? outPath : outPath.toString\n"
|
||||
" var dirPath = path[0...(path.lastIndexOf(\"/\") + 1)]\n"
|
||||
" if (dirPath.count > 0 && !Directory.exists(dirPath)) {\n"
|
||||
" Directory.create(dirPath)\n"
|
||||
" }\n"
|
||||
" File.create(path) {|f|\n"
|
||||
" for (smp in samples) {\n"
|
||||
" var obj = {\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"system\", \"content\": systemPrompt},\n"
|
||||
" {\"role\": \"user\", \"content\": smp.user},\n"
|
||||
" {\"role\": \"assistant\", \"content\": smp.assistant}\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
" f.writeBytes(Json.stringify(obj) + \"\\n\", 0)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static validateJsonl(path) {\n"
|
||||
" var pathStr = path is String ? path : path.toString\n"
|
||||
" var content = File.read(pathStr)\n"
|
||||
" var lines = content.split(\"\\n\")\n"
|
||||
" for (i in 0...lines.count) {\n"
|
||||
" var line = lines[i].trim()\n"
|
||||
" if (line.isEmpty) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): empty line\")\n"
|
||||
" }\n"
|
||||
" var obj = null\n"
|
||||
" Fiber.new {\n"
|
||||
" obj = Json.parse(line)\n"
|
||||
" }.try()\n"
|
||||
" if (obj == null) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): invalid JSON\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (!obj.containsKey(\"messages\") || !(obj[\"messages\"] is List)) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): missing/invalid 'messages' array\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var messages = obj[\"messages\"]\n"
|
||||
" for (j in 0...messages.count) {\n"
|
||||
" var msg = messages[j]\n"
|
||||
" if (!(msg is Map)) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): messages[%(j)] not an object\")\n"
|
||||
" }\n"
|
||||
" var role = msg[\"role\"]\n"
|
||||
" if (role != \"system\" && role != \"user\" && role != \"assistant\") {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): messages[%(j)].role invalid\")\n"
|
||||
" }\n"
|
||||
" if (!(msg[\"content\"] is String)) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): messages[%(j)].content must be a string\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static splitTestBlocks(lines) {\n"
|
||||
" var blocks = []\n"
|
||||
" var cur = []\n"
|
||||
" var expectRe = Regex.new(\"\\\\bexpect\\\\b\\\\s*(.*)\\\\s*$\")\n"
|
||||
" var headerNoiseRe = Regex.new(\"^\\\\s*(retoor\\\\s+retoormolodetz\\\\.nl|TITLE\\\\s+Subtitle\\\\.\\\\.\\\\.)\\\\s*$\")\n"
|
||||
"\n"
|
||||
" var flush = Fn.new {\n"
|
||||
" if (!cur.isEmpty) {\n"
|
||||
" var hasExpect = false\n"
|
||||
" for (ln in cur) {\n"
|
||||
" if (ln.contains(\"expect\")) {\n"
|
||||
" hasExpect = true\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (hasExpect) blocks.add(cur)\n"
|
||||
" }\n"
|
||||
" cur = []\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" for (raw in lines) {\n"
|
||||
" var ln = raw.replace(\"\\n\", \"\").replace(\"\\r\", \"\")\n"
|
||||
"\n"
|
||||
" var m = headerNoiseRe.match(ln.trim())\n"
|
||||
" if (m != null) {\n"
|
||||
" flush.call()\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (ln.trim().isEmpty) {\n"
|
||||
" flush.call()\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var hasExpect = false\n"
|
||||
" for (x in cur) {\n"
|
||||
" if (x.contains(\"expect\")) {\n"
|
||||
" hasExpect = true\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (hasExpect && ln.lstrip().startsWith(\"import \")) {\n"
|
||||
" flush.call()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" cur.add(ln)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" flush.call()\n"
|
||||
" return blocks\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parseWrenTestsMarkdown(path, maxCodeChars, maxAnswerChars) {\n"
|
||||
" var content = File.read(path)\n"
|
||||
" var text = normalizeNewlines(content)\n"
|
||||
" var lines = text.split(\"\\n\")\n"
|
||||
" var blocks = splitTestBlocks(lines)\n"
|
||||
" var expectRe = Regex.new(\"\\\\bexpect\\\\b\\\\s*(.*)\\\\s*$\")\n"
|
||||
"\n"
|
||||
" var samples = []\n"
|
||||
" for (block in blocks) {\n"
|
||||
" var expected = []\n"
|
||||
" for (ln in block) {\n"
|
||||
" var m = expectRe.match(ln)\n"
|
||||
" if (m != null) {\n"
|
||||
" expected.add(m.group(1).trim())\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (expected.isEmpty) continue\n"
|
||||
"\n"
|
||||
" var code = block.join(\"\\n\").trim()\n"
|
||||
" code = safeTrim(code, maxCodeChars)\n"
|
||||
"\n"
|
||||
" var assistantParts = []\n"
|
||||
" for (exp in expected) {\n"
|
||||
" assistantParts.add(safeTrim(exp, maxAnswerChars))\n"
|
||||
" }\n"
|
||||
" var assistant = assistantParts.join(\"\\n\").trim()\n"
|
||||
"\n"
|
||||
" var user = \"Given this Wren-CLI program, what output should it produce?\\nReturn one output line per expectation, in order.\\n\\n```wren\\n%(code)\\n```\"\n"
|
||||
"\n"
|
||||
" samples.add(Sample.new(user, assistant))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return samples\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parseManualHtml(path, maxExampleChars, maxAnswerChars) {\n"
|
||||
" var content = File.read(path)\n"
|
||||
" var samples = []\n"
|
||||
"\n"
|
||||
" var articleRe = Regex.new(\"<article>([\\\\s\\\\S]*?)</article>\", \"s\")\n"
|
||||
" var articles = articleRe.matchAll(content)\n"
|
||||
"\n"
|
||||
" for (articleMatch in articles) {\n"
|
||||
" var articleContent = articleMatch.group(1)\n"
|
||||
"\n"
|
||||
" var h1Re = Regex.new(\"<h1[^>]*>([\\\\s\\\\S]*?)</h1>\", \"s\")\n"
|
||||
" var h1Match = h1Re.match(articleContent)\n"
|
||||
" if (h1Match == null) continue\n"
|
||||
"\n"
|
||||
" var h1Content = h1Match.group(1)\n"
|
||||
" var moduleName = stripHtmlTags(h1Content)\n"
|
||||
" if (moduleName.isEmpty) continue\n"
|
||||
"\n"
|
||||
" var pRe = Regex.new(\"<p[^>]*>([\\\\s\\\\S]*?)</p>\", \"s\")\n"
|
||||
" var firstPMatch = pRe.match(articleContent)\n"
|
||||
" var moduleDesc = \"\"\n"
|
||||
" if (firstPMatch != null) {\n"
|
||||
" moduleDesc = stripHtmlTags(firstPMatch.group(1))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var sigRe = Regex.new('<div class=\"method-signature\"[^>]*>([\\\\s\\\\S]*?)</div>', \"s\")\n"
|
||||
" var sigs = sigRe.matchAll(articleContent)\n"
|
||||
"\n"
|
||||
" for (sigMatch in sigs) {\n"
|
||||
" var sigContent = sigMatch.group(1)\n"
|
||||
"\n"
|
||||
" var nameRe = Regex.new('<span class=\"method-name\"[^>]*>([\\\\s\\\\S]*?)</span>', \"s\")\n"
|
||||
" var nameMatch = nameRe.match(sigContent)\n"
|
||||
" if (nameMatch == null) continue\n"
|
||||
"\n"
|
||||
" var methodName = stripHtmlTags(nameMatch.group(1))\n"
|
||||
" if (methodName.isEmpty) continue\n"
|
||||
"\n"
|
||||
" var methodDesc = \"\"\n"
|
||||
" var descPRe = Regex.new('<p[^>]*>([\\\\s\\\\S]*?)</p>', \"s\")\n"
|
||||
" var afterSig = articleContent[sigMatch.end..-1]\n"
|
||||
" var descMatch = descPRe.match(afterSig)\n"
|
||||
" if (descMatch != null) {\n"
|
||||
" methodDesc = stripHtmlTags(descMatch.group(1))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var preRe = Regex.new('<pre[^>]*><code[^>]*>([\\\\s\\\\S]*?)</code></pre>', \"s\")\n"
|
||||
" var preMatch = preRe.match(afterSig)\n"
|
||||
" var example = \"\"\n"
|
||||
" if (preMatch != null) {\n"
|
||||
" example = stripHtmlEntities(preMatch.group(1))\n"
|
||||
" example = safeTrim(example, maxExampleChars)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var assistantParts = []\n"
|
||||
" if (!moduleDesc.isEmpty) assistantParts.add(moduleDesc)\n"
|
||||
" if (!methodDesc.isEmpty) assistantParts.add(methodDesc)\n"
|
||||
" if (!example.isEmpty) assistantParts.add(\"Example:\\n```wren\\n%(example)\\n```\")\n"
|
||||
"\n"
|
||||
" var assistant = assistantParts.join(\"\\n\\n\").trim()\n"
|
||||
" assistant = safeTrim(assistant, maxAnswerChars)\n"
|
||||
"\n"
|
||||
" var user = \"In the Wren-CLI `%(moduleName)` module, how do I use `%(methodName)`?\\nGive a short explanation and a minimal example.\"\n"
|
||||
"\n"
|
||||
" if (assistant.count < 20) continue\n"
|
||||
"\n"
|
||||
" samples.add(Sample.new(user, assistant))\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return samples\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stripHtmlTags(html) {\n"
|
||||
" var result = html\n"
|
||||
" var tagRe = Regex.new(\"<[^>]+>\")\n"
|
||||
" while (true) {\n"
|
||||
" var before = result\n"
|
||||
" result = tagRe.replaceAll(result, \"\")\n"
|
||||
" if (result == before) break\n"
|
||||
" }\n"
|
||||
" result = result.replace(\" \", \" \")\n"
|
||||
" result = result.replace(\"<\", \"<\")\n"
|
||||
" result = result.replace(\">\", \">\")\n"
|
||||
" result = result.replace(\"&\", \"&\")\n"
|
||||
" result = result.replace(\""\", \"\\\"\")\n"
|
||||
" result = result.replace(\"'\", \"'\")\n"
|
||||
" return result.trim()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stripHtmlEntities(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = result.replace(\"<\", \"<\")\n"
|
||||
" result = result.replace(\">\", \">\")\n"
|
||||
" result = result.replace(\"&\", \"&\")\n"
|
||||
" result = result.replace(\""\", \"\\\"\")\n"
|
||||
" result = result.replace(\"'\", \"'\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static dedupe(samples) {\n"
|
||||
" var seen = {}\n"
|
||||
" var out = []\n"
|
||||
" for (s in samples) {\n"
|
||||
" var key = sha1Text(s.user + \"\\n---\\n\" + s.assistant)\n"
|
||||
" if (!seen.containsKey(key)) {\n"
|
||||
" seen[key] = true\n"
|
||||
" out.add(s)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return out\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static main(args) {\n"
|
||||
" var parser = ArgumentParser.new(\"Create training data from Wren documentation and tests\")\n"
|
||||
" parser.addArgument(\"--tests-md\", {\"action\": \"append\", \"type\": \"string\", \"help\": \"Path(s) to wren_tests.md-like files\"})\n"
|
||||
" parser.addArgument(\"--manual-html\", {\"action\": \"append\", \"type\": \"string\", \"help\": \"Path(s) to manual.html-like files\"})\n"
|
||||
" parser.addArgument(\"--out-train\", {\"type\": \"string\", \"default\": \"train.jsonl\", \"help\": \"Output training file\"})\n"
|
||||
" parser.addArgument(\"--out-val\", {\"type\": \"string\", \"help\": \"Output validation file\"})\n"
|
||||
" parser.addArgument(\"--val-ratio\", {\"type\": \"float\", \"default\": 0.02, \"help\": \"Validation ratio\"})\n"
|
||||
" parser.addArgument(\"--seed\", {\"type\": \"int\", \"default\": 42, \"help\": \"Random seed\"})\n"
|
||||
" parser.addArgument(\"--max-code-chars\", {\"type\": \"int\", \"default\": 12000, \"help\": \"Max code characters\"})\n"
|
||||
" parser.addArgument(\"--max-example-chars\", {\"type\": \"int\", \"default\": 2000, \"help\": \"Max example characters\"})\n"
|
||||
" parser.addArgument(\"--max-answer-chars\", {\"type\": \"int\", \"default\": 6000, \"help\": \"Max answer characters\"})\n"
|
||||
" parser.addArgument(\"--system-prompt\", {\"type\": \"string\", \"default\": SYSTEM_PROMPT, \"help\": \"System prompt\"})\n"
|
||||
" parser.addArgument(\"--validate\", {\"action\": \"storeTrue\", \"help\": \"Validate output JSONL\"})\n"
|
||||
"\n"
|
||||
" var parsed = parser.parseArgs(args)\n"
|
||||
"\n"
|
||||
" var allSamples = []\n"
|
||||
"\n"
|
||||
" if (parsed.containsKey(\"tests_md\") && parsed[\"tests_md\"] != null) {\n"
|
||||
" var testsMdPaths = parsed[\"tests_md\"]\n"
|
||||
" for (p in testsMdPaths) {\n"
|
||||
" allSamples.addAll(parseWrenTestsMarkdown(p, parsed[\"max_code_chars\"], parsed[\"max_answer_chars\"]))\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (parsed.containsKey(\"manual_html\") && parsed[\"manual_html\"] != null) {\n"
|
||||
" var manualHtmlPaths = parsed[\"manual_html\"]\n"
|
||||
" for (p in manualHtmlPaths) {\n"
|
||||
" allSamples.addAll(parseManualHtml(p, parsed[\"max_example_chars\"], parsed[\"max_answer_chars\"]))\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" allSamples = dedupe(allSamples)\n"
|
||||
"\n"
|
||||
" var rng = PseudoRandom.new(parsed[\"seed\"])\n"
|
||||
" rng.shuffle(allSamples)\n"
|
||||
"\n"
|
||||
" if (parsed.containsKey(\"out_val\") && parsed[\"out_val\"] != null && parsed[\"val_ratio\"] > 0 && parsed[\"val_ratio\"] < 1) {\n"
|
||||
" var nVal = (allSamples.count * parsed[\"val_ratio\"]).floor.max(1)\n"
|
||||
" var val = allSamples[0...nVal]\n"
|
||||
" var train = allSamples[nVal..-1]\n"
|
||||
"\n"
|
||||
" writeJsonl(train, parsed[\"out_train\"], parsed[\"system_prompt\"])\n"
|
||||
" writeJsonl(val, parsed[\"out_val\"], parsed[\"system_prompt\"])\n"
|
||||
"\n"
|
||||
" if (parsed[\"validate\"]) {\n"
|
||||
" validateJsonl(parsed[\"out_train\"])\n"
|
||||
" validateJsonl(parsed[\"out_val\"])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" System.print(\"Wrote train=%(train.count) to %(parsed[\"out_train\"])\")\n"
|
||||
" System.print(\"Wrote val=%(val.count) to %(parsed[\"out_val\"])\")\n"
|
||||
" } else {\n"
|
||||
" writeJsonl(allSamples, parsed[\"out_train\"], parsed[\"system_prompt\"])\n"
|
||||
"\n"
|
||||
" if (parsed[\"validate\"]) {\n"
|
||||
" validateJsonl(parsed[\"out_train\"])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" System.print(\"Wrote train=%(allSamples.count) to %(parsed[\"out_train\"])\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"import \"os\" for Process\n"
|
||||
"TrainingDataGenerator.main(Process.arguments)\n";
|
||||
17
src/module/crypto.wren
vendored
17
src/module/crypto.wren
vendored
@ -1,6 +1,8 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler
|
||||
import "strutil" for Str
|
||||
import "bytes" for Bytes
|
||||
|
||||
class Crypto {
|
||||
foreign static randomBytes_(length, fiber)
|
||||
@ -46,18 +48,5 @@ class Hash {
|
||||
return sha256_(data)
|
||||
}
|
||||
|
||||
static toHex(bytes) {
|
||||
var hex = ""
|
||||
for (b in bytes) {
|
||||
var hi = (b >> 4) & 0x0F
|
||||
var lo = b & 0x0F
|
||||
hex = hex + hexDigit_(hi) + hexDigit_(lo)
|
||||
}
|
||||
return hex
|
||||
}
|
||||
|
||||
static hexDigit_(n) {
|
||||
if (n < 10) return String.fromCodePoint(48 + n)
|
||||
return String.fromCodePoint(97 + n - 10)
|
||||
}
|
||||
static toHex(bytes) { Str.hexEncode(Bytes.fromList(bytes)) }
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ static const char* cryptoModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"import \"bytes\" for Bytes\n"
|
||||
"\n"
|
||||
"class Crypto {\n"
|
||||
" foreign static randomBytes_(length, fiber)\n"
|
||||
@ -50,18 +52,5 @@ static const char* cryptoModuleSource =
|
||||
" return sha256_(data)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toHex(bytes) {\n"
|
||||
" var hex = \"\"\n"
|
||||
" for (b in bytes) {\n"
|
||||
" var hi = (b >> 4) & 0x0F\n"
|
||||
" var lo = b & 0x0F\n"
|
||||
" hex = hex + hexDigit_(hi) + hexDigit_(lo)\n"
|
||||
" }\n"
|
||||
" return hex\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static hexDigit_(n) {\n"
|
||||
" if (n < 10) return String.fromCodePoint(48 + n)\n"
|
||||
" return String.fromCodePoint(97 + n - 10)\n"
|
||||
" }\n"
|
||||
" static toHex(bytes) { Str.hexEncode(Bytes.fromList(bytes)) }\n"
|
||||
"}\n";
|
||||
|
||||
263
src/module/dataset.wren
vendored
Normal file
263
src/module/dataset.wren
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "sqlite" for Database
|
||||
import "uuid" for Uuid
|
||||
import "datetime" for DateTime
|
||||
import "json" for Json
|
||||
|
||||
class Dataset {
|
||||
construct open(path) {
|
||||
_db = Database.open(path)
|
||||
_tables = {}
|
||||
}
|
||||
|
||||
construct memory() {
|
||||
_db = Database.memory()
|
||||
_tables = {}
|
||||
}
|
||||
|
||||
db { _db }
|
||||
|
||||
[tableName] {
|
||||
if (!_tables.containsKey(tableName)) {
|
||||
_tables[tableName] = Table.new_(this, tableName)
|
||||
}
|
||||
return _tables[tableName]
|
||||
}
|
||||
|
||||
tables {
|
||||
var rows = _db.query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
||||
var result = []
|
||||
for (row in rows) {
|
||||
result.add(row["name"])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
close() { _db.close() }
|
||||
}
|
||||
|
||||
class Table {
|
||||
construct new_(dataset, name) {
|
||||
_dataset = dataset
|
||||
_name = name
|
||||
_columns = null
|
||||
}
|
||||
|
||||
name { _name }
|
||||
db { _dataset.db }
|
||||
|
||||
columns {
|
||||
if (_columns == null) {
|
||||
_columns = {}
|
||||
var rows = db.query("PRAGMA table_info(" + _name + ")")
|
||||
for (row in rows) {
|
||||
_columns[row["name"]] = row["type"]
|
||||
}
|
||||
}
|
||||
return _columns
|
||||
}
|
||||
|
||||
ensureTable_() {
|
||||
var exists = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?", [_name])
|
||||
if (exists.count == 0) {
|
||||
db.execute("CREATE TABLE " + _name + " (uid TEXT PRIMARY KEY, created_at TEXT, deleted_at TEXT)")
|
||||
_columns = null
|
||||
}
|
||||
}
|
||||
|
||||
ensureColumn_(colName, value) {
|
||||
ensureTable_()
|
||||
var cols = columns
|
||||
if (!cols.containsKey(colName)) {
|
||||
var sqlType = getSqlType_(value)
|
||||
db.execute("ALTER TABLE " + _name + " ADD COLUMN " + colName + " " + sqlType)
|
||||
_columns = null
|
||||
}
|
||||
}
|
||||
|
||||
getSqlType_(value) {
|
||||
if (value is Num) {
|
||||
if (value == value.floor) return "INTEGER"
|
||||
return "REAL"
|
||||
}
|
||||
if (value is Bool) return "INTEGER"
|
||||
if (value is Map || value is List) return "TEXT"
|
||||
return "TEXT"
|
||||
}
|
||||
|
||||
serializeValue_(value) {
|
||||
if (value is Bool) return value ? 1 : 0
|
||||
if (value is Map || value is List) return Json.stringify(value)
|
||||
return value
|
||||
}
|
||||
|
||||
deserializeRow_(row) {
|
||||
var result = {}
|
||||
for (key in row.keys) {
|
||||
var value = row[key]
|
||||
if (value is String && (value.startsWith("{") || value.startsWith("["))) {
|
||||
var fiber = Fiber.new { Json.parse(value) }
|
||||
var parsed = fiber.try()
|
||||
if (!fiber.error) {
|
||||
result[key] = parsed
|
||||
continue
|
||||
}
|
||||
}
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
insert(record) {
|
||||
ensureTable_()
|
||||
var uid = record.containsKey("uid") ? record["uid"] : Uuid.v4()
|
||||
var createdAt = record.containsKey("created_at") ? record["created_at"] : DateTime.now().toString
|
||||
|
||||
var colNames = ["uid", "created_at"]
|
||||
var placeholders = ["?", "?"]
|
||||
var values = [uid, createdAt]
|
||||
|
||||
for (key in record.keys) {
|
||||
if (key == "uid" || key == "created_at" || key == "deleted_at") continue
|
||||
ensureColumn_(key, record[key])
|
||||
colNames.add(key)
|
||||
placeholders.add("?")
|
||||
values.add(serializeValue_(record[key]))
|
||||
}
|
||||
|
||||
var sql = "INSERT INTO " + _name + " (" + colNames.join(", ") + ") VALUES (" + placeholders.join(", ") + ")"
|
||||
db.execute(sql, values)
|
||||
|
||||
var result = {}
|
||||
for (key in record.keys) {
|
||||
result[key] = record[key]
|
||||
}
|
||||
result["uid"] = uid
|
||||
result["created_at"] = createdAt
|
||||
return result
|
||||
}
|
||||
|
||||
update(record) {
|
||||
if (!record.containsKey("uid")) {
|
||||
Fiber.abort("Record must have a uid for update")
|
||||
}
|
||||
var uid = record["uid"]
|
||||
|
||||
var setParts = []
|
||||
var values = []
|
||||
for (key in record.keys) {
|
||||
if (key == "uid") continue
|
||||
ensureColumn_(key, record[key])
|
||||
setParts.add(key + " = ?")
|
||||
values.add(serializeValue_(record[key]))
|
||||
}
|
||||
values.add(uid)
|
||||
|
||||
var sql = "UPDATE " + _name + " SET " + setParts.join(", ") + " WHERE uid = ? AND deleted_at IS NULL"
|
||||
db.execute(sql, values)
|
||||
return db.changes
|
||||
}
|
||||
|
||||
delete(uid) {
|
||||
var sql = "UPDATE " + _name + " SET deleted_at = ? WHERE uid = ? AND deleted_at IS NULL"
|
||||
db.execute(sql, [DateTime.now().toString, uid])
|
||||
return db.changes > 0
|
||||
}
|
||||
|
||||
hardDelete(uid) {
|
||||
var sql = "DELETE FROM " + _name + " WHERE uid = ?"
|
||||
db.execute(sql, [uid])
|
||||
return db.changes > 0
|
||||
}
|
||||
|
||||
find(conditions) {
|
||||
ensureTable_()
|
||||
var where = buildWhere_(conditions)
|
||||
var sql = "SELECT * FROM " + _name + " WHERE deleted_at IS NULL" + where["clause"]
|
||||
var rows = db.query(sql, where["values"])
|
||||
var result = []
|
||||
for (row in rows) {
|
||||
result.add(deserializeRow_(row))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
findOne(conditions) {
|
||||
var results = find(conditions)
|
||||
return results.count > 0 ? results[0] : null
|
||||
}
|
||||
|
||||
all() {
|
||||
ensureTable_()
|
||||
var rows = db.query("SELECT * FROM " + _name + " WHERE deleted_at IS NULL")
|
||||
var result = []
|
||||
for (row in rows) {
|
||||
result.add(deserializeRow_(row))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
count() {
|
||||
ensureTable_()
|
||||
var rows = db.query("SELECT COUNT(*) as cnt FROM " + _name + " WHERE deleted_at IS NULL")
|
||||
return rows[0]["cnt"]
|
||||
}
|
||||
|
||||
buildWhere_(conditions) {
|
||||
var parts = []
|
||||
var values = []
|
||||
|
||||
for (key in conditions.keys) {
|
||||
var value = conditions[key]
|
||||
var op = "="
|
||||
var col = key
|
||||
|
||||
if (key.contains("__")) {
|
||||
var split = key.split("__")
|
||||
col = split[0]
|
||||
var suffix = split[1]
|
||||
if (suffix == "gt") {
|
||||
op = ">"
|
||||
} else if (suffix == "lt") {
|
||||
op = "<"
|
||||
} else if (suffix == "gte") {
|
||||
op = ">="
|
||||
} else if (suffix == "lte") {
|
||||
op = "<="
|
||||
} else if (suffix == "ne") {
|
||||
op = "!="
|
||||
} else if (suffix == "like") {
|
||||
op = "LIKE"
|
||||
} else if (suffix == "in") {
|
||||
if (value is List) {
|
||||
var placeholders = []
|
||||
for (v in value) {
|
||||
placeholders.add("?")
|
||||
values.add(v)
|
||||
}
|
||||
parts.add(col + " IN (" + placeholders.join(", ") + ")")
|
||||
continue
|
||||
}
|
||||
} else if (suffix == "null") {
|
||||
if (value) {
|
||||
parts.add(col + " IS NULL")
|
||||
} else {
|
||||
parts.add(col + " IS NOT NULL")
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
parts.add(col + " " + op + " ?")
|
||||
values.add(serializeValue_(value))
|
||||
}
|
||||
|
||||
var clause = ""
|
||||
if (parts.count > 0) {
|
||||
clause = " AND " + parts.join(" AND ")
|
||||
}
|
||||
|
||||
return {"clause": clause, "values": values}
|
||||
}
|
||||
}
|
||||
267
src/module/dataset.wren.inc
Normal file
267
src/module/dataset.wren.inc
Normal file
@ -0,0 +1,267 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/dataset.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* datasetModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"sqlite\" for Database\n"
|
||||
"import \"uuid\" for Uuid\n"
|
||||
"import \"datetime\" for DateTime\n"
|
||||
"import \"json\" for Json\n"
|
||||
"\n"
|
||||
"class Dataset {\n"
|
||||
" construct open(path) {\n"
|
||||
" _db = Database.open(path)\n"
|
||||
" _tables = {}\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" construct memory() {\n"
|
||||
" _db = Database.memory()\n"
|
||||
" _tables = {}\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" db { _db }\n"
|
||||
"\n"
|
||||
" [tableName] {\n"
|
||||
" if (!_tables.containsKey(tableName)) {\n"
|
||||
" _tables[tableName] = Table.new_(this, tableName)\n"
|
||||
" }\n"
|
||||
" return _tables[tableName]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" tables {\n"
|
||||
" var rows = _db.query(\"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name\")\n"
|
||||
" var result = []\n"
|
||||
" for (row in rows) {\n"
|
||||
" result.add(row[\"name\"])\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" close() { _db.close() }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Table {\n"
|
||||
" construct new_(dataset, name) {\n"
|
||||
" _dataset = dataset\n"
|
||||
" _name = name\n"
|
||||
" _columns = null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" name { _name }\n"
|
||||
" db { _dataset.db }\n"
|
||||
"\n"
|
||||
" columns {\n"
|
||||
" if (_columns == null) {\n"
|
||||
" _columns = {}\n"
|
||||
" var rows = db.query(\"PRAGMA table_info(\" + _name + \")\")\n"
|
||||
" for (row in rows) {\n"
|
||||
" _columns[row[\"name\"]] = row[\"type\"]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return _columns\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" ensureTable_() {\n"
|
||||
" var exists = db.query(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\", [_name])\n"
|
||||
" if (exists.count == 0) {\n"
|
||||
" db.execute(\"CREATE TABLE \" + _name + \" (uid TEXT PRIMARY KEY, created_at TEXT, deleted_at TEXT)\")\n"
|
||||
" _columns = null\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" ensureColumn_(colName, value) {\n"
|
||||
" ensureTable_()\n"
|
||||
" var cols = columns\n"
|
||||
" if (!cols.containsKey(colName)) {\n"
|
||||
" var sqlType = getSqlType_(value)\n"
|
||||
" db.execute(\"ALTER TABLE \" + _name + \" ADD COLUMN \" + colName + \" \" + sqlType)\n"
|
||||
" _columns = null\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" getSqlType_(value) {\n"
|
||||
" if (value is Num) {\n"
|
||||
" if (value == value.floor) return \"INTEGER\"\n"
|
||||
" return \"REAL\"\n"
|
||||
" }\n"
|
||||
" if (value is Bool) return \"INTEGER\"\n"
|
||||
" if (value is Map || value is List) return \"TEXT\"\n"
|
||||
" return \"TEXT\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" serializeValue_(value) {\n"
|
||||
" if (value is Bool) return value ? 1 : 0\n"
|
||||
" if (value is Map || value is List) return Json.stringify(value)\n"
|
||||
" return value\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" deserializeRow_(row) {\n"
|
||||
" var result = {}\n"
|
||||
" for (key in row.keys) {\n"
|
||||
" var value = row[key]\n"
|
||||
" if (value is String && (value.startsWith(\"{\") || value.startsWith(\"[\"))) {\n"
|
||||
" var fiber = Fiber.new { Json.parse(value) }\n"
|
||||
" var parsed = fiber.try()\n"
|
||||
" if (!fiber.error) {\n"
|
||||
" result[key] = parsed\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" result[key] = value\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" insert(record) {\n"
|
||||
" ensureTable_()\n"
|
||||
" var uid = record.containsKey(\"uid\") ? record[\"uid\"] : Uuid.v4()\n"
|
||||
" var createdAt = record.containsKey(\"created_at\") ? record[\"created_at\"] : DateTime.now().toString\n"
|
||||
"\n"
|
||||
" var colNames = [\"uid\", \"created_at\"]\n"
|
||||
" var placeholders = [\"?\", \"?\"]\n"
|
||||
" var values = [uid, createdAt]\n"
|
||||
"\n"
|
||||
" for (key in record.keys) {\n"
|
||||
" if (key == \"uid\" || key == \"created_at\" || key == \"deleted_at\") continue\n"
|
||||
" ensureColumn_(key, record[key])\n"
|
||||
" colNames.add(key)\n"
|
||||
" placeholders.add(\"?\")\n"
|
||||
" values.add(serializeValue_(record[key]))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var sql = \"INSERT INTO \" + _name + \" (\" + colNames.join(\", \") + \") VALUES (\" + placeholders.join(\", \") + \")\"\n"
|
||||
" db.execute(sql, values)\n"
|
||||
"\n"
|
||||
" var result = {}\n"
|
||||
" for (key in record.keys) {\n"
|
||||
" result[key] = record[key]\n"
|
||||
" }\n"
|
||||
" result[\"uid\"] = uid\n"
|
||||
" result[\"created_at\"] = createdAt\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" update(record) {\n"
|
||||
" if (!record.containsKey(\"uid\")) {\n"
|
||||
" Fiber.abort(\"Record must have a uid for update\")\n"
|
||||
" }\n"
|
||||
" var uid = record[\"uid\"]\n"
|
||||
"\n"
|
||||
" var setParts = []\n"
|
||||
" var values = []\n"
|
||||
" for (key in record.keys) {\n"
|
||||
" if (key == \"uid\") continue\n"
|
||||
" ensureColumn_(key, record[key])\n"
|
||||
" setParts.add(key + \" = ?\")\n"
|
||||
" values.add(serializeValue_(record[key]))\n"
|
||||
" }\n"
|
||||
" values.add(uid)\n"
|
||||
"\n"
|
||||
" var sql = \"UPDATE \" + _name + \" SET \" + setParts.join(\", \") + \" WHERE uid = ? AND deleted_at IS NULL\"\n"
|
||||
" db.execute(sql, values)\n"
|
||||
" return db.changes\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" delete(uid) {\n"
|
||||
" var sql = \"UPDATE \" + _name + \" SET deleted_at = ? WHERE uid = ? AND deleted_at IS NULL\"\n"
|
||||
" db.execute(sql, [DateTime.now().toString, uid])\n"
|
||||
" return db.changes > 0\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" hardDelete(uid) {\n"
|
||||
" var sql = \"DELETE FROM \" + _name + \" WHERE uid = ?\"\n"
|
||||
" db.execute(sql, [uid])\n"
|
||||
" return db.changes > 0\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" find(conditions) {\n"
|
||||
" ensureTable_()\n"
|
||||
" var where = buildWhere_(conditions)\n"
|
||||
" var sql = \"SELECT * FROM \" + _name + \" WHERE deleted_at IS NULL\" + where[\"clause\"]\n"
|
||||
" var rows = db.query(sql, where[\"values\"])\n"
|
||||
" var result = []\n"
|
||||
" for (row in rows) {\n"
|
||||
" result.add(deserializeRow_(row))\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" findOne(conditions) {\n"
|
||||
" var results = find(conditions)\n"
|
||||
" return results.count > 0 ? results[0] : null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" all() {\n"
|
||||
" ensureTable_()\n"
|
||||
" var rows = db.query(\"SELECT * FROM \" + _name + \" WHERE deleted_at IS NULL\")\n"
|
||||
" var result = []\n"
|
||||
" for (row in rows) {\n"
|
||||
" result.add(deserializeRow_(row))\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" count() {\n"
|
||||
" ensureTable_()\n"
|
||||
" var rows = db.query(\"SELECT COUNT(*) as cnt FROM \" + _name + \" WHERE deleted_at IS NULL\")\n"
|
||||
" return rows[0][\"cnt\"]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" buildWhere_(conditions) {\n"
|
||||
" var parts = []\n"
|
||||
" var values = []\n"
|
||||
"\n"
|
||||
" for (key in conditions.keys) {\n"
|
||||
" var value = conditions[key]\n"
|
||||
" var op = \"=\"\n"
|
||||
" var col = key\n"
|
||||
"\n"
|
||||
" if (key.contains(\"__\")) {\n"
|
||||
" var split = key.split(\"__\")\n"
|
||||
" col = split[0]\n"
|
||||
" var suffix = split[1]\n"
|
||||
" if (suffix == \"gt\") {\n"
|
||||
" op = \">\"\n"
|
||||
" } else if (suffix == \"lt\") {\n"
|
||||
" op = \"<\"\n"
|
||||
" } else if (suffix == \"gte\") {\n"
|
||||
" op = \">=\"\n"
|
||||
" } else if (suffix == \"lte\") {\n"
|
||||
" op = \"<=\"\n"
|
||||
" } else if (suffix == \"ne\") {\n"
|
||||
" op = \"!=\"\n"
|
||||
" } else if (suffix == \"like\") {\n"
|
||||
" op = \"LIKE\"\n"
|
||||
" } else if (suffix == \"in\") {\n"
|
||||
" if (value is List) {\n"
|
||||
" var placeholders = []\n"
|
||||
" for (v in value) {\n"
|
||||
" placeholders.add(\"?\")\n"
|
||||
" values.add(v)\n"
|
||||
" }\n"
|
||||
" parts.add(col + \" IN (\" + placeholders.join(\", \") + \")\")\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
" } else if (suffix == \"null\") {\n"
|
||||
" if (value) {\n"
|
||||
" parts.add(col + \" IS NULL\")\n"
|
||||
" } else {\n"
|
||||
" parts.add(col + \" IS NOT NULL\")\n"
|
||||
" }\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" parts.add(col + \" \" + op + \" ?\")\n"
|
||||
" values.add(serializeValue_(value))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var clause = \"\"\n"
|
||||
" if (parts.count > 0) {\n"
|
||||
" clause = \" AND \" + parts.join(\" AND \")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return {\"clause\": clause, \"values\": values}\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
129
src/module/html.wren
vendored
Normal file
129
src/module/html.wren
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "strutil" for Str
|
||||
|
||||
class Html {
|
||||
static isUnreserved_(c) {
|
||||
var code = c.codePoints.toList[0]
|
||||
if (code >= 65 && code <= 90) return true
|
||||
if (code >= 97 && code <= 122) return true
|
||||
if (code >= 48 && code <= 57) return true
|
||||
if (c == "-" || c == "_" || c == "." || c == "~") return true
|
||||
return false
|
||||
}
|
||||
|
||||
static hexDigit_(n) {
|
||||
if (n < 10) return String.fromCodePoint(48 + n)
|
||||
return String.fromCodePoint(65 + n - 10)
|
||||
}
|
||||
|
||||
static hexValue_(c) {
|
||||
var code = c.codePoints.toList[0]
|
||||
if (code >= 48 && code <= 57) return code - 48
|
||||
if (code >= 65 && code <= 70) return code - 55
|
||||
if (code >= 97 && code <= 102) return code - 87
|
||||
return -1
|
||||
}
|
||||
|
||||
static urlencode(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
return Str.urlEncode(string)
|
||||
}
|
||||
|
||||
static decodeUtf8_(bytes) {
|
||||
var result = ""
|
||||
var i = 0
|
||||
while (i < bytes.count) {
|
||||
var b = bytes[i]
|
||||
if (b < 128) {
|
||||
result = result + String.fromCodePoint(b)
|
||||
i = i + 1
|
||||
} else if ((b & 0xE0) == 0xC0 && i + 1 < bytes.count) {
|
||||
var cp = ((b & 0x1F) << 6) | (bytes[i + 1] & 0x3F)
|
||||
result = result + String.fromCodePoint(cp)
|
||||
i = i + 2
|
||||
} else if ((b & 0xF0) == 0xE0 && i + 2 < bytes.count) {
|
||||
var cp = ((b & 0x0F) << 12) | ((bytes[i + 1] & 0x3F) << 6) | (bytes[i + 2] & 0x3F)
|
||||
result = result + String.fromCodePoint(cp)
|
||||
i = i + 3
|
||||
} else if ((b & 0xF8) == 0xF0 && i + 3 < bytes.count) {
|
||||
var cp = ((b & 0x07) << 18) | ((bytes[i + 1] & 0x3F) << 12) | ((bytes[i + 2] & 0x3F) << 6) | (bytes[i + 3] & 0x3F)
|
||||
result = result + String.fromCodePoint(cp)
|
||||
i = i + 4
|
||||
} else {
|
||||
result = result + String.fromCodePoint(0xFFFD)
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static urldecode(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
return Str.urlDecode(string)
|
||||
}
|
||||
|
||||
static slugify(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
var result = ""
|
||||
var prevHyphen = true
|
||||
for (c in string.bytes) {
|
||||
if ((c >= 65 && c <= 90)) {
|
||||
result = result + String.fromCodePoint(c + 32)
|
||||
prevHyphen = false
|
||||
} else if ((c >= 97 && c <= 122) || (c >= 48 && c <= 57)) {
|
||||
result = result + String.fromCodePoint(c)
|
||||
prevHyphen = false
|
||||
} else if (!prevHyphen) {
|
||||
result = result + "-"
|
||||
prevHyphen = true
|
||||
}
|
||||
}
|
||||
if (result.count > 0 && result[-1] == "-") {
|
||||
result = result[0..-2]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static quote(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
return Str.escapeHtml(string)
|
||||
}
|
||||
|
||||
static unquote(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
return string
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace(""", "\"")
|
||||
.replace("'", "'")
|
||||
}
|
||||
|
||||
static encodeParams(params) {
|
||||
if (!(params is Map)) Fiber.abort("Argument must be a map.")
|
||||
var parts = []
|
||||
for (key in params.keys) {
|
||||
parts.add(urlencode(key.toString) + "=" + urlencode(params[key].toString))
|
||||
}
|
||||
return parts.join("&")
|
||||
}
|
||||
|
||||
static decodeParams(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
var params = {}
|
||||
if (string.count == 0) return params
|
||||
var pairs = string.split("&")
|
||||
for (pair in pairs) {
|
||||
var idx = pair.indexOf("=")
|
||||
if (idx > 0) {
|
||||
var key = urldecode(pair[0...idx])
|
||||
var value = urldecode(pair[idx + 1..-1])
|
||||
params[key] = value
|
||||
} else if (pair.count > 0) {
|
||||
params[urldecode(pair)] = ""
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
}
|
||||
133
src/module/html.wren.inc
Normal file
133
src/module/html.wren.inc
Normal file
@ -0,0 +1,133 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/html.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* htmlModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class Html {\n"
|
||||
" static isUnreserved_(c) {\n"
|
||||
" var code = c.codePoints.toList[0]\n"
|
||||
" if (code >= 65 && code <= 90) return true\n"
|
||||
" if (code >= 97 && code <= 122) return true\n"
|
||||
" if (code >= 48 && code <= 57) return true\n"
|
||||
" if (c == \"-\" || c == \"_\" || c == \".\" || c == \"~\") return true\n"
|
||||
" return false\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static hexDigit_(n) {\n"
|
||||
" if (n < 10) return String.fromCodePoint(48 + n)\n"
|
||||
" return String.fromCodePoint(65 + n - 10)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static hexValue_(c) {\n"
|
||||
" var code = c.codePoints.toList[0]\n"
|
||||
" if (code >= 48 && code <= 57) return code - 48\n"
|
||||
" if (code >= 65 && code <= 70) return code - 55\n"
|
||||
" if (code >= 97 && code <= 102) return code - 87\n"
|
||||
" return -1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static urlencode(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" return Str.urlEncode(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static decodeUtf8_(bytes) {\n"
|
||||
" var result = \"\"\n"
|
||||
" var i = 0\n"
|
||||
" while (i < bytes.count) {\n"
|
||||
" var b = bytes[i]\n"
|
||||
" if (b < 128) {\n"
|
||||
" result = result + String.fromCodePoint(b)\n"
|
||||
" i = i + 1\n"
|
||||
" } else if ((b & 0xE0) == 0xC0 && i + 1 < bytes.count) {\n"
|
||||
" var cp = ((b & 0x1F) << 6) | (bytes[i + 1] & 0x3F)\n"
|
||||
" result = result + String.fromCodePoint(cp)\n"
|
||||
" i = i + 2\n"
|
||||
" } else if ((b & 0xF0) == 0xE0 && i + 2 < bytes.count) {\n"
|
||||
" var cp = ((b & 0x0F) << 12) | ((bytes[i + 1] & 0x3F) << 6) | (bytes[i + 2] & 0x3F)\n"
|
||||
" result = result + String.fromCodePoint(cp)\n"
|
||||
" i = i + 3\n"
|
||||
" } else if ((b & 0xF8) == 0xF0 && i + 3 < bytes.count) {\n"
|
||||
" var cp = ((b & 0x07) << 18) | ((bytes[i + 1] & 0x3F) << 12) | ((bytes[i + 2] & 0x3F) << 6) | (bytes[i + 3] & 0x3F)\n"
|
||||
" result = result + String.fromCodePoint(cp)\n"
|
||||
" i = i + 4\n"
|
||||
" } else {\n"
|
||||
" result = result + String.fromCodePoint(0xFFFD)\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static urldecode(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" return Str.urlDecode(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static slugify(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" var result = \"\"\n"
|
||||
" var prevHyphen = true\n"
|
||||
" for (c in string.bytes) {\n"
|
||||
" if ((c >= 65 && c <= 90)) {\n"
|
||||
" result = result + String.fromCodePoint(c + 32)\n"
|
||||
" prevHyphen = false\n"
|
||||
" } else if ((c >= 97 && c <= 122) || (c >= 48 && c <= 57)) {\n"
|
||||
" result = result + String.fromCodePoint(c)\n"
|
||||
" prevHyphen = false\n"
|
||||
" } else if (!prevHyphen) {\n"
|
||||
" result = result + \"-\"\n"
|
||||
" prevHyphen = true\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (result.count > 0 && result[-1] == \"-\") {\n"
|
||||
" result = result[0..-2]\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static quote(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" return Str.escapeHtml(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static unquote(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" return string\n"
|
||||
" .replace(\"&\", \"&\")\n"
|
||||
" .replace(\"<\", \"<\")\n"
|
||||
" .replace(\">\", \">\")\n"
|
||||
" .replace(\""\", \"\\\"\")\n"
|
||||
" .replace(\"'\", \"'\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static encodeParams(params) {\n"
|
||||
" if (!(params is Map)) Fiber.abort(\"Argument must be a map.\")\n"
|
||||
" var parts = []\n"
|
||||
" for (key in params.keys) {\n"
|
||||
" parts.add(urlencode(key.toString) + \"=\" + urlencode(params[key].toString))\n"
|
||||
" }\n"
|
||||
" return parts.join(\"&\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static decodeParams(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" var params = {}\n"
|
||||
" if (string.count == 0) return params\n"
|
||||
" var pairs = string.split(\"&\")\n"
|
||||
" for (pair in pairs) {\n"
|
||||
" var idx = pair.indexOf(\"=\")\n"
|
||||
" if (idx > 0) {\n"
|
||||
" var key = urldecode(pair[0...idx])\n"
|
||||
" var value = urldecode(pair[idx + 1..-1])\n"
|
||||
" params[key] = value\n"
|
||||
" } else if (pair.count > 0) {\n"
|
||||
" params[urldecode(pair)] = \"\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return params\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
14
src/module/http.wren
vendored
14
src/module/http.wren
vendored
@ -5,6 +5,7 @@ import "tls" for TlsSocket
|
||||
import "dns" for Dns
|
||||
import "json" for Json
|
||||
import "base64" for Base64
|
||||
import "strutil" for Str
|
||||
|
||||
class Url {
|
||||
construct parse(url) {
|
||||
@ -44,18 +45,7 @@ class Url {
|
||||
}
|
||||
}
|
||||
|
||||
static toLower_(str) {
|
||||
var result = ""
|
||||
for (c in str) {
|
||||
var cp = c.codePoints[0]
|
||||
if (cp >= 65 && cp <= 90) {
|
||||
result = result + String.fromCodePoint(cp + 32)
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
static toLower_(str) { Str.toLower(str) }
|
||||
|
||||
scheme { _scheme }
|
||||
host { _host }
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `/home/retoor/projects/wren-cli/src/module/http.wren` using `util/wren_to_c_string.py`
|
||||
// from `src/module/http.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* httpModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
@ -9,6 +9,7 @@ static const char* httpModuleSource =
|
||||
"import \"dns\" for Dns\n"
|
||||
"import \"json\" for Json\n"
|
||||
"import \"base64\" for Base64\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class Url {\n"
|
||||
" construct parse(url) {\n"
|
||||
@ -48,18 +49,7 @@ static const char* httpModuleSource =
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toLower_(str) {\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in str) {\n"
|
||||
" var cp = c.codePoints[0]\n"
|
||||
" if (cp >= 65 && cp <= 90) {\n"
|
||||
" result = result + String.fromCodePoint(cp + 32)\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
" static toLower_(str) { Str.toLower(str) }\n"
|
||||
"\n"
|
||||
" scheme { _scheme }\n"
|
||||
" host { _host }\n"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user