clara_shell/
lib.rs

1pub mod token;
2pub mod parser;
3
4use std::{env, fs};
5use std::error::Error;
6use std::fs::read_to_string;
7use std::io::{stdin, stdout, Write};
8use std::path::Path;
9use std::process::{Child, Command, Stdio};
10use logos::{Logos, Lexer};
11use subst::substitute;
12use crate::parser::Parser;
13use crate::token::Token;
14
15
16/// Main run function with an optional config file
17/// string slice you can specify
18pub fn run(config_file: Option<&str>) {
19    match config_file {
20        None => {
21            match fs::read("../../.clararc") {
22                Ok(_) => {
23                    setup(".clararc").unwrap()
24                }
25                Err(_) => {
26                    fs::write("../../.clararc", "").unwrap();
27                }
28            }
29        }
30        Some(file) => {
31            setup(file).unwrap();
32        }
33    }
34
35    loop {
36        print!("> ");
37        stdout().flush().expect("Error while pushing to stdout");
38
39        let mut input = String::new();
40        stdin().read_line(&mut input).unwrap();
41
42        let mut san_input = String::new();
43        for i in input.split(" ") {
44            if i.contains("$") {
45
46                match substitute(i, &subst::Env) {
47                    Ok(sub) => {
48                        san_input.push_str(sub.as_str());
49                        san_input.push_str(" ");
50                    }
51
52                    Err(err) => {
53                        println!("{}", err )
54                    }
55                }
56
57            } else {
58                san_input.push_str(i);
59                san_input.push_str(" ");
60            }
61        }
62
63        // must be peekable so we know when we are on the last command
64        let mut commands = san_input.trim().split(" | ").peekable();
65        let mut previous_command = None;
66
67        while let Some(command) = commands.next() {
68            let mut parts = command.trim().split_whitespace();
69
70            let command = match parts.next() {
71                Some(data) => data,
72                None => break,
73            };
74            let args = parts;
75
76            match command {
77                "cd" => {
78                    let new_dir = args.peekable().peek().map_or("/", |x| *x);
79                    let root = Path::new(new_dir);
80                    if let Err(e) = env::set_current_dir(&root) {
81                        eprintln!("{}", e);
82                    }
83
84                    previous_command = None;
85                }
86                "exit" => return,
87                command => {
88                    let stdin = previous_command.map_or(Stdio::inherit(), |output: Child| {
89                        Stdio::from(output.stdout.unwrap())
90                    });
91
92                    let stdout = if commands.peek().is_some() {
93                        // there is another command piped behind this one
94                        // prepare to send output to the next command
95                        Stdio::piped()
96                    } else {
97                        // there are no more commands piped behind this one
98                        // send output to shell stdout
99                        Stdio::inherit()
100                    };
101
102                    let output = Command::new(command)
103                        .args(args)
104                        .stdin(stdin)
105                        .stdout(stdout)
106                        .spawn();
107
108                    match output {
109                        Ok(output) => {
110                            previous_command = Some(output);
111                        }
112                        Err(e) => {
113                            previous_command = None;
114                            eprintln!("{}", e);
115                        }
116                    };
117                }
118            }
119        }
120
121        if let Some(mut final_command) = previous_command {
122            // block until the final command has finished
123            final_command.wait().expect(format!("Error while waiting for task {:?} with PID {}",
124                                                final_command,
125                                                final_command.id()).as_str());
126        }
127    }
128}
129
130
131fn setup(config_name: &str) -> Result<(), Box<dyn Error>> {
132    match read_to_string(config_name) {
133        Ok(data) => {
134            let lex: Lexer<Token> = Token::lexer(data.as_str());
135            let mut token_list = vec![];
136
137            for i in lex {
138                if i != Token::Error {
139                    token_list.push(i);
140                }
141            }
142
143
144            let mut parser = Parser::new(token_list);
145            parser.run().expect("TODO: Error message");
146
147            Ok(())
148        }
149        Err(_) => {
150            Err(Box::from("Error: config file not found"))
151        }
152    }
153}
154
155
156#[cfg(test)]
157mod tests {
158    use serial_test::serial;
159    use std::fs::{remove_file, write};
160    use super::*;
161    #[serial(".clararc_temp")]
162    #[test]
163    fn test_empty_config() {
164        write(".clararc_temp", "").unwrap();
165        setup(".clararc_temp").unwrap();
166        remove_file(".clararc_temp").unwrap();
167    }
168
169    #[serial(".clararc_temp")]
170    #[test]
171    fn test_simple_config() {
172        write(".clararc_temp", "export TEST=\"test\"").unwrap();
173        setup(".clararc_temp").unwrap();
174        remove_file(".clararc_temp").unwrap();
175    }
176}