Skip to main content

parse_style/
attributes.rs

1use std::fmt;
2use thiserror::Error;
3
4/// Individual effects that can be applied to text in a terminal.
5///
6/// `Attribute` values can be combined with bitwise operators to produce
7/// [`AttributeSet`]s.
8///
9/// `Attribute` values can be [parsed][std::str::FromStr] from the
10/// following case-insensitive strings:
11///
12/// - `"bold"` or `"b"` — `Bold`
13/// - `"dim"` or `"d"` — `Dim`
14/// - `"italic"` or `"i"` — `Italic`
15/// - `"underline"` or `"u"` — `Underline`
16/// - `"blink"` — `Blink`
17/// - `"blink2"` — `Blink2`
18/// - `"reverse"` or `"r"` — `Reverse`
19/// - `"conceal"` or `"c"` — `Conceal`
20/// - `"strike"` or `"s"` — `Strike`
21/// - `"underline2"` or `"uu"` — `Underline2`
22/// - `"frame"` — `Frame`
23/// - `"encircle"` — `Encircle`
24/// - `"overline"` — `Overline`
25///
26/// `Attribute` values are [displayed][std::fmt::Display] as lowercase
27/// strings from the above list; for values with two strings, the longer
28/// one is used.
29#[derive(Clone, Copy, Debug, strum::EnumIter, Eq, Hash, Ord, PartialEq, PartialOrd)]
30#[repr(u16)]
31pub enum Attribute {
32    Bold = 1 << 0,
33    Dim = 1 << 1,
34    Italic = 1 << 2,
35    Underline = 1 << 3,
36    Blink = 1 << 4,
37    /// Fast blinking
38    Blink2 = 1 << 5,
39    /// Reverse video
40    Reverse = 1 << 6,
41    /// Concealed/hidden
42    Conceal = 1 << 7,
43    /// Strikethrough
44    Strike = 1 << 8,
45    /// Double-underline
46    Underline2 = 1 << 9,
47    Frame = 1 << 10,
48    Encircle = 1 << 11,
49    Overline = 1 << 12,
50}
51
52impl Attribute {
53    const COUNT: u16 = 13;
54
55    /// Returns an iterator over all [`Attribute`] variants
56    pub fn iter() -> AttributeIter {
57        // To avoid the need for users to import the trait
58        <Attribute as strum::IntoEnumIterator>::iter()
59    }
60
61    /// Return the long name of the attribute
62    ///
63    /// # Example
64    ///
65    /// ```
66    /// use parse_style::Attribute;
67    ///
68    /// assert_eq!(Attribute::Bold.as_str(), "bold");
69    /// ```
70    pub fn as_str(self) -> &'static str {
71        match self {
72            Attribute::Bold => "bold",
73            Attribute::Dim => "dim",
74            Attribute::Italic => "italic",
75            Attribute::Underline => "underline",
76            Attribute::Blink => "blink",
77            Attribute::Blink2 => "blink2",
78            Attribute::Reverse => "reverse",
79            Attribute::Conceal => "conceal",
80            Attribute::Strike => "strike",
81            Attribute::Underline2 => "underline2",
82            Attribute::Frame => "frame",
83            Attribute::Encircle => "encircle",
84            Attribute::Overline => "overline",
85        }
86    }
87}
88
89impl fmt::Display for Attribute {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        write!(f, "{}", self.as_str())
92    }
93}
94
95impl std::str::FromStr for Attribute {
96    type Err = ParseAttributeError;
97
98    fn from_str(s: &str) -> Result<Attribute, ParseAttributeError> {
99        match s.to_ascii_lowercase().as_str() {
100            "bold" | "b" => Ok(Attribute::Bold),
101            "dim" | "d" => Ok(Attribute::Dim),
102            "italic" | "i" => Ok(Attribute::Italic),
103            "underline" | "u" => Ok(Attribute::Underline),
104            "blink" => Ok(Attribute::Blink),
105            "blink2" => Ok(Attribute::Blink2),
106            "reverse" | "r" => Ok(Attribute::Reverse),
107            "conceal" | "c" => Ok(Attribute::Conceal),
108            "strike" | "s" => Ok(Attribute::Strike),
109            "underline2" | "uu" => Ok(Attribute::Underline2),
110            "frame" => Ok(Attribute::Frame),
111            "encircle" => Ok(Attribute::Encircle),
112            "overline" => Ok(Attribute::Overline),
113            _ => Err(ParseAttributeError(s.to_owned())),
114        }
115    }
116}
117
118impl<A: Into<AttributeSet>> std::ops::BitAnd<A> for Attribute {
119    type Output = AttributeSet;
120
121    fn bitand(self, rhs: A) -> AttributeSet {
122        AttributeSet((self as u16) & rhs.into().0)
123    }
124}
125
126impl<A: Into<AttributeSet>> std::ops::BitOr<A> for Attribute {
127    type Output = AttributeSet;
128
129    fn bitor(self, rhs: A) -> AttributeSet {
130        AttributeSet((self as u16) | rhs.into().0)
131    }
132}
133
134impl<A: Into<AttributeSet>> std::ops::BitXor<A> for Attribute {
135    type Output = AttributeSet;
136
137    fn bitxor(self, rhs: A) -> AttributeSet {
138        AttributeSet((self as u16) ^ rhs.into().0)
139    }
140}
141
142impl<A: Into<AttributeSet>> std::ops::Sub<A> for Attribute {
143    type Output = AttributeSet;
144
145    fn sub(self, rhs: A) -> AttributeSet {
146        AttributeSet((self as u16) & !rhs.into().0)
147    }
148}
149
150impl std::ops::Not for Attribute {
151    type Output = AttributeSet;
152
153    fn not(self) -> AttributeSet {
154        AttributeSet::ALL - self
155    }
156}
157
158/// A set of [`Attribute`] values.
159///
160/// `AttributeSet` values can be combined with bitwise operators and can be
161/// iterated over.
162#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
163pub struct AttributeSet(u16);
164
165impl AttributeSet {
166    /// A set containing no [`Attribute`]s
167    pub const EMPTY: AttributeSet = AttributeSet(0);
168
169    /// A set containing all [`Attribute`]s
170    pub const ALL: AttributeSet = AttributeSet((1 << Attribute::COUNT) - 1);
171
172    /// Return a new set containing no [`Attribute`]s
173    pub fn new() -> AttributeSet {
174        AttributeSet(0)
175    }
176
177    /// Test whether the set is empty
178    pub fn is_empty(self) -> bool {
179        self.0 == 0
180    }
181
182    /// Returns the number of [`Attribute`]s in the set
183    pub fn len(self) -> usize {
184        let qty = self.0.count_ones();
185        match usize::try_from(qty) {
186            Ok(sz) => sz,
187            Err(_) => unreachable!("The number of bits in a u16 should fit in a usize"),
188        }
189    }
190
191    /// Test whether the set contains all [`Attribute`]s
192    pub fn is_all(self) -> bool {
193        self == Self::ALL
194    }
195
196    /// Test whether the set contains the given [`Attribute`]
197    pub fn contains(self, attr: Attribute) -> bool {
198        self.0 & (attr as u16) != 0
199    }
200
201    /// Adds the given [`Attribute`] to the set if not already present.
202    ///
203    /// Returns `true` if the given `Attribute` was not already in the set.
204    ///
205    /// # Example
206    ///
207    /// ```
208    /// use parse_style::{Attribute, AttributeSet};
209    ///
210    /// let mut attrset = AttributeSet::new();
211    /// assert!(!attrset.contains(Attribute::Bold));
212    /// assert!(attrset.insert(Attribute::Bold));
213    /// assert!(attrset.contains(Attribute::Bold));
214    /// assert!(!attrset.insert(Attribute::Bold));
215    /// assert!(attrset.contains(Attribute::Bold));
216    /// ```
217    pub fn insert(&mut self, attr: Attribute) -> bool {
218        let attr = attr as u16;
219        let adding = (self.0 & attr) == 0;
220        self.0 |= attr;
221        adding
222    }
223
224    /// Removes the given [`Attribute`] from the set if present.
225    ///
226    /// Returns `true` if the given `Attribute` was present in the set.
227    ///
228    /// # Example
229    ///
230    /// ```
231    /// use parse_style::{Attribute, AttributeSet};
232    ///
233    /// let mut attrset = AttributeSet::from([Attribute::Bold]);
234    /// assert!(attrset.contains(Attribute::Bold));
235    /// assert!(attrset.remove(Attribute::Bold));
236    /// assert!(!attrset.contains(Attribute::Bold));
237    /// assert!(!attrset.remove(Attribute::Bold));
238    /// assert!(!attrset.contains(Attribute::Bold));
239    /// ```
240    pub fn remove(&mut self, attr: Attribute) -> bool {
241        let attr = attr as u16;
242        let present = (self.0 & attr) != 0;
243        self.0 &= !attr;
244        present
245    }
246
247    /// Removes all [`Attribute`]s from the set
248    pub fn clear(&mut self) {
249        *self = Self::default();
250    }
251
252    /// Returns true if `self` and `other` are disjoint, i.e., if there is no
253    /// [`Attribute`] that is in both sets.
254    ///
255    /// # Examples
256    ///
257    /// ```
258    /// use parse_style::{Attribute, AttributeSet};
259    ///
260    /// let attrset1 = AttributeSet::from([Attribute::Bold,
261    /// Attribute::Italic]);
262    /// let attrset2 = AttributeSet::from([Attribute::Underline,
263    /// Attribute::Blink]);
264    /// assert!(attrset1.is_disjoint(attrset2));
265    /// assert!(attrset2.is_disjoint(attrset1));
266    /// ```
267    ///
268    /// ```
269    /// use parse_style::{Attribute, AttributeSet};
270    ///
271    /// let attrset1 = AttributeSet::from([Attribute::Bold,
272    /// Attribute::Italic]);
273    /// let attrset2 = AttributeSet::from([Attribute::Bold,
274    /// Attribute::Underline]);
275    /// assert!(!attrset1.is_disjoint(attrset2));
276    /// assert!(!attrset2.is_disjoint(attrset1));
277    /// ```
278    pub fn is_disjoint(self, other: AttributeSet) -> bool {
279        self.0 & other.0 == 0
280    }
281
282    /// Returns `true` if `self` is a subset of `other`
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use parse_style::{Attribute, AttributeSet};
288    ///
289    /// let attrset1 = AttributeSet::from([Attribute::Bold]);
290    /// let attrset2 = AttributeSet::from([Attribute::Bold,
291    /// Attribute::Underline]);
292    /// assert!(attrset1.is_subset(attrset2));
293    /// assert!(!attrset2.is_subset(attrset1));
294    /// ```
295    ///
296    /// ```
297    /// use parse_style::{Attribute, AttributeSet};
298    ///
299    /// let attrset1 = AttributeSet::from([Attribute::Bold,
300    /// Attribute::Italic]);
301    /// let attrset2 = AttributeSet::from([Attribute::Bold,
302    /// Attribute::Underline]);
303    /// assert!(!attrset1.is_subset(attrset2));
304    /// assert!(!attrset2.is_subset(attrset1));
305    /// ```
306    pub fn is_subset(self, other: AttributeSet) -> bool {
307        self.0 & other.0 == self.0
308    }
309
310    /// Returns `true` if `self` is a superset of `other`
311    ///
312    /// # Examples
313    ///
314    /// ```
315    /// use parse_style::{Attribute, AttributeSet};
316    ///
317    /// let attrset1 = AttributeSet::from([Attribute::Bold]);
318    /// let attrset2 = AttributeSet::from([Attribute::Bold,
319    /// Attribute::Underline]);
320    /// assert!(!attrset1.is_superset(attrset2));
321    /// assert!(attrset2.is_superset(attrset1));
322    /// ```
323    ///
324    /// ```
325    /// use parse_style::{Attribute, AttributeSet};
326    ///
327    /// let attrset1 = AttributeSet::from([Attribute::Bold,
328    /// Attribute::Italic]);
329    /// let attrset2 = AttributeSet::from([Attribute::Bold,
330    /// Attribute::Underline]);
331    /// assert!(!attrset1.is_superset(attrset2));
332    /// assert!(!attrset2.is_superset(attrset1));
333    /// ```
334    pub fn is_superset(self, other: AttributeSet) -> bool {
335        self.0 & other.0 == other.0
336    }
337}
338
339impl From<Attribute> for AttributeSet {
340    fn from(value: Attribute) -> AttributeSet {
341        AttributeSet(value as u16)
342    }
343}
344
345impl<const N: usize> From<[Attribute; N]> for AttributeSet {
346    fn from(value: [Attribute; N]) -> AttributeSet {
347        AttributeSet::from_iter(value)
348    }
349}
350
351impl IntoIterator for AttributeSet {
352    type Item = Attribute;
353    type IntoIter = AttributeSetIter;
354
355    fn into_iter(self) -> AttributeSetIter {
356        AttributeSetIter::new(self)
357    }
358}
359
360impl FromIterator<Attribute> for AttributeSet {
361    fn from_iter<I: IntoIterator<Item = Attribute>>(iter: I) -> Self {
362        iter.into_iter()
363            .fold(AttributeSet::new(), |set, attr| set | attr)
364    }
365}
366
367impl Extend<Attribute> for AttributeSet {
368    fn extend<I: IntoIterator<Item = Attribute>>(&mut self, iter: I) {
369        for attr in iter {
370            *self |= attr;
371        }
372    }
373}
374
375#[cfg(feature = "anstyle")]
376#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
377impl From<AttributeSet> for anstyle::Effects {
378    /// Convert an `AttributeSet` to an [`anstyle::Effects`]
379    ///
380    /// # Data Loss
381    ///
382    /// The following attributes are discarded during conversion, as they have
383    /// no `anstyle::Effects` equivalents:
384    ///
385    /// - [`Attribute::Blink2`]
386    /// - [`Attribute::Frame`]
387    /// - [`Attribute::Encircle`]
388    /// - [`Attribute::Overline`]
389    fn from(value: AttributeSet) -> anstyle::Effects {
390        let mut efs = anstyle::Effects::new();
391        for attr in value {
392            match attr {
393                Attribute::Bold => efs |= anstyle::Effects::BOLD,
394                Attribute::Dim => efs |= anstyle::Effects::DIMMED,
395                Attribute::Italic => efs |= anstyle::Effects::ITALIC,
396                Attribute::Underline => efs |= anstyle::Effects::UNDERLINE,
397                Attribute::Blink => efs |= anstyle::Effects::BLINK,
398                Attribute::Blink2 => (),
399                Attribute::Reverse => efs |= anstyle::Effects::INVERT,
400                Attribute::Conceal => efs |= anstyle::Effects::HIDDEN,
401                Attribute::Strike => efs |= anstyle::Effects::STRIKETHROUGH,
402                Attribute::Underline2 => efs |= anstyle::Effects::DOUBLE_UNDERLINE,
403                Attribute::Frame => (),
404                Attribute::Encircle => (),
405                Attribute::Overline => (),
406            }
407        }
408        efs
409    }
410}
411
412#[cfg(feature = "anstyle")]
413#[cfg_attr(docsrs, doc(cfg(feature = "anstyle")))]
414impl From<anstyle::Effects> for AttributeSet {
415    /// Convert an [`anstyle::Effects`] to an `AttributeSet`
416    ///
417    ///
418    /// # Data Loss
419    ///
420    /// The following effects are discarded during conversion, as they have no
421    /// `Attribute` equivalents:
422    ///
423    /// - [`anstyle::Effects::CURLY_UNDERLINE`]
424    /// - [`anstyle::Effects::DOTTED_UNDERLINE`]
425    /// - [`anstyle::Effects::DASHED_UNDERLINE`]
426    fn from(value: anstyle::Effects) -> AttributeSet {
427        let mut set = AttributeSet::new();
428        for eff in value.iter() {
429            match eff {
430                anstyle::Effects::BOLD => set |= Attribute::Bold,
431                anstyle::Effects::DIMMED => set |= Attribute::Dim,
432                anstyle::Effects::ITALIC => set |= Attribute::Italic,
433                anstyle::Effects::UNDERLINE => set |= Attribute::Underline,
434                anstyle::Effects::DOUBLE_UNDERLINE => set |= Attribute::Underline2,
435                anstyle::Effects::CURLY_UNDERLINE => (),
436                anstyle::Effects::DOTTED_UNDERLINE => (),
437                anstyle::Effects::DASHED_UNDERLINE => (),
438                anstyle::Effects::BLINK => set |= Attribute::Blink,
439                anstyle::Effects::INVERT => set |= Attribute::Reverse,
440                anstyle::Effects::HIDDEN => set |= Attribute::Conceal,
441                anstyle::Effects::STRIKETHROUGH => set |= Attribute::Strike,
442                // Because an `Effects` can be either a single effect or
443                // multiple, we need a catch-all arm here, even though the
444                // iterator will only yield single effects.
445                _ => (),
446            }
447        }
448        set
449    }
450}
451
452#[cfg(feature = "crossterm")]
453#[cfg_attr(docsrs, doc(cfg(feature = "crossterm")))]
454impl From<AttributeSet> for crossterm::style::Attributes {
455    /// Convert an `AttributeSet` to a [`crossterm::style::Attributes`] that
456    /// enables the input attributes
457    fn from(value: AttributeSet) -> crossterm::style::Attributes {
458        use crossterm::style::Attribute as CrossAttrib;
459        let mut attributes = crossterm::style::Attributes::none();
460        for attr in value {
461            let ca = match attr {
462                Attribute::Bold => CrossAttrib::Bold,
463                Attribute::Dim => CrossAttrib::Dim,
464                Attribute::Italic => CrossAttrib::Italic,
465                Attribute::Underline => CrossAttrib::Underlined,
466                Attribute::Blink => CrossAttrib::SlowBlink,
467                Attribute::Blink2 => CrossAttrib::RapidBlink,
468                Attribute::Reverse => CrossAttrib::Reverse,
469                Attribute::Conceal => CrossAttrib::Hidden,
470                Attribute::Strike => CrossAttrib::CrossedOut,
471                Attribute::Underline2 => CrossAttrib::DoubleUnderlined,
472                Attribute::Frame => CrossAttrib::Framed,
473                Attribute::Encircle => CrossAttrib::Encircled,
474                Attribute::Overline => CrossAttrib::OverLined,
475            };
476            attributes.set(ca);
477        }
478        attributes
479    }
480}
481
482#[cfg(feature = "ratatui")]
483#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
484impl From<AttributeSet> for ratatui_core::style::Modifier {
485    /// Convert an `AttributeSet` to a [`ratatui_core::style::Modifier`]
486    ///
487    /// # Data Loss
488    ///
489    /// The following attributes are discarded during conversion, as they have
490    /// no `ratatui_core::style::Modifier` equivalents:
491    ///
492    /// - [`Attribute::Underline2`]
493    /// - [`Attribute::Frame`]
494    /// - [`Attribute::Encircle`]
495    /// - [`Attribute::Overline`]
496    fn from(value: AttributeSet) -> ratatui_core::style::Modifier {
497        let mut mods = ratatui_core::style::Modifier::empty();
498        for attr in value {
499            match attr {
500                Attribute::Bold => mods |= ratatui_core::style::Modifier::BOLD,
501                Attribute::Dim => mods |= ratatui_core::style::Modifier::DIM,
502                Attribute::Italic => mods |= ratatui_core::style::Modifier::ITALIC,
503                Attribute::Underline => mods |= ratatui_core::style::Modifier::UNDERLINED,
504                Attribute::Blink => mods |= ratatui_core::style::Modifier::SLOW_BLINK,
505                Attribute::Blink2 => mods |= ratatui_core::style::Modifier::RAPID_BLINK,
506                Attribute::Reverse => mods |= ratatui_core::style::Modifier::REVERSED,
507                Attribute::Conceal => mods |= ratatui_core::style::Modifier::HIDDEN,
508                Attribute::Strike => mods |= ratatui_core::style::Modifier::CROSSED_OUT,
509                Attribute::Underline2 => (),
510                Attribute::Frame => (),
511                Attribute::Encircle => (),
512                Attribute::Overline => (),
513            }
514        }
515        mods
516    }
517}
518
519#[cfg(feature = "ratatui")]
520#[cfg_attr(docsrs, doc(cfg(feature = "ratatui")))]
521impl From<ratatui_core::style::Modifier> for AttributeSet {
522    /// Convert a [`ratatui_core::style::Modifier`] to an `AttributeSet`
523    fn from(value: ratatui_core::style::Modifier) -> AttributeSet {
524        let mut set = AttributeSet::new();
525        for m in value.iter() {
526            match m {
527                ratatui_core::style::Modifier::BOLD => set |= Attribute::Bold,
528                ratatui_core::style::Modifier::DIM => set |= Attribute::Dim,
529                ratatui_core::style::Modifier::ITALIC => set |= Attribute::Italic,
530                ratatui_core::style::Modifier::UNDERLINED => set |= Attribute::Underline,
531                ratatui_core::style::Modifier::SLOW_BLINK => set |= Attribute::Blink,
532                ratatui_core::style::Modifier::RAPID_BLINK => set |= Attribute::Blink,
533                ratatui_core::style::Modifier::REVERSED => set |= Attribute::Reverse,
534                ratatui_core::style::Modifier::HIDDEN => set |= Attribute::Conceal,
535                ratatui_core::style::Modifier::CROSSED_OUT => set |= Attribute::Strike,
536                // Because a `Modifier` can be either a single effect or
537                // multiple, we need a catch-all arm here, even though the
538                // iterator will only yield single modifiers.
539                _ => (),
540            }
541        }
542        set
543    }
544}
545
546impl<A: Into<AttributeSet>> std::ops::BitAnd<A> for AttributeSet {
547    type Output = AttributeSet;
548
549    fn bitand(self, rhs: A) -> AttributeSet {
550        AttributeSet(self.0 & rhs.into().0)
551    }
552}
553
554impl<A: Into<AttributeSet>> std::ops::BitAndAssign<A> for AttributeSet {
555    fn bitand_assign(&mut self, rhs: A) {
556        self.0 &= rhs.into().0;
557    }
558}
559
560impl<A: Into<AttributeSet>> std::ops::BitOr<A> for AttributeSet {
561    type Output = AttributeSet;
562
563    fn bitor(self, rhs: A) -> AttributeSet {
564        AttributeSet(self.0 | rhs.into().0)
565    }
566}
567
568impl<A: Into<AttributeSet>> std::ops::BitOrAssign<A> for AttributeSet {
569    fn bitor_assign(&mut self, rhs: A) {
570        self.0 |= rhs.into().0;
571    }
572}
573
574impl<A: Into<AttributeSet>> std::ops::BitXor<A> for AttributeSet {
575    type Output = AttributeSet;
576
577    fn bitxor(self, rhs: A) -> AttributeSet {
578        AttributeSet(self.0 ^ rhs.into().0)
579    }
580}
581
582impl<A: Into<AttributeSet>> std::ops::BitXorAssign<A> for AttributeSet {
583    fn bitxor_assign(&mut self, rhs: A) {
584        self.0 ^= rhs.into().0;
585    }
586}
587
588impl<A: Into<AttributeSet>> std::ops::Sub<A> for AttributeSet {
589    type Output = AttributeSet;
590
591    fn sub(self, rhs: A) -> AttributeSet {
592        AttributeSet(self.0 & !rhs.into().0)
593    }
594}
595
596impl<A: Into<AttributeSet>> std::ops::SubAssign<A> for AttributeSet {
597    fn sub_assign(&mut self, rhs: A) {
598        self.0 &= !rhs.into().0;
599    }
600}
601
602impl std::ops::Not for AttributeSet {
603    type Output = AttributeSet;
604
605    fn not(self) -> AttributeSet {
606        AttributeSet(!self.0 & ((1 << Attribute::COUNT) - 1))
607    }
608}
609
610/// An iterator over the [`Attribute`]s in an [`AttributeSet`]
611#[derive(Clone, Debug)]
612pub struct AttributeSetIter {
613    inner: AttributeIter,
614    set: AttributeSet,
615}
616
617impl AttributeSetIter {
618    fn new(set: AttributeSet) -> AttributeSetIter {
619        AttributeSetIter {
620            inner: Attribute::iter(),
621            set,
622        }
623    }
624}
625
626impl Iterator for AttributeSetIter {
627    type Item = Attribute;
628
629    fn next(&mut self) -> Option<Attribute> {
630        self.inner.by_ref().find(|&attr| self.set.contains(attr))
631    }
632
633    fn size_hint(&self) -> (usize, Option<usize>) {
634        (0, self.inner.size_hint().1)
635    }
636}
637
638impl DoubleEndedIterator for AttributeSetIter {
639    fn next_back(&mut self) -> Option<Attribute> {
640        self.inner.by_ref().rfind(|&attr| self.set.contains(attr))
641    }
642}
643
644impl std::iter::FusedIterator for AttributeSetIter {}
645
646/// Error returned when parsing an attribute fails
647#[derive(Clone, Debug, Eq, Error, PartialEq)]
648#[error("invalid attribute name: {0:?}")]
649pub struct ParseAttributeError(
650    /// The invalid attribute string
651    pub String,
652);
653
654#[cfg(test)]
655mod tests {
656    use super::*;
657
658    #[test]
659    fn double_ended_iteration() {
660        let attrs = Attribute::Bold | Attribute::Frame | Attribute::Reverse | Attribute::Strike;
661        let mut iter = attrs.into_iter();
662        assert_eq!(iter.next(), Some(Attribute::Bold));
663        assert_eq!(iter.next_back(), Some(Attribute::Frame));
664        assert_eq!(iter.next(), Some(Attribute::Reverse));
665        assert_eq!(iter.next_back(), Some(Attribute::Strike));
666        assert_eq!(iter.next(), None);
667        assert_eq!(iter.next_back(), None);
668        assert_eq!(iter.next(), None);
669        assert_eq!(iter.next_back(), None);
670    }
671}