1#![allow(non_camel_case_types)]
2#![allow(unused_variables)]
3
4use std::borrow::Cow::{self, Borrowed, Owned};
5use std::collections::HashMap;
6
7pub mod command;
8pub mod lang;
9
10use chrono::{Local, Timelike};
11use rustyline::completion::FilenameCompleter;
12use rustyline::error::ReadlineError;
13use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
14use rustyline::hint::HistoryHinter;
15use rustyline::validate::MatchingBracketValidator;
16use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyEvent};
17use rustyline_derive::{Completer, Helper, Hinter, Validator};
18use whoami::fallible;
19
20use self::command::Command;
21use self::lang::{InterpreterTraitObject, LexerTraitObject, ParserTraitObject};
22
23fn to_rfc1035(s: &str) -> String {
24 s.chars()
25 .enumerate()
26 .map(|(i, c)| match c {
27 'a'..='z' | 'A'..='Z' | '0'..='9' => c,
28 '-' if i != 0 && i != s.len() - 1 => c,
29 _ => '-',
30 })
31 .collect::<String>()
32 .to_lowercase()
33}
34
35#[derive(Helper, Completer, Hinter, Validator)]
36struct MyHelper {
37 #[rustyline(Completer)]
38 completer: FilenameCompleter,
39 highlighter: MatchingBracketHighlighter,
40 #[rustyline(Validator)]
41 validator: MatchingBracketValidator,
42 #[rustyline(Hinter)]
43 hinter: HistoryHinter,
44 colored_prompt: String,
45}
46
47impl Highlighter for MyHelper {
48 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
49 &'s self,
50 prompt: &'p str,
51 default: bool,
52 ) -> Cow<'b, str> {
53 if default {
54 Borrowed(&self.colored_prompt)
55 } else {
56 Borrowed(prompt)
57 }
58 }
59
60 fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
61 Owned("\x1b[2m".to_owned() + hint + "\x1b[m")
62 }
63
64 fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
65 self.highlighter.highlight(line, pos)
66 }
67
68 fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
69 self.highlighter.highlight_char(line, pos, forced)
70 }
71}
72
73pub struct custom_pwd;
74impl Command for custom_pwd {
75 fn run(
76 &self,
77 input: &str,
78 ) -> Result<String, Box<dyn std::error::Error + Send>> {
79 let args = input.split(" ").collect::<Vec<&str>>();
80
81 let path_buf = std::env::current_dir().unwrap_or_default();
82 return Ok(path_buf.to_str().unwrap_or_default().into());
83 }
84}
85
86pub fn shell_main(
87 lexer: impl LexerTraitObject,
88 parser: impl ParserTraitObject,
89 interpreter: impl InterpreterTraitObject,
90) -> rustyline::Result<()> {
91 let mut temp: HashMap<String, Box<dyn Command>> = Default::default();
92
93 temp.insert("custom_pwd".to_owned(), Box::new(custom_pwd));
94
95 return shell_main_with_all(lexer, parser, interpreter, temp);
97}
98
99pub fn shell_main_with_all(
102 lexer: impl LexerTraitObject,
103 parser: impl ParserTraitObject,
104 interpreter: impl InterpreterTraitObject,
105 custom_commands: HashMap<String, Box<dyn Command>>,
106) -> rustyline::Result<()> {
107 let config = Config::builder()
109 .history_ignore_space(true)
110 .completion_type(CompletionType::List)
111 .edit_mode(EditMode::Vi)
112 .build();
113
114 let h = MyHelper {
115 completer: FilenameCompleter::new(),
116 highlighter: MatchingBracketHighlighter::new(),
117 hinter: HistoryHinter::new(),
118 colored_prompt: "".to_owned(),
119 validator: MatchingBracketValidator::new(),
120 };
121
122 let mut rl = Editor::with_config(config)?;
123 rl.set_helper(Some(h));
124
125 rl.bind_sequence(KeyEvent::ctrl('o'), Cmd::ClearScreen);
126
127 rl.bind_sequence(KeyEvent::ctrl('l'), Cmd::CompleteHint);
128 rl.bind_sequence(KeyEvent::ctrl('j'), Cmd::HistorySearchForward);
129 rl.bind_sequence(KeyEvent::ctrl('k'), Cmd::HistorySearchBackward);
130
131 rl.clear_screen()?;
132
133 if rl.load_history("history.txt").is_err() {
134 println!("No previous history.");
135 }
136
137 loop {
138 let now = Local::now();
139 let hour = now.hour();
140 let minute = now.minute();
141 let second = now.second();
142
143 let username = whoami::username();
144 let hostname = to_rfc1035(&fallible::hostname().unwrap_or_default());
145
146 let path_buf = std::env::current_dir().unwrap_or_default();
147 let pwd = path_buf.to_str().unwrap_or_default();
148
149 let p = format!(
150 "[{hour:02}:{minute:02}:{second:02}] - {username}@{hostname}:{pwd}\n$ "
151 );
152
153 rl.helper_mut().expect("No helper").colored_prompt = format!("{p}\x1b[0m");
154 let readline = rl.readline(&p);
155
156 match readline {
157 Ok(line) => {
158 rl.add_history_entry(line.as_str())?;
159
160 println!("Line: {line}");
161
162 let tokens = lexer.run(&line);
163 let ast = parser.run(tokens);
164 interpreter.run(ast, &custom_commands);
165 }
166 Err(ReadlineError::Interrupted) => (),
167 Err(ReadlineError::Eof) => {
168 break;
169 }
170 Err(err) => {
171 println!("Error: {err:?}");
172 }
174 }
175 }
176
177 return rl.append_history("history.txt");
178}