1use std::{str::FromStr, sync::OnceLock};
2
3use anyhow::{anyhow, Context};
4use clap::builder::PossibleValue;
5use clap::ValueEnum;
6use strum::{EnumIter, EnumString, IntoEnumIterator};
7
8#[derive(Debug, Clone, Copy, strum::Display, EnumString, EnumIter)]
9#[strum(serialize_all = "snake_case")]
10pub enum ElementStory {
11 Avatar,
12}
13
14#[derive(Debug, Clone, Copy, strum::Display, EnumString, EnumIter)]
15#[strum(serialize_all = "snake_case")]
16pub enum ComponentStory {
17 Breadcrumb,
18 Facepile,
19 Toolbar,
20 TrafficLights,
21}
22
23#[derive(Debug, Clone, Copy)]
24pub enum StorySelector {
25 Element(ElementStory),
26 Component(ComponentStory),
27}
28
29impl FromStr for StorySelector {
30 type Err = anyhow::Error;
31
32 fn from_str(raw_story_name: &str) -> std::result::Result<Self, Self::Err> {
33 let story = raw_story_name.to_ascii_lowercase();
34
35 if let Some((_, story)) = story.split_once("elements/") {
36 let element_story = ElementStory::from_str(story)
37 .with_context(|| format!("story not found for element '{story}'"))?;
38
39 return Ok(Self::Element(element_story));
40 }
41
42 if let Some((_, story)) = story.split_once("components/") {
43 let component_story = ComponentStory::from_str(story)
44 .with_context(|| format!("story not found for component '{story}'"))?;
45
46 return Ok(Self::Component(component_story));
47 }
48
49 Err(anyhow!("story not found for '{raw_story_name}'"))
50 }
51}
52
53/// The list of all stories available in the storybook.
54static ALL_STORIES: OnceLock<Vec<StorySelector>> = OnceLock::new();
55
56impl ValueEnum for StorySelector {
57 fn value_variants<'a>() -> &'a [Self] {
58 let stories = ALL_STORIES.get_or_init(|| {
59 let element_stories = ElementStory::iter().map(Self::Element);
60 let component_stories = ComponentStory::iter().map(Self::Component);
61
62 element_stories.chain(component_stories).collect::<Vec<_>>()
63 });
64
65 stories
66 }
67
68 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
69 let value = match self {
70 Self::Element(story) => format!("elements/{story}"),
71 Self::Component(story) => format!("components/{story}"),
72 };
73
74 Some(PossibleValue::new(value))
75 }
76}