--[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 Copyright (C) 2002-2018 stdlib authors ]] --[[-- Additions to the core string module. The module table returned by `std.string` also contains all of the entries from the core string table. An hygienic way to import this module, then, is simply to override the core `string` locally: local string = require 'std.string' @corelibrary std.string ]] local _ = require 'std._base' local argscheck = _.typecheck and _.std.typecheck.argscheck local escape_pattern = _.string.escape_pattern local split = _.string.split _ = nil local _ENV = require 'std.normalize' { 'string', abs = 'math.abs', concat = 'table.concat', find = 'string.find', floor = 'math.floor', format = 'string.format', gsub = 'string.gsub', insert = 'table.insert', match = 'string.match', merge = 'table.merge', render = 'string.render', sort = 'table.sort', sub = 'string.sub', upper = 'string.upper', } --[[ =============== ]]-- --[[ Implementation. ]]-- --[[ =============== ]]-- local M local function toqstring(x, xstr) if type(x) ~= 'string' then return xstr end return format('%q', x) end local concatvfns = { elem = tostring, term = function(x) return type(x) ~= 'table' or getmetamethod(x, '__tostring') end, sort = function(keys) return keys end, open = function(x) return '{' end, close = function(x) return '}' end, pair = function(x, kp, vp, k, v, kstr, vstr, seqp) return toqstring(k, kstr) .. '=' .. toqstring(v, vstr) end, sep = function(x, kp, vp, kn, vn, seqp) return kp ~= nil and kn ~= nil and ',' or '' end, } local function __concat(s, o) -- Don't use normalize.str here, because we don't want ASCII escape rendering. return render(s, concatvfns) .. render(o, concatvfns) end local function __index(s, i) if type(i) == 'number' then return sub(s, i, i) else -- Fall back to module metamethods return M[i] end end local _format = string.format local function format(f, arg1, ...) return(arg1 ~= nil) and _format(f, arg1, ...) or f end local function tpack(from, to, ...) return from, to, {...} end local function tfind(s, ...) return tpack(find(s, ...)) end local function finds(s, p, i, ...) i = i or 1 local l = {} local from, to, r repeat from, to, r = tfind(s, p, i, ...) if from ~= nil then insert(l, {from, to, capt=r}) i = to + 1 end until not from return l end local function caps(s) return(gsub(s, '(%w)([%w]*)', function(l, ls) return upper(l) .. ls end)) end local function escape_shell(s) return(gsub(s, '([ %(%)%\\%[%]\'"])', '\\%1')) end local function ordinal_suffix(n) n = abs(n) % 100 local d = n % 10 if d == 1 and n ~= 11 then return 'st' elseif d == 2 and n ~= 12 then return 'nd' elseif d == 3 and n ~= 13 then return 'rd' else return 'th' end end local function pad(s, w, p) p = string.rep(p or ' ', abs(w)) if w < 0 then return string.sub(p .. s, w) end return string.sub(s .. p, 1, w) end local function wrap(s, w, ind, ind1) w = w or 78 ind = ind or 0 ind1 = ind1 or ind assert(ind1 < w and ind < w, 'the indents must be less than the line width') local r = {string.rep(' ', ind1)} local i, lstart, lens = 1, ind1, len(s) while i <= lens do local j = i + w - lstart while len(s[j]) > 0 and s[j] ~= ' ' and j > i do j = j - 1 end local ni = j + 1 while s[j] == ' ' do j = j - 1 end insert(r, sub(s, i, j)) i = ni if i < lens then insert(r, '\n' .. string.rep(' ', ind)) lstart = ind end end return concat(r) end local function numbertosi(n) local SIprefix = { [-8]='y', [-7]='z', [-6]='a', [-5]='f', [-4]='p', [-3]='n', [-2]='mu', [-1]='m', [0]='', [1]='k', [2]='M', [3]='G', [4]='T', [5]='P', [6]='E', [7]='Z', [8]='Y' } local t = _format('% #.2e', n) local _, _, m, e = find(t, '.(.%...)e(.+)') local man, exp = tonumber(m), tonumber(e) local siexp = floor(exp / 3) local shift = exp - siexp * 3 local s = SIprefix[siexp] or 'e' .. tostring(siexp) man = man *(10 ^ shift) return _format('%0.f', man) .. s end -- Ordor numbers first then asciibetically. local function keycmp(a, b) if type(a) == 'number' then return type(b) ~= 'number' or a < b end return type(b) ~= 'number' and tostring(a) < tostring(b) end local render_fallbacks = { __index = concatvfns, } local function prettytostring(x, indent, spacing) indent = indent or '\t' spacing = spacing or '' return render(x, setmetatable({ elem = function(x) if type(x) ~= 'string' then return tostring(x) end return format('%q', x) end, sort = function(keylist) sort(keylist, keycmp) return keylist end, open = function() local s = spacing .. '{' spacing = spacing .. indent return s end, close = function() spacing = string.gsub(spacing, indent .. '$', '') return spacing .. '}' end, pair = function(x, _, _, k, v, kstr, vstr) local type_k = type(k) local s = spacing if type_k ~= 'string' or match(k, '[^%w_]') then s = s .. '[' if type_k == 'table' then s = s .. '\n' end s = s .. kstr if type_k == 'table' then s = s .. '\n' end s = s .. ']' else s = s .. k end s = s .. ' =' if type(v) == 'table' then s = s .. '\n' else s = s .. ' ' end s = s .. vstr return s end, sep = function(_, k) local s = '\n' if k then s = ',' .. s end return s end, }, render_fallbacks)) end local function trim(s, r) r = r or '%s+' return (gsub(gsub(s, '^' .. r, ''), r .. '$', '')) end --[[ ================= ]]-- --[[ Public Interface. ]]-- --[[ ================= ]]-- local function X(decl, fn) return argscheck and argscheck('std.string.' .. decl, fn) or fn end M = { --- Metamethods -- @section metamethods --- String concatenation operation. -- @function __concat -- @string s initial string -- @param o object to stringify and concatenate -- @return s .. tostring(o) -- @usage -- local string = setmetatable('', require 'std.string') -- concatenated = 'foo' .. {'bar'} __concat = __concat, --- String subscript operation. -- @function __index -- @string s string -- @tparam int|string i index or method name -- @return `sub(s, i, i)` if i is a number, otherwise -- fall back to a `std.string` metamethod(if any). -- @usage -- getmetatable('').__index = require 'std.string'.__index -- third =('12345')[3] __index = __index, --- Core Functions -- @section corefuncs --- Capitalise each word in a string. -- @function caps -- @string s any string -- @treturn string *s* with each word capitalized -- @usage -- userfullname = caps(input_string) caps = X('caps(string)', caps), --- Remove any final newline from a string. -- @function chomp -- @string s any string -- @treturn string *s* with any single trailing newline removed -- @usage -- line = chomp(line) chomp = X('chomp(string)', function(s) return(gsub(s, '\n$', '')) end), --- Escape a string to be used as a pattern. -- @function escape_pattern -- @string s any string -- @treturn string *s* with active pattern characters escaped -- @usage -- substr = match(inputstr, escape_pattern(literal)) escape_pattern = X('escape_pattern(string)', escape_pattern), --- Escape a string to be used as a shell token. -- Quotes spaces, parentheses, brackets, quotes, apostrophes and -- whitespace. -- @function escape_shell -- @string s any string -- @treturn string *s* with active shell characters escaped -- @usage -- os.execute('echo ' .. escape_shell(outputstr)) escape_shell = X('escape_shell(string)', escape_shell), --- Repeatedly `string.find` until target string is exhausted. -- @function finds -- @string s target string -- @string pattern pattern to match in *s* -- @int[opt=1] init start position -- @bool[opt] plain inhibit magic characters -- @return list of `{from, to; capt={captures}}` -- @see std.string.tfind -- @usage -- for t in std.elems(finds('the target string', '%S+')) do -- print(tostring(t.capt)) -- end finds = X('finds(string, string, ?int, ?boolean|:plain)', finds), --- Extend to work better with one argument. -- If only one argument is passed, no formatting is attempted. -- @function format -- @string f format string -- @param[opt] ... arguments to format -- @return formatted string -- @usage -- print(format '100% stdlib!') format = X('format(string, [any...])', format), --- Remove leading matter from a string. -- @function ltrim -- @string s any string -- @string[opt='%s+'] r leading pattern -- @treturn string *s* with leading *r* stripped -- @usage -- print('got: ' .. ltrim(userinput)) ltrim = X('ltrim(string, ?string)', function(s, r) return (gsub(s, '^' ..(r or '%s+'), '')) end), --- Write a number using SI suffixes. -- The number is always written to 3 s.f. -- @function numbertosi -- @tparam number|string n any numeric value -- @treturn string *n* simplifed using largest available SI suffix. -- @usage -- print(numbertosi(bitspersecond) .. 'bps') numbertosi = X('numbertosi(number|string)', numbertosi), --- Return the English suffix for an ordinal. -- @function ordinal_suffix -- @tparam int|string n any integer value -- @treturn string English suffix for *n* -- @usage -- local now = os.date '*t' -- print('%d%s day of the week', now.day, ordinal_suffix(now.day)) ordinal_suffix = X('ordinal_suffix(int|string)', ordinal_suffix), --- Justify a string. -- When the string is longer than w, it is truncated(left or right -- according to the sign of w). -- @function pad -- @string s a string to justify -- @int w width to justify to(-ve means right-justify; +ve means -- left-justify) -- @string[opt=' '] p string to pad with -- @treturn string *s* justified to *w* characters wide -- @usage -- print(pad(trim(outputstr, 78)) .. '\n') pad = X('pad(string, int, ?string)', pad), --- Pretty-print a table, or other object. -- @function prettytostring -- @param x object to convert to string -- @string[opt='\t'] indent indent between levels -- @string[opt=''] spacing space before every line -- @treturn string pretty string rendering of *x* -- @usage -- print(prettytostring(std, ' ')) prettytostring = X('prettytostring(?any, ?string, ?string)', prettytostring), --- Remove trailing matter from a string. -- @function rtrim -- @string s any string -- @string[opt='%s+'] r trailing pattern -- @treturn string *s* with trailing *r* stripped -- @usage -- print('got: ' .. rtrim(userinput)) rtrim = X('rtrim(string, ?string)', function(s, r) return (gsub(s, (r or '%s+') .. '$', '')) end), --- Split a string at a given separator. -- Separator is a Lua pattern, so you have to escape active characters, -- `^$()%.[]*+-?` with a `%` prefix to match a literal character in *s*. -- @function split -- @string s to split -- @string[opt='%s+'] sep separator pattern -- @return list of strings -- @usage -- words = split 'a very short sentence' split = X('split(string, ?string)', split), --- Do `string.find`, returning a table of captures. -- @function tfind -- @string s target string -- @string pattern pattern to match in *s* -- @int[opt=1] init start position -- @bool[opt] plain inhibit magic characters -- @treturn int start of match -- @treturn int end of match -- @treturn table list of captured strings -- @see std.string.finds -- @usage -- b, e, captures = tfind('the target string', '%s', 10) tfind = X('tfind(string, string, ?int, ?boolean|:plain)', tfind), --- Remove leading and trailing matter from a string. -- @function trim -- @string s any string -- @string[opt='%s+'] r trailing pattern -- @treturn string *s* with leading and trailing *r* stripped -- @usage -- print('got: ' .. trim(userinput)) trim = X('trim(string, ?string)', trim), --- Wrap a string into a paragraph. -- @function wrap -- @string s a paragraph of text -- @int[opt=78] w width to wrap to -- @int[opt=0] ind indent -- @int[opt=ind] ind1 indent of first line -- @treturn string *s* wrapped to *w* columns -- @usage -- print(wrap(copyright, 72, 4)) wrap = X('wrap(string, ?int, ?int, ?int)', wrap), } return merge(string, M)