-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrow.lua
138 lines (114 loc) · 3.25 KB
/
row.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
--- Module row encapsulates renderers and returns their rendered contents laid
-- out horizontally. Width can be specified, which will constain content width.
-- @module row
--- getWidestLine returns the widest line (highest len()) for a table of
-- strings.
-- @table t A table of strings.
-- @treturn number
local function getWidestLine(t)
local w = 0
for _, v in ipairs(t) do
w = math.max(v:len(), w)
end
return w
end
--- renderContents calls the render method on all content passed into the row
-- constructor. Will calculate and constrain the width of inner content based
-- on provided width and number of contents.
-- @table contents A table of a table of strings.
-- @number maxWidth
-- @treturn A table of a table of formatted strings.
local function renderContents(contents, maxWidth)
local width = (maxWidth and math.floor(maxWidth / #contents))
local rem = (width and maxWidth % width)
local renders = {}
for i, c in ipairs(contents) do
local w = width
if w then
if rem > 0 then
w = w + 1
rem = rem - 1
end
c.width(w)
end
local render = c.render()
assert(type(render) == "table", "invalid return from render, expected table of strings")
for _, v in ipairs(render) do
assert(type(v) == "string", "invalid return from render, expected table of strings")
end
renders[i] = render
if w then
for j, r in ipairs(renders[i]) do
if r:len() < w then
local fmt = string.format("%%-%ds", w)
renders[i][j] = string.format(fmt, r)
elseif r:len() > w then
renders[i][j] = r:sub(1, w)
end
end
end
end
return renders
end
--- row is a constructor for a row object.
-- @table contents A table of objects satisfying an interface consisting of
-- width() and render() methods.
-- @treturn row
local function row(contents)
assert(type(contents) == "table", "invalid input, expected table")
for i, v in ipairs(contents) do
assert(v.render and type(v.render) == "function",
string.format("invalid input, content %d has no render method", i))
assert(v.width and type(v.width) == "function",
string.format("invalid input, content %d has no width method", i))
end
--- @type row
local R = {}
local width
--- render processes internal contents and returns a table of formatted
-- strings.
-- @treturn table
function R.render()
if #contents == 0 then
return {}
end
local lines = {}
do
local renders = renderContents(contents, width)
local i = 1
while true do
local misses = 0
for _, r in ipairs(renders) do
local w = getWidestLine(r)
if r[i] then
local fmt = string.format("%%-%ds", w)
lines[i] = string.format("%s%s", lines[i] or "", string.format(fmt, r[i]))
else
lines[i] = string.format("%s%s", lines[i] or "", string.rep(" ", w))
misses = misses + 1
end
end
i = i + 1
if misses == #renders then
break
end
end
-- remove final line if it's only spaces
if not lines[#lines]:find("%S") then
table.remove(lines, #lines)
end
end
return lines
end
--- width sets the width of the row, which will constrain all internal
-- content.
-- @number w
-- @treturn row
function R.width(w)
assert(type(w) == "number", "invalid input, expected number")
width = w
return R
end
return R
end
return row