#[allow(clippy::unsafe_derive_deserialize)]
#[derive(Default, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnsiFlags {
bits: u8,
}
impl AnsiFlags {
pub const BOLD: Self = Self { bits: (1 << 0) };
pub const UNDERLINE: Self = Self { bits: (1 << 1) };
pub const ITALIC: Self = Self { bits: (1 << 2) };
pub const BLINK: Self = Self { bits: (1 << 3) };
pub const REVERSE: Self = Self { bits: (1 << 4) };
pub const STRIKE: Self = Self { bits: (1 << 5) };
#[inline]
#[must_use]
pub const fn empty() -> Self {
Self { bits: 0 }
}
#[inline]
#[must_use]
pub const fn all() -> Self {
Self {
bits: <Self as BitFlags>::BOLD
| <Self as BitFlags>::UNDERLINE
| <Self as BitFlags>::ITALIC
| <Self as BitFlags>::BLINK
| <Self as BitFlags>::REVERSE
| <Self as BitFlags>::STRIKE,
}
}
#[inline]
#[must_use]
pub const fn bits(&self) -> u8 {
self.bits
}
#[inline]
#[must_use]
pub const fn from_bits(bits: u8) -> Option<Self> {
if (bits & !Self::all().bits()) == 0 {
Some(Self { bits })
} else {
None
}
}
#[inline]
#[must_use]
pub const fn from_bits_truncate(bits: u8) -> Self {
Self {
bits: bits & Self::all().bits,
}
}
#[inline]
#[must_use]
pub const unsafe fn from_bits_unchecked(bits: u8) -> Self {
Self { bits }
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.bits() == Self::empty().bits()
}
#[inline]
#[must_use]
pub const fn is_all(&self) -> bool {
Self::all().bits | self.bits == self.bits
}
#[inline]
#[must_use]
pub const fn intersects(&self, other: Self) -> bool {
!(Self {
bits: self.bits & other.bits,
})
.is_empty()
}
#[inline]
#[must_use]
pub const fn contains(&self, other: Self) -> bool {
(self.bits & other.bits) == other.bits
}
#[inline]
#[must_use]
pub const fn intersection(self, other: Self) -> Self {
Self {
bits: self.bits & other.bits,
}
}
#[inline]
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self {
bits: self.bits | other.bits,
}
}
#[inline]
#[must_use]
pub const fn difference(self, other: Self) -> Self {
Self {
bits: self.bits & !other.bits,
}
}
#[inline]
#[must_use]
pub const fn symmetric_difference(self, other: Self) -> Self {
Self {
bits: self.bits ^ other.bits,
}
}
#[inline]
#[must_use]
pub const fn complement(self) -> Self {
Self::from_bits_truncate(!self.bits)
}
#[inline]
pub fn insert(&mut self, other: Self) {
self.bits |= other.bits;
}
#[inline]
#[must_use]
pub const fn insert_to(self, other: Self) -> Self {
Self {
bits: self.bits | other.bits,
}
}
#[inline]
pub fn remove(&mut self, other: Self) {
self.bits &= !other.bits;
}
#[inline]
#[must_use]
pub const fn remove_to(self, other: Self) -> Self {
Self {
bits: self.bits & !other.bits,
}
}
#[inline]
pub fn toggle(&mut self, other: Self) {
self.bits ^= other.bits;
}
#[inline]
#[must_use]
pub const fn toggle_to(self, other: Self) -> Self {
Self {
bits: self.bits ^ other.bits,
}
}
#[inline]
pub fn set(&mut self, other: Self, value: bool) {
if value {
self.insert(other);
} else {
self.remove(other);
}
}
}
impl std::ops::BitOr for AnsiFlags {
type Output = Self;
#[inline]
fn bitor(self, other: AnsiFlags) -> Self {
Self {
bits: self.bits | other.bits,
}
}
}
impl std::ops::BitOrAssign for AnsiFlags {
#[inline]
fn bitor_assign(&mut self, other: Self) {
self.bits |= other.bits;
}
}
impl std::ops::BitXor for AnsiFlags {
type Output = Self;
#[inline]
fn bitxor(self, other: Self) -> Self {
Self {
bits: self.bits ^ other.bits,
}
}
}
impl std::ops::BitXorAssign for AnsiFlags {
#[inline]
fn bitxor_assign(&mut self, other: Self) {
self.bits ^= other.bits;
}
}
impl std::ops::BitAnd for AnsiFlags {
type Output = Self;
#[inline]
fn bitand(self, other: Self) -> Self {
Self {
bits: self.bits & other.bits,
}
}
}
impl std::ops::BitAndAssign for AnsiFlags {
#[inline]
fn bitand_assign(&mut self, other: Self) {
self.bits &= other.bits;
}
}
impl std::ops::Sub for AnsiFlags {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self {
Self {
bits: self.bits & !other.bits,
}
}
}
impl std::ops::SubAssign for AnsiFlags {
#[inline]
fn sub_assign(&mut self, other: Self) {
self.bits &= !other.bits;
}
}
impl std::ops::Not for AnsiFlags {
type Output = Self;
#[inline]
fn not(self) -> Self {
Self { bits: !self.bits } & Self::all()
}
}
impl std::iter::Extend<AnsiFlags> for AnsiFlags {
fn extend<T: std::iter::IntoIterator<Item = Self>>(&mut self, iterator: T) {
for item in iterator {
self.insert(item);
}
}
}
impl std::iter::FromIterator<AnsiFlags> for AnsiFlags {
fn from_iter<T: std::iter::IntoIterator<Item = Self>>(iterator: T) -> Self {
let mut result = Self::empty();
result.extend(iterator);
result
}
}
impl std::fmt::Debug for AnsiFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
if <Self as BoolFlags>::BOLD(self) {
if !first {
f.write_str(" | ")?;
}
first = false;
f.write_str("BOLD")?;
}
if <Self as BoolFlags>::UNDERLINE(self) {
if !first {
f.write_str(" | ")?;
}
first = false;
f.write_str("UNDERLINE")?;
}
if <Self as BoolFlags>::ITALIC(self) {
if !first {
f.write_str(" | ")?;
}
first = false;
f.write_str("ITALIC")?;
}
if <Self as BoolFlags>::BLINK(self) {
if !first {
f.write_str(" | ")?;
}
first = false;
f.write_str("BLINK")?;
}
if <Self as BoolFlags>::REVERSE(self) {
if !first {
f.write_str(" | ")?;
}
first = false;
f.write_str("REVERSE")?;
}
if <Self as BoolFlags>::STRIKE(self) {
if !first {
f.write_str(" | ")?;
}
first = false;
f.write_str("STRIKE")?;
}
let extra_bits = self.bits & !Self::all().bits();
if extra_bits != 0 {
if !first {
f.write_str(" | ")?;
}
first = false;
f.write_str("0x")?;
std::fmt::LowerHex::fmt(&extra_bits, f)?;
}
if first {
f.write_str("(empty)")?;
}
Ok(())
}
}
impl std::fmt::Binary for AnsiFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Binary::fmt(&self.bits, f)
}
}
impl std::fmt::Octal for AnsiFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Octal::fmt(&self.bits, f)
}
}
impl std::fmt::LowerHex for AnsiFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::LowerHex::fmt(&self.bits, f)
}
}
impl std::fmt::UpperHex for AnsiFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::UpperHex::fmt(&self.bits, f)
}
}
trait BitFlags {
const BOLD: u8 = 0;
const UNDERLINE: u8 = 0;
const ITALIC: u8 = 0;
const BLINK: u8 = 0;
const REVERSE: u8 = 0;
const STRIKE: u8 = 0;
}
impl BitFlags for AnsiFlags {
const BOLD: u8 = Self::BOLD.bits;
const UNDERLINE: u8 = Self::UNDERLINE.bits;
const ITALIC: u8 = Self::ITALIC.bits;
const BLINK: u8 = Self::BLINK.bits;
const REVERSE: u8 = Self::REVERSE.bits;
const STRIKE: u8 = Self::STRIKE.bits;
}
#[allow(non_snake_case)]
trait BoolFlags {
#[inline]
fn BOLD(&self) -> bool {
false
}
#[inline]
fn UNDERLINE(&self) -> bool {
false
}
#[inline]
fn ITALIC(&self) -> bool {
false
}
#[inline]
fn BLINK(&self) -> bool {
false
}
#[inline]
fn REVERSE(&self) -> bool {
false
}
#[inline]
fn STRIKE(&self) -> bool {
false
}
}
#[allow(non_snake_case)]
impl BoolFlags for AnsiFlags {
#[allow(deprecated)]
#[inline]
fn BOLD(&self) -> bool {
if Self::BOLD.bits == 0 && self.bits != 0 {
false
} else {
self.bits & Self::BOLD.bits == Self::BOLD.bits
}
}
#[allow(deprecated)]
#[inline]
fn UNDERLINE(&self) -> bool {
if Self::UNDERLINE.bits == 0 && self.bits != 0 {
false
} else {
self.bits & Self::UNDERLINE.bits == Self::UNDERLINE.bits
}
}
#[allow(deprecated)]
#[inline]
fn ITALIC(&self) -> bool {
if Self::ITALIC.bits == 0 && self.bits != 0 {
false
} else {
self.bits & Self::ITALIC.bits == Self::ITALIC.bits
}
}
#[allow(deprecated)]
#[inline]
fn BLINK(&self) -> bool {
if Self::BLINK.bits == 0 && self.bits != 0 {
false
} else {
self.bits & Self::BLINK.bits == Self::BLINK.bits
}
}
#[allow(deprecated)]
#[inline]
fn REVERSE(&self) -> bool {
if Self::REVERSE.bits == 0 && self.bits != 0 {
false
} else {
self.bits & Self::REVERSE.bits == Self::REVERSE.bits
}
}
#[allow(deprecated)]
#[inline]
fn STRIKE(&self) -> bool {
if Self::STRIKE.bits == 0 && self.bits != 0 {
false
} else {
self.bits & Self::STRIKE.bits == Self::STRIKE.bits
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn all_the_things() {
assert_eq!(
AnsiFlags::all(),
AnsiFlags::BOLD
| AnsiFlags::UNDERLINE
| AnsiFlags::ITALIC
| AnsiFlags::BLINK
| AnsiFlags::REVERSE
| AnsiFlags::STRIKE
);
assert!(AnsiFlags::is_all(&AnsiFlags::all()));
assert_eq!(
format!("{:?}", AnsiFlags::all()),
"BOLD | UNDERLINE | ITALIC | BLINK | REVERSE | STRIKE"
);
assert!(!AnsiFlags::is_empty(&AnsiFlags::all()));
assert!(AnsiFlags::all().contains(AnsiFlags::UNDERLINE));
assert_eq!(AnsiFlags::from_bits(0x0), Some(AnsiFlags::empty()));
assert_eq!(AnsiFlags::from_bits(0x1), Some(AnsiFlags::BOLD));
assert_eq!(AnsiFlags::from_bits(0x64), None);
assert_eq!(AnsiFlags::from_bits_truncate(200), AnsiFlags::BLINK);
unsafe {
assert_eq!(AnsiFlags::from_bits_unchecked(1), AnsiFlags::BOLD);
}
assert!((AnsiFlags::BOLD | AnsiFlags::ITALIC).intersects(AnsiFlags::BOLD));
assert_eq!(
(AnsiFlags::BOLD | AnsiFlags::ITALIC)
.intersection(AnsiFlags::BOLD | AnsiFlags::UNDERLINE | AnsiFlags::STRIKE),
AnsiFlags::BOLD
);
assert_eq!(
(AnsiFlags::BOLD | AnsiFlags::ITALIC)
.union(AnsiFlags::STRIKE | AnsiFlags::UNDERLINE | AnsiFlags::ITALIC),
AnsiFlags::BOLD | AnsiFlags::ITALIC | AnsiFlags::STRIKE | AnsiFlags::UNDERLINE
);
assert_eq!(
(AnsiFlags::BOLD | AnsiFlags::ITALIC)
.difference(AnsiFlags::STRIKE | AnsiFlags::UNDERLINE | AnsiFlags::ITALIC),
AnsiFlags::BOLD
);
assert_eq!(
(AnsiFlags::BOLD | AnsiFlags::ITALIC)
.symmetric_difference(AnsiFlags::STRIKE | AnsiFlags::UNDERLINE | AnsiFlags::ITALIC),
AnsiFlags::BOLD | AnsiFlags::STRIKE | AnsiFlags::UNDERLINE
);
assert_eq!(
(AnsiFlags::STRIKE | AnsiFlags::UNDERLINE | AnsiFlags::ITALIC).complement(),
AnsiFlags::all() - AnsiFlags::STRIKE - AnsiFlags::UNDERLINE - AnsiFlags::ITALIC
);
let mut strike = AnsiFlags::STRIKE;
strike.insert(AnsiFlags::UNDERLINE);
assert_eq!(strike, AnsiFlags::STRIKE | AnsiFlags::UNDERLINE);
assert_eq!(strike, AnsiFlags::STRIKE.insert_to(AnsiFlags::UNDERLINE));
strike.remove(AnsiFlags::UNDERLINE);
assert_eq!(strike, AnsiFlags::STRIKE);
assert_eq!(
strike,
(AnsiFlags::STRIKE | AnsiFlags::UNDERLINE).remove_to(AnsiFlags::UNDERLINE)
);
let mut bold = AnsiFlags::BOLD;
bold.toggle(AnsiFlags::all());
assert_eq!(bold, AnsiFlags::all() - AnsiFlags::BOLD);
bold.set(AnsiFlags::BOLD, true);
assert_eq!(bold, AnsiFlags::all());
bold.set(AnsiFlags::all(), false);
assert_eq!(bold, AnsiFlags::empty());
assert!(bold.is_empty());
}
#[test]
fn ops_binary() {
#[allow(clippy::wildcard_imports)]
use std::ops::*;
let mut bold = AnsiFlags::BOLD;
let out = bold.bitand(AnsiFlags::BOLD | AnsiFlags::UNDERLINE);
assert_eq!(out, AnsiFlags::BOLD);
bold.bitand_assign(AnsiFlags::BOLD | AnsiFlags::UNDERLINE);
assert_eq!(out, bold);
let italic = AnsiFlags::ITALIC;
let out = italic.bitand(AnsiFlags::BOLD | AnsiFlags::UNDERLINE);
assert_eq!(out, AnsiFlags::empty());
let mut italic = AnsiFlags::ITALIC;
let out = italic.bitor(AnsiFlags::BOLD | AnsiFlags::UNDERLINE);
assert_eq!(
out,
AnsiFlags::BOLD | AnsiFlags::UNDERLINE | AnsiFlags::ITALIC
);
italic.bitor_assign(AnsiFlags::BOLD | AnsiFlags::UNDERLINE);
assert_eq!(out, italic);
let mut blink = AnsiFlags::BLINK;
let out = blink.bitxor(AnsiFlags::BOLD | AnsiFlags::UNDERLINE);
assert_eq!(
out,
AnsiFlags::BOLD | AnsiFlags::UNDERLINE | AnsiFlags::BLINK
);
blink.bitxor_assign(AnsiFlags::BOLD | AnsiFlags::UNDERLINE);
assert_eq!(out, blink);
let blink = AnsiFlags::BLINK;
let out = blink.not();
assert_eq!(out, AnsiFlags::all() - AnsiFlags::BLINK);
}
#[test]
fn other_traits() {
let mut flags = AnsiFlags::REVERSE;
flags.extend([AnsiFlags::BOLD]);
assert_eq!(flags, AnsiFlags::REVERSE | AnsiFlags::BOLD);
assert!(!AnsiFlags::ITALIC.BOLD());
assert!(!AnsiFlags::ITALIC.BLINK());
assert!(!AnsiFlags::ITALIC.STRIKE());
assert!(!AnsiFlags::ITALIC.UNDERLINE());
assert!(!AnsiFlags::ITALIC.REVERSE());
assert!(AnsiFlags::ITALIC.ITALIC());
}
#[test]
fn format() {
assert_eq!(format!("{:02x}", AnsiFlags::all()), "3f");
assert_eq!(format!("{:02X}", AnsiFlags::all()), "3F");
assert_eq!(format!("{:02o}", AnsiFlags::all()), "77");
assert_eq!(format!("{:02b}", AnsiFlags::all()), "111111");
}
}