-- 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