use anyhow::{Result, bail};
use std::ffi::OsString;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use crate::cli::app::AppOpenArgs;
use super::launch_plan::{
append_open_launch_target_args, default_installed_app_path, env_assignment_for_path,
resolve_launch_target,
};
use super::model::OpenCommandSpec;
pub(super) fn plan_url_command(
workspace: &Path,
args: &AppOpenArgs,
cli_executable: Option<&Path>,
) -> Result<OpenCommandSpec> {
plan_url_command_with_installed_path(
workspace,
args,
cli_executable,
default_installed_app_path(),
)
}
pub(super) fn plan_url_command_with_installed_path(
workspace: &Path,
args: &AppOpenArgs,
cli_executable: Option<&Path>,
installed_app_path: Option<PathBuf>,
) -> Result<OpenCommandSpec> {
let encoded_path = percent_encode_path(workspace);
let url = format!("ralph://open?workspace={}", encoded_path);
let launch_target = resolve_launch_target(args, installed_app_path)?;
let mut args_out: Vec<OsString> = Vec::new();
if let Some(cli_executable) = cli_executable {
args_out.push(OsString::from("--env"));
args_out.push(env_assignment_for_path(cli_executable));
}
append_open_launch_target_args(&mut args_out, &launch_target);
args_out.push(OsString::from(url));
Ok(OpenCommandSpec {
program: OsString::from("open"),
args: args_out,
})
}
pub(super) fn resolve_workspace_path(args: &AppOpenArgs) -> Result<Option<PathBuf>> {
if let Some(ref workspace) = args.workspace {
if !workspace.exists() {
bail!("Workspace path does not exist: {}", workspace.display());
}
return Ok(Some(workspace.clone()));
}
Ok(std::env::current_dir().ok().filter(|path| path.exists()))
}
#[cfg(unix)]
pub(super) fn percent_encode_path(path: &Path) -> String {
percent_encode(path.as_os_str().as_bytes())
}
#[cfg(not(unix))]
pub(super) fn percent_encode_path(path: &Path) -> String {
percent_encode(path.to_string_lossy().as_bytes())
}
pub(super) fn percent_encode(input: &[u8]) -> String {
let mut result = String::with_capacity(input.len() * 3);
for &byte in input {
if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'~' | b'/') {
result.push(byte as char);
} else {
result.push('%');
result.push_str(&format!("{:02X}", byte));
}
}
result
}