diff options
author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2022-01-05 18:59:17 +0300 |
---|---|---|
committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2022-01-05 22:52:22 +0300 |
commit | 98cb7f4a4f035595b9f794fed44b4f30f86b956e (patch) | |
tree | 314cf97a94963bd1626ed6d75f29d784e780e5b5 | |
parent | 838a27aa13469f9173c74e0ad5e3a16c1469f3e4 (diff) |
chore(test) move stringx tests over to busted
-rw-r--r-- | lua/pl/stringx.lua | 167 | ||||
-rw-r--r-- | spec/stringx_spec.lua | 428 | ||||
-rw-r--r-- | spec/text_spec.lua | 5 | ||||
-rw-r--r-- | tests/test-stringx.lua | 381 | ||||
-rw-r--r-- | tests/test-stringx2.lua | 20 |
5 files changed, 517 insertions, 484 deletions
diff --git a/lua/pl/stringx.lua b/lua/pl/stringx.lua index 6276113..8454f08 100644 --- a/lua/pl/stringx.lua +++ b/lua/pl/stringx.lua @@ -67,7 +67,8 @@ function stringx.isalnum(s) return find(s,'^%w+$') == 1 end ---- does s only contain spaces? +--- does s only contain whitespace? +-- Matches on pattern '%s' so matches space, newline, tabs, etc. -- @string s a string function stringx.isspace(s) assert_string(1,s) @@ -386,29 +387,30 @@ local function _strip(s,left,right,chrs) return sub(s,f,t) end ---- trim any whitespace on the left of s. +--- trim any characters on the left of s. -- @string s the string -- @string[opt='%s'] chrs default any whitespace character, --- but can be a string of characters to be trimmed +-- but can be a string of characters to be trimmed function stringx.lstrip(s,chrs) assert_string(1,s) return _strip(s,true,false,chrs) end lstrip = stringx.lstrip ---- trim any whitespace on the right of s. +--- trim any characters on the right of s. -- @string s the string -- @string[opt='%s'] chrs default any whitespace character, --- but can be a string of characters to be trimmed +-- but can be a string of characters to be trimmed function stringx.rstrip(s,chrs) assert_string(1,s) return _strip(s,false,true,chrs) end ---- trim any whitespace on both left and right of s. +--- trim any characters on both left and right of s. -- @string s the string -- @string[opt='%s'] chrs default any whitespace character, --- but can be a string of characters to be trimmed +-- but can be a string of characters to be trimmed +-- @usage stringx.strip(' --== Hello ==-- ', "- =") --> 'Hello' function stringx.strip(s,chrs) assert_string(1,s) return _strip(s,true,true,chrs) @@ -442,7 +444,7 @@ end --- partition the string using first occurance of a delimiter -- @string s the string --- @string ch delimiter +-- @string ch delimiter (match as plain string, no patterns) -- @return part before ch -- @return ch -- @return part after ch @@ -456,7 +458,7 @@ end --- partition the string p using last occurance of a delimiter -- @string s the string --- @string ch delimiter +-- @string ch delimiter (match as plain string, no patterns) -- @return part before ch -- @return ch -- @return part after ch @@ -514,77 +516,82 @@ end stringx.capitalize = stringx.title -local ellipsis = '...' -local n_ellipsis = #ellipsis - ---- Return a shortened version of a string. --- Fits string within w characters. Removed characters are marked with ellipsis. --- @string s the string --- @int w the maxinum size allowed --- @bool tail true if we want to show the end of the string (head otherwise) --- @usage ('1234567890'):shorten(8) == '12345...' --- @usage ('1234567890'):shorten(8, true) == '...67890' --- @usage ('1234567890'):shorten(20) == '1234567890' -function stringx.shorten(s,w,tail) - assert_string(1,s) - if #s > w then - if w < n_ellipsis then return ellipsis:sub(1,w) end - if tail then - local i = #s - w + 1 + n_ellipsis - return ellipsis .. s:sub(i) - else - return s:sub(1,w-n_ellipsis) .. ellipsis - end - end - return s -end - ---- Utility function that finds any patterns that match a long string's an open or close. --- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with. --- Right now, it simply returns the greatest number of them found. --- @param s The string --- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches. -local function has_lquote(s) - local lstring_pat = '([%[%]])(=*)%1' - local equals, new_equals, _ - local finish = 1 - repeat - _, finish, _, new_equals = s:find(lstring_pat, finish) - if new_equals then - equals = max(equals or 0, #new_equals) - end - until not new_equals - - return equals -end - ---- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. --- @param s The string to be quoted. --- @return The quoted string. -function stringx.quote_string(s) - assert_string(1,s) - -- Find out if there are any embedded long-quote sequences that may cause issues. - -- This is important when strings are embedded within strings, like when serializing. - -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string. - local equal_signs = has_lquote(s .. "]") - - -- Note that strings containing "\r" can't be quoted using long brackets - -- as Lua lexer converts all newlines to "\n" within long strings. - if (s:find("\n") or equal_signs) and not s:find("\r") then - -- If there is an embedded sequence that matches a long quote, then - -- find the one with the maximum number of = signs and add one to that number. - equal_signs = ("="):rep((equal_signs or -1) + 1) - -- Long strings strip out leading newline. We want to retain that, when quoting. - if s:find("^\n") then s = "\n" .. s end - local lbracket, rbracket = - "[" .. equal_signs .. "[", - "]" .. equal_signs .. "]" - s = lbracket .. s .. rbracket - else - -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly. - s = ("%q"):format(s):gsub("\r", "\\r") - end - return s +do + local ellipsis = '...' + local n_ellipsis = #ellipsis + + --- Return a shortened version of a string. + -- Fits string within w characters. Removed characters are marked with ellipsis. + -- @string s the string + -- @int w the maxinum size allowed + -- @bool tail true if we want to show the end of the string (head otherwise) + -- @usage ('1234567890'):shorten(8) == '12345...' + -- @usage ('1234567890'):shorten(8, true) == '...67890' + -- @usage ('1234567890'):shorten(20) == '1234567890' + function stringx.shorten(s,w,tail) + assert_string(1,s) + if #s > w then + if w < n_ellipsis then return ellipsis:sub(1,w) end + if tail then + local i = #s - w + 1 + n_ellipsis + return ellipsis .. s:sub(i) + else + return s:sub(1,w-n_ellipsis) .. ellipsis + end + end + return s + end +end + + +do + -- Utility function that finds any patterns that match a long string's an open or close. + -- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with. + -- Right now, it simply returns the greatest number of them found. + -- @param s The string + -- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches. + local function has_lquote(s) + local lstring_pat = '([%[%]])(=*)%1' + local equals, new_equals, _ + local finish = 1 + repeat + _, finish, _, new_equals = s:find(lstring_pat, finish) + if new_equals then + equals = max(equals or 0, #new_equals) + end + until not new_equals + + return equals + end + + --- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result. + -- @param s The string to be quoted. + -- @return The quoted string. + function stringx.quote_string(s) + assert_string(1,s) + -- Find out if there are any embedded long-quote sequences that may cause issues. + -- This is important when strings are embedded within strings, like when serializing. + -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string. + local equal_signs = has_lquote(s .. "]") + + -- Note that strings containing "\r" can't be quoted using long brackets + -- as Lua lexer converts all newlines to "\n" within long strings. + if (s:find("\n") or equal_signs) and not s:find("\r") then + -- If there is an embedded sequence that matches a long quote, then + -- find the one with the maximum number of = signs and add one to that number. + equal_signs = ("="):rep((equal_signs or -1) + 1) + -- Long strings strip out leading newline. We want to retain that, when quoting. + if s:find("^\n") then s = "\n" .. s end + local lbracket, rbracket = + "[" .. equal_signs .. "[", + "]" .. equal_signs .. "]" + s = lbracket .. s .. rbracket + else + -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly. + s = ("%q"):format(s):gsub("\r", "\\r") + end + return s + end end function stringx.import() diff --git a/spec/stringx_spec.lua b/spec/stringx_spec.lua new file mode 100644 index 0000000..132f5ab --- /dev/null +++ b/spec/stringx_spec.lua @@ -0,0 +1,428 @@ +describe("stringx", function() + + local stringx = require "pl.stringx" + + + it("isalpha()", function() + assert.equal(false, stringx.isalpha '') + assert.equal(false, stringx.isalpha' ') + assert.equal(false, stringx.isalpha'0') + assert.equal(false, stringx.isalpha'\0') + assert.equal(true, stringx.isalpha'azAZ') + assert.equal(false, stringx.isalpha'az9AZ') + end) + + + it("isdigit()", function() + assert.equal(false, stringx.isdigit'') + assert.equal(false, stringx.isdigit' ') + assert.equal(false, stringx.isdigit'a') + assert.equal(true, stringx.isdigit'0123456789') + end) + + + it("isalnum()", function() + assert.equal(false, stringx.isalnum'') + assert.equal(false, stringx.isalnum' ') + assert.equal(true, stringx.isalnum'azAZ01234567890') + end) + + + it("isspace()", function() + assert.equal(false, stringx.isspace'') + assert.equal(true, stringx.isspace' ') + assert.equal(true, stringx.isspace' \r\n\f\t') + assert.equal(false, stringx.isspace' \r\n-\f\t') + end) + + + it("islower()", function() + assert.equal(false, stringx.islower'') + assert.equal(true, stringx.islower'az') + assert.equal(false, stringx.islower'aMz') + assert.equal(true, stringx.islower'a z') + end) + + + it("isupper()", function() + assert.equal(false, stringx.isupper'') + assert.equal(true, stringx.isupper'AZ') + assert.equal(false, stringx.isupper'AmZ') + assert.equal(true, stringx.isupper'A Z') + end) + + + it("startswith()", function() + local startswith = stringx.startswith + assert.equal(true, startswith('', '')) + assert.equal(false, startswith('', 'a')) + assert.equal(true, startswith('a', '')) + assert.equal(true, startswith('a', 'a')) + assert.equal(false, startswith('a', 'b')) + assert.equal(false, startswith('a', 'ab')) + assert.equal(true, startswith('abc', 'ab')) + assert.equal(false, startswith('abc', 'bc')) -- off by one + assert.equal(false, startswith('abc', '.')) -- Lua pattern char + assert.equal(true, startswith('a\0bc', 'a\0b')) -- '\0' + + assert.equal(true, startswith('abcfoo',{'abc','def'})) + assert.equal(true, startswith('deffoo',{'abc','def'})) + assert.equal(false, startswith('cdefoo',{'abc','def'})) + end) + + + it("endswith()", function() + local endswith = stringx.endswith + assert.equal(true, endswith("", "")) + assert.equal(false, endswith("", "a")) + assert.equal(true, endswith("a", "")) + assert.equal(true, endswith("a", "a")) + assert.equal(false, endswith("a", "A")) -- case sensitive + assert.equal(false, endswith("a", "aa")) + assert.equal(true, endswith("abc", "")) + assert.equal(false, endswith("abc", "ab")) -- off by one + assert.equal(true, endswith("abc", "c")) + assert.equal(true, endswith("abc", "bc")) + assert.equal(true, endswith("abc", "abc")) + assert.equal(false, endswith("abc", " abc")) + assert.equal(false, endswith("abc", "a")) + assert.equal(false, endswith("abc", ".")) -- Lua pattern char + assert.equal(true, endswith("ab\0c", "b\0c")) -- \0 + assert.equal(false, endswith("ab\0c", "b\0d")) -- \0 + + assert.equal(true, endswith('dollar.dot',{'.dot','.txt'})) + assert.equal(true, endswith('dollar.txt',{'.dot','.txt'})) + assert.equal(false, endswith('dollar.rtxt',{'.dot','.txt'})) + end) + + + it("join()", function() + assert.equal('1 2 3', stringx.join(' ', {1,2,3})) + end) + + + it("splitlines", function() + assert.same({}, stringx.splitlines('')) + assert.same({'a'}, stringx.splitlines('a')) + assert.same({''}, stringx.splitlines('\n')) + assert.same({'', ''}, stringx.splitlines('\n\n')) + assert.same({'', ''}, stringx.splitlines('\r\r')) + assert.same({''}, stringx.splitlines('\r\n')) + assert.same({'ab', 'cd'}, stringx.splitlines('ab\ncd\n')) + assert.same({'ab\n', 'cd\n'}, stringx.splitlines('ab\ncd\n', true)) + assert.same({'\n', 'ab\r', '\r\n', 'cd\n'}, stringx.splitlines('\nab\r\r\ncd\n', true)) + end) + + + it("split()", function() + local split = stringx.split + assert.same({''}, split('', '')) + assert.same({}, split('', 'z')) --FIX:intended and specified behavior? + assert.same({'a'}, split('a', '')) --FIX:intended and specified behavior? + assert.same({''}, split('a', 'a')) + -- stringx.split now follows the Python pattern, so it uses a substring, not a pattern. + -- If you need to split on a pattern, use utils.split() + -- asserteq(split('ab1cd23ef%d', '%d+'), {'ab', 'cd', 'ef%d'}) -- pattern chars + -- note that leading space is ignored by the default + assert.same({'1','2','3'}, split(' 1 2 3 ')) + assert.same({'a','bb','c','ddd'}, split('a*bb*c*ddd','*')) + assert.same({'dog','fred','bonzo:alice'}, split('dog:fred:bonzo:alice',':',3)) + assert.same({'dog','fred','bonzo:alice:'}, split('dog:fred:bonzo:alice:',':',3)) + assert.same({'','','',''}, split('///','/')) + end) + + + it("expandtabs()", function() + assert.equal('', stringx.expandtabs('',0)) + assert.equal('', stringx.expandtabs('',1)) + assert.equal(' ', stringx.expandtabs(' ',1)) + assert.equal((' '):rep(1+8), stringx.expandtabs(' \t ')) + assert.equal((' '):rep(3), stringx.expandtabs(' \t ',2)) + assert.equal((' '):rep(2), stringx.expandtabs(' \t ',0)) + end) + + + it("lfind()", function() + assert.equal(1, stringx.lfind('', '')) + assert.equal(1, stringx.lfind('a', '')) + assert.equal(2, stringx.lfind('ab', 'b')) + assert.is_nil(stringx.lfind('abc', 'cd')) + assert.equal(2, stringx.lfind('abcbc', 'bc')) + assert.equal(3, stringx.lfind('ab..cd', '.')) -- pattern char + assert.equal(4, stringx.lfind('abcbcbbc', 'bc', 3)) + assert.is_nil(stringx.lfind('abcbcbbc', 'bc', 3, 4)) + assert.equal(4, stringx.lfind('abcbcbbc', 'bc', 3, 5)) + assert.equal(2, stringx.lfind('abcbcbbc', 'bc', nil, 5)) + end) + + + it("rfind()", function() + assert.equal(1, stringx.rfind('', '')) + assert.equal(3, stringx.rfind('ab', '')) + assert.is_nil(stringx.rfind('abc', 'cd')) + assert.equal(4, stringx.rfind('abcbc', 'bc')) + assert.equal(4, stringx.rfind('abcbcb', 'bc')) + assert.equal(4, stringx.rfind('ab..cd', '.')) -- pattern char + assert.equal(7, stringx.rfind('abcbcbbc', 'bc', 3)) + assert.is_nil(stringx.rfind('abcbcbbc', 'bc', 3, 4)) + assert.equal(4, stringx.rfind('abcbcbbc', 'bc', 3, 5)) + assert.equal(4, stringx.rfind('abcbcbbc', 'bc', nil, 5)) + assert.equal(4, stringx.rfind('banana', 'ana')) + end) + + + it("replace()", function() + assert.equal('', stringx.replace('', '', '')) + assert.equal(' ', stringx.replace(' ', '', '')) + assert.equal(' ', stringx.replace(' ', '', ' ')) + assert.equal('', stringx.replace(' ', ' ', '')) + assert.equal('aBCaBCaBC', stringx.replace('abcabcabc', 'bc', 'BC')) + assert.equal('aBCabcabc', stringx.replace('abcabcabc', 'bc', 'BC', 1)) + assert.equal('abcabcabc', stringx.replace('abcabcabc', 'bc', 'BC', 0)) + assert.equal('abc', stringx.replace('abc', 'd', 'e')) + assert.equal('a%db', stringx.replace('a.b', '.', '%d')) + end) + + + it("count()", function() + assert.equal(0, stringx.count('', '')) --infinite loop]] + assert.equal(2, stringx.count(' ', '')) --infinite loop]] + assert.equal(2, stringx.count('a..c', '.')) -- pattern chars + assert.equal(0, stringx.count('a1c', '%d')) -- pattern chars + assert.equal(3, stringx.count('Anna Anna Anna', 'Anna')) -- no overlap + assert.equal(1, stringx.count('banana', 'ana', false)) -- no overlap + assert.equal(2, stringx.count('banana', 'ana', true)) -- overlap + end) + + + it("ljust()", function() + assert.equal('', stringx.ljust('', 0)) + assert.equal(' ', stringx.ljust('', 2)) + assert.equal('ab ', stringx.ljust('ab', 3)) + assert.equal('ab%', stringx.ljust('ab', 3, '%')) + assert.equal('abcd', stringx.ljust('abcd', 3)) -- agrees with Python + end) + + + it("rjust()", function() + assert.equal('', stringx.rjust('', 0)) + assert.equal(' ', stringx.rjust('', 2)) + assert.equal(' ab', stringx.rjust('ab', 3)) + assert.equal('%ab', stringx.rjust('ab', 3, '%')) + assert.equal('abcd', stringx.rjust('abcd', 3)) -- agrees with Python + end) + + + it("center()", function() + assert.equal('', stringx.center('', 0)) + assert.equal(' ', stringx.center('', 1)) + assert.equal(' ', stringx.center('', 2)) + assert.equal('a', stringx.center('a', 1)) + assert.equal('a ', stringx.center('a', 2)) + assert.equal(' a ', stringx.center('a', 3)) + end) + + + it("lstrip()", function() + local trim = stringx.lstrip + assert.equal('', trim'') + assert.equal('', trim' ') + assert.equal('', trim' ') + assert.equal('a', trim'a') + assert.equal('a', trim' a') + assert.equal('a ', trim'a ') + assert.equal('a ', trim' a ') + assert.equal('a ', trim' a ') + assert.equal('ab cd ', trim' ab cd ') + assert.equal('a\000b \r\t\n\f\v', trim' \t\r\n\f\va\000b \r\t\n\f\v') + assert.equal('hello] -- - ', trim(' - -- [hello] -- - ','-[] ')) + end) + + + it("rstrip()", function() + local trim = stringx.rstrip + assert.equal('', trim'') + assert.equal('', trim' ') + assert.equal('', trim' ') + assert.equal('a', trim'a') + assert.equal(' a', trim' a') + assert.equal('a', trim'a ') + assert.equal(' a', trim' a ') + assert.equal(' a', trim' a ') + assert.equal(' ab cd', trim' ab cd ') + assert.equal(' \t\r\n\f\va\000b', trim' \t\r\n\f\va\000b \r\t\n\f\v') + assert.equal(' - -- [hello', trim(' - -- [hello] -- - ','-[] ')) + end) + + + it("strip()", function() + local trim = stringx.strip + assert.equal('', trim'') + assert.equal('', trim' ') + assert.equal('', trim' ') + assert.equal('a', trim'a') + assert.equal('a', trim' a') + assert.equal('a', trim'a ') + assert.equal('a', trim' a ') + assert.equal('a', trim' a ') + assert.equal('ab cd', trim' ab cd ') + assert.equal('a\000b', trim' \t\r\n\f\va\000b \r\t\n\f\v') + assert.equal('hello', trim(' - -- [hello] -- - ','-[] ')) + local long = 'a' .. string.rep(' ', 200000) .. 'a' + assert.equal(long, trim(long)) + end) + + it("splitv()", function() + -- is actually 'utils.splitv' + assert.same({"hello", "dolly"}, {stringx.splitv("hello dolly")}) + end) + + + it("partition()", function() + assert.has.error(function() + stringx.partition('a', '') + end) + assert.same({'', 'a', ''}, {stringx.partition('a', 'a')}) + assert.same({'a', 'b', 'c'}, {stringx.partition('abc', 'b')}) + assert.same({'abc','',''}, {stringx.partition('abc', '.+')}) + assert.same({'ab','.','c'}, {stringx.partition('ab.c', '.')}) + assert.same({'a',',','b,c'}, {stringx.partition('a,b,c', ',')}) + assert.same({'abc', '', ''}, {stringx.partition('abc', '/')}) + end) + + + it("rpartition()", function() + assert.has.error(function() + stringx.rpartition('a', '') + end) + assert.same({'a/b', '/', 'c'}, {stringx.rpartition('a/b/c', '/')}) + assert.same({'a', 'b', 'c'}, {stringx.rpartition('abc', 'b')}) + assert.same({'', 'a', ''}, {stringx.rpartition('a', 'a')}) + assert.same({'', '', 'abc'}, {stringx.rpartition('abc', '/')}) + end) + + + it("at()", function() + -- at (works like s:sub(idx,idx), so negative indices allowed + assert.equal('a', stringx.at('a', 1)) + assert.equal('b', stringx.at('ab', 2)) + assert.equal('d', stringx.at('abcd', -1)) + assert.equal('', stringx.at('abcd', 10)) -- not found + end) + + + it("lines()", function() + local function merge(it, ...) + assert(select('#', ...) == 0) + local ts = {} + for val in it do ts[#ts+1] = val end + return ts + end + assert.same({''}, merge(stringx.lines(''))) + assert.same({'ab'}, merge(stringx.lines('ab'))) + assert.same({'ab', 'cd'}, merge(stringx.lines('ab\ncd'))) + end) + + + it("title()", function() + assert.equal('', stringx.title('')) + assert.equal('Abc Def1', stringx.title('abC deF1')) -- Python behaviour + assert.equal('Hello World', stringx.capitalize('hello world')) + end) + + + it("capitalize()", function() + -- old name for 'title' + assert.equal(stringx.title, stringx.capitalize) + end) + + + it("shorten()", function() + assert.equal('', stringx.shorten('', 0)) + assert.equal('a', stringx.shorten('a', 1)) + assert.equal('.', stringx.shorten('ab', 1)) --FIX:ok? + assert.equal('abc', stringx.shorten('abc', 3)) + assert.equal('...', stringx.shorten('abcd', 3)) + assert.equal('abcde', stringx.shorten('abcde', 5)) + assert.equal('a...', stringx.shorten('abcde', 4)) + assert.equal('...', stringx.shorten('abcde', 3)) + assert.equal('..', stringx.shorten('abcde', 2)) + assert.equal('', stringx.shorten('abcde', 0)) + assert.equal('', stringx.shorten('', 0, true)) + assert.equal('a', stringx.shorten('a', 1, true)) + assert.equal('.', stringx.shorten('ab', 1, true)) + assert.equal('abcde', stringx.shorten('abcde', 5, true)) + assert.equal('...e', stringx.shorten('abcde', 4, true)) + assert.equal('...', stringx.shorten('abcde', 3, true)) + assert.equal('..', stringx.shorten('abcde', 2, true)) + assert.equal('', stringx.shorten('abcde', 0, true)) + end) + + + it("quote_string()", function() + local assert_str_round_trip = function(s) + + local qs = stringx.quote_string(s) + local compiled, err = require("pl.utils").load("return "..qs) + + if not compiled then + print( + ("stringx.quote_string assert failed: invalid string created: Received:\n%s\n\nCompiled to\n%s\n\nError:\t%s\n"): + format(s, qs, err) + ) + error() + else + compiled = compiled() + end + + if compiled ~= s then + print("stringx.quote_string assert Failed: String compiled but did not round trip.") + print("input string:\t\t",s, #s) + print("compiled string:\t", compiled, #compiled) + print("output string:\t\t",qs, #qs) + error() + -- else + -- print("input string:\t\t",s) + -- print("compiled string:\t", compiled) + -- print("output string:\t\t",qs) + end + end + + assert_str_round_trip( "normal string with nothing weird.") + assert_str_round_trip( "Long string quoted with escaped quote \\\" and a long string pattern match [==[ found near the end.") + + assert_str_round_trip( "Unescapped quote \" in the middle") + assert_str_round_trip( "[[Embedded long quotes \\\". Escaped must stay! ]]") + assert_str_round_trip( [[Long quoted string with a slash prior to quote \\\". ]]) + assert_str_round_trip( "[[Completely normal\n long quote. ]]") + assert_str_round_trip( "String with a newline\nending with a closing bracket]") + assert_str_round_trip( "[[String with opening brackets ending with part of a long closing bracket]=") + assert_str_round_trip( "\n[[Completely normal\n long quote. Except that we lead with a return! Tricky! ]]") + assert_str_round_trip( '"balance [======[ doesn\'t ]====] mater when searching for embedded long-string quotes.') + assert_str_round_trip( "Any\0 \t control character other than a return will be handled by the %q mechanism.") + assert_str_round_trip( "This\tincludes\ttabs.") + assert_str_round_trip( "But not returns.\n Returns are easier to see using long quotes.") + assert_str_round_trip( "The \z escape does not trigger a control pattern, however.") + + assert_str_round_trip( "[==[If a string is long-quoted, escaped \\\" quotes have to stay! ]==]") + assert_str_round_trip('"A quoted string looks like what?"') + assert_str_round_trip( "'I think that it should be quoted, anyway.'") + assert_str_round_trip( "[[Even if they're long quoted.]]") + assert_str_round_trip( "]=]==]") + + assert_str_round_trip( "\"\\\"\\' pathalogical:starts with a quote ]\"\\']=]]==][[]]]=========]") + assert_str_round_trip( "\\\"\\\"\\' pathalogical: quote is after this text with a quote ]\"\\']=]]==][[]]]=========]") + assert_str_round_trip( "\\\"\\\"\\' pathalogical: quotes are all escaped. ]\\\"\\']=]]==][[]]]=========]") + assert_str_round_trip( "") + assert_str_round_trip( " ") + assert_str_round_trip( "\n") --tricky. + assert_str_round_trip( "\r") + assert_str_round_trip( "\r\n") + assert_str_round_trip( "\r1\n") + assert_str_round_trip( "[[") + assert_str_round_trip( "''") + assert_str_round_trip( '""') + end) + +end) + diff --git a/spec/text_spec.lua b/spec/text_spec.lua index b69bf66..b823768 100644 --- a/spec/text_spec.lua +++ b/spec/text_spec.lua @@ -208,8 +208,8 @@ three end) it("word-wraps a text", function() - local binstring = require("luassert.formatters.binarystring") - assert:add_formatter(binstring) + -- local binstring = require("luassert.formatters.binarystring") + -- assert:add_formatter(binstring) assert.equal([[ It is often said of Lua that it does not @@ -310,4 +310,3 @@ hello "world" 'this' -is- a bb ccc dddd test... but wouldn't it pass??? fin end) end) - diff --git a/tests/test-stringx.lua b/tests/test-stringx.lua deleted file mode 100644 index 20ebe17..0000000 --- a/tests/test-stringx.lua +++ /dev/null @@ -1,381 +0,0 @@ -local stringx = require 'pl.stringx' -local utils = require 'pl.utils' -local asserteq = require 'pl.test' . asserteq -local T = require 'pl.test'.tuple - -local function FIX(s) - io.stderr:write('FIX:' .. s .. '\n') -end - - --- isalpha -asserteq(T(stringx.isalpha''), T(false)) -asserteq(T(stringx.isalpha' '), T(false)) -asserteq(T(stringx.isalpha'0'), T(false)) -asserteq(T(stringx.isalpha'\0'), T(false)) -asserteq(T(stringx.isalpha'azAZ'), T(true)) -asserteq(T(stringx.isalpha'az9AZ'), T(false)) - --- isdigit -asserteq(T(stringx.isdigit''), T(false)) -asserteq(T(stringx.isdigit' '), T(false)) -asserteq(T(stringx.isdigit'a'), T(false)) -asserteq(T(stringx.isdigit'0123456789'), T(true)) - --- isalnum -asserteq(T(stringx.isalnum''), T(false)) -asserteq(T(stringx.isalnum' '), T(false)) -asserteq(T(stringx.isalnum('azAZ01234567890')), T(true)) - --- isspace -asserteq(T(stringx.isspace''), T(false)) -asserteq(T(stringx.isspace' '), T(true)) -asserteq(T(stringx.isspace' \r\n\f\t'), T(true)) -asserteq(T(stringx.isspace' \r\n-\f\t'), T(false)) - --- islower -asserteq(T(stringx.islower''), T(false)) -asserteq(T(stringx.islower'az'), T(true)) -asserteq(T(stringx.islower'aMz'), T(false)) -asserteq(T(stringx.islower'a z'), T(true)) - --- isupper -asserteq(T(stringx.isupper''), T(false)) -asserteq(T(stringx.isupper'AZ'), T(true)) -asserteq(T(stringx.isupper'AmZ'), T(false)) -asserteq(T(stringx.isupper'A Z'), T(true)) - --- startswith -local startswith = stringx.startswith -asserteq(T(startswith('', '')), T(true)) -asserteq(T(startswith('', 'a')), T(false)) -asserteq(T(startswith('a', '')), T(true)) -asserteq(T(startswith('a', 'a')), T(true)) -asserteq(T(startswith('a', 'b')), T(false)) -asserteq(T(startswith('a', 'ab')), T(false)) -asserteq(T(startswith('abc', 'ab')), T(true)) -asserteq(T(startswith('abc', 'bc')), T(false)) -- off by one -asserteq(T(startswith('abc', '.')), T(false)) -- Lua pattern char -asserteq(T(startswith('a\0bc', 'a\0b')), T(true)) -- '\0' - -asserteq(startswith('abcfoo',{'abc','def'}),true) -asserteq(startswith('deffoo',{'abc','def'}),true) -asserteq(startswith('cdefoo',{'abc','def'}),false) - - --- endswith --- http://snippets.luacode.org/sputnik.lua?p=snippets/Check_string_ends_with_other_string_74 -local endswith = stringx.endswith -asserteq(T(endswith("", "")), T(true)) -asserteq(T(endswith("", "a")), T(false)) -asserteq(T(endswith("a", "")), T(true)) -asserteq(T(endswith("a", "a")), T(true)) -asserteq(T(endswith("a", "A")), T(false)) -- case sensitive -asserteq(T(endswith("a", "aa")), T(false)) -asserteq(T(endswith("abc", "")), T(true)) -asserteq(T(endswith("abc", "ab")), T(false)) -- off by one -asserteq(T(endswith("abc", "c")), T(true)) -asserteq(T(endswith("abc", "bc")), T(true)) -asserteq(T(endswith("abc", "abc")), T(true)) -asserteq(T(endswith("abc", " abc")), T(false)) -asserteq(T(endswith("abc", "a")), T(false)) -asserteq(T(endswith("abc", ".")), T(false)) -- Lua pattern char -asserteq(T(endswith("ab\0c", "b\0c")), T(true)) -- \0 -asserteq(T(endswith("ab\0c", "b\0d")), T(false)) -- \0 - -asserteq(endswith('dollar.dot',{'.dot','.txt'}),true) -asserteq(endswith('dollar.txt',{'.dot','.txt'}),true) -asserteq(endswith('dollar.rtxt',{'.dot','.txt'}),false) - --- join -asserteq(stringx.join(' ', {1,2,3}), '1 2 3') - --- splitlines -asserteq(stringx.splitlines(''), {}) -asserteq(stringx.splitlines('a'), {'a'}) -asserteq(stringx.splitlines('\n'), {''}) -asserteq(stringx.splitlines('\n\n'), {'', ''}) -asserteq(stringx.splitlines('\r\r'), {'', ''}) -asserteq(stringx.splitlines('\r\n'), {''}) -asserteq(stringx.splitlines('ab\ncd\n'), {'ab', 'cd'}) -asserteq(stringx.splitlines('ab\ncd\n', true), {'ab\n', 'cd\n'}) -asserteq(stringx.splitlines('\nab\r\r\ncd\n', true), {'\n', 'ab\r', '\r\n', 'cd\n'}) - --- expandtabs ----FIX[[raises error -asserteq(T(stringx.expandtabs('',0)), T('')) -asserteq(T(stringx.expandtabs('',1)), T('')) -asserteq(T(stringx.expandtabs(' ',1)), T(' ')) --- expandtabs now works like Python's str.expandtabs (up to next tab stop) -asserteq(T(stringx.expandtabs(' \t ')), T((' '):rep(1+8))) -asserteq(T(stringx.expandtabs(' \t ',2)), T(' ')) ---]] - --- lfind -asserteq(T(stringx.lfind('', '')), T(1)) -asserteq(T(stringx.lfind('a', '')), T(1)) -asserteq(T(stringx.lfind('ab', 'b')), T(2)) -asserteq(T(stringx.lfind('abc', 'cd')), T(nil)) -asserteq(T(stringx.lfind('abcbc', 'bc')), T(2)) -asserteq(T(stringx.lfind('ab..cd', '.')), T(3)) -- pattern char -asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3)), T(4)) -asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3, 4)), T(nil)) -asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3, 5)), T(4)) -asserteq(T(stringx.lfind('abcbcbbc', 'bc', nil, 5)), T(2)) - --- rfind -asserteq(T(stringx.rfind('', '')), T(1)) -asserteq(T(stringx.rfind('ab', '')), T(3)) -asserteq(T(stringx.rfind('abc', 'cd')), T(nil)) -asserteq(T(stringx.rfind('abcbc', 'bc')), T(4)) -asserteq(T(stringx.rfind('abcbcb', 'bc')), T(4)) -asserteq(T(stringx.rfind('ab..cd', '.')), T(4)) -- pattern char -asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3)), T(7)) -asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3, 4)), T(nil)) -asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3, 5)), T(4)) -asserteq(T(stringx.rfind('abcbcbbc', 'bc', nil, 5)), T(4)) -asserteq(T(stringx.rfind('banana', 'ana')), T(4)) - --- replace -asserteq(T(stringx.replace('', '', '')), T('')) -asserteq(T(stringx.replace(' ', '', '')), T(' ')) -asserteq(T(stringx.replace(' ', '', ' ')), T(' ')) -asserteq(T(stringx.replace(' ', ' ', '')), T('')) -asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC')), T('aBCaBCaBC')) -asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC', 1)), T('aBCabcabc')) -asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC', 0)), T('abcabcabc')) -asserteq(T(stringx.replace('abc', 'd', 'e')), T('abc')) -asserteq(T(stringx.replace('a.b', '.', '%d')), T('a%db')) - --- split -local split = stringx.split -asserteq(split('', ''), {''}) -asserteq(split('', 'z'), {}) --FIX:intended and specified behavior? -asserteq(split('a', ''), {'a'}) --FIX:intended and specified behavior? -asserteq(split('a', 'a'), {''}) --- stringx.split now follows the Python pattern, so it uses a substring, not a pattern. --- If you need to split on a pattern, use utils.split() --- asserteq(split('ab1cd23ef%d', '%d+'), {'ab', 'cd', 'ef%d'}) -- pattern chars --- note that leading space is ignored by the default -asserteq(split(' 1 2 3 '),{'1','2','3'}) -asserteq(split('a*bb*c*ddd','*'),{'a','bb','c','ddd'}) -asserteq(split('dog:fred:bonzo:alice',':',3), {'dog','fred','bonzo:alice'}) -asserteq(split('dog:fred:bonzo:alice:',':',3), {'dog','fred','bonzo:alice:'}) -asserteq(split('///','/'),{'','','',''}) --- capitalize -asserteq(T(stringx.capitalize('')), T('')) -asserteq(T(stringx.capitalize('abC deF1')), T('Abc Def1')) -- Python behaviour - --- count -asserteq(T(stringx.count('', '')), T(0)) --infinite loop]] -asserteq(T(stringx.count(' ', '')), T(2)) --infinite loop]] -asserteq(T(stringx.count('a..c', '.')), T(2)) -- pattern chars -asserteq(T(stringx.count('a1c', '%d')), T(0)) -- pattern chars -asserteq(T(stringx.count('Anna Anna Anna', 'Anna')), T(3)) -- no overlap -asserteq(T(stringx.count('banana', 'ana', false)), T(1)) -- no overlap -asserteq(T(stringx.count('banana', 'ana', true)), T(2)) -- overlap - --- ljust -asserteq(T(stringx.ljust('', 0)), T('')) -asserteq(T(stringx.ljust('', 2)), T(' ')) -asserteq(T(stringx.ljust('ab', 3)), T('ab ')) -asserteq(T(stringx.ljust('ab', 3, '%')), T('ab%')) -asserteq(T(stringx.ljust('abcd', 3)), T('abcd')) -- agrees with Python - --- rjust -asserteq(T(stringx.rjust('', 0)), T('')) -asserteq(T(stringx.rjust('', 2)), T(' ')) -asserteq(T(stringx.rjust('ab', 3)), T(' ab')) -asserteq(T(stringx.rjust('ab', 3, '%')), T('%ab')) -asserteq(T(stringx.rjust('abcd', 3)), T('abcd')) -- agrees with Python - --- center -asserteq(T(stringx.center('', 0)), T('')) -asserteq(T(stringx.center('', 1)), T(' ')) -asserteq(T(stringx.center('', 2)), T(' ')) -asserteq(T(stringx.center('a', 1)), T('a')) -asserteq(T(stringx.center('a', 2)), T('a ')) -asserteq(T(stringx.center('a', 3)), T(' a ')) - - --- ltrim --- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 -local trim = stringx.lstrip -asserteq(T(trim''), T'') -asserteq(T(trim' '), T'') -asserteq(T(trim' '), T'') -asserteq(T(trim'a'), T'a') -asserteq(T(trim' a'), T'a') -asserteq(T(trim'a '), T'a ') -asserteq(T(trim' a '), T'a ') -asserteq(T(trim' a '), T'a ') -asserteq(T(trim' ab cd '), T'ab cd ') -asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T'a\000b \r\t\n\f\v') --- more - - --- rtrim --- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 -local trim = stringx.rstrip -asserteq(T(trim''), T'') -asserteq(T(trim' '), T'') -asserteq(T(trim' '), T'') -asserteq(T(trim'a'), T'a') -asserteq(T(trim' a'), T' a') -asserteq(T(trim'a '), T'a') -asserteq(T(trim' a '), T' a') -asserteq(T(trim' a '), T' a') -asserteq(T(trim' ab cd '), T' ab cd') -asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T' \t\r\n\f\va\000b') --- more - - --- trim --- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 -local trim = stringx.strip -asserteq(T(trim''), T'') -asserteq(T(trim' '), T'') -asserteq(T(trim' '), T'') -asserteq(T(trim'a'), T'a') -asserteq(T(trim' a'), T'a') -asserteq(T(trim'a '), T'a') -asserteq(T(trim' a '), T'a') -asserteq(T(trim' a '), T'a') -asserteq(T(trim' ab cd '), T'ab cd') -asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T'a\000b') -local long = 'a' .. string.rep(' ', 200000) .. 'a' -asserteq(T(trim(long)), T(long)) --- more - - -asserteq({stringx.splitv("hello dolly")}, {"hello", "dolly"}) - - --- partition --- as per str.partition in Python, delimiter must be non-empty; --- interpreted as a plain string ---asserteq(T(stringx.partition('', '')), T('', '', '')) -- error]] ---asserteq(T(stringx.partition('a', '')), T('', '', 'a')) --error]] -asserteq(T(stringx.partition('a', 'a')), T('', 'a', '')) -asserteq(T(stringx.partition('abc', 'b')), T('a', 'b', 'c')) -asserteq(T(stringx.partition('abc', '.+')), T('abc','','')) -asserteq(T(stringx.partition('a,b,c', ',')), T('a',',','b,c')) -asserteq(T(stringx.partition('abc', '/')), T('abc', '', '')) --- rpartition -asserteq(T(stringx.rpartition('a/b/c', '/')), T('a/b', '/', 'c')) -asserteq(T(stringx.rpartition('abc', 'b')), T('a', 'b', 'c')) -asserteq(T(stringx.rpartition('a', 'a')), T('', 'a', '')) -asserteq(T(stringx.rpartition('abc', '/')), T('', '', 'abc')) - - --- at (works like s:sub(idx,idx), so negative indices allowed -asserteq(T(stringx.at('a', 1)), T('a')) -asserteq(T(stringx.at('ab', 2)), T('b')) -asserteq(T(stringx.at('abcd', -1)), T('d')) -asserteq(T(stringx.at('abcd', 10)), T('')) -- not found - --- lines -local function merge(it, ...) - assert(select('#', ...) == 0) - local ts = {} - for val in it do ts[#ts+1] = val end - return ts -end -asserteq(merge(stringx.lines('')), {''}) -asserteq(merge(stringx.lines('ab')), {'ab'}) -asserteq(merge(stringx.lines('ab\ncd')), {'ab', 'cd'}) - -asserteq(stringx.capitalize("hello world"), "Hello World") -asserteq(stringx.title("hello world"), "Hello World") - --- shorten --- The returned string is always equal or less to the given size. -asserteq(T(stringx.shorten('', 0)), T'') -asserteq(T(stringx.shorten('a', 1)), T'a') -asserteq(T(stringx.shorten('ab', 1)), T'.') --FIX:ok? -asserteq(T(stringx.shorten('abc', 3)), T'abc') -asserteq(T(stringx.shorten('abcd', 3)), T'...') -asserteq(T(stringx.shorten('abcde', 5)), T'abcde') -asserteq(T(stringx.shorten('abcde', 4)), T'a...') -asserteq(T(stringx.shorten('abcde', 3)), T'...') -asserteq(T(stringx.shorten('abcde', 2)), T'..') -asserteq(T(stringx.shorten('abcde', 0)), T'') -asserteq(T(stringx.shorten('', 0, true)), T'') -asserteq(T(stringx.shorten('a', 1, true)), T'a') -asserteq(T(stringx.shorten('ab', 1, true)), T'.') -asserteq(T(stringx.shorten('abcde', 5, true)), T'abcde') -asserteq(T(stringx.shorten('abcde', 4, true)), T'...e') -asserteq(T(stringx.shorten('abcde', 3, true)), T'...') -asserteq(T(stringx.shorten('abcde', 2, true)), T'..') -asserteq(T(stringx.shorten('abcde', 0, true)), T'') - --- strip -asserteq(stringx.strip(' hello '),'hello') -asserteq(stringx.strip('--[hello] -- - ','-[] '),'hello') -asserteq(stringx.rstrip('--[hello] -- - ','-[] '),'--[hello') -asserteq(stringx.strip('hello'..((" "):rep(500))), "hello") - --- - -local assert_str_round_trip = function(s) - - local qs = stringx.quote_string(s) - local compiled, err = utils.load("return "..qs) - - if not compiled then - print( - ("stringx.quote_string assert failed: invalid string created: Received:\n%s\n\nCompiled to\n%s\n\nError:\t%s\n"): - format(s, qs, err) - ) - error() - else - compiled = compiled() - end - - if compiled ~= s then - print("stringx.quote_string assert Failed: String compiled but did not round trip.") - print("input string:\t\t",s, #s) - print("compiled string:\t", compiled, #compiled) - print("output string:\t\t",qs, #qs) - error() - else - -- print("input string:\t\t",s) - -- print("compiled string:\t", compiled) - -- print("output string:\t\t",qs) - end -end - -assert_str_round_trip( "normal string with nothing weird.") -assert_str_round_trip( "Long string quoted with escaped quote \\\" and a long string pattern match [==[ found near the end.") - -assert_str_round_trip( "Unescapped quote \" in the middle") -assert_str_round_trip( "[[Embedded long quotes \\\". Escaped must stay! ]]") -assert_str_round_trip( [[Long quoted string with a slash prior to quote \\\". ]]) -assert_str_round_trip( "[[Completely normal\n long quote. ]]") -assert_str_round_trip( "String with a newline\nending with a closing bracket]") -assert_str_round_trip( "[[String with opening brackets ending with part of a long closing bracket]=") -assert_str_round_trip( "\n[[Completely normal\n long quote. Except that we lead with a return! Tricky! ]]") -assert_str_round_trip( '"balance [======[ doesn\'t ]====] mater when searching for embedded long-string quotes.') -assert_str_round_trip( "Any\0 \t control character other than a return will be handled by the %q mechanism.") -assert_str_round_trip( "This\tincludes\ttabs.") -assert_str_round_trip( "But not returns.\n Returns are easier to see using long quotes.") -assert_str_round_trip( "The \z escape does not trigger a control pattern, however.") - -assert_str_round_trip( "[==[If a string is long-quoted, escaped \\\" quotes have to stay! ]==]") -assert_str_round_trip('"A quoted string looks like what?"') -assert_str_round_trip( "'I think that it should be quoted, anyway.'") -assert_str_round_trip( "[[Even if they're long quoted.]]") -assert_str_round_trip( "]=]==]") - -assert_str_round_trip( "\"\\\"\\' pathalogical:starts with a quote ]\"\\']=]]==][[]]]=========]") -assert_str_round_trip( "\\\"\\\"\\' pathalogical: quote is after this text with a quote ]\"\\']=]]==][[]]]=========]") -assert_str_round_trip( "\\\"\\\"\\' pathalogical: quotes are all escaped. ]\\\"\\']=]]==][[]]]=========]") -assert_str_round_trip( "") -assert_str_round_trip( " ") -assert_str_round_trip( "\n") --tricky. -assert_str_round_trip( "\r") -assert_str_round_trip( "\r\n") -assert_str_round_trip( "\r1\n") -assert_str_round_trip( "[[") -assert_str_round_trip( "''") -assert_str_round_trip( '""') diff --git a/tests/test-stringx2.lua b/tests/test-stringx2.lua deleted file mode 100644 index c690e58..0000000 --- a/tests/test-stringx2.lua +++ /dev/null @@ -1,20 +0,0 @@ -local asserteq = require 'pl.test' . asserteq - - --- strings --- -require 'pl.stringx'.import() ---> convenient! -local s = '123' -assert (s:isdigit()) -assert (not s:isspace()) -s = 'here the dog is just a dog' -assert (s:startswith('here')) -assert (s:endswith('dog')) -assert (s:count('dog') == 2) -s = ' here we go ' -asserteq (s:lstrip() , 'here we go ') -asserteq (s:rstrip() , ' here we go') -asserteq (s:strip() , 'here we go') -asserteq (('hello'):center(20,'+') , '+++++++hello++++++++') - -asserteq (('hello dolly'):title() , 'Hello Dolly') -asserteq (('h bk bonzo TOK fred m'):title() , 'H Bk Bonzo Tok Fred M') |