Skip to main content

farben_core/
registry.rs

1//! Global named style registry.
2//!
3//! Stores user-defined [`Style`] values under string keys, allowing markup like
4//! `[danger]` to expand into a pre-configured combination of colors and emphasis.
5//! Styles are registered at startup via the [`style!`] macro and optionally given
6//! a prefix string via the [`prefix!`] macro.
7//!
8//! The registry is process-global and backed by a [`Mutex`]-protected [`HashMap`].
9//! All operations are safe to call from multiple threads, though the typical usage
10//! pattern is to register styles once at program startup.
11
12use std::{
13    collections::HashMap,
14    sync::{Mutex, OnceLock},
15};
16
17use crate::ansi::Style;
18use crate::errors::LexError;
19
20static REGISTRY: OnceLock<Mutex<HashMap<String, Style>>> = OnceLock::new();
21
22/// Registers a named style in the global registry.
23///
24/// If a style with `name` already exists, it is replaced.
25pub fn insert_style(name: impl Into<String>, style: Style) {
26    REGISTRY
27        .get_or_init(|| Mutex::new(HashMap::new()))
28        .lock()
29        .unwrap()
30        .insert(name.into(), style);
31}
32
33/// Sets the prefix string for an already-registered named style.
34///
35/// The prefix is prepended to the style's escape sequence at render time,
36/// allowing a named style to inject arbitrary text before its ANSI codes.
37///
38/// # Errors
39///
40/// Returns [`LexError::UnknownStyle`] if `name` has not been registered via
41/// [`insert_style`] (or the [`style!`] macro).
42pub fn set_prefix(name: impl Into<String>, prefix: impl Into<String>) -> Result<(), LexError> {
43    let name = name.into();
44    let prefix = prefix.into();
45
46    let mut map = REGISTRY
47        .get_or_init(|| Mutex::new(HashMap::new()))
48        .lock()
49        .unwrap();
50
51    if let Some(style) = map.get_mut(&name) {
52        style.prefix = Some(prefix);
53        Ok(())
54    } else {
55        Err(LexError::UnknownStyle(name))
56    }
57}
58
59/// Looks up a named style in the global registry.
60///
61/// # Errors
62///
63/// Returns `LexError::InvalidTag` if `query` does not match any registered style name.
64pub(crate) fn search_registry(query: impl Into<String>) -> Result<Style, LexError> {
65    let map = REGISTRY
66        .get_or_init(|| Mutex::new(HashMap::new()))
67        .lock()
68        .unwrap();
69
70    let query = query.into();
71    match map.get(&query) {
72        Some(style) => Ok(style.clone()),
73        None => Err(LexError::InvalidTag(query)),
74    }
75}
76
77/// Defines a named style in the global registry.
78///
79/// Parses `$markup` as a farben markup string and stores the resulting [`Style`]
80/// under `$name`. Panics if the markup is invalid.
81///
82/// # Example
83///
84/// ```ignore
85/// style!("danger", "[bold red]");
86/// // [danger] in markup now expands to bold red text
87/// ```
88#[macro_export]
89macro_rules! style {
90    ($name:expr, $markup:expr) => {
91        farben_core::registry::insert_style(
92            $name,
93            farben_core::ansi::Style::parse($markup).unwrap(),
94        );
95    };
96}
97
98/// Sets a prefix string on a previously defined named style.
99///
100/// The prefix is injected as a literal string before the style's ANSI escape sequence
101/// when rendered. The style must already exist in the registry; call [`style!`] first.
102///
103/// # Panics
104///
105/// Panics if `$name` has not been registered. Use [`set_prefix`] directly to handle
106/// this case without panicking.
107///
108/// # Example
109///
110/// ```ignore
111/// style!("warn", "[yellow]");
112/// prefix!("warn", "⚠ ");
113/// // [warn] now renders "⚠ " followed by the yellow escape sequence
114/// ```
115#[macro_export]
116macro_rules! prefix {
117    ($name:expr, $prefix:expr) => {
118        farben_core::registry::set_prefix($name, $prefix)
119            .expect("prefix!() called with unregistered style name");
120    };
121}