format_string.lua (4105B)
1 local luaassert = require('luassert') 2 3 local M = {} 4 5 local SUBTBL = { 6 '\\000', 7 '\\001', 8 '\\002', 9 '\\003', 10 '\\004', 11 '\\005', 12 '\\006', 13 '\\007', 14 '\\008', 15 '\\t', 16 '\\n', 17 '\\011', 18 '\\012', 19 '\\r', 20 '\\014', 21 '\\015', 22 '\\016', 23 '\\017', 24 '\\018', 25 '\\019', 26 '\\020', 27 '\\021', 28 '\\022', 29 '\\023', 30 '\\024', 31 '\\025', 32 '\\026', 33 '\\027', 34 '\\028', 35 '\\029', 36 '\\030', 37 '\\031', 38 } 39 40 --- @param v any 41 --- @return string 42 local function format_float(v) 43 -- On windows exponent appears to have three digits and not two 44 local ret = ('%.6e'):format(v) 45 local l, f, es, e = ret:match('^(%-?%d)%.(%d+)e([+%-])0*(%d%d+)$') 46 return l .. '.' .. f .. 'e' .. es .. e 47 end 48 49 -- Formats Lua value `v`. 50 -- 51 -- TODO(justinmk): redundant with vim.inspect() ? 52 -- 53 -- "Nice table formatting similar to screen:snapshot_util()". 54 -- Commit: 520c0b91a528 55 function M.format_luav(v, indent, opts) 56 opts = opts or {} 57 local linesep = '\n' 58 local next_indent_arg = nil 59 local indent_shift = opts.indent_shift or ' ' 60 local next_indent 61 local nl = '\n' 62 if indent == nil then 63 indent = '' 64 linesep = '' 65 next_indent = '' 66 nl = ' ' 67 else 68 next_indent_arg = indent .. indent_shift 69 next_indent = indent .. indent_shift 70 end 71 local ret = '' 72 if type(v) == 'string' then 73 if opts.literal_strings then 74 ret = v 75 else 76 local quote = opts.dquote_strings and '"' or "'" 77 ret = quote 78 .. tostring(v) 79 :gsub(opts.dquote_strings and '["\\]' or "['\\]", '\\%0') 80 :gsub('[%z\1-\31]', function(match) 81 return SUBTBL[match:byte() + 1] 82 end) 83 .. quote 84 end 85 elseif type(v) == 'table' then 86 if v == vim.NIL then 87 ret = 'REMOVE_THIS' 88 else 89 local processed_keys = {} 90 ret = '{' .. linesep 91 local non_empty = false 92 local format_luav = M.format_luav 93 for i, subv in ipairs(v) do 94 ret = ('%s%s%s,%s'):format(ret, next_indent, format_luav(subv, next_indent_arg, opts), nl) 95 processed_keys[i] = true 96 non_empty = true 97 end 98 for k, subv in pairs(v) do 99 if not processed_keys[k] then 100 if type(k) == 'string' and k:match('^[a-zA-Z_][a-zA-Z0-9_]*$') then 101 ret = ret .. next_indent .. k .. ' = ' 102 else 103 ret = ('%s%s[%s] = '):format(ret, next_indent, format_luav(k, nil, opts)) 104 end 105 ret = ret .. format_luav(subv, next_indent_arg, opts) .. ',' .. nl 106 non_empty = true 107 end 108 end 109 if nl == ' ' and non_empty then 110 ret = ret:sub(1, -3) 111 end 112 ret = ret .. indent .. '}' 113 end 114 elseif type(v) == 'number' then 115 if v % 1 == 0 then 116 ret = ('%d'):format(v) 117 else 118 ret = format_float(v) 119 end 120 elseif type(v) == 'nil' then 121 ret = 'nil' 122 elseif type(v) == 'boolean' then 123 ret = (v and 'true' or 'false') 124 else 125 print(type(v)) 126 -- Not implemented yet 127 luaassert(false) 128 end 129 return ret 130 end 131 132 -- Like Python repr(), "{!r}".format(s) 133 -- 134 -- Commit: 520c0b91a528 135 function M.format_string(fmt, ...) 136 local i = 0 137 local args = { ... } 138 local function getarg() 139 i = i + 1 140 return args[i] 141 end 142 local ret = fmt:gsub('%%[0-9*]*%.?[0-9*]*[cdEefgGiouXxqsr%%]', function(match) 143 local subfmt = match:gsub('%*', function() 144 return tostring(getarg()) 145 end) 146 local arg = nil 147 if subfmt:sub(-1) ~= '%' then 148 arg = getarg() 149 end 150 if subfmt:sub(-1) == 'r' or subfmt:sub(-1) == 'q' then 151 -- %r is like built-in %q, but it is supposed to single-quote strings and 152 -- not double-quote them, and also work not only for strings. 153 -- Builtin %q is replaced here as it gives invalid and inconsistent with 154 -- luajit results for e.g. "\e" on lua: luajit transforms that into `\27`, 155 -- lua leaves as-is. 156 arg = M.format_luav(arg, nil, { dquote_strings = (subfmt:sub(-1) == 'q') }) 157 subfmt = subfmt:sub(1, -2) .. 's' 158 end 159 if subfmt == '%e' then 160 return format_float(arg) 161 else 162 return subfmt:format(arg) 163 end 164 end) 165 return ret 166 end 167 168 return M