diff options
Diffstat (limited to 'src/output.rs')
-rw-r--r-- | src/output.rs | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/src/output.rs b/src/output.rs new file mode 100644 index 0000000..77fb93b --- /dev/null +++ b/src/output.rs @@ -0,0 +1,160 @@ +// output.rs + +// Copyright (C) 2020 The Nitrocli Developers +// SPDX-License-Identifier: GPL-3.0-or-later + +//! Defines data types that can be formatted in different output formats. + +use std::fmt; + +use crate::Context; + +/// A trait for objects that can be printed as nitrocli’s output. +pub trait Output { + /// Formats this object as a string that can be printed to the standard output. + fn format(&self) -> anyhow::Result<String>; + + /// Prints this object to the output set in the given context. + /// + /// The default implementation for this method prints the return value of `format` to + /// `ctx.stdout`. + fn print(&self, ctx: &mut Context<'_>) -> anyhow::Result<()> { + println!(ctx, "{}", self.format()?.trim_end()).map_err(From::from) + } +} + +/// A single object. +pub struct Value<T: fmt::Display>(T); + +/// A list of objects of the same type that is displayed as a table with a fallback message for an +/// empty list. +pub struct Table<T: TableItem> { + items: Vec<T>, + empty_message: String, +} + +/// A trait for objects that can be displayed in a table. +pub trait TableItem { + /// Returns the column headers for this type of table items. + fn headers() -> Vec<&'static str>; + /// Returns the values of the column for this table item. + fn values(&self) -> Vec<String>; +} + +/// A helper struct for building text reprensetations of objects. +pub struct TextObject { + name: String, + items: Vec<(usize, String, String)>, +} + +impl<T: fmt::Display> Value<T> { + pub fn new(value: T) -> Value<T> { + Value(value) + } +} + +impl<T: fmt::Display> Output for Value<T> { + fn format(&self) -> anyhow::Result<String> { + Ok(self.0.to_string()) + } +} + +impl<T: TableItem> Table<T> { + pub fn new(empty_message: impl Into<String>) -> Table<T> { + Table { + items: Vec::new(), + empty_message: empty_message.into(), + } + } + + pub fn push(&mut self, item: T) { + self.items.push(item); + } + + pub fn append(&mut self, vec: &mut Vec<T>) { + self.items.append(vec); + } +} + +impl<T: TableItem> Output for Table<T> { + fn format(&self) -> anyhow::Result<String> { + if self.items.is_empty() { + Ok(self.empty_message.clone()) + } else { + let headers = T::headers().into_iter().map(ToOwned::to_owned).collect(); + let values = self.items.iter().map(TableItem::values); + Ok(print_table(headers, values)) + } + } +} + +fn print_table<I>(headers: Vec<String>, iter: I) -> String +where + I: Iterator<Item = Vec<String>>, +{ + let mut values = Vec::new(); + values.push(headers); + values.extend(iter); + let n = values.iter().map(Vec::len).min().unwrap_or_default(); + let lens: Vec<_> = (0..n) + .map(|idx| { + values + .iter() + .map(|v| v[idx].len()) + .max() + .unwrap_or_default() + }) + .collect(); + values + .iter() + .map(|v| print_table_line(&lens, &v)) + .collect::<Vec<_>>() + .join("\n") +} + +fn print_table_line(lens: &[usize], values: &[String]) -> String { + lens + .iter() + .zip(values) + .map(|(width, value)| format!("{:width$}", value, width = width)) + .collect::<Vec<_>>() + .join("\t") +} + +impl TextObject { + pub fn new(name: impl Into<String>) -> TextObject { + TextObject { + name: name.into(), + items: Vec::new(), + } + } + + pub fn push_line(&mut self, key: impl Into<String>, value: impl Into<String>) { + self.items.push((1, key.into(), value.into())); + } + + pub fn push_object(&mut self, o: TextObject) { + self.push_line(o.name, ""); + for (indent, key, value) in o.items { + self.items.push((1 + indent, key, value)); + } + } +} + +impl fmt::Display for TextObject { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{}:", self.name)?; + let max_len = self + .items + .iter() + .map(|(indent, key, _)| indent * 2 + key.len()) + .max() + .unwrap_or(0); + for (indent, key, value) in &self.items { + let prefix = " ".repeat(indent * 2); + let padding = " ".repeat(max_len - key.len() - indent * 2); + writeln!(f, "{}{}:{} {}", prefix, key, padding, value)?; + } + Ok(()) + } +} |