i6_shell/
lib.rs

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, Default::default());
96  return shell_main_with_all(lexer, parser, interpreter, temp);
97}
98
99// To debug rustyline:
100// RUST_LOG=rustyline=debug cargo run --example example 2> debug.log
101pub 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  // env_logger::init();
108  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        // break;
173      }
174    }
175  }
176
177  return rl.append_history("history.txt");
178}