feat: add merge_all_wren utility, strutil demo, and 7 new API manual pages

Add WrenFileReader class in apps/merge_all_wren.wren for recursive .wren file discovery and content printing. Introduce example/strutil_demo.wren demonstrating case conversion, hex encoding, SHA256 hashing, string repetition, padding, HTML/JSON escaping, and URL encoding/decoding. Create manual/api pages for argparse, dataset, html, markdown, uuid, wdantic, and web modules. Update manual/api/index.html sidebar and module count from 21 to 28, adding links and cards for the new modules.
This commit is contained in:
2026-01-25 01:47:47 +00:00
parent 5a4054ec66
commit 74ef5cbc11
74 changed files with 7749 additions and 292 deletions
+56
View File
@@ -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)
+2 -5
View File
@@ -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, " ") }
}
+2 -5
View File
@@ -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
View 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(\"&nbsp;\", \" \")\n"
" result = result.replace(\"&lt;\", \"<\")\n"
" result = result.replace(\"&gt;\", \">\")\n"
" result = result.replace(\"&amp;\", \"&\")\n"
" result = result.replace(\"&quot;\", \"\\\"\")\n"
" result = result.replace(\"&#39;\", \"'\")\n"
" return result.trim()\n"
" }\n"
"\n"
" static stripHtmlEntities(html) {\n"
" var result = html\n"
" result = result.replace(\"&lt;\", \"<\")\n"
" result = result.replace(\"&gt;\", \">\")\n"
" result = result.replace(\"&amp;\", \"&\")\n"
" result = result.replace(\"&quot;\", \"\\\"\")\n"
" result = result.replace(\"&#39;\", \"'\")\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";
+3 -14
View File
@@ -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)) }
}
+3 -14
View File
@@ -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";
+5 -53
View File
@@ -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 + "&amp;"
} else if (c == "<") {
result = result + "&lt;"
} else if (c == ">") {
result = result + "&gt;"
} else if (c == "\"") {
result = result + "&quot;"
} else if (c == "'") {
result = result + "&#39;"
} else {
result = result + c
}
}
return result
return Str.escapeHtml(string)
}
static unquote(string) {
+5 -53
View File
@@ -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 + \"&amp;\"\n"
" } else if (c == \"<\") {\n"
" result = result + \"&lt;\"\n"
" } else if (c == \">\") {\n"
" result = result + \"&gt;\"\n"
" } else if (c == \"\\\"\") {\n"
" result = result + \"&quot;\"\n"
" } else if (c == \"'\") {\n"
" result = result + \"&#39;\"\n"
" } else {\n"
" result = result + c\n"
" }\n"
" }\n"
" return result\n"
" return Str.escapeHtml(string)\n"
" }\n"
"\n"
" static unquote(string) {\n"
+2 -12
View File
@@ -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 }
+3 -13
View File
@@ -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"
+3 -23
View File
@@ -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 "[]"
+3 -23
View File
@@ -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
View File
@@ -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 = "![" + alt + "](" + src + ")"
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")
}
}
+559
View File
@@ -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 = \"![\" + alt + \"](\" + src + \")\"\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
View 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
View 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
View 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
View 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
View 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, "&amp;", 5);
j += 5;
} else if (c == '<') {
memcpy(result + j, "&lt;", 4);
j += 4;
} else if (c == '>') {
memcpy(result + j, "&gt;", 4);
j += 4;
} else if (c == '"') {
memcpy(result + j, "&quot;", 6);
j += 6;
} else if (c == '\'') {
memcpy(result + j, "&#39;", 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
View 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
View 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
View 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";
+3 -14
View File
@@ -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)
+3 -14
View File
@@ -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"
+2 -12
View File
@@ -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 {
+2 -12
View File
@@ -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"
+2 -12
View File
@@ -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"
+2 -12
View File
@@ -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"