cargo_openvm/commands/
init.rs

1use std::{
2    fs::{read_to_string, write},
3    path::{Path, PathBuf},
4    process::Command,
5};
6
7use clap::Parser;
8use eyre::Result;
9use include_dir::{include_dir, Dir};
10use toml_edit::{DocumentMut, Item, Value};
11
12static TEMPLATES: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
13
14#[derive(Parser)]
15#[command(
16    name = "init",
17    about = "Create an OpenVM package in an existing directory"
18)]
19pub struct InitCmd {
20    #[arg(
21        index = 1,
22        help = "Path to create the package in",
23        help_heading = "Arguments"
24    )]
25    pub path: Option<PathBuf>,
26
27    #[arg(
28        long,
29        help = "Create a package with a binary target (src/main.rs)",
30        help_heading = "Init Options"
31    )]
32    pub bin: bool,
33
34    #[arg(
35        long,
36        help = "Create a package with a library target (src/lib.rs)",
37        help_heading = "Init Options"
38    )]
39    pub lib: bool,
40
41    #[arg(
42        long,
43        default_value = "2021",
44        help = "Specify the Rust edition to use (2015, 2018, 2021, or 2024)",
45        help_heading = "Init Options"
46    )]
47    pub edition: String,
48
49    #[arg(
50        long,
51        help = "Set the package name, default is the directory name",
52        help_heading = "Init Options"
53    )]
54    pub name: Option<String>,
55
56    #[arg(
57        long,
58        default_value = "git",
59        help = "Initialize a new VCS repository for the given version control system (git, hg, pijul, fossil, or none)",
60        help_heading = "Init Options"
61    )]
62    pub vcs: String,
63
64    #[arg(
65        long,
66        short = 'v',
67        help = "Use verbose output",
68        help_heading = "Display Options"
69    )]
70    pub verbose: bool,
71
72    #[arg(
73        long,
74        short = 'q',
75        help = "Do not print cargo log messages",
76        help_heading = "Display Options"
77    )]
78    pub quiet: bool,
79
80    #[arg(
81        long,
82        value_name = "WHEN",
83        default_value = "always",
84        help = "Control when colored output is used",
85        help_heading = "Display Options"
86    )]
87    pub color: String,
88}
89
90impl InitCmd {
91    pub fn run(&self) -> Result<()> {
92        let mut args = vec!["init"];
93        args.extend_from_slice(&["--edition", &self.edition]);
94        args.extend_from_slice(&["--vcs", &self.vcs]);
95        args.extend_from_slice(&["--color", &self.color]);
96        if let Some(name) = &self.name {
97            args.extend_from_slice(&["--name", name]);
98        }
99
100        let boolean_flags = [
101            ("--bin", self.bin),
102            ("--lib", self.lib),
103            ("--verbose", self.verbose),
104            ("--quiet", self.quiet),
105        ];
106        for (flag, enabled) in boolean_flags {
107            if enabled {
108                args.push(flag);
109            }
110        }
111
112        let path = self
113            .path
114            .clone()
115            .unwrap_or(PathBuf::from(".").canonicalize()?);
116        args.push(path.to_str().unwrap());
117
118        let mut cmd = Command::new("cargo");
119        cmd.args(&args);
120
121        let status = cmd.status()?;
122        if !status.success() {
123            return Err(eyre::eyre!("cargo init failed with status: {}", status));
124        }
125
126        // Add openvm dependency to Cargo.toml, then write template main.rs or lib.rs
127        if self.lib {
128            add_openvm_dependency(&path, &[])?;
129            write_template_file("lib.rs", &path.join("src"))?;
130        } else {
131            add_openvm_dependency(&path, &["std"])?;
132            write_template_file("main.rs", &path.join("src"))?;
133        }
134
135        // Write template openvm.toml
136        write_template_file("openvm.toml", &path)?;
137
138        Ok(())
139    }
140}
141
142fn add_openvm_dependency(path: &Path, features: &[&str]) -> Result<()> {
143    let cargo_toml_path = path.join("Cargo.toml");
144    let cargo_toml_content = read_to_string(&cargo_toml_path)?;
145    let mut doc = cargo_toml_content.parse::<DocumentMut>()?;
146    let mut openvm_table = toml_edit::InlineTable::new();
147    let mut openvm_features = toml_edit::Array::new();
148    for feature in features {
149        openvm_features.push(Value::from(feature.to_string()));
150    }
151    openvm_table.insert(
152        "git",
153        Value::from("https://github.com/openvm-org/openvm.git"),
154    );
155
156    // Add version tag
157    let version_tag = format!("v{}", env!("CARGO_PKG_VERSION"));
158    openvm_table.insert("tag", Value::from(version_tag));
159
160    openvm_table.insert("features", Value::Array(openvm_features));
161    doc["dependencies"]["openvm"] = Item::Value(toml_edit::Value::InlineTable(openvm_table));
162    write(cargo_toml_path, doc.to_string())?;
163    Ok(())
164}
165
166fn write_template_file(file_name: &str, dest_dir: &Path) -> Result<()> {
167    let file = TEMPLATES
168        .get_file(file_name)
169        .ok_or_else(|| eyre::eyre!("Template not found: {}", file_name))?;
170    write(dest_dir.join(file_name), file.contents())?;
171    Ok(())
172}