elko/
new.rs

1//! Command `New`
2use crate::examples;
3use crate::utils::Manifest;
4use anyhow::{anyhow, Result};
5use ccli::clap::{self, Parser};
6use colored::*;
7use std::{fs, path::PathBuf};
8
9/// Create a new zink project.
10#[derive(Debug, Parser)]
11pub struct New {
12    path: PathBuf,
13    /// The name of the project.
14    #[clap(short, long, value_name = "name")]
15    name: Option<String>,
16    /// Initialize the project with an example (e.g., "addition", "erc20").
17    #[clap(long, value_name = "example")]
18    example: Option<String>,
19}
20
21impl New {
22    /// Get the project name.
23    pub fn name(&self) -> Result<String> {
24        let name = if let Some(name) = self.name.as_ref() {
25            name.into()
26        } else {
27            self.path
28                .file_name()
29                .and_then(|n| n.to_str())
30                .ok_or_else(|| anyhow!("Invalid project path"))?
31                .into()
32        };
33        Ok(name)
34    }
35
36    /// Create a new zink project.
37    pub fn run(&self) -> Result<()> {
38        fs::create_dir_all(&self.path)?;
39        let name = self.name()?;
40
41        // Select the example based on the --example flag
42        let example = match self.example.as_deref() {
43            Some("erc20") => examples::ERC20,
44            Some("addition") | None => examples::ADDITION,
45            Some(unknown) => return Err(anyhow!("Unknown example: {}", unknown)),
46        };
47
48        // Create the cargo manifest
49        let mut manifest = Manifest::default();
50        manifest.name(&name);
51        fs::write(
52            self.path.join("Cargo.toml"),
53            toml::to_string_pretty(&manifest)?,
54        )?;
55
56        // Create the src directory and lib.rs
57        let src = self.path.join("src");
58        fs::create_dir_all(&src)?;
59        fs::write(
60            src.join("lib.rs"),
61            example.lib_rs.trim_start().replace("${name}", &name),
62        )?;
63
64        // Create README
65        fs::write(
66            self.path.join("README.md"),
67            example.readme.trim_start().replace("${name}", &name),
68        )?;
69
70        println!(
71            "{} zink project `{}` with example `{}`",
72            "Created".green().bold(),
73            name,
74            self.example.as_deref().unwrap_or("addition")
75        );
76        Ok(())
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use std::path::Path;
84
85    #[test]
86    fn test_new_addition() -> Result<()> {
87        let tmp = tempfile::tempdir()?;
88        let new = New {
89            path: tmp.path().join("myproj"),
90            name: Some("myproj".to_string()),
91            example: None,
92        };
93        new.run()?;
94        assert!(Path::new(&tmp.path().join("myproj/src/lib.rs")).exists());
95        let lib_rs = fs::read_to_string(tmp.path().join("myproj/src/lib.rs"))?;
96        assert!(lib_rs.contains("addition(x: u64, y: u64)"));
97        Ok(())
98    }
99
100    #[test]
101    fn test_new_erc20() -> Result<()> {
102        let tmp = tempfile::tempdir()?;
103        let new = New {
104            path: tmp.path().join("mytoken"),
105            name: Some("mytoken".to_string()),
106            example: Some("erc20".to_string()),
107        };
108        new.run()?;
109        assert!(Path::new(&tmp.path().join("mytoken/src/lib.rs")).exists());
110        let lib_rs = fs::read_to_string(tmp.path().join("mytoken/src/lib.rs"))?;
111        assert!(lib_rs.contains("transfer(to: Address, value: U256)"));
112        assert!(lib_rs.contains("approve(spender: Address, value: U256)"));
113        Ok(())
114    }
115
116    #[test]
117    fn test_new_invalid_example() {
118        let tmp = tempfile::tempdir().unwrap();
119        let new = New {
120            path: tmp.path().join("myproj"),
121            name: Some("myproj".to_string()),
122            example: Some("invalid".to_string()),
123        };
124        assert!(new.run().is_err());
125    }
126}