1 -- Variables ending with these names will not
2 -- have their values printed ('password' includes
3 -- 'new_password', etc.)
5 -- luacheck: ignore 122/debug
7 local censored_names
= {
13 local optimal_line_length
= 65;
15 local termcolours
= require
"util.termcolours";
16 local getstring
= termcolours
.getstring
;
19 local _
= termcolours
.getstyle
;
21 boundary_padding
= _("bright");
22 filename
= _("bright", "blue");
23 level_num
= _("green");
24 funcname
= _("yellow");
25 location
= _("yellow");
29 local function get_locals_table(thread
, level
)
31 for local_num
= 1, math
.huge
do
34 name
, value
= debug
.getlocal(thread
, level
, local_num
);
36 name
, value
= debug
.getlocal(level
+1, local_num
);
38 if not name
then break; end
39 table.insert(locals
, { name
= name
, value
= value
});
44 local function get_upvalues_table(func
)
47 for upvalue_num
= 1, math
.huge
do
48 local name
, value
= debug
.getupvalue(func
, upvalue_num
);
49 if not name
then break; end
50 if name
== "" then name
= ("[%d]"):format(upvalue_num
); end
51 table.insert(upvalues
, { name
= name
, value
= value
});
57 local function string_from_var_table(var_table
, max_line_len
, indent_str
)
58 local var_string
= {};
60 max_line_len
= max_line_len
or math
.huge
;
61 indent_str
= "\n"..(indent_str
or "");
62 for _
, var
in ipairs(var_table
) do
63 local name
, value
= var
.name
, var
.value
;
64 if name
:sub(1,1) ~= "(" then
65 if type(value
) == "string" then
66 if censored_names
[name
:match("%a+$")] then
69 value
= ("%q"):format(value
);
72 value
= tostring(value
);
74 if #value
> max_line_len
then
75 value
= value
:sub(1, max_line_len
-3).."…";
77 local str
= ("%s = %s"):format(name
, tostring(value
));
78 col_pos
= col_pos
+ #str
;
79 if col_pos
> max_line_len
then
80 table.insert(var_string
, indent_str
);
83 table.insert(var_string
, str
);
86 if #var_string
== 0 then
89 return "{ "..table.concat(var_string
, ", "):gsub(indent_str
..", ", indent_str
).." }";
93 local function get_traceback_table(thread
, start_level
)
95 for level
= start_level
, math
.huge
do
98 info
= debug
.getinfo(thread
, level
);
100 info
= debug
.getinfo(level
+1);
102 if not info
then break; end
104 levels
[(level
-start_level
)+1] = {
107 locals
= get_locals_table(thread
, level
+(thread
and 0 or 1));
108 upvalues
= get_upvalues_table(info
.func
);
114 local function build_source_boundary_marker(last_source_desc
)
115 local padding
= string.rep("-", math
.floor(((optimal_line_length
- 6) - #last_source_desc
)/2));
116 return getstring(styles
.boundary_padding
, "v"..padding
).." "..
117 getstring(styles
.filename
, last_source_desc
).." "..
118 getstring(styles
.boundary_padding
, padding
..(#last_source_desc
%2==0 and "-v" or "v "));
121 local function _traceback(thread
, message
, level
)
123 -- Lua manual says: debug.traceback ([thread,] [message [, level]])
124 -- I fathom this to mean one of:
128 -- (thread, message, level)
130 if thread
== nil then -- Defaults
131 thread
, message
, level
= coroutine
.running(), message
, level
;
132 elseif type(thread
) == "string" then
133 thread
, message
, level
= coroutine
.running(), thread
, message
;
134 elseif type(thread
) ~= "thread" then
135 return nil; -- debug.traceback() does this
140 message
= message
and (message
.."\n") or "";
142 -- +3 counts for this function, and the pcall() and wrapper above us, the +1... I don't know.
143 local levels
= get_traceback_table(thread
, level
+(thread
== nil and 4 or 0));
145 local last_source_desc
;
148 for nlevel
, current_level
in ipairs(levels
) do
149 local info
= current_level
.info
;
151 local func_type
= info
.namewhat
.." ";
152 local source_desc
= (info
.short_src
== "[C]" and "C code") or info
.short_src
or "Unknown";
153 if func_type
== " " then func_type
= ""; end;
154 if info
.short_src
== "[C]" then
155 line
= "[ C ] "..func_type
.."C function "..getstring(styles
.location
, (info
.name
and ("%q"):format(info
.name
) or "(unknown name)"));
156 elseif info
.what
== "main" then
157 line
= "[Lua] "..getstring(styles
.location
, info
.short_src
.." line "..info
.currentline
);
159 local name
= info
.name
or " ";
161 name
= ("%q"):format(name
);
163 if func_type
== "global " or func_type
== "local " then
164 func_type
= func_type
.."function ";
166 line
= "[Lua] "..getstring(styles
.location
, info
.short_src
.." line "..
167 info
.currentline
).." in "..func_type
..getstring(styles
.funcname
, name
)..
168 " (defined on line "..info
.linedefined
..")";
170 if source_desc
~= last_source_desc
then -- Venturing into a new source, add marker for previous
171 last_source_desc
= source_desc
;
172 table.insert(lines
, "\t "..build_source_boundary_marker(last_source_desc
));
175 table.insert(lines
, "\t"..(nlevel
==0 and ">" or " ")..getstring(styles
.level_num
, "("..nlevel
..") ")..line
);
176 local npadding
= (" "):rep(#tostring(nlevel
));
177 if current_level
.locals
then
178 local locals_str
= string_from_var_table(current_level
.locals
, optimal_line_length
, "\t "..npadding
);
180 table.insert(lines
, "\t "..npadding
.."Locals: "..locals_str
);
183 local upvalues_str
= string_from_var_table(current_level
.upvalues
, optimal_line_length
, "\t "..npadding
);
185 table.insert(lines
, "\t "..npadding
.."Upvals: "..upvalues_str
);
189 -- table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc));
191 return message
.."stack traceback:\n"..table.concat(lines
, "\n");
194 local function traceback(...)
195 local ok
, ret
= pcall(_traceback
, ...);
197 return "Error in error handling: "..ret
;
203 debug
.traceback
= traceback
;
207 get_locals_table
= get_locals_table
;
208 get_upvalues_table
= get_upvalues_table
;
209 string_from_var_table
= string_from_var_table
;
210 get_traceback_table
= get_traceback_table
;
211 traceback
= traceback
;