crepuscularity-cli 0.7.4

crepus CLI — scaffolding and builds for Crepuscularity (UNSTABLE; in active development).
/// `crepus new NAME` — scaffold a new GPUI application.
use std::fs;
use std::path::Path;
use std::time::Instant;

use crate::ui;

pub fn run(name: &str) {
    let t0 = Instant::now();
    let dir = Path::new(name);

    if dir.exists() {
        ui::error(&format!("'{}' already exists", name));
    }

    fs::create_dir_all(dir.join("src")).unwrap_or_else(|e| {
        ui::error(&format!("could not create directories: {e}"));
    });

    fs::write(dir.join("Cargo.toml"), cargo_toml(name)).unwrap();
    fs::write(dir.join("src").join("main.rs"), main_rs(name)).unwrap();
    fs::write(dir.join(".gitignore"), "/target\n").unwrap();

    use console::style;
    eprintln!("\n{} created {}", ui::ok(), style(name).cyan().bold());
    eprintln!();
    eprintln!("{}", style("Next steps:").dim());
    eprintln!("  cd {name}");
    eprintln!("  crepus dev");
    ui::done_in(t0.elapsed());
}

fn cargo_toml(name: &str) -> String {
    format!(
        r#"[package]
name = "{name}"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "{name}"
path = "src/main.rs"

[dependencies]
gpui = {{ version = "0.2", default-features = false, features = ["font-kit"] }}
crepuscularity-gpui = {{ version = "0.3" }}
"#
    )
}

fn main_rs(name: &str) -> String {
    let pascal = to_pascal_case(name);
    format!(
        r##"use crepuscularity_gpui::prelude::*;
use gpui::{{App, Application, WindowOptions}};

struct {pascal}View {{
    count: i32,
}}

impl {pascal}View {{
    fn new(_cx: &mut Context<Self>) -> Self {{
        Self {{ count: 0 }}
    }}

    fn increment(&mut self, _: &gpui::ClickEvent, _: &mut gpui::Window, cx: &mut Context<Self>) {{
        self.count += 1;
        cx.notify();
    }}
}}

impl Render for {pascal}View {{
    fn render(&mut self, _window: &mut gpui::Window, _cx: &mut Context<Self>) -> impl IntoElement {{
        let count = self.count;
        view! {{r#"
            div w-full h-full bg-zinc-950 text-white flex flex-col items-center justify-center gap-6
                div text-8xl font-bold leading-none
                    "{{count}}"
                button bg-white text-black font-semibold px-6 py-2 rounded-lg @click=increment
                    "increment"
        "#}}
    }}
}}

fn main() {{
    Application::new().run(|cx: &mut App| {{
        use gpui::prelude::*;
        cx.open_window(WindowOptions::default(), |_win, cx| {{
            cx.new({pascal}View::new)
        }})
        .unwrap();
    }});
}}
"##,
        pascal = pascal,
    )
}

fn to_pascal_case(s: &str) -> String {
    s.split(&['-', '_', ' '][..])
        .filter(|p| !p.is_empty())
        .map(|p| {
            let mut c = p.chars();
            match c.next() {
                None => String::new(),
                Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
            }
        })
        .collect()
}