add max table width arg, refactor printer (#237)

* printer: refactor output to pass down args from cli

* msg: add missing max width arg to search cmd

* output: rename printer service, merge print with output folder

* doc: update changelog and wiki

* table: rename print fn
This commit is contained in:
Clément DOUIN
2021-10-24 21:02:02 +02:00
committed by GitHub
parent 192445d7e4
commit e154481c5b
23 changed files with 457 additions and 346 deletions
+8 -2
View File
@@ -5,8 +5,14 @@ pub mod output_arg;
pub mod output_utils;
pub use output_utils::*;
pub mod output_service;
pub use output_service::*;
pub mod output_entity;
pub use output_entity::*;
pub mod print;
pub use print::*;
pub mod print_table;
pub use print_table::*;
pub mod printer_service;
pub use printer_service::*;
+57
View File
@@ -0,0 +1,57 @@
use anyhow::{anyhow, Error, Result};
use serde::Serialize;
use std::{
convert::TryFrom,
fmt::{self, Display},
};
/// Represents the available output formats.
#[derive(Debug, PartialEq)]
pub enum OutputFmt {
Plain,
Json,
}
impl From<&str> for OutputFmt {
fn from(fmt: &str) -> Self {
match fmt {
slice if slice.eq_ignore_ascii_case("json") => Self::Json,
_ => Self::Plain,
}
}
}
impl TryFrom<Option<&str>> for OutputFmt {
type Error = Error;
fn try_from(fmt: Option<&str>) -> Result<Self, Self::Error> {
match fmt {
Some(fmt) if fmt.eq_ignore_ascii_case("json") => Ok(Self::Json),
Some(fmt) if fmt.eq_ignore_ascii_case("plain") => Ok(Self::Plain),
None => Ok(Self::Plain),
Some(fmt) => Err(anyhow!(r#"cannot parse output format "{}""#, fmt)),
}
}
}
impl Display for OutputFmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fmt = match self {
&OutputFmt::Json => "JSON",
&OutputFmt::Plain => "Plain",
};
write!(f, "{}", fmt)
}
}
/// Defines a struct-wrapper to provide a JSON output.
#[derive(Debug, Serialize, Clone)]
pub struct OutputJson<T: Serialize> {
response: T,
}
impl<T: Serialize> OutputJson<T> {
pub fn new(response: T) -> Self {
Self { response }
}
}
-132
View File
@@ -1,132 +0,0 @@
use anyhow::{anyhow, Error, Result};
use atty::Stream;
use log::debug;
use serde::Serialize;
use std::{
convert::{TryFrom, TryInto},
fmt,
};
use termcolor::{ColorChoice, StandardStream};
use crate::output::Print;
#[derive(Debug, PartialEq)]
pub enum OutputFmt {
Plain,
Json,
}
impl From<&str> for OutputFmt {
fn from(fmt: &str) -> Self {
match fmt {
slice if slice.eq_ignore_ascii_case("json") => Self::Json,
_ => Self::Plain,
}
}
}
impl TryFrom<Option<&str>> for OutputFmt {
type Error = Error;
fn try_from(fmt: Option<&str>) -> Result<Self, Self::Error> {
match fmt {
Some(slice) if slice.eq_ignore_ascii_case("json") => Ok(Self::Json),
Some(slice) if slice.eq_ignore_ascii_case("plain") => Ok(Self::Plain),
None => Ok(Self::Plain),
Some(slice) => Err(anyhow!("cannot parse output `{}`", slice)),
}
}
}
impl fmt::Display for OutputFmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let slice = match self {
&OutputFmt::Json => "JSON",
&OutputFmt::Plain => "PLAIN",
};
write!(f, "{}", slice)
}
}
// JSON output helper
/// A little struct-wrapper to provide a JSON output.
#[derive(Debug, Serialize, Clone)]
pub struct OutputJson<T: Serialize> {
response: T,
}
impl<T: Serialize> OutputJson<T> {
pub fn new(response: T) -> Self {
Self { response }
}
}
pub trait OutputServiceInterface {
fn print<T: Serialize + Print>(&self, data: T) -> Result<()>;
fn is_json(&self) -> bool;
}
#[derive(Debug)]
pub struct OutputService {
fmt: OutputFmt,
}
impl OutputServiceInterface for OutputService {
/// Print the provided item out according to the formatting setting when you created this
/// struct.
fn print<T: Serialize + Print>(&self, data: T) -> Result<()> {
match self.fmt {
OutputFmt::Plain => {
data.print(&mut StandardStream::stdout(if atty::isnt(Stream::Stdin) {
// Colors should be deactivated if the terminal is not a tty.
ColorChoice::Never
} else {
// Otherwise let's `termcolor` decide by inspecting the environment. From the [doc]:
// - If `NO_COLOR` is set to any value, then colors will be suppressed.
// - If `TERM` is set to dumb, then colors will be suppressed.
// - In non-Windows environments, if `TERM` is not set, then colors will be suppressed.
//
// [doc]: https://github.com/BurntSushi/termcolor#automatic-color-selection
ColorChoice::Auto
}))?;
}
OutputFmt::Json => {
print!("{}", serde_json::to_string(&OutputJson::new(data))?)
}
};
Ok(())
}
/// Returns true, if the formatting should be json.
fn is_json(&self) -> bool {
self.fmt == OutputFmt::Json
}
}
impl Default for OutputService {
fn default() -> Self {
Self {
fmt: OutputFmt::Plain,
}
}
}
impl From<&str> for OutputService {
fn from(fmt: &str) -> Self {
debug!("init output service");
debug!("output: `{:?}`", fmt);
let fmt = fmt.into();
Self { fmt }
}
}
impl TryFrom<Option<&str>> for OutputService {
type Error = Error;
fn try_from(fmt: Option<&str>) -> Result<Self, Self::Error> {
debug!("init output service");
debug!("output: `{:?}`", fmt);
let fmt = fmt.try_into()?;
Ok(Self { fmt })
}
}
+1
View File
@@ -1,6 +1,7 @@
use anyhow::Result;
use std::process::Command;
/// TODO: move this in a more approriate place.
pub fn run_cmd(cmd: &str) -> Result<String> {
let output = if cfg!(target_os = "windows") {
Command::new("cmd").args(&["/C", cmd]).output()
+9 -14
View File
@@ -1,28 +1,23 @@
use anyhow::{Context, Result};
use std::io;
use termcolor::{StandardStream, WriteColor};
use log::error;
pub trait WriteWithColor: io::Write + WriteColor {}
impl WriteWithColor for StandardStream {}
use crate::output::WriteColor;
pub trait Print {
fn print<W: WriteWithColor>(&self, writter: &mut W) -> Result<()>;
fn println<W: WriteWithColor>(&self, writter: &mut W) -> Result<()> {
println!();
self.print(writter)
}
fn print(&self, writter: &mut dyn WriteColor) -> Result<()>;
}
impl Print for &str {
fn print<W: WriteWithColor>(&self, writter: &mut W) -> Result<()> {
write!(writter, "{}", self).context(format!(r#"cannot print string "{}""#, self))
fn print(&self, writter: &mut dyn WriteColor) -> Result<()> {
write!(writter, "{}", self).with_context(|| {
error!(r#"cannot write string to writter: "{}""#, self);
"cannot write string to writter"
})
}
}
impl Print for String {
fn print<W: WriteWithColor>(&self, writter: &mut W) -> Result<()> {
fn print(&self, writter: &mut dyn WriteColor) -> Result<()> {
self.as_str().print(writter)
}
}
+15
View File
@@ -0,0 +1,15 @@
use anyhow::Result;
use std::io;
use termcolor::{self, StandardStream};
pub trait WriteColor: io::Write + termcolor::WriteColor {}
impl WriteColor for StandardStream {}
pub trait PrintTable {
fn print_table(&self, writter: &mut dyn WriteColor, opts: PrintTableOpts) -> Result<()>;
}
pub struct PrintTableOpts {
pub max_width: Option<usize>,
}
+78
View File
@@ -0,0 +1,78 @@
use anyhow::{Context, Error, Result};
use atty::Stream;
use serde::Serialize;
use std::{convert::TryFrom, fmt::Debug};
use termcolor::{ColorChoice, StandardStream};
use crate::output::{OutputFmt, OutputJson, Print, PrintTable, PrintTableOpts, WriteColor};
pub trait PrinterService {
fn print<T: Debug + Print + Serialize>(&mut self, data: T) -> Result<()>;
fn print_table<T: Debug + PrintTable + Serialize>(
&mut self,
data: T,
opts: PrintTableOpts,
) -> Result<()>;
fn is_json(&self) -> bool;
}
pub struct StdoutPrinter {
pub writter: Box<dyn WriteColor>,
pub fmt: OutputFmt,
}
impl PrinterService for StdoutPrinter {
fn print<T: Debug + Print + Serialize>(&mut self, data: T) -> Result<()> {
match self.fmt {
OutputFmt::Plain => data.print(self.writter.as_mut()),
OutputFmt::Json => serde_json::to_writer(self.writter.as_mut(), &OutputJson::new(data))
.context("cannot write JSON to writter"),
}
}
fn print_table<T: Debug + PrintTable + Serialize>(
&mut self,
data: T,
opts: PrintTableOpts,
) -> Result<()> {
match self.fmt {
OutputFmt::Plain => data.print_table(self.writter.as_mut(), opts),
OutputFmt::Json => serde_json::to_writer(self.writter.as_mut(), &OutputJson::new(data))
.context("cannot write JSON to writter"),
}
}
fn is_json(&self) -> bool {
self.fmt == OutputFmt::Json
}
}
impl From<OutputFmt> for StdoutPrinter {
fn from(fmt: OutputFmt) -> Self {
let writter = StandardStream::stdout(if atty::isnt(Stream::Stdin) {
// Colors should be deactivated if the terminal is not a tty.
ColorChoice::Never
} else {
// Otherwise let's `termcolor` decide by inspecting the environment. From the [doc]:
// - If `NO_COLOR` is set to any value, then colors will be suppressed.
// - If `TERM` is set to dumb, then colors will be suppressed.
// - In non-Windows environments, if `TERM` is not set, then colors will be suppressed.
//
// [doc]: https://github.com/BurntSushi/termcolor#automatic-color-selection
ColorChoice::Auto
});
let writter = Box::new(writter);
Self { writter, fmt }
}
}
impl TryFrom<Option<&str>> for StdoutPrinter {
type Error = Error;
fn try_from(fmt: Option<&str>) -> Result<Self> {
Ok(Self {
fmt: OutputFmt::try_from(fmt)?,
..Self::from(OutputFmt::Plain)
})
}
}