starbase_shell/shells/
ion.rs1use super::Shell;
2use crate::helpers::{ProfileSet, get_config_dir, get_env_var_regex, quotable_into_string};
3use crate::hooks::*;
4use crate::quoter::*;
5use shell_quote::Quotable;
6use std::fmt;
7use std::path::{Path, PathBuf};
8use std::sync::Arc;
9
10#[derive(Clone, Copy, Debug)]
11pub struct Ion;
12
13impl Ion {
14 #[allow(clippy::new_without_default)]
15 pub fn new() -> Self {
16 Self
17 }
18
19 fn do_quote(value: String) -> String {
22 if value.starts_with('$') {
23 format!("\"{value}\"")
25 } else if value.contains('{') || value.contains('}') {
26 format!("'{value}'")
28 } else if value.chars().all(|c| {
29 c.is_ascii_graphic() && !c.is_whitespace() && c != '"' && c != '\'' && c != '\\'
30 }) {
31 value.to_string()
33 } else {
34 format!("\"{}\"", value.replace('"', "\\\""))
36 }
37 }
38
39 fn replace_env(&self, value: impl AsRef<str>) -> String {
41 get_env_var_regex()
42 .replace_all(value.as_ref(), "$${env::$name}")
43 .to_string()
44 }
45}
46
47impl Shell for Ion {
48 fn create_quoter<'a>(&self, data: Quotable<'a>) -> Quoter<'a> {
49 Quoter::new(
50 data,
51 QuoterOptions {
52 quoted_syntax: vec![
54 Syntax::Symbol("$".into()),
55 Syntax::Pair("${".into(), "}".into()),
56 Syntax::Pair("$(".into(), ")".into()),
57 Syntax::Symbol("@".into()),
58 Syntax::Pair("@{".into(), "}".into()),
59 Syntax::Pair("@(".into(), ")".into()),
60 ],
61 on_quote: Arc::new(|data| Ion::do_quote(quotable_into_string(data))),
62 ..Default::default()
63 },
64 )
65 }
66
67 fn format(&self, statement: Statement<'_>) -> String {
69 match statement {
70 Statement::ModifyPath {
71 paths,
72 key,
73 orig_key,
74 } => {
75 let key = key.unwrap_or("PATH");
76 let value = self.replace_env(paths.join(":"));
77
78 match orig_key {
79 Some(orig_key) => format!(r#"export {key} = "{value}:${{env::{orig_key}}}""#,),
80 None => format!(r#"export {key} = "{value}""#,),
81 }
82 }
83 Statement::SetEnv { key, value } => {
84 format!(
85 "export {}={}",
86 self.quote(key),
87 self.quote(self.replace_env(value).as_str())
88 )
89 }
90 Statement::UnsetEnv { key } => {
91 format!("drop {}", self.quote(key))
92 }
93 }
94 }
95
96 fn get_config_path(&self, home_dir: &Path) -> PathBuf {
97 get_config_dir(home_dir).join("ion").join("initrc")
98 }
99
100 fn get_env_path(&self, home_dir: &Path) -> PathBuf {
101 self.get_config_path(home_dir)
102 }
103
104 fn get_env_regex(&self) -> regex::Regex {
105 regex::Regex::new(r"\$\{env::(?<name>[A-Za-z0-9_]+)\}").unwrap()
106 }
107
108 fn get_profile_paths(&self, home_dir: &Path) -> Vec<PathBuf> {
110 ProfileSet::default()
111 .insert(get_config_dir(home_dir).join("ion").join("initrc"), 1)
112 .insert(home_dir.join(".config").join("ion").join("initrc"), 2)
113 .into_list()
114 }
115}
116
117impl fmt::Display for Ion {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(f, "ion")
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn formats_env_var() {
129 assert_eq!(
130 Ion.format_env_set("PROTO_HOME", "$HOME/.proto"),
131 r#"export PROTO_HOME="${env::HOME}/.proto""#
132 );
133 }
134
135 #[test]
136 fn formats_path_prepend() {
137 assert_eq!(
138 Ion.format_path_prepend(&["$PROTO_HOME/shims".into(), "$PROTO_HOME/bin".into()]),
139 r#"export PATH = "${env::PROTO_HOME}/shims:${env::PROTO_HOME}/bin:${env::PATH}""#
140 );
141 }
142
143 #[test]
144 fn formats_path_set() {
145 assert_eq!(
146 Ion.format_path_set(&["$PROTO_HOME/shims".into(), "$PROTO_HOME/bin".into()]),
147 r#"export PATH = "${env::PROTO_HOME}/shims:${env::PROTO_HOME}/bin""#
148 );
149 }
150
151 #[test]
152 fn test_profile_paths() {
153 #[allow(deprecated)]
154 let home_dir = std::env::home_dir().unwrap();
155
156 assert_eq!(
157 Ion::new().get_profile_paths(&home_dir),
158 vec![home_dir.join(".config").join("ion").join("initrc")]
159 );
160 }
161
162 #[test]
163 fn test_ion_quoting() {
164 assert_eq!(Ion.quote("simplevalue"), "simplevalue");
165 assert_eq!(Ion.quote("value with spaces"), r#""value with spaces""#);
166 assert_eq!(
167 Ion.quote(r#"value "with" quotes"#),
168 r#""value \"with\" quotes""#
169 );
170 assert_eq!(Ion.quote("$variable"), "\"$variable\"");
171 assert_eq!(Ion.quote("{brace_expansion}"), "{brace_expansion}");
172 assert_eq!(
173 Ion.quote("value with 'single quotes'"),
174 r#""value with 'single quotes'""#
175 );
176 }
177}