1
0

Moved strings and their tests to new file.

This commit is contained in:
Jon Michael Aanes 2017-07-20 19:20:29 +02:00
parent 33fb88fdd7
commit 1f20c29f68
4 changed files with 228 additions and 174 deletions

View File

@ -41,7 +41,7 @@ a table, we have a better idea, but then the output would be cluttered.
-- Ensure loading library, if it exists, no matter where pretty.lua was loaded from.
-- Load the library component
local format_number, format_function, analyze_structure, TABLE_TYPE
local format_number, format_function, format_string, analyze_structure, TABLE_TYPE
do
local thispath = ... and select('1', ...):match('.+%.') or ''
local function import (name, ignore_failure)
@ -56,14 +56,18 @@ do
-- Load number and function formatting
-- Will use a very simple number formatting, if number.lua is not available.
-- Will use a very simple function formatting, if function.lua is not available.
format_number = import('number', true) or function (value, _, l) l[#l+1] = tostring(value) end
format_function = import('function', true) or function (value, _, l) l[#l+1] = 'function (...) --[['..tostring(value):sub(11)..']] end' end
-- Will use a very simple string formatting, if string.lua is not available.
format_number = import('number', true) or function (value, _, l) l[#l+1] = tostring(value) end
format_function = import('function', true) or function (value, _, l) l[#l+1] = 'function (...) --[['..tostring(value):sub(11)..']] end' end
format_string = import('pstring', true) or function (value, _, l) l[#l+1] = '[['..value..']]' end
-- Load other stuff
analyze_structure = import 'analyze_structure'
TABLE_TYPE = import 'table_type'
end
--
--------------------------------------------------------------------------------
-- Constants
local ERROR_UNKNOWN_TYPE = [[
[pretty]: Attempting to format unsupported value of type "%s".
@ -72,7 +76,6 @@ local ERROR_UNKNOWN_TYPE = [[
We are attempting to cover all Lua features, so please report this bug, so we can improve.
]]
local NR_CHARS_IN_LONG_STRING = 40
local MAX_WIDTH_FOR_SINGLE_LINE_TABLE = 38
local KEY_TYPE_SORT_ORDER = {
@ -96,24 +99,8 @@ local VALUE_TYPE_SORT_ORDER = {
['function'] = 7,
}
local CHAR_TO_STR_REPR = {}
do
for i = 00, 031 do CHAR_TO_STR_REPR[i] = '\\0'..(i < 10 and '0' or '')..i end
for i = 32, 255 do CHAR_TO_STR_REPR[i] = string.char(i) end
CHAR_TO_STR_REPR[7] = '\\a'
CHAR_TO_STR_REPR[8] = '\\b'
CHAR_TO_STR_REPR[9] = '\t'
CHAR_TO_STR_REPR[10] = '\n'
CHAR_TO_STR_REPR[11] = '\\v'
CHAR_TO_STR_REPR[12] = '\\f'
CHAR_TO_STR_REPR[13] = '\\r'
CHAR_TO_STR_REPR[92] = '\\\\'
CHAR_TO_STR_REPR[127] = '\\127'
end
--------------------------------------------------------------------------------
-- Util
-- Key-value-pair Util
local function padnum(d)
local dec, n = string.match(d, "(%.?)0*(.+)")
@ -197,31 +184,8 @@ local function fill_holes_in_key_value_pairs (key_value_pairs)
table.sort(key_value_pairs, compare_key_value_pair)
end
local function smallest_secure_longform_string_level (str)
-- Determines the level a longform string needs to use, to avoid code
-- injection. For example, if we want to use longform on the string
-- 'Hello ]] World', we cannot use level-0 as this would result in
-- '[[Hello ]] World]]', which could be an issue in certain applications.
-- Error checking
assert(type(str) == 'string')
-- Do stuff
local levels = { [1] = 1 }
str:gsub('%]=*%]', function (m) levels[m:len()] = true end)
return #levels - 1
end
local function escape_string (str)
-- Error checking
assert(type(str) == 'string')
-- Do stuff
local l = {}
for i = 1, #str do l[#l+1] = CHAR_TO_STR_REPR[str:byte(i)] end
return table.concat(l, '')
end
--------------------------------------------------------------------------------
-- Formatting Util
local function width_of_strings_in_l (l, start_i, stop_i)
@ -495,53 +459,6 @@ end
-- Formatting Strings
local function format_string (str, depth, l)
-- TODO: Add option for escaping unicode characters.
-- TODO: Improve cutstring argument.
-- TODO: Move this to it's own file.
-- Error checking
assert( type(str) == 'string' )
assert(type(depth) == 'number' and type(l) == 'table')
-- Do work
local is_long_string = (str:len() >= NR_CHARS_IN_LONG_STRING)
local newline_or_tab_index = str:find('[\n\t]')
local single_quote_index = str:find('\'')
local double_quote_index = str:find('\"')
-- ...
local chance_of_longform = is_long_string and ((newline_or_tab_index or math.huge) <= NR_CHARS_IN_LONG_STRING) or double_quote_index and single_quote_index
local cut_string_index = l.options.cut_strings and (is_long_string or chance_of_longform)
and math.min(NR_CHARS_IN_LONG_STRING - 3, newline_or_tab_index or 1/0, double_quote_index or 1/0, single_quote_index or 1/0)
local longform = chance_of_longform and ((not cut_string_index) or cut_string_index < math.min(newline_or_tab_index or 1/0, double_quote_index or 1/0, single_quote_index or 1/0))
local escape_newline_and_tab = not longform and newline_or_tab_index
-- Determine string delimiters
local left, right
if longform then
local level = smallest_secure_longform_string_level(str)
left, right = '['..string.rep('=', level)..'[', ']'..string.rep('=', level)..']'
if newline_or_tab_index then str = '\n' .. str end
elseif not single_quote_index then
left, right = '\'', '\''
else
left, right = '\"', '\"'
end
-- Cut string
if cut_string_index then str = str:sub(1, cut_string_index) end
str = escape_string(str)
-- Escape newline and tab
if escape_newline_and_tab then str = str:gsub('\n', '\\n'):gsub('\t', '\\t') end
l[#l+1] = left
l[#l+1] = str
l[#l+1] = right
end
local function format_coroutine (value, depth, l)
-- Formats a coroutine. Unfortunantly we cannot gather a lot of information
@ -572,7 +489,7 @@ local TYPE_TO_FORMAT_FUNC = {
['thread'] = format_coroutine,
['table'] = format_table,
['function'] = format_function, -- TODO: Improve a little
['function'] = format_function,
['userdata'] = format_primitive, -- TODO
['cdata'] = format_primitive, -- TODO & Luajit only
}

114
pstring.lua Normal file
View File

@ -0,0 +1,114 @@
-- pretty.string
-- The string formatting module for pretty.
--[=[ Thoughts on displaying strings in the useful ways.
TODO
--]=]
--------------------------------------------------------------------------------
-- Constants
local NR_CHARS_IN_LONG_STRING = 40
local CHAR_TO_STR_REPR = {}
do
for i = 00, 031 do CHAR_TO_STR_REPR[i] = ('\\%03i'):format(i) end
for i = 32, 255 do CHAR_TO_STR_REPR[i] = string.char(i) end
CHAR_TO_STR_REPR[7] = '\\a'
CHAR_TO_STR_REPR[8] = '\\b'
CHAR_TO_STR_REPR[9] = '\t'
CHAR_TO_STR_REPR[10] = '\n'
CHAR_TO_STR_REPR[11] = '\\v'
CHAR_TO_STR_REPR[12] = '\\f'
CHAR_TO_STR_REPR[13] = '\\r'
CHAR_TO_STR_REPR[92] = '\\\\'
CHAR_TO_STR_REPR[127] = '\\127'
end
--------------------------------------------------------------------------------
-- Util
local function escape_string (str)
-- Attempts to escape the string, to a format that is both a valid Lua
-- constant, and ledible unicode.
-- TODO: Escape invalid unicode sequences.
-- Error checking
assert(type(str) == 'string')
-- Do stuff
local l = {}
for i = 1, #str do l[#l+1] = CHAR_TO_STR_REPR[str:byte(i)] end
return table.concat(l, '')
end
local function smallest_secure_longform_string_level (str)
-- Determines the level a longform string needs to use, to avoid code
-- injection. For example, if we want to use longform on the string
-- 'Hello ]] World', we cannot use level-0 as this would result in
-- '[[Hello ]] World]]', which could be an issue in certain applications.
-- Error checking
assert(type(str) == 'string')
-- Do stuff
local levels = { [1] = 1 }
str:gsub('%]=*%]', function (m) levels[m:len()] = true end)
return #levels - 1
end
--------------------------------------------------------------------------------
return function (str, depth, l)
-- pretty.format_string
-- TODO: Add option for escaping unicode characters.
-- TODO: Improve cutstring argument.
-- Error checking
assert( type(str) == 'string' )
assert(type(depth) == 'number' and type(l) == 'table')
-- Do work
local is_long_string = (str:len() >= NR_CHARS_IN_LONG_STRING)
local newline_or_tab_index = str:find('[\n\t]')
local single_quote_index = str:find('\'')
local double_quote_index = str:find('\"')
-- ...
local chance_of_longform = is_long_string and ((newline_or_tab_index or math.huge) <= NR_CHARS_IN_LONG_STRING) or double_quote_index and single_quote_index
local cut_string_index = l.options.cut_strings and (is_long_string or chance_of_longform)
and math.min(NR_CHARS_IN_LONG_STRING - 3, newline_or_tab_index or 1/0, double_quote_index or 1/0, single_quote_index or 1/0)
local longform = chance_of_longform and ((not cut_string_index) or cut_string_index < math.min(newline_or_tab_index or 1/0, double_quote_index or 1/0, single_quote_index or 1/0))
local escape_newline_and_tab = not longform and newline_or_tab_index
-- Determine string delimiters
local left, right
if longform then
local level = smallest_secure_longform_string_level(str)
left, right = '['..string.rep('=', level)..'[', ']'..string.rep('=', level)..']'
if newline_or_tab_index then str = '\n' .. str end
elseif not single_quote_index then
left, right = '\'', '\''
else
left, right = '\"', '\"'
end
-- Cut string
if cut_string_index then str = str:sub(1, cut_string_index) end
str = escape_string(str)
-- Escape newline and tab
if escape_newline_and_tab then str = str:gsub('\n', '\\n'):gsub('\t', '\\t') end
l[#l+1] = left
l[#l+1] = str
l[#l+1] = right
end

View File

@ -34,85 +34,6 @@ local function format_test (t)
end, { line = debug.getinfo(2).currentline })
end
--------------------------------------------------------------------------------
-- Strings
format_test {
input = 'Hello World',
expect = '\'Hello World\'',
}
format_test {
input = 'Hello \'World\'',
expect = '\"Hello \'World\'\"',
}
format_test {
input = 'Hello \"World\"',
expect = '\'Hello \"World\"\'',
}
format_test {
input = 'Hello [[World]]',
expect = '\'Hello [[World]]\'',
}
format_test {
input = '\'Hello\' [[World]]',
expect = '\"\'Hello\' [[World]]\"',
}
format_test {
input = '\'Hello\' \"there\" [[World]]',
expect = '[=[\'Hello\' \"there\" [[World]]]=]',
}
format_test {
input = '\'Hello\' \"there\" [=[World]=]',
expect = '[[\'Hello\' \"there\" [=[World]=]]]',
}
format_test {
input = '\nHello World',
expect = '\'\\nHello World\'',
}
format_test {
input = '\'\"\n',
expect = '[[\n\'\"\n]]',
}
format_test {
input = '\n',
expect = '\'\\n\'',
}
format_test {
input = '\\',
expect = '\'\\\\\'',
}
format_test {
input = '\000',
expect = '\'\\000\'',
}
format_test {
input = '\a\b\v\r\f',
expect = '\'\\a\\b\\v\\r\\f\'',
}
format_test {
input = 'ø',
expect = '\'ø\'',
}
format_test {
name = 'Malformed Unicode is escaped',
input = '\000\001\003\012\169\003\000\030',
expect = '\'\\000\\000\\001\\003\\012\\169\\003\\000\\030\'',
}
--------------------------------------------------------------------------------
-- Primitive types

102
test/test_pstring.lua Normal file
View File

@ -0,0 +1,102 @@
local SUITE = require 'TestSuite' 'string'
SUITE:setEnviroment{
format = require('pretty')
}
--------------------------------------------------------------------------------
-- Compat
if not loadstring then loadstring = load end -- Lua 5.3 compat
--
local function format_test (t)
SUITE:addTest(t.name or t.expect, function ()
local actual_result = format(t.input, t.options)
assert_equal(t.expect, actual_result)
end, { line = debug.getinfo(2).currentline })
end
--------------------------------------------------------------------------------
format_test {
input = 'Hello World',
expect = '\'Hello World\'',
}
format_test {
input = 'Hello \'World\'',
expect = '\"Hello \'World\'\"',
}
format_test {
input = 'Hello \"World\"',
expect = '\'Hello \"World\"\'',
}
format_test {
input = 'Hello [[World]]',
expect = '\'Hello [[World]]\'',
}
format_test {
input = '\'Hello\' [[World]]',
expect = '\"\'Hello\' [[World]]\"',
}
format_test {
input = '\'Hello\' \"there\" [[World]]',
expect = '[=[\'Hello\' \"there\" [[World]]]=]',
}
format_test {
input = '\'Hello\' \"there\" [=[World]=]',
expect = '[[\'Hello\' \"there\" [=[World]=]]]',
}
format_test {
input = '\nHello World',
expect = '\'\\nHello World\'',
}
format_test {
input = '\'\"\n',
expect = '[[\n\'\"\n]]',
}
format_test {
input = '\n',
expect = '\'\\n\'',
}
format_test {
input = '\\',
expect = '\'\\\\\'',
}
format_test {
input = '\000',
expect = '\'\\000\'',
}
format_test {
input = '\a\b\v\r\f',
expect = '\'\\a\\b\\v\\r\\f\'',
}
--------------------------------------------------------------------------------
-- Unicode
format_test {
input = 'ø',
expect = '\'ø\'',
}
format_test {
name = 'Malformed Unicode is escaped',
input = '\000\001\003\012\169\003\000\030',
expect = '\'\\000\\000\\001\\003\\012\\169\\003\\000\\030\'',
}
--------------------------------------------------------------------------------
return SUITE