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:

<img width="1435" alt="Screenshot 2023-09-22 at 6 11 00 PM"
src="https://github.com/zed-industries/zed/assets/1486634/284e1a24-46ec-462e-9709-0f9b6e94931f">

Inputting an invalid story name will also show the list of available
stories:

<img width="1435" alt="Screenshot 2023-09-22 at 6 10 43 PM"
src="https://github.com/zed-industries/zed/assets/1486634/1ce3ae3f-ab03-4976-a06a-5a2b5f61eae3">

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2023-09-22 18:16:16 -04:00 committed by GitHub
parent fe4248cf34
commit ad62a966a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 51 deletions

2
Cargo.lock generated
View file

@ -7374,7 +7374,7 @@ name = "storybook"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 3.2.25",
"clap 4.4.4",
"gpui2",
"log",
"rust-embed",

View file

@ -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

View file

@ -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<Self, Self::Err> {
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<Vec<StorySelector>> = 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::<Vec<_>>()
});
stories
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
let value = match self {
Self::Element(story) => format!("elements/{story}"),
Self::Component(story) => format!("components/{story}"),
};
Some(PossibleValue::new(value))
}
}

View file

@ -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<Self, Self::Err> {
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<StorySelector>,
}
@ -146,7 +103,7 @@ fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
.clone()
}
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Result};
use gpui2::AssetSource;
use rust_embed::RustEmbed;
use workspace::WorkspaceElement;