import io.pebbletemplates.pebble.extension.escaper.EscapeFilter
import io.pebbletemplates.pebble.extension.escaper.EscapingStrategy
import io.pebbletemplates.pebble.extension.escaper.RawFilter
+import io.pebbletemplates.pebble.extension.escaper.SafeString
import io.pebbletemplates.pebble.extension.i18n.i18nFunction
import io.pebbletemplates.pebble.loader.Loader
import io.pebbletemplates.pebble.template.EvaluationContext
override fun resolveRelativePath(relativePath: String, anchorPath: String): String {
val templateDir = File(Configuration.CurrentConfiguration.templateDir)
- if ('\n' in anchorPath) // Probably a raw template string
+ if ('\n' in anchorPath) // Probably a raw template contents string
return relativePath
return templateDir.combineSafe("$anchorPath/$relativePath").toRelativeString(templateDir)
fun deconvertJson(data: Any?): JsonElement = when (data) {
null -> JsonNull
is String -> JsonPrimitive(data)
+ is SafeString -> JsonPrimitive(data.toString())
is Number -> JsonPrimitive(data)
is Boolean -> JsonPrimitive(data)
is List<*> -> JsonArray(data.map { deconvertJson(it) })
return mutableListOf()
}
- override fun apply(input: Any?, args: MutableMap<String, Any>?, self: PebbleTemplate?, context: EvaluationContext?, lineNumber: Int): Any {
+ override fun apply(input: Any?, args: MutableMap<String, Any?>, self: PebbleTemplate?, context: EvaluationContext?, lineNumber: Int): Any? {
return PebbleJsonLoader.deconvertJson(input).toString()
}
}
return mutableListOf()
}
- override fun apply(input: Any?, args: MutableMap<String, Any>?, self: PebbleTemplate?, context: EvaluationContext?, lineNumber: Int): Any? {
+ override fun apply(input: Any?, args: MutableMap<String, Any?>, self: PebbleTemplate?, context: EvaluationContext?, lineNumber: Int): Any? {
return PebbleJsonLoader.convertJson(JsonStorageCodec.parseToJsonElement(input.toString()))
}
}
return mutableListOf("data", "path")
}
- override fun execute(args: MutableMap<String, Any>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any? {
+ override fun execute(args: MutableMap<String, Any?>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any? {
val dataName = args["data"]?.toString()
?: throw PebbleException(null, "Missing 'data' argument", lineNumber, self.name)
cache[digest] = compiledScript
return compiledScript
}
-}
-
-object PebbleScriptFilter : Filter {
- override fun getArgumentNames(): MutableList<String> {
- return mutableListOf("script")
- }
- override fun apply(input: Any?, args: MutableMap<String, Any?>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any {
- val scriptName = args["script"]?.toString()
- ?: throw PebbleException(null, "Missing 'script' argument", lineNumber, self.name)
-
- val script = PebbleScriptLoader.loadFunction(scriptName)
- ?: throw PebbleException(null, "Script $scriptName could not be found", lineNumber, self.name)
-
+ fun runScript(scriptName: String, script: CompiledScript, input: Any?, args: MutableMap<String, Any?>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any? {
val bindings = SimpleBindings()
bindings["text"] = input
- bindings["ctx"] = java.util.function.Function(context::getVariable)
+ bindings["stdlib"] = PebbleScriptStdlib(bindings, self, lineNumber)
+ bindings["ctx"] = PebbleScriptVarContext(context::getVariable)
bindings["args"] = args.toMutableMap().apply { remove("script") }
return try {
throw PebbleException(ex, "Unhandled ScriptException from $scriptName", lineNumber, self.name)
}
}
-}
-
-object PebbleScriptFunction : Function {
- override fun getArgumentNames(): MutableList<String> {
- return mutableListOf("script")
+
+ fun runScript(scriptName: String, input: Any?, args: MutableMap<String, Any?>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any? {
+ val script = loadFunction(scriptName)
+ ?: throw PebbleException(null, "Script $scriptName could not be found", lineNumber, self.name)
+
+ return runScript(scriptName, script, input, args, self, context, lineNumber)
}
- override fun execute(args: MutableMap<String, Any>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any {
+ fun runScript(input: Any?, args: MutableMap<String, Any?>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any? {
val scriptName = args["script"]?.toString()
?: throw PebbleException(null, "Missing 'script' argument", lineNumber, self.name)
+ return runScript(scriptName, input, args, self, context, lineNumber)
+ }
+}
+
+class PebbleScriptStdlib(private val bindings: Bindings, private val self: PebbleTemplate, private val lineNumber: Int) {
+ fun serialize(data: Any?): String {
+ return PebbleJsonLoader.deconvertJson(data).toString()
+ }
+
+ fun deserialize(json: String): Any? {
+ return PebbleJsonLoader.convertJson(JsonStorageCodec.parseToJsonElement(json))
+ }
+
+ @JvmOverloads
+ fun loadScript(scriptName: String, env: Map<String, Any?> = emptyMap()): Any? {
val script = PebbleScriptLoader.loadFunction(scriptName)
?: throw PebbleException(null, "Script $scriptName could not be found", lineNumber, self.name)
- val bindings = SimpleBindings()
- bindings["ctx"] = java.util.function.Function(context::getVariable)
- bindings["args"] = args.toMutableMap().apply { remove("script") }
+ val innerBindings = SimpleBindings()
+ innerBindings.putAll(env)
+
+ return try {
+ script.eval(innerBindings)
+ } catch (ex: ScriptException) {
+ throw PebbleException(ex, "Unhandled ScriptException from $scriptName", lineNumber, self.name)
+ }
+ }
+
+ fun loadScriptWith(scriptName: String, env: MutableMap<String, Any?> = mutableMapOf()): Any? {
+ val script = PebbleScriptLoader.loadFunction(scriptName)
+ ?: throw PebbleException(null, "Script $scriptName could not be found", lineNumber, self.name)
+
+ val innerBindings = SimpleBindings()
+ innerBindings.putAll(env)
+
+ return try {
+ script.eval(innerBindings).also { _ ->
+ env.clear()
+ env.putAll(innerBindings)
+ }
+ } catch (ex: ScriptException) {
+ throw PebbleException(ex, "Unhandled ScriptException from $scriptName", lineNumber, self.name)
+ }
+ }
+
+ fun loadScriptHere(scriptName: String): Any? {
+ val script = PebbleScriptLoader.loadFunction(scriptName)
+ ?: throw PebbleException(null, "Script $scriptName could not be found", lineNumber, self.name)
return try {
script.eval(bindings)
}
}
}
+
+fun interface PebbleScriptVarContext {
+ operator fun get(name: String): Any?
+}
+
+object PebbleScriptFilter : Filter {
+ override fun getArgumentNames(): MutableList<String> {
+ return mutableListOf("script")
+ }
+
+ override fun apply(input: Any?, args: MutableMap<String, Any?>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any? {
+ return PebbleScriptLoader.runScript(input, args, self, context, lineNumber)
+ }
+}
+
+object PebbleScriptFunction : Function {
+ override fun getArgumentNames(): MutableList<String> {
+ return mutableListOf("script")
+ }
+
+ override fun execute(args: MutableMap<String, Any?>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any? {
+ return PebbleScriptLoader.runScript(null, args, self, context, lineNumber)
+ }
+}
}
});
+ window.renderVocab = function (vocab) {
+ function renderWord(word, index) {
+ const wordRoot = document.createElement("div");
+
+ const wordLabel = document.createElement("strong");
+ wordLabel.append(word);
+ const indexLabel = document.createElement("i");
+ indexLabel.append("definition " + (index + 1));
+ wordRoot.appendChild(document.createElement("p")).append(wordLabel, "\u00A0", indexLabel);
+
+ const definition = vocab.words[word][index];
+ const inflection = vocab.inflections[definition.type];
+
+ const defList = wordRoot.appendChild(document.createElement("ol"));
+ for (const def of definition.definitions) {
+ defList.appendChild(document.createElement("li")).append(def);
+ }
+
+ const inflectionTable = wordRoot.appendChild(document.createElement("table"));
+ for (const row of inflection) {
+ const rowElem = inflectionTable.appendChild(document.createElement("tr"));
+ for (const cell of row) {
+ const cellElem = rowElem.appendChild(document.createElement(cell.tag));
+ for (const attr of Object.keys(cell.attrs)) {
+ cellElem.setAttribute(attr, cell.attrs[attr]);
+ }
+ if ((typeof cell.text) === "string") {
+ cellElem.innerHTML = cell.text;
+ } else {
+ cellElem.innerHTML = definition.forms[cell.text.form].replace(RegExp(cell.text.regexp, "ui"), cell.text.replacement);
+ }
+ }
+ }
+
+ return wordRoot;
+ }
+
+ const localeCompareSorter = (a, b) => a.localeCompare(b);
+
+ const englishToWord = {};
+ for (const word of Object.keys(vocab.words).sort(localeCompareSorter)) {
+ const definitions = vocab.words[word];
+ const definitionsLength = definitions.length;
+ for (let i = 0; i < definitionsLength; i++) {
+ for (const keyword of definitions[i].inEnglish) {
+ const english = englishToWord[keyword] || (englishToWord[keyword] = []);
+ english.push({"word": word, "index": i});
+ }
+ }
+ }
+
+ const vocabRoot = document.createElement("div");
+ const vocabSearchRoot = vocabRoot.appendChild(document.createElement("form"));
+ const vocabSearchResults = vocabRoot.appendChild(document.createElement("div"));
+ vocabSearchResults.appendChild(document.createElement("i")).append("Search results will appear here");
+
+ const vocabSearch = vocabSearchRoot.appendChild(document.createElement("input"));
+ vocabSearch.name = "q";
+ vocabSearch.type = "text";
+
+ const vocabEnglishToLangRoot = vocabSearchRoot.appendChild(document.createElement("label"));
+ const vocabEnglishToLang = vocabEnglishToLangRoot.appendChild(document.createElement("input"));
+ vocabEnglishToLang.name = "target";
+ vocabEnglishToLang.type = "radio";
+ vocabEnglishToLang.value = "from-english";
+ vocabEnglishToLang.checked = true;
+ vocabEnglishToLangRoot.append("English to " + vocab.langName);
+
+ vocabSearchRoot.appendChild(document.createElement("br"));
+
+ const vocabLangToEnglishRoot = vocabSearchRoot.appendChild(document.createElement("label"));
+ const vocabLangToEnglish = vocabLangToEnglishRoot.appendChild(document.createElement("input"));
+ vocabLangToEnglish.name = "target";
+ vocabLangToEnglish.type = "radio";
+ vocabLangToEnglish.value = "to-english";
+ vocabLangToEnglishRoot.append(vocab.langName + " to English");
+
+ const vocabSearchButton = vocabSearchRoot.appendChild(document.createElement("input"));
+ vocabSearchButton.type = "submit";
+ vocabSearchButton.value = "Search";
+
+ vocabSearchRoot.onsubmit = function (ev) {
+ ev.preventDefault();
+
+ const searchTerm = vocabSearch.value.trim();
+
+ while (vocabSearchResults.hasChildNodes()) {
+ vocabSearchResults.firstChild.remove();
+ }
+
+ const searchResults = [];
+ if (vocabEnglishToLang.checked) {
+ for (const englishWord of Object.keys(englishToWord).sort(localeCompareSorter)) {
+ if (!englishWord.startsWith(searchTerm)) continue;
+
+ for (const vocabItem of englishToWord[englishWord]) {
+ searchResults.push(vocabItem);
+ }
+ }
+ } else {
+ for (const langWord of Object.keys(vocab.words).sort(localeCompareSorter)) {
+ if (!langWord.startsWith(searchTerm)) continue;
+
+ const numDefs = vocab.words[langWord].length;
+ for (let i = 0; i < numDefs; i++) {
+ searchResults.push({"word": langWord, "index": i});
+ }
+ }
+ }
+
+ if (searchResults.length === 0) {
+ vocabSearchResults.appendChild(document.createElement("i")).append("No results found");
+ return;
+ }
+
+ searchResults.sort((a, b) => (a.word === b.word) ? (a.index - b.index) : localeCompareSorter(a.word, b.word));
+
+ for (const searchResult of searchResults) {
+ vocabSearchResults.append(renderWord(searchResult.word, searchResult.index));
+ }
+ };
+
+ document.currentScript.after(vocabRoot);
+ };
+
window.renderQuiz = function (quiz) {
const quizFunctions = {};
const quizRoot = document.createElement("table");