From e2b8325088e588ab4ccc00ce738dad223228114d Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 2 Jun 2020 08:00:37 +0100 Subject: util.human.io: New central place for UI helpers --- util/human/io.lua | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 util/human/io.lua (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua new file mode 100644 index 00000000..bfd1c00d --- /dev/null +++ b/util/human/io.lua @@ -0,0 +1,96 @@ +local function getchar(n) + local stty_ret = os.execute("stty raw -echo 2>/dev/null"); + local ok, char; + if stty_ret == true or stty_ret == 0 then + ok, char = pcall(io.read, n or 1); + os.execute("stty sane"); + else + ok, char = pcall(io.read, "*l"); + if ok then + char = char:sub(1, n or 1); + end + end + if ok then + return char; + end +end + +local function getline() + local ok, line = pcall(io.read, "*l"); + if ok then + return line; + end +end + +local function getpass() + local stty_ret, _, status_code = os.execute("stty -echo 2>/dev/null"); + if status_code then -- COMPAT w/ Lua 5.1 + stty_ret = status_code; + end + if stty_ret ~= 0 then + io.write("\027[08m"); -- ANSI 'hidden' text attribute + end + local ok, pass = pcall(io.read, "*l"); + if stty_ret == 0 then + os.execute("stty sane"); + else + io.write("\027[00m"); + end + io.write("\n"); + if ok then + return pass; + end +end + +local function show_yesno(prompt) + io.write(prompt, " "); + local choice = getchar():lower(); + io.write("\n"); + if not choice:match("%a") then + choice = prompt:match("%[.-(%U).-%]$"); + if not choice then return nil; end + end + return (choice == "y"); +end + +local function read_password() + local password; + while true do + io.write("Enter new password: "); + password = getpass(); + if not password then + print("No password - cancelled"); + return; + end + io.write("Retype new password: "); + if getpass() ~= password then + if not show_yesno [=[Passwords did not match, try again? [Y/n]]=] then + return; + end + else + break; + end + end + return password; +end + +local function show_prompt(prompt) + io.write(prompt, " "); + local line = getline(); + line = line and line:gsub("\n$",""); + return (line and #line > 0) and line or nil; +end + +local function printf(fmt, ...) + print(msg:format(...)); +end + +return { + getchar = getchar; + getline = getline; + getpass = getpass; + show_yesno = show_yesno; + read_password = read_password; + show_prompt = show_prompt; + printf = printf; +}; -- cgit v1.2.3 From 01f780eef118521830a7e377f69f9b70acfc3896 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 2 Jun 2020 08:02:03 +0100 Subject: util.human.io: Fix variable name [luacheck] --- util/human/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index bfd1c00d..4c84c4a4 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -82,7 +82,7 @@ local function show_prompt(prompt) end local function printf(fmt, ...) - print(msg:format(...)); + print(fmt:format(...)); end return { -- cgit v1.2.3 From 994f59501b9eecf736792d256434ceb7d519adc7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Jan 2019 08:46:26 +0100 Subject: util.human.units: A library for formatting numbers with SI units --- util/human/units.lua | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 util/human/units.lua (limited to 'util/human') diff --git a/util/human/units.lua b/util/human/units.lua new file mode 100644 index 00000000..2c4662cd --- /dev/null +++ b/util/human/units.lua @@ -0,0 +1,58 @@ +local large = { + "k", 1000, + "M", 1000000, + "G", 1000000000, + "T", 1000000000000, + "P", 1000000000000000, + "E", 1000000000000000000, + "Z", 1000000000000000000000, + "Y", 1000000000000000000000000, +} +local small = { + "m", 0.001, + "μ", 0.000001, + "n", 0.000000001, + "p", 0.000000000001, + "f", 0.000000000000001, + "a", 0.000000000000000001, + "z", 0.000000000000000000001, + "y", 0.000000000000000000000001, +} + +local binary = { + "Ki", 2^10, + "Mi", 2^20, + "Gi", 2^30, + "Ti", 2^40, + "Pi", 2^50, + "Ei", 2^60, + "Zi", 2^70, + "Yi", 2^80, +} + +-- n: number, the number to format +-- unit: string, the base unit +-- b: optional enum 'b', thousands base +local function format(n, unit, b) --> string + local round = math.floor; + local prefixes = large; + local logbase = 1000; + local fmt = "%.3g %s%s"; + if n == 0 then + return fmt:format(n, "", unit); + end + if b == 'b' then + prefixes = binary; + logbase = 1024; + elseif n < 1 then + prefixes = small; + round = math.ceil; + end + local m = math.max(0, math.min(8, round(math.abs(math.log(math.abs(n), logbase))))); + local prefix, multiplier = table.unpack(prefixes, m * 2-1, m*2); + return fmt:format(n / (multiplier or 1), prefix or "", unit); +end + +return { + format = format; +}; -- cgit v1.2.3 From b95b0e602662861b8db5524d5e5971a927fb076f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 19:46:17 +0200 Subject: util.human.units: Handle location of unpack() in Lua 5.1 --- util/human/units.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'util/human') diff --git a/util/human/units.lua b/util/human/units.lua index 2c4662cd..91d6f0d5 100644 --- a/util/human/units.lua +++ b/util/human/units.lua @@ -1,3 +1,5 @@ +local unpack = table.unpack or unpack; --luacheck: ignore 113 + local large = { "k", 1000, "M", 1000000, @@ -49,7 +51,7 @@ local function format(n, unit, b) --> string round = math.ceil; end local m = math.max(0, math.min(8, round(math.abs(math.log(math.abs(n), logbase))))); - local prefix, multiplier = table.unpack(prefixes, m * 2-1, m*2); + local prefix, multiplier = unpack(prefixes, m * 2-1, m*2); return fmt:format(n / (multiplier or 1), prefix or "", unit); end -- cgit v1.2.3 From b73cad7f8e92862a77d03ecc3eb9e24bb389cc07 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 20:16:00 +0200 Subject: util.human.units: Put math functions into locals Primarily because the next commit will deal with math.log behaving differently on Lua 5.1 and that's eaiser with locals. --- util/human/units.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'util/human') diff --git a/util/human/units.lua b/util/human/units.lua index 91d6f0d5..471c82a0 100644 --- a/util/human/units.lua +++ b/util/human/units.lua @@ -1,3 +1,9 @@ +local math_abs = math.abs; +local math_ceil = math.ceil; +local math_floor = math.floor; +local math_log = math.log; +local math_max = math.max; +local math_min = math.min; local unpack = table.unpack or unpack; --luacheck: ignore 113 local large = { @@ -36,7 +42,7 @@ local binary = { -- unit: string, the base unit -- b: optional enum 'b', thousands base local function format(n, unit, b) --> string - local round = math.floor; + local round = math_floor; local prefixes = large; local logbase = 1000; local fmt = "%.3g %s%s"; @@ -48,9 +54,9 @@ local function format(n, unit, b) --> string logbase = 1024; elseif n < 1 then prefixes = small; - round = math.ceil; + round = math_ceil; end - local m = math.max(0, math.min(8, round(math.abs(math.log(math.abs(n), logbase))))); + local m = math_max(0, math_min(8, round(math_abs(math_log(math_abs(n), logbase))))); local prefix, multiplier = unpack(prefixes, m * 2-1, m*2); return fmt:format(n / (multiplier or 1), prefix or "", unit); end -- cgit v1.2.3 From b2ad87f966f17bacb36366f9e3bf21c21c31b6f7 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 3 Jun 2020 20:17:33 +0200 Subject: util.human.units: Handle lack of math.log(n, base) on Lua 5.1 --- util/human/units.lua | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'util/human') diff --git a/util/human/units.lua b/util/human/units.lua index 471c82a0..5a083783 100644 --- a/util/human/units.lua +++ b/util/human/units.lua @@ -6,6 +6,14 @@ local math_max = math.max; local math_min = math.min; local unpack = table.unpack or unpack; --luacheck: ignore 113 +if math_log(10, 10) ~= 1 then + -- Lua 5.1 COMPAT + local log10 = math.log10; + function math_log(n, base) + return log10(n) / log10(base); + end +end + local large = { "k", 1000, "M", 1000000, -- cgit v1.2.3 From baf9f5aeefeb2900af1d3026799ec382f4f69d5b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 3 Jun 2020 22:21:17 +0100 Subject: util.human.io: Add padleft, padright and a table printing function --- util/human/io.lua | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 4c84c4a4..338509b1 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -85,6 +85,56 @@ local function printf(fmt, ...) print(fmt:format(...)); end +local function padright(s, width) + return s..string.rep(" ", width-#s); +end + +local function padleft(s, width) + return string.rep(" ", width-#s)..s; +end + +local function table(col_specs, max_width, padding) + max_width = max_width or 80; + padding = padding or 4; + + local widths = {}; + local total_width = max_width - padding; + local free_width = total_width; + -- Calculate width of fixed-size columns + for i = 1, #col_specs do + local width = col_specs[i].width or "0"; + if not(type(width) == "string" and width:sub(-1) == "%") then + local title = col_specs[i].title; + width = math.max(tonumber(width), title and (#title+1) or 0); + widths[i] = width; + free_width = free_width - width; + end + end + -- Calculate width of %-based columns + for i = 1, #col_specs do + if not widths[i] then + local pc_width = tonumber((col_specs[i].width:gsub("%%$", ""))); + widths[i] = math.floor(free_width*(pc_width/100)); + end + end + + return function (row, f) + for i, column in ipairs(col_specs) do + local width = widths[i]; + local v = tostring(row[column.key or i] or ""):sub(1, width); + if #v < width then + if column.align == "right" then + v = padleft(v, width-1).." "; + else + v = padright(v, width); + end + end + (f or io.stdout):write(v); + end + (f or io.stdout):write("\n"); + end; +end + return { getchar = getchar; getline = getline; @@ -93,4 +143,7 @@ return { read_password = read_password; show_prompt = show_prompt; printf = printf; + padleft = padleft; + padright = padright; + table = table; }; -- cgit v1.2.3 From 8467f4f9eaeeaf1c70c8ee958eb9eb9757c8fb64 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 3 Jun 2020 22:45:33 +0100 Subject: util.human.io: table: switch row function to simply returning prepared row string --- util/human/io.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 338509b1..dfed3b09 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -93,7 +93,7 @@ local function padleft(s, width) return string.rep(" ", width-#s)..s; end -local function table(col_specs, max_width, padding) +local function new_table(col_specs, max_width, padding) max_width = max_width or 80; padding = padding or 4; @@ -118,7 +118,8 @@ local function table(col_specs, max_width, padding) end end - return function (row, f) + return function (row) + local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; local v = tostring(row[column.key or i] or ""):sub(1, width); @@ -129,9 +130,9 @@ local function table(col_specs, max_width, padding) v = padright(v, width); end end - (f or io.stdout):write(v); + table.insert(output, v); end - (f or io.stdout):write("\n"); + return table.concat(output); end; end @@ -145,5 +146,5 @@ return { printf = printf; padleft = padleft; padright = padright; - table = table; + table = new_table; }; -- cgit v1.2.3 From 7b7084ad68dd4c30998a4e5d79ac0e5d04fb08d6 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 3 Jun 2020 22:58:29 +0100 Subject: util.human.io: table: Return title row when no row data passed --- util/human/io.lua | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index dfed3b09..8c328c14 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -1,3 +1,5 @@ +local array = require "util.array"; + local function getchar(n) local stty_ret = os.execute("stty raw -echo 2>/dev/null"); local ok, char; @@ -119,6 +121,9 @@ local function new_table(col_specs, max_width, padding) end return function (row) + if not row then + row = array.pluck(col_specs, "title"); + end local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; -- cgit v1.2.3 From 19b7e38e1c80c4f9a8623877d36bbe4c5251c696 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 4 Jun 2020 10:39:55 +0100 Subject: util.human.io: table: Fix title printing when columns use named keys --- util/human/io.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 8c328c14..9e700e89 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -121,13 +121,14 @@ local function new_table(col_specs, max_width, padding) end return function (row) + local titles; if not row then - row = array.pluck(col_specs, "title"); + titles, row = true, array.pluck(col_specs, "title", ""); end local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; - local v = tostring(row[column.key or i] or ""):sub(1, width); + local v = tostring(row[not titles and column.key or i] or ""):sub(1, width); if #v < width then if column.align == "right" then v = padleft(v, width-1).." "; -- cgit v1.2.3 From 8c7811ec43bd254b110319f983043e5fa16dcabf Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 16:56:28 +0200 Subject: util.human.units: Factor out function for getting multiplier --- util/human/units.lua | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'util/human') diff --git a/util/human/units.lua b/util/human/units.lua index 5a083783..af233e98 100644 --- a/util/human/units.lua +++ b/util/human/units.lua @@ -46,17 +46,10 @@ local binary = { "Yi", 2^80, } --- n: number, the number to format --- unit: string, the base unit --- b: optional enum 'b', thousands base -local function format(n, unit, b) --> string +local function adjusted_unit(n, b) local round = math_floor; local prefixes = large; local logbase = 1000; - local fmt = "%.3g %s%s"; - if n == 0 then - return fmt:format(n, "", unit); - end if b == 'b' then prefixes = binary; logbase = 1024; @@ -66,9 +59,22 @@ local function format(n, unit, b) --> string end local m = math_max(0, math_min(8, round(math_abs(math_log(math_abs(n), logbase))))); local prefix, multiplier = unpack(prefixes, m * 2-1, m*2); - return fmt:format(n / (multiplier or 1), prefix or "", unit); + return multiplier or 1, prefix; +end + +-- n: number, the number to format +-- unit: string, the base unit +-- b: optional enum 'b', thousands base +local function format(n, unit, b) --> string + local fmt = "%.3g %s%s"; + if n == 0 then + return fmt:format(n, "", unit); + end + local multiplier, prefix = adjusted_unit(n, b); + return fmt:format(n / multiplier, prefix or "", unit); end return { + adjust = adjusted_unit; format = format; }; -- cgit v1.2.3 From 1b30a8e979ed1962480e8f89f802211858896f84 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 4 Jun 2020 17:24:30 +0100 Subject: util.human.io: Remove padding option and use $COLUMNS as default width --- util/human/io.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 9e700e89..2cea4f6b 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -95,12 +95,11 @@ local function padleft(s, width) return string.rep(" ", width-#s)..s; end -local function new_table(col_specs, max_width, padding) - max_width = max_width or 80; - padding = padding or 4; +local function new_table(col_specs, max_width) + max_width = max_width or tonumber(os.getenv("COLUMNS")) or 80; local widths = {}; - local total_width = max_width - padding; + local total_width = max_width; local free_width = total_width; -- Calculate width of fixed-size columns for i = 1, #col_specs do -- cgit v1.2.3 From ad3d04768583786fdbf595f901e832c040afa038 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 18:31:50 +0200 Subject: util.human.io: Draw a separator between columns --- util/human/io.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 2cea4f6b..0f0c9155 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -97,9 +97,10 @@ end local function new_table(col_specs, max_width) max_width = max_width or tonumber(os.getenv("COLUMNS")) or 80; + local separator = " | "; local widths = {}; - local total_width = max_width; + local total_width = max_width - #separator * (#col_specs-1); local free_width = total_width; -- Calculate width of fixed-size columns for i = 1, #col_specs do @@ -137,7 +138,7 @@ local function new_table(col_specs, max_width) end table.insert(output, v); end - return table.concat(output); + return table.concat(output, separator); end; end -- cgit v1.2.3 From 435cd193730136205057c8cbed1c4095573303eb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 18:32:33 +0200 Subject: util.human.io: Replace overflow with ellipsis --- util/human/io.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 0f0c9155..389ed25a 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -128,13 +128,15 @@ local function new_table(col_specs, max_width) local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; - local v = tostring(row[not titles and column.key or i] or ""):sub(1, width); + local v = tostring(row[not titles and column.key or i] or ""); if #v < width then if column.align == "right" then v = padleft(v, width-1).." "; else v = padright(v, width); end + elseif #v > width then + v = v:sub(1, width-1) .. "\u{2026}"; end table.insert(output, v); end -- cgit v1.2.3 From 87777b26af1c1c63f90e1e0e8f9f3c18490322c1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 18:36:47 +0200 Subject: util.human.io: Use literal ellipsis instead of \u escape For compat with Lua 5.2 and before --- util/human/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 389ed25a..8d987355 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -136,7 +136,7 @@ local function new_table(col_specs, max_width) v = padright(v, width); end elseif #v > width then - v = v:sub(1, width-1) .. "\u{2026}"; + v = v:sub(1, width-1) .. "…"; end table.insert(output, v); end -- cgit v1.2.3 From 4e554bc4d156354ce835ae92fa0184504c77b61e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 18:40:37 +0200 Subject: util.human.io: Consider separator when calculating remaining width --- util/human/io.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 8d987355..76553fac 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -110,6 +110,9 @@ local function new_table(col_specs, max_width) width = math.max(tonumber(width), title and (#title+1) or 0); widths[i] = width; free_width = free_width - width; + if i > 1 then + free_width = free_width - #separator; + end end end -- Calculate width of %-based columns -- cgit v1.2.3 From 9b70ffb0ca54a2332459c90c1c32a9cc83b4076c Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Thu, 4 Jun 2020 21:32:28 +0200 Subject: util.human.io.table: Allow a map callaback per column This allows e.g. mapping booleans to "yes" or "no", specific number formatting or generating virtual columns. All while not mutating the underlying data or creating additional temporary tables. --- util/human/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 76553fac..7285c79f 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -131,7 +131,7 @@ local function new_table(col_specs, max_width) local output = {}; for i, column in ipairs(col_specs) do local width = widths[i]; - local v = tostring(row[not titles and column.key or i] or ""); + local v = (not titles and column.mapper or tostring)(row[not titles and column.key or i] or "", row); if #v < width then if column.align == "right" then v = padleft(v, width-1).." "; -- cgit v1.2.3 From 4a0cb5a30643224dd6a9ebd91e53e257badaf7f9 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 6 Jun 2020 16:43:28 +0200 Subject: util.human.io: Fix right-alignment --- util/human/io.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util/human') diff --git a/util/human/io.lua b/util/human/io.lua index 7285c79f..a38ab5dd 100644 --- a/util/human/io.lua +++ b/util/human/io.lua @@ -134,7 +134,7 @@ local function new_table(col_specs, max_width) local v = (not titles and column.mapper or tostring)(row[not titles and column.key or i] or "", row); if #v < width then if column.align == "right" then - v = padleft(v, width-1).." "; + v = padleft(v, width); else v = padright(v, width); end -- cgit v1.2.3