canic_host/icp/
command.rs1use std::{
2 env,
3 path::{Path, PathBuf},
4 process::Command,
5};
6
7use super::{
8 error::IcpCommandError,
9 model::{CANIC_ICP_LOCAL_NETWORK_URL_ENV, CANIC_ICP_LOCAL_ROOT_KEY_ENV, IcpCli, LOCAL_NETWORK},
10 version::compatible_version_output,
11};
12
13impl IcpCli {
14 #[must_use]
16 pub fn new(
17 executable: impl Into<String>,
18 environment: Option<String>,
19 network: Option<String>,
20 ) -> Self {
21 Self {
22 executable: executable.into(),
23 environment,
24 network,
25 cwd: None,
26 }
27 }
28
29 #[must_use]
31 pub fn with_cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
32 self.cwd = Some(cwd.into());
33 self
34 }
35
36 #[must_use]
38 pub fn environment(&self) -> Option<&str> {
39 self.environment.as_deref()
40 }
41
42 #[must_use]
44 pub fn network(&self) -> Option<&str> {
45 self.network.as_deref()
46 }
47
48 #[must_use]
50 pub fn command(&self) -> Command {
51 let mut command = Command::new(&self.executable);
52 if let Some(cwd) = &self.cwd {
53 command.current_dir(cwd);
54 add_project_root_override_arg(&mut command, cwd);
55 }
56 command
57 }
58
59 #[must_use]
61 pub fn command_in(&self, cwd: &Path) -> Command {
62 let mut command = Command::new(&self.executable);
63 command.current_dir(cwd);
64 add_project_root_override_arg(&mut command, cwd);
65 command
66 }
67
68 #[must_use]
70 pub fn canister_command(&self) -> Command {
71 let mut command = self.command();
72 command.arg("canister");
73 command
74 }
75
76 pub(super) fn add_target_args(&self, command: &mut Command) {
77 add_target_args(command, self.environment(), self.network());
78 }
79
80 pub(super) fn add_local_network_target(&self, command: &mut Command) {
81 if let Some(environment) = self.environment() {
82 command.args(["-e", environment]);
83 } else if let Some(network) = self.network() {
84 command.arg(network);
85 } else {
86 command.arg(LOCAL_NETWORK);
87 }
88 }
89}
90
91#[must_use]
93pub fn default_command() -> Command {
94 IcpCli::new("icp", None, None).command()
95}
96
97#[must_use]
99pub fn default_command_in(cwd: &Path) -> Command {
100 IcpCli::new("icp", None, None).command_in(cwd)
101}
102
103pub fn add_target_args(command: &mut Command, environment: Option<&str>, network: Option<&str>) {
105 if let Some(environment) = environment {
106 if environment == LOCAL_NETWORK
107 && let Some(url) = env::var_os(CANIC_ICP_LOCAL_NETWORK_URL_ENV)
108 {
109 command.env_remove("ICP_ENVIRONMENT");
110 command.arg("-n").arg(url);
111 if let Some(root_key) = env::var_os(CANIC_ICP_LOCAL_ROOT_KEY_ENV) {
112 command.arg("-k").arg(root_key);
113 }
114 return;
115 }
116 command.args(["-e", environment]);
117 } else if let Some(network) = network {
118 command.args(["-n", network]);
119 }
120}
121
122pub fn add_output_arg(command: &mut Command, output: &str) {
124 if output == "json" {
125 command.arg("--json");
126 } else {
127 command.args(["--output", output]);
128 }
129}
130
131pub fn add_candid_arg(command: &mut Command, candid_path: Option<&Path>) {
133 if let Some(candid_path) = candid_path {
134 command.arg("--candid").arg(candid_path);
135 }
136}
137
138#[must_use]
140pub fn local_canister_candid_path(icp_root: &Path, environment: &str, role: &str) -> PathBuf {
141 icp_root
142 .join(".icp")
143 .join(environment)
144 .join("canisters")
145 .join(role)
146 .join(format!("{role}.did"))
147}
148
149#[must_use]
151pub fn existing_local_canister_candid_path(
152 icp_root: &Path,
153 environment: &str,
154 role: &str,
155) -> Option<PathBuf> {
156 let path = local_canister_candid_path(icp_root, environment, role);
157 path.is_file().then_some(path)
158}
159
160pub fn add_debug_arg(command: &mut Command, debug: bool) {
162 if debug {
163 command.arg("--debug");
164 }
165}
166
167pub fn ensure_command_compatible(command: &Command) -> Result<(), IcpCommandError> {
169 let executable = command.get_program().to_string_lossy();
170 compatible_version_output(executable.as_ref(), command.get_current_dir()).map(|_| ())
171}
172
173fn add_project_root_override_arg(command: &mut Command, cwd: &Path) {
174 command.arg("--project-root-override").arg(cwd);
175}
176
177#[must_use]
179pub fn command_display(command: &Command) -> String {
180 let mut parts = vec![command.get_program().to_string_lossy().to_string()];
181 parts.extend(
182 command
183 .get_args()
184 .map(|arg| arg.to_string_lossy().to_string()),
185 );
186 parts.join(" ")
187}