Add ?skip and ?list REPL commands and fix some missed names

This commit is contained in:
NGnius (Graham) 2022-07-08 21:24:46 -04:00
parent c76562fa84
commit 0202c28385
17 changed files with 322 additions and 102 deletions

9
Cargo.lock generated
View file

@ -1230,17 +1230,18 @@ dependencies = [
[[package]] [[package]]
name = "muss" name = "muss"
version = "0.8.0" version = "0.9.0"
dependencies = [ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"console", "console",
"lazy_static 1.4.0",
"muss-interpreter", "muss-interpreter",
"muss-player", "muss-player",
] ]
[[package]] [[package]]
name = "muss-interpreter" name = "muss-interpreter"
version = "0.8.0" version = "0.9.0"
dependencies = [ dependencies = [
"bliss-audio-symphonia", "bliss-audio-symphonia",
"criterion", "criterion",
@ -1256,7 +1257,7 @@ dependencies = [
[[package]] [[package]]
name = "muss-m3u8" name = "muss-m3u8"
version = "0.8.0" version = "0.9.0"
dependencies = [ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"m3u8-rs", "m3u8-rs",
@ -1265,7 +1266,7 @@ dependencies = [
[[package]] [[package]]
name = "muss-player" name = "muss-player"
version = "0.8.0" version = "0.9.0"
dependencies = [ dependencies = [
"fluent-uri", "fluent-uri",
"m3u8-rs", "m3u8-rs",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "muss" name = "muss"
version = "0.8.0" version = "0.9.0"
edition = "2021" edition = "2021"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"] authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
description = "Music Set Script language (MuSS)" description = "Music Set Script language (MuSS)"
@ -19,17 +19,18 @@ members = [
[dependencies] [dependencies]
# local # local
muss-interpreter = { version = "0.8.0", path = "./interpreter" } muss-interpreter = { version = "0.9.0", path = "./interpreter" }
# external # external
clap = { version = "3.0", features = ["derive"] } clap = { version = "3.0", features = ["derive"] }
console = { version = "0.15" } console = { version = "0.15" }
lazy_static = { version = "1.4" }
[target.'cfg(not(target_os = "linux"))'.dependencies] [target.'cfg(not(target_os = "linux"))'.dependencies]
muss-player = { version = "0.8.0", path = "./player", default-features = false, features = ["mpd"] } muss-player = { version = "0.9.0", path = "./player", default-features = false, features = ["mpd"] }
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
# TODO fix need to specify OS-specific dependency of mps-player # TODO fix need to specify OS-specific dependency of mps-player
muss-player = { version = "0.8.0", path = "./player", features = ["mpris-player", "mpd"] } muss-player = { version = "0.9.0", path = "./player", features = ["mpris-player", "mpd"] }
[profile.release] [profile.release]
debug = false debug = false

View file

@ -1,6 +1,6 @@
[package] [package]
name = "muss-interpreter" name = "muss-interpreter"
version = "0.8.0" version = "0.9.0"
edition = "2021" edition = "2021"
license = "LGPL-2.1-only OR GPL-3.0-only" license = "LGPL-2.1-only OR GPL-3.0-only"
readme = "README.md" readme = "README.md"

View file

@ -1,31 +1,33 @@
use std::iter::Iterator; use std::iter::Iterator;
use super::tokens::TokenReader; use super::tokens::TokenReader;
use super::{InterpreterError, Interpreter, Item}; use super::{Interpreter, InterpreterItem};
/// Wrapper for InterpreterError with a built-in callback function for every iteration of the interpreter. /// Wrapper for InterpreterError with a built-in callback function for every iteration of the interpreter.
pub struct Debugger<'a, 'b, T> pub struct Debugger<'a, T, F>
where where
T: TokenReader, T: TokenReader,
F: Fn(
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{ {
interpreter: Interpreter<'a, T>, interpreter: Interpreter<'a, T>,
transmuter: &'b dyn Fn( transmuter: F,
&mut Interpreter<'a, T>,
Option<Result<Item, InterpreterError>>,
) -> Option<Result<Item, InterpreterError>>,
} }
impl<'a, 'b, T> Debugger<'a, 'b, T> impl<'a, T, F> Debugger<'a, T, F>
where where
T: TokenReader, T: TokenReader,
F: Fn(
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{ {
/// Create a new instance of Debugger using the provided interpreter and callback. /// Create a new instance of Debugger using the provided interpreter and callback.
pub fn new( pub fn new(
faye: Interpreter<'a, T>, faye: Interpreter<'a, T>,
item_handler: &'b dyn Fn( item_handler: F,
&mut Interpreter<'a, T>,
Option<Result<Item, InterpreterError>>,
) -> Option<Result<Item, InterpreterError>>,
) -> Self { ) -> Self {
Self { Self {
interpreter: faye, interpreter: faye,
@ -34,11 +36,15 @@ where
} }
} }
impl<'a, 'b, T> Iterator for Debugger<'a, 'b, T> impl<'a, T, F> Iterator for Debugger<'a, T, F>
where where
T: TokenReader, T: TokenReader,
F: Fn(
&mut Interpreter<'a, T>,
Option<InterpreterItem>,
) -> Option<InterpreterItem>,
{ {
type Item = Result<Item, InterpreterError>; type Item = InterpreterItem;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let next_item = self.interpreter.next(); let next_item = self.interpreter.next();

View file

@ -60,6 +60,12 @@ impl<'a, R: Read> Interpreter<'a, Tokenizer<R>> {
let tokenizer = Tokenizer::new(stream); let tokenizer = Tokenizer::new(stream);
Self::with_standard_vocab(tokenizer) Self::with_standard_vocab(tokenizer)
} }
pub fn with_stream_and_callback(stream: R, callback: &'a dyn Fn(&mut Interpreter<'a, Tokenizer<R>>, InterpreterEvent) -> Result<(), InterpreterError>) -> Self {
let tokenizer = Tokenizer::new(stream);
let vocab = LanguageDictionary::standard();
Self::with(vocab, tokenizer, callback)
}
} }
impl<'a, T> Interpreter<'a, T> impl<'a, T> Interpreter<'a, T>
@ -127,11 +133,13 @@ where
} }
} }
pub type InterpreterItem = Result<Item, InterpreterError>;
impl<'a, T> Iterator for Interpreter<'a, T> impl<'a, T> Iterator for Interpreter<'a, T>
where where
T: TokenReader, T: TokenReader,
{ {
type Item = Result<Item, InterpreterError>; type Item = InterpreterItem;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
loop { loop {

View file

@ -33,6 +33,7 @@ impl TypePrimitive {
} }
} }
/// Pretty-print the value, without type info
pub fn as_str(&self) -> String { pub fn as_str(&self) -> String {
match self { match self {
Self::String(s) => s.clone(), Self::String(s) => s.clone(),

View file

@ -263,7 +263,7 @@ pub mod tokens;
pub use context::Context; pub use context::Context;
pub use debug::Debugger; pub use debug::Debugger;
pub use errors::InterpreterError; pub use errors::InterpreterError;
pub use faye::{Interpreter, InterpreterEvent}; pub use faye::{Interpreter, InterpreterEvent, InterpreterItem};
//pub use interpretor::{interpretor, Interpretor}; //pub use interpretor::{interpretor, Interpretor};
pub use item::Item; pub use item::Item;
//pub(crate) use item::ItemRuntimeUtil; //pub(crate) use item::ItemRuntimeUtil;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "muss-m3u8" name = "muss-m3u8"
version = "0.8.0" version = "0.9.0"
edition = "2021" edition = "2021"
authors = ["NGnius <ngniusness@gmail.com>"] authors = ["NGnius <ngniusness@gmail.com>"]
description = "Minimal playlist generator for MPS files" description = "Minimal playlist generator for MPS files"
@ -13,7 +13,7 @@ readme = "README.md"
[dependencies] [dependencies]
# local # local
muss-interpreter = { version = "0.8.0", path = "../interpreter" } muss-interpreter = { version = "0.9.0", path = "../interpreter" }
# external # external
clap = { version = "3.0", features = ["derive"] } clap = { version = "3.0", features = ["derive"] }
m3u8-rs = { version = "^3.0.0" } m3u8-rs = { version = "^3.0.0" }

View file

@ -11,7 +11,7 @@ use std::path::Path;
use m3u8_rs::{MediaPlaylist, MediaSegment}; use m3u8_rs::{MediaPlaylist, MediaSegment};
use mps_interpreter::{Interpreter, Item}; use muss_interpreter::{Interpreter, Item};
fn main() { fn main() {
let args = cli::parse(); let args = cli::parse();

View file

@ -1,10 +1,10 @@
[Desktop Entry] [Desktop Entry]
Categories=AudioVideo;Player; Categories=AudioVideo;Player;
Comment= Comment=
Exec=mps %U Exec=muss %U
GenericName=Music Player GenericName=Music Player
Icon=utilities-terminal Icon=utilities-terminal
Name=MPS Name=Muss
StartupNotify=false StartupNotify=false
Terminal=true Terminal=true
Type=Application Type=Application

View file

@ -1,6 +1,6 @@
[package] [package]
name = "muss-player" name = "muss-player"
version = "0.8.0" version = "0.9.0"
edition = "2021" edition = "2021"
license = "LGPL-2.1-only OR GPL-3.0-only" license = "LGPL-2.1-only OR GPL-3.0-only"
readme = "README.md" readme = "README.md"
@ -12,7 +12,7 @@ fluent-uri = { version = "^0.1" }
mpd = { version = "0.0.12", optional = true } mpd = { version = "0.0.12", optional = true }
# local # local
muss-interpreter = { path = "../interpreter", version = "0.8.0" } muss-interpreter = { path = "../interpreter", version = "0.9.0" }
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
#dbus = { version = "^0.9" } #dbus = { version = "^0.9" }

View file

@ -1,7 +1,7 @@
use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread::JoinHandle; use std::thread::JoinHandle;
use muss_interpreter::tokens::TokenReader; use muss_interpreter::{Item, InterpreterError};
use super::os_controls::SystemControlWrapper; use super::os_controls::SystemControlWrapper;
use super::player_wrapper::{ControlAction, PlayerServer, PlayerAction}; use super::player_wrapper::{ControlAction, PlayerServer, PlayerAction};
@ -20,9 +20,8 @@ pub struct Controller {
impl Controller { impl Controller {
pub fn create< pub fn create<
'a, F: FnOnce() -> Player<I> + Send + 'static,
F: FnOnce() -> Player<'a, T> + Send + 'static, I: std::iter::Iterator<Item=Result<Item, InterpreterError>>,
T: TokenReader + 'static,
>( >(
player_gen: F, player_gen: F,
) -> Self { ) -> Self {
@ -48,9 +47,8 @@ impl Controller {
} }
pub fn create_repl< pub fn create_repl<
'a, F: FnOnce() -> Player<I> + Send + 'static,
F: FnOnce() -> Player<'a, T> + Send + 'static, I: std::iter::Iterator<Item=Result<Item, InterpreterError>>,
T: TokenReader + 'static,
>( >(
player_gen: F, player_gen: F,
) -> Self { ) -> Self {

View file

@ -59,7 +59,7 @@ impl SystemControlWrapper {
self.dbus_ctrl = Some(tx); self.dbus_ctrl = Some(tx);
let control_clone1 = self.control.clone(); let control_clone1 = self.control.clone();
self.dbus_handle = Some(std::thread::spawn(move || { self.dbus_handle = Some(std::thread::spawn(move || {
let dbus_conn = MprisPlayer::new("mps".into(), "mps".into(), "ngnius.mps".into()); let dbus_conn = MprisPlayer::new("muss".into(), "muss".into(), "ngnius.muss".into());
//let (msg_tx, msg_rx) = channel(); //let (msg_tx, msg_rx) = channel();
// dbus setup // dbus setup
//self.dbus_conn.set_supported_mime_types(vec![]); //self.dbus_conn.set_supported_mime_types(vec![]);

View file

@ -10,16 +10,18 @@ use mpd::{Client, Song, error};
use super::uri::Uri; use super::uri::Uri;
use muss_interpreter::{tokens::TokenReader, Interpreter, Item}; use muss_interpreter::{Item, InterpreterError};
//use super::PlaybackError; //use super::PlaybackError;
use super::PlayerError; use super::PlayerError;
use super::UriError; use super::UriError;
//type Interpreter = std::iter::Iterator<Item=Result<Item, InterpreterError>>;
/// Playback functionality for a script. /// Playback functionality for a script.
/// This takes the output of the runner and plays or saves it. /// This takes the output of the runner and plays or saves it.
pub struct Player<'a, T: TokenReader + 'a> { pub struct Player<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> {
runner: Interpreter<'a, T>, runner: I,
sink: Sink, sink: Sink,
#[allow(dead_code)] #[allow(dead_code)]
output_stream: OutputStream, // this is required for playback, so it must live as long as this struct instance output_stream: OutputStream, // this is required for playback, so it must live as long as this struct instance
@ -28,8 +30,8 @@ pub struct Player<'a, T: TokenReader + 'a> {
mpd_connection: Option<Client<std::net::TcpStream>>, mpd_connection: Option<Client<std::net::TcpStream>>,
} }
impl<'a, T: TokenReader + 'a> Player<'a, T> { impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> Player<I> {
pub fn new(runner: Interpreter<'a, T>) -> Result<Self, PlayerError> { pub fn new(runner: I) -> Result<Self, PlayerError> {
let (stream, output_handle) = let (stream, output_handle) =
OutputStream::try_default().map_err(PlayerError::from_err_playback)?; OutputStream::try_default().map_err(PlayerError::from_err_playback)?;
Ok(Self { Ok(Self {

View file

@ -1,8 +1,8 @@
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::{thread, thread::JoinHandle}; use std::{thread, thread::JoinHandle};
use muss_interpreter::tokens::TokenReader; //use muss_interpreter::tokens::TokenReader;
use muss_interpreter::Item; use muss_interpreter::{Item, InterpreterError};
use super::Player; use super::Player;
use super::PlayerError; use super::PlayerError;
@ -11,17 +11,17 @@ use super::PlayerError;
/// This allows for message passing between the player and controller. /// This allows for message passing between the player and controller.
/// ///
/// You will probably never directly interact with this, instead using Controller to communicate. /// You will probably never directly interact with this, instead using Controller to communicate.
pub struct PlayerServer<'a, T: TokenReader + 'a> { pub struct PlayerServer<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> {
player: Player<'a, T>, player: Player<I>,
control: Receiver<ControlAction>, control: Receiver<ControlAction>,
event: Sender<PlayerAction>, event: Sender<PlayerAction>,
playback: Sender<PlaybackAction>, playback: Sender<PlaybackAction>,
keep_alive: bool, keep_alive: bool,
} }
impl<'a, T: TokenReader + 'a> PlayerServer<'a, T> { impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> PlayerServer<I> {
pub fn new( pub fn new(
player: Player<'a, T>, player: Player<I>,
ctrl: Receiver<ControlAction>, ctrl: Receiver<ControlAction>,
event: Sender<PlayerAction>, event: Sender<PlayerAction>,
playback: Sender<PlaybackAction>, playback: Sender<PlaybackAction>,
@ -146,7 +146,7 @@ impl<'a, T: TokenReader + 'a> PlayerServer<'a, T> {
self.on_end(); self.on_end();
} }
pub fn spawn<F: FnOnce() -> Player<'a, T> + Send + 'static>( pub fn spawn<F: FnOnce() -> Player<I> + Send + 'static>(
factory: F, factory: F,
ctrl_tx: Sender<ControlAction>, ctrl_tx: Sender<ControlAction>,
ctrl_rx: Receiver<ControlAction>, ctrl_rx: Receiver<ControlAction>,

View file

@ -2,7 +2,8 @@
pub const HELP_STRING: &str = pub const HELP_STRING: &str =
"This language is all about iteration. Almost everything is an iterator or operates on iterators. By default, any operation which is not an assignment will cause the script runner to handle (play/save) the items which that statement contains. "This language is all about iteration. Almost everything is an iterator or operates on iterators. By default, any operation which is not an assignment will cause the script runner to handle (play/save) the items which that statement contains.
To view the currently-supported operations, try ?functions, ?filters, ?procedures, or ?sorters"; To view Muss syntax, try ?functions, ?filters, ?procedures, or ?sorters
To view REPL commands, try ?commands";
pub const FUNCTIONS: &str = pub const FUNCTIONS: &str =
"FUNCTIONS (?functions) "FUNCTIONS (?functions)
@ -161,3 +162,26 @@ Comma-separated procedure steps will be executed sequentially (like a for loop i
file(filepath) -- e.g. file(`~/Music/Romantic Traffic.flac`) file(filepath) -- e.g. file(`~/Music/Romantic Traffic.flac`)
Load an item from file, populating the item with the song's tags."; Load an item from file, populating the item with the song's tags.";
pub const REPL_COMMANDS: &str =
"COMMANDS (?commands)
REPL-specific operations to help with writing Muss scripts: ?command
help
filters
functions
procedures
sorters
Show various help strings for the Muss language and syntax.
list
List remaining items to be played instead of playing them. If no items are about to be played, list all items in the next statement. In playlist mode, only the latter functionality applies.
skip
Skip remaining items to be played. If no items are about to be played, skip all items in the next statement. This is a no-output version of ?list.
normal
Cancel any active ?skip or ?list operation.
verbose
Toggle verbose messages, like extra information on items in ?list and other goodies.";

View file

@ -1,15 +1,33 @@
//! Read, Execute, Print Loop functionality //! Read, Execute, Print Loop functionality
use std::sync::{RwLock};
use std::sync::mpsc::{self, Receiver};
use std::io::{self, Write}; use std::io::{self, Write};
use lazy_static::lazy_static;
use console::{Key, Term}; use console::{Key, Term};
use muss_interpreter::Interpreter; use muss_interpreter::{Interpreter, Debugger, Item, InterpreterEvent, InterpreterError};
use muss_interpreter::lang::TypePrimitive;
use muss_player::{Controller, Player}; use muss_player::{Controller, Player};
use super::channel_io::{channel_io, ChannelWriter}; use super::channel_io::{channel_io, ChannelWriter};
use super::cli::CliArgs; use super::cli::CliArgs;
lazy_static! {
static ref DEBUG_STATE: RwLock<DebugState> = RwLock::new(
DebugState {
debug_flag: DebugFlag::Normal,
verbose: false,
}
);
}
const TERMINAL_WRITE_ERROR: &str = "Failed to write to terminal output";
const INTERPRETER_WRITE_ERROR: &str = "Failed to write to interpreter";
type DebugItem = Result<Item, String>;
struct ReplState { struct ReplState {
terminal: Term, terminal: Term,
line_number: usize, line_number: usize,
@ -22,10 +40,25 @@ struct ReplState {
selected_history: usize, selected_history: usize,
current_line: Vec<char>, current_line: Vec<char>,
cursor_rightward_position: usize, cursor_rightward_position: usize,
//debug: Arc<RwLock<DebugState>>,
list_rx: Receiver<DebugItem>,
}
#[derive(Clone)]
struct DebugState {
debug_flag: DebugFlag,
verbose: bool,
}
#[derive(Copy, Clone)]
enum DebugFlag {
Skip,
List,
Normal
} }
impl ReplState { impl ReplState {
fn new(chan_writer: ChannelWriter, term: Term) -> Self { fn new(chan_writer: ChannelWriter, term: Term, debug_list: Receiver<DebugItem>) -> Self {
Self { Self {
terminal: term, terminal: term,
line_number: 0, line_number: 0,
@ -38,13 +71,92 @@ impl ReplState {
selected_history: 0, selected_history: 0,
current_line: Vec::new(), current_line: Vec::new(),
cursor_rightward_position: 0, cursor_rightward_position: 0,
/*debug: Arc::new(RwLock::new(DebugState {
debug_flag: DebugFlag::Normal,
})),*/
list_rx: debug_list,
} }
} }
} }
fn interpreter_event_callback<'a, T: muss_interpreter::tokens::TokenReader>(_interpreter: &mut Interpreter<'a, T>, event: InterpreterEvent) -> Result<(), InterpreterError> {
match event {
InterpreterEvent::StatementComplete => {
if let Ok(mut d_state) = DEBUG_STATE.write() {
d_state.debug_flag = DebugFlag::Normal;
}
},
_ => {},
}
Ok(())
}
fn pretty_print_item(item: &Item, terminal: &mut Term, args: &CliArgs, verbose: bool) {
if verbose {
writeln!(terminal, "I{}--\\/-- `{}` --\\/--", args.prompt,
item.field("title").unwrap_or(&TypePrimitive::Empty).as_str()
).expect(TERMINAL_WRITE_ERROR);
let mut fields: Vec<&_> = item.iter().collect();
fields.sort();
for field in fields {
if field != "title" {
writeln!(terminal, "I{} {}: `{}`",
args.prompt, field,
item.field(field).unwrap_or(&TypePrimitive::Empty).as_str()
).expect(TERMINAL_WRITE_ERROR);
}
}
} else {
writeln!(terminal, "I{}`{}` by `{}`", args.prompt,
item.field("title").unwrap_or(&TypePrimitive::Empty).as_str(),
item.field("artist").unwrap_or(&TypePrimitive::Empty).as_str(),
).expect(TERMINAL_WRITE_ERROR);
}
//writeln!(terminal, "I{}----", args.prompt).expect(TERMINAL_WRITE_ERROR);
}
fn handle_list_rx(state: &mut ReplState, args: &CliArgs) {
//let items = state.list_rx.try_iter().collect::<Vec<_>>();
let d_state = DEBUG_STATE.read().expect("Failed to get read lock for debug state info").clone();
for item in state.list_rx.try_iter() {
match item {
Ok(item) => pretty_print_item(&item, &mut state.terminal, args, d_state.verbose),
Err(e) => error_prompt(
muss_player::PlayerError::Playback(
muss_player::PlaybackError::from_err(e)
), args),
}
}
let flag = d_state.debug_flag;
match flag {
DebugFlag::List => {
while let Ok(item) = state.list_rx.recv() {
match item {
Ok(item) => pretty_print_item(&item, &mut state.terminal, args, d_state.verbose),
Err(e) => error_prompt(
muss_player::PlayerError::Playback(
muss_player::PlaybackError::from_err(e)
), args),
}
// stop listing if no longer in list mode
let flag = if let Ok(d_state) = DEBUG_STATE.read() {
d_state.debug_flag
} else {
DebugFlag::Normal
};
match flag {
DebugFlag::List => {},
_ => break,
}
}
},
_ => {}
}
}
pub fn repl(args: CliArgs) { pub fn repl(args: CliArgs) {
let term = Term::stdout(); let term = Term::stdout();
term.set_title("mps"); term.set_title("muss");
let (writer, reader) = channel_io(); let (writer, reader) = channel_io();
let volume = args.volume.clone(); let volume = args.volume.clone();
let mpd = match args.mpd.clone().map(|a| muss_player::mpd_connection(a.parse().unwrap())).transpose() { let mpd = match args.mpd.clone().map(|a| muss_player::mpd_connection(a.parse().unwrap())).transpose() {
@ -54,10 +166,41 @@ pub fn repl(args: CliArgs) {
return; return;
} }
}; };
let (list_tx, list_rx) = mpsc::channel();
let mut state = ReplState::new(writer, term, list_rx);
let player_builder = move || { let player_builder = move || {
let runner = Interpreter::with_stream(reader); let runner = Interpreter::with_stream_and_callback(reader,
&interpreter_event_callback);
let debugger = Debugger::new(runner, move |interpretor, item| {
let flag = if let Ok(d_state) = DEBUG_STATE.read() {
d_state.debug_flag
} else {
DebugFlag::Normal
};
match flag {
DebugFlag::Normal => item,
DebugFlag::Skip => {
while let Some(_) = interpretor.next() {
// NOTE: recursion occurs here
}
None
},
DebugFlag::List => {
if let Some(x) = item {
list_tx.send(x.map_err(|e| e.to_string())).unwrap_or(());
while let Some(x) = interpretor.next() {
// NOTE: recursion occurs here
// in most cases this will never be a case of Some(...) because
// recursive calls to this function intercept it first and return None
list_tx.send(x.map_err(|e| e.to_string())).unwrap_or(());
}
}
None
}
}
});
let mut player = Player::new(runner).unwrap(); let mut player = Player::new(debugger).unwrap();
if let Some(vol) = volume { if let Some(vol) = volume {
player.set_volume(vol); player.set_volume(vol);
} }
@ -66,7 +209,6 @@ pub fn repl(args: CliArgs) {
} }
player player
}; };
let mut state = ReplState::new(writer, term);
if let Some(playlist_file) = &args.playlist { if let Some(playlist_file) = &args.playlist {
if args.mpd.is_some() { if args.mpd.is_some() {
writeln!( writeln!(
@ -74,21 +216,21 @@ pub fn repl(args: CliArgs) {
"Playlist mode (output: `{}` & MPD)", "Playlist mode (output: `{}` & MPD)",
playlist_file playlist_file
) )
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
} else { } else {
writeln!( writeln!(
state.terminal, state.terminal,
"Playlist mode (output: `{}`)", "Playlist mode (output: `{}`)",
playlist_file playlist_file
) )
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
} }
let mut player = player_builder(); let mut player = player_builder();
let mut playlist_writer = let mut playlist_writer =
io::BufWriter::new(std::fs::File::create(playlist_file).unwrap_or_else(|_| { io::BufWriter::new(std::fs::File::create(playlist_file).unwrap_or_else(|_| {
panic!("Abort: Cannot create writeable file `{}`", playlist_file) panic!("Abort: Cannot create writeable file `{}`", playlist_file)
})); }));
read_loop(&args, &mut state, || { read_loop(&args, &mut state, |state, args| {
match player.save_m3u8(&mut playlist_writer) { match player.save_m3u8(&mut playlist_writer) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
@ -102,17 +244,18 @@ pub fn repl(args: CliArgs) {
playlist_writer playlist_writer
.flush() .flush()
.expect("Failed to flush playlist to file"); .expect("Failed to flush playlist to file");
handle_list_rx(state, args);
}); });
} else { } else {
if args.mpd.is_some() { if args.mpd.is_some() {
writeln!(state.terminal, "Playback mode (output: audio device & MPD)") writeln!(state.terminal, "Playback mode (output: audio device & MPD)")
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
} else { } else {
writeln!(state.terminal, "Playback mode (output: audio device)") writeln!(state.terminal, "Playback mode (output: audio device)")
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
} }
let ctrl = Controller::create_repl(player_builder); let ctrl = Controller::create_repl(player_builder);
read_loop(&args, &mut state, || { read_loop(&args, &mut state, |state, args| {
if args.wait { if args.wait {
match ctrl.wait_for_empty() { match ctrl.wait_for_empty() {
Ok(_) => {} Ok(_) => {}
@ -130,11 +273,12 @@ pub fn repl(args: CliArgs) {
had_err = new_had_err; had_err = new_had_err;
} }
} }
handle_list_rx(state, args);
}); });
} }
} }
fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! { fn read_loop<F: FnMut(&mut ReplState, &CliArgs)>(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! {
prompt(state, args); prompt(state, args);
loop { loop {
match state match state
@ -145,22 +289,22 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
Key::Char(read_c) => { Key::Char(read_c) => {
if state.cursor_rightward_position == 0 { if state.cursor_rightward_position == 0 {
write!(state.terminal, "{}", read_c) write!(state.terminal, "{}", read_c)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
state.statement_buf.push(read_c); state.statement_buf.push(read_c);
state.current_line.push(read_c); state.current_line.push(read_c);
} else { } else {
write!(state.terminal, "{}", read_c) write!(state.terminal, "{}", read_c)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
for i in state.current_line.len() - state.cursor_rightward_position for i in state.current_line.len() - state.cursor_rightward_position
..state.current_line.len() ..state.current_line.len()
{ {
write!(state.terminal, "{}", state.current_line[i]) write!(state.terminal, "{}", state.current_line[i])
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
} }
state state
.terminal .terminal
.move_cursor_left(state.cursor_rightward_position) .move_cursor_left(state.cursor_rightward_position)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
state.statement_buf.insert( state.statement_buf.insert(
state.statement_buf.len() - state.cursor_rightward_position, state.statement_buf.len() - state.cursor_rightward_position,
read_c, read_c,
@ -194,12 +338,16 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
} }
';' => { ';' => {
if state.in_literal.is_none() { if state.in_literal.is_none() {
state let statement = state.statement_buf.iter().collect::<String>();
.writer let statement_result = statement.trim();
.write(state.statement_buf.iter().collect::<String>().as_bytes()) if !statement_result.starts_with('?') {
.expect("Failed to write to MPS interpreter"); state
execute(); .writer
state.statement_buf.clear(); .write(state.statement_buf.iter().collect::<String>().as_bytes())
.expect(INTERPRETER_WRITE_ERROR);
execute(state, args);
state.statement_buf.clear();
}
} }
} }
'\n' => { '\n' => {
@ -207,7 +355,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
let statement_result = statement.trim(); let statement_result = statement.trim();
if statement_result.starts_with('?') { if statement_result.starts_with('?') {
//println!("Got {}", statement_result.unwrap()); //println!("Got {}", statement_result.unwrap());
repl_commands(statement_result); repl_commands(statement_result, state, args);
state.statement_buf.clear(); state.statement_buf.clear();
} else if state.bracket_depth == 0 } else if state.bracket_depth == 0
&& state.in_literal.is_none() && state.in_literal.is_none()
@ -217,8 +365,8 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state state
.writer .writer
.write(state.statement_buf.iter().collect::<String>().as_bytes()) .write(state.statement_buf.iter().collect::<String>().as_bytes())
.expect("Failed to write to MPS interpreter"); .expect(INTERPRETER_WRITE_ERROR);
execute(); execute(state, args);
state.statement_buf.clear(); state.statement_buf.clear();
} }
prompt(state, args); prompt(state, args);
@ -264,9 +412,9 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state state
.terminal .terminal
.move_cursor_left(1) .move_cursor_left(1)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
write!(state.terminal, " ") write!(state.terminal, " ")
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
state state
.terminal .terminal
.flush() .flush()
@ -274,7 +422,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state state
.terminal .terminal
.move_cursor_left(1) .move_cursor_left(1)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
} }
} }
} }
@ -316,18 +464,18 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state state
.terminal .terminal
.move_cursor_left(1) .move_cursor_left(1)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
for i in state.current_line.len() - state.cursor_rightward_position for i in state.current_line.len() - state.cursor_rightward_position
..state.current_line.len() ..state.current_line.len()
{ {
write!(state.terminal, "{}", state.current_line[i]) write!(state.terminal, "{}", state.current_line[i])
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
} }
write!(state.terminal, " ").expect("Failed to write to terminal output"); write!(state.terminal, " ").expect(TERMINAL_WRITE_ERROR);
state state
.terminal .terminal
.move_cursor_left(state.cursor_rightward_position + 1) .move_cursor_left(state.cursor_rightward_position + 1)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
} }
} }
} }
@ -335,12 +483,12 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state state
.terminal .terminal
.write_line("") .write_line("")
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
let statement = state.statement_buf.iter().collect::<String>(); let statement = state.statement_buf.iter().collect::<String>();
let statement_result = statement.trim(); let statement_result = statement.trim();
if statement_result.starts_with('?') { if statement_result.starts_with('?') {
//println!("Got {}", statement_result.unwrap()); //println!("Got {}", statement_result.unwrap());
repl_commands(statement_result); repl_commands(statement_result, state, args);
state.statement_buf.clear(); state.statement_buf.clear();
} else if state.bracket_depth == 0 } else if state.bracket_depth == 0
&& state.in_literal.is_none() && state.in_literal.is_none()
@ -352,7 +500,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
.writer .writer
.write(complete_statement.as_bytes()) .write(complete_statement.as_bytes())
.expect("Failed to write to MPS interpreter"); .expect("Failed to write to MPS interpreter");
execute(); execute(state, args);
state.statement_buf.clear(); state.statement_buf.clear();
} }
state.statement_buf.push('\n'); state.statement_buf.push('\n');
@ -387,7 +535,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state state
.terminal .terminal
.clear_line() .clear_line()
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
prompt(state, args); prompt(state, args);
// clear stale input buffer // clear stale input buffer
state.statement_buf.clear(); state.statement_buf.clear();
@ -402,7 +550,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state state
.terminal .terminal
.move_cursor_left(1) .move_cursor_left(1)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
state.cursor_rightward_position += 1; state.cursor_rightward_position += 1;
} }
} }
@ -411,7 +559,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
state state
.terminal .terminal
.move_cursor_right(1) .move_cursor_right(1)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
state.cursor_rightward_position -= 1; state.cursor_rightward_position -= 1;
} }
} }
@ -425,7 +573,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
#[inline(always)] #[inline(always)]
fn prompt(state: &mut ReplState, args: &CliArgs) { fn prompt(state: &mut ReplState, args: &CliArgs) {
write!(state.terminal, "{}{}", state.line_number, args.prompt) write!(state.terminal, "{}{}", state.line_number, args.prompt)
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
state.line_number += 1; state.line_number += 1;
state state
.terminal .terminal
@ -440,13 +588,13 @@ fn display_history_line(state: &mut ReplState, args: &CliArgs) {
state state
.terminal .terminal
.clear_line() .clear_line()
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
prompt(state, args); prompt(state, args);
let new_statement = state.history[state.history.len() - state.selected_history].trim(); let new_statement = state.history[state.history.len() - state.selected_history].trim();
state state
.terminal .terminal
.write(new_statement.as_bytes()) .write(new_statement.as_bytes())
.expect("Failed to write to terminal output"); .expect(TERMINAL_WRITE_ERROR);
// clear stale input buffer // clear stale input buffer
state.statement_buf.clear(); state.statement_buf.clear();
state.current_line.clear(); state.current_line.clear();
@ -462,14 +610,45 @@ fn error_prompt(error: muss_player::PlayerError, args: &CliArgs) {
eprintln!("E{}{}", args.prompt, error); eprintln!("E{}{}", args.prompt, error);
} }
fn repl_commands(command_str: &str) { fn repl_commands(command_str: &str, state: &mut ReplState, _args: &CliArgs) {
let words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect(); let words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect();
match words[0] { match words[0] {
"?help" => println!("{}", super::help::HELP_STRING), "?help" => writeln!(state.terminal, "{}", super::help::HELP_STRING).expect(TERMINAL_WRITE_ERROR),
"?function" | "?functions" => println!("{}", super::help::FUNCTIONS), "?function" | "?functions" => writeln!(state.terminal, "{}", super::help::FUNCTIONS).expect(TERMINAL_WRITE_ERROR),
"?filter" | "?filters" => println!("{}", super::help::FILTERS), "?filter" | "?filters" => writeln!(state.terminal, "{}", super::help::FILTERS).expect(TERMINAL_WRITE_ERROR),
"?sort" | "?sorter" | "?sorters" => println!("{}", super::help::SORTERS), "?sort" | "?sorter" | "?sorters" => writeln!(state.terminal, "{}", super::help::SORTERS).expect(TERMINAL_WRITE_ERROR),
"?proc" | "?procedure" | "?procedures" => println!("{}", super::help::PROCEDURES), "?proc" | "?procedure" | "?procedures" => writeln!(state.terminal, "{}", super::help::PROCEDURES).expect(TERMINAL_WRITE_ERROR),
_ => println!("Unknown command, try ?help"), "?list" => {
{
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::List;
}
writeln!(state.terminal, "Listing upcoming items").expect(TERMINAL_WRITE_ERROR);
},
"?skip" => {
{
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::Skip;
}
writeln!(state.terminal, "Skipping upcoming items").expect(TERMINAL_WRITE_ERROR);
},
"?normal" => {
{
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state");
debug_state.debug_flag = DebugFlag::Normal;
}
writeln!(state.terminal, "Resuming normal operation").expect(TERMINAL_WRITE_ERROR);
},
"?verbose" => {
let verbose = {
let mut debug_state = DEBUG_STATE.write().expect("Failed to get write lock for debug state");
debug_state.verbose = !debug_state.verbose;
debug_state.verbose
};
writeln!(state.terminal, "Verbosed toggled to {}", verbose).expect(TERMINAL_WRITE_ERROR);
},
"?commands" => writeln!(state.terminal, "{}", super::help::REPL_COMMANDS).expect(TERMINAL_WRITE_ERROR),
_ => writeln!(state.terminal, "Unknown command, try ?help").expect(TERMINAL_WRITE_ERROR),
} }
} }