1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//! Component previews registered with the `#[preview]` attribute.
//!
//! Enable the `preview` Cargo feature. Preview functions take no arguments and
//! return `()`; they can call composables with representative fixture data.
//! The IDE selects a descriptor using `CRANPOSE_PREVIEW` when launching an
//! embedded application. Normal desktop launches keep the application's root.
use serde::Serialize;
/// A component fixture, compiled into the app and discovered without source rewriting.
#[derive(Debug, Serialize)]
pub struct Preview {
/// Stable descriptor identifier, including the function and variant.
pub id: &'static str,
/// Display name of this variant.
pub name: &'static str,
/// Optional group shown in the preview browser.
pub group: &'static str,
/// Rust function name.
pub function: &'static str,
/// Source path supplied by the compiler.
pub file: &'static str,
/// One-based source line supplied by the compiler.
pub line: u32,
/// Suggested logical viewport width.
pub width: u32,
/// Suggested logical viewport height.
pub height: u32,
/// Suggested dark theme.
pub dark: bool,
/// Render entry point, invoked inside composition.
#[serde(skip)]
pub render: fn(),
}
inventory::collect!(Preview);
#[doc(hidden)]
pub use inventory::submit as __submit;
/// Returns compiled preview variants, ordered by group, name and identity.
pub fn registered() -> Vec<&'static Preview> {
let mut previews: Vec<_> = inventory::iter::<Preview>.into_iter().collect();
previews.sort_by_key(|preview| (preview.group, preview.name, preview.id));
previews
}
/// Finds an exact descriptor or an unambiguous function/name selector.
pub fn find(selector: &str) -> Result<&'static Preview, PreviewError> {
select(®istered(), selector)
}
/// Failure to select a compiled preview.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum PreviewError {
/// No descriptor matches the requested identity or name.
#[error(
"No compiled preview matches '{0}'. Enable the preview feature and annotate a parameterless composable with #[cranpose::preview]."
)]
Missing(String),
/// More than one variant matches a short name.
#[error("Preview '{0}' is ambiguous; select its full descriptor identity.")]
Ambiguous(String),
}
fn select(previews: &[&'static Preview], selector: &str) -> Result<&'static Preview, PreviewError> {
if let Some(found) = previews.iter().find(|preview| preview.id == selector) {
return Ok(found);
}
let mut matches = previews
.iter()
.copied()
.filter(|preview| preview.function == selector || preview.name == selector);
let first = matches
.next()
.ok_or_else(|| PreviewError::Missing(selector.to_string()))?;
if matches.next().is_some() {
return Err(PreviewError::Ambiguous(selector.to_string()));
}
Ok(first)
}
#[cfg(test)]
#[path = "tests/preview_tests.rs"]
mod tests;