diff --git a/src/cli.rs b/src/cli.rs index 4682332..e3d3e04 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,12 +6,18 @@ use clap::{Parser}; pub struct CliArgs { /// Script to run pub file: Option, + /// Generate m3u8 playlist #[clap(short, long)] pub playlist: Option, - /// In REPL mode, wait for all music in the queue to complete - #[clap(short, long)] + + /// In REPL mode, wait for all music in the queue to complete before accepting new input + #[clap(long)] pub wait: bool, + + /// In REPL mode, the prompt to display + #[clap(long, default_value_t = String::from("|"))] + pub prompt: String } pub fn parse() -> CliArgs { diff --git a/src/repl.rs b/src/repl.rs index 0609764..5eee21d 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,27 +1,39 @@ //! Read, Execute, Print Loop functionality -use std::io::{self, Write, Read}; +use std::io::{self, Write, Read, Stdin}; use mps_interpreter::MpsRunner; use mps_player::{MpsPlayer, MpsController}; use super::cli::CliArgs; -use super::channel_io::channel_io; +use super::channel_io::{channel_io, ChannelWriter}; + +struct ReplState { + stdin: Stdin, + line_number: usize, + statement_buf: Vec, + writer: ChannelWriter, +} + +impl ReplState { + fn new(chan_writer: ChannelWriter) -> Self { + Self { + stdin: io::stdin(), + line_number: 0, + statement_buf: Vec::new(), + writer: chan_writer, + } + } +} pub fn repl(args: CliArgs) { - let (mut writer, reader) = channel_io(); + let (writer, reader) = channel_io(); let player_builder = move || { let runner = MpsRunner::with_stream(reader); let player = MpsPlayer::new(runner).unwrap(); player }; - let stdin_t = io::stdin(); - let mut stdin = stdin_t.lock(); - let mut buf: Vec = Vec::new(); - let mut read_buf = [0]; - let mut current_line = 0; - // TODO: enable raw mode (char by char) reading of stdin - // TODO: generalize loop for better code reuse between playlist and playback mode + let mut state = ReplState::new(writer); if let Some(playlist_file) = &args.playlist { println!("Playlist mode (output: `{}`)", playlist_file); let mut player = player_builder(); @@ -29,69 +41,57 @@ pub fn repl(args: CliArgs) { std::fs::File::create(playlist_file) .expect(&format!("Abort: Cannot create writeable file `{}`", playlist_file)) ); - prompt(&mut current_line); - loop { - read_buf[0] = 0; - while read_buf[0] == 0 { - stdin.read_exact(&mut read_buf).expect("Failed to read stdin"); + read_loop(&args, &mut state, || { + match player.save_m3u8(&mut playlist_writer) { + Ok(_) => {}, + Err(e) => eprintln!("{}", e.message()), } - match read_buf[0] as char { - ';' => { - buf.push(read_buf[0]); - writer.write(buf.as_slice()).expect("Failed to write to MPS interpreter"); - match player.save_m3u8(&mut playlist_writer) { - Ok(_) => {}, - Err(e) => eprintln!("{}", e.message()), - } - buf.clear(); - playlist_writer.flush().expect("Failed to flush playlist to file"); - prompt(&mut current_line); - }, - /* - '\x27' => break, // ESC key - '\x03' => break, // Ctrl+C - */ - _ => buf.push(read_buf[0]), - } - } + playlist_writer.flush().expect("Failed to flush playlist to file"); + }); } else { println!("Playback mode (output: audio device)"); let ctrl = MpsController::create_repl(player_builder); - prompt(&mut current_line); - loop { - read_buf[0] = 0; - while read_buf[0] == 0 { - stdin.read_exact(&mut read_buf).expect("Failed to read stdin"); - } - match read_buf[0] as char { - ';' => { - buf.push(read_buf[0]); - writer.write(buf.as_slice()).expect("Failed to write to MPS interpreter"); - //ctrl.play().expect("Failed to start playback"); - if args.wait { - match ctrl.wait_for_empty() { - Ok(_) => {}, - Err(e) => eprintln!("{}", e.message()), - } - } else { - for e in ctrl.check_ack() { - eprintln!("{}", e.message()); - } - } - buf.clear(); - prompt(&mut current_line); - }, - '\x27' => break, // ESC key - '\x03' => break, // Ctrl+C - _ => buf.push(read_buf[0]), + read_loop(&args, &mut state, || { + if args.wait { + match ctrl.wait_for_empty() { + Ok(_) => {}, + Err(e) => eprintln!("{}", e.message()), + } + } else { + for e in ctrl.check_ack() { + eprintln!("{}", e.message()); + } } + }); + } +} + +fn read_loop(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! { + let mut read_buf: [u8;1] = [0]; + prompt(&mut state.line_number, args); + loop { + read_buf[0] = 0; + while read_buf[0] == 0 { + // TODO: enable raw mode (char by char) reading of stdin + state.stdin.read(&mut read_buf).expect("Failed to read stdin"); + } + match read_buf[0] as char { + '\n' => { + state.statement_buf.push(read_buf[0]); + state.writer.write(state.statement_buf.as_slice()) + .expect("Failed to write to MPS interpreter"); + execute(); + state.statement_buf.clear(); + prompt(&mut state.line_number, args); + }, + _ => state.statement_buf.push(read_buf[0]), } } } #[inline(always)] -fn prompt(line: &mut usize) { - print!("{} |-> ", line); +fn prompt(line: &mut usize, args: &CliArgs) { + print!("{}{}", line, args.prompt); *line += 1; std::io::stdout().flush().expect("Failed to flush stdout"); }