Skip to content

Commit

Permalink
Make the default condition check for OS support.
Browse files Browse the repository at this point in the history
This removes the Windows-specific check one every paint. This enables overriding
the check for Windows support, which on failure would previously disbale
coloring. We use this during integration testing to force-enable coloring.
  • Loading branch information
SergioBenitez committed Jun 22, 2023
1 parent e4b0b80 commit 4f1f71a
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 46 deletions.
33 changes: 30 additions & 3 deletions src/condition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ pub struct AtomicCondition(AtomicPtr<()>);
pub struct CachedBool(AtomicU8);

impl Condition {
/// A condition that evaluates to `true` if the OS supports coloring.
///
/// On Windows, this condition tries to enable coloring support on the first
/// call and caches the result for subsequent calls. Outside of Windows,
/// this always evaluates to `true`.
pub const DEFAULT: Condition = Condition(Condition::os_support);

/// A condition that always evaluates to `true`.
pub const ALWAYS: Condition = Condition(Condition::always);

Expand Down Expand Up @@ -125,11 +132,20 @@ impl Condition {

/// The backing function for [`Condition::NEVER`]. Returns `false` always.
pub const fn never() -> bool { false }

/// The backing function for [`Condition::DEFAULT`].
///
/// Returns `true` if the current OS supports ANSI escape sequences for
/// coloring. On Windows, the first call to this function attempts to enable
/// support. Outside of windows, this always returns `true`.
pub fn os_support() -> bool {
crate::windows::cache_enable()
}
}

impl Default for Condition {
fn default() -> Self {
Condition::ALWAYS
Condition::DEFAULT
}
}

Expand All @@ -142,7 +158,7 @@ impl core::ops::Deref for Condition {
}

impl AtomicCondition {
pub const ALWAYS: AtomicCondition = AtomicCondition::from(Condition::ALWAYS);
pub const DEFAULT: AtomicCondition = AtomicCondition::from(Condition::DEFAULT);

pub const fn from(value: Condition) -> Self {
AtomicCondition(AtomicPtr::new(value.0 as *mut ()))
Expand Down Expand Up @@ -196,7 +212,18 @@ impl CachedBool {

impl fmt::Debug for Condition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
let mut dbg = f.debug_tuple("Condition");
if *self == Condition::DEFAULT {
dbg.field(&"DEFAULT");
} else if *self == Condition::ALWAYS {
dbg.field(&"ALWAYS");
} else if *self == Condition::NEVER {
dbg.field(&"NEVER");
} else {
dbg.field(&self.0);
}

dbg.finish()
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/global.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::condition::{AtomicCondition, Condition};

static ENABLED: AtomicCondition = AtomicCondition::ALWAYS;
static ENABLED: AtomicCondition = AtomicCondition::DEFAULT;

/// Disables styling globally.
/// Unconditionally disables styling globally.
///
/// # Example
///
Expand All @@ -20,10 +20,10 @@ pub fn disable() {
ENABLED.store(Condition::NEVER);
}

/// Enables styling globally.
/// Unconditionally enables styling globally.
///
/// Styling is enabled by default, so this method should only be called to _re_
/// enable styling.
/// By default, styling is enabled based on [`Condition::Default`], which checks
/// for operating system support.
///
/// # Example
///
Expand All @@ -42,7 +42,7 @@ pub fn enable() {
ENABLED.store(Condition::ALWAYS);
}

/// Dynamically enable styling globally based on `condition`.
/// Dynamically enables styling globally based on `condition`.
///
/// The supplied `condition` is checked dynamically, any time a painted value is
/// displayed. As a result, `condition` should be _fast_.
Expand Down
4 changes: 1 addition & 3 deletions src/paint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,7 @@ impl<T> Painted<T> {

#[inline]
pub(crate) fn enabled(&self) -> bool {
crate::is_enabled()
&& (self.style.condition)()
&& crate::windows::cache_enable()
crate::is_enabled() && (self.style.condition)()
}

properties!([pub const] constructor(Self) -> Self);
Expand Down
79 changes: 48 additions & 31 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,48 @@ use alloc::{string::String, borrow::Cow};
use std::borrow::Cow;

/// A set of styling options.
///
/// ## Equivalence and Ordering
///
/// Only a style's `foreground`, `background`, and set of `attributes` is
/// considered when testing for equivalence or producing an ordering via
/// `PartialEq` or `Eq`, and `PartialOrd` or `Ord`. A style's quirks and
/// conditions are ignored.
#[derive(Default, Debug, Copy, Clone)]
pub struct Style {
/// The foreground color.
/// The foreground color. Defaults to `None`.
///
/// ```rust
/// use yansi::{Style, Color};
///
/// assert_eq!(Style::new().foreground, None);
/// assert_eq!(Style::new().green().foreground, Some(Color::Green));
/// ```
pub foreground: Option<Color>,
/// The background color.
/// The background color. Defaults to `None`.
///
/// ```rust
/// use yansi::{Style, Color};
///
/// assert_eq!(Style::new().background, None);
/// assert_eq!(Style::new().on_red().background, Some(Color::Red));
/// ```
pub background: Option<Color>,
pub(crate) attributes: Set<Attribute>,
pub(crate) quirks: Set<Quirk>,
/// The condition.
///
/// To check a style's condition directly, invoke it as a function:
///
/// ```rust
/// use yansi::{Style, Condition};
///
/// let style = Style::new().whenever(Condition::ALWAYS);
/// assert!((style.condition)());
///
/// let style = Style::new().whenever(Condition::NEVER);
/// assert!(!(style.condition)());
/// ```
pub condition: Condition,
}

Expand All @@ -45,13 +78,13 @@ impl Style {
background: None,
attributes: Set::EMPTY,
quirks: Set::EMPTY,
condition: Condition::ALWAYS,
condition: Condition::DEFAULT,
};

/// Returns a new style with no foreground or background, no attributes
/// or quirks, and an [`ALWAYS`](Condition::ALWAYS) condition.
/// or quirks, and [`Condition::DEFAULT`].
///
/// This is the default.
/// This is the default returned by [`Default::default()`].
///
/// # Example
///
Expand Down Expand Up @@ -230,42 +263,36 @@ impl fmt::Write for AnsiSplicer<'_> {
}
}

// , PartialEq, Eq, PartialOrd, Ord, Hash

impl PartialEq for Style {
fn eq(&self, other: &Self) -> bool {
let Style {
foreground: fg_a,
background: bg_a,
attributes: attrs_a,
quirks: _,
condition: cond_a,
condition: _,
} = self;

let Style {
foreground: fg_b,
background: bg_b,
attributes: attrs_b,
quirks: _,
condition: cond_b,
condition: _,
} = other;

fg_a == fg_b
&& bg_a == bg_b
&& attrs_a == attrs_b
&& cond_a == cond_b
fg_a == fg_b && bg_a == bg_b && attrs_a == attrs_b
}
}

impl Eq for Style { }

impl core::hash::Hash for Style {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
let Style { foreground, background, attributes, quirks: _, condition, } = self;
let Style { foreground, background, attributes, quirks: _, condition: _, } = self;
foreground.hash(state);
background.hash(state);
attributes.hash(state);
condition.hash(state);
}
}

Expand All @@ -276,15 +303,15 @@ impl PartialOrd for Style {
background: bg_a,
attributes: attrs_a,
quirks: _,
condition: cond_a,
condition: _,
} = self;

let Style {
foreground: fg_b,
background: bg_b,
attributes: attrs_b,
quirks: _,
condition: cond_b,
condition: _,
} = other;

match fg_a.partial_cmp(&fg_b) {
Expand All @@ -297,12 +324,7 @@ impl PartialOrd for Style {
ord => return ord,
}

match attrs_a.partial_cmp(&attrs_b) {
Some(core::cmp::Ordering::Equal) => {}
ord => return ord,
}

cond_a.partial_cmp(&cond_b)
attrs_a.partial_cmp(&attrs_b)
}
}

Expand All @@ -313,15 +335,15 @@ impl Ord for Style {
background: bg_a,
attributes: attrs_a,
quirks: _,
condition: cond_a,
condition: _,
} = self;

let Style {
foreground: fg_b,
background: bg_b,
attributes: attrs_b,
quirks: _,
condition: cond_b,
condition: _,
} = other;

match fg_a.cmp(&fg_b) {
Expand All @@ -334,11 +356,6 @@ impl Ord for Style {
ord => return ord,
}

match attrs_a.cmp(&attrs_b) {
core::cmp::Ordering::Equal => {}
ord => return ord,
}

cond_a.cmp(&cond_b)
attrs_a.cmp(&attrs_b)
}
}
2 changes: 0 additions & 2 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ mod windows_console {
#[allow(dead_code)]
pub fn enable() -> bool { true }

// Try to enable colors on Windows, and try to do it at most once. It's okay
// if we try more than once. We only try when painting is enabled.
#[inline(always)]
pub fn cache_enable() -> bool { true }
}
Expand Down
6 changes: 5 additions & 1 deletion tests/basic.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use yansi::{Paint, Style, Color::*};
use yansi::{Paint, Style, Condition, Color::*};

static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());

macro_rules! assert_renders {
($($input:expr => $expected:expr,)*) => {
let _lock = LOCK.lock().expect("FAIL FAST - LOCK POISONED");
yansi::enable();

$(
let (input, expected) = ($input.to_string(), $expected.to_string());
if input != expected {
Expand All @@ -16,6 +18,8 @@ macro_rules! assert_renders {
stringify!($input), $input.style);
}
)*

yansi::enable_when(Condition::DEFAULT);
};
}

Expand Down

0 comments on commit 4f1f71a

Please sign in to comment.