///|
/// Value provided by the user on the command line, if any.
/// Distinct from the model defaults because the argparse default_values
/// are unset for field options.
fn override_value(matches : @clap.Matches, name : String) -> String? {
matches.values.get(name).bind(arr => arr.get(0))
}
///|
fn ansi_code(color : String) -> String {
match color {
"black" => "30"
"red" => "31"
"green" => "32"
"yellow" => "33"
"blue" => "34"
"magenta" => "35"
"cyan" => "36"
"white" => "37"
_ => ""
}
}
///|
/// Render a number with two decimals, rounded to nearest hundredth,
/// e.g. `46.3` -> `"46.30"`.
fn fixed_two(x : Float) -> String {
let hundredths = (x * 100.0).round().to_int()
let whole = hundredths / 100
let frac = hundredths % 100
let frac_text = if frac < 10 { "0\{frac}" } else { "\{frac}" }
"\{whole}.\{frac_text}"
}
///|
/// Render the fake system-info output for the parsed command line.
pub fn render(matches : @clap.Matches) -> String {
let model = override_value(matches, "model")
.bind(model_by_name)
.unwrap_or(default_model())
let info = render_info(matches, model)
let no_logo = matches.flags.get("no-logo").unwrap_or(false)
let logo = {
guard !no_logo else { None }
logo_by_name(override_value(matches, "logo").unwrap_or(model.logo))
}
match logo {
None => render_info_only(info)
Some(logo) =>
render_with_logo(
info,
decode_logo(logo, override_value(matches, "color").unwrap_or("")),
)
}
}
///|
fn render_info(matches : @clap.Matches, model : DeviceModel) -> Array[String] {
let user = override_value(matches, "user").unwrap_or("kokic")
let host = override_value(matches, "host").unwrap_or(model.host)
let header = "\{user}@\{host}"
let dashline = "-".repeat(header.char_length())
[
header,
dashline,
..field_line("OS", override_value(matches, "os").unwrap_or(model.os)),
..field_line(
"Host",
override_value(matches, "device").unwrap_or(model.device),
),
..field_line(
"Kernel",
override_value(matches, "kernel").unwrap_or(model.kernel),
),
..field_line(
"Uptime",
structured_field(matches, "uptime", model.uptime.render(), s => {
s.parse_uptime().map(u => u.render())
}),
),
..field_line(
"Shell",
override_value(matches, "shell").unwrap_or(model.shell),
),
..field_line(
"Resolution",
structured_field(
matches,
"resolution",
model.resolution.map(r => r.render()).unwrap_or(""),
s => s.parse_resolution().map(r => r.render()),
),
),
..field_line("DE", override_value(matches, "de").unwrap_or(model.de)),
..field_line("WM", override_value(matches, "wm").unwrap_or(model.wm)),
..field_line(
"WM Theme",
override_value(matches, "wm-theme").unwrap_or(model.wm_theme),
),
..field_line(
"Terminal",
override_value(matches, "terminal").unwrap_or(model.terminal),
),
..field_line(
"Terminal Font",
override_value(matches, "term-font").unwrap_or(model.term_font),
),
..field_line(
"CPU",
structured_field(matches, "cpu", model.cpu.render(), s => {
s.parse_cpu().map(c => c.render())
}),
),
..field_line(
"GPU",
structured_field(matches, "gpu", model.gpu.render(), s => {
s.parse_gpu().map(g => g.render())
}),
),
..field_line(
"Memory",
structured_field(matches, "memory", model.memory.render(), s => {
s.parse_memory().map(m => m.render())
}),
),
]
}
///|
/// The rendered value of a structured field: an explicit override when it
/// parses, otherwise the model default. An empty override hides the line,
/// like other fields.
fn structured_field(
matches : @clap.Matches,
name : String,
default : String,
parse : (String) -> String?,
) -> String {
match override_value(matches, name) {
Some("") => ""
Some(s) => parse(s).unwrap_or(default)
None => default
}
}
///|
fn render_info_only(info : Array[String]) -> String {
"\{info.join("\n")}\n"
}
///|
fn render_with_logo(info : Array[String], rendered : DecodedLogo) -> String {
let buf = StringBuilder()
let width = rendered.max_width
let rows = rendered.lines.length().max(info.length())
for i in 0.. {
buf.write_string(text)
buf.write_string(" ".repeat(width - line_width))
}
None => buf.write_string(" ".repeat(width))
}
buf.write_string(" ")
if info.get(i) is Some(text) {
buf.write_string(text)
}
buf.write_string("\n")
}
buf.to_string()
}
///|
fn field_line(label : String, value : String) -> Array[String] {
guard !value.is_empty() else { [] }
["\{label}: \{value}"]
}
///|
priv struct DecodedLogo {
lines : Array[(String, Int)]
max_width : Int
}
///|
/// The palette entry used to color a logo cell: the forced color wins,
/// otherwise the palette color at `index`, unless colors are disabled.
fn resolve_code(
palette : Array[String],
forced_code : String,
no_color : Bool,
index : Int,
) -> String {
guard !no_color else { "" }
if !forced_code.is_empty() {
forced_code
} else {
palette.get(index).unwrap_or("")
}
}
///|
fn decode_logo(logo : Logo, color_mode : String) -> DecodedLogo {
let no_color = color_mode == "none"
let forced_code = match color_mode {
"auto" | "none" => ""
_ => ansi_code(color_mode)
}
let mut carry = resolve_code(logo.palette, forced_code, no_color, 0)
let lines : Array[(String, Int)] = []
let mut max_width = 0
for line in logo.lines {
let decoded = decode_logo_line(
line,
logo.palette,
forced_code,
carry,
no_color,
)
carry = decoded.carry
lines.push((decoded.text, decoded.width))
max_width = max_width.max(decoded.width)
}
{ lines, max_width }
}
///|
priv struct DecodedLogoLine {
text : String
width : Int
carry : String
}
///|
fn write_ansi(buf : StringBuilder, code : String) -> Unit {
buf.write_string("\u{1B}[")
buf.write_string(code)
buf.write_string("m")
}
///|
fn decode_logo_line(
line : String,
palette : Array[String],
forced_code : String,
start_carry : String,
no_color : Bool,
) -> DecodedLogoLine {
let buf = StringBuilder()
let mut carry = start_carry
let mut width = 0
if !carry.is_empty() {
write_ansi(buf, carry)
}
let chars = line.to_array()
let mut index = 0
while index < chars.length() {
match (chars[index], chars.get(index + 1)) {
('$', Some('$')) => {
buf.write_char('$')
width += 1
index += 2
}
('$', Some(n)) if n >= '1' && n <= '9' => {
let code = resolve_code(
palette,
forced_code,
no_color,
n.to_int() - '1'.to_int(),
)
carry = code
if !code.is_empty() {
write_ansi(buf, code)
}
index += 2
}
('$', _) => {
buf.write_char('$')
width += 1
index += 1
}
(c, _) => {
buf.write_char(c)
width += 1
index += 1
}
}
}
if !carry.is_empty() {
buf.write_string("\u{1B}[0m")
}
{ text: buf.to_string(), width, carry }
}