use std::io::{Result, Write};
use nu_ansi_term::{AnsiString, AnsiStrings, Color, Style};
use public_api::{PublicItem, diff::PublicApiDiff, tokens::Token};
use crate::Args;
pub struct Plain;
impl Plain {
pub fn print_items<'a>(
w: &mut dyn Write,
args: &Args,
items: impl Iterator<Item = &'a PublicItem>,
) -> Result<()> {
for item in items {
print_item(args, w, item)?;
}
Ok(())
}
pub fn print_diff(w: &mut dyn Write, args: &Args, diff: &PublicApiDiff) -> Result<()> {
let use_color = color_active(args.color);
print_items_with_header(
w,
"Removed items from the public API",
&diff.removed,
|w, item| {
if use_color {
writeln!(w, "-{}", color_item(item))
} else {
writeln!(w, "-{item}")
}
},
)?;
print_items_with_header(
w,
"Changed items in the public API",
&diff.changed,
|w, changed_item| {
if use_color {
let old_tokens: Vec<&Token> = changed_item.old.tokens().collect();
let new_tokens: Vec<&Token> = changed_item.new.tokens().collect();
let diff_slice = diff::slice(old_tokens.as_slice(), new_tokens.as_slice());
writeln!(
w,
"-{}\n+{}",
color_item_with_diff(&diff_slice, true),
color_item_with_diff(&diff_slice, false),
)
} else {
writeln!(w, "-{}\n+{}", changed_item.old, changed_item.new)
}
},
)?;
print_items_with_header(
w,
"Added items to the public API",
&diff.added,
|w, item| {
if use_color {
writeln!(w, "+{}", color_item(item))
} else {
writeln!(w, "+{item}")
}
},
)?;
Ok(())
}
}
fn print_item(args: &Args, w: &mut dyn Write, item: &PublicItem) -> Result<()> {
if color_active(args.color) {
writeln!(w, "{}", color_item(item))
} else {
writeln!(w, "{item}")
}
}
fn color_active(color: Option<Option<crate::arg_types::Color>>) -> bool {
match color {
Some(Some(color)) => color,
Some(None) => crate::arg_types::Color::Always,
None => crate::arg_types::Color::Auto,
}
.active()
}
fn color_item(item: &public_api::PublicItem) -> String {
color_token_stream(item.tokens(), None)
}
fn color_token_stream<'a>(tokens: impl Iterator<Item = &'a Token>, bg: Option<Color>) -> String {
let styled = tokens.map(|t| color_item_token(t, bg)).collect::<Vec<_>>();
AnsiStrings(&styled).to_string()
}
fn color_item_token(token: &Token, bg: Option<Color>) -> AnsiString<'_> {
let style = |color: Style, text: &str| {
bg.map_or_else(
|| color.paint(text.to_string()),
|bg| color.on(bg).paint(text.to_string()),
)
};
match token {
Token::Symbol(text) => style(Style::default(), text),
Token::Qualifier(text) => style(Color::Blue.into(), text),
Token::Kind(text) => style(Color::Blue.into(), text),
Token::Whitespace => style(Style::default(), " "),
Token::Identifier(text) => style(Color::Cyan.into(), text),
Token::Annotation(text) => style(Style::default(), text),
Token::Self_(text) => style(Color::Blue.into(), text),
Token::Function(text) => style(Color::Yellow.into(), text),
Token::Lifetime(text) => style(Color::Blue.into(), text),
Token::Keyword(text) => style(Color::Blue.into(), text),
Token::Generic(text) => style(Color::Green.into(), text),
Token::Primitive(text) => style(Color::Green.into(), text),
Token::Type(text) => style(Color::Green.into(), text),
}
}
fn color_item_with_diff(diff_slice: &[diff::Result<&&Token>], is_old_item: bool) -> String {
let styled_strings = diff_slice
.iter()
.filter_map(|diff_result| match *diff_result {
diff::Result::Left(&token) => is_old_item.then(|| {
Color::Fixed(9)
.on(Color::Fixed(52))
.bold()
.paint(token.text())
}),
diff::Result::Both(&token, _) => Some(color_item_token(token, None)),
diff::Result::Right(&token) => (!is_old_item).then(|| {
Color::Fixed(10)
.on(Color::Fixed(22))
.bold()
.paint(token.text())
}),
})
.collect::<Vec<_>>();
AnsiStrings(&styled_strings).to_string()
}
pub fn print_items_with_header<T>(
w: &mut dyn Write,
header: &str,
items: &[T],
print_fn: impl Fn(&mut dyn Write, &T) -> Result<()>,
) -> Result<()> {
writeln!(w, "{header}")?;
writeln!(w, "{}", "=".repeat(header.len()))?;
if items.is_empty() {
writeln!(w, "(none)")?;
} else {
for item in items {
print_fn(w, item)?;
}
}
writeln!(w)
}