use std::env; use std::io::{self, Write}; use std::sync::{Arc, Mutex}; mod buffer; mod path; mod poem; use poem::{read::Readable, recite::Reciteable, Poem}; mod compose; use buffer::{getline, STDIN}; use compose::Environment; use termios::{tcsetattr, Termios, ECHO, ECHOE, ICANON, TCSANOW}; /// Starts the main shell loop /// /// # Arguments /// * `away` - A mutex, indicating whether or not user is at the prompt /// * `env` - The global shell state /// /// # Examples /// ``` /// fn main() { /// let mut env = compose::env(); /// let mut away = Arc::new(Mutex::new(false)); /// ... /// repl(&mut away, &mut env); /// } /// ``` fn repl( away: &mut Arc>, buffer: &mut Arc>>, pos: &mut Arc>, env: &mut Environment, ) { // Setup termios flags let mut termios = Termios::from_fd(STDIN).unwrap(); // Initial path refresh on startup env.bins = path::refresh(); // Main shell loop loop { // Clear the buffer buffer.lock().unwrap().clear(); // Get the prompt let prompt = match env::var("PS1") { Ok(val) => val, Err(_) => String::from("|> "), }; // Output the prompt print!("{}", prompt); io::stdout().flush().unwrap(); // At the prompt *away.lock().unwrap() = false; // Unset ICANON and ECHO before the prompt termios.c_lflag &= !(ICANON | ECHO); tcsetattr(STDIN, TCSANOW, &mut termios).unwrap(); // Wait for user input let bytes = getline(buffer, pos); // Check if we've reached EOF (i.e. ) if bytes == 0 { println!(); break; } // Convert buffer to a string and trim it let poetry = String::from_utf8_lossy(&buffer.lock().unwrap()) .trim() .to_string(); // Skip parsing if there is no poetry if poetry.is_empty() { continue; } // Set ICANON and ECHO for other programs after the prompt termios.c_lflag |= ICANON | ECHO | ECHOE; tcsetattr(STDIN, TCSANOW, &mut termios).unwrap(); // Not at the prompt *away.lock().unwrap() = true; // Parse the poem let poem = Poem::read(poetry, env); let poem = match poem { Ok(poem) => poem, Err(e) => { eprintln!("dwvsh: {}", e.to_string().to_lowercase()); continue; } }; // Recite the poem match poem.recite(env) { Ok(_) => {} Err(e) => eprintln!("dwvsh: {}", e.to_string().to_lowercase()), } } } fn options(env: &mut Environment) { let args: Vec = env::args().collect(); for arg in args.iter() { if arg.eq("--version") { println!( "dwvsh v{} ({})", env!("CARGO_PKG_VERSION"), env!("DWVSH_BUILD") ); std::process::exit(0); } } match args.last() { Some(arg) => { if args.len() > 1 && !arg.starts_with('-') { let poetry = std::fs::read_to_string(arg) .expect(format!("dwvsh: can't open input file: {}", arg).as_str()); let poem = Poem::read(poetry, env); let poem = match poem { Ok(poem) => poem, Err(e) => { eprintln!("dwvsh: {}", e.to_string().to_lowercase()); std::process::exit(1); } }; // Recite the poem match poem.recite(env) { Ok(_) => {} Err(e) => eprintln!("dwvsh: {}", e.to_string().to_lowercase()), } // Quit std::process::exit(0); } } None => {} } } /// Shell entry /// /// Shell setup and entry fn main() { // Compose the environment for dwvsh let mut env = compose::env(); // Handle signals let mut away = Arc::new(Mutex::new(true)); let mut buffer: Arc>> = Arc::new(Mutex::new(vec![])); let mut pos: Arc> = Arc::new(Mutex::new(0)); unsafe { let away = Arc::clone(&away); let buffer = Arc::clone(&buffer); let pos = Arc::clone(&pos); signal_hook::low_level::register(signal_hook::consts::SIGINT, move || { buffer.lock().unwrap().clear(); *pos.lock().unwrap() = 0; if *away.lock().unwrap() { println!(); } else { let prompt = match env::var("PS1") { Ok(val) => val, Err(_) => String::from("|> "), }; print!("\n{}", prompt); io::stdout().flush().unwrap(); } }) .unwrap(); }; // Parse flags and other arguments options(&mut env); // Begin evaluating commands repl(&mut away, &mut buffer, &mut pos, &mut env); }