Generate `script/storybook` (#3652)

Nate Butler created

[[PR Description]]

This PR adds the ability to run stories with `script/storybook`.

Running it directly will give you a selector like this:

```zsh
➜  zed git:(add-storybook-script) script/storybook
1) auto_height_editor	 9) icon		17) scroll
2) avatar		10) icon_button		18) tab
3) button		11) keybinding		19) tab_bar
4) checkbox		12) label		20) text
5) context_menu		13) list		21) viewport_units
6) cursor		14) list_header		22) z_index
7) disclosure		15) list_item		23) picker
8) focus		16) overflow_scroll
```

You can also provide a value like:

`script/storybook {STORY_NAME}` - Example: `script/storybook text`

OR 

`script/storybook components/{STORY_NAME}` - Example: `script/storybook
components/text`

I just wanted an easier way to interface with stories quickly to make
using them a bit easier, and enable discovery of what exists easier with
the selector.

This was a really quick hack, in the future we can extend this to a
proper CLI.

Release Notes:

- N/A

Change summary

crates/storybook2/Cargo.toml                        |  7 +
crates/storybook2/src/bin/print_storybook_script.rs | 74 +++++++++++++++
crates/storybook2/src/bin/storybook2.rs             |  9 -
crates/storybook2/src/lib.rs                        |  4 
script/storybook                                    | 63 ++++++++++-
5 files changed, 141 insertions(+), 16 deletions(-)

Detailed changes

crates/storybook2/Cargo.toml 🔗

@@ -3,10 +3,15 @@ name = "storybook2"
 version = "0.1.0"
 edition = "2021"
 publish = false
+default-run = "storybook"
 
 [[bin]]
 name = "storybook"
-path = "src/storybook2.rs"
+path = "src/bin/storybook2.rs"
+
+[[bin]]
+name = "print_storybook_script"
+path = "src/bin/print_storybook_script.rs"
 
 [dependencies]
 anyhow.workspace = true

crates/storybook2/src/bin/print_storybook_script.rs 🔗

@@ -0,0 +1,74 @@
+use std::fs::File;
+use std::io::Write;
+
+use strum::IntoEnumIterator;
+
+use storybook2::story_selector::ComponentStory;
+
+// TOOD: Ideally we actually create a more full featured CLI,
+// but for the moment I just wanted a easier way to run the stories
+
+fn main() -> std::io::Result<()> {
+    let path = std::env::current_dir()?;
+    let out_file = path.join("script").join("storybook");
+
+    // the script output file
+    let mut file = File::create(out_file)?;
+
+    // generate the list of components, in `snake_case`
+    let components = ComponentStory::iter()
+        .map(|c| c.to_string()) // Converts enum to string in `snake_case`
+        .collect::<Vec<_>>();
+
+    // write the bash script
+    writeln!(file, "#!/bin/bash")?;
+    writeln!(file, "")?;
+    writeln!(file, "options=(")?;
+    for component in &components {
+        writeln!(file, "  \"{}\"", component)?;
+    }
+    writeln!(file, ")")?;
+    writeln!(file, "")?;
+
+    // Check if an argument is provided and if it matches a valid option
+    writeln!(file, "run_story() {{")?;
+    writeln!(file, "  echo \"Running story: $1\"")?;
+    writeln!(file, "  cargo run -p storybook2 -- \"$1\"")?;
+    writeln!(file, "}}")?;
+    writeln!(file, "")?;
+
+    writeln!(file, "if [ \"$#\" -gt 0 ]; then")?;
+    writeln!(file, "  story_arg=\"$1\"")?;
+    writeln!(file, "  # Add prefix 'components/' if not present")?;
+    writeln!(file, "  if [[ $story_arg != components/* ]]; then")?;
+    writeln!(file, "    story_arg=\"components/$story_arg\"")?;
+    writeln!(file, "  fi")?;
+    writeln!(file, "  # Check if the provided story is a valid option")?;
+    writeln!(file, "  for opt in \"${{options[@]}}\"; do")?;
+    writeln!(
+        file,
+        "    if [[ \"components/$opt\" == \"$story_arg\" ]]; then"
+    )?;
+    writeln!(file, "      run_story \"$story_arg\"")?;
+    writeln!(file, "      exit")?;
+    writeln!(file, "    fi")?;
+    writeln!(file, "  done")?;
+    writeln!(file, "  echo \"Invalid story name: $1\"")?;
+    writeln!(file, "  exit 1")?;
+    writeln!(file, "fi")?;
+    writeln!(file, "")?;
+
+    // Existing selection prompt
+    writeln!(file, "prompt=\"Please select a story:\"")?;
+    writeln!(file, "PS3=\"$prompt \"")?;
+    writeln!(file, "select story in \"${{options[@]}}\"; do")?;
+    writeln!(file, "  if [[ -n $story ]]; then")?;
+    writeln!(file, "    run_story \"components/$story\"")?;
+    writeln!(file, "    break")?;
+    writeln!(file, "  else")?;
+    writeln!(file, "    echo \"Invalid option\"")?;
+    writeln!(file, "  fi")?;
+    writeln!(file, "done")?;
+
+    Ok(())
+}

crates/storybook2/src/storybook2.rs → crates/storybook2/src/bin/storybook2.rs 🔗

@@ -1,7 +1,3 @@
-mod assets;
-mod stories;
-mod story_selector;
-
 use std::sync::Arc;
 
 use clap::Parser;
@@ -15,8 +11,9 @@ use simplelog::SimpleLogger;
 use theme2::{ThemeRegistry, ThemeSettings};
 use ui::prelude::*;
 
-use crate::assets::Assets;
-use crate::story_selector::StorySelector;
+use storybook2::assets::Assets;
+pub use storybook2::story_selector::*;
+// pub use crate::story_selector::{ComponentStory, StorySelector};
 
 // gpui::actions! {
 //     storybook,

script/storybook 🔗

@@ -1,15 +1,60 @@
 #!/bin/bash
 
-# This script takes a single text input and replaces 'list_item' with the input in a cargo run command
+options=(
+  "auto_height_editor"
+  "avatar"
+  "button"
+  "checkbox"
+  "context_menu"
+  "cursor"
+  "disclosure"
+  "focus"
+  "icon"
+  "icon_button"
+  "keybinding"
+  "label"
+  "list"
+  "list_header"
+  "list_item"
+  "overflow_scroll"
+  "scroll"
+  "tab"
+  "tab_bar"
+  "text"
+  "viewport_units"
+  "z_index"
+  "picker"
+)
 
-# Check if an argument is provided
-if [ "$#" -ne 1 ]; then
-  echo "Usage: $0 <component_name>"
+run_story() {
+  echo "Running story: $1"
+  cargo run -p storybook2 -- "$1"
+}
+
+if [ "$#" -gt 0 ]; then
+  story_arg="$1"
+  # Add prefix 'components/' if not present
+  if [[ $story_arg != components/* ]]; then
+    story_arg="components/$story_arg"
+  fi
+  # Check if the provided story is a valid option
+  for opt in "${options[@]}"; do
+    if [[ "components/$opt" == "$story_arg" ]]; then
+      run_story "$story_arg"
+      exit
+    fi
+  done
+  echo "Invalid story name: $1"
   exit 1
 fi
 
-# Assign the argument to a variable
-COMPONENT_NAME="$1"
-
-# Run the cargo command with the provided component name
-cargo run -p storybook2 -- components/"$COMPONENT_NAME"
+prompt="Please select a story:"
+PS3="$prompt "
+select story in "${options[@]}"; do
+  if [[ -n $story ]]; then
+    run_story "components/$story"
+    break
+  else
+    echo "Invalid option"
+  fi
+done