1use anyhow::{Context, Result};
2use std::fs;
3use std::path::Path;
4use std::process::Command;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum Template {
8 Minimal,
9 Dashboard,
10 AiCopilot,
11}
12
13impl std::str::FromStr for Template {
14 type Err = ();
15
16 fn from_str(s: &str) -> Result<Self, Self::Err> {
17 match s {
18 "dashboard" => Ok(Template::Dashboard),
19 "ai" | "ai-copilot" => Ok(Template::AiCopilot),
20 _ => Ok(Template::Minimal),
21 }
22 }
23}
24
25pub struct Scaffolder {
27 pub name: String,
28 pub template: Template,
29 pub init_git: bool,
30}
31
32impl Scaffolder {
33 pub fn new(name: String, template: Template, init_git: bool) -> Self {
35 Self {
36 name,
37 template,
38 init_git,
39 }
40 }
41
42 pub fn run(&self) -> Result<()> {
44 let root = Path::new(&self.name);
45 if root.exists() {
46 return Err(anyhow::anyhow!("Directory already exists: {}", self.name));
47 }
48
49 println!("🛠️ Scaffolding CVKG application: {}...", self.name);
50
51 fs::create_dir_all(root.join("src"))?;
52 fs::create_dir_all(root.join("assets"))?;
53 fs::create_dir_all(root.join("themes"))?;
54 fs::create_dir_all(root.join(".github").join("workflows"))?;
55
56 self.gen_cargo_toml(root)?;
57 self.gen_main_rs(root)?;
58 self.gen_theme_rs(root)?;
59 self.gen_gitignore(root)?;
60 self.gen_ci_yml(root)?;
61
62 if self.init_git {
63 println!("📦 Initializing git repository...");
64 Command::new("git").arg("init").current_dir(root).output()?;
65 }
66
67 println!("✅ Successfully scaffolded CVKG project: {}", self.name);
68 println!("\nNext steps:");
69 println!(" cd {}", self.name);
70 println!(" cvkg dev");
71
72 Ok(())
73 }
74
75 fn gen_cargo_toml(&self, root: &Path) -> Result<()> {
76 let version = env!("CARGO_PKG_VERSION");
77 let content = format!(
78 r#"[package]
79name = "{}"
80version = "0.1.0"
81edition = "2024"
82
83[dependencies]
84cvkg = {{ version = "{}" }}
85cvkg-core = {{ version = "{}" }}
86tokio = {{ version = "1.0", features = ["full"] }}
87log = "0.4"
88"#,
89 self.name, version, version
90 );
91
92 fs::write(root.join("Cargo.toml"), content).context("Failed to write Cargo.toml")
93 }
94
95 fn gen_main_rs(&self, root: &Path) -> Result<()> {
96 let content = match self.template {
97 Template::Minimal => {
98 r#"use cvkg_core::View;
99
100/// Application entry point for the minimal CVKG template.
101///
102/// For a full application, implement the `View` trait and
103/// register it with your platform renderer.
104fn main() {
105 println!("CVKG Minimal Template — Replace with your app.");
106 println!("See https://github.com/sydonayrex/cvkg for documentation.");
107}
108"#
109 }
110 Template::Dashboard => {
111 r#"use cvkg_core::View;
112
113/// Application entry point for the dashboard CVKG template.
114///
115/// For a full application, implement the `View` trait and
116/// register it with your platform renderer.
117fn main() {
118 println!("CVKG Dashboard Template — Replace with your app.");
119 println!("See https://github.com/sydonayrex/cvkg for documentation.");
120}
121"#
122 }
123 Template::AiCopilot => {
124 r#"use cvkg_core::View;
125
126/// Application entry point for the AI Copilot CVKG template.
127///
128/// For a full application, implement the `View` trait and
129/// register it with your platform renderer.
130fn main() {
131 println!("CVKG AI Copilot Template — Replace with your app.");
132 println!("See https://github.com/sydonayrex/cvkg for documentation.");
133}
134"#
135 }
136 };
137 fs::write(root.join("src/main.rs"), content).context("Failed to write src/main.rs")
138 }
139
140 fn gen_theme_rs(&self, root: &Path) -> Result<()> {
141 let content = r#"/// Default Niflheim Theme for CVKG
142pub struct Theme {
143 pub primary: [f32; 4],
144 pub background: [f32; 4],
145}
146
147pub const DEFAULT_THEME: Theme = Theme {
148 primary: [0.0, 1.0, 1.0, 1.0],
149 background: [0.05, 0.05, 0.1, 1.0],
150};
151"#;
152 fs::write(root.join("themes/default.rs"), content)
153 .context("Failed to write themes/default.rs")
154 }
155
156 fn gen_gitignore(&self, root: &Path) -> Result<()> {
157 let content = r#"/target
158**/*.rs.bk
159Cargo.lock
160"#;
161 fs::write(root.join(".gitignore"), content).context("Failed to write .gitignore")
162 }
163
164 fn gen_ci_yml(&self, root: &Path) -> Result<()> {
165 let content = r#"name: CVKG CI
166on:
167 push:
168 branches: [ main ]
169 pull_request:
170 branches: [ main ]
171
172jobs:
173 build:
174 runs-on: ubuntu-latest
175 steps:
176 - uses: actions/checkout@v3
177 - name: Build
178 run: cargo build --verbose
179 - name: Run tests
180 run: cargo test --verbose
181"#;
182 fs::write(root.join(".github/workflows/ci.yml"), content).context("Failed to write ci.yml")
183 }
184}