feat: Add support for cygwin/msys2/git-bash evironment (#2020)

* feat: Add support for cygwin/msys2/git-bash evironment

* Update src/init/mod.rs

Co-authored-by: David Knaack <davidkna@users.noreply.github.com>

Co-authored-by: Thomas O'Donnell <andytom@users.noreply.github.com>
Co-authored-by: David Knaack <davidkna@users.noreply.github.com>
This commit is contained in:
Zoritle 2021-01-08 02:13:57 +08:00 committed by GitHub
parent f03c3f1de9
commit 851cf22caa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 82 additions and 30 deletions

View File

@ -1,5 +1,5 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::Path; use std::path::{Path, PathBuf};
use std::{env, io}; use std::{env, io};
/* We use a two-phase init here: the first phase gives a simple command to the /* We use a two-phase init here: the first phase gives a simple command to the
@ -17,12 +17,62 @@ using whatever mechanism is available in the host shell--this two-phase solution
has been developed as a compatibility measure with `eval $(starship init X)` has been developed as a compatibility measure with `eval $(starship init X)`
*/ */
fn path_to_starship() -> io::Result<String> { struct StarshipPath {
let current_exe = env::current_exe()? native_path: PathBuf,
.to_str() }
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "can't convert to str"))? impl StarshipPath {
.to_string(); fn init() -> io::Result<Self> {
Ok(current_exe) Ok(Self {
native_path: env::current_exe()?,
})
}
fn str_path(&self) -> io::Result<&str> {
let current_exe = self
.native_path
.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "can't convert to str"))?;
Ok(current_exe)
}
fn sprint(&self) -> io::Result<String> {
self.str_path().map(|s| s.replace("\"", "\"'\"'\""))
}
fn sprint_posix(&self) -> io::Result<String> {
// On non-Windows platform, return directly.
if cfg!(not(target_os = "windows")) {
return self.sprint();
}
let str_path = self.str_path()?;
let res = std::process::Command::new("cygpath.exe")
.arg(str_path)
.output();
let output = match res {
Ok(output) => output,
Err(e) => {
if e.kind() != io::ErrorKind::NotFound {
log::warn!("Failed to convert \"{}\" to unix path:\n{:?}", str_path, e);
}
// Failed to execute cygpath.exe means there're not inside cygwin evironment,return directly.
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 \"{}\" to unix path:\n{}", str_path, e);
str_path
}
};
Ok(posix_path.replace("\"", "\"'\"'\""))
}
} }
/* This prints the setup stub, the short piece of code which sets up the main /* This prints the setup stub, the short piece of code which sets up the main
@ -33,7 +83,7 @@ pub fn init_stub(shell_name: &str) -> io::Result<()> {
let shell_basename = Path::new(shell_name).file_stem().and_then(OsStr::to_str); let shell_basename = Path::new(shell_name).file_stem().and_then(OsStr::to_str);
let starship = path_to_starship()?.replace("\"", "\"'\"'\""); let starship = StarshipPath::init()?;
let setup_stub = match shell_basename { let setup_stub = match shell_basename {
Some("bash") => { Some("bash") => {
@ -72,25 +122,28 @@ pub fn init_stub(shell_name: &str) -> io::Result<()> {
format!( format!(
r#"if [ "${{BASH_VERSINFO[0]}}" -gt 4 ] || ([ "${{BASH_VERSINFO[0]}}" -eq 4 ] && [ "${{BASH_VERSINFO[1]}}" -ge 1 ]) r#"if [ "${{BASH_VERSINFO[0]}}" -gt 4 ] || ([ "${{BASH_VERSINFO[0]}}" -eq 4 ] && [ "${{BASH_VERSINFO[1]}}" -ge 1 ])
then then
source <("{}" init bash --print-full-init) source <("{0}" init bash --print-full-init)
else else
source /dev/stdin <<<"$("{}" init bash --print-full-init)" source /dev/stdin <<<"$("{0}" init bash --print-full-init)"
fi"#, fi"#,
starship, starship starship.sprint_posix()?
) )
}; };
Some(script) Some(script)
} }
Some("zsh") => { Some("zsh") => {
let script = format!("source <(\"{}\" init zsh --print-full-init)", starship); let script = format!(
"source <(\"{}\" init zsh --print-full-init)",
starship.sprint_posix()?
);
Some(script) Some(script)
} }
Some("fish") => { Some("fish") => {
// Fish does process substitution with pipes and psub instead of bash syntax // Fish does process substitution with pipes and psub instead of bash syntax
let script = format!( let script = format!(
"source (\"{}\" init fish --print-full-init | psub)", "source (\"{}\" init fish --print-full-init | psub)",
starship starship.sprint_posix()?
); );
Some(script) Some(script)
} }
@ -105,12 +158,12 @@ fi"#,
// Powershell escapes with ` instead of \ thus `n translates to a newline. // Powershell escapes with ` instead of \ thus `n translates to a newline.
let script = format!( let script = format!(
"Invoke-Expression (@(&\"{}\" init powershell --print-full-init) -join \"`n\")", "Invoke-Expression (@(&\"{}\" init powershell --print-full-init) -join \"`n\")",
starship starship.sprint()?
); );
Some(script) Some(script)
} }
Some("ion") => { Some("ion") => {
let script = format!("eval $({} init ion --print-full-init)", starship); let script = format!("eval $({} init ion --print-full-init)", starship.sprint()?);
Some(script) Some(script)
} }
None => { None => {
@ -143,32 +196,31 @@ fi"#,
/* This function (called when `--print-full-init` is passed to `starship init`) /* This function (called when `--print-full-init` is passed to `starship init`)
prints out the main initialization script */ prints out the main initialization script */
pub fn init_main(shell_name: &str) -> io::Result<()> { pub fn init_main(shell_name: &str) -> io::Result<()> {
let starship_path = path_to_starship()?.replace("\"", "\"'\"'\""); let starship_path = StarshipPath::init()?;
let setup_script = match shell_name { match shell_name {
"bash" => Some(BASH_INIT), "bash" => print_script(BASH_INIT, &starship_path.sprint_posix()?),
"zsh" => Some(ZSH_INIT), "zsh" => print_script(ZSH_INIT, &starship_path.sprint_posix()?),
"fish" => Some(FISH_INIT), "fish" => print_script(FISH_INIT, &starship_path.sprint_posix()?),
"powershell" => Some(PWSH_INIT), "powershell" => print_script(PWSH_INIT, &starship_path.sprint()?),
"ion" => Some(ION_INIT), "ion" => print_script(ION_INIT, &starship_path.sprint()?),
_ => { _ => {
println!( println!(
"printf \"Shell name detection failed on phase two init.\\n\ "printf \"Shell name detection failed on phase two init.\\n\
This probably indicates a bug within starship: please open\\n\ This probably indicates a bug within starship: please open\\n\
an issue at https://github.com/starship/starship/issues/new\\n\"" an issue at https://github.com/starship/starship/issues/new\\n\""
); );
None
} }
}; }
if let Some(script) = setup_script {
// Set up quoting for starship path in case it has spaces.
let starship_path_string = format!("\"{}\"", starship_path);
let script = script.replace("::STARSHIP::", &starship_path_string);
print!("{}", script);
};
Ok(()) Ok(())
} }
fn print_script(script: &str, path: &str) {
let starship_path_string = format!("\"{}\"", path);
let script = script.replace("::STARSHIP::", &starship_path_string);
print!("{}", script);
}
/* GENERAL INIT SCRIPT NOTES /* GENERAL INIT SCRIPT NOTES
Each init script will be passed as-is. Global notes for init scripts are in this Each init script will be passed as-is. Global notes for init scripts are in this