diff options
Diffstat (limited to 'util/human')
-rw-r--r-- | util/human/io.lua | 192 | ||||
-rw-r--r-- | util/human/units.lua | 80 |
2 files changed, 272 insertions, 0 deletions
diff --git a/util/human/io.lua b/util/human/io.lua new file mode 100644 index 00000000..7d7dea97 --- /dev/null +++ b/util/human/io.lua @@ -0,0 +1,192 @@ +local array = require "util.array"; +local utf8 = rawget(_G, "utf8") or require"util.encodings".utf8; +local len = utf8.len or function(s) + local _, count = s:gsub("[%z\001-\127\194-\253][\128-\191]*", ""); + return count; +end; + +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(fmt:format(...)); +end + +local function padright(s, width) + return s..string.rep(" ", width-len(s)); +end + +local function padleft(s, width) + return string.rep(" ", width-len(s))..s; +end + +local pat = "[%z\001-\127\194-\253][\128-\191]*"; +local function utf8_cut(s, pos) + return s:match("^"..pat:rep(pos)) or s; +end + +if utf8.len and utf8.offset then + function utf8_cut(s, pos) + return s:sub(1, utf8.offset(s, pos+1)-1); + end +end + +local function ellipsis(s, width) + if len(s) <= width then return s; end + if width == 1 then return "…"; end + return utf8_cut(s, width - 1) .. "…"; +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 - #separator * (#col_specs-1); + 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; + if i > 1 then + free_width = free_width - #separator; + end + 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) + local titles; + if not row then + 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 = row[not titles and column.key or i]; + if not titles and column.mapper then + v = column.mapper(v, row); + end + if v == nil then + v = column.default or ""; + else + v = tostring(v); + end + if len(v) < width then + if column.align == "right" then + v = padleft(v, width); + else + v = padright(v, width); + end + elseif len(v) > width then + v = ellipsis(v, width); + end + table.insert(output, v); + end + return table.concat(output, separator); + end; +end + +return { + getchar = getchar; + getline = getline; + getpass = getpass; + show_yesno = show_yesno; + read_password = read_password; + show_prompt = show_prompt; + printf = printf; + padleft = padleft; + padright = padright; + ellipsis = ellipsis; + table = new_table; +}; diff --git a/util/human/units.lua b/util/human/units.lua new file mode 100644 index 00000000..af233e98 --- /dev/null +++ b/util/human/units.lua @@ -0,0 +1,80 @@ +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 + +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, + "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, +} + +local function adjusted_unit(n, b) + local round = math_floor; + local prefixes = large; + local logbase = 1000; + 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 = unpack(prefixes, m * 2-1, m*2); + 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; +}; |