Skip to content

Commit

Permalink
add option to choose color manually #407
Browse files Browse the repository at this point in the history
  • Loading branch information
pleshevskiy authored and soywod committed Dec 17, 2022
1 parent dcf942c commit 56513e4
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 46 deletions.
8 changes: 3 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use url::Url;
use himalaya::{
account, compl,
config::{self, DeserializedConfig},
email, flag, folder, man,
output::{self, OutputFmt},
email, flag, folder, man, output,
printer::StdoutPrinter,
tpl,
};
Expand Down Expand Up @@ -51,7 +50,7 @@ fn main() -> Result<()> {
let (account_config, backend_config) = config.to_configs(None)?;
let mut backend = BackendBuilder::build(&account_config, &backend_config)?;
let mut sender = SenderBuilder::build(&account_config)?;
let mut printer = StdoutPrinter::from_fmt(OutputFmt::Plain);
let mut printer = StdoutPrinter::default();

return email::handlers::mailto(
&account_config,
Expand Down Expand Up @@ -108,8 +107,7 @@ fn main() -> Result<()> {
// inits services
let mut backend = BackendBuilder::build(&account_config, &backend_config)?;
let mut sender = SenderBuilder::build(&account_config)?;
let mut printer =
StdoutPrinter::from_opt_str(m.get_one::<String>("output").map(String::as_str))?;
let mut printer = StdoutPrinter::try_from(&m)?;

// checks account commands
match account::args::matches(&m)? {
Expand Down
30 changes: 29 additions & 1 deletion src/output/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
use clap::Arg;

pub(crate) const ARG_OUTPUT: &str = "output";
pub(crate) const ARG_COLOR: &str = "color";

/// Output arguments.
pub fn args() -> Vec<Arg> {
vec![
Arg::new("output")
Arg::new(ARG_OUTPUT)
.help("Defines the output format")
.long("output")
.short('o')
Expand All @@ -22,5 +25,30 @@ pub fn args() -> Vec<Arg> {
.value_name("LEVEL")
.value_parser(["error", "warn", "info", "debug", "trace"])
.default_value("info"),
Arg::new(ARG_COLOR)
.help("Controls when to use colors.")
.long_help(
"
This flag controls when to use colors. The default setting is 'auto', which
means himalaya will try to guess when to use colors. For example, if himalaya is
printing to a terminal, then it will use colors, but if it is redirected to a
file or a pipe, then it will suppress color output. himalaya will suppress color
output in some other circumstances as well. For example, if the TERM
environment variable is not set or set to 'dumb', then himalaya will not use
colors.
The possible values for this flag are:
never Colors will never be used.
auto The default. himalaya tries to be smart.
always Colors will always be used regardless of where output is sent.
ansi Like 'always', but emits ANSI escapes (even in a Windows console).
",
)
.long("color")
.short('C')
.value_parser(["never", "auto", "always", "ansi"])
.default_value("auto")
.value_name("WHEN"),
]
}
102 changes: 87 additions & 15 deletions src/output/output.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
use anyhow::{anyhow, Error, Result};
use std::{convert::TryFrom, fmt};
use atty::Stream;
use serde::Serialize;
use std::{fmt, str::FromStr};
use termcolor::ColorChoice;

/// Represents the available output formats.
#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, Eq, 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 Default for OutputFmt {
fn default() -> Self {
Self::Plain
}
}

impl TryFrom<Option<&str>> for OutputFmt {
type Error = Error;
impl FromStr for OutputFmt {
type Err = Error;

fn try_from(fmt: Option<&str>) -> Result<Self, Self::Error> {
fn from_str(fmt: &str) -> Result<Self, Self::Err> {
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)),
fmt if fmt.eq_ignore_ascii_case("json") => Ok(Self::Json),
fmt if fmt.eq_ignore_ascii_case("plain") => Ok(Self::Plain),
unknown => Err(anyhow!("cannot parse output format {}", unknown)),
}
}
}
Expand All @@ -39,3 +38,76 @@ impl fmt::Display for OutputFmt {
write!(f, "{}", fmt)
}
}

/// Defines a struct-wrapper to provide a JSON output.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct OutputJson<T: Serialize> {
response: T,
}

impl<T: Serialize> OutputJson<T> {
pub fn new(response: T) -> Self {
Self { response }
}
}

/// Represent the available color configs.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ColorFmt {
Never,
Always,
Ansi,
Auto,
}

impl Default for ColorFmt {
fn default() -> Self {
Self::Auto
}
}

impl FromStr for ColorFmt {
type Err = Error;

fn from_str(fmt: &str) -> Result<Self, Self::Err> {
match fmt {
fmt if fmt.eq_ignore_ascii_case("never") => Ok(Self::Never),
fmt if fmt.eq_ignore_ascii_case("always") => Ok(Self::Always),
fmt if fmt.eq_ignore_ascii_case("ansi") => Ok(Self::Ansi),
fmt if fmt.eq_ignore_ascii_case("auto") => Ok(Self::Auto),
unknown => Err(anyhow!("cannot parse color format {}", unknown)),
}
}
}

impl From<ColorFmt> for ColorChoice {
fn from(fmt: ColorFmt) -> Self {
match fmt {
ColorFmt::Never => Self::Never,
ColorFmt::Always => Self::Always,
ColorFmt::Ansi => Self::AlwaysAnsi,
ColorFmt::Auto => {
if atty::is(Stream::Stdout) {
// 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
Self::Auto
} else {
// Colors should be deactivated if the terminal is
// not a tty.
Self::Never
}
}
}
}
}
65 changes: 40 additions & 25 deletions src/printer/printer.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use anyhow::{Context, Result};
use atty::Stream;
use anyhow::{Context, Error, Result};
use clap::ArgMatches;
use std::fmt::{self, Debug};
use termcolor::{ColorChoice, StandardStream};
use termcolor::StandardStream;

use crate::{
output::OutputFmt,
output::{args, ColorFmt, OutputFmt},
printer::{Print, PrintTable, PrintTableOpts, WriteColor},
};

Expand All @@ -24,29 +24,18 @@ pub struct StdoutPrinter {
pub fmt: OutputFmt,
}

impl StdoutPrinter {
pub fn from_fmt(fmt: OutputFmt) -> Self {
let writer = 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 writer = Box::new(writer);
Self { writer, fmt }
impl Default for StdoutPrinter {
fn default() -> Self {
let fmt = OutputFmt::default();
let writer = Box::new(StandardStream::stdout(ColorFmt::default().into()));
Self { fmt, writer }
}
}

pub fn from_opt_str(s: Option<&str>) -> Result<Self> {
Ok(Self {
fmt: OutputFmt::try_from(s)?,
..Self::from_fmt(OutputFmt::Plain)
})
impl StdoutPrinter {
pub fn new(fmt: OutputFmt, color: ColorFmt) -> Self {
let writer = Box::new(StandardStream::stdout(color.into()));
Self { fmt, writer }
}
}

Expand Down Expand Up @@ -86,3 +75,29 @@ impl Printer for StdoutPrinter {
self.fmt == OutputFmt::Json
}
}

impl From<OutputFmt> for StdoutPrinter {
fn from(fmt: OutputFmt) -> Self {
Self::new(fmt, ColorFmt::Auto)
}
}

impl TryFrom<&ArgMatches> for StdoutPrinter {
type Error = Error;

fn try_from(m: &ArgMatches) -> Result<Self, Self::Error> {
let fmt: OutputFmt = m
.get_one::<String>(args::ARG_OUTPUT)
.map(String::as_str)
.unwrap()
.parse()?;

let color: ColorFmt = m
.get_one::<String>(args::ARG_COLOR)
.map(String::as_str)
.unwrap()
.parse()?;

Ok(Self::new(fmt, color))
}
}

0 comments on commit 56513e4

Please sign in to comment.