From ad62a966a65dc506d90c5db80d231e869bb0077b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 22 Sep 2023 18:16:16 -0400 Subject: [PATCH] Display available stories in storybook CLI (#3021) This PR updates the storybook CLI to support displaying all of the available stories. The `--help` flag will now show a list of all the available stories: Screenshot 2023-09-22 at 6 11 00 PM Inputting an invalid story name will also show the list of available stories: Screenshot 2023-09-22 at 6 10 43 PM Release Notes: - N/A --- Cargo.lock | 2 +- crates/storybook/Cargo.toml | 2 +- crates/storybook/src/story_selector.rs | 76 ++++++++++++++++++++++++++ crates/storybook/src/storybook.rs | 55 ++----------------- 4 files changed, 84 insertions(+), 51 deletions(-) create mode 100644 crates/storybook/src/story_selector.rs diff --git a/Cargo.lock b/Cargo.lock index 186427e863..878604f360 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7374,7 +7374,7 @@ name = "storybook" version = "0.1.0" dependencies = [ "anyhow", - "clap 3.2.25", + "clap 4.4.4", "gpui2", "log", "rust-embed", diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index 49dd05ba30..5c73235d3e 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -10,7 +10,7 @@ path = "src/storybook.rs" [dependencies] anyhow.workspace = true -clap = { version = "3.1", features = ["derive"] } +clap = { version = "4.4", features = ["derive", "string"] } gpui2 = { path = "../gpui2" } log.workspace = true rust-embed.workspace = true diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs new file mode 100644 index 0000000000..d12368706a --- /dev/null +++ b/crates/storybook/src/story_selector.rs @@ -0,0 +1,76 @@ +use std::{str::FromStr, sync::OnceLock}; + +use anyhow::{anyhow, Context}; +use clap::builder::PossibleValue; +use clap::ValueEnum; +use strum::{EnumIter, EnumString, IntoEnumIterator}; + +#[derive(Debug, Clone, Copy, strum::Display, EnumString, EnumIter)] +#[strum(serialize_all = "snake_case")] +pub enum ElementStory { + Avatar, +} + +#[derive(Debug, Clone, Copy, strum::Display, EnumString, EnumIter)] +#[strum(serialize_all = "snake_case")] +pub enum ComponentStory { + Breadcrumb, + Facepile, + Toolbar, + TrafficLights, +} + +#[derive(Debug, Clone, Copy)] +pub enum StorySelector { + Element(ElementStory), + Component(ComponentStory), +} + +impl FromStr for StorySelector { + type Err = anyhow::Error; + + fn from_str(raw_story_name: &str) -> std::result::Result { + let story = raw_story_name.to_ascii_lowercase(); + + if let Some((_, story)) = story.split_once("elements/") { + let element_story = ElementStory::from_str(story) + .with_context(|| format!("story not found for element '{story}'"))?; + + return Ok(Self::Element(element_story)); + } + + if let Some((_, story)) = story.split_once("components/") { + let component_story = ComponentStory::from_str(story) + .with_context(|| format!("story not found for component '{story}'"))?; + + return Ok(Self::Component(component_story)); + } + + Err(anyhow!("story not found for '{raw_story_name}'")) + } +} + +/// The list of all stories available in the storybook. +static ALL_STORIES: OnceLock> = OnceLock::new(); + +impl ValueEnum for StorySelector { + fn value_variants<'a>() -> &'a [Self] { + let stories = ALL_STORIES.get_or_init(|| { + let element_stories = ElementStory::iter().map(Self::Element); + let component_stories = ComponentStory::iter().map(Self::Component); + + element_stories.chain(component_stories).collect::>() + }); + + stories + } + + fn to_possible_value(&self) -> Option { + let value = match self { + Self::Element(story) => format!("elements/{story}"), + Self::Component(story) => format!("components/{story}"), + }; + + Some(PossibleValue::new(value)) + } +} diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 59aca22be7..d52219f61e 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -3,10 +3,9 @@ mod collab_panel; mod stories; mod story; +mod story_selector; mod workspace; -use std::str::FromStr; - use ::theme as legacy_theme; use clap::Parser; use gpui2::{serde_json, vec2f, view, Element, IntoElement, RectF, ViewContext, WindowBounds}; @@ -19,61 +18,19 @@ use stories::components::facepile::FacepileStory; use stories::components::toolbar::ToolbarStory; use stories::components::traffic_lights::TrafficLightsStory; use stories::elements::avatar::AvatarStory; -use strum::EnumString; use ui::{ElementExt, Theme}; +use crate::story_selector::{ComponentStory, ElementStory, StorySelector}; + gpui2::actions! { storybook, [ToggleInspector] } -#[derive(Debug, Clone, Copy)] -enum StorySelector { - Element(ElementStory), - Component(ComponentStory), -} - -impl FromStr for StorySelector { - type Err = anyhow::Error; - - fn from_str(raw_story_name: &str) -> std::result::Result { - let story = raw_story_name.to_ascii_lowercase(); - - if let Some((_, story)) = story.split_once("elements/") { - let element_story = ElementStory::from_str(story) - .with_context(|| format!("story not found for element '{story}'"))?; - - return Ok(Self::Element(element_story)); - } - - if let Some((_, story)) = story.split_once("components/") { - let component_story = ComponentStory::from_str(story) - .with_context(|| format!("story not found for component '{story}'"))?; - - return Ok(Self::Component(component_story)); - } - - Err(anyhow!("story not found for '{raw_story_name}'")) - } -} - -#[derive(Debug, Clone, Copy, EnumString)] -#[strum(serialize_all = "snake_case")] -enum ElementStory { - Avatar, -} - -#[derive(Debug, Clone, Copy, EnumString)] -#[strum(serialize_all = "snake_case")] -enum ComponentStory { - Breadcrumb, - Facepile, - Toolbar, - TrafficLights, -} - #[derive(Parser)] +#[command(author, version, about, long_about = None)] struct Args { + #[arg(value_enum)] story: Option, } @@ -146,7 +103,7 @@ fn current_theme(cx: &mut ViewContext) -> Theme { .clone() } -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use gpui2::AssetSource; use rust_embed::RustEmbed; use workspace::WorkspaceElement;