add dfhack string functions and tests

added string:split
added string:trim
added a default value for wrap width
added function comments
added tests for all string functions (the tests for string:split will be commented out until we remove the competing implementation in gui/load-screen
develop
myk002 2021-07-02 13:21:54 -07:00
parent 0077b51646
commit 36b2d05ff6
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
2 changed files with 74 additions and 2 deletions

@ -361,19 +361,53 @@ function safe_index(obj,idx,...)
end end
end end
-- String class extentions
-- prefix is a literal string, not a pattern
function string:startswith(prefix) function string:startswith(prefix)
return self:sub(1, #prefix) == prefix return self:sub(1, #prefix) == prefix
end end
-- suffix is a literal string, not a pattern
function string:endswith(suffix) function string:endswith(suffix)
return self:sub(-#suffix) == suffix or #suffix == 0 return self:sub(-#suffix) == suffix or #suffix == 0
end end
-- Split a string by the given delimiter. If no delimiter is specified, space
-- (' ') is used. The delimter is treated as a pattern unless a <plain> is
-- specified and set to true. To treat multiple successive delimiter characters
-- as a single delimiter, e.g. to avoid getting empty string elements, pass a
-- pattern like ' +'. Be aware that passing patterns that match empty strings
-- (like ' *') will result in improper string splits.
function string:split(delimiter, plain)
delimiter = delimiter or ' '
local result = {}
local from = 1
local delim_from, delim_to = self:find(delimiter, from, plain)
-- delim_from will be greater than delim_to when the delimiter matches the
-- empty string, which would lead to an infinite loop if we didn't check it
while delim_from and delim_from <= delim_to do
table.insert(result, self:sub(from, delim_from - 1))
from = delim_to + 1
delim_from, delim_to = self:find(delimiter, from, plain)
end
table.insert(result, self:sub(from))
return result
end
-- Removes spaces (i.e. everything that matches '%s') from the start and end of
-- a string. Spaces between non-space characters are left untouched.
function string:trim()
local _, _, content = self:find('^%s*(.-)%s*$')
return content
end
-- Inserts newlines into a string so no individual line exceeds the given width. -- Inserts newlines into a string so no individual line exceeds the given width.
-- Lines are split at space-separated word boundaries. Any existing newlines are -- Lines are split at space-separated word boundaries. Any existing newlines are
-- kept in place. If a single word is longer than width, it is split over -- kept in place. If a single word is longer than width, it is split over
-- multiple lines. -- multiple lines. If width is not specified, 72 is used.
function string:wrap(width) function string:wrap(width)
width = width or 72
local wrapped_text = {} local wrapped_text = {}
for line in self:gmatch('[^\n]*') do for line in self:gmatch('[^\n]*') do
local line_start_pos = 1 local line_start_pos = 1

@ -8,6 +8,9 @@ function test.startswith()
expect.true_((''):startswith('')) expect.true_((''):startswith(''))
expect.false_((''):startswith('a')) expect.false_((''):startswith('a'))
expect.false_(('str'):startswith('.'),
'ensure we match literals, not patterns')
end end
function test.endswith() function test.endswith()
@ -18,6 +21,41 @@ function test.endswith()
expect.true_((''):endswith('')) expect.true_((''):endswith(''))
expect.false_((''):endswith('a')) expect.false_((''):endswith('a'))
expect.false_(('str'):endswith('.'),
'ensure we match literals, not patterns')
end
-- uncomment once gui/load-screen's string:split implementation is removed
--[[
function test.split()
expect.table_eq({'hello','world'}, ('hello world'):split())
expect.table_eq({'hello','','world'}, ('hello world'):split())
expect.table_eq({'hello','world'}, ('hello world'):split(' +'))
expect.table_eq({'hello','world'}, ('hello.world'):split('.', true),
'ensure literal interpretation when plain is true')
-- we don't actually care what this returns, just that it does return
expect.true_(('hello world'):split('.*'), 'ensure no infinite loop')
expect.table_eq({'hello ', ' world'}, ('hello , world'):split(','),
'ensure spaces are kept when they are not the delimiter')
expect.table_eq({'hello'}, ('hello'):split(), 'no delimiter')
end
]]
function test.trim()
expect.eq('hello', ('hello'):trim())
expect.eq('hello', (' hello'):trim())
expect.eq('hello', ('hello '):trim())
expect.eq('hello', (' hello '):trim())
expect.eq('', (''):trim())
expect.eq('', (' '):trim())
expect.eq('', (' \t \n \v '):trim())
expect.eq('hel lo', (' hel lo '):trim(), 'keep interior spaces')
expect.eq('hel \n lo', (' hel \n lo '):trim(),
'keep interior spaces across newlines')
end end
function test.wrap() function test.wrap()
@ -29,5 +67,5 @@ function test.wrap()
expect.eq('hello\nworld', ('hello world'):wrap(8)) expect.eq('hello\nworld', ('hello world'):wrap(8))
expect.eq('hel\nlo\nwor\nld', ('hello world'):wrap(3)) expect.eq('hel\nlo\nwor\nld', ('hello world'):wrap(3))
expect.eq('hel\nloo\nwor\nldo', ('helloo worldo'):wrap(3)) expect.eq('hel\nloo\nwor\nldo', ('helloo worldo'):wrap(3))
expect.eq('', (''):wrap(10)) expect.eq('', (''):wrap())
end end