Use ColorDisplay trait to print objects to the terminal (#926)

This commit is contained in:
Casey Rodarmor 2021-07-28 18:06:57 -07:00 committed by GitHub
parent 1f20ca6481
commit 27cf2b96df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 107 additions and 98 deletions

View File

@ -30,14 +30,6 @@ impl Color {
} }
} }
pub(crate) fn fmt(fmt: &Formatter) -> Self {
if fmt.alternate() {
Self::always()
} else {
Self::never()
}
}
pub(crate) fn auto() -> Self { pub(crate) fn auto() -> Self {
Self { Self {
use_color: UseColor::Auto, use_color: UseColor::Auto,

20
src/color_display.rs Normal file
View File

@ -0,0 +1,20 @@
use crate::common::*;
pub(crate) trait ColorDisplay {
fn color_display<'a>(&'a self, color: Color) -> Wrapper<'a>
where
Self: Sized,
{
Wrapper(self, color)
}
fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result;
}
pub(crate) struct Wrapper<'a>(&'a dyn ColorDisplay, Color);
impl<'a> Display for Wrapper<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.0.fmt(f, self.1)
}
}

View File

@ -38,8 +38,8 @@ pub(crate) use crate::{load_dotenv::load_dotenv, output::output, unindent::unind
// traits // traits
pub(crate) use crate::{ pub(crate) use crate::{
command_ext::CommandExt, keyed::Keyed, ordinal::Ordinal, platform_interface::PlatformInterface, color_display::ColorDisplay, command_ext::CommandExt, keyed::Keyed, ordinal::Ordinal,
range_ext::RangeExt, platform_interface::PlatformInterface, range_ext::RangeExt,
}; };
// structs and enums // structs and enums

View File

@ -160,30 +160,6 @@ impl<'src> Error<'src> {
message: message.into(), message: message.into(),
} }
} }
pub(crate) fn write(&self, w: &mut dyn Write, color: Color) -> io::Result<()> {
let color = color.stderr();
if color.active() {
writeln!(
w,
"{}: {}{:#}{}",
color.error().paint("error"),
color.message().prefix(),
self,
color.message().suffix()
)?;
} else {
writeln!(w, "error: {}", self)?;
}
if let Some(token) = self.context() {
token.write_context(w, color.error())?;
writeln!(w)?;
}
Ok(())
}
} }
impl<'src> From<CompileError<'src>> for Error<'src> { impl<'src> From<CompileError<'src>> for Error<'src> {
@ -210,10 +186,17 @@ impl<'src> From<SearchError> for Error<'src> {
} }
} }
impl<'src> Display for Error<'src> { impl<'src> ColorDisplay for Error<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result {
use Error::*; use Error::*;
write!(
f,
"{}: {}",
color.error().paint("error"),
color.message().prefix()
)?;
match self { match self {
ArgumentCountMismatch { ArgumentCountMismatch {
recipe, recipe,
@ -254,7 +237,7 @@ impl<'src> Display for Error<'src> {
} }
write!(f, "\nusage:\n just {}", recipe)?; write!(f, "\nusage:\n just {}", recipe)?;
for param in parameters { for param in parameters {
write!(f, " {}", param)?; write!(f, " {}", param.color_display(color))?;
} }
}, },
Backtick { output_error, .. } => match output_error { Backtick { output_error, .. } => match output_error {
@ -619,6 +602,13 @@ impl<'src> Display for Error<'src> {
}, },
} }
write!(f, "{}", color.message().suffix())?;
if let Some(token) = self.context() {
writeln!(f)?;
write!(f, "{}", token.color_display(color.error()))?;
}
Ok(()) Ok(())
} }
} }

View File

@ -21,9 +21,13 @@ impl InterruptHandler {
match INSTANCE.lock() { match INSTANCE.lock() {
Ok(guard) => guard, Ok(guard) => guard,
Err(poison_error) => { Err(poison_error) => {
eprintln!("{}", Error::Internal { eprintln!(
"{}",
Error::Internal {
message: format!("interrupt handler mutex poisoned: {}", poison_error), message: format!("interrupt handler mutex poisoned: {}", poison_error),
}); }
.color_display(Color::auto().stderr())
);
std::process::exit(EXIT_FAILURE); std::process::exit(EXIT_FAILURE);
}, },
} }
@ -58,9 +62,14 @@ impl InterruptHandler {
pub(crate) fn unblock(&mut self) { pub(crate) fn unblock(&mut self) {
if self.blocks == 0 { if self.blocks == 0 {
if self.verbosity.loud() { if self.verbosity.loud() {
eprintln!("{}", Error::Internal { eprintln!(
message: "attempted to unblock interrupt handler, but handler was not blocked".to_owned(), "{}",
}); Error::Internal {
message: "attempted to unblock interrupt handler, but handler was not blocked"
.to_owned(),
}
.color_display(Color::auto().stderr())
);
} }
std::process::exit(EXIT_FAILURE); std::process::exit(EXIT_FAILURE);
} }

View File

@ -16,7 +16,7 @@ impl<'src> Display for Item<'src> {
Item::Alias(alias) => write!(f, "{}", alias), Item::Alias(alias) => write!(f, "{}", alias),
Item::Assignment(assignment) => write!(f, "{}", assignment), Item::Assignment(assignment) => write!(f, "{}", assignment),
Item::Comment(comment) => write!(f, "{}", comment), Item::Comment(comment) => write!(f, "{}", comment),
Item::Recipe(recipe) => write!(f, "{}", recipe), Item::Recipe(recipe) => write!(f, "{}", recipe.color_display(Color::never())),
Item::Set(set) => write!(f, "{}", set), Item::Set(set) => write!(f, "{}", set),
} }
} }

View File

@ -375,8 +375,8 @@ impl<'src> Justfile<'src> {
} }
} }
impl<'src> Display for Justfile<'src> { impl<'src> ColorDisplay for Justfile<'src> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> {
let mut items = self.recipes.len() + self.assignments.len() + self.aliases.len(); let mut items = self.recipes.len() + self.assignments.len() + self.aliases.len();
for (name, assignment) in &self.assignments { for (name, assignment) in &self.assignments {
if assignment.export { if assignment.export {
@ -396,7 +396,7 @@ impl<'src> Display for Justfile<'src> {
} }
} }
for recipe in self.recipes.values() { for recipe in self.recipes.values() {
write!(f, "{}", recipe)?; write!(f, "{}", recipe.color_display(color))?;
items -= 1; items -= 1;
if items != 0 { if items != 0 {
write!(f, "\n\n")?; write!(f, "\n\n")?;
@ -683,11 +683,11 @@ mod tests {
fn test(input: &str, expected: &str) { fn test(input: &str, expected: &str) {
let justfile = compile(input); let justfile = compile(input);
let actual = format!("{:#}", justfile); let actual = format!("{}", justfile.color_display(Color::never()));
assert_eq!(actual, expected); assert_eq!(actual, expected);
println!("Re-parsing..."); println!("Re-parsing...");
let reparsed = compile(&actual); let reparsed = compile(&actual);
let redumped = format!("{:#}", reparsed); let redumped = format!("{}", reparsed.color_display(Color::never()));
assert_eq!(redumped, actual); assert_eq!(redumped, actual);
} }

View File

@ -2298,17 +2298,13 @@ mod tests {
} if message == "Lexer presumed character `-`" } if message == "Lexer presumed character `-`"
); );
let mut cursor = Cursor::new(Vec::new());
Error::Compile { compile_error }
.write(&mut cursor, Color::never())
.unwrap();
assert_eq!( assert_eq!(
str::from_utf8(&cursor.into_inner()).unwrap(), Error::Compile { compile_error }
.color_display(Color::never())
.to_string(),
"error: Internal error, this may indicate a bug in just: \ "error: Internal error, this may indicate a bug in just: \
Lexer presumed character `-`\nconsider filing an issue: \ Lexer presumed character `-`\nconsider filing an issue: \
https://github.com/casey/just/issues/new\n |\n1 | !\n | ^\n" https://github.com/casey/just/issues/new\n |\n1 | !\n | ^"
); );
} }
} }

View File

@ -65,6 +65,7 @@ mod assignment_resolver;
mod ast; mod ast;
mod binding; mod binding;
mod color; mod color;
mod color_display;
mod command_ext; mod command_ext;
mod common; mod common;
mod compile_error; mod compile_error;

View File

@ -23,9 +23,10 @@ pub(crate) fn load_dotenv(
.map(|val| val.as_os_str().to_str() == Some("1")) .map(|val| val.as_os_str().to_str() == Some("1"))
.unwrap_or(false) .unwrap_or(false)
{ {
Warning::DotenvLoad eprintln!(
.write(&mut io::stderr(), config.color.stderr()) "{}",
.ok(); Warning::DotenvLoad.color_display(config.color.stderr())
);
} }
let iter = dotenv::from_path_iter(&path)?; let iter = dotenv::from_path_iter(&path)?;

View File

@ -13,9 +13,8 @@ pub(crate) struct Parameter<'src> {
pub(crate) export: bool, pub(crate) export: bool,
} }
impl<'src> Display for Parameter<'src> { impl<'src> ColorDisplay for Parameter<'src> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> {
let color = Color::fmt(f);
if self.export { if self.export {
write!(f, "$")?; write!(f, "$")?;
} }

View File

@ -309,8 +309,8 @@ impl<'src, D> Keyed<'src> for Recipe<'src, D> {
} }
} }
impl<'src, D: Display> Display for Recipe<'src, D> { impl<'src, D: Display> ColorDisplay for Recipe<'src, D> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> {
if let Some(doc) = self.doc { if let Some(doc) = self.doc {
writeln!(f, "# {}", doc)?; writeln!(f, "# {}", doc)?;
} }
@ -322,7 +322,7 @@ impl<'src, D: Display> Display for Recipe<'src, D> {
} }
for parameter in &self.parameters { for parameter in &self.parameters {
write!(f, " {}", parameter)?; write!(f, " {}", parameter.color_display(color))?;
} }
write!(f, ":")?; write!(f, ":")?;

View File

@ -30,7 +30,7 @@ pub fn run() -> Result<(), i32> {
}) })
.map_err(|error| { .map_err(|error| {
if !verbosity.quiet() { if !verbosity.quiet() {
error.write(&mut io::stderr(), color).ok(); eprintln!("{}", error.color_display(color));
} }
error.code().unwrap_or(EXIT_FAILURE) error.code().unwrap_or(EXIT_FAILURE)
}) })

View File

@ -62,24 +62,24 @@ impl Subcommand {
if config.verbosity.loud() { if config.verbosity.loud() {
for warning in &justfile.warnings { for warning in &justfile.warnings {
warning.write(&mut io::stderr(), config.color.stderr()).ok(); eprintln!("{}", warning.color_display(config.color.stderr()));
} }
} }
match self { match self {
Choose { overrides, chooser } => Choose { overrides, chooser } =>
Self::choose(&config, justfile, &search, overrides, chooser.as_deref())?, Self::choose(config, justfile, &search, overrides, chooser.as_deref())?,
Command { overrides, .. } => justfile.run(&config, &search, overrides, &[])?, Command { overrides, .. } => justfile.run(config, &search, overrides, &[])?,
Dump => Self::dump(ast), Dump => Self::dump(ast),
Evaluate { overrides, .. } => justfile.run(&config, &search, overrides, &[])?, Evaluate { overrides, .. } => justfile.run(config, &search, overrides, &[])?,
Format => Self::format(&config, ast, &search)?, Format => Self::format(config, ast, &search)?,
List => Self::list(&config, justfile), List => Self::list(config, justfile),
Run { Run {
arguments, arguments,
overrides, overrides,
} => justfile.run(&config, &search, overrides, arguments)?, } => justfile.run(config, &search, overrides, arguments)?,
Show { ref name } => Self::show(&name, justfile)?, Show { ref name } => Self::show(config, &name, justfile)?,
Summary => Self::summary(&config, justfile), Summary => Self::summary(config, justfile),
Variables => Self::variables(justfile), Variables => Self::variables(justfile),
Completions { .. } | Edit | Init => unreachable!(), Completions { .. } | Edit | Init => unreachable!(),
} }
@ -308,7 +308,9 @@ impl Subcommand {
let mut line_width = UnicodeWidthStr::width(*name); let mut line_width = UnicodeWidthStr::width(*name);
for parameter in &recipe.parameters { for parameter in &recipe.parameters {
line_width += UnicodeWidthStr::width(format!(" {}", parameter).as_str()); line_width += UnicodeWidthStr::width(
format!(" {}", parameter.color_display(Color::never())).as_str(),
);
} }
if line_width <= 30 { if line_width <= 30 {
@ -331,11 +333,7 @@ impl Subcommand {
{ {
print!("{}{}", config.list_prefix, name); print!("{}{}", config.list_prefix, name);
for parameter in &recipe.parameters { for parameter in &recipe.parameters {
if config.color.stdout().active() { print!(" {}", parameter.color_display(config.color.stdout()));
print!(" {:#}", parameter);
} else {
print!(" {}", parameter);
}
} }
// Declaring this outside of the nested loops will probably be more efficient, // Declaring this outside of the nested loops will probably be more efficient,
@ -365,14 +363,14 @@ impl Subcommand {
} }
} }
fn show<'src>(name: &str, justfile: Justfile<'src>) -> Result<(), Error<'src>> { fn show<'src>(config: &Config, name: &str, justfile: Justfile<'src>) -> Result<(), Error<'src>> {
if let Some(alias) = justfile.get_alias(name) { if let Some(alias) = justfile.get_alias(name) {
let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap(); let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap();
println!("{}", alias); println!("{}", alias);
println!("{}", recipe); println!("{}", recipe.color_display(config.color.stdout()));
Ok(()) Ok(())
} else if let Some(recipe) = justfile.get_recipe(name) { } else if let Some(recipe) = justfile.get_recipe(name) {
println!("{}", recipe); println!("{}", recipe.color_display(config.color.stdout()));
Ok(()) Ok(())
} else { } else {
Err(Error::UnknownRecipes { Err(Error::UnknownRecipes {

View File

@ -18,8 +18,10 @@ impl<'src> Token<'src> {
pub(crate) fn error(&self, kind: CompileErrorKind<'src>) -> CompileError<'src> { pub(crate) fn error(&self, kind: CompileErrorKind<'src>) -> CompileError<'src> {
CompileError { token: *self, kind } CompileError { token: *self, kind }
} }
}
pub(crate) fn write_context(&self, w: &mut dyn Write, color: Color) -> io::Result<()> { impl<'src> ColorDisplay for Token<'src> {
fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result {
let width = if self.length == 0 { 1 } else { self.length }; let width = if self.length == 0 { 1 } else { self.length };
let line_number = self.line.ordinal(); let line_number = self.line.ordinal();
@ -50,11 +52,11 @@ impl<'src> Token<'src> {
i += c.len_utf8(); i += c.len_utf8();
} }
let line_number_width = line_number.to_string().len(); let line_number_width = line_number.to_string().len();
writeln!(w, "{0:1$} |", "", line_number_width)?; writeln!(f, "{0:1$} |", "", line_number_width)?;
writeln!(w, "{} | {}", line_number, space_line)?; writeln!(f, "{} | {}", line_number, space_line)?;
write!(w, "{0:1$} |", "", line_number_width)?; write!(f, "{0:1$} |", "", line_number_width)?;
write!( write!(
w, f,
" {0:1$}{2}{3:^<4$}{5}", " {0:1$}{2}{3:^<4$}{5}",
"", "",
space_column, space_column,
@ -67,7 +69,7 @@ impl<'src> Token<'src> {
None => None =>
if self.offset != self.src.len() { if self.offset != self.src.len() {
write!( write!(
w, f,
"internal error: Error has invalid line number: {}", "internal error: Error has invalid line number: {}",
line_number line_number
)?; )?;

View File

@ -2,8 +2,6 @@ use crate::common::*;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub(crate) enum Warning { pub(crate) enum Warning {
// Remove this on 2021-07-01.
#[allow(dead_code)]
DotenvLoad, DotenvLoad,
} }
@ -13,17 +11,19 @@ impl Warning {
Self::DotenvLoad => None, Self::DotenvLoad => None,
} }
} }
}
pub(crate) fn write(&self, w: &mut dyn Write, color: Color) -> io::Result<()> { impl ColorDisplay for Warning {
fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result {
let warning = color.warning(); let warning = color.warning();
let message = color.message(); let message = color.message();
write!(w, "{} {}", warning.paint("warning:"), message.prefix())?; write!(f, "{} {}", warning.paint("warning:"), message.prefix())?;
match self { match self {
Self::DotenvLoad => { Self::DotenvLoad => {
#[rustfmt::skip] #[rustfmt::skip]
write!(w, "\ write!(f, "\
A `.env` file was found and loaded, but this behavior will change in the future. A `.env` file was found and loaded, but this behavior will change in the future.
To silence this warning and continue loading `.env` files, add: To silence this warning and continue loading `.env` files, add:
@ -44,10 +44,11 @@ See https://github.com/casey/just/issues/469 for more details.")?;
}, },
} }
writeln!(w, "{}", message.suffix())?; write!(f, "{}", message.suffix())?;
if let Some(token) = self.context() { if let Some(token) = self.context() {
token.write_context(w, warning)?; writeln!(f)?;
write!(f, "{}", token.color_display(color))?;
} }
Ok(()) Ok(())