use std::path::PathBuf;
use crepuscularity_runtime::{HotReloadState, HotReloadView, TemplateContext, TemplateValue};
use gpui::{bounds, point, prelude::*, size, Application, WindowOptions};
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 || args[1] == "--help" || args[1] == "-h" {
eprintln!("Usage: crepus-dev <template.crepus> [--width N] [--height N] [--var key=value]");
eprintln!();
eprintln!("Options:");
eprintln!(" --width N Window width in pixels (default: 1200)");
eprintln!(" --height N Window height in pixels (default: 800)");
eprintln!(" --var k=v Set a string template variable (repeatable)");
eprintln!(" --bool k=true Set a boolean template variable");
eprintln!(" --int k=42 Set an integer template variable");
eprintln!();
eprintln!("Example:");
eprintln!(" crepus-dev my-view.crepus --width 800 --height 600 --var title=Hello --bool show_button=true");
std::process::exit(0);
}
let template_path = PathBuf::from(&args[1]);
if !template_path.exists() {
eprintln!("Error: template file not found: {:?}", template_path);
std::process::exit(1);
}
let mut width = 1200.0f32;
let mut height = 800.0f32;
let mut ctx = TemplateContext::new();
let mut i = 2;
while i < args.len() {
match args[i].as_str() {
"--width" => {
i += 1;
if let Some(v) = args.get(i) {
width = v.parse().unwrap_or(1200.0);
}
}
"--height" => {
i += 1;
if let Some(v) = args.get(i) {
height = v.parse().unwrap_or(800.0);
}
}
"--var" => {
i += 1;
if let Some(v) = args.get(i) {
if let Some(eq) = v.find('=') {
let key = &v[..eq];
let val = &v[eq + 1..];
ctx.set(key, val);
}
}
}
"--bool" => {
i += 1;
if let Some(v) = args.get(i) {
if let Some(eq) = v.find('=') {
let key = &v[..eq];
let val = v[eq + 1..].to_lowercase() == "true";
ctx.set(key, TemplateValue::Bool(val));
}
}
}
"--int" => {
i += 1;
if let Some(v) = args.get(i) {
if let Some(eq) = v.find('=') {
let key = &v[..eq];
if let Ok(n) = v[eq + 1..].parse::<i64>() {
ctx.set(key, TemplateValue::Int(n));
}
}
}
}
_ => {}
}
i += 1;
}
if let Some(dir) = template_path.parent() {
let ctx_path = dir.join("context.toml");
if ctx_path.exists() {
load_context_toml(&ctx_path, &mut ctx);
}
}
let display_name = template_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("template")
.to_string();
eprintln!("crepus-dev: watching {:?}", template_path);
eprintln!("crepus-dev: window {}x{}", width as u32, height as u32);
eprintln!("crepus-dev: edit and save to hot-reload");
Application::new().run(move |cx: &mut gpui::App| {
let window_options = WindowOptions {
window_bounds: Some(gpui::WindowBounds::Windowed(bounds(
point(gpui::px(100.), gpui::px(100.)),
size(gpui::px(width), gpui::px(height)),
))),
titlebar: None,
focus: true,
show: true,
kind: gpui::WindowKind::Normal,
is_movable: true,
is_resizable: true,
is_minimizable: true,
display_id: None,
window_background: gpui::WindowBackgroundAppearance::Opaque,
app_id: Some(format!("crepuscularity.dev.{}", display_name)),
window_min_size: None,
window_decorations: None,
tabbing_identifier: None,
};
let path = template_path.clone();
let ctx = ctx.clone();
cx.open_window(window_options, move |_window, cx| {
let state = cx.new(|cx| HotReloadState::new(path.clone(), ctx.clone(), cx));
cx.new(|_| HotReloadView::new(state))
})
.unwrap();
});
}
fn load_context_toml(path: &std::path::Path, ctx: &mut TemplateContext) {
let Ok(content) = std::fs::read_to_string(path) else {
return;
};
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(eq) = line.find('=') {
let key = line[..eq].trim();
let raw_val = line[eq + 1..].trim();
if raw_val == "true" {
ctx.set(key, TemplateValue::Bool(true));
} else if raw_val == "false" {
ctx.set(key, TemplateValue::Bool(false));
} else if let Ok(n) = raw_val.parse::<i64>() {
ctx.set(key, TemplateValue::Int(n));
} else if let Ok(f) = raw_val.parse::<f64>() {
ctx.set(key, TemplateValue::Float(f));
} else {
let s = raw_val.trim_matches('"').trim_matches('\'').to_string();
ctx.set(key, s);
}
}
}
}