mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-17 21:37:55 +08:00
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:
+8
-2
@@ -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::*;
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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,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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user