1use std::fs::File;
2use std::io::{self, Write};
3
4use crate::parser::ShellCommand;
5use crate::state::ShellState;
6
7pub struct ColoredWriter<W: Write> {
9 inner: W,
10}
11
12impl<W: Write> ColoredWriter<W> {
13 pub fn new(inner: W) -> Self {
14 Self { inner }
15 }
16}
17
18impl<W: Write> Write for ColoredWriter<W> {
19 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
20 self.inner.write(buf)
21 }
22
23 fn flush(&mut self) -> io::Result<()> {
24 self.inner.flush()
25 }
26}
27
28mod builtin_alias;
29mod builtin_cd;
30mod builtin_dirs;
31mod builtin_env;
32mod builtin_exit;
33mod builtin_export;
34mod builtin_help;
35mod builtin_popd;
36mod builtin_pushd;
37mod builtin_pwd;
38mod builtin_set_color_scheme;
39mod builtin_set_colors;
40mod builtin_set_condensed;
41mod builtin_shift;
42mod builtin_source;
43mod builtin_test;
44mod builtin_unalias;
45mod builtin_unset;
46mod builtin_declare;
47
48pub trait Builtin {
49 fn name(&self) -> &'static str;
50 fn names(&self) -> Vec<&'static str>;
51 fn description(&self) -> &'static str;
52 fn run(
53 &self,
54 cmd: &ShellCommand,
55 shell_state: &mut ShellState,
56 output_writer: &mut dyn Write,
57 ) -> i32;
58}
59
60fn get_builtins() -> Vec<Box<dyn Builtin>> {
61 vec![
62 Box::new(builtin_cd::CdBuiltin),
63 Box::new(builtin_pwd::PwdBuiltin),
64 Box::new(builtin_env::EnvBuiltin),
65 Box::new(builtin_exit::ExitBuiltin),
66 Box::new(builtin_help::HelpBuiltin),
67 Box::new(builtin_source::SourceBuiltin),
68 Box::new(builtin_export::ExportBuiltin),
69 Box::new(builtin_unset::UnsetBuiltin),
70 Box::new(builtin_pushd::PushdBuiltin),
71 Box::new(builtin_popd::PopdBuiltin),
72 Box::new(builtin_dirs::DirsBuiltin),
73 Box::new(builtin_alias::AliasBuiltin),
74 Box::new(builtin_unalias::UnaliasBuiltin),
75 Box::new(builtin_test::TestBuiltin),
76 Box::new(builtin_set_colors::SetColorsBuiltin),
77 Box::new(builtin_set_color_scheme::SetColorSchemeBuiltin),
78 Box::new(builtin_set_condensed::SetCondensedBuiltin),
79 Box::new(builtin_shift::ShiftBuiltin),
80 Box::new(builtin_declare::DeclareBuiltin),
81 ]
82}
83
84pub fn is_builtin(cmd: &str) -> bool {
85 get_builtins().iter().any(|b| b.names().contains(&cmd))
86}
87
88pub fn get_builtin_commands() -> Vec<String> {
89 let builtins = get_builtins();
90 let mut commands = Vec::new();
91 for b in builtins {
92 for &name in &b.names() {
93 commands.push(name.to_string());
94 }
95 }
96 commands
97}
98
99pub fn execute_builtin(
100 cmd: &ShellCommand,
101 shell_state: &mut ShellState,
102 output_override: Option<Box<dyn std::io::Write>>,
103) -> i32 {
104 let print_error = |msg: &str| {
106 if shell_state.colors_enabled {
107 eprintln!("{}{}\x1b[0m", shell_state.color_scheme.error, msg);
108 } else {
109 eprintln!("{}", msg);
110 }
111 };
112 let _input_content = if let Some(ref input_file) = cmd.input {
114 match std::fs::read_to_string(input_file) {
115 Ok(content) => Some(content),
116 Err(e) => {
117 print_error(&format!("Error reading input file '{}': {}", input_file, e));
118 return 1;
119 }
120 }
121 } else {
122 None
123 };
124
125 let mut output_writer: Box<dyn Write> = if let Some(override_writer) = output_override {
127 override_writer
128 } else if let Some(ref output_file) = cmd.output {
129 match File::create(output_file) {
131 Ok(file) => Box::new(file),
132 Err(e) => {
133 print_error(&format!(
134 "Error creating output file '{}': {}",
135 output_file, e
136 ));
137 return 1;
138 }
139 }
140 } else if let Some(ref append_file) = cmd.append {
141 match File::options().append(true).create(true).open(append_file) {
143 Ok(file) => Box::new(file),
144 Err(e) => {
145 print_error(&format!(
146 "Error opening append file '{}': {}",
147 append_file, e
148 ));
149 return 1;
150 }
151 }
152 } else {
153 Box::new(ColoredWriter::new(io::stdout()))
155 };
156
157 let builtins = get_builtins();
158 if let Some(builtin) = builtins
159 .into_iter()
160 .find(|b| b.names().contains(&cmd.args[0].as_str()))
161 {
162 builtin.run(cmd, shell_state, &mut *output_writer)
163 } else {
164 1
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_is_builtin() {
174 assert!(is_builtin("cd"));
175 assert!(is_builtin("pwd"));
176 assert!(is_builtin("env"));
177 assert!(is_builtin("exit"));
178 assert!(is_builtin("help"));
179 assert!(is_builtin("alias"));
180 assert!(is_builtin("unalias"));
181 assert!(is_builtin("test"));
182 assert!(is_builtin("["));
183 assert!(is_builtin("."));
184 assert!(!is_builtin("ls"));
185 assert!(!is_builtin("grep"));
186 assert!(!is_builtin("echo"));
187 }
188
189 #[test]
190 fn test_execute_builtin_unknown() {
191 let cmd = ShellCommand {
192 args: vec!["unknown".to_string()],
193 input: None,
194 output: None,
195 append: None,
196 };
197 let mut shell_state = crate::state::ShellState::new();
198 let exit_code = execute_builtin(&cmd, &mut shell_state, None);
199 assert_eq!(exit_code, 1);
200 }
201
202 #[test]
203 fn test_get_builtin_commands() {
204 let commands = get_builtin_commands();
205 assert!(commands.contains(&"cd".to_string()));
206 assert!(commands.contains(&"pwd".to_string()));
207 assert!(commands.contains(&"env".to_string()));
208 assert!(commands.contains(&"exit".to_string()));
209 assert!(commands.contains(&"help".to_string()));
210 assert!(commands.contains(&"source".to_string()));
211 assert!(commands.contains(&"export".to_string()));
212 assert!(commands.contains(&"unset".to_string()));
213 assert!(commands.contains(&"pushd".to_string()));
214 assert!(commands.contains(&"popd".to_string()));
215 assert!(commands.contains(&"dirs".to_string()));
216 assert!(commands.contains(&"alias".to_string()));
217 assert!(commands.contains(&"unalias".to_string()));
218 assert!(commands.contains(&"test".to_string()));
219 assert!(commands.contains(&"[".to_string()));
220 assert!(commands.contains(&".".to_string()));
221 assert!(commands.contains(&"set_colors".to_string()));
222 assert!(commands.contains(&"set_color_scheme".to_string()));
223 assert!(commands.contains(&"set_condensed".to_string()));
224 assert_eq!(commands.len(), 21);
225 }
226}