| 1 | //! Export actual Ratatui previews for the Codewhale component website. |
| 2 | //! |
| 3 | //! cargo run --locked --example website -- target/website-buffers |
| 4 | //! python3 tools/export-website.py target/website-buffers /path/to/public/ratatui |
| 5 | //! |
| 6 | //! The website receives painted buffers, rather than a second implementation |
| 7 | //! of the components. Width choices redraw the same fixture at that width. |
| 8 | |
| 9 | use std::{collections::BTreeSet, io, path::PathBuf}; |
| 10 | |
| 11 | use codewhale_ratatui::{ |
| 12 | gallery, |
| 13 | testing::{self, Profile}, |
| 14 | }; |
| 15 | use serde_json::{Map, Value, json}; |
| 16 | |
| 17 | fn export(dir: PathBuf) -> io::Result<()> { |
| 18 | let entries = gallery::entries(); |
| 19 | let names: BTreeSet<_> = entries.iter().map(|entry| entry.name).collect(); |
| 20 | let safe_name = |name: &str| { |
| 21 | !name.is_empty() |
| 22 | && name |
| 23 | .bytes() |
| 24 | .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_')) |
| 25 | }; |
| 26 | if names.len() != entries.len() || names.iter().any(|name| !safe_name(name)) { |
| 27 | return Err(io::Error::other( |
| 28 | "gallery names must be unique safe basenames", |
| 29 | )); |
| 30 | } |
| 31 | std::fs::create_dir_all(&dir)?; |
| 32 | let mut manifest = Vec::with_capacity(entries.len()); |
| 33 | let mut count = 0; |
| 34 | for entry in &entries { |
| 35 | let mut previews = Map::new(); |
| 36 | for profile in Profile::ALL { |
| 37 | let theme = profile.theme(); |
| 38 | let resolved = gallery::theme_for(entry, &theme); |
| 39 | let mut widths = Map::new(); |
| 40 | for (key, width) in [ |
| 41 | ("native", entry.width), |
| 42 | ("40", 40), |
| 43 | ("80", 80), |
| 44 | ("120", 120), |
| 45 | ] { |
| 46 | let buffer = gallery::render_at(entry, &theme, width, entry.height); |
| 47 | widths.insert(key.into(), Value::String(testing::svg(&buffer, &resolved))); |
| 48 | count += 1; |
| 49 | } |
| 50 | previews.insert(profile.name().into(), Value::Object(widths)); |
| 51 | } |
| 52 | let value = json!({ |
| 53 | "name": entry.name, |
| 54 | "width": entry.width, |
| 55 | "height": entry.height, |
| 56 | "previews": previews, |
| 57 | }); |
| 58 | std::fs::write( |
| 59 | dir.join(format!("{}.json", entry.name)), |
| 60 | serde_json::to_vec(&value)?, |
| 61 | )?; |
| 62 | manifest.push(json!({"name": entry.name, "width": entry.width, "height": entry.height})); |
| 63 | } |
| 64 | std::fs::write( |
| 65 | dir.join("manifest.json"), |
| 66 | serde_json::to_vec_pretty(&manifest)?, |
| 67 | )?; |
| 68 | println!( |
| 69 | "Exported {count} actual buffers for {} gallery entries to {}", |
| 70 | entries.len(), |
| 71 | dir.display() |
| 72 | ); |
| 73 | Ok(()) |
| 74 | } |
| 75 | |
| 76 | fn main() -> io::Result<()> { |
| 77 | let args: Vec<_> = std::env::args_os().skip(1).collect(); |
| 78 | if args.len() != 1 { |
| 79 | return Err(io::Error::new( |
| 80 | io::ErrorKind::InvalidInput, |
| 81 | "usage: website OUTPUT_DIRECTORY", |
| 82 | )); |
| 83 | } |
| 84 | export(PathBuf::from(&args[0])) |
| 85 | } |
| 86 |