use crate::utils::create_command;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::{env, io};
use which::which;
struct StarshipPath {
native_path: PathBuf,
}
impl StarshipPath {
fn init() -> io::Result<Self> {
let exe_name = option_env!("CARGO_PKG_NAME").unwrap_or("starship");
let native_path = which(exe_name).or_else(|_| env::current_exe())?;
Ok(Self { native_path })
}
fn str_path(&self) -> io::Result<&str> {
let current_exe = self
.native_path
.to_str()
.ok_or_else(|| io::Error::other("can't convert to str"))?;
Ok(current_exe)
}
fn sprint(&self) -> io::Result<String> {
self.str_path().map(|p| shell_words::quote(p).into_owned())
}
fn sprint_pwsh(&self) -> io::Result<String> {
self.str_path()
.map(|s| s.replace('\'', "''"))
.map(|s| format!("'{s}'"))
}
fn sprint_elv(&self) -> io::Result<String> {
self.str_path()
.map(|s| format!("e:{s}"))
.map(|s| shell_words::quote(&s).into_owned())
}
fn sprint_cmdexe(&self) -> io::Result<String> {
self.str_path().map(|s| format!("\"{s}\""))
}
fn sprint_posix(&self) -> io::Result<String> {
if cfg!(not(target_os = "windows")) {
return self.sprint();
}
let str_path = self.str_path()?;
let res = create_command("cygpath").and_then(|mut cmd| cmd.arg(str_path).output());
let output = match res {
Ok(output) => output,
Err(e) => {
if e.kind() != io::ErrorKind::NotFound {
log::warn!("Failed to convert \"{str_path}\" to unix path:\n{e:?}");
}
return self.sprint();
}
};
let res = String::from_utf8(output.stdout);
let posix_path = match res {
Ok(ref cygpath_path) if output.status.success() => cygpath_path.trim(),
Ok(_) => {
log::warn!(
"Failed to convert \"{}\" to unix path:\n{}",
str_path,
String::from_utf8_lossy(&output.stderr),
);
str_path
}
Err(e) => {
log::warn!("Failed to convert \"{str_path}\" to unix path:\n{e}");
str_path
}
};
Ok(shell_words::quote(posix_path).into_owned())
}
}
pub fn init_stub(shell_name: &str) -> io::Result<()> {
log::debug!("Shell name: {shell_name}");
let shell_basename = Path::new(shell_name)
.file_stem()
.and_then(OsStr::to_str)
.unwrap_or(shell_name);
let starship = StarshipPath::init()?;
match shell_basename {
"bash" => print!(
r#"eval -- "$({0} init bash --print-full-init)""#,
starship.sprint_posix()?
),
"zsh" => print_script(ZSH_INIT, &starship.sprint_posix()?),
"fish" => print!(
r"source ({} init fish --print-full-init | psub)",
starship.sprint_posix()?
),
"powershell" => print!(
r"Invoke-Expression (& {} init powershell --print-full-init | Out-String)",
starship.sprint_pwsh()?
),
"ion" => print!("eval $({} init ion --print-full-init)", starship.sprint()?),
"elvish" => print!(
r"eval ({} init elvish --print-full-init | slurp)",
starship.sprint_elv()?
),
"tcsh" => print!(
r"eval `({} init tcsh --print-full-init)`",
starship.sprint_posix()?
),
"nu" => print_script(NU_INIT, &StarshipPath::init()?.sprint()?),
"xonsh" => print!(
r"execx($({} init xonsh --print-full-init))",
starship.sprint_posix()?
),
"cmd" => print_script(CMDEXE_INIT, &StarshipPath::init()?.sprint_cmdexe()?),
_ => {
eprintln!(
"{shell_basename} is not yet supported by starship.\n\
For the time being, we support the following shells:\n\
* bash\n\
* elvish\n\
* fish\n\
* ion\n\
* powershell\n\
* tcsh\n\
* zsh\n\
* nu\n\
* xonsh\n\
* cmd\n\
\n\
Please open an issue in the starship repo if you would like to \
see support for {shell_basename}:\n\
https://github.com/starship/starship/issues/new\n"
);
}
};
Ok(())
}
pub fn init_main(shell_name: &str) -> io::Result<()> {
let starship_path = StarshipPath::init()?;
match shell_name {
"bash" => print_script(BASH_INIT, &starship_path.sprint_posix()?),
"zsh" => print_script(ZSH_INIT, &starship_path.sprint_posix()?),
"fish" => print_script(FISH_INIT, &starship_path.sprint_posix()?),
"powershell" => print_script(PWSH_INIT, &starship_path.sprint_pwsh()?),
"ion" => print_script(ION_INIT, &starship_path.sprint()?),
"elvish" => print_script(ELVISH_INIT, &starship_path.sprint_elv()?),
"tcsh" => print_script(TCSH_INIT, &starship_path.sprint_posix()?),
"xonsh" => print_script(XONSH_INIT, &starship_path.sprint_posix()?),
_ => {
println!(
"printf \"Shell name detection failed on phase two init.\\n\
This probably indicates a bug within starship: please open\\n\
an issue at https://github.com/starship/starship/issues/new\\n\""
);
}
}
Ok(())
}
fn print_script(script: &str, path: &str) {
let script = script.replace("::STARSHIP::", path);
print!("{script}");
}
const BASH_INIT: &str = include_str!("starship.bash");
const ZSH_INIT: &str = include_str!("starship.zsh");
const FISH_INIT: &str = include_str!("starship.fish");
const PWSH_INIT: &str = include_str!("starship.ps1");
const ION_INIT: &str = include_str!("starship.ion");
const ELVISH_INIT: &str = include_str!("starship.elv");
const TCSH_INIT: &str = include_str!("starship.tcsh");
const NU_INIT: &str = include_str!("starship.nu");
const XONSH_INIT: &str = include_str!("starship.xsh");
const CMDEXE_INIT: &str = include_str!("starship.lua");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_pwsh() -> io::Result<()> {
let starship_path = StarshipPath {
native_path: PathBuf::from(r"C:\starship.exe"),
};
assert_eq!(starship_path.sprint_pwsh()?, r"'C:\starship.exe'");
Ok(())
}
#[test]
fn escape_tick_pwsh() -> io::Result<()> {
let starship_path = StarshipPath {
native_path: PathBuf::from(r"C:\'starship.exe"),
};
assert_eq!(starship_path.sprint_pwsh()?, r"'C:\''starship.exe'");
Ok(())
}
#[test]
fn escape_cmdexe() -> io::Result<()> {
let starship_path = StarshipPath {
native_path: PathBuf::from(r"C:\starship.exe"),
};
assert_eq!(starship_path.sprint_cmdexe()?, r#""C:\starship.exe""#);
Ok(())
}
#[test]
fn escape_space_cmdexe() -> io::Result<()> {
let starship_path = StarshipPath {
native_path: PathBuf::from(r"C:\Cool Tools\starship.exe"),
};
assert_eq!(
starship_path.sprint_cmdexe()?,
r#""C:\Cool Tools\starship.exe""#
);
Ok(())
}
}