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:
@@ -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)
|
||||
|
||||
Vendored
+2
-5
@@ -1,6 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "os" for Process
|
||||
import "strutil" for Str
|
||||
|
||||
class ArgumentParser {
|
||||
construct new() {
|
||||
@@ -269,9 +270,5 @@ class ArgumentParser {
|
||||
return " " + parts.join(" ")
|
||||
}
|
||||
|
||||
pad_(str, width) {
|
||||
var result = str
|
||||
while (result.count < width) result = result + " "
|
||||
return result
|
||||
}
|
||||
pad_(str, width) { Str.padRight(str, width, " ") }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ static const char* argparseModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"os\" for Process\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class ArgumentParser {\n"
|
||||
" construct new() {\n"
|
||||
@@ -273,9 +274,5 @@ static const char* argparseModuleSource =
|
||||
" return \" \" + parts.join(\" \")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" pad_(str, width) {\n"
|
||||
" var result = str\n"
|
||||
" while (result.count < width) result = result + \" \"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
" pad_(str, width) { Str.padRight(str, width, \" \") }\n"
|
||||
"}\n";
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `create_training_data.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* create_training_dataModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"argparse\" for ArgumentParser\n"
|
||||
"import \"crypto\" for Hash\n"
|
||||
"import \"json\" for Json\n"
|
||||
"import \"regex\" for Regex\n"
|
||||
"import \"io\" for File, Directory\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"\n"
|
||||
"var SYSTEM_PROMPT = \"You are an expert assistant for the Wren-CLI language and its extended modules. You answer with correct Wren syntax and semantics, and you understand the Wren-CLI standard library plus the custom modules documented in the provided manual/tests. When asked for expected output, provide exactly the printed output lines in order. When asked about APIs, provide concise explanations and minimal working examples.\"\n"
|
||||
"\n"
|
||||
"class Sample {\n"
|
||||
" construct new(user, assistant) {\n"
|
||||
" _user = user\n"
|
||||
" _assistant = assistant\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" user { _user }\n"
|
||||
" assistant { _assistant }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class PseudoRandom {\n"
|
||||
" construct new(seed) {\n"
|
||||
" _state = seed\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" next() {\n"
|
||||
" _state = (_state * 1103515245 + 12345) & 0x7fffffff\n"
|
||||
" return _state\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" shuffle(list) {\n"
|
||||
" if (list.count < 2) return\n"
|
||||
" var i = list.count - 1\n"
|
||||
" while (i > 0) {\n"
|
||||
" var j = next() % (i + 1)\n"
|
||||
" var temp = list[i]\n"
|
||||
" list[i] = list[j]\n"
|
||||
" list[j] = temp\n"
|
||||
" i = i - 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"class TrainingDataGenerator {\n"
|
||||
" static sha1Text(s) {\n"
|
||||
" return Hash.toHex(Hash.sha1(s))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static normalizeNewlines(s) {\n"
|
||||
" var result = s.replace(\"\\r\\n\", \"\\n\")\n"
|
||||
" result = result.replace(\"\\r\", \"\\n\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static safeTrim(s, maxChars) {\n"
|
||||
" if (maxChars <= 0) return s\n"
|
||||
" if (s.count <= maxChars) return s\n"
|
||||
" return s[0...(maxChars - 1)] + \"…\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static writeJsonl(samples, outPath, systemPrompt) {\n"
|
||||
" var path = outPath is String ? outPath : outPath.toString\n"
|
||||
" var dirPath = path[0...(path.lastIndexOf(\"/\") + 1)]\n"
|
||||
" if (dirPath.count > 0 && !Directory.exists(dirPath)) {\n"
|
||||
" Directory.create(dirPath)\n"
|
||||
" }\n"
|
||||
" File.create(path) {|f|\n"
|
||||
" for (smp in samples) {\n"
|
||||
" var obj = {\n"
|
||||
" \"messages\": [\n"
|
||||
" {\"role\": \"system\", \"content\": systemPrompt},\n"
|
||||
" {\"role\": \"user\", \"content\": smp.user},\n"
|
||||
" {\"role\": \"assistant\", \"content\": smp.assistant}\n"
|
||||
" ]\n"
|
||||
" }\n"
|
||||
" f.writeBytes(Json.stringify(obj) + \"\\n\", 0)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static validateJsonl(path) {\n"
|
||||
" var pathStr = path is String ? path : path.toString\n"
|
||||
" var content = File.read(pathStr)\n"
|
||||
" var lines = content.split(\"\\n\")\n"
|
||||
" for (i in 0...lines.count) {\n"
|
||||
" var line = lines[i].trim()\n"
|
||||
" if (line.isEmpty) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): empty line\")\n"
|
||||
" }\n"
|
||||
" var obj = null\n"
|
||||
" Fiber.new {\n"
|
||||
" obj = Json.parse(line)\n"
|
||||
" }.try()\n"
|
||||
" if (obj == null) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): invalid JSON\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (!obj.containsKey(\"messages\") || !(obj[\"messages\"] is List)) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): missing/invalid 'messages' array\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var messages = obj[\"messages\"]\n"
|
||||
" for (j in 0...messages.count) {\n"
|
||||
" var msg = messages[j]\n"
|
||||
" if (!(msg is Map)) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): messages[%(j)] not an object\")\n"
|
||||
" }\n"
|
||||
" var role = msg[\"role\"]\n"
|
||||
" if (role != \"system\" && role != \"user\" && role != \"assistant\") {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): messages[%(j)].role invalid\")\n"
|
||||
" }\n"
|
||||
" if (!(msg[\"content\"] is String)) {\n"
|
||||
" Fiber.abort(\"%(pathStr):%(i + 1): messages[%(j)].content must be a string\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static splitTestBlocks(lines) {\n"
|
||||
" var blocks = []\n"
|
||||
" var cur = []\n"
|
||||
" var expectRe = Regex.new(\"\\\\bexpect\\\\b\\\\s*(.*)\\\\s*$\")\n"
|
||||
" var headerNoiseRe = Regex.new(\"^\\\\s*(retoor\\\\s+retoormolodetz\\\\.nl|TITLE\\\\s+Subtitle\\\\.\\\\.\\\\.)\\\\s*$\")\n"
|
||||
"\n"
|
||||
" var flush = Fn.new {\n"
|
||||
" if (!cur.isEmpty) {\n"
|
||||
" var hasExpect = false\n"
|
||||
" for (ln in cur) {\n"
|
||||
" if (ln.contains(\"expect\")) {\n"
|
||||
" hasExpect = true\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (hasExpect) blocks.add(cur)\n"
|
||||
" }\n"
|
||||
" cur = []\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" for (raw in lines) {\n"
|
||||
" var ln = raw.replace(\"\\n\", \"\").replace(\"\\r\", \"\")\n"
|
||||
"\n"
|
||||
" var m = headerNoiseRe.match(ln.trim())\n"
|
||||
" if (m != null) {\n"
|
||||
" flush.call()\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (ln.trim().isEmpty) {\n"
|
||||
" flush.call()\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var hasExpect = false\n"
|
||||
" for (x in cur) {\n"
|
||||
" if (x.contains(\"expect\")) {\n"
|
||||
" hasExpect = true\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (hasExpect && ln.lstrip().startsWith(\"import \")) {\n"
|
||||
" flush.call()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" cur.add(ln)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" flush.call()\n"
|
||||
" return blocks\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parseWrenTestsMarkdown(path, maxCodeChars, maxAnswerChars) {\n"
|
||||
" var content = File.read(path)\n"
|
||||
" var text = normalizeNewlines(content)\n"
|
||||
" var lines = text.split(\"\\n\")\n"
|
||||
" var blocks = splitTestBlocks(lines)\n"
|
||||
" var expectRe = Regex.new(\"\\\\bexpect\\\\b\\\\s*(.*)\\\\s*$\")\n"
|
||||
"\n"
|
||||
" var samples = []\n"
|
||||
" for (block in blocks) {\n"
|
||||
" var expected = []\n"
|
||||
" for (ln in block) {\n"
|
||||
" var m = expectRe.match(ln)\n"
|
||||
" if (m != null) {\n"
|
||||
" expected.add(m.group(1).trim())\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (expected.isEmpty) continue\n"
|
||||
"\n"
|
||||
" var code = block.join(\"\\n\").trim()\n"
|
||||
" code = safeTrim(code, maxCodeChars)\n"
|
||||
"\n"
|
||||
" var assistantParts = []\n"
|
||||
" for (exp in expected) {\n"
|
||||
" assistantParts.add(safeTrim(exp, maxAnswerChars))\n"
|
||||
" }\n"
|
||||
" var assistant = assistantParts.join(\"\\n\").trim()\n"
|
||||
"\n"
|
||||
" var user = \"Given this Wren-CLI program, what output should it produce?\\nReturn one output line per expectation, in order.\\n\\n```wren\\n%(code)\\n```\"\n"
|
||||
"\n"
|
||||
" samples.add(Sample.new(user, assistant))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return samples\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static parseManualHtml(path, maxExampleChars, maxAnswerChars) {\n"
|
||||
" var content = File.read(path)\n"
|
||||
" var samples = []\n"
|
||||
"\n"
|
||||
" var articleRe = Regex.new(\"<article>([\\\\s\\\\S]*?)</article>\", \"s\")\n"
|
||||
" var articles = articleRe.matchAll(content)\n"
|
||||
"\n"
|
||||
" for (articleMatch in articles) {\n"
|
||||
" var articleContent = articleMatch.group(1)\n"
|
||||
"\n"
|
||||
" var h1Re = Regex.new(\"<h1[^>]*>([\\\\s\\\\S]*?)</h1>\", \"s\")\n"
|
||||
" var h1Match = h1Re.match(articleContent)\n"
|
||||
" if (h1Match == null) continue\n"
|
||||
"\n"
|
||||
" var h1Content = h1Match.group(1)\n"
|
||||
" var moduleName = stripHtmlTags(h1Content)\n"
|
||||
" if (moduleName.isEmpty) continue\n"
|
||||
"\n"
|
||||
" var pRe = Regex.new(\"<p[^>]*>([\\\\s\\\\S]*?)</p>\", \"s\")\n"
|
||||
" var firstPMatch = pRe.match(articleContent)\n"
|
||||
" var moduleDesc = \"\"\n"
|
||||
" if (firstPMatch != null) {\n"
|
||||
" moduleDesc = stripHtmlTags(firstPMatch.group(1))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var sigRe = Regex.new('<div class=\"method-signature\"[^>]*>([\\\\s\\\\S]*?)</div>', \"s\")\n"
|
||||
" var sigs = sigRe.matchAll(articleContent)\n"
|
||||
"\n"
|
||||
" for (sigMatch in sigs) {\n"
|
||||
" var sigContent = sigMatch.group(1)\n"
|
||||
"\n"
|
||||
" var nameRe = Regex.new('<span class=\"method-name\"[^>]*>([\\\\s\\\\S]*?)</span>', \"s\")\n"
|
||||
" var nameMatch = nameRe.match(sigContent)\n"
|
||||
" if (nameMatch == null) continue\n"
|
||||
"\n"
|
||||
" var methodName = stripHtmlTags(nameMatch.group(1))\n"
|
||||
" if (methodName.isEmpty) continue\n"
|
||||
"\n"
|
||||
" var methodDesc = \"\"\n"
|
||||
" var descPRe = Regex.new('<p[^>]*>([\\\\s\\\\S]*?)</p>', \"s\")\n"
|
||||
" var afterSig = articleContent[sigMatch.end..-1]\n"
|
||||
" var descMatch = descPRe.match(afterSig)\n"
|
||||
" if (descMatch != null) {\n"
|
||||
" methodDesc = stripHtmlTags(descMatch.group(1))\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var preRe = Regex.new('<pre[^>]*><code[^>]*>([\\\\s\\\\S]*?)</code></pre>', \"s\")\n"
|
||||
" var preMatch = preRe.match(afterSig)\n"
|
||||
" var example = \"\"\n"
|
||||
" if (preMatch != null) {\n"
|
||||
" example = stripHtmlEntities(preMatch.group(1))\n"
|
||||
" example = safeTrim(example, maxExampleChars)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var assistantParts = []\n"
|
||||
" if (!moduleDesc.isEmpty) assistantParts.add(moduleDesc)\n"
|
||||
" if (!methodDesc.isEmpty) assistantParts.add(methodDesc)\n"
|
||||
" if (!example.isEmpty) assistantParts.add(\"Example:\\n```wren\\n%(example)\\n```\")\n"
|
||||
"\n"
|
||||
" var assistant = assistantParts.join(\"\\n\\n\").trim()\n"
|
||||
" assistant = safeTrim(assistant, maxAnswerChars)\n"
|
||||
"\n"
|
||||
" var user = \"In the Wren-CLI `%(moduleName)` module, how do I use `%(methodName)`?\\nGive a short explanation and a minimal example.\"\n"
|
||||
"\n"
|
||||
" if (assistant.count < 20) continue\n"
|
||||
"\n"
|
||||
" samples.add(Sample.new(user, assistant))\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return samples\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stripHtmlTags(html) {\n"
|
||||
" var result = html\n"
|
||||
" var tagRe = Regex.new(\"<[^>]+>\")\n"
|
||||
" while (true) {\n"
|
||||
" var before = result\n"
|
||||
" result = tagRe.replaceAll(result, \"\")\n"
|
||||
" if (result == before) break\n"
|
||||
" }\n"
|
||||
" result = result.replace(\" \", \" \")\n"
|
||||
" result = result.replace(\"<\", \"<\")\n"
|
||||
" result = result.replace(\">\", \">\")\n"
|
||||
" result = result.replace(\"&\", \"&\")\n"
|
||||
" result = result.replace(\""\", \"\\\"\")\n"
|
||||
" result = result.replace(\"'\", \"'\")\n"
|
||||
" return result.trim()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stripHtmlEntities(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = result.replace(\"<\", \"<\")\n"
|
||||
" result = result.replace(\">\", \">\")\n"
|
||||
" result = result.replace(\"&\", \"&\")\n"
|
||||
" result = result.replace(\""\", \"\\\"\")\n"
|
||||
" result = result.replace(\"'\", \"'\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static dedupe(samples) {\n"
|
||||
" var seen = {}\n"
|
||||
" var out = []\n"
|
||||
" for (s in samples) {\n"
|
||||
" var key = sha1Text(s.user + \"\\n---\\n\" + s.assistant)\n"
|
||||
" if (!seen.containsKey(key)) {\n"
|
||||
" seen[key] = true\n"
|
||||
" out.add(s)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return out\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static main(args) {\n"
|
||||
" var parser = ArgumentParser.new(\"Create training data from Wren documentation and tests\")\n"
|
||||
" parser.addArgument(\"--tests-md\", {\"action\": \"append\", \"type\": \"string\", \"help\": \"Path(s) to wren_tests.md-like files\"})\n"
|
||||
" parser.addArgument(\"--manual-html\", {\"action\": \"append\", \"type\": \"string\", \"help\": \"Path(s) to manual.html-like files\"})\n"
|
||||
" parser.addArgument(\"--out-train\", {\"type\": \"string\", \"default\": \"train.jsonl\", \"help\": \"Output training file\"})\n"
|
||||
" parser.addArgument(\"--out-val\", {\"type\": \"string\", \"help\": \"Output validation file\"})\n"
|
||||
" parser.addArgument(\"--val-ratio\", {\"type\": \"float\", \"default\": 0.02, \"help\": \"Validation ratio\"})\n"
|
||||
" parser.addArgument(\"--seed\", {\"type\": \"int\", \"default\": 42, \"help\": \"Random seed\"})\n"
|
||||
" parser.addArgument(\"--max-code-chars\", {\"type\": \"int\", \"default\": 12000, \"help\": \"Max code characters\"})\n"
|
||||
" parser.addArgument(\"--max-example-chars\", {\"type\": \"int\", \"default\": 2000, \"help\": \"Max example characters\"})\n"
|
||||
" parser.addArgument(\"--max-answer-chars\", {\"type\": \"int\", \"default\": 6000, \"help\": \"Max answer characters\"})\n"
|
||||
" parser.addArgument(\"--system-prompt\", {\"type\": \"string\", \"default\": SYSTEM_PROMPT, \"help\": \"System prompt\"})\n"
|
||||
" parser.addArgument(\"--validate\", {\"action\": \"storeTrue\", \"help\": \"Validate output JSONL\"})\n"
|
||||
"\n"
|
||||
" var parsed = parser.parseArgs(args)\n"
|
||||
"\n"
|
||||
" var allSamples = []\n"
|
||||
"\n"
|
||||
" if (parsed.containsKey(\"tests_md\") && parsed[\"tests_md\"] != null) {\n"
|
||||
" var testsMdPaths = parsed[\"tests_md\"]\n"
|
||||
" for (p in testsMdPaths) {\n"
|
||||
" allSamples.addAll(parseWrenTestsMarkdown(p, parsed[\"max_code_chars\"], parsed[\"max_answer_chars\"]))\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (parsed.containsKey(\"manual_html\") && parsed[\"manual_html\"] != null) {\n"
|
||||
" var manualHtmlPaths = parsed[\"manual_html\"]\n"
|
||||
" for (p in manualHtmlPaths) {\n"
|
||||
" allSamples.addAll(parseManualHtml(p, parsed[\"max_example_chars\"], parsed[\"max_answer_chars\"]))\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" allSamples = dedupe(allSamples)\n"
|
||||
"\n"
|
||||
" var rng = PseudoRandom.new(parsed[\"seed\"])\n"
|
||||
" rng.shuffle(allSamples)\n"
|
||||
"\n"
|
||||
" if (parsed.containsKey(\"out_val\") && parsed[\"out_val\"] != null && parsed[\"val_ratio\"] > 0 && parsed[\"val_ratio\"] < 1) {\n"
|
||||
" var nVal = (allSamples.count * parsed[\"val_ratio\"]).floor.max(1)\n"
|
||||
" var val = allSamples[0...nVal]\n"
|
||||
" var train = allSamples[nVal..-1]\n"
|
||||
"\n"
|
||||
" writeJsonl(train, parsed[\"out_train\"], parsed[\"system_prompt\"])\n"
|
||||
" writeJsonl(val, parsed[\"out_val\"], parsed[\"system_prompt\"])\n"
|
||||
"\n"
|
||||
" if (parsed[\"validate\"]) {\n"
|
||||
" validateJsonl(parsed[\"out_train\"])\n"
|
||||
" validateJsonl(parsed[\"out_val\"])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" System.print(\"Wrote train=%(train.count) to %(parsed[\"out_train\"])\")\n"
|
||||
" System.print(\"Wrote val=%(val.count) to %(parsed[\"out_val\"])\")\n"
|
||||
" } else {\n"
|
||||
" writeJsonl(allSamples, parsed[\"out_train\"], parsed[\"system_prompt\"])\n"
|
||||
"\n"
|
||||
" if (parsed[\"validate\"]) {\n"
|
||||
" validateJsonl(parsed[\"out_train\"])\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" System.print(\"Wrote train=%(allSamples.count) to %(parsed[\"out_train\"])\")\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"\n"
|
||||
"import \"os\" for Process\n"
|
||||
"TrainingDataGenerator.main(Process.arguments)\n";
|
||||
Vendored
+3
-14
@@ -1,6 +1,8 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "scheduler" for Scheduler
|
||||
import "strutil" for Str
|
||||
import "bytes" for Bytes
|
||||
|
||||
class Crypto {
|
||||
foreign static randomBytes_(length, fiber)
|
||||
@@ -46,18 +48,5 @@ class Hash {
|
||||
return sha256_(data)
|
||||
}
|
||||
|
||||
static toHex(bytes) {
|
||||
var hex = ""
|
||||
for (b in bytes) {
|
||||
var hi = (b >> 4) & 0x0F
|
||||
var lo = b & 0x0F
|
||||
hex = hex + hexDigit_(hi) + hexDigit_(lo)
|
||||
}
|
||||
return hex
|
||||
}
|
||||
|
||||
static hexDigit_(n) {
|
||||
if (n < 10) return String.fromCodePoint(48 + n)
|
||||
return String.fromCodePoint(97 + n - 10)
|
||||
}
|
||||
static toHex(bytes) { Str.hexEncode(Bytes.fromList(bytes)) }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ static const char* cryptoModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"scheduler\" for Scheduler\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"import \"bytes\" for Bytes\n"
|
||||
"\n"
|
||||
"class Crypto {\n"
|
||||
" foreign static randomBytes_(length, fiber)\n"
|
||||
@@ -50,18 +52,5 @@ static const char* cryptoModuleSource =
|
||||
" return sha256_(data)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toHex(bytes) {\n"
|
||||
" var hex = \"\"\n"
|
||||
" for (b in bytes) {\n"
|
||||
" var hi = (b >> 4) & 0x0F\n"
|
||||
" var lo = b & 0x0F\n"
|
||||
" hex = hex + hexDigit_(hi) + hexDigit_(lo)\n"
|
||||
" }\n"
|
||||
" return hex\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static hexDigit_(n) {\n"
|
||||
" if (n < 10) return String.fromCodePoint(48 + n)\n"
|
||||
" return String.fromCodePoint(97 + n - 10)\n"
|
||||
" }\n"
|
||||
" static toHex(bytes) { Str.hexEncode(Bytes.fromList(bytes)) }\n"
|
||||
"}\n";
|
||||
|
||||
Vendored
+5
-53
@@ -1,5 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "strutil" for Str
|
||||
|
||||
class Html {
|
||||
static isUnreserved_(c) {
|
||||
var code = c.codePoints.toList[0]
|
||||
@@ -25,19 +27,7 @@ class Html {
|
||||
|
||||
static urlencode(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
var result = ""
|
||||
for (c in string) {
|
||||
if (isUnreserved_(c)) {
|
||||
result = result + c
|
||||
} else if (c == " ") {
|
||||
result = result + "+"
|
||||
} else {
|
||||
for (b in c.bytes) {
|
||||
result = result + "\%" + hexDigit_((b >> 4) & 0x0F) + hexDigit_(b & 0x0F)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
return Str.urlEncode(string)
|
||||
}
|
||||
|
||||
static decodeUtf8_(bytes) {
|
||||
@@ -70,29 +60,7 @@ class Html {
|
||||
|
||||
static urldecode(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
var bytes = []
|
||||
var i = 0
|
||||
var chars = string.toList
|
||||
while (i < chars.count) {
|
||||
if (chars[i] == "+") {
|
||||
bytes.add(32)
|
||||
i = i + 1
|
||||
} else if (chars[i] == "\%" && i + 2 < chars.count) {
|
||||
var hi = hexValue_(chars[i + 1])
|
||||
var lo = hexValue_(chars[i + 2])
|
||||
if (hi >= 0 && lo >= 0) {
|
||||
bytes.add((hi << 4) | lo)
|
||||
i = i + 3
|
||||
} else {
|
||||
for (b in chars[i].bytes) bytes.add(b)
|
||||
i = i + 1
|
||||
}
|
||||
} else {
|
||||
for (b in chars[i].bytes) bytes.add(b)
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
return decodeUtf8_(bytes)
|
||||
return Str.urlDecode(string)
|
||||
}
|
||||
|
||||
static slugify(string) {
|
||||
@@ -119,23 +87,7 @@ class Html {
|
||||
|
||||
static quote(string) {
|
||||
if (!(string is String)) Fiber.abort("Argument must be a string.")
|
||||
var result = ""
|
||||
for (c in string) {
|
||||
if (c == "&") {
|
||||
result = result + "&"
|
||||
} else if (c == "<") {
|
||||
result = result + "<"
|
||||
} else if (c == ">") {
|
||||
result = result + ">"
|
||||
} else if (c == "\"") {
|
||||
result = result + """
|
||||
} else if (c == "'") {
|
||||
result = result + "'"
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
return Str.escapeHtml(string)
|
||||
}
|
||||
|
||||
static unquote(string) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
static const char* htmlModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class Html {\n"
|
||||
" static isUnreserved_(c) {\n"
|
||||
" var code = c.codePoints.toList[0]\n"
|
||||
@@ -29,19 +31,7 @@ static const char* htmlModuleSource =
|
||||
"\n"
|
||||
" static urlencode(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in string) {\n"
|
||||
" if (isUnreserved_(c)) {\n"
|
||||
" result = result + c\n"
|
||||
" } else if (c == \" \") {\n"
|
||||
" result = result + \"+\"\n"
|
||||
" } else {\n"
|
||||
" for (b in c.bytes) {\n"
|
||||
" result = result + \"\\%\" + hexDigit_((b >> 4) & 0x0F) + hexDigit_(b & 0x0F)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" return Str.urlEncode(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static decodeUtf8_(bytes) {\n"
|
||||
@@ -74,29 +64,7 @@ static const char* htmlModuleSource =
|
||||
"\n"
|
||||
" static urldecode(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" var bytes = []\n"
|
||||
" var i = 0\n"
|
||||
" var chars = string.toList\n"
|
||||
" while (i < chars.count) {\n"
|
||||
" if (chars[i] == \"+\") {\n"
|
||||
" bytes.add(32)\n"
|
||||
" i = i + 1\n"
|
||||
" } else if (chars[i] == \"\\%\" && i + 2 < chars.count) {\n"
|
||||
" var hi = hexValue_(chars[i + 1])\n"
|
||||
" var lo = hexValue_(chars[i + 2])\n"
|
||||
" if (hi >= 0 && lo >= 0) {\n"
|
||||
" bytes.add((hi << 4) | lo)\n"
|
||||
" i = i + 3\n"
|
||||
" } else {\n"
|
||||
" for (b in chars[i].bytes) bytes.add(b)\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" for (b in chars[i].bytes) bytes.add(b)\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return decodeUtf8_(bytes)\n"
|
||||
" return Str.urlDecode(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static slugify(string) {\n"
|
||||
@@ -123,23 +91,7 @@ static const char* htmlModuleSource =
|
||||
"\n"
|
||||
" static quote(string) {\n"
|
||||
" if (!(string is String)) Fiber.abort(\"Argument must be a string.\")\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in string) {\n"
|
||||
" if (c == \"&\") {\n"
|
||||
" result = result + \"&\"\n"
|
||||
" } else if (c == \"<\") {\n"
|
||||
" result = result + \"<\"\n"
|
||||
" } else if (c == \">\") {\n"
|
||||
" result = result + \">\"\n"
|
||||
" } else if (c == \"\\\"\") {\n"
|
||||
" result = result + \""\"\n"
|
||||
" } else if (c == \"'\") {\n"
|
||||
" result = result + \"'\"\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" return Str.escapeHtml(string)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static unquote(string) {\n"
|
||||
|
||||
Vendored
+2
-12
@@ -5,6 +5,7 @@ import "tls" for TlsSocket
|
||||
import "dns" for Dns
|
||||
import "json" for Json
|
||||
import "base64" for Base64
|
||||
import "strutil" for Str
|
||||
|
||||
class Url {
|
||||
construct parse(url) {
|
||||
@@ -44,18 +45,7 @@ class Url {
|
||||
}
|
||||
}
|
||||
|
||||
static toLower_(str) {
|
||||
var result = ""
|
||||
for (c in str) {
|
||||
var cp = c.codePoints[0]
|
||||
if (cp >= 65 && cp <= 90) {
|
||||
result = result + String.fromCodePoint(cp + 32)
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
static toLower_(str) { Str.toLower(str) }
|
||||
|
||||
scheme { _scheme }
|
||||
host { _host }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Please do not edit this file. It has been generated automatically
|
||||
// from `/home/retoor/projects/wren-cli/src/module/http.wren` using `util/wren_to_c_string.py`
|
||||
// from `src/module/http.wren` using `util/wren_to_c_string.py`
|
||||
|
||||
static const char* httpModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
@@ -9,6 +9,7 @@ static const char* httpModuleSource =
|
||||
"import \"dns\" for Dns\n"
|
||||
"import \"json\" for Json\n"
|
||||
"import \"base64\" for Base64\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class Url {\n"
|
||||
" construct parse(url) {\n"
|
||||
@@ -48,18 +49,7 @@ static const char* httpModuleSource =
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toLower_(str) {\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in str) {\n"
|
||||
" var cp = c.codePoints[0]\n"
|
||||
" if (cp >= 65 && cp <= 90) {\n"
|
||||
" result = result + String.fromCodePoint(cp + 32)\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
" static toLower_(str) { Str.toLower(str) }\n"
|
||||
"\n"
|
||||
" scheme { _scheme }\n"
|
||||
" host { _host }\n"
|
||||
|
||||
Vendored
+3
-23
@@ -1,5 +1,7 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "strutil" for Str
|
||||
|
||||
class Json {
|
||||
foreign static parse(string)
|
||||
|
||||
@@ -35,29 +37,7 @@ class Json {
|
||||
return "null"
|
||||
}
|
||||
|
||||
static escapeString_(s) {
|
||||
var result = "\""
|
||||
for (c in s) {
|
||||
if (c == "\"") {
|
||||
result = result + "\\\""
|
||||
} else if (c == "\\") {
|
||||
result = result + "\\\\"
|
||||
} else if (c == "\b") {
|
||||
result = result + "\\b"
|
||||
} else if (c == "\f") {
|
||||
result = result + "\\f"
|
||||
} else if (c == "\n") {
|
||||
result = result + "\\n"
|
||||
} else if (c == "\r") {
|
||||
result = result + "\\r"
|
||||
} else if (c == "\t") {
|
||||
result = result + "\\t"
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result + "\""
|
||||
}
|
||||
static escapeString_(s) { Str.escapeJson(s) }
|
||||
|
||||
static stringifyList_(list, indent, currentIndent) {
|
||||
if (list.count == 0) return "[]"
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
static const char* jsonModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class Json {\n"
|
||||
" foreign static parse(string)\n"
|
||||
"\n"
|
||||
@@ -39,29 +41,7 @@ static const char* jsonModuleSource =
|
||||
" return \"null\"\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static escapeString_(s) {\n"
|
||||
" var result = \"\\\"\"\n"
|
||||
" for (c in s) {\n"
|
||||
" if (c == \"\\\"\") {\n"
|
||||
" result = result + \"\\\\\\\"\"\n"
|
||||
" } else if (c == \"\\\\\") {\n"
|
||||
" result = result + \"\\\\\\\\\"\n"
|
||||
" } else if (c == \"\\b\") {\n"
|
||||
" result = result + \"\\\\b\"\n"
|
||||
" } else if (c == \"\\f\") {\n"
|
||||
" result = result + \"\\\\f\"\n"
|
||||
" } else if (c == \"\\n\") {\n"
|
||||
" result = result + \"\\\\n\"\n"
|
||||
" } else if (c == \"\\r\") {\n"
|
||||
" result = result + \"\\\\r\"\n"
|
||||
" } else if (c == \"\\t\") {\n"
|
||||
" result = result + \"\\\\t\"\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result + \"\\\"\"\n"
|
||||
" }\n"
|
||||
" static escapeString_(s) { Str.escapeJson(s) }\n"
|
||||
"\n"
|
||||
" static stringifyList_(list, indent, currentIndent) {\n"
|
||||
" if (list.count == 0) return \"[]\"\n"
|
||||
|
||||
Vendored
+559
@@ -325,4 +325,563 @@ class Markdown {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static fromHtml(html) { fromHtml(html, {}) }
|
||||
|
||||
static fromHtml(html, options) {
|
||||
var stripUnknown = options.containsKey("stripUnknown") ? options["stripUnknown"] : true
|
||||
var text = html
|
||||
|
||||
text = removeHiddenTags_(text)
|
||||
text = processBlockElements_(text)
|
||||
text = processInlineFromHtml_(text)
|
||||
text = Html.unquote(text)
|
||||
text = cleanupWhitespace_(text)
|
||||
|
||||
return text.trim()
|
||||
}
|
||||
|
||||
static removeHiddenTags_(html) {
|
||||
var result = html
|
||||
var tags = ["script", "style", "head", "meta", "link", "noscript"]
|
||||
for (tag in tags) {
|
||||
result = removeTagWithContent_(result, tag)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static removeTagWithContent_(html, tag) {
|
||||
var result = html
|
||||
var openTag = "<" + tag
|
||||
var closeTag = "</" + tag + ">"
|
||||
while (true) {
|
||||
var startLower = result.indexOf(openTag)
|
||||
var startUpper = result.indexOf("<" + tag[0].codePoints.toList[0].toString)
|
||||
var start = -1
|
||||
|
||||
var i = 0
|
||||
while (i < result.count - openTag.count) {
|
||||
if (matchTagName_(result, i, tag)) {
|
||||
start = i
|
||||
break
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
if (start < 0) break
|
||||
|
||||
var tagEnd = start
|
||||
while (tagEnd < result.count && result[tagEnd] != ">") {
|
||||
tagEnd = tagEnd + 1
|
||||
}
|
||||
if (tagEnd >= result.count) break
|
||||
|
||||
if (result[tagEnd - 1] == "/") {
|
||||
result = result[0...start] + result[tagEnd + 1..-1]
|
||||
continue
|
||||
}
|
||||
|
||||
var closeStart = findCloseTag_(result, tagEnd + 1, tag)
|
||||
if (closeStart < 0) {
|
||||
result = result[0...start] + result[tagEnd + 1..-1]
|
||||
} else {
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
result = result[0...start] + result[closeEnd + 1..-1]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static matchTagName_(html, pos, tag) {
|
||||
if (pos >= html.count || html[pos] != "<") return false
|
||||
var rest = html[pos + 1..-1].trim()
|
||||
var tagLower = tag
|
||||
for (i in 0...tag.count) {
|
||||
if (i >= rest.count) return false
|
||||
var c1 = rest[i].codePoints.toList[0]
|
||||
var c2 = tag[i].codePoints.toList[0]
|
||||
if (c1 >= 65 && c1 <= 90) c1 = c1 + 32
|
||||
if (c2 >= 65 && c2 <= 90) c2 = c2 + 32
|
||||
if (c1 != c2) return false
|
||||
}
|
||||
if (tag.count < rest.count) {
|
||||
var next = rest[tag.count]
|
||||
if (next != " " && next != ">" && next != "/" && next != "\t" && next != "\n") return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static findCloseTag_(html, start, tag) {
|
||||
var i = start
|
||||
while (i < html.count - tag.count - 2) {
|
||||
if (html[i] == "<" && html[i + 1] == "/") {
|
||||
var match = true
|
||||
for (j in 0...tag.count) {
|
||||
var c1 = html[i + 2 + j].codePoints.toList[0]
|
||||
var c2 = tag[j].codePoints.toList[0]
|
||||
if (c1 >= 65 && c1 <= 90) c1 = c1 + 32
|
||||
if (c2 >= 65 && c2 <= 90) c2 = c2 + 32
|
||||
if (c1 != c2) {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (match) return i
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
static processBlockElements_(html) {
|
||||
var result = html
|
||||
|
||||
result = processHeadingsFromHtml_(result)
|
||||
result = processCodeBlocksFromHtml_(result)
|
||||
result = processBlockquotesFromHtml_(result)
|
||||
result = processListsFromHtml_(result)
|
||||
result = processHrFromHtml_(result)
|
||||
result = processParagraphsFromHtml_(result)
|
||||
result = processBrFromHtml_(result)
|
||||
result = stripContainerTags_(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processHeadingsFromHtml_(html) {
|
||||
var result = html
|
||||
for (level in 1..6) {
|
||||
var prefix = ""
|
||||
for (i in 0...level) prefix = prefix + "#"
|
||||
result = replaceTag_(result, "h" + level.toString, prefix + " ", "\n\n")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static processCodeBlocksFromHtml_(html) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var preStart = findTagStart_(result, "pre")
|
||||
if (preStart < 0) break
|
||||
|
||||
var preOpenEnd = preStart
|
||||
while (preOpenEnd < result.count && result[preOpenEnd] != ">") {
|
||||
preOpenEnd = preOpenEnd + 1
|
||||
}
|
||||
if (preOpenEnd >= result.count) break
|
||||
|
||||
var preCloseStart = findCloseTag_(result, preOpenEnd + 1, "pre")
|
||||
if (preCloseStart < 0) break
|
||||
|
||||
var content = result[preOpenEnd + 1...preCloseStart]
|
||||
|
||||
var codeStart = findTagStart_(content, "code")
|
||||
if (codeStart >= 0) {
|
||||
var codeOpenEnd = codeStart
|
||||
while (codeOpenEnd < content.count && content[codeOpenEnd] != ">") {
|
||||
codeOpenEnd = codeOpenEnd + 1
|
||||
}
|
||||
var codeCloseStart = findCloseTag_(content, codeOpenEnd + 1, "code")
|
||||
if (codeCloseStart >= 0) {
|
||||
content = content[codeOpenEnd + 1...codeCloseStart]
|
||||
}
|
||||
}
|
||||
|
||||
var preCloseEnd = preCloseStart
|
||||
while (preCloseEnd < result.count && result[preCloseEnd] != ">") {
|
||||
preCloseEnd = preCloseEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...preStart]
|
||||
var after = result[preCloseEnd + 1..-1]
|
||||
result = before + "\n```\n" + content.trim() + "\n```\n" + after
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processBlockquotesFromHtml_(html) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var start = findTagStart_(result, "blockquote")
|
||||
if (start < 0) break
|
||||
|
||||
var openEnd = start
|
||||
while (openEnd < result.count && result[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= result.count) break
|
||||
|
||||
var closeStart = findCloseTag_(result, openEnd + 1, "blockquote")
|
||||
if (closeStart < 0) break
|
||||
|
||||
var content = result[openEnd + 1...closeStart].trim()
|
||||
var lines = content.split("\n")
|
||||
var quoted = []
|
||||
for (line in lines) {
|
||||
var trimmed = line.trim()
|
||||
if (trimmed.count > 0) {
|
||||
quoted.add("> " + trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...start]
|
||||
var after = result[closeEnd + 1..-1]
|
||||
result = before + "\n" + quoted.join("\n") + "\n" + after
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processListsFromHtml_(html) {
|
||||
var result = html
|
||||
result = processListType_(result, "ul", "-")
|
||||
result = processListType_(result, "ol", "1.")
|
||||
return result
|
||||
}
|
||||
|
||||
static processListType_(html, tag, marker) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var start = findTagStart_(result, tag)
|
||||
if (start < 0) break
|
||||
|
||||
var openEnd = start
|
||||
while (openEnd < result.count && result[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= result.count) break
|
||||
|
||||
var closeStart = findCloseTag_(result, openEnd + 1, tag)
|
||||
if (closeStart < 0) break
|
||||
|
||||
var content = result[openEnd + 1...closeStart]
|
||||
var items = extractListItems_(content)
|
||||
var mdItems = []
|
||||
var num = 1
|
||||
for (item in items) {
|
||||
if (tag == "ol") {
|
||||
mdItems.add(num.toString + ". " + item.trim())
|
||||
num = num + 1
|
||||
} else {
|
||||
mdItems.add(marker + " " + item.trim())
|
||||
}
|
||||
}
|
||||
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...start]
|
||||
var after = result[closeEnd + 1..-1]
|
||||
result = before + "\n" + mdItems.join("\n") + "\n" + after
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static extractListItems_(html) {
|
||||
var items = []
|
||||
var i = 0
|
||||
while (i < html.count) {
|
||||
var liStart = findTagStartFrom_(html, i, "li")
|
||||
if (liStart < 0) break
|
||||
|
||||
var openEnd = liStart
|
||||
while (openEnd < html.count && html[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= html.count) break
|
||||
|
||||
var closeStart = findCloseTag_(html, openEnd + 1, "li")
|
||||
var content = ""
|
||||
if (closeStart >= 0) {
|
||||
content = html[openEnd + 1...closeStart]
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < html.count && html[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
i = closeEnd + 1
|
||||
} else {
|
||||
var nextLi = findTagStartFrom_(html, openEnd + 1, "li")
|
||||
if (nextLi >= 0) {
|
||||
content = html[openEnd + 1...nextLi]
|
||||
i = nextLi
|
||||
} else {
|
||||
content = html[openEnd + 1..-1]
|
||||
i = html.count
|
||||
}
|
||||
}
|
||||
items.add(stripAllTags_(content).trim())
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
static findTagStartFrom_(html, start, tag) {
|
||||
var i = start
|
||||
while (i < html.count) {
|
||||
if (matchTagName_(html, i, tag)) return i
|
||||
i = i + 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
static processHrFromHtml_(html) {
|
||||
var result = html
|
||||
var i = 0
|
||||
while (i < result.count) {
|
||||
if (matchTagName_(result, i, "hr")) {
|
||||
var end = i
|
||||
while (end < result.count && result[end] != ">") {
|
||||
end = end + 1
|
||||
}
|
||||
result = result[0...i] + "\n---\n" + result[end + 1..-1]
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static processParagraphsFromHtml_(html) {
|
||||
var result = html
|
||||
result = replaceTag_(result, "p", "", "\n\n")
|
||||
return result
|
||||
}
|
||||
|
||||
static processBrFromHtml_(html) {
|
||||
var result = html
|
||||
var i = 0
|
||||
while (i < result.count) {
|
||||
if (matchTagName_(result, i, "br")) {
|
||||
var end = i
|
||||
while (end < result.count && result[end] != ">") {
|
||||
end = end + 1
|
||||
}
|
||||
result = result[0...i] + "\n" + result[end + 1..-1]
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static stripContainerTags_(html) {
|
||||
var result = html
|
||||
var tags = ["div", "span", "section", "article", "main", "header", "footer", "nav", "html", "body"]
|
||||
for (tag in tags) {
|
||||
result = replaceTag_(result, tag, "", "")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static processInlineFromHtml_(html) {
|
||||
var result = html
|
||||
|
||||
result = processStrongFromHtml_(result)
|
||||
result = processEmFromHtml_(result)
|
||||
result = processCodeFromHtml_(result)
|
||||
result = processDelFromHtml_(result)
|
||||
result = processLinksFromHtml_(result)
|
||||
result = processImagesFromHtml_(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processStrongFromHtml_(html) {
|
||||
var result = html
|
||||
result = replaceTag_(result, "strong", "**", "**")
|
||||
result = replaceTag_(result, "b", "**", "**")
|
||||
return result
|
||||
}
|
||||
|
||||
static processEmFromHtml_(html) {
|
||||
var result = html
|
||||
result = replaceTag_(result, "em", "*", "*")
|
||||
result = replaceTag_(result, "i", "*", "*")
|
||||
return result
|
||||
}
|
||||
|
||||
static processCodeFromHtml_(html) {
|
||||
return replaceTag_(html, "code", "`", "`")
|
||||
}
|
||||
|
||||
static processDelFromHtml_(html) {
|
||||
var result = html
|
||||
result = replaceTag_(result, "del", "~~", "~~")
|
||||
result = replaceTag_(result, "s", "~~", "~~")
|
||||
result = replaceTag_(result, "strike", "~~", "~~")
|
||||
return result
|
||||
}
|
||||
|
||||
static processLinksFromHtml_(html) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var start = findTagStart_(result, "a")
|
||||
if (start < 0) break
|
||||
|
||||
var openEnd = start
|
||||
while (openEnd < result.count && result[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= result.count) break
|
||||
|
||||
var tagContent = result[start...openEnd + 1]
|
||||
var href = extractAttr_(tagContent, "href")
|
||||
|
||||
var closeStart = findCloseTag_(result, openEnd + 1, "a")
|
||||
if (closeStart < 0) break
|
||||
|
||||
var linkText = result[openEnd + 1...closeStart]
|
||||
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...start]
|
||||
var after = result[closeEnd + 1..-1]
|
||||
|
||||
if (href != null && href.count > 0) {
|
||||
result = before + "[" + stripAllTags_(linkText) + "](" + href + ")" + after
|
||||
} else {
|
||||
result = before + stripAllTags_(linkText) + after
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static processImagesFromHtml_(html) {
|
||||
var result = html
|
||||
var i = 0
|
||||
|
||||
while (i < result.count) {
|
||||
if (matchTagName_(result, i, "img")) {
|
||||
var end = i
|
||||
while (end < result.count && result[end] != ">") {
|
||||
end = end + 1
|
||||
}
|
||||
|
||||
var tagContent = result[i...end + 1]
|
||||
var src = extractAttr_(tagContent, "src")
|
||||
var alt = extractAttr_(tagContent, "alt")
|
||||
|
||||
if (src == null) src = ""
|
||||
if (alt == null) alt = ""
|
||||
|
||||
var mdImg = ""
|
||||
result = result[0...i] + mdImg + result[end + 1..-1]
|
||||
i = i + mdImg.count
|
||||
} else {
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static extractAttr_(tag, name) {
|
||||
var search = name + "=\""
|
||||
var start = tag.indexOf(search)
|
||||
if (start < 0) {
|
||||
search = name + "='"
|
||||
start = tag.indexOf(search)
|
||||
}
|
||||
if (start < 0) return null
|
||||
|
||||
var valueStart = start + search.count
|
||||
var quote = search[-1]
|
||||
var valueEnd = valueStart
|
||||
while (valueEnd < tag.count && tag[valueEnd] != quote) {
|
||||
valueEnd = valueEnd + 1
|
||||
}
|
||||
|
||||
return tag[valueStart...valueEnd]
|
||||
}
|
||||
|
||||
static replaceTag_(html, tag, prefix, suffix) {
|
||||
var result = html
|
||||
|
||||
while (true) {
|
||||
var start = findTagStart_(result, tag)
|
||||
if (start < 0) break
|
||||
|
||||
var openEnd = start
|
||||
while (openEnd < result.count && result[openEnd] != ">") {
|
||||
openEnd = openEnd + 1
|
||||
}
|
||||
if (openEnd >= result.count) break
|
||||
|
||||
if (result[openEnd - 1] == "/") {
|
||||
result = result[0...start] + result[openEnd + 1..-1]
|
||||
continue
|
||||
}
|
||||
|
||||
var closeStart = findCloseTag_(result, openEnd + 1, tag)
|
||||
if (closeStart < 0) {
|
||||
result = result[0...start] + result[openEnd + 1..-1]
|
||||
continue
|
||||
}
|
||||
|
||||
var content = result[openEnd + 1...closeStart]
|
||||
|
||||
var closeEnd = closeStart
|
||||
while (closeEnd < result.count && result[closeEnd] != ">") {
|
||||
closeEnd = closeEnd + 1
|
||||
}
|
||||
|
||||
var before = result[0...start]
|
||||
var after = result[closeEnd + 1..-1]
|
||||
result = before + prefix + content + suffix + after
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static findTagStart_(html, tag) {
|
||||
return findTagStartFrom_(html, 0, tag)
|
||||
}
|
||||
|
||||
static stripAllTags_(html) {
|
||||
var result = ""
|
||||
var inTag = false
|
||||
for (c in html) {
|
||||
if (c == "<") {
|
||||
inTag = true
|
||||
} else if (c == ">") {
|
||||
inTag = false
|
||||
} else if (!inTag) {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static cleanupWhitespace_(text) {
|
||||
var result = text
|
||||
|
||||
while (result.contains("\n\n\n")) {
|
||||
result = result.replace("\n\n\n", "\n\n")
|
||||
}
|
||||
|
||||
var lines = result.split("\n")
|
||||
var cleaned = []
|
||||
for (line in lines) {
|
||||
cleaned.add(line.trim())
|
||||
}
|
||||
|
||||
return cleaned.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,4 +329,563 @@ static const char* markdownModuleSource =
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static fromHtml(html) { fromHtml(html, {}) }\n"
|
||||
"\n"
|
||||
" static fromHtml(html, options) {\n"
|
||||
" var stripUnknown = options.containsKey(\"stripUnknown\") ? options[\"stripUnknown\"] : true\n"
|
||||
" var text = html\n"
|
||||
"\n"
|
||||
" text = removeHiddenTags_(text)\n"
|
||||
" text = processBlockElements_(text)\n"
|
||||
" text = processInlineFromHtml_(text)\n"
|
||||
" text = Html.unquote(text)\n"
|
||||
" text = cleanupWhitespace_(text)\n"
|
||||
"\n"
|
||||
" return text.trim()\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static removeHiddenTags_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var tags = [\"script\", \"style\", \"head\", \"meta\", \"link\", \"noscript\"]\n"
|
||||
" for (tag in tags) {\n"
|
||||
" result = removeTagWithContent_(result, tag)\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static removeTagWithContent_(html, tag) {\n"
|
||||
" var result = html\n"
|
||||
" var openTag = \"<\" + tag\n"
|
||||
" var closeTag = \"</\" + tag + \">\"\n"
|
||||
" while (true) {\n"
|
||||
" var startLower = result.indexOf(openTag)\n"
|
||||
" var startUpper = result.indexOf(\"<\" + tag[0].codePoints.toList[0].toString)\n"
|
||||
" var start = -1\n"
|
||||
"\n"
|
||||
" var i = 0\n"
|
||||
" while (i < result.count - openTag.count) {\n"
|
||||
" if (matchTagName_(result, i, tag)) {\n"
|
||||
" start = i\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var tagEnd = start\n"
|
||||
" while (tagEnd < result.count && result[tagEnd] != \">\") {\n"
|
||||
" tagEnd = tagEnd + 1\n"
|
||||
" }\n"
|
||||
" if (tagEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" if (result[tagEnd - 1] == \"/\") {\n"
|
||||
" result = result[0...start] + result[tagEnd + 1..-1]\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, tagEnd + 1, tag)\n"
|
||||
" if (closeStart < 0) {\n"
|
||||
" result = result[0...start] + result[tagEnd + 1..-1]\n"
|
||||
" } else {\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
" result = result[0...start] + result[closeEnd + 1..-1]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static matchTagName_(html, pos, tag) {\n"
|
||||
" if (pos >= html.count || html[pos] != \"<\") return false\n"
|
||||
" var rest = html[pos + 1..-1].trim()\n"
|
||||
" var tagLower = tag\n"
|
||||
" for (i in 0...tag.count) {\n"
|
||||
" if (i >= rest.count) return false\n"
|
||||
" var c1 = rest[i].codePoints.toList[0]\n"
|
||||
" var c2 = tag[i].codePoints.toList[0]\n"
|
||||
" if (c1 >= 65 && c1 <= 90) c1 = c1 + 32\n"
|
||||
" if (c2 >= 65 && c2 <= 90) c2 = c2 + 32\n"
|
||||
" if (c1 != c2) return false\n"
|
||||
" }\n"
|
||||
" if (tag.count < rest.count) {\n"
|
||||
" var next = rest[tag.count]\n"
|
||||
" if (next != \" \" && next != \">\" && next != \"/\" && next != \"\\t\" && next != \"\\n\") return false\n"
|
||||
" }\n"
|
||||
" return true\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static findCloseTag_(html, start, tag) {\n"
|
||||
" var i = start\n"
|
||||
" while (i < html.count - tag.count - 2) {\n"
|
||||
" if (html[i] == \"<\" && html[i + 1] == \"/\") {\n"
|
||||
" var match = true\n"
|
||||
" for (j in 0...tag.count) {\n"
|
||||
" var c1 = html[i + 2 + j].codePoints.toList[0]\n"
|
||||
" var c2 = tag[j].codePoints.toList[0]\n"
|
||||
" if (c1 >= 65 && c1 <= 90) c1 = c1 + 32\n"
|
||||
" if (c2 >= 65 && c2 <= 90) c2 = c2 + 32\n"
|
||||
" if (c1 != c2) {\n"
|
||||
" match = false\n"
|
||||
" break\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (match) return i\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" return -1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processBlockElements_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" result = processHeadingsFromHtml_(result)\n"
|
||||
" result = processCodeBlocksFromHtml_(result)\n"
|
||||
" result = processBlockquotesFromHtml_(result)\n"
|
||||
" result = processListsFromHtml_(result)\n"
|
||||
" result = processHrFromHtml_(result)\n"
|
||||
" result = processParagraphsFromHtml_(result)\n"
|
||||
" result = processBrFromHtml_(result)\n"
|
||||
" result = stripContainerTags_(result)\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processHeadingsFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" for (level in 1..6) {\n"
|
||||
" var prefix = \"\"\n"
|
||||
" for (i in 0...level) prefix = prefix + \"#\"\n"
|
||||
" result = replaceTag_(result, \"h\" + level.toString, prefix + \" \", \"\\n\\n\")\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processCodeBlocksFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var preStart = findTagStart_(result, \"pre\")\n"
|
||||
" if (preStart < 0) break\n"
|
||||
"\n"
|
||||
" var preOpenEnd = preStart\n"
|
||||
" while (preOpenEnd < result.count && result[preOpenEnd] != \">\") {\n"
|
||||
" preOpenEnd = preOpenEnd + 1\n"
|
||||
" }\n"
|
||||
" if (preOpenEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" var preCloseStart = findCloseTag_(result, preOpenEnd + 1, \"pre\")\n"
|
||||
" if (preCloseStart < 0) break\n"
|
||||
"\n"
|
||||
" var content = result[preOpenEnd + 1...preCloseStart]\n"
|
||||
"\n"
|
||||
" var codeStart = findTagStart_(content, \"code\")\n"
|
||||
" if (codeStart >= 0) {\n"
|
||||
" var codeOpenEnd = codeStart\n"
|
||||
" while (codeOpenEnd < content.count && content[codeOpenEnd] != \">\") {\n"
|
||||
" codeOpenEnd = codeOpenEnd + 1\n"
|
||||
" }\n"
|
||||
" var codeCloseStart = findCloseTag_(content, codeOpenEnd + 1, \"code\")\n"
|
||||
" if (codeCloseStart >= 0) {\n"
|
||||
" content = content[codeOpenEnd + 1...codeCloseStart]\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var preCloseEnd = preCloseStart\n"
|
||||
" while (preCloseEnd < result.count && result[preCloseEnd] != \">\") {\n"
|
||||
" preCloseEnd = preCloseEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...preStart]\n"
|
||||
" var after = result[preCloseEnd + 1..-1]\n"
|
||||
" result = before + \"\\n```\\n\" + content.trim() + \"\\n```\\n\" + after\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processBlockquotesFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var start = findTagStart_(result, \"blockquote\")\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = start\n"
|
||||
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, openEnd + 1, \"blockquote\")\n"
|
||||
" if (closeStart < 0) break\n"
|
||||
"\n"
|
||||
" var content = result[openEnd + 1...closeStart].trim()\n"
|
||||
" var lines = content.split(\"\\n\")\n"
|
||||
" var quoted = []\n"
|
||||
" for (line in lines) {\n"
|
||||
" var trimmed = line.trim()\n"
|
||||
" if (trimmed.count > 0) {\n"
|
||||
" quoted.add(\"> \" + trimmed)\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...start]\n"
|
||||
" var after = result[closeEnd + 1..-1]\n"
|
||||
" result = before + \"\\n\" + quoted.join(\"\\n\") + \"\\n\" + after\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processListsFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = processListType_(result, \"ul\", \"-\")\n"
|
||||
" result = processListType_(result, \"ol\", \"1.\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processListType_(html, tag, marker) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var start = findTagStart_(result, tag)\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = start\n"
|
||||
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, openEnd + 1, tag)\n"
|
||||
" if (closeStart < 0) break\n"
|
||||
"\n"
|
||||
" var content = result[openEnd + 1...closeStart]\n"
|
||||
" var items = extractListItems_(content)\n"
|
||||
" var mdItems = []\n"
|
||||
" var num = 1\n"
|
||||
" for (item in items) {\n"
|
||||
" if (tag == \"ol\") {\n"
|
||||
" mdItems.add(num.toString + \". \" + item.trim())\n"
|
||||
" num = num + 1\n"
|
||||
" } else {\n"
|
||||
" mdItems.add(marker + \" \" + item.trim())\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...start]\n"
|
||||
" var after = result[closeEnd + 1..-1]\n"
|
||||
" result = before + \"\\n\" + mdItems.join(\"\\n\") + \"\\n\" + after\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static extractListItems_(html) {\n"
|
||||
" var items = []\n"
|
||||
" var i = 0\n"
|
||||
" while (i < html.count) {\n"
|
||||
" var liStart = findTagStartFrom_(html, i, \"li\")\n"
|
||||
" if (liStart < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = liStart\n"
|
||||
" while (openEnd < html.count && html[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= html.count) break\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(html, openEnd + 1, \"li\")\n"
|
||||
" var content = \"\"\n"
|
||||
" if (closeStart >= 0) {\n"
|
||||
" content = html[openEnd + 1...closeStart]\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < html.count && html[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
" i = closeEnd + 1\n"
|
||||
" } else {\n"
|
||||
" var nextLi = findTagStartFrom_(html, openEnd + 1, \"li\")\n"
|
||||
" if (nextLi >= 0) {\n"
|
||||
" content = html[openEnd + 1...nextLi]\n"
|
||||
" i = nextLi\n"
|
||||
" } else {\n"
|
||||
" content = html[openEnd + 1..-1]\n"
|
||||
" i = html.count\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" items.add(stripAllTags_(content).trim())\n"
|
||||
" }\n"
|
||||
" return items\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static findTagStartFrom_(html, start, tag) {\n"
|
||||
" var i = start\n"
|
||||
" while (i < html.count) {\n"
|
||||
" if (matchTagName_(html, i, tag)) return i\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" return -1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processHrFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var i = 0\n"
|
||||
" while (i < result.count) {\n"
|
||||
" if (matchTagName_(result, i, \"hr\")) {\n"
|
||||
" var end = i\n"
|
||||
" while (end < result.count && result[end] != \">\") {\n"
|
||||
" end = end + 1\n"
|
||||
" }\n"
|
||||
" result = result[0...i] + \"\\n---\\n\" + result[end + 1..-1]\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processParagraphsFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = replaceTag_(result, \"p\", \"\", \"\\n\\n\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processBrFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var i = 0\n"
|
||||
" while (i < result.count) {\n"
|
||||
" if (matchTagName_(result, i, \"br\")) {\n"
|
||||
" var end = i\n"
|
||||
" while (end < result.count && result[end] != \">\") {\n"
|
||||
" end = end + 1\n"
|
||||
" }\n"
|
||||
" result = result[0...i] + \"\\n\" + result[end + 1..-1]\n"
|
||||
" }\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stripContainerTags_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var tags = [\"div\", \"span\", \"section\", \"article\", \"main\", \"header\", \"footer\", \"nav\", \"html\", \"body\"]\n"
|
||||
" for (tag in tags) {\n"
|
||||
" result = replaceTag_(result, tag, \"\", \"\")\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processInlineFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" result = processStrongFromHtml_(result)\n"
|
||||
" result = processEmFromHtml_(result)\n"
|
||||
" result = processCodeFromHtml_(result)\n"
|
||||
" result = processDelFromHtml_(result)\n"
|
||||
" result = processLinksFromHtml_(result)\n"
|
||||
" result = processImagesFromHtml_(result)\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processStrongFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = replaceTag_(result, \"strong\", \"**\", \"**\")\n"
|
||||
" result = replaceTag_(result, \"b\", \"**\", \"**\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processEmFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = replaceTag_(result, \"em\", \"*\", \"*\")\n"
|
||||
" result = replaceTag_(result, \"i\", \"*\", \"*\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processCodeFromHtml_(html) {\n"
|
||||
" return replaceTag_(html, \"code\", \"`\", \"`\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processDelFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" result = replaceTag_(result, \"del\", \"~~\", \"~~\")\n"
|
||||
" result = replaceTag_(result, \"s\", \"~~\", \"~~\")\n"
|
||||
" result = replaceTag_(result, \"strike\", \"~~\", \"~~\")\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processLinksFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var start = findTagStart_(result, \"a\")\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = start\n"
|
||||
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" var tagContent = result[start...openEnd + 1]\n"
|
||||
" var href = extractAttr_(tagContent, \"href\")\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, openEnd + 1, \"a\")\n"
|
||||
" if (closeStart < 0) break\n"
|
||||
"\n"
|
||||
" var linkText = result[openEnd + 1...closeStart]\n"
|
||||
"\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...start]\n"
|
||||
" var after = result[closeEnd + 1..-1]\n"
|
||||
"\n"
|
||||
" if (href != null && href.count > 0) {\n"
|
||||
" result = before + \"[\" + stripAllTags_(linkText) + \"](\" + href + \")\" + after\n"
|
||||
" } else {\n"
|
||||
" result = before + stripAllTags_(linkText) + after\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static processImagesFromHtml_(html) {\n"
|
||||
" var result = html\n"
|
||||
" var i = 0\n"
|
||||
"\n"
|
||||
" while (i < result.count) {\n"
|
||||
" if (matchTagName_(result, i, \"img\")) {\n"
|
||||
" var end = i\n"
|
||||
" while (end < result.count && result[end] != \">\") {\n"
|
||||
" end = end + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var tagContent = result[i...end + 1]\n"
|
||||
" var src = extractAttr_(tagContent, \"src\")\n"
|
||||
" var alt = extractAttr_(tagContent, \"alt\")\n"
|
||||
"\n"
|
||||
" if (src == null) src = \"\"\n"
|
||||
" if (alt == null) alt = \"\"\n"
|
||||
"\n"
|
||||
" var mdImg = \"\"\n"
|
||||
" result = result[0...i] + mdImg + result[end + 1..-1]\n"
|
||||
" i = i + mdImg.count\n"
|
||||
" } else {\n"
|
||||
" i = i + 1\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static extractAttr_(tag, name) {\n"
|
||||
" var search = name + \"=\\\"\"\n"
|
||||
" var start = tag.indexOf(search)\n"
|
||||
" if (start < 0) {\n"
|
||||
" search = name + \"='\"\n"
|
||||
" start = tag.indexOf(search)\n"
|
||||
" }\n"
|
||||
" if (start < 0) return null\n"
|
||||
"\n"
|
||||
" var valueStart = start + search.count\n"
|
||||
" var quote = search[-1]\n"
|
||||
" var valueEnd = valueStart\n"
|
||||
" while (valueEnd < tag.count && tag[valueEnd] != quote) {\n"
|
||||
" valueEnd = valueEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return tag[valueStart...valueEnd]\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static replaceTag_(html, tag, prefix, suffix) {\n"
|
||||
" var result = html\n"
|
||||
"\n"
|
||||
" while (true) {\n"
|
||||
" var start = findTagStart_(result, tag)\n"
|
||||
" if (start < 0) break\n"
|
||||
"\n"
|
||||
" var openEnd = start\n"
|
||||
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
|
||||
" openEnd = openEnd + 1\n"
|
||||
" }\n"
|
||||
" if (openEnd >= result.count) break\n"
|
||||
"\n"
|
||||
" if (result[openEnd - 1] == \"/\") {\n"
|
||||
" result = result[0...start] + result[openEnd + 1..-1]\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var closeStart = findCloseTag_(result, openEnd + 1, tag)\n"
|
||||
" if (closeStart < 0) {\n"
|
||||
" result = result[0...start] + result[openEnd + 1..-1]\n"
|
||||
" continue\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var content = result[openEnd + 1...closeStart]\n"
|
||||
"\n"
|
||||
" var closeEnd = closeStart\n"
|
||||
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
|
||||
" closeEnd = closeEnd + 1\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var before = result[0...start]\n"
|
||||
" var after = result[closeEnd + 1..-1]\n"
|
||||
" result = before + prefix + content + suffix + after\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static findTagStart_(html, tag) {\n"
|
||||
" return findTagStartFrom_(html, 0, tag)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static stripAllTags_(html) {\n"
|
||||
" var result = \"\"\n"
|
||||
" var inTag = false\n"
|
||||
" for (c in html) {\n"
|
||||
" if (c == \"<\") {\n"
|
||||
" inTag = true\n"
|
||||
" } else if (c == \">\") {\n"
|
||||
" inTag = false\n"
|
||||
" } else if (!inTag) {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static cleanupWhitespace_(text) {\n"
|
||||
" var result = text\n"
|
||||
"\n"
|
||||
" while (result.contains(\"\\n\\n\\n\")) {\n"
|
||||
" result = result.replace(\"\\n\\n\\n\", \"\\n\\n\")\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" var lines = result.split(\"\\n\")\n"
|
||||
" var cleaned = []\n"
|
||||
" for (line in lines) {\n"
|
||||
" cleaned.add(line.trim())\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return cleaned.join(\"\\n\")\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+516
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
@@ -0,0 +1,450 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "strutil.h"
|
||||
#include "wren.h"
|
||||
|
||||
void strutilToLower(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
char* result = (char*)malloc(len + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
result[i] = c + 32;
|
||||
} else {
|
||||
result[i] = c;
|
||||
}
|
||||
}
|
||||
result[len] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilToUpper(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
char* result = (char*)malloc(len + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
result[i] = c - 32;
|
||||
} else {
|
||||
result[i] = c;
|
||||
}
|
||||
}
|
||||
result[len] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static const char hexCharsLower[] = "0123456789abcdef";
|
||||
static const char hexCharsUpper[] = "0123456789ABCDEF";
|
||||
|
||||
void strutilHexEncode(WrenVM* vm) {
|
||||
int length = 0;
|
||||
const char* bytes = wrenGetSlotBytes(vm, 1, &length);
|
||||
|
||||
if (bytes == NULL || length == 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(length * 2 + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
unsigned char b = (unsigned char)bytes[i];
|
||||
result[i * 2] = hexCharsLower[(b >> 4) & 0x0F];
|
||||
result[i * 2 + 1] = hexCharsLower[b & 0x0F];
|
||||
}
|
||||
result[length * 2] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static int hexValue(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void strutilHexDecode(WrenVM* vm) {
|
||||
const char* hex = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (hex == NULL) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(hex);
|
||||
if (len == 0 || len % 2 != 0) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t outLen = len / 2;
|
||||
char* result = (char*)malloc(outLen);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < outLen; i++) {
|
||||
int hi = hexValue(hex[i * 2]);
|
||||
int lo = hexValue(hex[i * 2 + 1]);
|
||||
if (hi < 0 || lo < 0) {
|
||||
free(result);
|
||||
wrenSetSlotBytes(vm, 0, "", 0);
|
||||
return;
|
||||
}
|
||||
result[i] = (char)((hi << 4) | lo);
|
||||
}
|
||||
|
||||
wrenSetSlotBytes(vm, 0, result, outLen);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilRepeat(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
int count = (int)wrenGetSlotDouble(vm, 2);
|
||||
|
||||
if (str == NULL || count <= 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (len == 0) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t totalLen = len * count;
|
||||
char* result = (char*)malloc(totalLen + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
memcpy(result + i * len, str, len);
|
||||
}
|
||||
result[totalLen] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilPadLeft(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
int width = (int)wrenGetSlotDouble(vm, 2);
|
||||
const char* padChar = wrenGetSlotString(vm, 3);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (width <= 0 || (size_t)width <= len) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
char pad = (padChar != NULL && padChar[0] != '\0') ? padChar[0] : ' ';
|
||||
size_t padLen = width - len;
|
||||
|
||||
char* result = (char*)malloc(width + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
memset(result, pad, padLen);
|
||||
memcpy(result + padLen, str, len);
|
||||
result[width] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilPadRight(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
int width = (int)wrenGetSlotDouble(vm, 2);
|
||||
const char* padChar = wrenGetSlotString(vm, 3);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (width <= 0 || (size_t)width <= len) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
char pad = (padChar != NULL && padChar[0] != '\0') ? padChar[0] : ' ';
|
||||
size_t padLen = width - len;
|
||||
|
||||
char* result = (char*)malloc(width + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(result, str, len);
|
||||
memset(result + len, pad, padLen);
|
||||
result[width] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilEscapeHtml(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
size_t outLen = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
if (c == '&') outLen += 5;
|
||||
else if (c == '<') outLen += 4;
|
||||
else if (c == '>') outLen += 4;
|
||||
else if (c == '"') outLen += 6;
|
||||
else if (c == '\'') outLen += 5;
|
||||
else outLen += 1;
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(outLen + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
if (c == '&') {
|
||||
memcpy(result + j, "&", 5);
|
||||
j += 5;
|
||||
} else if (c == '<') {
|
||||
memcpy(result + j, "<", 4);
|
||||
j += 4;
|
||||
} else if (c == '>') {
|
||||
memcpy(result + j, ">", 4);
|
||||
j += 4;
|
||||
} else if (c == '"') {
|
||||
memcpy(result + j, """, 6);
|
||||
j += 6;
|
||||
} else if (c == '\'') {
|
||||
memcpy(result + j, "'", 5);
|
||||
j += 5;
|
||||
} else {
|
||||
result[j++] = c;
|
||||
}
|
||||
}
|
||||
result[j] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilEscapeJson(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "\"\"");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
size_t outLen = 2;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (c == '"' || c == '\\' || c == '\b' || c == '\f' ||
|
||||
c == '\n' || c == '\r' || c == '\t') {
|
||||
outLen += 2;
|
||||
} else if (c < 32) {
|
||||
outLen += 6;
|
||||
} else {
|
||||
outLen += 1;
|
||||
}
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(outLen + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, "\"\"");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
result[j++] = '"';
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (c == '"') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = '"';
|
||||
} else if (c == '\\') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = '\\';
|
||||
} else if (c == '\b') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'b';
|
||||
} else if (c == '\f') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'f';
|
||||
} else if (c == '\n') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'n';
|
||||
} else if (c == '\r') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'r';
|
||||
} else if (c == '\t') {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 't';
|
||||
} else if (c < 32) {
|
||||
result[j++] = '\\';
|
||||
result[j++] = 'u';
|
||||
result[j++] = '0';
|
||||
result[j++] = '0';
|
||||
result[j++] = hexCharsLower[(c >> 4) & 0x0F];
|
||||
result[j++] = hexCharsLower[c & 0x0F];
|
||||
} else {
|
||||
result[j++] = c;
|
||||
}
|
||||
}
|
||||
result[j++] = '"';
|
||||
result[j] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static int isUnreserved(unsigned char c) {
|
||||
if (c >= 'A' && c <= 'Z') return 1;
|
||||
if (c >= 'a' && c <= 'z') return 1;
|
||||
if (c >= '0' && c <= '9') return 1;
|
||||
if (c == '-' || c == '_' || c == '.' || c == '~') return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void strutilUrlEncode(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
size_t outLen = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (isUnreserved(c)) {
|
||||
outLen += 1;
|
||||
} else if (c == ' ') {
|
||||
outLen += 1;
|
||||
} else {
|
||||
outLen += 3;
|
||||
}
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(outLen + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
if (isUnreserved(c)) {
|
||||
result[j++] = c;
|
||||
} else if (c == ' ') {
|
||||
result[j++] = '+';
|
||||
} else {
|
||||
result[j++] = '%';
|
||||
result[j++] = hexCharsUpper[(c >> 4) & 0x0F];
|
||||
result[j++] = hexCharsUpper[c & 0x0F];
|
||||
}
|
||||
}
|
||||
result[j] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void strutilUrlDecode(WrenVM* vm) {
|
||||
const char* str = wrenGetSlotString(vm, 1);
|
||||
|
||||
if (str == NULL) {
|
||||
wrenSetSlotString(vm, 0, "");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(str);
|
||||
char* result = (char*)malloc(len + 1);
|
||||
if (result == NULL) {
|
||||
wrenSetSlotString(vm, 0, str);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char c = str[i];
|
||||
if (c == '+') {
|
||||
result[j++] = ' ';
|
||||
} else if (c == '%' && i + 2 < len) {
|
||||
int hi = hexValue(str[i + 1]);
|
||||
int lo = hexValue(str[i + 2]);
|
||||
if (hi >= 0 && lo >= 0) {
|
||||
result[j++] = (char)((hi << 4) | lo);
|
||||
i += 2;
|
||||
} else {
|
||||
result[j++] = c;
|
||||
}
|
||||
} else {
|
||||
result[j++] = c;
|
||||
}
|
||||
}
|
||||
result[j] = '\0';
|
||||
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
free(result);
|
||||
}
|
||||
@@ -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
|
||||
Vendored
+15
@@ -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)
|
||||
}
|
||||
@@ -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";
|
||||
Vendored
+3
-14
@@ -1,22 +1,11 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import "crypto" for Crypto
|
||||
import "strutil" for Str
|
||||
import "bytes" for Bytes
|
||||
|
||||
class Uuid {
|
||||
static hexDigit_(n) {
|
||||
if (n < 10) return String.fromCodePoint(48 + n)
|
||||
return String.fromCodePoint(97 + n - 10)
|
||||
}
|
||||
|
||||
static toHex_(bytes) {
|
||||
var hex = ""
|
||||
for (b in bytes) {
|
||||
var hi = (b >> 4) & 0x0F
|
||||
var lo = b & 0x0F
|
||||
hex = hex + hexDigit_(hi) + hexDigit_(lo)
|
||||
}
|
||||
return hex
|
||||
}
|
||||
static toHex_(bytes) { Str.hexEncode(Bytes.fromList(bytes)) }
|
||||
|
||||
static v4() {
|
||||
var bytes = Crypto.randomBytes(16)
|
||||
|
||||
@@ -5,22 +5,11 @@ static const char* uuidModuleSource =
|
||||
"// retoor <retoor@molodetz.nl>\n"
|
||||
"\n"
|
||||
"import \"crypto\" for Crypto\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"import \"bytes\" for Bytes\n"
|
||||
"\n"
|
||||
"class Uuid {\n"
|
||||
" static hexDigit_(n) {\n"
|
||||
" if (n < 10) return String.fromCodePoint(48 + n)\n"
|
||||
" return String.fromCodePoint(97 + n - 10)\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toHex_(bytes) {\n"
|
||||
" var hex = \"\"\n"
|
||||
" for (b in bytes) {\n"
|
||||
" var hi = (b >> 4) & 0x0F\n"
|
||||
" var lo = b & 0x0F\n"
|
||||
" hex = hex + hexDigit_(hi) + hexDigit_(lo)\n"
|
||||
" }\n"
|
||||
" return hex\n"
|
||||
" }\n"
|
||||
" static toHex_(bytes) { Str.hexEncode(Bytes.fromList(bytes)) }\n"
|
||||
"\n"
|
||||
" static v4() {\n"
|
||||
" var bytes = Crypto.randomBytes(16)\n"
|
||||
|
||||
Vendored
+2
-12
@@ -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
@@ -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"
|
||||
|
||||
Vendored
+2
-12
@@ -6,6 +6,7 @@ import "dns" for Dns
|
||||
import "crypto" for Crypto, Hash
|
||||
import "base64" for Base64
|
||||
import "bytes" for Bytes
|
||||
import "strutil" for Str
|
||||
|
||||
class WebSocketMessage {
|
||||
construct new_(opcode, payload, fin) {
|
||||
@@ -205,18 +206,7 @@ class WebSocket {
|
||||
return lines
|
||||
}
|
||||
|
||||
static toLower_(str) {
|
||||
var result = ""
|
||||
for (c in str) {
|
||||
var cp = c.codePoints[0]
|
||||
if (cp >= 65 && cp <= 90) {
|
||||
result = result + String.fromCodePoint(cp + 32)
|
||||
} else {
|
||||
result = result + c
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
static toLower_(str) { Str.toLower(str) }
|
||||
|
||||
static computeAcceptKey_(key) {
|
||||
var magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
@@ -10,6 +10,7 @@ static const char* websocketModuleSource =
|
||||
"import \"crypto\" for Crypto, Hash\n"
|
||||
"import \"base64\" for Base64\n"
|
||||
"import \"bytes\" for Bytes\n"
|
||||
"import \"strutil\" for Str\n"
|
||||
"\n"
|
||||
"class WebSocketMessage {\n"
|
||||
" construct new_(opcode, payload, fin) {\n"
|
||||
@@ -209,18 +210,7 @@ static const char* websocketModuleSource =
|
||||
" return lines\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" static toLower_(str) {\n"
|
||||
" var result = \"\"\n"
|
||||
" for (c in str) {\n"
|
||||
" var cp = c.codePoints[0]\n"
|
||||
" if (cp >= 65 && cp <= 90) {\n"
|
||||
" result = result + String.fromCodePoint(cp + 32)\n"
|
||||
" } else {\n"
|
||||
" result = result + c\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" return result\n"
|
||||
" }\n"
|
||||
" static toLower_(str) { Str.toLower(str) }\n"
|
||||
"\n"
|
||||
" static computeAcceptKey_(key) {\n"
|
||||
" var magic = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n"
|
||||
|
||||
Reference in New Issue
Block a user