format_string.lua 4.0 KB

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