local doc = require "wslua.doc" local fndef = require "wslua.fndef" local ty = require "wslua.ty" local list = require "wslua.list" local string = setmetatable({}, {__index = string}) doc.module{ name = "wslua.string", what = "Additional string functionality", descr = [[ This module also contains all functions from the default `string` library, and can be used as a drop-in replacement. Use the main `wslua` module to inject this module into the string metatable. ]], } string.split = fndef({ name = "string.split", what = "Split a string into segments using a separator.", args = { {name = "subj", type = ty.stringish, what = "String to be split."}, {name = "sep", type = ty.string & ~ ty.eq(""), what = "Separator pattern to split on."}, {name = "plain", type = #ty.boolean, what = "Match `sep` literally, not as a pattern."}, }, ret = { {type = ty.class(list) & ty.list(ty.string, 1)}, }, tests = { function() return "{}", string.split("", "/"):__tostring() end, function() return "{1, 2, 3}", string.split("1/2/3", "/", true):__tostring() end, function() return "{1, 2, 3}", string.split("1 2 3", "%s"):__tostring() end, function() return "{1, 2, }", string.split("1 2 ", "%s"):__tostring() end, function() return "{, 2, 3}", string.split(" 2 3", "%s"):__tostring() end, function() return "{1, 2, , 3}", string.split("1 2 3", "%s"):__tostring() end, }, }, function(subj, sep) subj = tostring(subj) local ret = list() local i = 1 while true do local a, b = subj:find(sep, i, plain) if not a then break end ret:insert(subj:sub(i, a-1)) i = b + 1 end ret:insert(subj:sub(i, -1)) return ret end) string.print = fndef({ name = "string.print", what = "Print a string to a stream.", descr = [[ Mainly useful if injected into the string metatable using the main `wslua` module, that allows `:print()` at the end of a long call chain. ]], args = { {name = "s", type = ty.string}, {name = "file", type = #ty.file, what = "File to print to, defaults to `stdout`."}, {name = "nonl", type = #ty.boolean, what = "Don't add a newline after the string, defaults to `false` (= add a newline)."}, }, ret = { {type = ty.string, what = "s"}, } }, function(s, file, nonl) file = file or io.stdout file:write(s) if not nonl then file:write("\n") end return s end) string.template = fndef({ name = "string.template", what = "Process a string with embedded Lua snippets.", descr = [=[ There are two kinds of snippets: expression snippets and statement snippets. Expression snippets start with `%[[` and end with the first `]]` afterwards, statement snippets are delimited by `%{{` and `}}`. An arbitrary amount of `=`s can be placed between the brackets, with semantics analogous to Lua strings/comments. The function "inverts" the string: - Everything except the snippets is put inside string literals, which are fed to a concatenation function. - Expression snippets are wrapped in `tostring` calls, and then also fed to the concatenation function. - Statement snippets are left as-is between the other code. The resulting code is executed inside the specified `env`. ]=], args = { {name = "input", type = ty.stringish, what = "String to process."}, {name = "loc", type = ty.stringish, what = "Location information used to generate error messages."}, {name = "env", type = ty.table, what = "Environment to execute snippets in."}, }, ret = { {type = ty.string}, }, tests = { function() return "", string.template("", "test", {}) end, function() return "asdfg", string.template("as%[=[d]=]fg", "test", {d = "d"}) end, function() return "Hello, World!", string.template("Hello, %[[name()]]!", "test", {name = function() return "World" end}) end, function() return "Hello, World!", string.template("%{{ name = 'World' }}Hello, %[[ name ]]!", "test", {}) end, }, ex = [=[ require "wslua" (); local t = "%{{ name = 'World' }}%[[greeting]], %[[name]]!" s:template("test string", {name = "Lua"}):print() ]=] }, function(input, loc, env) input = tostring(input ) loc = tostring(loc ) local s = table.concat({ "local ___out___ = ...; ___out___[=========[", input :gsub("%%%[(=*)%[(.-)%]%1%]", "]=========] ___out___(%2) ___out___[=========[") :gsub("%%{(=*){(.-)}%1}", "]=========] %2 ___out___[=========[") :gsub("___out___%[=========%[\n", "___out___'\\n' %1"), "]=========]" }) local ret = list{} assert(load(s, tostring(loc), "t", env))(function(s) ret:insert(tostring(s)) end) return ret:concat() end) return string