mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-13 11:40:29 +00:00
Use a Wadler style pretty printer in the Lua REPL (#334)
- Add a cc.pretty module, which provides a Wadler style pretty printer [1]. - The cc.pretty.pretty function converts an arbitrary object into a pretty-printed document. This can then be printed to the screen with cc.pretty.{write, print} or converted to a string with cc.pretty.render. - Convert the Lua REPL to use the pretty printer rather than textutils.serialise. [1]: http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
This commit is contained in:
parent
c79f643ba7
commit
798868427e
@ -0,0 +1,416 @@
|
||||
--- Provides a "pretty printer", for rendering data structures in an
|
||||
-- aesthetically pleasing manner.
|
||||
--
|
||||
-- In order to display something using @{cc.pretty}, you build up a series of
|
||||
-- @{documents|Doc}. These behave a little bit like strings; you can concatenate
|
||||
-- them together and then print them to the screen.
|
||||
--
|
||||
-- However, documents also allow you to control how they should be printed. There
|
||||
-- are several functions (such as @{nest} and @{group}) which allow you to control
|
||||
-- the "layout" of the document. When you come to display the document, the 'best'
|
||||
-- (most compact) layout is used.
|
||||
--
|
||||
-- @module cc.pretty
|
||||
-- @usage Print a table to the terminal
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.write(pretty.dump({ 1, 2, 3 }))
|
||||
--
|
||||
-- @usage Build a custom document and display it
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.write(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
|
||||
|
||||
local expect = require "cc.expect".expect
|
||||
local type, getmetatable, setmetatable, colours, str_write = type, getmetatable, setmetatable, colours, write
|
||||
|
||||
--- @{table.insert} alternative, but with the length stored inline.
|
||||
local function append(out, value)
|
||||
local n = out.n + 1
|
||||
out[n], out.n = value, n
|
||||
end
|
||||
|
||||
--- A document, which
|
||||
--
|
||||
-- Documents effectively represent a sequence of strings in alternative layouts,
|
||||
-- which we will try to print in the most compact form necessary.
|
||||
--
|
||||
-- @type Doc
|
||||
local Doc = { }
|
||||
|
||||
--- An empty document.
|
||||
local empty = setmetatable({ tag = "nil" }, Doc)
|
||||
|
||||
--- A document with a single space in it.
|
||||
local space = setmetatable({ tag = "text", text = " " }, Doc)
|
||||
|
||||
--- A line break. When collapsed with @{group}, this will be replaced with @{empty}.
|
||||
local line = setmetatable({ tag = "line", flat = empty }, Doc)
|
||||
|
||||
--- A line break. When collapsed with @{group}, this will be replaced with @{space}.
|
||||
local space_line = setmetatable({ tag = "line", flat = space }, Doc)
|
||||
|
||||
local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line }
|
||||
|
||||
local function mk_text(text, colour)
|
||||
return text_cache[text] or setmetatable({ tag = "text", text = text, colour = colour }, Doc)
|
||||
end
|
||||
|
||||
--- Create a new document from a string.
|
||||
--
|
||||
-- If your string contains multiple lines, @{group} will flatten the string
|
||||
-- into a single line, with spaces between each line.
|
||||
--
|
||||
-- @tparam string text The string to construct a new document with.
|
||||
-- @tparam[opt] number colour The colour this text should be printed with. If not given, we default to the current
|
||||
-- colour.
|
||||
-- @treturn Doc The document with the provided text.
|
||||
local function text(text, colour)
|
||||
expect(1, text, "string")
|
||||
expect(2, colour, "number", "nil")
|
||||
|
||||
local cached = text_cache[text]
|
||||
if cached then return cached end
|
||||
|
||||
local new_line = text:find("\n", 1)
|
||||
if not new_line then return mk_text(text, colour) end
|
||||
|
||||
-- Split the string by "\n". With a micro-optimisation to skip empty strings.
|
||||
local doc = setmetatable({ tag = "concat", n = 0 }, Doc)
|
||||
if new_line ~= 1 then append(doc, mk_text(text:sub(1, new_line - 1), colour)) end
|
||||
|
||||
new_line = new_line + 1
|
||||
while true do
|
||||
local next_line = text:find("\n", new_line)
|
||||
append(doc, space_line)
|
||||
if not next_line then
|
||||
if new_line <= #text then append(doc, mk_text(text:sub(new_line), colour)) end
|
||||
return doc
|
||||
else
|
||||
if new_line <= next_line - 1 then
|
||||
append(doc, mk_text(text:sub(new_line, next_line - 1), colour))
|
||||
end
|
||||
new_line = next_line + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Concatenate several documents together. This behaves very similar to string concatenation.
|
||||
|
||||
-- @tparam Doc|string ... The documents to concatenate.
|
||||
-- @treturn Doc The concatenated documents.
|
||||
-- @usage pretty.concat(doc1, " - ", doc2)
|
||||
-- @usage doc1 .. " - " .. doc2
|
||||
local function concat(...)
|
||||
local args = table.pack(...)
|
||||
for i = 1, args.n do
|
||||
if type(args[i]) == "string" then args[i] = text(args[i]) end
|
||||
if getmetatable(args[i]) ~= Doc then expect(i, args[i], "document") end
|
||||
end
|
||||
|
||||
if args.n == 0 then return empty end
|
||||
if args.n == 1 then return args[1] end
|
||||
|
||||
args.tag = "concat"
|
||||
return setmetatable(args, Doc)
|
||||
end
|
||||
|
||||
Doc.__concat = concat
|
||||
|
||||
--- Indent later lines of the given document with the given number of spaces.
|
||||
--
|
||||
-- For instance, nesting the document
|
||||
-- ```txt
|
||||
-- foo
|
||||
-- bar
|
||||
-- ``
|
||||
-- by two spaces will produce
|
||||
-- ```txt
|
||||
-- foo
|
||||
-- bar
|
||||
-- ```
|
||||
--
|
||||
-- @tparam number depth The number of spaces with which the document should be indented.
|
||||
-- @tparam Doc doc The document to indent.
|
||||
-- @treturn Doc The nested document.
|
||||
-- @usage pretty.nest(2, pretty.text("foo\nbar"))
|
||||
local function nest(depth, doc)
|
||||
expect(1, depth, "number")
|
||||
if getmetatable(doc) ~= Doc then expect(2, doc, "document") end
|
||||
if depth <= 0 then error("depth must be a positive number", 2) end
|
||||
|
||||
return setmetatable({ tag = "nest", depth = depth, doc }, Doc)
|
||||
end
|
||||
|
||||
local function flatten(doc)
|
||||
if doc.flat then return doc.flat end
|
||||
|
||||
local kind = doc.tag
|
||||
if kind == "nil" or kind == "text" then
|
||||
return doc
|
||||
elseif kind == "concat" then
|
||||
local out = setmetatable({ tag = "concat", n = doc.n }, Doc)
|
||||
for i = 1, doc.n do out[i] = flatten(doc[i]) end
|
||||
doc.flat, out.flat = out, out -- cache the flattened node
|
||||
return out
|
||||
elseif kind == "nest" then
|
||||
return flatten(doc[1])
|
||||
elseif kind == "group" then
|
||||
return doc[1]
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
--- Builds a document which is displayed on a single line if there is enough
|
||||
-- room, or as normal if not.
|
||||
--
|
||||
-- @tparam Doc doc The document to group.
|
||||
-- @treturn Doc The grouped document.
|
||||
local function group(doc)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
|
||||
if doc.tag == "group" then return doc end -- Skip if already grouped.
|
||||
|
||||
local flattened = flatten(doc)
|
||||
if flattened == doc then return doc end -- Also skip if flattening does nothing.
|
||||
return setmetatable({ tag = "group", flattened, doc }, Doc)
|
||||
end
|
||||
|
||||
local function get_remaining(doc, width)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" or kind == "line" then
|
||||
return width
|
||||
elseif kind == "text" then
|
||||
return width - #doc.text
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do
|
||||
width = get_remaining(doc[i], width)
|
||||
if width < 0 then break end
|
||||
end
|
||||
return width
|
||||
elseif kind == "group" or kind == "nest" then
|
||||
return get_remaining(kind[1])
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
--- Display a document on the terminal.
|
||||
--
|
||||
-- @tparam Doc doc The document to render
|
||||
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function write(doc, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, ribbon_frac, "number", "nil")
|
||||
|
||||
local term = term
|
||||
local width, height = term.getSize()
|
||||
local ribbon_width = (ribbon_frac or 0.6) * width
|
||||
if ribbon_width < 0 then ribbon_width = 0 end
|
||||
if ribbon_width > width then ribbon_width = width end
|
||||
|
||||
local def_colour = term.getTextColour()
|
||||
local current_colour = def_colour
|
||||
|
||||
local function go(doc, indent, col)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" then
|
||||
return col
|
||||
elseif kind == "text" then
|
||||
local doc_colour = doc.colour or def_colour
|
||||
if doc_colour ~= current_colour then
|
||||
term.setTextColour(doc_colour)
|
||||
current_colour = doc_colour
|
||||
end
|
||||
|
||||
str_write(doc.text)
|
||||
|
||||
return col + #doc.text
|
||||
elseif kind == "line" then
|
||||
local _, y = term.getCursorPos()
|
||||
if y < height then
|
||||
term.setCursorPos(indent + 1, y + 1)
|
||||
else
|
||||
term.scroll(1)
|
||||
term.setCursorPos(indent + 1, height)
|
||||
end
|
||||
|
||||
return indent
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do col = go(doc[i], indent, col) end
|
||||
return col
|
||||
elseif kind == "nest" then
|
||||
return go(doc[1], indent + doc.depth, col)
|
||||
elseif kind == "group" then
|
||||
if get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
|
||||
return go(doc[1], indent, col)
|
||||
else
|
||||
return go(doc[2], indent, col)
|
||||
end
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
local col = math.max(term.getCursorPos() - 1, 0)
|
||||
go(doc, 0, col)
|
||||
if current_colour ~= def_colour then term.setTextColour(def_colour) end
|
||||
end
|
||||
|
||||
--- Display a document on the terminal with a trailing new line.
|
||||
--
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function print(doc, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, ribbon_frac, "number", "nil")
|
||||
write(doc, ribbon_frac)
|
||||
str_write("\n")
|
||||
end
|
||||
|
||||
--- Render a document, converting it into a string.
|
||||
--
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to
|
||||
-- fit this width - it is only used for finding the best layout.
|
||||
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function render(doc, width, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, width, "number", "nil")
|
||||
expect(3, ribbon_frac, "number", "nil")
|
||||
|
||||
local ribbon_width
|
||||
if width then
|
||||
ribbon_width = (ribbon_frac or 0.6) * width
|
||||
if ribbon_width < 0 then ribbon_width = 0 end
|
||||
if ribbon_width > width then ribbon_width = width end
|
||||
end
|
||||
|
||||
local out = { n = 0 }
|
||||
local function go(doc, indent, col)
|
||||
local kind = doc.tag
|
||||
if kind == "nil" then
|
||||
return col
|
||||
elseif kind == "text" then
|
||||
append(out, doc.text)
|
||||
return col + #doc.text
|
||||
elseif kind == "line" then
|
||||
append(out, "\n" .. (" "):rep(indent))
|
||||
return indent
|
||||
elseif kind == "concat" then
|
||||
for i = 1, doc.n do col = go(doc[i], indent, col) end
|
||||
return col
|
||||
elseif kind == "nest" then
|
||||
return go(doc[1], indent + doc.depth, col)
|
||||
elseif kind == "group" then
|
||||
if not width or get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
|
||||
return go(doc[1], indent, col)
|
||||
else
|
||||
return go(doc[2], indent, col)
|
||||
end
|
||||
else
|
||||
error("Unknown doc " .. kind)
|
||||
end
|
||||
end
|
||||
|
||||
go(doc, 0, 0)
|
||||
return table.concat(out, "", 1, out.n)
|
||||
end
|
||||
|
||||
local keywords = {
|
||||
[ "and" ] = true, [ "break" ] = true, [ "do" ] = true, [ "else" ] = true,
|
||||
[ "elseif" ] = true, [ "end" ] = true, [ "false" ] = true, [ "for" ] = true,
|
||||
[ "function" ] = true, [ "if" ] = true, [ "in" ] = true, [ "local" ] = true,
|
||||
[ "nil" ] = true, [ "not" ] = true, [ "or" ] = true, [ "repeat" ] = true, [ "return" ] = true,
|
||||
[ "then" ] = true, [ "true" ] = true, [ "until" ] = true, [ "while" ] = true,
|
||||
}
|
||||
|
||||
local comma = text(",")
|
||||
local braces = text("{}")
|
||||
local obrace, cbrace = text("{"), text("}")
|
||||
local obracket, cbracket = text("["), text("] = ")
|
||||
|
||||
local function key_compare(a, b)
|
||||
local ta, tb = type(a), type(b)
|
||||
|
||||
if ta == "string" then return tb ~= "string" or a < b
|
||||
elseif tb == "string" then return false
|
||||
end
|
||||
|
||||
if ta == "number" then return tb ~= "number" or a < b end
|
||||
return false
|
||||
end
|
||||
|
||||
local function pretty_impl(obj, tracking)
|
||||
local obj_type = type(obj)
|
||||
if obj_type == "string" then
|
||||
local formatted = ("%q"):format(obj):gsub("\\\n", "\\n")
|
||||
return text(formatted, colours.red)
|
||||
elseif obj_type == "number" then
|
||||
return text(tostring(obj), colours.magenta)
|
||||
elseif obj_type ~= "table" or tracking[obj] then
|
||||
return text(tostring(obj), colours.lightGrey)
|
||||
elseif getmetatable(obj) ~= nil and getmetatable(obj).__tostring then
|
||||
return text(tostring(obj))
|
||||
elseif next(obj) == nil then
|
||||
return braces
|
||||
else
|
||||
tracking[obj] = true
|
||||
local doc = setmetatable({ tag = "concat", n = 1, space_line }, Doc)
|
||||
|
||||
local length, keys, keysn = #obj, {}, 1
|
||||
for k in pairs(obj) do keys[keysn], keysn = k, keysn + 1 end
|
||||
table.sort(keys, key_compare)
|
||||
|
||||
for i = 1, keysn - 1 do
|
||||
if i > 1 then append(doc, comma) append(doc, space_line) end
|
||||
|
||||
local k = keys[i]
|
||||
local v = obj[k]
|
||||
local ty = type(k)
|
||||
if ty == "number" and k % 1 == 0 and k >= 1 and k <= length then
|
||||
append(doc, pretty_impl(v, tracking))
|
||||
elseif ty == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then
|
||||
append(doc, text(k .. " = "))
|
||||
append(doc, pretty_impl(v, tracking))
|
||||
else
|
||||
append(doc, obracket)
|
||||
append(doc, pretty_impl(k, tracking))
|
||||
append(doc, cbracket)
|
||||
append(doc, pretty_impl(v, tracking))
|
||||
end
|
||||
end
|
||||
|
||||
tracking[obj] = nil
|
||||
return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, n))), space_line, cbrace))
|
||||
end
|
||||
end
|
||||
|
||||
--- Pretty-print an arbitrary object, converting it into a document.
|
||||
--
|
||||
-- This can then be rendered with @{write} or @{print}.
|
||||
--
|
||||
-- @param obj The object to pretty-print.
|
||||
-- @treturn Doc The object formatted as a document.
|
||||
-- @usage Display a table on the screen
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.print(pretty.pretty({ 1, 2, 3 })
|
||||
local function pretty(obj)
|
||||
return pretty_impl(obj, {})
|
||||
end
|
||||
|
||||
return {
|
||||
empty = empty,
|
||||
space = space,
|
||||
line = line,
|
||||
space_line = space_line,
|
||||
text = text,
|
||||
concat = concat,
|
||||
nest = nest,
|
||||
group = group,
|
||||
|
||||
write = write,
|
||||
print = print,
|
||||
render = render,
|
||||
|
||||
pretty = pretty,
|
||||
}
|
@ -6,6 +6,8 @@ if #tArgs > 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local pretty = require "cc.pretty"
|
||||
|
||||
local bRunning = true
|
||||
local tCommandHistory = {}
|
||||
local tEnv = {
|
||||
@ -87,20 +89,11 @@ while bRunning do
|
||||
local n = 1
|
||||
while n < tResults.n or n <= nForcePrint do
|
||||
local value = tResults[ n + 1 ]
|
||||
if type( value ) == "table" then
|
||||
local metatable = getmetatable( value )
|
||||
if type(metatable) == "table" and type(metatable.__tostring) == "function" then
|
||||
print( tostring( value ) )
|
||||
else
|
||||
local ok, serialised = pcall( textutils.serialise, value )
|
||||
if ok then
|
||||
print( serialised )
|
||||
else
|
||||
print( tostring( value ) )
|
||||
end
|
||||
end
|
||||
local ok, serialised = pcall(pretty.pretty, value)
|
||||
if ok then
|
||||
pretty.print(serialised)
|
||||
else
|
||||
print( tostring( value ) )
|
||||
print(tostring(value))
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
|
208
src/test/resources/test-rom/spec/modules/cc/pretty_spec.lua
Normal file
208
src/test/resources/test-rom/spec/modules/cc/pretty_spec.lua
Normal file
@ -0,0 +1,208 @@
|
||||
local with_window = require "test_helpers".with_window
|
||||
|
||||
describe("cc.pretty", function()
|
||||
local pp = require("cc.pretty")
|
||||
|
||||
describe("text", function()
|
||||
it("is constant for the empty string", function()
|
||||
expect(pp.text("")):eq(pp.empty)
|
||||
end)
|
||||
|
||||
it("is constant for a space", function()
|
||||
expect(pp.text(" ")):eq(pp.space)
|
||||
end)
|
||||
|
||||
it("is constant for a newline", function()
|
||||
expect(pp.text("\n")):eq(pp.space_line)
|
||||
end)
|
||||
|
||||
it("validates arguments", function()
|
||||
expect.error(pp.text, 123):eq("bad argument #1 (expected string, got number)")
|
||||
expect.error(pp.text, "", ""):eq("bad argument #2 (expected number, got string)")
|
||||
end)
|
||||
|
||||
it("produces text documents", function()
|
||||
expect(pp.text("a")):same({ tag = "text", text = "a" })
|
||||
expect(pp.text("a", colours.grey)):same({ tag = "text", text = "a", colour = colours.grey })
|
||||
end)
|
||||
|
||||
it("splits lines", function()
|
||||
expect(pp.text("a\nb"))
|
||||
:same(pp.concat(pp.text("a"), pp.space_line, pp.text("b")))
|
||||
expect(pp.text("ab\ncd\nef"))
|
||||
:same(pp.concat(pp.text("ab"), pp.space_line, pp.text("cd"), pp.space_line, pp.text("ef")))
|
||||
end)
|
||||
|
||||
it("preserves empty lines", function()
|
||||
expect(pp.text("a\n\nb"))
|
||||
:same(pp.concat(pp.text("a"), pp.space_line, pp.space_line, pp.text("b")))
|
||||
expect(pp.text("\n\nb"))
|
||||
:same(pp.concat(pp.space_line, pp.space_line, pp.text("b")))
|
||||
expect(pp.text("a\n\n"))
|
||||
:same(pp.concat(pp.text("a"), pp.space_line, pp.space_line))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("concat", function()
|
||||
it("returns empty with 0 arguments", function()
|
||||
expect(pp.concat()):eq(pp.empty)
|
||||
end)
|
||||
|
||||
it("acts as the identity with 1 argument", function()
|
||||
local x = pp.text("test")
|
||||
expect(pp.concat(x)):eq(x)
|
||||
end)
|
||||
|
||||
it("coerces strings", function()
|
||||
expect(pp.concat("a", "b")):same(pp.concat(pp.text("a"), pp.text("b")))
|
||||
end)
|
||||
|
||||
it("validates arguments", function()
|
||||
expect.error(pp.concat, 123):eq("bad argument #1 (expected document, got number)")
|
||||
expect.error(pp.concat, "", {}):eq("bad argument #2 (expected document, got table)")
|
||||
end)
|
||||
|
||||
it("can be used as an operator", function()
|
||||
local a, b = pp.text("a"), pp.text("b")
|
||||
expect(pp.concat(a, b)):same(a .. b)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("group", function()
|
||||
it("is idempotent", function()
|
||||
local x = pp.group(pp.text("a\nb"))
|
||||
expect(pp.group(x)):eq(x)
|
||||
end)
|
||||
|
||||
it("does nothing for flat strings", function()
|
||||
local x = pp.text("a")
|
||||
expect(pp.group(x)):eq(x)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- Allows us to test
|
||||
local function test_output(display)
|
||||
it("displays the empty document", function()
|
||||
expect(display(pp.empty)):same { "" }
|
||||
end)
|
||||
|
||||
it("displays a multiline string", function()
|
||||
expect(display(pp.text("hello\nworld"))):same {
|
||||
"hello",
|
||||
"world",
|
||||
}
|
||||
end)
|
||||
|
||||
it("displays a nested string", function()
|
||||
expect(display(pp.nest(2, pp.concat("hello", pp.line, "world")))):same {
|
||||
"hello",
|
||||
" world",
|
||||
}
|
||||
end)
|
||||
|
||||
it("displays a flattened group", function()
|
||||
expect(display(pp.group(pp.concat("hello", pp.space_line, "world")))):same {
|
||||
"hello world",
|
||||
}
|
||||
|
||||
expect(display(pp.group(pp.concat("hello", pp.line, "world")))):same {
|
||||
"helloworld",
|
||||
}
|
||||
end)
|
||||
|
||||
it("displays an expanded group", function()
|
||||
expect(display(pp.group(pp.concat("hello darkness", pp.space_line, "my old friend")))):same {
|
||||
"hello darkness",
|
||||
"my old friend",
|
||||
}
|
||||
end)
|
||||
|
||||
it("group removes nest", function()
|
||||
expect(display(pp.group(pp.nest(2, pp.concat("hello", pp.space_line, "world"))))):same {
|
||||
"hello world",
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
describe("write", function()
|
||||
local function display(doc)
|
||||
local w = with_window(20, 10, function() pp.write(doc) end)
|
||||
local _, y = w.getCursorPos()
|
||||
|
||||
local out = {}
|
||||
for i = 1, y do out[i] = w.getLine(i):gsub("%s+$", "") end
|
||||
return out
|
||||
end
|
||||
|
||||
test_output(display)
|
||||
|
||||
it("wraps a long string", function()
|
||||
expect(display(pp.text("hello world this is a long string which will wrap"))):same {
|
||||
"hello world this is",
|
||||
"a long string which",
|
||||
"will wrap",
|
||||
}
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("render", function()
|
||||
local function display(doc)
|
||||
local rendered = pp.render(doc, 20)
|
||||
local n, lines = 1, {}
|
||||
for line in (rendered .. "\n"):gmatch("([^\n]*)\n") do lines[n], n = line, n + 1 end
|
||||
return lines
|
||||
end
|
||||
|
||||
test_output(display)
|
||||
|
||||
it("does not wrap a long string", function()
|
||||
expect(display(pp.text("hello world this is a long string which will wrap"))):same {
|
||||
"hello world this is a long string which will wrap",
|
||||
}
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("pretty", function()
|
||||
--- We make use of "render" here, as it's considerably easier than checking against the actual structure.
|
||||
-- However, it does also mean our tests are less unit-like.
|
||||
local function pretty(x, width) return pp.render(pp.pretty(x), width) end
|
||||
|
||||
describe("tables", function()
|
||||
it("displays empty tables", function()
|
||||
expect(pp.pretty({})):same(pp.text("{}"))
|
||||
end)
|
||||
|
||||
it("displays list-like tables", function()
|
||||
expect(pretty({ 1, 2, 3 })):eq("{ 1, 2, 3 }")
|
||||
end)
|
||||
|
||||
it("displays mixed tables", function()
|
||||
expect(pretty({ n = 3, 1, 2, 3 })):eq("{ n = 3, 1, 2, 3 }")
|
||||
end)
|
||||
|
||||
it("escapes keys", function()
|
||||
expect(pretty({ ["and"] = 1, ["not that"] = 2 })):eq('{ ["and"] = 1, ["not that"] = 2 }')
|
||||
end)
|
||||
|
||||
it("sorts keys", function()
|
||||
expect(pretty({ c = 1, b = 2, a = 3 })):eq('{ a = 3, b = 2, c = 1 }')
|
||||
end)
|
||||
|
||||
it("groups tables", function()
|
||||
expect(pretty({ 1, 2, 3 }, 4)):eq("{\n 1,\n 2,\n 3\n}")
|
||||
end)
|
||||
end)
|
||||
|
||||
it("shows numbers", function()
|
||||
expect(pretty(123)):eq("123")
|
||||
end)
|
||||
|
||||
it("shows strings", function()
|
||||
expect(pretty("hello\nworld")):eq('"hello\\nworld"')
|
||||
end)
|
||||
|
||||
it("shows functions", function()
|
||||
expect(pretty(pretty)):eq(tostring(pretty))
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue
Block a user