starbase_shell/shells/
ion.rs1use 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 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 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 Syntax::Pair("{".into(), "}".into()),
44 ],
45 ..Default::default()
46 },
47 )
48 }
49
50 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 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}