use std::borrow::Cow;
use std::path::Path;
use crate::shell::{is_dir_in_path, is_dir_not_in_nix, Shell};
#[derive(Default)]
pub struct Xonsh {}
fn xonsh_escape_sq(input: &str) -> Cow<str> {
for (i, ch) in input.chars().enumerate() {
if xonsh_escape_char(ch).is_some() {
let mut escaped_string = String::with_capacity(input.len());
escaped_string.push_str(&input[..i]);
for ch in input[i..].chars() {
match xonsh_escape_char(ch) {
Some(escaped_char) => escaped_string.push_str(escaped_char),
None => escaped_string.push(ch),
};
}
return Cow::Owned(escaped_string);
}
}
Cow::Borrowed(input)
}
fn xonsh_escape_char(ch: char) -> Option<&'static str> {
match ch {
'\'' => Some("\\'"),
'\\' => Some("\\\\"),
'\n' => Some("\\n"),
_ => None,
}
}
impl Shell for Xonsh {
fn activate(&self, exe: &Path, flags: String) -> String {
let dir = exe.parent().unwrap();
let exe = exe.display();
let mut out = String::new();
out.push_str(&formatdoc! {r#"
from os import environ
from xonsh.built_ins import XSH
"#});
if is_dir_not_in_nix(dir) && !is_dir_in_path(dir) {
let dir_str = dir.to_string_lossy();
let dir_esc = xonsh_escape_sq(&dir_str);
out.push_str(&formatdoc! {r#"
envx = XSH.env
envx['PATH'].add('{dir_esc}')
environ['PATH'] = envx.get_detyped('PATH')
"#});
}
out.push_str(&formatdoc! {r#"
def listen_prompt(): # Hook Events
execx($({exe} hook-env{flags} -s xonsh))
XSH.builtins.events.on_pre_prompt(listen_prompt) # Activate hook: before showing the prompt
"#});
out
}
fn deactivate(&self) -> String {
formatdoc! {r#"
from xonsh.built_ins import XSH
hooks = {{
'on_pre_prompt' : ['listen_prompt'],
}}
for hook_type in hooks:
hook_fns = hooks[hook_type]
for hook_fn in hook_fns:
hndl = getattr(XSH.builtins.events, hook_type)
for fn in hndl:
if fn.__name__ == hook_fn:
hndl.remove(fn)
break
"#}
}
fn set_env(&self, k: &str, v: &str) -> String {
let k = shell_escape::unix::escape(k.into()); formatdoc!(
r#"
from os import environ
from xonsh.built_ins import XSH
envx = XSH.env
envx[ '{k}'] = '{v}'
environ['{k}'] = envx.get_detyped('{k}')
"#,
k = shell_escape::unix::escape(k), v = xonsh_escape_sq(v)
)
}
fn unset_env(&self, k: &str) -> String {
formatdoc!(
r#"
from os import environ
from xonsh.built_ins import XSH
envx = XSH.env
envx.pop( '{k}',None)
environ.pop('{k}',None)
"#,
k = shell_escape::unix::escape(k.into()) )
}
}
#[cfg(test)]
mod tests {
use crate::test::replace_path;
use super::*;
#[test]
fn test_hook_init() {
let xonsh = Xonsh::default();
let exe = Path::new("/some/dir/rtx");
insta::assert_snapshot!(xonsh.activate(exe, " --status".into()));
}
#[test]
fn test_hook_init_nix() {
let xonsh = Xonsh::default();
let exe = Path::new("/nix/store/rtx");
insta::assert_snapshot!(xonsh.activate(exe, " --status".into()));
}
#[test]
fn test_set_env() {
insta::assert_snapshot!(Xonsh::default().set_env("FOO", "1"));
}
#[test]
fn test_unset_env() {
insta::assert_snapshot!(Xonsh::default().unset_env("FOO"));
}
#[test]
fn test_xonsh_escape_sq() {
assert_eq!(xonsh_escape_sq("foo"), "foo");
assert_eq!(xonsh_escape_sq("foo'bar"), "foo\\'bar");
assert_eq!(xonsh_escape_sq("foo\\bar"), "foo\\\\bar");
assert_eq!(xonsh_escape_sq("foo\nbar"), "foo\\nbar");
}
#[test]
fn test_xonsh_deactivate() {
let deactivate = Xonsh::default().deactivate();
assert_snapshot!(replace_path(&deactivate));
}
}