Skip to main content

starbase_shell/shells/
ion.rs

1use super::Shell;
2use crate::helpers::{ProfileSet, get_config_dir, get_env_var_regex};
3use crate::hooks::*;
4use crate::quoter::*;
5use shell_quote::Quotable;
6use std::fmt;
7use std::path::{Path, PathBuf};
8
9#[derive(Clone, Copy, Debug)]
10pub struct Ion;
11
12impl Ion {
13    #[allow(clippy::new_without_default)]
14    pub fn new() -> Self {
15        Self
16    }
17
18    // $FOO -> ${env::FOO}
19    fn replace_env(&self, value: impl AsRef<str>) -> String {
20        get_env_var_regex()
21            .replace_all(value.as_ref(), "$${env::$name}")
22            .to_string()
23    }
24}
25
26impl Shell for Ion {
27    fn create_quoter<'a>(&self, data: Quotable<'a>) -> Quoter<'a> {
28        Quoter::new(
29            data,
30            QuoterOptions {
31                // https://github.com/redox-os/ion/blob/master/src/lib/expansion/methods/strings.rs
32                // https://doc.redox-os.org/ion-manual/expansions/00-expansions.html
33                quoted_syntax: vec![
34                    Syntax::Symbol("$".into()),
35                    Syntax::Pair("${".into(), "}".into()),
36                    Syntax::Pair("$(".into(), ")".into()),
37                    Syntax::Symbol("@".into()),
38                    Syntax::Pair("@{".into(), "}".into()),
39                    Syntax::Pair("@(".into(), ")".into()),
40                ],
41                unquoted_syntax: vec![
42                    // brace
43                    Syntax::Pair("{".into(), "}".into()),
44                ],
45                ..Default::default()
46            },
47        )
48    }
49
50    // https://doc.redox-os.org/ion-manual/variables/05-exporting.html
51    fn format(&self, statement: Statement<'_>) -> String {
52        match statement {
53            Statement::ModifyPath {
54                paths,
55                key,
56                orig_key,
57            } => {
58                let key = key.unwrap_or("PATH");
59                let value = self.replace_env(paths.join(":"));
60
61                match orig_key {
62                    Some(orig_key) => format!(r#"export {key} = "{value}:${{env::{orig_key}}}""#,),
63                    None => format!(r#"export {key} = "{value}""#,),
64                }
65            }
66            Statement::SetEnv { key, value } => {
67                format!(
68                    "export {}={}",
69                    self.quote(key),
70                    self.quote(self.replace_env(value).as_str())
71                )
72            }
73            Statement::UnsetEnv { key } => {
74                format!("drop {}", self.quote(key))
75            }
76        }
77    }
78
79    fn get_config_path(&self, home_dir: &Path) -> PathBuf {
80        get_config_dir(home_dir).join("ion").join("initrc")
81    }
82
83    fn get_env_path(&self, home_dir: &Path) -> PathBuf {
84        self.get_config_path(home_dir)
85    }
86
87    fn get_env_regex(&self) -> regex::Regex {
88        regex::Regex::new(r"\$\{env::(?<name>[A-Za-z0-9_]+)\}").unwrap()
89    }
90
91    // https://doc.redox-os.org/ion-manual/general.html#xdg-app-dirs-support
92    fn get_profile_paths(&self, home_dir: &Path) -> Vec<PathBuf> {
93        ProfileSet::default()
94            .insert(get_config_dir(home_dir).join("ion").join("initrc"), 1)
95            .insert(home_dir.join(".config").join("ion").join("initrc"), 2)
96            .into_list()
97    }
98}
99
100impl fmt::Display for Ion {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        write!(f, "ion")
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn formats_env_var() {
112        assert_eq!(
113            Ion.format_env_set("PROTO_HOME", "$HOME/.proto"),
114            r#"export PROTO_HOME="${env::HOME}/.proto""#
115        );
116    }
117
118    #[test]
119    fn formats_path_prepend() {
120        assert_eq!(
121            Ion.format_path_prepend(&["$PROTO_HOME/shims".into(), "$PROTO_HOME/bin".into()]),
122            r#"export PATH = "${env::PROTO_HOME}/shims:${env::PROTO_HOME}/bin:${env::PATH}""#
123        );
124    }
125
126    #[test]
127    fn formats_path_set() {
128        assert_eq!(
129            Ion.format_path_set(&["$PROTO_HOME/shims".into(), "$PROTO_HOME/bin".into()]),
130            r#"export PATH = "${env::PROTO_HOME}/shims:${env::PROTO_HOME}/bin""#
131        );
132    }
133
134    #[test]
135    fn test_profile_paths() {
136        #[allow(deprecated)]
137        let home_dir = std::env::home_dir().unwrap();
138
139        assert_eq!(
140            Ion::new().get_profile_paths(&home_dir),
141            vec![home_dir.join(".config").join("ion").join("initrc")]
142        );
143    }
144
145    #[test]
146    fn test_ion_quoting() {
147        assert_eq!(Ion.quote("simplevalue"), "simplevalue");
148        assert_eq!(Ion.quote("value with spaces"), r#"'value with spaces'"#);
149        assert_eq!(
150            Ion.quote(r#"value "with" quotes"#),
151            r#""value \"with\" quotes""#
152        );
153        assert_eq!(Ion.quote("$variable"), "\"$variable\"");
154        assert_eq!(Ion.quote("{brace_expansion}"), "{brace_expansion}");
155        assert_eq!(
156            Ion.quote("value with 'single quotes'"),
157            r#"'value with 'single quotes''"#
158        );
159    }
160}