Skip to main content

rich_micro/
api.rs

1//! The Rust API (#570): an extension trait on core's [`Text`] and a
2//! renderable, so micro assets compose with everything else without a core
3//! method.
4
5use std::sync::Arc;
6
7use rich::measure::Measurement;
8use rich::{Console, ConsoleOptions, Renderable, Segment, Text};
9use rich_plugin_api::{Plugin, PluginError, PluginMetadata, PluginRegistrar};
10
11use crate::error::MicroError;
12use crate::markup::{Diagnostic, MicroTransform, expand};
13use crate::model::MicroAsset;
14use crate::registry::MicroRegistry;
15use crate::render::{FallbackPreference, placeholder};
16use crate::rows::reserve_rows;
17
18/// Micro assets on [`Text`].
19///
20/// ```
21/// use rich::Text;
22/// use rich_micro::{Layer, MicroAsset, MicroExt, MicroRegistry};
23///
24/// let mut registry = MicroRegistry::new();
25/// registry.add(Layer::Inline, MicroAsset::new("ship", "rocket")?.with_emoji("🚀")?)?;
26/// let mut text = Text::new("Deploying ");
27/// text.append_micro(&registry, "ship")?;
28/// assert_eq!(text.plain(), "Deploying 🚀");
29/// assert_eq!(text.cell_len(), 12);
30/// # Ok::<(), rich_micro::MicroError>(())
31/// ```
32pub trait MicroExt {
33    /// Append the asset `name` resolves to, as its placeholder cells.
34    fn append_micro(
35        &mut self,
36        registry: &MicroRegistry,
37        name: &str,
38    ) -> Result<&mut Self, MicroError>;
39
40    /// Append `asset`'s placeholder cells.
41    fn append_micro_asset(&mut self, asset: &MicroAsset) -> &mut Self;
42
43    /// Replace the `:micro:name:` tokens in this text (see
44    /// [`markup`](crate::markup) for when to prefer the markup path).
45    fn expand_micro(&mut self, registry: &MicroRegistry) -> Vec<Diagnostic>;
46}
47
48impl MicroExt for Text {
49    fn append_micro(
50        &mut self,
51        registry: &MicroRegistry,
52        name: &str,
53    ) -> Result<&mut Self, MicroError> {
54        let asset = registry.require(name)?;
55        Ok(self.append_micro_asset(asset))
56    }
57
58    fn append_micro_asset(&mut self, asset: &MicroAsset) -> &mut Self {
59        let appended =
60            std::mem::take(self).append_text(&placeholder(asset, FallbackPreference::Emoji));
61        *self = appended;
62        self
63    }
64
65    fn expand_micro(&mut self, registry: &MicroRegistry) -> Vec<Diagnostic> {
66        let (text, diagnostics) = expand(self, registry, FallbackPreference::Emoji);
67        *self = text;
68        diagnostics
69    }
70}
71
72/// One asset as a renderable: exactly its columns wide, in any layout, and
73/// its rows high: a two-row asset renders both its rows (see
74/// [`MicroRows`](crate::MicroRows)), so a table row or a panel holding it
75/// grows to fit.
76///
77/// ```
78/// use rich_micro::{MicroAsset, MicroAssetRef};
79/// use rich::{Console, Renderable};
80///
81/// let asset = MicroAsset::new("ok", "check")?.with_text("OK")?;
82/// let console = Console::builder().width(20).build();
83/// let micro = MicroAssetRef::new(asset);
84/// let measured = micro.measure(&console, &console.options());
85/// assert_eq!((measured.minimum, measured.maximum), (2, 2));
86/// # Ok::<(), rich_micro::MicroError>(())
87/// ```
88#[derive(Clone, Debug)]
89pub struct MicroAssetRef {
90    asset: Arc<MicroAsset>,
91    preference: FallbackPreference,
92}
93
94impl MicroAssetRef {
95    pub fn new(asset: impl Into<Arc<MicroAsset>>) -> Self {
96        MicroAssetRef {
97            asset: asset.into(),
98            preference: FallbackPreference::default(),
99        }
100    }
101
102    /// The asset `name` resolves to in `registry`.
103    pub fn from_registry(registry: &MicroRegistry, name: &str) -> Result<Self, MicroError> {
104        Ok(MicroAssetRef::new(Arc::clone(registry.require(name)?)))
105    }
106
107    #[must_use]
108    pub fn preference(mut self, preference: FallbackPreference) -> Self {
109        self.preference = preference;
110        self
111    }
112
113    pub fn asset(&self) -> &MicroAsset {
114        &self.asset
115    }
116
117    /// Its placeholder, as a [`Text`]. Each call is a new occurrence.
118    pub fn text(&self) -> Text {
119        placeholder(&self.asset, self.preference)
120    }
121}
122
123impl Renderable for MicroAssetRef {
124    fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
125        reserve_rows(self.text().rich_render(console, options))
126    }
127
128    fn measure(&self, _console: &Console, options: &ConsoleOptions) -> Measurement {
129        let cols = self.asset.cols();
130        Measurement::new(cols, cols).clamp(None, Some(options.max_width))
131    }
132
133    fn fit_to_measurement(&self) -> bool {
134        true
135    }
136}
137
138/// Registers the substitution as the text transform `micro`, for hosts that
139/// build transform pipelines from plugins.
140#[derive(Clone, Debug)]
141pub struct MicroPlugin {
142    registry: Arc<MicroRegistry>,
143}
144
145impl MicroPlugin {
146    pub fn new(registry: Arc<MicroRegistry>) -> Self {
147        MicroPlugin { registry }
148    }
149}
150
151impl Plugin for MicroPlugin {
152    fn metadata(&self) -> PluginMetadata {
153        PluginMetadata::new("micro", "Micro assets", env!("CARGO_PKG_VERSION"))
154            .description("Replaces :micro:name: tokens with inline micro assets")
155    }
156
157    fn register(&self, registrar: &mut dyn PluginRegistrar) -> Result<(), PluginError> {
158        registrar.transform(
159            "micro",
160            Arc::new(MicroTransform::new(Arc::clone(&self.registry))),
161        );
162        Ok(())
163    }
164}