use alloc::borrow::Cow;
use core::{
fmt::{self, Debug, Formatter},
sync::atomic::{AtomicBool, Ordering},
};
use std::env;
use once_cell::sync::Lazy;
use crate::term::{wants_emoji, Term};
#[cfg(feature = "ansi-parsing")]
use crate::ansi::AnsiCodeIterator;
fn default_colors_enabled(out: &Term) -> bool {
(out.features().colors_supported()
&& &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0")
|| &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0"
}
static STDOUT_COLORS: Lazy<AtomicBool> =
Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stdout())));
static STDERR_COLORS: Lazy<AtomicBool> =
Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stderr())));
#[inline]
pub fn colors_enabled() -> bool {
STDOUT_COLORS.load(Ordering::Relaxed)
}
#[inline]
pub fn set_colors_enabled(val: bool) {
STDOUT_COLORS.store(val, Ordering::Relaxed)
}
#[inline]
pub fn colors_enabled_stderr() -> bool {
STDERR_COLORS.load(Ordering::Relaxed)
}
#[inline]
pub fn set_colors_enabled_stderr(val: bool) {
STDERR_COLORS.store(val, Ordering::Relaxed)
}
pub fn measure_text_width(s: &str) -> usize {
#[cfg(feature = "ansi-parsing")]
{
AnsiCodeIterator::new(s)
.filter_map(|(s, is_ansi)| match is_ansi {
false => Some(str_width(s)),
true => None,
})
.sum()
}
#[cfg(not(feature = "ansi-parsing"))]
{
str_width(s)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Color256(u8),
}
impl Color {
#[inline]
fn ansi_num(self) -> usize {
match self {
Color::Black => 0,
Color::Red => 1,
Color::Green => 2,
Color::Yellow => 3,
Color::Blue => 4,
Color::Magenta => 5,
Color::Cyan => 6,
Color::White => 7,
Color::Color256(x) => x as usize,
}
}
#[inline]
fn is_color256(self) -> bool {
#[allow(clippy::match_like_matches_macro)]
match self {
Color::Color256(_) => true,
_ => false,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
#[repr(u16)]
pub enum Attribute {
Bold = 0,
Dim = 1,
Italic = 2,
Underlined = 3,
Blink = 4,
BlinkFast = 5,
Reverse = 6,
Hidden = 7,
StrikeThrough = 8,
}
impl Attribute {
const MAP: [Attribute; 9] = [
Attribute::Bold,
Attribute::Dim,
Attribute::Italic,
Attribute::Underlined,
Attribute::Blink,
Attribute::BlinkFast,
Attribute::Reverse,
Attribute::Hidden,
Attribute::StrikeThrough,
];
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct Attributes(u16);
impl Attributes {
#[inline]
const fn new() -> Self {
Self(0)
}
#[inline]
#[must_use]
const fn insert(mut self, attr: Attribute) -> Self {
let bit = attr as u16;
self.0 |= 1 << bit;
self
}
#[inline]
const fn bits(self) -> BitsIter {
BitsIter(self.0)
}
#[inline]
fn attrs(self) -> impl Iterator<Item = Attribute> {
self.bits().map(|bit| Attribute::MAP[bit as usize])
}
#[inline]
fn is_empty(self) -> bool {
self.0 == 0
}
}
impl fmt::Display for Attributes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for ansi in self.bits().map(|bit| bit + 1) {
write!(f, "\x1b[{ansi}m")?;
}
Ok(())
}
}
struct BitsIter(u16);
impl Iterator for BitsIter {
type Item = u16;
fn next(&mut self) -> Option<Self::Item> {
if self.0 == 0 {
return None;
}
let bit = self.0.trailing_zeros();
self.0 ^= (1 << bit) as u16;
Some(bit as u16)
}
}
impl Debug for Attributes {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.attrs()).finish()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Alignment {
Left,
Center,
Right,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Style {
fg: Option<Color>,
bg: Option<Color>,
fg_bright: bool,
bg_bright: bool,
attrs: Attributes,
force: Option<bool>,
for_stderr: bool,
}
impl Default for Style {
fn default() -> Self {
Self::new()
}
}
impl Style {
pub const fn new() -> Self {
Self {
fg: None,
bg: None,
fg_bright: false,
bg_bright: false,
attrs: Attributes::new(),
force: None,
for_stderr: false,
}
}
pub fn from_dotted_str(s: &str) -> Self {
let mut rv = Self::new();
for part in s.split('.') {
rv = match part {
"black" => rv.black(),
"red" => rv.red(),
"green" => rv.green(),
"yellow" => rv.yellow(),
"blue" => rv.blue(),
"magenta" => rv.magenta(),
"cyan" => rv.cyan(),
"white" => rv.white(),
"bright" => rv.bright(),
"on_black" => rv.on_black(),
"on_red" => rv.on_red(),
"on_green" => rv.on_green(),
"on_yellow" => rv.on_yellow(),
"on_blue" => rv.on_blue(),
"on_magenta" => rv.on_magenta(),
"on_cyan" => rv.on_cyan(),
"on_white" => rv.on_white(),
"on_bright" => rv.on_bright(),
"bold" => rv.bold(),
"dim" => rv.dim(),
"underlined" => rv.underlined(),
"blink" => rv.blink(),
"blink_fast" => rv.blink_fast(),
"reverse" => rv.reverse(),
"hidden" => rv.hidden(),
"strikethrough" => rv.strikethrough(),
on_c if on_c.starts_with("on_") => {
if let Ok(n) = on_c[3..].parse::<u8>() {
rv.on_color256(n)
} else {
continue;
}
}
c => {
if let Ok(n) = c.parse::<u8>() {
rv.color256(n)
} else {
continue;
}
}
};
}
rv
}
pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
StyledObject {
style: self.clone(),
val,
}
}
#[inline]
pub const fn force_styling(mut self, value: bool) -> Self {
self.force = Some(value);
self
}
#[inline]
pub const fn for_stderr(mut self) -> Self {
self.for_stderr = true;
self
}
#[inline]
pub const fn for_stdout(mut self) -> Self {
self.for_stderr = false;
self
}
#[inline]
pub const fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
#[inline]
pub const fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
#[inline]
pub const fn attr(mut self, attr: Attribute) -> Self {
self.attrs = self.attrs.insert(attr);
self
}
#[inline]
pub const fn black(self) -> Self {
self.fg(Color::Black)
}
#[inline]
pub const fn red(self) -> Self {
self.fg(Color::Red)
}
#[inline]
pub const fn green(self) -> Self {
self.fg(Color::Green)
}
#[inline]
pub const fn yellow(self) -> Self {
self.fg(Color::Yellow)
}
#[inline]
pub const fn blue(self) -> Self {
self.fg(Color::Blue)
}
#[inline]
pub const fn magenta(self) -> Self {
self.fg(Color::Magenta)
}
#[inline]
pub const fn cyan(self) -> Self {
self.fg(Color::Cyan)
}
#[inline]
pub const fn white(self) -> Self {
self.fg(Color::White)
}
#[inline]
pub const fn color256(self, color: u8) -> Self {
self.fg(Color::Color256(color))
}
#[inline]
pub const fn bright(mut self) -> Self {
self.fg_bright = true;
self
}
#[inline]
pub const fn on_black(self) -> Self {
self.bg(Color::Black)
}
#[inline]
pub const fn on_red(self) -> Self {
self.bg(Color::Red)
}
#[inline]
pub const fn on_green(self) -> Self {
self.bg(Color::Green)
}
#[inline]
pub const fn on_yellow(self) -> Self {
self.bg(Color::Yellow)
}
#[inline]
pub const fn on_blue(self) -> Self {
self.bg(Color::Blue)
}
#[inline]
pub const fn on_magenta(self) -> Self {
self.bg(Color::Magenta)
}
#[inline]
pub const fn on_cyan(self) -> Self {
self.bg(Color::Cyan)
}
#[inline]
pub const fn on_white(self) -> Self {
self.bg(Color::White)
}
#[inline]
pub const fn on_color256(self, color: u8) -> Self {
self.bg(Color::Color256(color))
}
#[inline]
pub const fn on_bright(mut self) -> Self {
self.bg_bright = true;
self
}
#[inline]
pub const fn bold(self) -> Self {
self.attr(Attribute::Bold)
}
#[inline]
pub const fn dim(self) -> Self {
self.attr(Attribute::Dim)
}
#[inline]
pub const fn italic(self) -> Self {
self.attr(Attribute::Italic)
}
#[inline]
pub const fn underlined(self) -> Self {
self.attr(Attribute::Underlined)
}
#[inline]
pub const fn blink(self) -> Self {
self.attr(Attribute::Blink)
}
#[inline]
pub const fn blink_fast(self) -> Self {
self.attr(Attribute::BlinkFast)
}
#[inline]
pub const fn reverse(self) -> Self {
self.attr(Attribute::Reverse)
}
#[inline]
pub const fn hidden(self) -> Self {
self.attr(Attribute::Hidden)
}
#[inline]
pub const fn strikethrough(self) -> Self {
self.attr(Attribute::StrikeThrough)
}
}
pub fn style<D>(val: D) -> StyledObject<D> {
Style::new().apply_to(val)
}
#[derive(Clone)]
pub struct StyledObject<D> {
style: Style,
val: D,
}
impl<D> StyledObject<D> {
#[inline]
pub fn force_styling(mut self, value: bool) -> StyledObject<D> {
self.style = self.style.force_styling(value);
self
}
#[inline]
pub fn for_stderr(mut self) -> StyledObject<D> {
self.style = self.style.for_stderr();
self
}
#[inline]
pub const fn for_stdout(mut self) -> StyledObject<D> {
self.style = self.style.for_stdout();
self
}
#[inline]
pub const fn fg(mut self, color: Color) -> StyledObject<D> {
self.style = self.style.fg(color);
self
}
#[inline]
pub const fn bg(mut self, color: Color) -> StyledObject<D> {
self.style = self.style.bg(color);
self
}
#[inline]
pub const fn attr(mut self, attr: Attribute) -> StyledObject<D> {
self.style = self.style.attr(attr);
self
}
#[inline]
pub const fn black(self) -> StyledObject<D> {
self.fg(Color::Black)
}
#[inline]
pub const fn red(self) -> StyledObject<D> {
self.fg(Color::Red)
}
#[inline]
pub const fn green(self) -> StyledObject<D> {
self.fg(Color::Green)
}
#[inline]
pub const fn yellow(self) -> StyledObject<D> {
self.fg(Color::Yellow)
}
#[inline]
pub const fn blue(self) -> StyledObject<D> {
self.fg(Color::Blue)
}
#[inline]
pub const fn magenta(self) -> StyledObject<D> {
self.fg(Color::Magenta)
}
#[inline]
pub const fn cyan(self) -> StyledObject<D> {
self.fg(Color::Cyan)
}
#[inline]
pub const fn white(self) -> StyledObject<D> {
self.fg(Color::White)
}
#[inline]
pub const fn color256(self, color: u8) -> StyledObject<D> {
self.fg(Color::Color256(color))
}
#[inline]
pub const fn bright(mut self) -> StyledObject<D> {
self.style = self.style.bright();
self
}
#[inline]
pub const fn on_black(self) -> StyledObject<D> {
self.bg(Color::Black)
}
#[inline]
pub const fn on_red(self) -> StyledObject<D> {
self.bg(Color::Red)
}
#[inline]
pub const fn on_green(self) -> StyledObject<D> {
self.bg(Color::Green)
}
#[inline]
pub const fn on_yellow(self) -> StyledObject<D> {
self.bg(Color::Yellow)
}
#[inline]
pub const fn on_blue(self) -> StyledObject<D> {
self.bg(Color::Blue)
}
#[inline]
pub const fn on_magenta(self) -> StyledObject<D> {
self.bg(Color::Magenta)
}
#[inline]
pub const fn on_cyan(self) -> StyledObject<D> {
self.bg(Color::Cyan)
}
#[inline]
pub const fn on_white(self) -> StyledObject<D> {
self.bg(Color::White)
}
#[inline]
pub const fn on_color256(self, color: u8) -> StyledObject<D> {
self.bg(Color::Color256(color))
}
#[inline]
pub const fn on_bright(mut self) -> StyledObject<D> {
self.style = self.style.on_bright();
self
}
#[inline]
pub const fn bold(self) -> StyledObject<D> {
self.attr(Attribute::Bold)
}
#[inline]
pub const fn dim(self) -> StyledObject<D> {
self.attr(Attribute::Dim)
}
#[inline]
pub const fn italic(self) -> StyledObject<D> {
self.attr(Attribute::Italic)
}
#[inline]
pub const fn underlined(self) -> StyledObject<D> {
self.attr(Attribute::Underlined)
}
#[inline]
pub const fn blink(self) -> StyledObject<D> {
self.attr(Attribute::Blink)
}
#[inline]
pub const fn blink_fast(self) -> StyledObject<D> {
self.attr(Attribute::BlinkFast)
}
#[inline]
pub const fn reverse(self) -> StyledObject<D> {
self.attr(Attribute::Reverse)
}
#[inline]
pub const fn hidden(self) -> StyledObject<D> {
self.attr(Attribute::Hidden)
}
#[inline]
pub const fn strikethrough(self) -> StyledObject<D> {
self.attr(Attribute::StrikeThrough)
}
}
macro_rules! impl_fmt {
($name:ident) => {
impl<D: fmt::$name> fmt::$name for StyledObject<D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut reset = false;
if self
.style
.force
.unwrap_or_else(|| match self.style.for_stderr {
true => colors_enabled_stderr(),
false => colors_enabled(),
})
{
if let Some(fg) = self.style.fg {
if fg.is_color256() {
write!(f, "\x1b[38;5;{}m", fg.ansi_num())?;
} else if self.style.fg_bright {
write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?;
} else {
write!(f, "\x1b[{}m", fg.ansi_num() + 30)?;
}
reset = true;
}
if let Some(bg) = self.style.bg {
if bg.is_color256() {
write!(f, "\x1b[48;5;{}m", bg.ansi_num())?;
} else if self.style.bg_bright {
write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?;
} else {
write!(f, "\x1b[{}m", bg.ansi_num() + 40)?;
}
reset = true;
}
if !self.style.attrs.is_empty() {
write!(f, "{}", self.style.attrs)?;
reset = true;
}
}
fmt::$name::fmt(&self.val, f)?;
if reset {
write!(f, "\x1b[0m")?;
}
Ok(())
}
}
};
}
impl_fmt!(Binary);
impl_fmt!(Debug);
impl_fmt!(Display);
impl_fmt!(LowerExp);
impl_fmt!(LowerHex);
impl_fmt!(Octal);
impl_fmt!(Pointer);
impl_fmt!(UpperExp);
impl_fmt!(UpperHex);
#[derive(Copy, Clone)]
pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str);
impl<'a, 'b> Emoji<'a, 'b> {
pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> {
Emoji(emoji, fallback)
}
}
impl fmt::Display for Emoji<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if wants_emoji() {
write!(f, "{}", self.0)
} else {
write!(f, "{}", self.1)
}
}
}
fn str_width(s: &str) -> usize {
#[cfg(feature = "unicode-width")]
{
use unicode_width::UnicodeWidthStr;
s.width()
}
#[cfg(not(feature = "unicode-width"))]
{
s.chars().count()
}
}
#[cfg(feature = "ansi-parsing")]
pub(crate) fn char_width(c: char) -> usize {
#[cfg(feature = "unicode-width")]
{
use unicode_width::UnicodeWidthChar;
c.width().unwrap_or(0)
}
#[cfg(not(feature = "unicode-width"))]
{
let _c = c;
1
}
}
#[cfg(not(feature = "ansi-parsing"))]
pub(crate) fn char_width(_c: char) -> usize {
1
}
pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
if measure_text_width(s) <= width {
return Cow::Borrowed(s);
}
#[cfg(feature = "ansi-parsing")]
{
use core::cmp::Ordering;
let mut iter = AnsiCodeIterator::new(s);
let mut length = 0;
let mut rv = None;
while let Some(item) = iter.next() {
match item {
(s, false) => {
if rv.is_none() {
if str_width(s) + length > width.saturating_sub(str_width(tail)) {
let ts = iter.current_slice();
let mut s_byte = 0;
let mut s_width = 0;
let rest_width =
width.saturating_sub(str_width(tail)).saturating_sub(length);
for c in s.chars() {
s_byte += c.len_utf8();
s_width += char_width(c);
match s_width.cmp(&rest_width) {
Ordering::Equal => break,
Ordering::Greater => {
s_byte -= c.len_utf8();
break;
}
Ordering::Less => continue,
}
}
let idx = ts.len() - s.len() + s_byte;
let mut buf = ts[..idx].to_string();
buf.push_str(tail);
rv = Some(buf);
}
length += str_width(s);
}
}
(s, true) => {
if let Some(ref mut rv) = rv {
rv.push_str(s);
}
}
}
}
if let Some(buf) = rv {
Cow::Owned(buf)
} else {
Cow::Borrowed(s)
}
}
#[cfg(not(feature = "ansi-parsing"))]
{
Cow::Owned(format!(
"{}{}",
&s[..width.saturating_sub(tail.len())],
tail
))
}
}
pub fn pad_str<'a>(
s: &'a str,
width: usize,
align: Alignment,
truncate: Option<&str>,
) -> Cow<'a, str> {
pad_str_with(s, width, align, truncate, ' ')
}
pub fn pad_str_with<'a>(
s: &'a str,
width: usize,
align: Alignment,
truncate: Option<&str>,
pad: char,
) -> Cow<'a, str> {
let cols = measure_text_width(s);
if cols >= width {
return match truncate {
None => Cow::Borrowed(s),
Some(tail) => truncate_str(s, width, tail),
};
}
let diff = width - cols;
let (left_pad, right_pad) = match align {
Alignment::Left => (0, diff),
Alignment::Right => (diff, 0),
Alignment::Center => (diff / 2, diff - diff / 2),
};
let mut rv = String::new();
for _ in 0..left_pad {
rv.push(pad);
}
rv.push_str(s);
for _ in 0..right_pad {
rv.push(pad);
}
Cow::Owned(rv)
}
#[test]
fn test_text_width() {
let s = style("foo")
.red()
.on_black()
.bold()
.force_styling(true)
.to_string();
assert_eq!(
measure_text_width(&s),
if cfg!(feature = "ansi-parsing") {
3
} else {
21
}
);
let s = style("🐶 <3").red().force_styling(true).to_string();
assert_eq!(
measure_text_width(&s),
match (
cfg!(feature = "ansi-parsing"),
cfg!(feature = "unicode-width")
) {
(true, true) => 5, (true, false) => 4, (false, true) => 14, (false, false) => 13, }
);
}
#[test]
#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
fn test_truncate_str() {
let s = format!("foo {}", style("bar").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 5, ""),
&format!("foo {}", style("b").red().force_styling(true))
);
let s = format!("foo {}", style("bar").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 5, "!"),
&format!("foo {}", style("!").red().force_styling(true))
);
let s = format!("foo {} baz", style("bar").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 10, "..."),
&format!("foo {}...", style("bar").red().force_styling(true))
);
let s = format!("foo {}", style("バー").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 5, ""),
&format!("foo {}", style("").red().force_styling(true))
);
let s = format!("foo {}", style("バー").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 6, ""),
&format!("foo {}", style("バ").red().force_styling(true))
);
let s = format!("foo {}", style("バー").red().force_styling(true));
assert_eq!(
&truncate_str(&s, 2, "!!!"),
&format!("!!!{}", style("").red().force_styling(true))
);
}
#[test]
fn test_truncate_str_no_ansi() {
assert_eq!(&truncate_str("foo bar", 7, "!"), "foo bar");
assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
assert_eq!(&truncate_str("foo bar", 0, ""), "");
assert_eq!(&truncate_str("foo bar", 0, "!"), "!");
assert_eq!(&truncate_str("foo bar", 2, "!!!"), "!!!");
assert_eq!(&truncate_str("ab", 2, "!!!"), "ab");
}
#[test]
fn test_pad_str() {
assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo ");
assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo ");
assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo");
assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
assert_eq!(
pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
"foo..."
);
}
#[test]
fn test_pad_str_with() {
assert_eq!(
pad_str_with("foo", 7, Alignment::Center, None, '#'),
"##foo##"
);
assert_eq!(
pad_str_with("foo", 7, Alignment::Left, None, '#'),
"foo####"
);
assert_eq!(
pad_str_with("foo", 7, Alignment::Right, None, '#'),
"####foo"
);
assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
assert_eq!(
pad_str_with("foobar", 3, Alignment::Left, None, '#'),
"foobar"
);
assert_eq!(
pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
"foo"
);
assert_eq!(
pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
"foo..."
);
}
#[test]
fn test_attributes_single() {
for attr in Attribute::MAP {
let attrs = Attributes::new().insert(attr);
assert_eq!(attrs.bits().collect::<Vec<_>>(), [attr as u16]);
assert_eq!(attrs.attrs().collect::<Vec<_>>(), [attr]);
assert_eq!(format!("{attrs:?}"), format!("{{{:?}}}", attr));
}
}
#[test]
fn test_attributes_many() {
let tests: [&[Attribute]; 3] = [
&[
Attribute::Bold,
Attribute::Underlined,
Attribute::BlinkFast,
Attribute::Hidden,
],
&[
Attribute::Dim,
Attribute::Italic,
Attribute::Blink,
Attribute::Reverse,
Attribute::StrikeThrough,
],
&Attribute::MAP,
];
for test_attrs in tests {
let mut attrs = Attributes::new();
for attr in test_attrs {
attrs = attrs.insert(*attr);
}
assert_eq!(
attrs.bits().collect::<Vec<_>>(),
test_attrs
.iter()
.map(|attr| *attr as u16)
.collect::<Vec<_>>()
);
assert_eq!(&attrs.attrs().collect::<Vec<_>>(), test_attrs);
}
}