cargo_openvm/commands/
init.rs1use 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 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_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 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}