Update.
This commit is contained in:
parent
8c1a721c4c
commit
957e3a4762
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()
|
||||
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))
|
||||
396
manual/api/argparse.html
Normal file
396
manual/api/argparse.html
Normal file
@ -0,0 +1,396 @@
|
||||
<!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="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="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>
|
||||
389
manual/api/dataset.html
Normal file
389
manual/api/dataset.html
Normal file
@ -0,0 +1,389 @@
|
||||
<!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="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="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>
|
||||
281
manual/api/html.html
Normal file
281
manual/api/html.html
Normal file
@ -0,0 +1,281 @@
|
||||
<!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="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="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>
|
||||
@ -60,6 +60,13 @@
|
||||
<li><a href="io.html">io</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">
|
||||
@ -97,7 +104,7 @@
|
||||
<article>
|
||||
<h1>API Reference</h1>
|
||||
|
||||
<p>Wren-CLI provides 21 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>
|
||||
<p>Wren-CLI provides 28 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
|
||||
@ -153,6 +160,18 @@ import "io" for File</code></pre>
|
||||
<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>
|
||||
@ -205,6 +224,28 @@ import "io" for File</code></pre>
|
||||
<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>
|
||||
@ -314,6 +355,41 @@ import "io" for File</code></pre>
|
||||
<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>
|
||||
|
||||
|
||||
430
manual/api/markdown.html
Normal file
430
manual/api/markdown.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>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="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="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>
|
||||
223
manual/api/uuid.html
Normal file
223
manual/api/uuid.html
Normal file
@ -0,0 +1,223 @@
|
||||
<!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="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="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>
|
||||
410
manual/api/wdantic.html
Normal file
410
manual/api/wdantic.html
Normal file
@ -0,0 +1,410 @@
|
||||
<!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="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="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>
|
||||
550
manual/api/web.html
Normal file
550
manual/api/web.html
Normal file
@ -0,0 +1,550 @@
|
||||
<!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="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="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>
|
||||
@ -146,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
|
||||
@ -438,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 "$<"
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include "argparse.wren.inc"
|
||||
#include "base64.wren.inc"
|
||||
#include "strutil.wren.inc"
|
||||
#include "bytes.wren.inc"
|
||||
#include "crypto.wren.inc"
|
||||
#include "dataset.wren.inc"
|
||||
@ -20,6 +21,7 @@
|
||||
#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"
|
||||
@ -163,6 +165,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
|
||||
@ -402,6 +427,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)
|
||||
@ -430,6 +471,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)
|
||||
|
||||
7
src/module/argparse.wren
vendored
7
src/module/argparse.wren
vendored
@ -1,6 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "os" for Process
|
||||
import "strutil" for Str
|
||||
|
||||
class ArgumentParser {
|
||||
construct new() {
|
||||
@ -269,9 +270,5 @@ class ArgumentParser {
|
||||
return " " + parts.join(" ")
|
||||
}
|
||||
|
||||
pad_(str, width) {
|
||||
var result = str
|
||||
while (result.count < width) result = result + " "
|
||||
return result
|
||||
}
|
||||
pad_(str, width) { Str.padRight(str, width, " ") }
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ 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"
|
||||
@ -273,9 +274,5 @@ static const char* argparseModuleSource =
|
||||
" return \" \" + parts.join(\" \")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" pad_(str, width) {\n"
|
||||
" var result = str\n"
|
||||
" while (result.count < width) result = result + \" \"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
" pad_(str, width) { Str.padRight(str, width, \" \") }\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";
|
||||
|
||||
58
src/module/html.wren
vendored
58
src/module/html.wren
vendored
@ -1,5 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "strutil" for Str
|
||||
|
||||
class Html {
|
||||
static isUnreserved_(c) {
|
||||
var code = c.codePoints.toList[0]
|
||||
@ -25,19 +27,7 @@ class Html {
|
||||
|
||||
static urlencode(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
var result = ""
|
||||
for (c in string) {
|
||||
if (isUnreserved_(c)) {
|
||||
result = result + c
|
||||
} else if (c == " ") {
|
||||
result = result + "+"
|
||||
} else {
|
||||
for (b in c.bytes) {
|
||||
result = result + "\%" + hexDigit_((b >> 4) & 0x0F) + hexDigit_(b & 0x0F)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
return Str.urlEncode(string)
|
||||
}
|
||||
|
||||
static decodeUtf8_(bytes) {
|
||||
@ -70,29 +60,7 @@ class Html {
|
||||
|
||||
static urldecode(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
var bytes = []
|
||||
var i = 0
|
||||
var chars = string.toList
|
||||
while (i < chars.count) {
|
||||
if (chars[i] == "+") {
|
||||
bytes.add(32)
|
||||
i = i + 1
|
||||
} else if (chars[i] == "\%" && i + 2 < chars.count) {
|
||||
var hi = hexValue_(chars[i + 1])
|
||||
var lo = hexValue_(chars[i + 2])
|
||||
if (hi >= 0 && lo >= 0) {
|
||||
bytes.add((hi << 4) | lo)
|
||||
i = i + 3
|
||||
} else {
|
||||
for (b in chars[i].bytes) bytes.add(b)
|
||||
i = i + 1
|
||||
}
|
||||
} else {
|
||||
for (b in chars[i].bytes) bytes.add(b)
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
return decodeUtf8_(bytes)
|
||||
return Str.urlDecode(string)
|
||||
}
|
||||
|
||||
static slugify(string) {
|
||||
@ -119,23 +87,7 @@ class Html {
|
||||
|
||||
static quote(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
var result = ""
|
||||
for (c in string) {
|
||||
if (c == "&") {
|
||||
result = result + "&"
|
||||
} else if (c == "<") {
|
||||
result = result + "<"
|
||||
} else if (c == ">") {
|
||||
result = result + ">"
|
||||
} else if (c == "\"") {
|
||||
result = result + """
|
||||
} else if (c == "'") {
|
||||
result = result + "'"
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
return Str.escapeHtml(string)
|
||||
}
|
||||
|
||||
static unquote(string) {
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
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"
|
||||
@ -29,19 +31,7 @@ static const char* htmlModuleSource =
|
||||
"\n"
|
||||
" static urlencode(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in string) {\n"
|
||||
" if (isUnreserved_(c)) {\n"
|
||||
" result = result + c\n"
|
||||
" } else if (c == \" \") {\n"
|
||||
" result = result + \"+\"\n"
|
||||
" } else {\n"
|
||||
" for (b in c.bytes) {\n"
|
||||
" result = result + \"\\%\" + hexDigit_((b >> 4) & 0x0F) + hexDigit_(b & 0x0F)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" return Str.urlEncode(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static decodeUtf8_(bytes) {\n"
|
||||
@ -74,29 +64,7 @@ static const char* htmlModuleSource =
|
||||
"\n"
|
||||
" static urldecode(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" var bytes = []\n"
|
||||
" var i = 0\n"
|
||||
" var chars = string.toList\n"
|
||||
" while (i < chars.count) {\n"
|
||||
" if (chars[i] == \"+\") {\n"
|
||||
" bytes.add(32)\n"
|
||||
" i = i + 1\n"
|
||||
" } else if (chars[i] == \"\\%\" && i + 2 < chars.count) {\n"
|
||||
" var hi = hexValue_(chars[i + 1])\n"
|
||||
" var lo = hexValue_(chars[i + 2])\n"
|
||||
" if (hi >= 0 && lo >= 0) {\n"
|
||||
" bytes.add((hi << 4) | lo)\n"
|
||||
" i = i + 3\n"
|
||||
" } else {\n"
|
||||
" for (b in chars[i].bytes) bytes.add(b)\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" for (b in chars[i].bytes) bytes.add(b)\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return decodeUtf8_(bytes)\n"
|
||||
" return Str.urlDecode(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static slugify(string) {\n"
|
||||
@ -123,23 +91,7 @@ static const char* htmlModuleSource =
|
||||
"\n"
|
||||
" static quote(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in string) {\n"
|
||||
" if (c == \"&\") {\n"
|
||||
" result = result + \"&\"\n"
|
||||
" } else if (c == \"<\") {\n"
|
||||
" result = result + \"<\"\n"
|
||||
" } else if (c == \">\") {\n"
|
||||
" result = result + \">\"\n"
|
||||
" } else if (c == \"\\\"\") {\n"
|
||||
" result = result + \""\"\n"
|
||||
" } else if (c == \"'\") {\n"
|
||||
" result = result + \"'\"\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" return Str.escapeHtml(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static unquote(string) {\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"
|
||||
|
||||
26
src/module/json.wren
vendored
26
src/module/json.wren
vendored
@ -1,5 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "strutil" for Str
|
||||
|
||||
class Json {
|
||||
foreign static parse(string)
|
||||
|
||||
@ -35,29 +37,7 @@ class Json {
|
||||
return "null"
|
||||
}
|
||||
|
||||
static escapeString_(s) {
|
||||
var result = "\""
|
||||
for (c in s) {
|
||||
if (c == "\"") {
|
||||
result = result + "\\\""
|
||||
} else if (c == "\\") {
|
||||
result = result + "\\\\"
|
||||
} else if (c == "\b") {
|
||||
result = result + "\\b"
|
||||
} else if (c == "\f") {
|
||||
result = result + "\\f"
|
||||
} else if (c == "\n") {
|
||||
result = result + "\\n"
|
||||
} else if (c == "\r") {
|
||||
result = result + "\\r"
|
||||
} else if (c == "\t") {
|
||||
result = result + "\\t"
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result + "\""
|
||||
}
|
||||
static escapeString_(s) { Str.escapeJson(s) }
|
||||
|
||||
static stringifyList_(list, indent, currentIndent) {
|
||||
if (list.count == 0) return "[]"
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
static const char* jsonModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class Json {\n"
|
||||
" foreign static parse(string)\n"
|
||||
"\n"
|
||||
@ -39,29 +41,7 @@ static const char* jsonModuleSource =
|
||||
" return \"null\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static escapeString_(s) {\n"
|
||||
" var result = \"\\\"\"\n"
|
||||
" for (c in s) {\n"
|
||||
" if (c == \"\\\"\") {\n"
|
||||
" result = result + \"\\\\\\\"\"\n"
|
||||
" } else if (c == \"\\\\\") {\n"
|
||||
" result = result + \"\\\\\\\\\"\n"
|
||||
" } else if (c == \"\\b\") {\n"
|
||||
" result = result + \"\\\\b\"\n"
|
||||
" } else if (c == \"\\f\") {\n"
|
||||
" result = result + \"\\\\f\"\n"
|
||||
" } else if (c == \"\\n\") {\n"
|
||||
" result = result + \"\\\\n\"\n"
|
||||
" } else if (c == \"\\r\") {\n"
|
||||
" result = result + \"\\\\r\"\n"
|
||||
" } else if (c == \"\\t\") {\n"
|
||||
" result = result + \"\\\\t\"\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result + \"\\\"\"\n"
|
||||
" }\n"
|
||||
" static escapeString_(s) { Str.escapeJson(s) }\n"
|
||||
"\n"
|
||||
" static stringifyList_(list, indent, currentIndent) {\n"
|
||||
" if (list.count == 0) return \"[]\"\n"
|
||||
|
||||
559
src/module/markdown.wren
vendored
559
src/module/markdown.wren
vendored
@ -325,4 +325,563 @@ class Markdown {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static fromHtml(html) { fromHtml(html, {}) }
|
||||
|
||||
static fromHtml(html, options) {
|
||||
var stripUnknown = options.containsKey("stripUnknown") ? options["stripUnknown"] : true
|
||||
var text = html
|
||||
|
||||
text = removeHiddenTags_(text)
|
||||
text = processBlockElements_(text)
|
||||
text = processInlineFromHtml_(text)
|
||||
text = Html.unquote(text)
|
||||
text = cleanupWhitespace_(text)
|
||||
|
||||
return text.trim()
|
||||
}
|
||||
|
||||
static removeHiddenTags_(html) {
|
||||
var result = html
|
||||
var tags = ["script", "style", "head", "meta", "link", "noscript"]
|
||||
for (tag in tags) {
|
||||
result = removeTagWithContent_(result, tag)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static removeTagWithContent_(html, tag) {
|
||||
var result = html
|
||||
var openTag = "<" + tag
|
||||
var closeTag = "</" + tag + ">"
|
||||
while (true) {
|
||||
var startLower = result.indexOf(openTag)
|
||||
var startUpper = result.indexOf("<" + tag[0].codePoints.toList[0].toString)
|
||||
var start = -1
|
||||
|
||||
var i = 0
|
||||
while (i < result.count - openTag.count) {
|
||||
if (matchTagName_(result, i, tag)) {
|
||||
start = i
|
||||
break
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
if (start < 0) break
|
||||
|
||||
var tagEnd = start
|
||||
while (tagEnd < result.count && result[tagEnd] != ">") {
|
||||
tagEnd = tagEnd + 1
|
||||
}
|
||||
if (tagEnd >= result.count) break
|
||||
|
||||
if (result[tagEnd - 1] == "/") {
|
||||
result = result[0...start] + result[tagEnd + 1..-1]
|
||||
continue
|
||||
}
|
||||
|
||||
var closeStart = findCloseTag_(result, tagEnd + 1, tag)
|
||||
if (closeStart < 0) {
|
||||
result = result[0...start] + result[tagEnd + 1..-1]
|
||||
} else {
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
result = result[0...start] + result[closeEnd + 1..-1]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static matchTagName_(html, pos, tag) {
|
||||
if (pos >= html.count || html[pos] != "<") return false
|
||||
var rest = html[pos + 1..-1].trim()
|
||||
var tagLower = tag
|
||||
for (i in 0...tag.count) {
|
||||
if (i >= rest.count) return false
|
||||
var c1 = rest[i].codePoints.toList[0]
|
||||
var c2 = tag[i].codePoints.toList[0]
|
||||
if (c1 >= 65 && c1 <= 90) c1 = c1 + 32
|
||||
if (c2 >= 65 && c2 <= 90) c2 = c2 + 32
|
||||
if (c1 != c2) return false
|
||||
}
|
||||
if (tag.count < rest.count) {
|
||||
var next = rest[tag.count]
|
||||
if (next != " " && next != ">" && next != "/" && next != "\t" && next != "\n") return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static findCloseTag_(html, start, tag) {
|
||||
var i = start
|
||||
while (i < html.count - tag.count - 2) {
|
||||
if (html[i] == "<" && html[i + 1] == "/") {
|
||||
var match = true
|
||||
for (j in 0...tag.count) {
|
||||
var c1 = html[i + 2 + j].codePoints.toList[0]
|
||||
var c2 = tag[j].codePoints.toList[0]
|
||||
if (c1 >= 65 && c1 <= 90) c1 = c1 + 32
|
||||
if (c2 >= 65 && c2 <= 90) c2 = c2 + 32
|
||||
if (c1 != c2) {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (match) return i
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
static processBlockElements_(html) {
|
||||
var result = html
|
||||
|
||||
result = processHeadingsFromHtml_(result)
|
||||
result = processCodeBlocksFromHtml_(result)
|
||||
result = processBlockquotesFromHtml_(result)
|
||||
result = processListsFromHtml_(result)
|
||||
result = processHrFromHtml_(result)
|
||||
result = processParagraphsFromHtml_(result)
|
||||
result = processBrFromHtml_(result)
|
||||
result = stripContainerTags_(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processHeadingsFromHtml_(html) {
|
||||
var result = html
|
||||
for (level in 1..6) {
|
||||
var prefix = ""
|
||||
for (i in 0...level) prefix = prefix + "#"
|
||||
result = replaceTag_(result, "h" + level.toString, prefix + " ", "\n\n")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static processCodeBlocksFromHtml_(html) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var preStart = findTagStart_(result, "pre")
|
||||
if (preStart < 0) break
|
||||
|
||||
var preOpenEnd = preStart
|
||||
while (preOpenEnd < result.count && result[preOpenEnd] != ">") {
|
||||
preOpenEnd = preOpenEnd + 1
|
||||
}
|
||||
if (preOpenEnd >= result.count) break
|
||||
|
||||
var preCloseStart = findCloseTag_(result, preOpenEnd + 1, "pre")
|
||||
if (preCloseStart < 0) break
|
||||
|
||||
var content = result[preOpenEnd + 1...preCloseStart]
|
||||
|
||||
var codeStart = findTagStart_(content, "code")
|
||||
if (codeStart >= 0) {
|
||||
var codeOpenEnd = codeStart
|
||||
while (codeOpenEnd < content.count && content[codeOpenEnd] != ">") {
|
||||
codeOpenEnd = codeOpenEnd + 1
|
||||
}
|
||||
var codeCloseStart = findCloseTag_(content, codeOpenEnd + 1, "code")
|
||||
if (codeCloseStart >= 0) {
|
||||
content = content[codeOpenEnd + 1...codeCloseStart]
|
||||
}
|
||||
}
|
||||
|
||||
var preCloseEnd = preCloseStart
|
||||
while (preCloseEnd < result.count && result[preCloseEnd] != ">") {
|
||||
preCloseEnd = preCloseEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...preStart]
|
||||
var after = result[preCloseEnd + 1..-1]
|
||||
result = before + "\n```\n" + content.trim() + "\n```\n" + after
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processBlockquotesFromHtml_(html) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var start = findTagStart_(result, "blockquote")
|
||||
if (start < 0) break
|
||||
|
||||
var openEnd = start
|
||||
while (openEnd < result.count && result[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= result.count) break
|
||||
|
||||
var closeStart = findCloseTag_(result, openEnd + 1, "blockquote")
|
||||
if (closeStart < 0) break
|
||||
|
||||
var content = result[openEnd + 1...closeStart].trim()
|
||||
var lines = content.split("\n")
|
||||
var quoted = []
|
||||
for (line in lines) {
|
||||
var trimmed = line.trim()
|
||||
if (trimmed.count > 0) {
|
||||
quoted.add("> " + trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...start]
|
||||
var after = result[closeEnd + 1..-1]
|
||||
result = before + "\n" + quoted.join("\n") + "\n" + after
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processListsFromHtml_(html) {
|
||||
var result = html
|
||||
result = processListType_(result, "ul", "-")
|
||||
result = processListType_(result, "ol", "1.")
|
||||
return result
|
||||
}
|
||||
|
||||
static processListType_(html, tag, marker) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var start = findTagStart_(result, tag)
|
||||
if (start < 0) break
|
||||
|
||||
var openEnd = start
|
||||
while (openEnd < result.count && result[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= result.count) break
|
||||
|
||||
var closeStart = findCloseTag_(result, openEnd + 1, tag)
|
||||
if (closeStart < 0) break
|
||||
|
||||
var content = result[openEnd + 1...closeStart]
|
||||
var items = extractListItems_(content)
|
||||
var mdItems = []
|
||||
var num = 1
|
||||
for (item in items) {
|
||||
if (tag == "ol") {
|
||||
mdItems.add(num.toString + ". " + item.trim())
|
||||
num = num + 1
|
||||
} else {
|
||||
mdItems.add(marker + " " + item.trim())
|
||||
}
|
||||
}
|
||||
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...start]
|
||||
var after = result[closeEnd + 1..-1]
|
||||
result = before + "\n" + mdItems.join("\n") + "\n" + after
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static extractListItems_(html) {
|
||||
var items = []
|
||||
var i = 0
|
||||
while (i < html.count) {
|
||||
var liStart = findTagStartFrom_(html, i, "li")
|
||||
if (liStart < 0) break
|
||||
|
||||
var openEnd = liStart
|
||||
while (openEnd < html.count && html[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= html.count) break
|
||||
|
||||
var closeStart = findCloseTag_(html, openEnd + 1, "li")
|
||||
var content = ""
|
||||
if (closeStart >= 0) {
|
||||
content = html[openEnd + 1...closeStart]
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < html.count && html[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
i = closeEnd + 1
|
||||
} else {
|
||||
var nextLi = findTagStartFrom_(html, openEnd + 1, "li")
|
||||
if (nextLi >= 0) {
|
||||
content = html[openEnd + 1...nextLi]
|
||||
i = nextLi
|
||||
} else {
|
||||
content = html[openEnd + 1..-1]
|
||||
i = html.count
|
||||
}
|
||||
}
|
||||
items.add(stripAllTags_(content).trim())
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
static findTagStartFrom_(html, start, tag) {
|
||||
var i = start
|
||||
while (i < html.count) {
|
||||
if (matchTagName_(html, i, tag)) return i
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
static processHrFromHtml_(html) {
|
||||
var result = html
|
||||
var i = 0
|
||||
while (i < result.count) {
|
||||
if (matchTagName_(result, i, "hr")) {
|
||||
var end = i
|
||||
while (end < result.count && result[end] != ">") {
|
||||
end = end + 1
|
||||
}
|
||||
result = result[0...i] + "\n---\n" + result[end + 1..-1]
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static processParagraphsFromHtml_(html) {
|
||||
var result = html
|
||||
result = replaceTag_(result, "p", "", "\n\n")
|
||||
return result
|
||||
}
|
||||
|
||||
static processBrFromHtml_(html) {
|
||||
var result = html
|
||||
var i = 0
|
||||
while (i < result.count) {
|
||||
if (matchTagName_(result, i, "br")) {
|
||||
var end = i
|
||||
while (end < result.count && result[end] != ">") {
|
||||
end = end + 1
|
||||
}
|
||||
result = result[0...i] + "\n" + result[end + 1..-1]
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static stripContainerTags_(html) {
|
||||
var result = html
|
||||
var tags = ["div", "span", "section", "article", "main", "header", "footer", "nav", "html", "body"]
|
||||
for (tag in tags) {
|
||||
result = replaceTag_(result, tag, "", "")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static processInlineFromHtml_(html) {
|
||||
var result = html
|
||||
|
||||
result = processStrongFromHtml_(result)
|
||||
result = processEmFromHtml_(result)
|
||||
result = processCodeFromHtml_(result)
|
||||
result = processDelFromHtml_(result)
|
||||
result = processLinksFromHtml_(result)
|
||||
result = processImagesFromHtml_(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processStrongFromHtml_(html) {
|
||||
var result = html
|
||||
result = replaceTag_(result, "strong", "**", "**")
|
||||
result = replaceTag_(result, "b", "**", "**")
|
||||
return result
|
||||
}
|
||||
|
||||
static processEmFromHtml_(html) {
|
||||
var result = html
|
||||
result = replaceTag_(result, "em", "*", "*")
|
||||
result = replaceTag_(result, "i", "*", "*")
|
||||
return result
|
||||
}
|
||||
|
||||
static processCodeFromHtml_(html) {
|
||||
return replaceTag_(html, "code", "`", "`")
|
||||
}
|
||||
|
||||
static processDelFromHtml_(html) {
|
||||
var result = html
|
||||
result = replaceTag_(result, "del", "~~", "~~")
|
||||
result = replaceTag_(result, "s", "~~", "~~")
|
||||
result = replaceTag_(result, "strike", "~~", "~~")
|
||||
return result
|
||||
}
|
||||
|
||||
static processLinksFromHtml_(html) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var start = findTagStart_(result, "a")
|
||||
if (start < 0) break
|
||||
|
||||
var openEnd = start
|
||||
while (openEnd < result.count && result[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= result.count) break
|
||||
|
||||
var tagContent = result[start...openEnd + 1]
|
||||
var href = extractAttr_(tagContent, "href")
|
||||
|
||||
var closeStart = findCloseTag_(result, openEnd + 1, "a")
|
||||
if (closeStart < 0) break
|
||||
|
||||
var linkText = result[openEnd + 1...closeStart]
|
||||
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...start]
|
||||
var after = result[closeEnd + 1..-1]
|
||||
|
||||
if (href != null && href.count > 0) {
|
||||
result = before + "[" + stripAllTags_(linkText) + "](" + href + ")" + after
|
||||
} else {
|
||||
result = before + stripAllTags_(linkText) + after
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processImagesFromHtml_(html) {
|
||||
var result = html
|
||||
var i = 0
|
||||
|
||||
while (i < result.count) {
|
||||
if (matchTagName_(result, i, "img")) {
|
||||
var end = i
|
||||
while (end < result.count && result[end] != ">") {
|
||||
end = end + 1
|
||||
}
|
||||
|
||||
var tagContent = result[i...end + 1]
|
||||
var src = extractAttr_(tagContent, "src")
|
||||
var alt = extractAttr_(tagContent, "alt")
|
||||
|
||||
if (src == null) src = ""
|
||||
if (alt == null) alt = ""
|
||||
|
||||
var mdImg = ""
|
||||
result = result[0...i] + mdImg + result[end + 1..-1]
|
||||
i = i + mdImg.count
|
||||
} else {
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static extractAttr_(tag, name) {
|
||||
var search = name + "=\""
|
||||
var start = tag.indexOf(search)
|
||||
if (start < 0) {
|
||||
search = name + "='"
|
||||
start = tag.indexOf(search)
|
||||
}
|
||||
if (start < 0) return null
|
||||
|
||||
var valueStart = start + search.count
|
||||
var quote = search[-1]
|
||||
var valueEnd = valueStart
|
||||
while (valueEnd < tag.count && tag[valueEnd] != quote) {
|
||||
valueEnd = valueEnd + 1
|
||||
}
|
||||
|
||||
return tag[valueStart...valueEnd]
|
||||
}
|
||||
|
||||
static replaceTag_(html, tag, prefix, suffix) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var start = findTagStart_(result, tag)
|
||||
if (start < 0) break
|
||||
|
||||
var openEnd = start
|
||||
while (openEnd < result.count && result[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= result.count) break
|
||||
|
||||
if (result[openEnd - 1] == "/") {
|
||||
result = result[0...start] + result[openEnd + 1..-1]
|
||||
continue
|
||||
}
|
||||
|
||||
var closeStart = findCloseTag_(result, openEnd + 1, tag)
|
||||
if (closeStart < 0) {
|
||||
result = result[0...start] + result[openEnd + 1..-1]
|
||||
continue
|
||||
}
|
||||
|
||||
var content = result[openEnd + 1...closeStart]
|
||||
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...start]
|
||||
var after = result[closeEnd + 1..-1]
|
||||
result = before + prefix + content + suffix + after
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static findTagStart_(html, tag) {
|
||||
return findTagStartFrom_(html, 0, tag)
|
||||
}
|
||||
|
||||
static stripAllTags_(html) {
|
||||
var result = ""
|
||||
var inTag = false
|
||||
for (c in html) {
|
||||
if (c == "<") {
|
||||
inTag = true
|
||||
} else if (c == ">") {
|
||||
inTag = false
|
||||
} else if (!inTag) {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static cleanupWhitespace_(text) {
|
||||
var result = text
|
||||
|
||||
while (result.contains("\n\n\n")) {
|
||||
result = result.replace("\n\n\n", "\n\n")
|
||||
}
|
||||
|
||||
var lines = result.split("\n")
|
||||
var cleaned = []
|
||||
for (line in lines) {
|
||||
cleaned.add(line.trim())
|
||||
}
|
||||
|
||||
return cleaned.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,4 +329,563 @@ static const char* markdownModuleSource =
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static fromHtml(html) { fromHtml(html, {}) }\n"
|
||||
"\n"
|
||||
" static fromHtml(html, options) {\n"
|
||||
" var stripUnknown = options.containsKey(\"stripUnknown\") ? options[\"stripUnknown\"] : true\n"
|
||||
" var text = html\n"
|
||||
"\n"
|
||||
" text = removeHiddenTags_(text)\n"
|
||||
" text = processBlockElements_(text)\n"
|
||||
" text = processInlineFromHtml_(text)\n"
|
||||
" text = Html.unquote(text)\n"
|
||||
" text = cleanupWhitespace_(text)\n"
|
||||
"\n"
|
||||
" return text.trim()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static removeHiddenTags_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var tags = [\"script\", \"style\", \"head\", \"meta\", \"link\", \"noscript\"]\n"
|
||||
" for (tag in tags) {\n"
|
||||
" result = removeTagWithContent_(result, tag)\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static removeTagWithContent_(html, tag) {\n"
|
||||
" var result = html\n"
|
||||
" var openTag = \"<\" + tag\n"
|
||||
" var closeTag = \"</\" + tag + \">\"\n"
|
||||
" while (true) {\n"
|
||||
" var startLower = result.indexOf(openTag)\n"
|
||||
" var startUpper = result.indexOf(\"<\" + tag[0].codePoints.toList[0].toString)\n"
|
||||
" var start = -1\n"
|
||||
"\n"
|
||||
" var i = 0\n"
|
||||
" while (i < result.count - openTag.count) {\n"
|
||||
" if (matchTagName_(result, i, tag)) {\n"
|
||||
" start = i\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var tagEnd = start\n"
|
||||
" while (tagEnd < result.count && result[tagEnd] != \">\") {\n"
|
||||
" tagEnd = tagEnd + 1\n"
|
||||
" }\n"
|
||||
" if (tagEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" if (result[tagEnd - 1] == \"/\") {\n"
|
||||
" result = result[0...start] + result[tagEnd + 1..-1]\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, tagEnd + 1, tag)\n"
|
||||
" if (closeStart < 0) {\n"
|
||||
" result = result[0...start] + result[tagEnd + 1..-1]\n"
|
||||
" } else {\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
" result = result[0...start] + result[closeEnd + 1..-1]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static matchTagName_(html, pos, tag) {\n"
|
||||
" if (pos >= html.count || html[pos] != \"<\") return false\n"
|
||||
" var rest = html[pos + 1..-1].trim()\n"
|
||||
" var tagLower = tag\n"
|
||||
" for (i in 0...tag.count) {\n"
|
||||
" if (i >= rest.count) return false\n"
|
||||
" var c1 = rest[i].codePoints.toList[0]\n"
|
||||
" var c2 = tag[i].codePoints.toList[0]\n"
|
||||
" if (c1 >= 65 && c1 <= 90) c1 = c1 + 32\n"
|
||||
" if (c2 >= 65 && c2 <= 90) c2 = c2 + 32\n"
|
||||
" if (c1 != c2) return false\n"
|
||||
" }\n"
|
||||
" if (tag.count < rest.count) {\n"
|
||||
" var next = rest[tag.count]\n"
|
||||
" if (next != \" \" && next != \">\" && next != \"/\" && next != \"\\t\" && next != \"\\n\") return false\n"
|
||||
" }\n"
|
||||
" return true\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static findCloseTag_(html, start, tag) {\n"
|
||||
" var i = start\n"
|
||||
" while (i < html.count - tag.count - 2) {\n"
|
||||
" if (html[i] == \"<\" && html[i + 1] == \"/\") {\n"
|
||||
" var match = true\n"
|
||||
" for (j in 0...tag.count) {\n"
|
||||
" var c1 = html[i + 2 + j].codePoints.toList[0]\n"
|
||||
" var c2 = tag[j].codePoints.toList[0]\n"
|
||||
" if (c1 >= 65 && c1 <= 90) c1 = c1 + 32\n"
|
||||
" if (c2 >= 65 && c2 <= 90) c2 = c2 + 32\n"
|
||||
" if (c1 != c2) {\n"
|
||||
" match = false\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (match) return i\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" return -1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processBlockElements_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" result = processHeadingsFromHtml_(result)\n"
|
||||
" result = processCodeBlocksFromHtml_(result)\n"
|
||||
" result = processBlockquotesFromHtml_(result)\n"
|
||||
" result = processListsFromHtml_(result)\n"
|
||||
" result = processHrFromHtml_(result)\n"
|
||||
" result = processParagraphsFromHtml_(result)\n"
|
||||
" result = processBrFromHtml_(result)\n"
|
||||
" result = stripContainerTags_(result)\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processHeadingsFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" for (level in 1..6) {\n"
|
||||
" var prefix = \"\"\n"
|
||||
" for (i in 0...level) prefix = prefix + \"#\"\n"
|
||||
" result = replaceTag_(result, \"h\" + level.toString, prefix + \" \", \"\\n\\n\")\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processCodeBlocksFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var preStart = findTagStart_(result, \"pre\")\n"
|
||||
" if (preStart < 0) break\n"
|
||||
"\n"
|
||||
" var preOpenEnd = preStart\n"
|
||||
" while (preOpenEnd < result.count && result[preOpenEnd] != \">\") {\n"
|
||||
" preOpenEnd = preOpenEnd + 1\n"
|
||||
" }\n"
|
||||
" if (preOpenEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" var preCloseStart = findCloseTag_(result, preOpenEnd + 1, \"pre\")\n"
|
||||
" if (preCloseStart < 0) break\n"
|
||||
"\n"
|
||||
" var content = result[preOpenEnd + 1...preCloseStart]\n"
|
||||
"\n"
|
||||
" var codeStart = findTagStart_(content, \"code\")\n"
|
||||
" if (codeStart >= 0) {\n"
|
||||
" var codeOpenEnd = codeStart\n"
|
||||
" while (codeOpenEnd < content.count && content[codeOpenEnd] != \">\") {\n"
|
||||
" codeOpenEnd = codeOpenEnd + 1\n"
|
||||
" }\n"
|
||||
" var codeCloseStart = findCloseTag_(content, codeOpenEnd + 1, \"code\")\n"
|
||||
" if (codeCloseStart >= 0) {\n"
|
||||
" content = content[codeOpenEnd + 1...codeCloseStart]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var preCloseEnd = preCloseStart\n"
|
||||
" while (preCloseEnd < result.count && result[preCloseEnd] != \">\") {\n"
|
||||
" preCloseEnd = preCloseEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...preStart]\n"
|
||||
" var after = result[preCloseEnd + 1..-1]\n"
|
||||
" result = before + \"\\n```\\n\" + content.trim() + \"\\n```\\n\" + after\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processBlockquotesFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var start = findTagStart_(result, \"blockquote\")\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = start\n"
|
||||
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, openEnd + 1, \"blockquote\")\n"
|
||||
" if (closeStart < 0) break\n"
|
||||
"\n"
|
||||
" var content = result[openEnd + 1...closeStart].trim()\n"
|
||||
" var lines = content.split(\"\\n\")\n"
|
||||
" var quoted = []\n"
|
||||
" for (line in lines) {\n"
|
||||
" var trimmed = line.trim()\n"
|
||||
" if (trimmed.count > 0) {\n"
|
||||
" quoted.add(\"> \" + trimmed)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...start]\n"
|
||||
" var after = result[closeEnd + 1..-1]\n"
|
||||
" result = before + \"\\n\" + quoted.join(\"\\n\") + \"\\n\" + after\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processListsFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = processListType_(result, \"ul\", \"-\")\n"
|
||||
" result = processListType_(result, \"ol\", \"1.\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processListType_(html, tag, marker) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var start = findTagStart_(result, tag)\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = start\n"
|
||||
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, openEnd + 1, tag)\n"
|
||||
" if (closeStart < 0) break\n"
|
||||
"\n"
|
||||
" var content = result[openEnd + 1...closeStart]\n"
|
||||
" var items = extractListItems_(content)\n"
|
||||
" var mdItems = []\n"
|
||||
" var num = 1\n"
|
||||
" for (item in items) {\n"
|
||||
" if (tag == \"ol\") {\n"
|
||||
" mdItems.add(num.toString + \". \" + item.trim())\n"
|
||||
" num = num + 1\n"
|
||||
" } else {\n"
|
||||
" mdItems.add(marker + \" \" + item.trim())\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...start]\n"
|
||||
" var after = result[closeEnd + 1..-1]\n"
|
||||
" result = before + \"\\n\" + mdItems.join(\"\\n\") + \"\\n\" + after\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static extractListItems_(html) {\n"
|
||||
" var items = []\n"
|
||||
" var i = 0\n"
|
||||
" while (i < html.count) {\n"
|
||||
" var liStart = findTagStartFrom_(html, i, \"li\")\n"
|
||||
" if (liStart < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = liStart\n"
|
||||
" while (openEnd < html.count && html[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= html.count) break\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(html, openEnd + 1, \"li\")\n"
|
||||
" var content = \"\"\n"
|
||||
" if (closeStart >= 0) {\n"
|
||||
" content = html[openEnd + 1...closeStart]\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < html.count && html[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
" i = closeEnd + 1\n"
|
||||
" } else {\n"
|
||||
" var nextLi = findTagStartFrom_(html, openEnd + 1, \"li\")\n"
|
||||
" if (nextLi >= 0) {\n"
|
||||
" content = html[openEnd + 1...nextLi]\n"
|
||||
" i = nextLi\n"
|
||||
" } else {\n"
|
||||
" content = html[openEnd + 1..-1]\n"
|
||||
" i = html.count\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" items.add(stripAllTags_(content).trim())\n"
|
||||
" }\n"
|
||||
" return items\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static findTagStartFrom_(html, start, tag) {\n"
|
||||
" var i = start\n"
|
||||
" while (i < html.count) {\n"
|
||||
" if (matchTagName_(html, i, tag)) return i\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" return -1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processHrFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var i = 0\n"
|
||||
" while (i < result.count) {\n"
|
||||
" if (matchTagName_(result, i, \"hr\")) {\n"
|
||||
" var end = i\n"
|
||||
" while (end < result.count && result[end] != \">\") {\n"
|
||||
" end = end + 1\n"
|
||||
" }\n"
|
||||
" result = result[0...i] + \"\\n---\\n\" + result[end + 1..-1]\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processParagraphsFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = replaceTag_(result, \"p\", \"\", \"\\n\\n\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processBrFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var i = 0\n"
|
||||
" while (i < result.count) {\n"
|
||||
" if (matchTagName_(result, i, \"br\")) {\n"
|
||||
" var end = i\n"
|
||||
" while (end < result.count && result[end] != \">\") {\n"
|
||||
" end = end + 1\n"
|
||||
" }\n"
|
||||
" result = result[0...i] + \"\\n\" + result[end + 1..-1]\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stripContainerTags_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var tags = [\"div\", \"span\", \"section\", \"article\", \"main\", \"header\", \"footer\", \"nav\", \"html\", \"body\"]\n"
|
||||
" for (tag in tags) {\n"
|
||||
" result = replaceTag_(result, tag, \"\", \"\")\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processInlineFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" result = processStrongFromHtml_(result)\n"
|
||||
" result = processEmFromHtml_(result)\n"
|
||||
" result = processCodeFromHtml_(result)\n"
|
||||
" result = processDelFromHtml_(result)\n"
|
||||
" result = processLinksFromHtml_(result)\n"
|
||||
" result = processImagesFromHtml_(result)\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processStrongFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = replaceTag_(result, \"strong\", \"**\", \"**\")\n"
|
||||
" result = replaceTag_(result, \"b\", \"**\", \"**\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processEmFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = replaceTag_(result, \"em\", \"*\", \"*\")\n"
|
||||
" result = replaceTag_(result, \"i\", \"*\", \"*\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processCodeFromHtml_(html) {\n"
|
||||
" return replaceTag_(html, \"code\", \"`\", \"`\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processDelFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = replaceTag_(result, \"del\", \"~~\", \"~~\")\n"
|
||||
" result = replaceTag_(result, \"s\", \"~~\", \"~~\")\n"
|
||||
" result = replaceTag_(result, \"strike\", \"~~\", \"~~\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processLinksFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var start = findTagStart_(result, \"a\")\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = start\n"
|
||||
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" var tagContent = result[start...openEnd + 1]\n"
|
||||
" var href = extractAttr_(tagContent, \"href\")\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, openEnd + 1, \"a\")\n"
|
||||
" if (closeStart < 0) break\n"
|
||||
"\n"
|
||||
" var linkText = result[openEnd + 1...closeStart]\n"
|
||||
"\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...start]\n"
|
||||
" var after = result[closeEnd + 1..-1]\n"
|
||||
"\n"
|
||||
" if (href != null && href.count > 0) {\n"
|
||||
" result = before + \"[\" + stripAllTags_(linkText) + \"](\" + href + \")\" + after\n"
|
||||
" } else {\n"
|
||||
" result = before + stripAllTags_(linkText) + after\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processImagesFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var i = 0\n"
|
||||
"\n"
|
||||
" while (i < result.count) {\n"
|
||||
" if (matchTagName_(result, i, \"img\")) {\n"
|
||||
" var end = i\n"
|
||||
" while (end < result.count && result[end] != \">\") {\n"
|
||||
" end = end + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var tagContent = result[i...end + 1]\n"
|
||||
" var src = extractAttr_(tagContent, \"src\")\n"
|
||||
" var alt = extractAttr_(tagContent, \"alt\")\n"
|
||||
"\n"
|
||||
" if (src == null) src = \"\"\n"
|
||||
" if (alt == null) alt = \"\"\n"
|
||||
"\n"
|
||||
" var mdImg = \"\"\n"
|
||||
" result = result[0...i] + mdImg + result[end + 1..-1]\n"
|
||||
" i = i + mdImg.count\n"
|
||||
" } else {\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static extractAttr_(tag, name) {\n"
|
||||
" var search = name + \"=\\\"\"\n"
|
||||
" var start = tag.indexOf(search)\n"
|
||||
" if (start < 0) {\n"
|
||||
" search = name + \"='\"\n"
|
||||
" start = tag.indexOf(search)\n"
|
||||
" }\n"
|
||||
" if (start < 0) return null\n"
|
||||
"\n"
|
||||
" var valueStart = start + search.count\n"
|
||||
" var quote = search[-1]\n"
|
||||
" var valueEnd = valueStart\n"
|
||||
" while (valueEnd < tag.count && tag[valueEnd] != quote) {\n"
|
||||
" valueEnd = valueEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return tag[valueStart...valueEnd]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static replaceTag_(html, tag, prefix, suffix) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var start = findTagStart_(result, tag)\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = start\n"
|
||||
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" if (result[openEnd - 1] == \"/\") {\n"
|
||||
" result = result[0...start] + result[openEnd + 1..-1]\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, openEnd + 1, tag)\n"
|
||||
" if (closeStart < 0) {\n"
|
||||
" result = result[0...start] + result[openEnd + 1..-1]\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var content = result[openEnd + 1...closeStart]\n"
|
||||
"\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...start]\n"
|
||||
" var after = result[closeEnd + 1..-1]\n"
|
||||
" result = before + prefix + content + suffix + after\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static findTagStart_(html, tag) {\n"
|
||||
" return findTagStartFrom_(html, 0, tag)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stripAllTags_(html) {\n"
|
||||
" var result = \"\"\n"
|
||||
" var inTag = false\n"
|
||||
" for (c in html) {\n"
|
||||
" if (c == \"<\") {\n"
|
||||
" inTag = true\n"
|
||||
" } else if (c == \">\") {\n"
|
||||
" inTag = false\n"
|
||||
" } else if (!inTag) {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static cleanupWhitespace_(text) {\n"
|
||||
" var result = text\n"
|
||||
"\n"
|
||||
" while (result.contains(\"\\n\\n\\n\")) {\n"
|
||||
" result = result.replace(\"\\n\\n\\n\", \"\\n\\n\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var lines = result.split(\"\\n\")\n"
|
||||
" var cleaned = []\n"
|
||||
" for (line in lines) {\n"
|
||||
" cleaned.add(line.trim())\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return cleaned.join(\"\\n\")\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
697
src/module/pathlib.c
Normal file
697
src/module/pathlib.c
Normal file
@ -0,0 +1,697 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#endif
|
||||
|
||||
#include "uv.h"
|
||||
#include "pathlib.h"
|
||||
#include "scheduler.h"
|
||||
#include "stat.h"
|
||||
#include "vm.h"
|
||||
#include "wren.h"
|
||||
|
||||
typedef struct {
|
||||
WrenHandle* fiber;
|
||||
char* path;
|
||||
char* path2;
|
||||
int mode;
|
||||
bool recursive;
|
||||
char* pattern;
|
||||
} PathlibRequest;
|
||||
|
||||
static WrenHandle* statClass = NULL;
|
||||
|
||||
static uv_fs_t* createPathlibRequest(WrenHandle* fiber)
|
||||
{
|
||||
uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t));
|
||||
if (request == NULL) return NULL;
|
||||
|
||||
PathlibRequest* data = (PathlibRequest*)malloc(sizeof(PathlibRequest));
|
||||
if (data == NULL) {
|
||||
free(request);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data->fiber = fiber;
|
||||
data->path = NULL;
|
||||
data->path2 = NULL;
|
||||
data->mode = 0;
|
||||
data->recursive = false;
|
||||
data->pattern = NULL;
|
||||
|
||||
request->data = data;
|
||||
return request;
|
||||
}
|
||||
|
||||
static WrenHandle* freePathlibRequest(uv_fs_t* request)
|
||||
{
|
||||
PathlibRequest* data = (PathlibRequest*)request->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
|
||||
if (data->path != NULL) free(data->path);
|
||||
if (data->path2 != NULL) free(data->path2);
|
||||
if (data->pattern != NULL) free(data->pattern);
|
||||
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
|
||||
return fiber;
|
||||
}
|
||||
|
||||
static bool handlePathlibError(uv_fs_t* request)
|
||||
{
|
||||
if (request->result >= 0) return false;
|
||||
|
||||
PathlibRequest* data = (PathlibRequest*)request->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
|
||||
int error = (int)request->result;
|
||||
|
||||
if (data->path != NULL) free(data->path);
|
||||
if (data->path2 != NULL) free(data->path2);
|
||||
if (data->pattern != NULL) free(data->pattern);
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
|
||||
schedulerResumeError(fiber, uv_strerror(error));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void isSymlinkCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handlePathlibError(request)) return;
|
||||
|
||||
bool isSymlink = S_ISLNK(request->statbuf.st_mode);
|
||||
schedulerResume(freePathlibRequest(request), true);
|
||||
wrenSetSlotBool(getVM(), 2, isSymlink);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void pathlibIsSymlink(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_lstat(getLoop(), request, path, isSymlinkCallback);
|
||||
}
|
||||
|
||||
static void lstatCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handlePathlibError(request)) return;
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 3);
|
||||
|
||||
if (statClass == NULL) {
|
||||
wrenGetVariable(vm, "io", "Stat", 0);
|
||||
statClass = wrenGetSlotHandle(vm, 0);
|
||||
}
|
||||
|
||||
wrenSetSlotHandle(vm, 2, statClass);
|
||||
wrenSetSlotNewForeign(vm, 2, 2, sizeof(uv_stat_t));
|
||||
|
||||
uv_stat_t* data = (uv_stat_t*)wrenGetSlotForeign(vm, 2);
|
||||
*data = request->statbuf;
|
||||
|
||||
schedulerResume(freePathlibRequest(request), true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void pathlibLstat(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_lstat(getLoop(), request, path, lstatCallback);
|
||||
}
|
||||
|
||||
static void touchCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handlePathlibError(request)) return;
|
||||
schedulerResume(freePathlibRequest(request), false);
|
||||
}
|
||||
|
||||
void pathlibTouch(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
double now = (double)time(NULL);
|
||||
uv_fs_utime(getLoop(), request, path, now, now, touchCallback);
|
||||
}
|
||||
|
||||
static void renameCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handlePathlibError(request)) return;
|
||||
schedulerResume(freePathlibRequest(request), false);
|
||||
}
|
||||
|
||||
void pathlibRename(WrenVM* vm)
|
||||
{
|
||||
const char* oldPath = wrenGetSlotString(vm, 1);
|
||||
const char* newPath = wrenGetSlotString(vm, 2);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_rename(getLoop(), request, oldPath, newPath, renameCallback);
|
||||
}
|
||||
|
||||
static void chmodCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handlePathlibError(request)) return;
|
||||
schedulerResume(freePathlibRequest(request), false);
|
||||
}
|
||||
|
||||
void pathlibChmod(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
int mode = (int)wrenGetSlotDouble(vm, 2);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_chmod(getLoop(), request, path, mode, chmodCallback);
|
||||
}
|
||||
|
||||
static void symlinkCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handlePathlibError(request)) return;
|
||||
schedulerResume(freePathlibRequest(request), false);
|
||||
}
|
||||
|
||||
void pathlibSymlinkTo(WrenVM* vm)
|
||||
{
|
||||
const char* linkPath = wrenGetSlotString(vm, 1);
|
||||
const char* targetPath = wrenGetSlotString(vm, 2);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_symlink(getLoop(), request, targetPath, linkPath, 0, symlinkCallback);
|
||||
}
|
||||
|
||||
static void hardlinkCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handlePathlibError(request)) return;
|
||||
schedulerResume(freePathlibRequest(request), false);
|
||||
}
|
||||
|
||||
void pathlibHardlinkTo(WrenVM* vm)
|
||||
{
|
||||
const char* linkPath = wrenGetSlotString(vm, 1);
|
||||
const char* targetPath = wrenGetSlotString(vm, 2);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_link(getLoop(), request, targetPath, linkPath, hardlinkCallback);
|
||||
}
|
||||
|
||||
static void readlinkCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handlePathlibError(request)) return;
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotString(vm, 2, (const char*)request->ptr);
|
||||
schedulerResume(freePathlibRequest(request), true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void pathlibReadlink(WrenVM* vm)
|
||||
{
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_readlink(getLoop(), request, path, readlinkCallback);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
WrenHandle* fiber;
|
||||
char* path1;
|
||||
char* path2;
|
||||
uv_stat_t stat1;
|
||||
bool hasStat1;
|
||||
} SamefileRequest;
|
||||
|
||||
static void samefileStat2Callback(uv_fs_t* request)
|
||||
{
|
||||
SamefileRequest* data = (SamefileRequest*)request->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
|
||||
if (request->result < 0) {
|
||||
int error = (int)request->result;
|
||||
free(data->path1);
|
||||
free(data->path2);
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
schedulerResumeError(fiber, uv_strerror(error));
|
||||
return;
|
||||
}
|
||||
|
||||
bool same = (data->stat1.st_dev == request->statbuf.st_dev &&
|
||||
data->stat1.st_ino == request->statbuf.st_ino);
|
||||
|
||||
free(data->path1);
|
||||
free(data->path2);
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
|
||||
schedulerResume(fiber, true);
|
||||
wrenSetSlotBool(getVM(), 2, same);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
static void samefileStat1Callback(uv_fs_t* request)
|
||||
{
|
||||
SamefileRequest* data = (SamefileRequest*)request->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
|
||||
if (request->result < 0) {
|
||||
int error = (int)request->result;
|
||||
free(data->path1);
|
||||
free(data->path2);
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
schedulerResumeError(fiber, uv_strerror(error));
|
||||
return;
|
||||
}
|
||||
|
||||
data->stat1 = request->statbuf;
|
||||
data->hasStat1 = true;
|
||||
|
||||
uv_fs_req_cleanup(request);
|
||||
uv_fs_stat(getLoop(), request, data->path2, samefileStat2Callback);
|
||||
}
|
||||
|
||||
void pathlibSamefile(WrenVM* vm)
|
||||
{
|
||||
const char* path1 = wrenGetSlotString(vm, 1);
|
||||
const char* path2 = wrenGetSlotString(vm, 2);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 3);
|
||||
|
||||
uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t));
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
SamefileRequest* data = (SamefileRequest*)malloc(sizeof(SamefileRequest));
|
||||
if (data == NULL) {
|
||||
free(request);
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len1 = strlen(path1);
|
||||
size_t len2 = strlen(path2);
|
||||
|
||||
data->path1 = (char*)malloc(len1 + 1);
|
||||
data->path2 = (char*)malloc(len2 + 1);
|
||||
|
||||
if (data->path1 == NULL || data->path2 == NULL) {
|
||||
if (data->path1 != NULL) free(data->path1);
|
||||
if (data->path2 != NULL) free(data->path2);
|
||||
free(data);
|
||||
free(request);
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(data->path1, path1, len1 + 1);
|
||||
memcpy(data->path2, path2, len2 + 1);
|
||||
data->fiber = fiber;
|
||||
data->hasStat1 = false;
|
||||
|
||||
request->data = data;
|
||||
uv_fs_stat(getLoop(), request, data->path1, samefileStat1Callback);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char** entries;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} PathList;
|
||||
|
||||
static void pathListInit(PathList* list)
|
||||
{
|
||||
list->entries = NULL;
|
||||
list->count = 0;
|
||||
list->capacity = 0;
|
||||
}
|
||||
|
||||
static void pathListAdd(PathList* list, const char* path)
|
||||
{
|
||||
if (list->count >= list->capacity) {
|
||||
size_t newCapacity = list->capacity == 0 ? 16 : list->capacity * 2;
|
||||
char** newEntries = (char**)realloc(list->entries, newCapacity * sizeof(char*));
|
||||
if (newEntries == NULL) return;
|
||||
list->entries = newEntries;
|
||||
list->capacity = newCapacity;
|
||||
}
|
||||
|
||||
size_t len = strlen(path);
|
||||
char* copy = (char*)malloc(len + 1);
|
||||
if (copy == NULL) return;
|
||||
memcpy(copy, path, len + 1);
|
||||
list->entries[list->count++] = copy;
|
||||
}
|
||||
|
||||
static void pathListFree(PathList* list)
|
||||
{
|
||||
for (size_t i = 0; i < list->count; i++) {
|
||||
free(list->entries[i]);
|
||||
}
|
||||
if (list->entries != NULL) free(list->entries);
|
||||
list->entries = NULL;
|
||||
list->count = 0;
|
||||
list->capacity = 0;
|
||||
}
|
||||
|
||||
static bool matchGlobSimple(const char* text, const char* pattern)
|
||||
{
|
||||
const char* tp = text;
|
||||
const char* pp = pattern;
|
||||
const char* starTp = NULL;
|
||||
const char* starPp = NULL;
|
||||
|
||||
while (*tp != '\0') {
|
||||
if (*pp == '?' || *pp == *tp) {
|
||||
tp++;
|
||||
pp++;
|
||||
} else if (*pp == '*') {
|
||||
starPp = pp;
|
||||
starTp = tp;
|
||||
pp++;
|
||||
} else if (starPp != NULL) {
|
||||
pp = starPp + 1;
|
||||
starTp++;
|
||||
tp = starTp;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
while (*pp == '*') pp++;
|
||||
|
||||
return *pp == '\0';
|
||||
}
|
||||
|
||||
static void globRecursive(const char* basePath, const char* pattern, bool recursive, PathList* results)
|
||||
{
|
||||
uv_fs_t req;
|
||||
int r = uv_fs_scandir(NULL, &req, basePath, 0, NULL);
|
||||
if (r < 0) {
|
||||
uv_fs_req_cleanup(&req);
|
||||
return;
|
||||
}
|
||||
|
||||
uv_dirent_t entry;
|
||||
while (uv_fs_scandir_next(&req, &entry) != UV_EOF) {
|
||||
size_t baseLen = strlen(basePath);
|
||||
size_t nameLen = strlen(entry.name);
|
||||
size_t fullLen = baseLen + 1 + nameLen;
|
||||
|
||||
char* fullPath = (char*)malloc(fullLen + 1);
|
||||
if (fullPath == NULL) continue;
|
||||
|
||||
if (baseLen > 0 && basePath[baseLen - 1] == '/') {
|
||||
snprintf(fullPath, fullLen + 1, "%s%s", basePath, entry.name);
|
||||
} else {
|
||||
snprintf(fullPath, fullLen + 1, "%s/%s", basePath, entry.name);
|
||||
}
|
||||
|
||||
if (matchGlobSimple(entry.name, pattern)) {
|
||||
pathListAdd(results, fullPath);
|
||||
}
|
||||
|
||||
if (recursive && entry.type == UV_DIRENT_DIR) {
|
||||
globRecursive(fullPath, pattern, true, results);
|
||||
}
|
||||
|
||||
free(fullPath);
|
||||
}
|
||||
|
||||
uv_fs_req_cleanup(&req);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
WrenHandle* fiber;
|
||||
char* basePath;
|
||||
char* pattern;
|
||||
bool recursive;
|
||||
} GlobRequest;
|
||||
|
||||
static void globCallback(uv_fs_t* request)
|
||||
{
|
||||
GlobRequest* data = (GlobRequest*)request->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
|
||||
if (request->result < 0) {
|
||||
int error = (int)request->result;
|
||||
free(data->basePath);
|
||||
free(data->pattern);
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
schedulerResumeError(fiber, uv_strerror(error));
|
||||
return;
|
||||
}
|
||||
|
||||
PathList results;
|
||||
pathListInit(&results);
|
||||
|
||||
uv_dirent_t entry;
|
||||
while (uv_fs_scandir_next(request, &entry) != UV_EOF) {
|
||||
size_t baseLen = strlen(data->basePath);
|
||||
size_t nameLen = strlen(entry.name);
|
||||
size_t fullLen = baseLen + 1 + nameLen;
|
||||
|
||||
char* fullPath = (char*)malloc(fullLen + 1);
|
||||
if (fullPath == NULL) continue;
|
||||
|
||||
if (baseLen > 0 && data->basePath[baseLen - 1] == '/') {
|
||||
snprintf(fullPath, fullLen + 1, "%s%s", data->basePath, entry.name);
|
||||
} else {
|
||||
snprintf(fullPath, fullLen + 1, "%s/%s", data->basePath, entry.name);
|
||||
}
|
||||
|
||||
if (matchGlobSimple(entry.name, data->pattern)) {
|
||||
pathListAdd(&results, fullPath);
|
||||
}
|
||||
|
||||
if (data->recursive && entry.type == UV_DIRENT_DIR) {
|
||||
globRecursive(fullPath, data->pattern, true, &results);
|
||||
}
|
||||
|
||||
free(fullPath);
|
||||
}
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotNewList(vm, 2);
|
||||
|
||||
for (size_t i = 0; i < results.count; i++) {
|
||||
wrenSetSlotString(vm, 1, results.entries[i]);
|
||||
wrenInsertInList(vm, 2, -1, 1);
|
||||
}
|
||||
|
||||
pathListFree(&results);
|
||||
free(data->basePath);
|
||||
free(data->pattern);
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
|
||||
schedulerResume(fiber, true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void pathlibGlob(WrenVM* vm)
|
||||
{
|
||||
const char* basePath = wrenGetSlotString(vm, 1);
|
||||
const char* pattern = wrenGetSlotString(vm, 2);
|
||||
bool recursive = wrenGetSlotBool(vm, 3);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 4);
|
||||
|
||||
uv_fs_t* request = (uv_fs_t*)malloc(sizeof(uv_fs_t));
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
GlobRequest* data = (GlobRequest*)malloc(sizeof(GlobRequest));
|
||||
if (data == NULL) {
|
||||
free(request);
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t baseLen = strlen(basePath);
|
||||
size_t patternLen = strlen(pattern);
|
||||
|
||||
data->basePath = (char*)malloc(baseLen + 1);
|
||||
data->pattern = (char*)malloc(patternLen + 1);
|
||||
|
||||
if (data->basePath == NULL || data->pattern == NULL) {
|
||||
if (data->basePath != NULL) free(data->basePath);
|
||||
if (data->pattern != NULL) free(data->pattern);
|
||||
free(data);
|
||||
free(request);
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(data->basePath, basePath, baseLen + 1);
|
||||
memcpy(data->pattern, pattern, patternLen + 1);
|
||||
data->fiber = fiber;
|
||||
data->recursive = recursive;
|
||||
|
||||
request->data = data;
|
||||
uv_fs_scandir(getLoop(), request, basePath, 0, globCallback);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
static void ownerCallback(uv_fs_t* request)
|
||||
{
|
||||
if (request->result < 0) {
|
||||
PathlibRequest* data = (PathlibRequest*)request->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
int error = (int)request->result;
|
||||
|
||||
if (data->path != NULL) free(data->path);
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
|
||||
schedulerResumeError(fiber, uv_strerror(error));
|
||||
return;
|
||||
}
|
||||
|
||||
struct passwd* pwd = getpwuid(request->statbuf.st_uid);
|
||||
const char* name = pwd != NULL ? pwd->pw_name : "";
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotString(vm, 2, name);
|
||||
schedulerResume(freePathlibRequest(request), true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
#endif
|
||||
|
||||
void pathlibOwner(WrenVM* vm)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_stat(getLoop(), request, path, ownerCallback);
|
||||
#else
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
static void groupCallback(uv_fs_t* request)
|
||||
{
|
||||
if (request->result < 0) {
|
||||
PathlibRequest* data = (PathlibRequest*)request->data;
|
||||
WrenHandle* fiber = data->fiber;
|
||||
int error = (int)request->result;
|
||||
|
||||
if (data->path != NULL) free(data->path);
|
||||
free(data);
|
||||
uv_fs_req_cleanup(request);
|
||||
free(request);
|
||||
|
||||
schedulerResumeError(fiber, uv_strerror(error));
|
||||
return;
|
||||
}
|
||||
|
||||
struct group* grp = getgrgid(request->statbuf.st_gid);
|
||||
const char* name = grp != NULL ? grp->gr_name : "";
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 3);
|
||||
wrenSetSlotString(vm, 2, name);
|
||||
schedulerResume(freePathlibRequest(request), true);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
#endif
|
||||
|
||||
void pathlibGroup(WrenVM* vm)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
WrenHandle* fiber = wrenGetSlotHandle(vm, 2);
|
||||
uv_fs_t* request = createPathlibRequest(fiber);
|
||||
|
||||
if (request == NULL) {
|
||||
schedulerResumeError(fiber, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_fs_stat(getLoop(), request, path, groupCallback);
|
||||
#else
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
#endif
|
||||
}
|
||||
21
src/module/pathlib.h
Normal file
21
src/module/pathlib.h
Normal file
@ -0,0 +1,21 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef pathlib_h
|
||||
#define pathlib_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void pathlibIsSymlink(WrenVM* vm);
|
||||
void pathlibLstat(WrenVM* vm);
|
||||
void pathlibTouch(WrenVM* vm);
|
||||
void pathlibRename(WrenVM* vm);
|
||||
void pathlibChmod(WrenVM* vm);
|
||||
void pathlibSymlinkTo(WrenVM* vm);
|
||||
void pathlibHardlinkTo(WrenVM* vm);
|
||||
void pathlibReadlink(WrenVM* vm);
|
||||
void pathlibSamefile(WrenVM* vm);
|
||||
void pathlibGlob(WrenVM* vm);
|
||||
void pathlibOwner(WrenVM* vm);
|
||||
void pathlibGroup(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
516
src/module/pathlib.wren
vendored
Normal file
516
src/module/pathlib.wren
vendored
Normal file
@ -0,0 +1,516 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "io" for Directory, File, Stat
|
||||
import "os" for Platform, Process
|
||||
import "scheduler" for Scheduler
|
||||
|
||||
class PurePath {
|
||||
construct new(path) {
|
||||
if (path is PurePath) {
|
||||
_path = path.toString
|
||||
} else if (path is String) {
|
||||
_path = path
|
||||
} else {
|
||||
Fiber.abort("Path must be a string or Path.")
|
||||
}
|
||||
}
|
||||
|
||||
toString { _path }
|
||||
|
||||
parts {
|
||||
if (_path.isEmpty) return []
|
||||
var result = []
|
||||
var current = ""
|
||||
var i = 0
|
||||
|
||||
if (_path[0] == "/") {
|
||||
result.add("/")
|
||||
i = 1
|
||||
}
|
||||
|
||||
while (i < _path.count) {
|
||||
var c = _path[i]
|
||||
if (c == "/") {
|
||||
if (!current.isEmpty) {
|
||||
result.add(current)
|
||||
current = ""
|
||||
}
|
||||
} else {
|
||||
current = current + c
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
if (!current.isEmpty) {
|
||||
result.add(current)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
name {
|
||||
if (_path.isEmpty) return ""
|
||||
var p = parts
|
||||
if (p.isEmpty) return ""
|
||||
return p[-1]
|
||||
}
|
||||
|
||||
suffix {
|
||||
var n = name
|
||||
if (n.isEmpty) return ""
|
||||
var dotIndex = -1
|
||||
for (i in (n.count - 1)..0) {
|
||||
if (n[i] == ".") {
|
||||
dotIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (dotIndex <= 0) return ""
|
||||
return n[dotIndex..-1]
|
||||
}
|
||||
|
||||
suffixes {
|
||||
var n = name
|
||||
if (n.isEmpty) return []
|
||||
var result = []
|
||||
var i = n.count - 1
|
||||
while (i > 0) {
|
||||
if (n[i] == ".") {
|
||||
result.insert(0, n[i..-1])
|
||||
n = n[0...i]
|
||||
i = n.count - 1
|
||||
} else {
|
||||
i = i - 1
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
stem {
|
||||
var n = name
|
||||
if (n.isEmpty) return ""
|
||||
var s = suffix
|
||||
if (s.isEmpty) return n
|
||||
return n[0...(n.count - s.count)]
|
||||
}
|
||||
|
||||
parent {
|
||||
if (_path.isEmpty) return Path.new(".")
|
||||
var p = parts
|
||||
if (p.count <= 1) {
|
||||
if (isAbsolute) return Path.new("/")
|
||||
return Path.new(".")
|
||||
}
|
||||
p = p[0...-1]
|
||||
return Path.new(joinParts_(p))
|
||||
}
|
||||
|
||||
parents {
|
||||
var result = []
|
||||
var current = this
|
||||
while (true) {
|
||||
var p = current.parent
|
||||
if (p.toString == current.toString) break
|
||||
result.add(p)
|
||||
current = p
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
drive {
|
||||
if (_path.count >= 2 && _path[1] == ":") {
|
||||
return _path[0..1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
root {
|
||||
if (_path.isEmpty) return ""
|
||||
if (_path[0] == "/") return "/"
|
||||
if (drive != "" && _path.count >= 3 && _path[2] == "/") return "/"
|
||||
return ""
|
||||
}
|
||||
|
||||
anchor { drive + root }
|
||||
|
||||
isAbsolute { root != "" }
|
||||
|
||||
asPosix {
|
||||
var result = ""
|
||||
for (i in 0..._path.count) {
|
||||
var c = _path[i]
|
||||
if (c == "\\") {
|
||||
result = result + "/"
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
joinpath(other) {
|
||||
if (other is List) {
|
||||
var result = this
|
||||
for (p in other) {
|
||||
result = result.joinpath(p)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var otherPath = other is PurePath ? other.toString : other.toString
|
||||
|
||||
if (otherPath.isEmpty) return Path.new(_path)
|
||||
if (otherPath[0] == "/") return Path.new(otherPath)
|
||||
if (_path.isEmpty) return Path.new(otherPath)
|
||||
if (_path[-1] == "/") return Path.new(_path + otherPath)
|
||||
return Path.new(_path + "/" + otherPath)
|
||||
}
|
||||
|
||||
/(other) { joinpath(other) }
|
||||
|
||||
withName(newName) {
|
||||
if (name.isEmpty) Fiber.abort("Path has no name.")
|
||||
var p = parent
|
||||
return p / newName
|
||||
}
|
||||
|
||||
withStem(newStem) {
|
||||
var s = suffix
|
||||
return withName(newStem + s)
|
||||
}
|
||||
|
||||
withSuffix(newSuffix) {
|
||||
var s = stem
|
||||
if (s.isEmpty) Fiber.abort("Path has no stem.")
|
||||
return withName(s + newSuffix)
|
||||
}
|
||||
|
||||
relativeTo(base) {
|
||||
var basePath = base is PurePath ? base : PurePath.new(base)
|
||||
var myParts = parts
|
||||
var baseParts = basePath.parts
|
||||
|
||||
if (baseParts.count > myParts.count) {
|
||||
Fiber.abort("'%(this)' is not relative to '%(basePath)'")
|
||||
}
|
||||
|
||||
for (i in 0...baseParts.count) {
|
||||
if (myParts[i] != baseParts[i]) {
|
||||
Fiber.abort("'%(this)' is not relative to '%(basePath)'")
|
||||
}
|
||||
}
|
||||
|
||||
if (baseParts.count == myParts.count) return Path.new(".")
|
||||
|
||||
var relParts = myParts[baseParts.count..-1]
|
||||
return Path.new(joinParts_(relParts))
|
||||
}
|
||||
|
||||
match(pattern) {
|
||||
var n = name
|
||||
return matchGlob_(n, pattern)
|
||||
}
|
||||
|
||||
==(other) {
|
||||
if (!(other is PurePath)) return false
|
||||
return _path == other.toString
|
||||
}
|
||||
|
||||
!=(other) { !(this == other) }
|
||||
|
||||
hashCode { _path.hashCode }
|
||||
|
||||
joinParts_(p) {
|
||||
if (p.isEmpty) return ""
|
||||
if (p.count == 1 && p[0] == "/") return "/"
|
||||
var result = ""
|
||||
for (i in 0...p.count) {
|
||||
if (i > 0 && p[i] != "/") {
|
||||
if (result != "" && result[-1] != "/") {
|
||||
result = result + "/"
|
||||
}
|
||||
}
|
||||
result = result + p[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
matchGlob_(text, pattern) {
|
||||
var ti = 0
|
||||
var pi = 0
|
||||
var starTi = -1
|
||||
var starPi = -1
|
||||
|
||||
while (ti < text.count) {
|
||||
if (pi < pattern.count && (pattern[pi] == "?" || pattern[pi] == text[ti])) {
|
||||
ti = ti + 1
|
||||
pi = pi + 1
|
||||
} else if (pi < pattern.count && pattern[pi] == "*") {
|
||||
starPi = pi
|
||||
starTi = ti
|
||||
pi = pi + 1
|
||||
} else if (starPi >= 0) {
|
||||
pi = starPi + 1
|
||||
starTi = starTi + 1
|
||||
ti = starTi
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
while (pi < pattern.count && pattern[pi] == "*") {
|
||||
pi = pi + 1
|
||||
}
|
||||
|
||||
return pi == pattern.count
|
||||
}
|
||||
}
|
||||
|
||||
class Path is PurePath {
|
||||
construct new(path) {
|
||||
super(path)
|
||||
}
|
||||
|
||||
static cwd { Path.new(Process.cwd) }
|
||||
|
||||
static home { Path.new(Platform.homePath) }
|
||||
|
||||
exists() {
|
||||
var stat
|
||||
Fiber.new {
|
||||
stat = Stat.path(toString)
|
||||
}.try()
|
||||
return stat != null
|
||||
}
|
||||
|
||||
isFile() {
|
||||
var stat
|
||||
Fiber.new {
|
||||
stat = Stat.path(toString)
|
||||
}.try()
|
||||
if (stat == null) return false
|
||||
return stat.isFile
|
||||
}
|
||||
|
||||
isDir() {
|
||||
var stat
|
||||
Fiber.new {
|
||||
stat = Stat.path(toString)
|
||||
}.try()
|
||||
if (stat == null) return false
|
||||
return stat.isDirectory
|
||||
}
|
||||
|
||||
foreign static isSymlink_(path, fiber)
|
||||
|
||||
isSymlink() {
|
||||
return Scheduler.await_ { Path.isSymlink_(toString, Fiber.current) }
|
||||
}
|
||||
|
||||
stat() { Stat.path(toString) }
|
||||
|
||||
foreign static lstat_(path, fiber)
|
||||
|
||||
lstat() {
|
||||
return Scheduler.await_ { Path.lstat_(toString, Fiber.current) }
|
||||
}
|
||||
|
||||
readText() { File.read(toString) }
|
||||
|
||||
readBytes() { File.read(toString) }
|
||||
|
||||
writeText(content) {
|
||||
File.create(toString) {|f|
|
||||
f.writeBytes(content, 0)
|
||||
}
|
||||
}
|
||||
|
||||
writeBytes(content) {
|
||||
File.create(toString) {|f|
|
||||
f.writeBytes(content, 0)
|
||||
}
|
||||
}
|
||||
|
||||
unlink() { File.delete(toString) }
|
||||
|
||||
rmdir() { Directory.delete(toString) }
|
||||
|
||||
mkdir() { mkdir(false) }
|
||||
|
||||
mkdir(parents) {
|
||||
if (parents) {
|
||||
var current = Path.new("")
|
||||
for (part in parts) {
|
||||
current = current / part
|
||||
if (!current.exists()) {
|
||||
Directory.create(current.toString)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Directory.create(toString)
|
||||
}
|
||||
}
|
||||
|
||||
iterdir() {
|
||||
var entries = Directory.list(toString)
|
||||
var result = []
|
||||
for (entry in entries) {
|
||||
result.add(this / entry)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
resolve() {
|
||||
var realPath = File.realPath(toString)
|
||||
return Path.new(realPath)
|
||||
}
|
||||
|
||||
expanduser() {
|
||||
var s = toString
|
||||
if (s.isEmpty) return this
|
||||
if (s[0] == "~") {
|
||||
if (s.count == 1) {
|
||||
return Path.home
|
||||
}
|
||||
if (s[1] == "/") {
|
||||
return Path.home / s[2..-1]
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
foreign static touch_(path, fiber)
|
||||
|
||||
touch() {
|
||||
if (!exists()) {
|
||||
File.create(toString) {|f| }
|
||||
} else {
|
||||
Scheduler.await_ { Path.touch_(toString, Fiber.current) }
|
||||
}
|
||||
}
|
||||
|
||||
foreign static rename_(oldPath, newPath, fiber)
|
||||
|
||||
rename(target) {
|
||||
var targetPath = target is Path ? target.toString : target.toString
|
||||
Scheduler.await_ { Path.rename_(toString, targetPath, Fiber.current) }
|
||||
return Path.new(targetPath)
|
||||
}
|
||||
|
||||
replace(target) { rename(target) }
|
||||
|
||||
foreign static chmod_(path, mode, fiber)
|
||||
|
||||
chmod(mode) {
|
||||
Scheduler.await_ { Path.chmod_(toString, mode, Fiber.current) }
|
||||
}
|
||||
|
||||
foreign static symlinkTo_(link, target, fiber)
|
||||
|
||||
symlinkTo(target) {
|
||||
var targetPath = target is Path ? target.toString : target.toString
|
||||
Scheduler.await_ { Path.symlinkTo_(toString, targetPath, Fiber.current) }
|
||||
}
|
||||
|
||||
foreign static hardlinkTo_(link, target, fiber)
|
||||
|
||||
hardlinkTo(target) {
|
||||
var targetPath = target is Path ? target.toString : target.toString
|
||||
Scheduler.await_ { Path.hardlinkTo_(toString, targetPath, Fiber.current) }
|
||||
}
|
||||
|
||||
foreign static readlink_(path, fiber)
|
||||
|
||||
readlink() {
|
||||
return Scheduler.await_ { Path.readlink_(toString, Fiber.current) }
|
||||
}
|
||||
|
||||
foreign static samefile_(path1, path2, fiber)
|
||||
|
||||
samefile(other) {
|
||||
var otherPath = other is Path ? other.toString : other.toString
|
||||
return Scheduler.await_ { Path.samefile_(toString, otherPath, Fiber.current) }
|
||||
}
|
||||
|
||||
foreign static glob_(basePath, pattern, recursive, fiber)
|
||||
|
||||
glob(pattern) {
|
||||
var results = Scheduler.await_ { Path.glob_(toString, pattern, false, Fiber.current) }
|
||||
var paths = []
|
||||
for (r in results) {
|
||||
paths.add(Path.new(r))
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
rglob(pattern) {
|
||||
var results = Scheduler.await_ { Path.glob_(toString, pattern, true, Fiber.current) }
|
||||
var paths = []
|
||||
for (r in results) {
|
||||
paths.add(Path.new(r))
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
foreign static owner_(path, fiber)
|
||||
|
||||
owner() {
|
||||
return Scheduler.await_ { Path.owner_(toString, Fiber.current) }
|
||||
}
|
||||
|
||||
foreign static group_(path, fiber)
|
||||
|
||||
group() {
|
||||
return Scheduler.await_ { Path.group_(toString, Fiber.current) }
|
||||
}
|
||||
|
||||
walk() { walk(true) }
|
||||
|
||||
walk(topDown) {
|
||||
var results = []
|
||||
walkRecursive_(this, results, topDown)
|
||||
return results
|
||||
}
|
||||
|
||||
walkRecursive_(dir, results, topDown) {
|
||||
var files = []
|
||||
var dirs = []
|
||||
|
||||
for (entry in dir.iterdir()) {
|
||||
if (entry.isDir()) {
|
||||
dirs.add(entry.name)
|
||||
} else {
|
||||
files.add(entry.name)
|
||||
}
|
||||
}
|
||||
|
||||
if (topDown) {
|
||||
results.add([dir, dirs, files])
|
||||
for (d in dirs) {
|
||||
walkRecursive_(dir / d, results, topDown)
|
||||
}
|
||||
} else {
|
||||
for (d in dirs) {
|
||||
walkRecursive_(dir / d, results, topDown)
|
||||
}
|
||||
results.add([dir, dirs, files])
|
||||
}
|
||||
}
|
||||
|
||||
rmtree() {
|
||||
for (entry in walk(false)) {
|
||||
var root = entry[0]
|
||||
var files = entry[2]
|
||||
for (f in files) {
|
||||
(root / f).unlink()
|
||||
}
|
||||
root.rmdir()
|
||||
}
|
||||
}
|
||||
|
||||
copyfile(dest) {
|
||||
var content = readBytes()
|
||||
var destPath = dest is Path ? dest : Path.new(dest)
|
||||
destPath.writeBytes(content)
|
||||
}
|
||||
}
|
||||
520
src/module/pathlib.wren.inc
Normal file
520
src/module/pathlib.wren.inc
Normal file
@ -0,0 +1,520 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/pathlib.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* pathlibModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"io\" for Directory, File, Stat\n"
|
||||
"import \"os\" for Platform, Process\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"class PurePath {\n"
|
||||
" construct new(path) {\n"
|
||||
" if (path is PurePath) {\n"
|
||||
" _path = path.toString\n"
|
||||
" } else if (path is String) {\n"
|
||||
" _path = path\n"
|
||||
" } else {\n"
|
||||
" Fiber.abort(\"Path must be a string or Path.\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" toString { _path }\n"
|
||||
"\n"
|
||||
" parts {\n"
|
||||
" if (_path.isEmpty) return []\n"
|
||||
" var result = []\n"
|
||||
" var current = \"\"\n"
|
||||
" var i = 0\n"
|
||||
"\n"
|
||||
" if (_path[0] == \"/\") {\n"
|
||||
" result.add(\"/\")\n"
|
||||
" i = 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" while (i < _path.count) {\n"
|
||||
" var c = _path[i]\n"
|
||||
" if (c == \"/\") {\n"
|
||||
" if (!current.isEmpty) {\n"
|
||||
" result.add(current)\n"
|
||||
" current = \"\"\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" current = current + c\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (!current.isEmpty) {\n"
|
||||
" result.add(current)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" name {\n"
|
||||
" if (_path.isEmpty) return \"\"\n"
|
||||
" var p = parts\n"
|
||||
" if (p.isEmpty) return \"\"\n"
|
||||
" return p[-1]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" suffix {\n"
|
||||
" var n = name\n"
|
||||
" if (n.isEmpty) return \"\"\n"
|
||||
" var dotIndex = -1\n"
|
||||
" for (i in (n.count - 1)..0) {\n"
|
||||
" if (n[i] == \".\") {\n"
|
||||
" dotIndex = i\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (dotIndex <= 0) return \"\"\n"
|
||||
" return n[dotIndex..-1]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" suffixes {\n"
|
||||
" var n = name\n"
|
||||
" if (n.isEmpty) return []\n"
|
||||
" var result = []\n"
|
||||
" var i = n.count - 1\n"
|
||||
" while (i > 0) {\n"
|
||||
" if (n[i] == \".\") {\n"
|
||||
" result.insert(0, n[i..-1])\n"
|
||||
" n = n[0...i]\n"
|
||||
" i = n.count - 1\n"
|
||||
" } else {\n"
|
||||
" i = i - 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" stem {\n"
|
||||
" var n = name\n"
|
||||
" if (n.isEmpty) return \"\"\n"
|
||||
" var s = suffix\n"
|
||||
" if (s.isEmpty) return n\n"
|
||||
" return n[0...(n.count - s.count)]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" parent {\n"
|
||||
" if (_path.isEmpty) return Path.new(\".\")\n"
|
||||
" var p = parts\n"
|
||||
" if (p.count <= 1) {\n"
|
||||
" if (isAbsolute) return Path.new(\"/\")\n"
|
||||
" return Path.new(\".\")\n"
|
||||
" }\n"
|
||||
" p = p[0...-1]\n"
|
||||
" return Path.new(joinParts_(p))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" parents {\n"
|
||||
" var result = []\n"
|
||||
" var current = this\n"
|
||||
" while (true) {\n"
|
||||
" var p = current.parent\n"
|
||||
" if (p.toString == current.toString) break\n"
|
||||
" result.add(p)\n"
|
||||
" current = p\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" drive {\n"
|
||||
" if (_path.count >= 2 && _path[1] == \":\") {\n"
|
||||
" return _path[0..1]\n"
|
||||
" }\n"
|
||||
" return \"\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" root {\n"
|
||||
" if (_path.isEmpty) return \"\"\n"
|
||||
" if (_path[0] == \"/\") return \"/\"\n"
|
||||
" if (drive != \"\" && _path.count >= 3 && _path[2] == \"/\") return \"/\"\n"
|
||||
" return \"\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" anchor { drive + root }\n"
|
||||
"\n"
|
||||
" isAbsolute { root != \"\" }\n"
|
||||
"\n"
|
||||
" asPosix {\n"
|
||||
" var result = \"\"\n"
|
||||
" for (i in 0..._path.count) {\n"
|
||||
" var c = _path[i]\n"
|
||||
" if (c == \"\\\\\") {\n"
|
||||
" result = result + \"/\"\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" joinpath(other) {\n"
|
||||
" if (other is List) {\n"
|
||||
" var result = this\n"
|
||||
" for (p in other) {\n"
|
||||
" result = result.joinpath(p)\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var otherPath = other is PurePath ? other.toString : other.toString\n"
|
||||
"\n"
|
||||
" if (otherPath.isEmpty) return Path.new(_path)\n"
|
||||
" if (otherPath[0] == \"/\") return Path.new(otherPath)\n"
|
||||
" if (_path.isEmpty) return Path.new(otherPath)\n"
|
||||
" if (_path[-1] == \"/\") return Path.new(_path + otherPath)\n"
|
||||
" return Path.new(_path + \"/\" + otherPath)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /(other) { joinpath(other) }\n"
|
||||
"\n"
|
||||
" withName(newName) {\n"
|
||||
" if (name.isEmpty) Fiber.abort(\"Path has no name.\")\n"
|
||||
" var p = parent\n"
|
||||
" return p / newName\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" withStem(newStem) {\n"
|
||||
" var s = suffix\n"
|
||||
" return withName(newStem + s)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" withSuffix(newSuffix) {\n"
|
||||
" var s = stem\n"
|
||||
" if (s.isEmpty) Fiber.abort(\"Path has no stem.\")\n"
|
||||
" return withName(s + newSuffix)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" relativeTo(base) {\n"
|
||||
" var basePath = base is PurePath ? base : PurePath.new(base)\n"
|
||||
" var myParts = parts\n"
|
||||
" var baseParts = basePath.parts\n"
|
||||
"\n"
|
||||
" if (baseParts.count > myParts.count) {\n"
|
||||
" Fiber.abort(\"'%(this)' is not relative to '%(basePath)'\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" for (i in 0...baseParts.count) {\n"
|
||||
" if (myParts[i] != baseParts[i]) {\n"
|
||||
" Fiber.abort(\"'%(this)' is not relative to '%(basePath)'\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (baseParts.count == myParts.count) return Path.new(\".\")\n"
|
||||
"\n"
|
||||
" var relParts = myParts[baseParts.count..-1]\n"
|
||||
" return Path.new(joinParts_(relParts))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" match(pattern) {\n"
|
||||
" var n = name\n"
|
||||
" return matchGlob_(n, pattern)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" ==(other) {\n"
|
||||
" if (!(other is PurePath)) return false\n"
|
||||
" return _path == other.toString\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" !=(other) { !(this == other) }\n"
|
||||
"\n"
|
||||
" hashCode { _path.hashCode }\n"
|
||||
"\n"
|
||||
" joinParts_(p) {\n"
|
||||
" if (p.isEmpty) return \"\"\n"
|
||||
" if (p.count == 1 && p[0] == \"/\") return \"/\"\n"
|
||||
" var result = \"\"\n"
|
||||
" for (i in 0...p.count) {\n"
|
||||
" if (i > 0 && p[i] != \"/\") {\n"
|
||||
" if (result != \"\" && result[-1] != \"/\") {\n"
|
||||
" result = result + \"/\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" result = result + p[i]\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" matchGlob_(text, pattern) {\n"
|
||||
" var ti = 0\n"
|
||||
" var pi = 0\n"
|
||||
" var starTi = -1\n"
|
||||
" var starPi = -1\n"
|
||||
"\n"
|
||||
" while (ti < text.count) {\n"
|
||||
" if (pi < pattern.count && (pattern[pi] == \"?\" || pattern[pi] == text[ti])) {\n"
|
||||
" ti = ti + 1\n"
|
||||
" pi = pi + 1\n"
|
||||
" } else if (pi < pattern.count && pattern[pi] == \"*\") {\n"
|
||||
" starPi = pi\n"
|
||||
" starTi = ti\n"
|
||||
" pi = pi + 1\n"
|
||||
" } else if (starPi >= 0) {\n"
|
||||
" pi = starPi + 1\n"
|
||||
" starTi = starTi + 1\n"
|
||||
" ti = starTi\n"
|
||||
" } else {\n"
|
||||
" return false\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" while (pi < pattern.count && pattern[pi] == \"*\") {\n"
|
||||
" pi = pi + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return pi == pattern.count\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Path is PurePath {\n"
|
||||
" construct new(path) {\n"
|
||||
" super(path)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static cwd { Path.new(Process.cwd) }\n"
|
||||
"\n"
|
||||
" static home { Path.new(Platform.homePath) }\n"
|
||||
"\n"
|
||||
" exists() {\n"
|
||||
" var stat\n"
|
||||
" Fiber.new {\n"
|
||||
" stat = Stat.path(toString)\n"
|
||||
" }.try()\n"
|
||||
" return stat != null\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isFile() {\n"
|
||||
" var stat\n"
|
||||
" Fiber.new {\n"
|
||||
" stat = Stat.path(toString)\n"
|
||||
" }.try()\n"
|
||||
" if (stat == null) return false\n"
|
||||
" return stat.isFile\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" isDir() {\n"
|
||||
" var stat\n"
|
||||
" Fiber.new {\n"
|
||||
" stat = Stat.path(toString)\n"
|
||||
" }.try()\n"
|
||||
" if (stat == null) return false\n"
|
||||
" return stat.isDirectory\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static isSymlink_(path, fiber)\n"
|
||||
"\n"
|
||||
" isSymlink() {\n"
|
||||
" return Scheduler.await_ { Path.isSymlink_(toString, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" stat() { Stat.path(toString) }\n"
|
||||
"\n"
|
||||
" foreign static lstat_(path, fiber)\n"
|
||||
"\n"
|
||||
" lstat() {\n"
|
||||
" return Scheduler.await_ { Path.lstat_(toString, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" readText() { File.read(toString) }\n"
|
||||
"\n"
|
||||
" readBytes() { File.read(toString) }\n"
|
||||
"\n"
|
||||
" writeText(content) {\n"
|
||||
" File.create(toString) {|f|\n"
|
||||
" f.writeBytes(content, 0)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" writeBytes(content) {\n"
|
||||
" File.create(toString) {|f|\n"
|
||||
" f.writeBytes(content, 0)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" unlink() { File.delete(toString) }\n"
|
||||
"\n"
|
||||
" rmdir() { Directory.delete(toString) }\n"
|
||||
"\n"
|
||||
" mkdir() { mkdir(false) }\n"
|
||||
"\n"
|
||||
" mkdir(parents) {\n"
|
||||
" if (parents) {\n"
|
||||
" var current = Path.new(\"\")\n"
|
||||
" for (part in parts) {\n"
|
||||
" current = current / part\n"
|
||||
" if (!current.exists()) {\n"
|
||||
" Directory.create(current.toString)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" Directory.create(toString)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" iterdir() {\n"
|
||||
" var entries = Directory.list(toString)\n"
|
||||
" var result = []\n"
|
||||
" for (entry in entries) {\n"
|
||||
" result.add(this / entry)\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" resolve() {\n"
|
||||
" var realPath = File.realPath(toString)\n"
|
||||
" return Path.new(realPath)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" expanduser() {\n"
|
||||
" var s = toString\n"
|
||||
" if (s.isEmpty) return this\n"
|
||||
" if (s[0] == \"~\") {\n"
|
||||
" if (s.count == 1) {\n"
|
||||
" return Path.home\n"
|
||||
" }\n"
|
||||
" if (s[1] == \"/\") {\n"
|
||||
" return Path.home / s[2..-1]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return this\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static touch_(path, fiber)\n"
|
||||
"\n"
|
||||
" touch() {\n"
|
||||
" if (!exists()) {\n"
|
||||
" File.create(toString) {|f| }\n"
|
||||
" } else {\n"
|
||||
" Scheduler.await_ { Path.touch_(toString, Fiber.current) }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static rename_(oldPath, newPath, fiber)\n"
|
||||
"\n"
|
||||
" rename(target) {\n"
|
||||
" var targetPath = target is Path ? target.toString : target.toString\n"
|
||||
" Scheduler.await_ { Path.rename_(toString, targetPath, Fiber.current) }\n"
|
||||
" return Path.new(targetPath)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" replace(target) { rename(target) }\n"
|
||||
"\n"
|
||||
" foreign static chmod_(path, mode, fiber)\n"
|
||||
"\n"
|
||||
" chmod(mode) {\n"
|
||||
" Scheduler.await_ { Path.chmod_(toString, mode, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static symlinkTo_(link, target, fiber)\n"
|
||||
"\n"
|
||||
" symlinkTo(target) {\n"
|
||||
" var targetPath = target is Path ? target.toString : target.toString\n"
|
||||
" Scheduler.await_ { Path.symlinkTo_(toString, targetPath, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static hardlinkTo_(link, target, fiber)\n"
|
||||
"\n"
|
||||
" hardlinkTo(target) {\n"
|
||||
" var targetPath = target is Path ? target.toString : target.toString\n"
|
||||
" Scheduler.await_ { Path.hardlinkTo_(toString, targetPath, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static readlink_(path, fiber)\n"
|
||||
"\n"
|
||||
" readlink() {\n"
|
||||
" return Scheduler.await_ { Path.readlink_(toString, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static samefile_(path1, path2, fiber)\n"
|
||||
"\n"
|
||||
" samefile(other) {\n"
|
||||
" var otherPath = other is Path ? other.toString : other.toString\n"
|
||||
" return Scheduler.await_ { Path.samefile_(toString, otherPath, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static glob_(basePath, pattern, recursive, fiber)\n"
|
||||
"\n"
|
||||
" glob(pattern) {\n"
|
||||
" var results = Scheduler.await_ { Path.glob_(toString, pattern, false, Fiber.current) }\n"
|
||||
" var paths = []\n"
|
||||
" for (r in results) {\n"
|
||||
" paths.add(Path.new(r))\n"
|
||||
" }\n"
|
||||
" return paths\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" rglob(pattern) {\n"
|
||||
" var results = Scheduler.await_ { Path.glob_(toString, pattern, true, Fiber.current) }\n"
|
||||
" var paths = []\n"
|
||||
" for (r in results) {\n"
|
||||
" paths.add(Path.new(r))\n"
|
||||
" }\n"
|
||||
" return paths\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static owner_(path, fiber)\n"
|
||||
"\n"
|
||||
" owner() {\n"
|
||||
" return Scheduler.await_ { Path.owner_(toString, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" foreign static group_(path, fiber)\n"
|
||||
"\n"
|
||||
" group() {\n"
|
||||
" return Scheduler.await_ { Path.group_(toString, Fiber.current) }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" walk() { walk(true) }\n"
|
||||
"\n"
|
||||
" walk(topDown) {\n"
|
||||
" var results = []\n"
|
||||
" walkRecursive_(this, results, topDown)\n"
|
||||
" return results\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" walkRecursive_(dir, results, topDown) {\n"
|
||||
" var files = []\n"
|
||||
" var dirs = []\n"
|
||||
"\n"
|
||||
" for (entry in dir.iterdir()) {\n"
|
||||
" if (entry.isDir()) {\n"
|
||||
" dirs.add(entry.name)\n"
|
||||
" } else {\n"
|
||||
" files.add(entry.name)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (topDown) {\n"
|
||||
" results.add([dir, dirs, files])\n"
|
||||
" for (d in dirs) {\n"
|
||||
" walkRecursive_(dir / d, results, topDown)\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" for (d in dirs) {\n"
|
||||
" walkRecursive_(dir / d, results, topDown)\n"
|
||||
" }\n"
|
||||
" results.add([dir, dirs, files])\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" rmtree() {\n"
|
||||
" for (entry in walk(false)) {\n"
|
||||
" var root = entry[0]\n"
|
||||
" var files = entry[2]\n"
|
||||
" for (f in files) {\n"
|
||||
" (root / f).unlink()\n"
|
||||
" }\n"
|
||||
" root.rmdir()\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" copyfile(dest) {\n"
|
||||
" var content = readBytes()\n"
|
||||
" var destPath = dest is Path ? dest : Path.new(dest)\n"
|
||||
" destPath.writeBytes(content)\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
450
src/module/strutil.c
Normal file
450
src/module/strutil.c
Normal file
@ -0,0 +1,450 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "strutil.h"
|
||||
#include "wren.h"
|
||||
|
||||
void strutilToLower(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
char* result = (char*)malloc(len + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
result[i] = c + 32;
|
||||
} else {
|
||||
result[i] = c;
|
||||
}
|
||||
}
|
||||
result[len] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilToUpper(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
char* result = (char*)malloc(len + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
result[i] = c - 32;
|
||||
} else {
|
||||
result[i] = c;
|
||||
}
|
||||
}
|
||||
result[len] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static const char hexCharsLower[] = "0123456789abcdef";
|
||||
static const char hexCharsUpper[] = "0123456789ABCDEF";
|
||||
|
||||
void strutilHexEncode(WrenVM* vm) {
|
||||
int length = 0;
|
||||
const char* bytes = wrenGetSlotBytes(vm, 1, &length);
|
||||
|
||||
if (bytes == NULL || length == 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(length * 2 + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
unsigned char b = (unsigned char)bytes[i];
|
||||
result[i * 2] = hexCharsLower[(b >> 4) & 0x0F];
|
||||
result[i * 2 + 1] = hexCharsLower[b & 0x0F];
|
||||
}
|
||||
result[length * 2] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static int hexValue(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void strutilHexDecode(WrenVM* vm) {
|
||||
const char* hex = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (hex == NULL) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(hex);
|
||||
if (len == 0 || len % 2 != 0) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t outLen = len / 2;
|
||||
char* result = (char*)malloc(outLen);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < outLen; i++) {
|
||||
int hi = hexValue(hex[i * 2]);
|
||||
int lo = hexValue(hex[i * 2 + 1]);
|
||||
if (hi < 0 || lo < 0) {
|
||||
free(result);
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
result[i] = (char)((hi << 4) | lo);
|
||||
}
|
||||
|
||||
wrenSetSlotBytes(vm, 0, result, outLen);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilRepeat(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
int count = (int)wrenGetSlotDouble(vm, 2);
|
||||
|
||||
if (str == NULL || count <= 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (len == 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t totalLen = len * count;
|
||||
char* result = (char*)malloc(totalLen + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
memcpy(result + i * len, str, len);
|
||||
}
|
||||
result[totalLen] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilPadLeft(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
int width = (int)wrenGetSlotDouble(vm, 2);
|
||||
const char* padChar = wrenGetSlotString(vm, 3);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (width <= 0 || (size_t)width <= len) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
char pad = (padChar != NULL && padChar[0] != '\0') ? padChar[0] : ' ';
|
||||
size_t padLen = width - len;
|
||||
|
||||
char* result = (char*)malloc(width + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(result, pad, padLen);
|
||||
memcpy(result + padLen, str, len);
|
||||
result[width] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilPadRight(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
int width = (int)wrenGetSlotDouble(vm, 2);
|
||||
const char* padChar = wrenGetSlotString(vm, 3);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (width <= 0 || (size_t)width <= len) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
char pad = (padChar != NULL && padChar[0] != '\0') ? padChar[0] : ' ';
|
||||
size_t padLen = width - len;
|
||||
|
||||
char* result = (char*)malloc(width + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(result, str, len);
|
||||
memset(result + len, pad, padLen);
|
||||
result[width] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilEscapeHtml(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
size_t outLen = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
if (c == '&') outLen += 5;
|
||||
else if (c == '<') outLen += 4;
|
||||
else if (c == '>') outLen += 4;
|
||||
else if (c == '"') outLen += 6;
|
||||
else if (c == '\'') outLen += 5;
|
||||
else outLen += 1;
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(outLen + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
if (c == '&') {
|
||||
memcpy(result + j, "&", 5);
|
||||
j += 5;
|
||||
} else if (c == '<') {
|
||||
memcpy(result + j, "<", 4);
|
||||
j += 4;
|
||||
} else if (c == '>') {
|
||||
memcpy(result + j, ">", 4);
|
||||
j += 4;
|
||||
} else if (c == '"') {
|
||||
memcpy(result + j, """, 6);
|
||||
j += 6;
|
||||
} else if (c == '\'') {
|
||||
memcpy(result + j, "'", 5);
|
||||
j += 5;
|
||||
} else {
|
||||
result[j++] = c;
|
||||
}
|
||||
}
|
||||
result[j] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilEscapeJson(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "\"\"");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
size_t outLen = 2;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (c == '"' || c == '\\' || c == '\b' || c == '\f' ||
|
||||
c == '\n' || c == '\r' || c == '\t') {
|
||||
outLen += 2;
|
||||
} else if (c < 32) {
|
||||
outLen += 6;
|
||||
} else {
|
||||
outLen += 1;
|
||||
}
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(outLen + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "\"\"");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
result[j++] = '"';
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (c == '"') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = '"';
|
||||
} else if (c == '\\') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = '\\';
|
||||
} else if (c == '\b') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'b';
|
||||
} else if (c == '\f') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'f';
|
||||
} else if (c == '\n') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'n';
|
||||
} else if (c == '\r') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'r';
|
||||
} else if (c == '\t') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 't';
|
||||
} else if (c < 32) {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'u';
|
||||
result[j++] = '0';
|
||||
result[j++] = '0';
|
||||
result[j++] = hexCharsLower[(c >> 4) & 0x0F];
|
||||
result[j++] = hexCharsLower[c & 0x0F];
|
||||
} else {
|
||||
result[j++] = c;
|
||||
}
|
||||
}
|
||||
result[j++] = '"';
|
||||
result[j] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static int isUnreserved(unsigned char c) {
|
||||
if (c >= 'A' && c <= 'Z') return 1;
|
||||
if (c >= 'a' && c <= 'z') return 1;
|
||||
if (c >= '0' && c <= '9') return 1;
|
||||
if (c == '-' || c == '_' || c == '.' || c == '~') return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void strutilUrlEncode(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
size_t outLen = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (isUnreserved(c)) {
|
||||
outLen += 1;
|
||||
} else if (c == ' ') {
|
||||
outLen += 1;
|
||||
} else {
|
||||
outLen += 3;
|
||||
}
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(outLen + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (isUnreserved(c)) {
|
||||
result[j++] = c;
|
||||
} else if (c == ' ') {
|
||||
result[j++] = '+';
|
||||
} else {
|
||||
result[j++] = '%';
|
||||
result[j++] = hexCharsUpper[(c >> 4) & 0x0F];
|
||||
result[j++] = hexCharsUpper[c & 0x0F];
|
||||
}
|
||||
}
|
||||
result[j] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilUrlDecode(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
char* result = (char*)malloc(len + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
if (c == '+') {
|
||||
result[j++] = ' ';
|
||||
} else if (c == '%' && i + 2 < len) {
|
||||
int hi = hexValue(str[i + 1]);
|
||||
int lo = hexValue(str[i + 2]);
|
||||
if (hi >= 0 && lo >= 0) {
|
||||
result[j++] = (char)((hi << 4) | lo);
|
||||
i += 2;
|
||||
} else {
|
||||
result[j++] = c;
|
||||
}
|
||||
} else {
|
||||
result[j++] = c;
|
||||
}
|
||||
}
|
||||
result[j] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
20
src/module/strutil.h
Normal file
20
src/module/strutil.h
Normal file
@ -0,0 +1,20 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#ifndef wren_strutil_h
|
||||
#define wren_strutil_h
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void strutilToLower(WrenVM* vm);
|
||||
void strutilToUpper(WrenVM* vm);
|
||||
void strutilHexEncode(WrenVM* vm);
|
||||
void strutilHexDecode(WrenVM* vm);
|
||||
void strutilRepeat(WrenVM* vm);
|
||||
void strutilPadLeft(WrenVM* vm);
|
||||
void strutilPadRight(WrenVM* vm);
|
||||
void strutilEscapeHtml(WrenVM* vm);
|
||||
void strutilEscapeJson(WrenVM* vm);
|
||||
void strutilUrlEncode(WrenVM* vm);
|
||||
void strutilUrlDecode(WrenVM* vm);
|
||||
|
||||
#endif
|
||||
15
src/module/strutil.wren
vendored
Normal file
15
src/module/strutil.wren
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
class Str {
|
||||
foreign static toLower(str)
|
||||
foreign static toUpper(str)
|
||||
foreign static hexEncode(bytes)
|
||||
foreign static hexDecode(hex)
|
||||
foreign static repeat(str, count)
|
||||
foreign static padLeft(str, width, char)
|
||||
foreign static padRight(str, width, char)
|
||||
foreign static escapeHtml(str)
|
||||
foreign static escapeJson(str)
|
||||
foreign static urlEncode(str)
|
||||
foreign static urlDecode(str)
|
||||
}
|
||||
19
src/module/strutil.wren.inc
Normal file
19
src/module/strutil.wren.inc
Normal file
@ -0,0 +1,19 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `src/module/strutil.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* strutilModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"class Str {\n"
|
||||
" foreign static toLower(str)\n"
|
||||
" foreign static toUpper(str)\n"
|
||||
" foreign static hexEncode(bytes)\n"
|
||||
" foreign static hexDecode(hex)\n"
|
||||
" foreign static repeat(str, count)\n"
|
||||
" foreign static padLeft(str, width, char)\n"
|
||||
" foreign static padRight(str, width, char)\n"
|
||||
" foreign static escapeHtml(str)\n"
|
||||
" foreign static escapeJson(str)\n"
|
||||
" foreign static urlEncode(str)\n"
|
||||
" foreign static urlDecode(str)\n"
|
||||
"}\n";
|
||||
17
src/module/uuid.wren
vendored
17
src/module/uuid.wren
vendored
@ -1,22 +1,11 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "crypto" for Crypto
|
||||
import "strutil" for Str
|
||||
import "bytes" for Bytes
|
||||
|
||||
class Uuid {
|
||||
static hexDigit_(n) {
|
||||
if (n < 10) return String.fromCodePoint(48 + n)
|
||||
return String.fromCodePoint(97 + n - 10)
|
||||
}
|
||||
|
||||
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 toHex_(bytes) { Str.hexEncode(Bytes.fromList(bytes)) }
|
||||
|
||||
static v4() {
|
||||
var bytes = Crypto.randomBytes(16)
|
||||
|
||||
@ -5,22 +5,11 @@ static const char* uuidModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"crypto\" for Crypto\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"import \"bytes\" for Bytes\n"
|
||||
"\n"
|
||||
"class Uuid {\n"
|
||||
" static hexDigit_(n) {\n"
|
||||
" if (n < 10) return String.fromCodePoint(48 + n)\n"
|
||||
" return String.fromCodePoint(97 + n - 10)\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"
|
||||
" static toHex_(bytes) { Str.hexEncode(Bytes.fromList(bytes)) }\n"
|
||||
"\n"
|
||||
" static v4() {\n"
|
||||
" var bytes = Crypto.randomBytes(16)\n"
|
||||
|
||||
14
src/module/web.wren
vendored
14
src/module/web.wren
vendored
@ -9,6 +9,7 @@ import "html" for Html
|
||||
import "uuid" for Uuid
|
||||
import "datetime" for DateTime
|
||||
import "io" for File
|
||||
import "strutil" for Str
|
||||
|
||||
class Request {
|
||||
construct new_(method, path, query, headers, body, params, socket) {
|
||||
@ -77,18 +78,7 @@ class Request {
|
||||
session { _session }
|
||||
session=(s) { _session = s }
|
||||
|
||||
toLower_(str) {
|
||||
var result = ""
|
||||
for (c in str) {
|
||||
var cp = c.codePoints.toList[0]
|
||||
if (cp >= 65 && cp <= 90) {
|
||||
result = result + String.fromCodePoint(cp + 32)
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
toLower_(str) { Str.toLower(str) }
|
||||
}
|
||||
|
||||
class Response {
|
||||
|
||||
@ -13,6 +13,7 @@ static const char* webModuleSource =
|
||||
"import \"uuid\" for Uuid\n"
|
||||
"import \"datetime\" for DateTime\n"
|
||||
"import \"io\" for File\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class Request {\n"
|
||||
" construct new_(method, path, query, headers, body, params, socket) {\n"
|
||||
@ -81,18 +82,7 @@ static const char* webModuleSource =
|
||||
" session { _session }\n"
|
||||
" session=(s) { _session = s }\n"
|
||||
"\n"
|
||||
" toLower_(str) {\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in str) {\n"
|
||||
" var cp = c.codePoints.toList[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"
|
||||
" toLower_(str) { Str.toLower(str) }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class Response {\n"
|
||||
|
||||
14
src/module/websocket.wren
vendored
14
src/module/websocket.wren
vendored
@ -6,6 +6,7 @@ import "dns" for Dns
|
||||
import "crypto" for Crypto, Hash
|
||||
import "base64" for Base64
|
||||
import "bytes" for Bytes
|
||||
import "strutil" for Str
|
||||
|
||||
class WebSocketMessage {
|
||||
construct new_(opcode, payload, fin) {
|
||||
@ -205,18 +206,7 @@ class WebSocket {
|
||||
return lines
|
||||
}
|
||||
|
||||
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) }
|
||||
|
||||
static computeAcceptKey_(key) {
|
||||
var magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
@ -10,6 +10,7 @@ static const char* websocketModuleSource =
|
||||
"import \"crypto\" for Crypto, Hash\n"
|
||||
"import \"base64\" for Base64\n"
|
||||
"import \"bytes\" for Bytes\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class WebSocketMessage {\n"
|
||||
" construct new_(opcode, payload, fin) {\n"
|
||||
@ -209,18 +210,7 @@ static const char* websocketModuleSource =
|
||||
" return lines\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"
|
||||
" static computeAcceptKey_(key) {\n"
|
||||
" var magic = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n"
|
||||
|
||||
24
test/dataset/boolean_fields.wren
vendored
Normal file
24
test/dataset/boolean_fields.wren
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var users = ds["users"]
|
||||
|
||||
var active = users.insert({"name": "Alice", "active": true, "admin": false})
|
||||
System.print(active["active"]) // expect: true
|
||||
System.print(active["admin"]) // expect: false
|
||||
|
||||
var inactive = users.insert({"name": "Bob", "active": false, "admin": true})
|
||||
System.print(inactive["active"]) // expect: false
|
||||
System.print(inactive["admin"]) // expect: true
|
||||
|
||||
var found = users.findOne({"name": "Alice"})
|
||||
System.print(found["active"]) // expect: 1
|
||||
System.print(found["admin"]) // expect: 0
|
||||
|
||||
var activeUsers = users.find({"active": true})
|
||||
System.print(activeUsers.count) // expect: 1
|
||||
System.print(activeUsers[0]["name"]) // expect: Alice
|
||||
|
||||
ds.close()
|
||||
23
test/dataset/custom_created_at.wren
vendored
Normal file
23
test/dataset/custom_created_at.wren
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var events = ds["events"]
|
||||
|
||||
var event = events.insert({
|
||||
"name": "Conference",
|
||||
"created_at": "2024-01-15T10:00:00"
|
||||
})
|
||||
|
||||
System.print(event["created_at"]) // expect: 2024-01-15T10:00:00
|
||||
System.print(event["name"]) // expect: Conference
|
||||
|
||||
var found = events.findOne({"name": "Conference"})
|
||||
System.print(found["created_at"]) // expect: 2024-01-15T10:00:00
|
||||
|
||||
var auto = events.insert({"name": "Meeting"})
|
||||
System.print(auto["created_at"] != null) // expect: true
|
||||
System.print(auto["created_at"] != "2024-01-15T10:00:00") // expect: true
|
||||
|
||||
ds.close()
|
||||
24
test/dataset/custom_uid.wren
vendored
Normal file
24
test/dataset/custom_uid.wren
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var users = ds["users"]
|
||||
|
||||
var user = users.insert({
|
||||
"uid": "my-custom-id-123",
|
||||
"name": "Alice"
|
||||
})
|
||||
|
||||
System.print(user["uid"]) // expect: my-custom-id-123
|
||||
System.print(user["name"]) // expect: Alice
|
||||
|
||||
var found = users.findOne({"uid": "my-custom-id-123"})
|
||||
System.print(found != null) // expect: true
|
||||
System.print(found["name"]) // expect: Alice
|
||||
|
||||
users.update({"uid": "my-custom-id-123", "name": "Alice Smith"})
|
||||
var updated = users.findOne({"uid": "my-custom-id-123"})
|
||||
System.print(updated["name"]) // expect: Alice Smith
|
||||
|
||||
ds.close()
|
||||
38
test/dataset/edge_cases.wren
vendored
Normal file
38
test/dataset/edge_cases.wren
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var users = ds["users"]
|
||||
|
||||
var emptyAll = users.all()
|
||||
System.print(emptyAll.count) // expect: 0
|
||||
|
||||
System.print(users.count()) // expect: 0
|
||||
|
||||
var user = users.insert({"name": "Alice", "score": 100})
|
||||
|
||||
var notFound = users.findOne({"name": "Nobody"})
|
||||
System.print(notFound == null) // expect: true
|
||||
|
||||
var emptyFind = users.find({"name": "Nobody"})
|
||||
System.print(emptyFind.count) // expect: 0
|
||||
System.print(emptyFind is List) // expect: true
|
||||
var uid = user["uid"]
|
||||
|
||||
var changes1 = users.update({"uid": uid, "score": 200})
|
||||
System.print(changes1) // expect: 1
|
||||
|
||||
var changes2 = users.update({"uid": uid, "score": 300, "level": 5})
|
||||
System.print(changes2) // expect: 1
|
||||
|
||||
var updated = users.findOne({"uid": uid})
|
||||
System.print(updated["score"]) // expect: 300
|
||||
System.print(updated["level"]) // expect: 5
|
||||
|
||||
var fakeChanges = users.update({"uid": "nonexistent-uid", "score": 999})
|
||||
System.print(fakeChanges) // expect: 0
|
||||
|
||||
System.print(users.delete("nonexistent-uid")) // expect: false
|
||||
|
||||
ds.close()
|
||||
33
test/dataset/file_database.wren
vendored
Normal file
33
test/dataset/file_database.wren
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
import "io" for File
|
||||
|
||||
var dbPath = "/tmp/test_dataset.db"
|
||||
|
||||
if (File.exists(dbPath)) {
|
||||
File.delete(dbPath)
|
||||
}
|
||||
|
||||
var ds = Dataset.open(dbPath)
|
||||
var users = ds["users"]
|
||||
|
||||
var user = users.insert({"name": "Alice", "score": 100})
|
||||
System.print(user["name"]) // expect: Alice
|
||||
System.print(users.count()) // expect: 1
|
||||
|
||||
ds.close()
|
||||
|
||||
System.print(File.exists(dbPath)) // expect: true
|
||||
|
||||
var ds2 = Dataset.open(dbPath)
|
||||
var users2 = ds2["users"]
|
||||
|
||||
System.print(users2.count()) // expect: 1
|
||||
var found = users2.findOne({"name": "Alice"})
|
||||
System.print(found["score"]) // expect: 100
|
||||
|
||||
ds2.close()
|
||||
|
||||
File.delete(dbPath)
|
||||
System.print(File.exists(dbPath)) // expect: false
|
||||
29
test/dataset/hard_delete.wren
vendored
Normal file
29
test/dataset/hard_delete.wren
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var users = ds["users"]
|
||||
|
||||
var user = users.insert({"name": "Alice"})
|
||||
var uid = user["uid"]
|
||||
|
||||
System.print(users.count()) // expect: 1
|
||||
|
||||
users.delete(uid)
|
||||
System.print(users.count()) // expect: 0
|
||||
|
||||
var softDeleted = ds.db.query("SELECT * FROM users WHERE uid = ?", [uid])
|
||||
System.print(softDeleted.count) // expect: 1
|
||||
System.print(softDeleted[0]["deleted_at"] != null) // expect: true
|
||||
|
||||
var user2 = users.insert({"name": "Bob"})
|
||||
var uid2 = user2["uid"]
|
||||
|
||||
System.print(users.hardDelete(uid2)) // expect: true
|
||||
var hardDeleted = ds.db.query("SELECT * FROM users WHERE uid = ?", [uid2])
|
||||
System.print(hardDeleted.count) // expect: 0
|
||||
|
||||
System.print(users.hardDelete("nonexistent")) // expect: false
|
||||
|
||||
ds.close()
|
||||
39
test/dataset/json_fields.wren
vendored
Normal file
39
test/dataset/json_fields.wren
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var posts = ds["posts"]
|
||||
|
||||
var post = posts.insert({
|
||||
"title": "Hello",
|
||||
"tags": ["wren", "programming"],
|
||||
"metadata": {"views": 100, "featured": true}
|
||||
})
|
||||
|
||||
System.print(post["title"]) // expect: Hello
|
||||
System.print(post["tags"] is List) // expect: true
|
||||
System.print(post["tags"].count) // expect: 2
|
||||
System.print(post["tags"][0]) // expect: wren
|
||||
System.print(post["metadata"] is Map) // expect: true
|
||||
System.print(post["metadata"]["views"]) // expect: 100
|
||||
System.print(post["metadata"]["featured"]) // expect: true
|
||||
|
||||
var retrieved = posts.findOne({"title": "Hello"})
|
||||
System.print(retrieved["tags"] is List) // expect: true
|
||||
System.print(retrieved["tags"][1]) // expect: programming
|
||||
System.print(retrieved["metadata"]["views"]) // expect: 100
|
||||
|
||||
var nested = posts.insert({
|
||||
"title": "Nested",
|
||||
"data": {
|
||||
"level1": {
|
||||
"level2": ["a", "b", "c"]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var fetched = posts.findOne({"title": "Nested"})
|
||||
System.print(fetched["data"]["level1"]["level2"][0]) // expect: a
|
||||
|
||||
ds.close()
|
||||
33
test/dataset/multiple_tables.wren
vendored
Normal file
33
test/dataset/multiple_tables.wren
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
|
||||
var users = ds["users"]
|
||||
var posts = ds["posts"]
|
||||
var comments = ds["comments"]
|
||||
|
||||
var user = users.insert({"name": "Alice"})
|
||||
var userUid = user["uid"]
|
||||
|
||||
var post = posts.insert({"title": "Hello", "author_uid": userUid})
|
||||
var postUid = post["uid"]
|
||||
|
||||
comments.insert({"text": "Nice!", "post_uid": postUid, "author_uid": userUid})
|
||||
comments.insert({"text": "Great!", "post_uid": postUid, "author_uid": userUid})
|
||||
|
||||
System.print(users.count()) // expect: 1
|
||||
System.print(posts.count()) // expect: 1
|
||||
System.print(comments.count()) // expect: 2
|
||||
|
||||
var tables = ds.tables
|
||||
System.print(tables.count) // expect: 3
|
||||
System.print(tables.contains("users")) // expect: true
|
||||
System.print(tables.contains("posts")) // expect: true
|
||||
System.print(tables.contains("comments")) // expect: true
|
||||
|
||||
var postComments = comments.find({"post_uid": postUid})
|
||||
System.print(postComments.count) // expect: 2
|
||||
|
||||
ds.close()
|
||||
22
test/dataset/null_operator.wren
vendored
Normal file
22
test/dataset/null_operator.wren
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var users = ds["users"]
|
||||
|
||||
users.insert({"name": "Alice", "email": "alice@example.com"})
|
||||
users.insert({"name": "Bob", "email": null})
|
||||
users.insert({"name": "Charlie"})
|
||||
|
||||
var withEmail = users.find({"email__null": false})
|
||||
System.print(withEmail.count) // expect: 1
|
||||
System.print(withEmail[0]["name"]) // expect: Alice
|
||||
|
||||
var withoutEmail = users.find({"email__null": true})
|
||||
System.print(withoutEmail.count) // expect: 2
|
||||
|
||||
var all = users.all()
|
||||
System.print(all.count) // expect: 3
|
||||
|
||||
ds.close()
|
||||
27
test/dataset/number_types.wren
vendored
Normal file
27
test/dataset/number_types.wren
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var products = ds["products"]
|
||||
|
||||
var p1 = products.insert({"name": "Widget", "price": 19.99, "quantity": 100})
|
||||
System.print(p1["price"]) // expect: 19.99
|
||||
System.print(p1["quantity"]) // expect: 100
|
||||
|
||||
var p2 = products.insert({"name": "Gadget", "price": 0.5, "quantity": 0})
|
||||
System.print(p2["price"]) // expect: 0.5
|
||||
System.print(p2["quantity"]) // expect: 0
|
||||
|
||||
var found = products.findOne({"name": "Widget"})
|
||||
System.print(found["price"]) // expect: 19.99
|
||||
System.print(found["quantity"]) // expect: 100
|
||||
|
||||
var cheap = products.find({"price__lt": 1})
|
||||
System.print(cheap.count) // expect: 1
|
||||
System.print(cheap[0]["name"]) // expect: Gadget
|
||||
|
||||
var expensive = products.find({"price__gte": 10})
|
||||
System.print(expensive.count) // expect: 1
|
||||
|
||||
ds.close()
|
||||
36
test/dataset/query_operators.wren
vendored
Normal file
36
test/dataset/query_operators.wren
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
var products = ds["products"]
|
||||
|
||||
products.insert({"name": "Apple", "price": 10, "stock": 100})
|
||||
products.insert({"name": "Banana", "price": 5, "stock": 50})
|
||||
products.insert({"name": "Cherry", "price": 20, "stock": 0})
|
||||
products.insert({"name": "Date", "price": 15, "stock": 25})
|
||||
|
||||
var gte = products.find({"price__gte": 15})
|
||||
System.print(gte.count) // expect: 2
|
||||
|
||||
var lte = products.find({"price__lte": 10})
|
||||
System.print(lte.count) // expect: 2
|
||||
|
||||
var ne = products.find({"name__ne": "Apple"})
|
||||
System.print(ne.count) // expect: 3
|
||||
|
||||
var like = products.find({"name__like": "A\%"})
|
||||
System.print(like.count) // expect: 1
|
||||
System.print(like[0]["name"]) // expect: Apple
|
||||
|
||||
var inList = products.find({"name__in": ["Apple", "Banana"]})
|
||||
System.print(inList.count) // expect: 2
|
||||
|
||||
var zeroStock = products.find({"stock": 0})
|
||||
System.print(zeroStock.count) // expect: 1
|
||||
System.print(zeroStock[0]["name"]) // expect: Cherry
|
||||
|
||||
var combined = products.find({"price__gte": 10, "stock__gt": 0})
|
||||
System.print(combined.count) // expect: 2
|
||||
|
||||
ds.close()
|
||||
16
test/dataset/table_name.wren
vendored
Normal file
16
test/dataset/table_name.wren
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
|
||||
var users = ds["users"]
|
||||
System.print(users.name) // expect: users
|
||||
|
||||
var products = ds["products"]
|
||||
System.print(products.name) // expect: products
|
||||
|
||||
var orders = ds["my_orders"]
|
||||
System.print(orders.name) // expect: my_orders
|
||||
|
||||
ds.close()
|
||||
20
test/dataset/tables_property.wren
vendored
Normal file
20
test/dataset/tables_property.wren
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "dataset" for Dataset
|
||||
|
||||
var ds = Dataset.memory()
|
||||
|
||||
System.print(ds.tables.count) // expect: 0
|
||||
|
||||
ds["users"].insert({"name": "Alice"})
|
||||
System.print(ds.tables.count) // expect: 1
|
||||
System.print(ds.tables.contains("users")) // expect: true
|
||||
|
||||
ds["products"].insert({"name": "Widget"})
|
||||
System.print(ds.tables.count) // expect: 2
|
||||
System.print(ds.tables.contains("products")) // expect: true
|
||||
|
||||
ds["orders"].insert({"total": 100})
|
||||
System.print(ds.tables.count) // expect: 3
|
||||
|
||||
ds.close()
|
||||
22
test/markdown/blocks_advanced.wren
vendored
Normal file
22
test/markdown/blocks_advanced.wren
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
var multiLine = Markdown.toHtml("```\nline 1\nline 2\nline 3\n```")
|
||||
System.print(multiLine.contains("line 1")) // expect: true
|
||||
System.print(multiLine.contains("line 2")) // expect: true
|
||||
System.print(multiLine.contains("line 3")) // expect: true
|
||||
System.print(multiLine.contains("<pre><code>")) // expect: true
|
||||
|
||||
var multiQuote = Markdown.toHtml("> line 1\n> line 2")
|
||||
System.print(multiQuote.contains("<blockquote>")) // expect: true
|
||||
System.print(multiQuote.contains("line 1")) // expect: true
|
||||
System.print(multiQuote.contains("line 2")) // expect: true
|
||||
|
||||
var withLang = Markdown.toHtml("```wren\nSystem.print(\"hello\")\n```")
|
||||
System.print(withLang.contains("<pre><code>")) // expect: true
|
||||
System.print(withLang.contains("System.print")) // expect: true
|
||||
|
||||
var emptyBlock = Markdown.toHtml("```\n\n```")
|
||||
System.print(emptyBlock.contains("<pre><code>")) // expect: true
|
||||
System.print(emptyBlock.contains("</code></pre>")) // expect: true
|
||||
24
test/markdown/formatting_advanced.wren
vendored
Normal file
24
test/markdown/formatting_advanced.wren
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
System.print(Markdown.toHtml("__bold underscore__")) // expect: <p><strong>bold underscore</strong></p>
|
||||
|
||||
var mixed = Markdown.toHtml("This is **bold** and *italic* text")
|
||||
System.print(mixed.contains("<strong>bold</strong>")) // expect: true
|
||||
System.print(mixed.contains("<em>italic</em>")) // expect: true
|
||||
|
||||
var code = Markdown.toHtml("Use `print()` function")
|
||||
System.print(code.contains("<code>print()</code>")) // expect: true
|
||||
|
||||
var multi = Markdown.toHtml("**bold** and `code` and *italic*")
|
||||
System.print(multi.contains("<strong>bold</strong>")) // expect: true
|
||||
System.print(multi.contains("<code>code</code>")) // expect: true
|
||||
System.print(multi.contains("<em>italic</em>")) // expect: true
|
||||
|
||||
var unclosed = Markdown.toHtml("This has *unclosed italic")
|
||||
System.print(unclosed.contains("<p>")) // expect: true
|
||||
|
||||
var empty = Markdown.toHtml("**bold****more**")
|
||||
System.print(empty.contains("<strong>bold</strong>")) // expect: true
|
||||
System.print(empty.contains("<strong>more</strong>")) // expect: true
|
||||
49
test/markdown/from_html.wren
vendored
Normal file
49
test/markdown/from_html.wren
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
System.print(Markdown.fromHtml("<h1>Title</h1>")) // expect: # Title
|
||||
System.print(Markdown.fromHtml("<h2>Subtitle</h2>")) // expect: ## Subtitle
|
||||
System.print(Markdown.fromHtml("<h3>Section</h3>")) // expect: ### Section
|
||||
System.print(Markdown.fromHtml("<h4>Subsection</h4>")) // expect: #### Subsection
|
||||
System.print(Markdown.fromHtml("<h5>Minor</h5>")) // expect: ##### Minor
|
||||
System.print(Markdown.fromHtml("<h6>Smallest</h6>")) // expect: ###### Smallest
|
||||
|
||||
System.print(Markdown.fromHtml("<strong>bold</strong>")) // expect: **bold**
|
||||
System.print(Markdown.fromHtml("<b>bold</b>")) // expect: **bold**
|
||||
|
||||
System.print(Markdown.fromHtml("<em>italic</em>")) // expect: *italic*
|
||||
System.print(Markdown.fromHtml("<i>italic</i>")) // expect: *italic*
|
||||
|
||||
System.print(Markdown.fromHtml("<code>code</code>")) // expect: `code`
|
||||
|
||||
System.print(Markdown.fromHtml("<del>deleted</del>")) // expect: ~~deleted~~
|
||||
System.print(Markdown.fromHtml("<s>strike</s>")) // expect: ~~strike~~
|
||||
|
||||
System.print(Markdown.fromHtml("<a href=\"https://example.com\">Example</a>")) // expect: [Example](https://example.com)
|
||||
|
||||
System.print(Markdown.fromHtml("<img src=\"image.png\" alt=\"My Image\">")) // expect: 
|
||||
|
||||
var ul = Markdown.fromHtml("<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>")
|
||||
System.print(ul.contains("- Item 1")) // expect: true
|
||||
System.print(ul.contains("- Item 2")) // expect: true
|
||||
System.print(ul.contains("- Item 3")) // expect: true
|
||||
|
||||
var ol = Markdown.fromHtml("<ol><li>First</li><li>Second</li><li>Third</li></ol>")
|
||||
System.print(ol.contains("1. First")) // expect: true
|
||||
System.print(ol.contains("2. Second")) // expect: true
|
||||
System.print(ol.contains("3. Third")) // expect: true
|
||||
|
||||
var bq = Markdown.fromHtml("<blockquote>Quote text</blockquote>")
|
||||
System.print(bq.contains("> Quote text")) // expect: true
|
||||
|
||||
System.print(Markdown.fromHtml("<hr>").contains("---")) // expect: true
|
||||
System.print(Markdown.fromHtml("<hr/>").contains("---")) // expect: true
|
||||
|
||||
var p = Markdown.fromHtml("<p>First paragraph</p><p>Second paragraph</p>")
|
||||
System.print(p.contains("First paragraph")) // expect: true
|
||||
System.print(p.contains("Second paragraph")) // expect: true
|
||||
|
||||
var br = Markdown.fromHtml("Line one<br>Line two")
|
||||
System.print(br.contains("Line one")) // expect: true
|
||||
System.print(br.contains("Line two")) // expect: true
|
||||
78
test/markdown/from_html_advanced.wren
vendored
Normal file
78
test/markdown/from_html_advanced.wren
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
var nested = Markdown.fromHtml("<p><strong>Bold <em>and italic</em> text</strong></p>")
|
||||
System.print(nested.contains("**Bold *and italic* text**")) // expect: true
|
||||
|
||||
var mixed = Markdown.fromHtml("<p>Normal <strong>bold</strong> and <em>italic</em> and <code>code</code></p>")
|
||||
System.print(mixed.contains("Normal **bold** and *italic* and `code`")) // expect: true
|
||||
|
||||
var codeBlock = Markdown.fromHtml("<pre><code>function test() {\n return true;\n}</code></pre>")
|
||||
System.print(codeBlock.contains("```")) // expect: true
|
||||
System.print(codeBlock.contains("function test()")) // expect: true
|
||||
|
||||
var script = Markdown.fromHtml("<p>Hello</p><script>alert('xss');</script><p>World</p>")
|
||||
System.print(script.contains("Hello")) // expect: true
|
||||
System.print(script.contains("World")) // expect: true
|
||||
System.print(script.contains("alert")) // expect: false
|
||||
System.print(script.contains("script")) // expect: false
|
||||
|
||||
var style = Markdown.fromHtml("<p>Content</p><style>.red { color: red; }</style>")
|
||||
System.print(style.contains("Content")) // expect: true
|
||||
System.print(style.contains("color")) // expect: false
|
||||
|
||||
var entities = Markdown.fromHtml("<p>5 > 3 && 3 < 5</p>")
|
||||
System.print(entities.contains(">")) // expect: true
|
||||
System.print(entities.contains("<")) // expect: true
|
||||
System.print(entities.contains("&")) // expect: true
|
||||
|
||||
var doc = """
|
||||
<html>
|
||||
<head><title>Test</title></head>
|
||||
<body>
|
||||
<h1>Main Title</h1>
|
||||
<p>Introduction paragraph with <strong>bold</strong> text.</p>
|
||||
<h2>Section One</h2>
|
||||
<ul>
|
||||
<li>Item A</li>
|
||||
<li>Item B</li>
|
||||
</ul>
|
||||
<blockquote>A wise quote</blockquote>
|
||||
<hr>
|
||||
<p>Footer content</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
var result = Markdown.fromHtml(doc)
|
||||
System.print(result.contains("# Main Title")) // expect: true
|
||||
System.print(result.contains("**bold**")) // expect: true
|
||||
System.print(result.contains("## Section One")) // expect: true
|
||||
System.print(result.contains("- Item A")) // expect: true
|
||||
System.print(result.contains("> A wise quote")) // expect: true
|
||||
System.print(result.contains("---")) // expect: true
|
||||
|
||||
var linkNested = Markdown.fromHtml("<a href=\"/page\"><strong>Bold Link</strong></a>")
|
||||
System.print(linkNested.contains("[**Bold Link**](/page)")) // expect: true
|
||||
|
||||
var container = Markdown.fromHtml("<div><section><article>Content</article></section></div>")
|
||||
System.print(container.contains("Content")) // expect: true
|
||||
System.print(container.contains("<div>")) // expect: false
|
||||
System.print(container.contains("<section>")) // expect: false
|
||||
|
||||
System.print(Markdown.fromHtml("<img src=\"a.png\" alt=\"test\"/>").contains("")) // expect: true
|
||||
System.print(Markdown.fromHtml("<br/>").trim() == "") // expect: true
|
||||
|
||||
System.print(Markdown.fromHtml("<p></p>").trim() == "") // expect: true
|
||||
System.print(Markdown.fromHtml("<strong></strong>").trim() == "****") // expect: true
|
||||
|
||||
var ws = Markdown.fromHtml("<p> spaced content </p>")
|
||||
System.print(ws.contains("spaced content")) // expect: true
|
||||
|
||||
var multi = Markdown.fromHtml("<p>Para 1</p><p>Para 2</p><p>Para 3</p>")
|
||||
var lines = multi.split("\n")
|
||||
var nonEmpty = 0
|
||||
for (line in lines) {
|
||||
if (line.trim().count > 0) nonEmpty = nonEmpty + 1
|
||||
}
|
||||
System.print(nonEmpty >= 3) // expect: true
|
||||
11
test/markdown/horizontal_rules.wren
vendored
Normal file
11
test/markdown/horizontal_rules.wren
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
System.print(Markdown.toHtml("---").contains("<hr>")) // expect: true
|
||||
System.print(Markdown.toHtml("***").contains("<hr>")) // expect: true
|
||||
System.print(Markdown.toHtml("___").contains("<hr>")) // expect: true
|
||||
|
||||
System.print(Markdown.toHtml("-----").contains("<hr>")) // expect: true
|
||||
System.print(Markdown.toHtml("*****").contains("<hr>")) // expect: true
|
||||
System.print(Markdown.toHtml("_____").contains("<hr>")) // expect: true
|
||||
21
test/markdown/links_advanced.wren
vendored
Normal file
21
test/markdown/links_advanced.wren
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
var multi = Markdown.toHtml("[First](http://a.com) and [Second](http://b.com)")
|
||||
System.print(multi.contains("<a href=\"http://a.com\">First</a>")) // expect: true
|
||||
System.print(multi.contains("<a href=\"http://b.com\">Second</a>")) // expect: true
|
||||
|
||||
var withPath = Markdown.toHtml("[Docs](/docs/api/index.html)")
|
||||
System.print(withPath.contains("<a href=\"/docs/api/index.html\">Docs</a>")) // expect: true
|
||||
|
||||
var imgAlt = Markdown.toHtml("")
|
||||
System.print(imgAlt.contains("<img src=\"cat.jpg\" alt=\"A cat\">")) // expect: true
|
||||
|
||||
var imgUrl = Markdown.toHtml("")
|
||||
System.print(imgUrl.contains("<img src=\"https://example.com/logo.png\" alt=\"Logo\">")) // expect: true
|
||||
|
||||
var textAround = Markdown.toHtml("Click [here](url) now")
|
||||
System.print(textAround.contains("Click ")) // expect: true
|
||||
System.print(textAround.contains("<a href=\"url\">here</a>")) // expect: true
|
||||
System.print(textAround.contains(" now")) // expect: true
|
||||
22
test/markdown/lists_advanced.wren
vendored
Normal file
22
test/markdown/lists_advanced.wren
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
var star = Markdown.toHtml("* item a\n* item b")
|
||||
System.print(star.contains("<ul>")) // expect: true
|
||||
System.print(star.contains("<li>item a</li>")) // expect: true
|
||||
System.print(star.contains("<li>item b</li>")) // expect: true
|
||||
|
||||
var plus = Markdown.toHtml("+ item x\n+ item y")
|
||||
System.print(plus.contains("<ul>")) // expect: true
|
||||
System.print(plus.contains("<li>item x</li>")) // expect: true
|
||||
System.print(plus.contains("<li>item y</li>")) // expect: true
|
||||
|
||||
var multiDigit = Markdown.toHtml("10. tenth\n11. eleventh")
|
||||
System.print(multiDigit.contains("<ol>")) // expect: true
|
||||
System.print(multiDigit.contains("<li>tenth</li>")) // expect: true
|
||||
System.print(multiDigit.contains("<li>eleventh</li>")) // expect: true
|
||||
|
||||
var formatted = Markdown.toHtml("- **bold item**\n- *italic item*")
|
||||
System.print(formatted.contains("<li><strong>bold item</strong></li>")) // expect: true
|
||||
System.print(formatted.contains("<li><em>italic item</em></li>")) // expect: true
|
||||
40
test/markdown/mixed_content.wren
vendored
Normal file
40
test/markdown/mixed_content.wren
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
var doc = "# Title
|
||||
|
||||
This is a paragraph with **bold** text.
|
||||
|
||||
- Item 1
|
||||
- Item 2
|
||||
|
||||
Another paragraph.
|
||||
|
||||
> A quote
|
||||
|
||||
---
|
||||
|
||||
## Subtitle
|
||||
|
||||
1. First
|
||||
2. Second"
|
||||
|
||||
var html = Markdown.toHtml(doc)
|
||||
System.print(html.contains("<h1>Title</h1>")) // expect: true
|
||||
System.print(html.contains("<strong>bold</strong>")) // expect: true
|
||||
System.print(html.contains("<ul>")) // expect: true
|
||||
System.print(html.contains("<li>Item 1</li>")) // expect: true
|
||||
System.print(html.contains("<blockquote>")) // expect: true
|
||||
System.print(html.contains("<hr>")) // expect: true
|
||||
System.print(html.contains("<h2>Subtitle</h2>")) // expect: true
|
||||
System.print(html.contains("<ol>")) // expect: true
|
||||
System.print(html.contains("<li>First</li>")) // expect: true
|
||||
|
||||
var linkInList = Markdown.toHtml("- [Link](http://example.com)")
|
||||
System.print(linkInList.contains("<li>")) // expect: true
|
||||
System.print(linkInList.contains("<a href=\"http://example.com\">Link</a>")) // expect: true
|
||||
|
||||
var codeInHeading = Markdown.toHtml("# Using `print()`")
|
||||
System.print(codeInHeading.contains("<h1>")) // expect: true
|
||||
System.print(codeInHeading.contains("<code>print()</code>")) // expect: true
|
||||
17
test/markdown/paragraphs.wren
vendored
Normal file
17
test/markdown/paragraphs.wren
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
var p = Markdown.toHtml("This is a paragraph.")
|
||||
System.print(p) // expect: <p>This is a paragraph.</p>
|
||||
|
||||
var multi = Markdown.toHtml("First paragraph.\n\nSecond paragraph.")
|
||||
System.print(multi.contains("<p>First paragraph.</p>")) // expect: true
|
||||
System.print(multi.contains("<p>Second paragraph.</p>")) // expect: true
|
||||
|
||||
var afterHeading = Markdown.toHtml("# Title\n\nSome text here.")
|
||||
System.print(afterHeading.contains("<h1>Title</h1>")) // expect: true
|
||||
System.print(afterHeading.contains("<p>Some text here.</p>")) // expect: true
|
||||
|
||||
var empty = Markdown.toHtml("")
|
||||
System.print(empty) // expect:
|
||||
21
test/markdown/safe_mode.wren
vendored
Normal file
21
test/markdown/safe_mode.wren
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "markdown" for Markdown
|
||||
|
||||
var unsafe = Markdown.toHtml("<script>alert('xss')</script>")
|
||||
System.print(unsafe.contains("<script>")) // expect: true
|
||||
|
||||
var safe = Markdown.toHtml("<script>alert('xss')</script>", {"safeMode": true})
|
||||
System.print(safe.contains("<script>")) // expect: false
|
||||
System.print(safe.contains("<script>")) // expect: true
|
||||
|
||||
var safeCode = Markdown.toHtml("```\n<div>test</div>\n```", {"safeMode": true})
|
||||
System.print(safeCode.contains("<div>")) // expect: false
|
||||
System.print(safeCode.contains("<div>")) // expect: true
|
||||
|
||||
var safeInline = Markdown.toHtml("Text with <b>html</b> inside", {"safeMode": true})
|
||||
System.print(safeInline.contains("<b>")) // expect: false
|
||||
System.print(safeInline.contains("<b>")) // expect: true
|
||||
|
||||
var ampersand = Markdown.toHtml("A & B", {"safeMode": true})
|
||||
System.print(ampersand.contains("&")) // expect: true
|
||||
13
test/pathlib/absolute.wren
vendored
Normal file
13
test/pathlib/absolute.wren
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "pathlib" for Path
|
||||
|
||||
var p1 = Path.new("/home/user")
|
||||
System.print(p1.isAbsolute) // expect: true
|
||||
System.print(p1.root) // expect: /
|
||||
System.print(p1.anchor) // expect: /
|
||||
|
||||
var p2 = Path.new("relative/path")
|
||||
System.print(p2.isAbsolute) // expect: false
|
||||
System.print(p2.root) // expect:
|
||||
System.print(p2.anchor) // expect:
|
||||
15
test/pathlib/construction.wren
vendored
Normal file
15
test/pathlib/construction.wren
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "pathlib" for Path
|
||||
|
||||
var p1 = Path.new("/home/user/file.txt")
|
||||
System.print(p1.toString) // expect: /home/user/file.txt
|
||||
|
||||
var p2 = Path.new("")
|
||||
System.print(p2.toString) // expect:
|
||||
|
||||
var p3 = Path.new(p1)
|
||||
System.print(p3.toString) // expect: /home/user/file.txt
|
||||
|
||||
var p4 = Path.new("/home") / "user" / "file.txt"
|
||||
System.print(p4.toString) // expect: /home/user/file.txt
|
||||
27
test/pathlib/name_stem_suffix.wren
vendored
Normal file
27
test/pathlib/name_stem_suffix.wren
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "pathlib" for Path
|
||||
|
||||
var p1 = Path.new("/home/user/file.tar.gz")
|
||||
System.print(p1.name) // expect: file.tar.gz
|
||||
System.print(p1.stem) // expect: file.tar
|
||||
System.print(p1.suffix) // expect: .gz
|
||||
System.print(p1.suffixes) // expect: [.tar, .gz]
|
||||
|
||||
var p2 = Path.new("/home/user/file.txt")
|
||||
System.print(p2.name) // expect: file.txt
|
||||
System.print(p2.stem) // expect: file
|
||||
System.print(p2.suffix) // expect: .txt
|
||||
System.print(p2.suffixes) // expect: [.txt]
|
||||
|
||||
var p3 = Path.new("/home/user/noext")
|
||||
System.print(p3.name) // expect: noext
|
||||
System.print(p3.stem) // expect: noext
|
||||
System.print(p3.suffix) // expect:
|
||||
System.print(p3.suffixes) // expect: []
|
||||
|
||||
var p4 = Path.new("/home/user/.hidden")
|
||||
System.print(p4.name) // expect: .hidden
|
||||
System.print(p4.stem) // expect: .hidden
|
||||
System.print(p4.suffix) // expect:
|
||||
System.print(p4.suffixes) // expect: []
|
||||
21
test/pathlib/navigation.wren
vendored
Normal file
21
test/pathlib/navigation.wren
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "pathlib" for Path
|
||||
|
||||
var p1 = Path.new("/home/user/file.txt")
|
||||
System.print(p1.parent) // expect: /home/user
|
||||
System.print(p1.parent.parent) // expect: /home
|
||||
System.print(p1.parent.parent.parent) // expect: /
|
||||
|
||||
var p2 = Path.new("/home/user/file.txt")
|
||||
var parents = p2.parents
|
||||
System.print(parents.count) // expect: 3
|
||||
System.print(parents[0]) // expect: /home/user
|
||||
System.print(parents[1]) // expect: /home
|
||||
System.print(parents[2]) // expect: /
|
||||
|
||||
var p3 = Path.new("relative/path")
|
||||
System.print(p3.parent) // expect: relative
|
||||
|
||||
var p4 = Path.new("file.txt")
|
||||
System.print(p4.parent) // expect: .
|
||||
18
test/pathlib/parts.wren
vendored
Normal file
18
test/pathlib/parts.wren
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "pathlib" for Path
|
||||
|
||||
var p1 = Path.new("/home/user/file.txt")
|
||||
System.print(p1.parts) // expect: [/, home, user, file.txt]
|
||||
|
||||
var p2 = Path.new("relative/path/file.txt")
|
||||
System.print(p2.parts) // expect: [relative, path, file.txt]
|
||||
|
||||
var p3 = Path.new("")
|
||||
System.print(p3.parts) // expect: []
|
||||
|
||||
var p4 = Path.new("/")
|
||||
System.print(p4.parts) // expect: [/]
|
||||
|
||||
var p5 = Path.new("file.txt")
|
||||
System.print(p5.parts) // expect: [file.txt]
|
||||
44
test/strutil/basic.wren
vendored
Normal file
44
test/strutil/basic.wren
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "strutil" for Str
|
||||
|
||||
System.print(Str.toLower("HELLO World")) // expect: hello world
|
||||
System.print(Str.toLower("abc123")) // expect: abc123
|
||||
System.print(Str.toLower("")) // expect:
|
||||
|
||||
System.print(Str.toUpper("hello World")) // expect: HELLO WORLD
|
||||
System.print(Str.toUpper("ABC123")) // expect: ABC123
|
||||
System.print(Str.toUpper("")) // expect:
|
||||
|
||||
System.print(Str.hexEncode("ABC")) // expect: 414243
|
||||
System.print(Str.hexEncode("\x00\xFF")) // expect: 00ff
|
||||
System.print(Str.hexEncode("")) // expect:
|
||||
|
||||
System.print(Str.hexDecode("414243")) // expect: ABC
|
||||
System.print(Str.hexDecode("")) // expect:
|
||||
|
||||
System.print(Str.repeat("ab", 3)) // expect: ababab
|
||||
System.print(Str.repeat("x", 5)) // expect: xxxxx
|
||||
System.print(Str.repeat("", 10)) // expect:
|
||||
|
||||
System.print(Str.padLeft("hi", 5, " ")) // expect: hi
|
||||
System.print(Str.padLeft("hello", 3, " ")) // expect: hello
|
||||
System.print(Str.padLeft("x", 4, "0")) // expect: 000x
|
||||
|
||||
System.print("[" + Str.padRight("hi", 5, " ") + "]") // expect: [hi ]
|
||||
System.print(Str.padRight("hello", 3, " ")) // expect: hello
|
||||
System.print(Str.padRight("x", 4, "0")) // expect: x000
|
||||
|
||||
System.print(Str.escapeHtml("<div class=\"test\">A & B</div>")) // expect: <div class="test">A & B</div>
|
||||
System.print(Str.escapeHtml("hello")) // expect: hello
|
||||
System.print(Str.escapeHtml("it's")) // expect: it's
|
||||
|
||||
System.print(Str.escapeJson("hello")) // expect: "hello"
|
||||
System.print(Str.escapeJson("a\"b")) // expect: "a\"b"
|
||||
System.print(Str.escapeJson("line1\nline2")) // expect: "line1\nline2"
|
||||
|
||||
System.print(Str.urlEncode("hello world")) // expect: hello+world
|
||||
System.print(Str.urlEncode("test")) // expect: test
|
||||
|
||||
System.print(Str.urlDecode("hello+world")) // expect: hello world
|
||||
System.print(Str.urlDecode("test")) // expect: test
|
||||
27
test/web/client_url_parse.wren
vendored
Normal file
27
test/web/client_url_parse.wren
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "web" for Client
|
||||
|
||||
var parsed1 = Client.parseUrl_("http://example.com/path")
|
||||
System.print(parsed1["scheme"]) // expect: http
|
||||
System.print(parsed1["host"]) // expect: example.com
|
||||
System.print(parsed1["port"]) // expect: 80
|
||||
System.print(parsed1["path"]) // expect: /path
|
||||
|
||||
var parsed2 = Client.parseUrl_("https://example.com/path")
|
||||
System.print(parsed2["scheme"]) // expect: https
|
||||
System.print(parsed2["port"]) // expect: 443
|
||||
|
||||
var parsed3 = Client.parseUrl_("http://example.com:8080/api/v1")
|
||||
System.print(parsed3["host"]) // expect: example.com
|
||||
System.print(parsed3["port"]) // expect: 8080
|
||||
System.print(parsed3["path"]) // expect: /api/v1
|
||||
|
||||
var parsed4 = Client.parseUrl_("https://api.example.com")
|
||||
System.print(parsed4["host"]) // expect: api.example.com
|
||||
System.print(parsed4["path"]) // expect: /
|
||||
|
||||
var parsed5 = Client.parseUrl_("http://localhost:3000/")
|
||||
System.print(parsed5["host"]) // expect: localhost
|
||||
System.print(parsed5["port"]) // expect: 3000
|
||||
System.print(parsed5["path"]) // expect: /
|
||||
36
test/web/request.wren
vendored
Normal file
36
test/web/request.wren
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "web" for Request
|
||||
|
||||
var headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "text/html",
|
||||
"Cookie": "session=abc123; user=alice"
|
||||
}
|
||||
|
||||
var req = Request.new_("POST", "/api/users", {"page": "1", "limit": "10"}, headers, "{\"name\":\"Alice\"}", {"id": "42"}, null)
|
||||
|
||||
System.print(req.method) // expect: POST
|
||||
System.print(req.path) // expect: /api/users
|
||||
System.print(req.query["page"]) // expect: 1
|
||||
System.print(req.query["limit"]) // expect: 10
|
||||
System.print(req.params["id"]) // expect: 42
|
||||
System.print(req.body) // expect: {"name":"Alice"}
|
||||
|
||||
System.print(req.header("Content-Type")) // expect: application/json
|
||||
System.print(req.header("content-type")) // expect: application/json
|
||||
System.print(req.header("ACCEPT")) // expect: text/html
|
||||
System.print(req.header("X-Missing") == null) // expect: true
|
||||
|
||||
var json = req.json
|
||||
System.print(json["name"]) // expect: Alice
|
||||
|
||||
var cookies = req.cookies
|
||||
System.print(cookies["session"]) // expect: abc123
|
||||
System.print(cookies["user"]) // expect: alice
|
||||
|
||||
var formBody = "username=bob&password=secret"
|
||||
var formReq = Request.new_("POST", "/login", {}, {"Content-Type": "application/x-www-form-urlencoded"}, formBody, {}, null)
|
||||
var form = formReq.form
|
||||
System.print(form["username"]) // expect: bob
|
||||
System.print(form["password"]) // expect: secret
|
||||
39
test/web/response_advanced.wren
vendored
Normal file
39
test/web/response_advanced.wren
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "web" for Response
|
||||
|
||||
var r1 = Response.redirect("/dashboard", 301)
|
||||
System.print(r1.status) // expect: 301
|
||||
System.print(r1.headers["Location"]) // expect: /dashboard
|
||||
|
||||
var r2 = Response.new()
|
||||
r2.status = 404
|
||||
r2.body = "Not Found"
|
||||
System.print(r2.status) // expect: 404
|
||||
System.print(r2.body) // expect: Not Found
|
||||
|
||||
var r3 = Response.new()
|
||||
r3.header("X-Custom", "value1")
|
||||
r3.header("X-Another", "value2")
|
||||
System.print(r3.headers["X-Custom"]) // expect: value1
|
||||
System.print(r3.headers["X-Another"]) // expect: value2
|
||||
|
||||
var r4 = Response.new()
|
||||
r4.cookie("session", "abc123")
|
||||
r4.body = "test"
|
||||
var built = r4.build()
|
||||
System.print(built.contains("Set-Cookie: session=abc123")) // expect: true
|
||||
|
||||
var r5 = Response.new()
|
||||
r5.cookie("auth", "token", {"path": "/", "httpOnly": true, "maxAge": 3600})
|
||||
r5.body = "test"
|
||||
var built2 = r5.build()
|
||||
System.print(built2.contains("Path=/")) // expect: true
|
||||
System.print(built2.contains("HttpOnly")) // expect: true
|
||||
System.print(built2.contains("Max-Age=3600")) // expect: true
|
||||
|
||||
var r6 = Response.text("Hello World")
|
||||
var httpResponse = r6.build()
|
||||
System.print(httpResponse.contains("HTTP/1.1 200 OK")) // expect: true
|
||||
System.print(httpResponse.contains("Content-Length:")) // expect: true
|
||||
System.print(httpResponse.contains("Hello World")) // expect: true
|
||||
42
test/web/router_advanced.wren
vendored
Normal file
42
test/web/router_advanced.wren
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "web" for Router
|
||||
|
||||
var router = Router.new()
|
||||
|
||||
router.get("/api/v1/*", Fn.new { |r| "wildcard" })
|
||||
router.put("/users/:id", Fn.new { |r| "put user" })
|
||||
router.delete("/users/:id", Fn.new { |r| "delete user" })
|
||||
router.patch("/users/:id", Fn.new { |r| "patch user" })
|
||||
router.post("/users", Fn.new { |r| "create user" })
|
||||
|
||||
var m1 = router.match("GET", "/api/v1/anything")
|
||||
System.print(m1 != null) // expect: true
|
||||
|
||||
var m2 = router.match("GET", "/api/v1/nested/path")
|
||||
System.print(m2 != null) // expect: true
|
||||
|
||||
var m3 = router.match("PUT", "/users/456")
|
||||
System.print(m3 != null) // expect: true
|
||||
System.print(m3["params"]["id"]) // expect: 456
|
||||
|
||||
var m4 = router.match("DELETE", "/users/789")
|
||||
System.print(m4 != null) // expect: true
|
||||
System.print(m4["params"]["id"]) // expect: 789
|
||||
|
||||
var m5 = router.match("PATCH", "/users/101")
|
||||
System.print(m5 != null) // expect: true
|
||||
|
||||
var m6 = router.match("POST", "/users")
|
||||
System.print(m6 != null) // expect: true
|
||||
|
||||
var m7 = router.match("OPTIONS", "/users")
|
||||
System.print(m7 == null) // expect: true
|
||||
|
||||
var router2 = Router.new()
|
||||
router2.get("/posts/:postId/comments/:commentId", Fn.new { |r| "comment" })
|
||||
|
||||
var m8 = router2.match("GET", "/posts/10/comments/20")
|
||||
System.print(m8 != null) // expect: true
|
||||
System.print(m8["params"]["postId"]) // expect: 10
|
||||
System.print(m8["params"]["commentId"]) // expect: 20
|
||||
34
test/web/session_advanced.wren
vendored
Normal file
34
test/web/session_advanced.wren
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "web" for Session, SessionStore
|
||||
|
||||
var store = SessionStore.new()
|
||||
var session = store.create()
|
||||
|
||||
session["name"] = "Alice"
|
||||
session["age"] = 30
|
||||
session["active"] = true
|
||||
|
||||
System.print(session["name"]) // expect: Alice
|
||||
System.print(session["age"]) // expect: 30
|
||||
System.print(session["active"]) // expect: true
|
||||
System.print(session["nonexistent"] == null) // expect: true
|
||||
|
||||
System.print(session.isModified) // expect: true
|
||||
|
||||
session.remove("age")
|
||||
System.print(session["age"] == null) // expect: true
|
||||
|
||||
store.save(session)
|
||||
|
||||
var data = session.data
|
||||
System.print(data.containsKey("name")) // expect: true
|
||||
System.print(data.containsKey("age")) // expect: false
|
||||
System.print(data["active"]) // expect: true
|
||||
|
||||
var session2 = store.create()
|
||||
var session3 = store.create()
|
||||
System.print(session.id != session2.id) // expect: true
|
||||
System.print(session2.id != session3.id) // expect: true
|
||||
System.print(session.id.count) // expect: 36
|
||||
System.print(session2.id.count) // expect: 36
|
||||
58
test/web/view.wren
vendored
Normal file
58
test/web/view.wren
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "web" for View, Response, Request
|
||||
|
||||
class TestView is View {
|
||||
construct new() {}
|
||||
|
||||
get(request) {
|
||||
return Response.json({"method": "GET"})
|
||||
}
|
||||
|
||||
post(request) {
|
||||
return Response.json({"method": "POST"})
|
||||
}
|
||||
|
||||
put(request) {
|
||||
return Response.json({"method": "PUT"})
|
||||
}
|
||||
|
||||
delete(request) {
|
||||
return Response.json({"method": "DELETE"})
|
||||
}
|
||||
}
|
||||
|
||||
class GetOnlyView is View {
|
||||
construct new() {}
|
||||
|
||||
get(request) {
|
||||
return Response.text("GET only")
|
||||
}
|
||||
}
|
||||
|
||||
var mockGet = Request.new_("GET", "/test", {}, {}, "", {}, null)
|
||||
var mockPost = Request.new_("POST", "/test", {}, {}, "", {}, null)
|
||||
var mockPut = Request.new_("PUT", "/test", {}, {}, "", {}, null)
|
||||
var mockDelete = Request.new_("DELETE", "/test", {}, {}, "", {}, null)
|
||||
var mockPatch = Request.new_("PATCH", "/test", {}, {}, "", {}, null)
|
||||
|
||||
var view = TestView.new()
|
||||
|
||||
var r1 = view.dispatch(mockGet)
|
||||
System.print(r1.body.contains("GET")) // expect: true
|
||||
|
||||
var r2 = view.dispatch(mockPost)
|
||||
System.print(r2.body.contains("POST")) // expect: true
|
||||
|
||||
var r3 = view.dispatch(mockPut)
|
||||
System.print(r3.body.contains("PUT")) // expect: true
|
||||
|
||||
var r4 = view.dispatch(mockDelete)
|
||||
System.print(r4.body.contains("DELETE")) // expect: true
|
||||
|
||||
var getOnlyView = GetOnlyView.new()
|
||||
var r5 = getOnlyView.dispatch(mockGet)
|
||||
System.print(r5.body) // expect: GET only
|
||||
|
||||
var r6 = getOnlyView.dispatch(mockPost)
|
||||
System.print(r6.status) // expect: 405
|
||||
Loading…
Reference in New Issue
Block a user