use std::{
env,
path::{Path, PathBuf},
process::Command,
};
use super::{
error::IcpCommandError,
model::{CANIC_ICP_LOCAL_NETWORK_URL_ENV, CANIC_ICP_LOCAL_ROOT_KEY_ENV, IcpCli, LOCAL_NETWORK},
version::compatible_version_output,
};
impl IcpCli {
#[must_use]
pub fn new(
executable: impl Into<String>,
environment: Option<String>,
network: Option<String>,
) -> Self {
Self {
executable: executable.into(),
environment,
network,
cwd: None,
}
}
#[must_use]
pub fn with_cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
self.cwd = Some(cwd.into());
self
}
#[must_use]
pub fn environment(&self) -> Option<&str> {
self.environment.as_deref()
}
#[must_use]
pub fn network(&self) -> Option<&str> {
self.network.as_deref()
}
#[must_use]
pub fn command(&self) -> Command {
let mut command = Command::new(&self.executable);
if let Some(cwd) = &self.cwd {
command.current_dir(cwd);
add_project_root_override_arg(&mut command, cwd);
}
command
}
#[must_use]
pub fn command_in(&self, cwd: &Path) -> Command {
let mut command = Command::new(&self.executable);
command.current_dir(cwd);
add_project_root_override_arg(&mut command, cwd);
command
}
#[must_use]
pub fn canister_command(&self) -> Command {
let mut command = self.command();
command.arg("canister");
command
}
pub(super) fn add_target_args(&self, command: &mut Command) {
add_target_args(command, self.environment(), self.network());
}
pub(super) fn add_local_network_target(&self, command: &mut Command) {
if let Some(environment) = self.environment() {
command.args(["-e", environment]);
} else if let Some(network) = self.network() {
command.arg(network);
} else {
command.arg(LOCAL_NETWORK);
}
}
}
#[must_use]
pub fn default_command() -> Command {
IcpCli::new("icp", None, None).command()
}
#[must_use]
pub fn default_command_in(cwd: &Path) -> Command {
IcpCli::new("icp", None, None).command_in(cwd)
}
pub fn add_target_args(command: &mut Command, environment: Option<&str>, network: Option<&str>) {
if let Some(environment) = environment {
if environment == LOCAL_NETWORK
&& let Some(url) = env::var_os(CANIC_ICP_LOCAL_NETWORK_URL_ENV)
{
command.env_remove("ICP_ENVIRONMENT");
command.arg("-n").arg(url);
if let Some(root_key) = env::var_os(CANIC_ICP_LOCAL_ROOT_KEY_ENV) {
command.arg("-k").arg(root_key);
}
return;
}
command.args(["-e", environment]);
} else if let Some(network) = network {
command.args(["-n", network]);
}
}
pub fn add_output_arg(command: &mut Command, output: &str) {
if output == "json" {
command.arg("--json");
} else {
command.args(["--output", output]);
}
}
pub fn add_candid_arg(command: &mut Command, candid_path: Option<&Path>) {
if let Some(candid_path) = candid_path {
command.arg("--candid").arg(candid_path);
}
}
#[must_use]
pub fn local_canister_candid_path(icp_root: &Path, environment: &str, role: &str) -> PathBuf {
icp_root
.join(".icp")
.join(environment)
.join("canisters")
.join(role)
.join(format!("{role}.did"))
}
#[must_use]
pub fn existing_local_canister_candid_path(
icp_root: &Path,
environment: &str,
role: &str,
) -> Option<PathBuf> {
let path = local_canister_candid_path(icp_root, environment, role);
path.is_file().then_some(path)
}
pub fn add_debug_arg(command: &mut Command, debug: bool) {
if debug {
command.arg("--debug");
}
}
pub fn ensure_command_compatible(command: &Command) -> Result<(), IcpCommandError> {
let executable = command.get_program().to_string_lossy();
compatible_version_output(executable.as_ref(), command.get_current_dir()).map(|_| ())
}
fn add_project_root_override_arg(command: &mut Command, cwd: &Path) {
command.arg("--project-root-override").arg(cwd);
}
#[must_use]
pub fn command_display(command: &Command) -> String {
let mut parts = vec![command.get_program().to_string_lossy().to_string()];
parts.extend(
command
.get_args()
.map(|arg| arg.to_string_lossy().to_string()),
);
parts.join(" ")
}