Skip to main content

concinnity_dev/command/
new.rs

1// src/cli/new.rs
2
3use concinnity_cook::authoring::world::WORLD_JSONL;
4use concinnity_cook::build_from_path;
5
6// Default starter world file. Everything else a running world needs (window,
7// renderer, debug HUD) is injected at build time and recorded in
8// world-lock.json; `cn list --expanded` shows the effective world.
9//
10// The label names no Font, so it draws with the engine's built-in face. It asks
11// for `centered` itself rather than leaning on a default: unset, the greeting
12// lands at the label's default x/y, under the HUD chips in the top-left corner.
13const INIT_WORLD_JSONL: &str = r#"{"name":"hello_world","type":"TextLabel","args":{"content":"Hello, world!","centered":true}}
14"#;
15
16/// Create a new project in a new directory at `path`.
17pub fn new(path: &str) -> std::io::Result<()> {
18    if std::path::Path::new(path).exists() {
19        // allow creating a project in a pre-existing empty directory,
20        // but refuse if it already has a world.jsonl
21        let world = std::path::Path::new(path).join(WORLD_JSONL);
22        if world.exists() {
23            return Err(std::io::Error::new(
24                std::io::ErrorKind::AlreadyExists,
25                format!("'{}' already contains a {}", path, WORLD_JSONL),
26            ));
27        }
28    }
29    std::fs::create_dir_all(path)?;
30    println!("Created directory '{}'", path);
31    init_in_dir(path)
32}
33
34/// Create a new project in the working directory.
35pub fn init() -> std::io::Result<()> {
36    init_in_dir(".")
37}
38
39// Write the starter world.jsonl into `dir` and run an initial build
40fn init_in_dir(dir: &str) -> std::io::Result<()> {
41    let world_path = std::path::Path::new(dir).join(WORLD_JSONL);
42
43    if world_path.exists() {
44        println!("{} already exists, skipping init", world_path.display());
45        return Ok(());
46    }
47
48    std::fs::write(&world_path, INIT_WORLD_JSONL)?;
49    println!("Created {}", world_path.display());
50
51    let world_path_str = world_path.to_str().unwrap_or(WORLD_JSONL);
52    build_from_path(world_path_str, crate::cook_platform())
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    // Only the paths that stop before the initial build are exercised here;
60    // a successful `cn new` runs the full compile pipeline.
61
62    #[test]
63    fn new_refuses_a_directory_that_already_has_a_world() {
64        let dir = tempfile::tempdir().unwrap();
65        std::fs::write(dir.path().join(WORLD_JSONL), "").unwrap();
66
67        let err = new(dir.path().to_str().unwrap()).unwrap_err();
68        assert_eq!(err.kind(), std::io::ErrorKind::AlreadyExists);
69        assert!(err.to_string().contains(WORLD_JSONL), "got: {err}");
70    }
71
72    #[test]
73    fn init_in_dir_skips_when_a_world_exists() {
74        let dir = tempfile::tempdir().unwrap();
75        let world = dir.path().join(WORLD_JSONL);
76        std::fs::write(
77            &world,
78            "{\"name\":\"keep\",\"type\":\"Logger\",\"args\":{}}\n",
79        )
80        .unwrap();
81
82        init_in_dir(dir.path().to_str().unwrap()).unwrap();
83        // The existing world is untouched, not overwritten by the starter.
84        let content = std::fs::read_to_string(&world).unwrap();
85        assert!(content.contains("\"keep\""), "got: {content}");
86    }
87}