aboutsummaryrefslogtreecommitdiffstats
path: root/util/human
diff options
context:
space:
mode:
Diffstat (limited to 'util/human')
-rw-r--r--util/human/io.lua192
-rw-r--r--util/human/units.lua80
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;
+};