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