Add ?skip and ?list REPL commands and fix some missed names
This commit is contained in:
parent
c76562fa84
commit
0202c28385
17 changed files with 322 additions and 102 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1230,17 +1230,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "muss"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"console",
|
||||
"lazy_static 1.4.0",
|
||||
"muss-interpreter",
|
||||
"muss-player",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "muss-interpreter"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"bliss-audio-symphonia",
|
||||
"criterion",
|
||||
|
@ -1256,7 +1257,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "muss-m3u8"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"m3u8-rs",
|
||||
|
@ -1265,7 +1266,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "muss-player"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"fluent-uri",
|
||||
"m3u8-rs",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "muss"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
edition = "2021"
|
||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||
description = "Music Set Script language (MuSS)"
|
||||
|
@ -19,17 +19,18 @@ members = [
|
|||
|
||||
[dependencies]
|
||||
# local
|
||||
muss-interpreter = { version = "0.8.0", path = "./interpreter" }
|
||||
muss-interpreter = { version = "0.9.0", path = "./interpreter" }
|
||||
# external
|
||||
clap = { version = "3.0", features = ["derive"] }
|
||||
console = { version = "0.15" }
|
||||
lazy_static = { version = "1.4" }
|
||||
|
||||
[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]
|
||||
# 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]
|
||||
debug = false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "muss-interpreter"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
edition = "2021"
|
||||
license = "LGPL-2.1-only OR GPL-3.0-only"
|
||||
readme = "README.md"
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
use std::iter::Iterator;
|
||||
|
||||
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.
|
||||
pub struct Debugger<'a, 'b, T>
|
||||
pub struct Debugger<'a, T, F>
|
||||
where
|
||||
T: TokenReader,
|
||||
F: Fn(
|
||||
&mut Interpreter<'a, T>,
|
||||
Option<InterpreterItem>,
|
||||
) -> Option<InterpreterItem>,
|
||||
{
|
||||
interpreter: Interpreter<'a, T>,
|
||||
transmuter: &'b dyn Fn(
|
||||
&mut Interpreter<'a, T>,
|
||||
Option<Result<Item, InterpreterError>>,
|
||||
) -> Option<Result<Item, InterpreterError>>,
|
||||
transmuter: F,
|
||||
}
|
||||
|
||||
impl<'a, 'b, T> Debugger<'a, 'b, T>
|
||||
impl<'a, T, F> Debugger<'a, T, F>
|
||||
where
|
||||
T: TokenReader,
|
||||
F: Fn(
|
||||
&mut Interpreter<'a, T>,
|
||||
Option<InterpreterItem>,
|
||||
) -> Option<InterpreterItem>,
|
||||
{
|
||||
/// Create a new instance of Debugger using the provided interpreter and callback.
|
||||
pub fn new(
|
||||
faye: Interpreter<'a, T>,
|
||||
item_handler: &'b dyn Fn(
|
||||
&mut Interpreter<'a, T>,
|
||||
Option<Result<Item, InterpreterError>>,
|
||||
) -> Option<Result<Item, InterpreterError>>,
|
||||
item_handler: F,
|
||||
) -> Self {
|
||||
Self {
|
||||
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
|
||||
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> {
|
||||
let next_item = self.interpreter.next();
|
||||
|
|
|
@ -60,6 +60,12 @@ impl<'a, R: Read> Interpreter<'a, Tokenizer<R>> {
|
|||
let tokenizer = Tokenizer::new(stream);
|
||||
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>
|
||||
|
@ -127,11 +133,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub type InterpreterItem = Result<Item, InterpreterError>;
|
||||
|
||||
impl<'a, T> Iterator for Interpreter<'a, T>
|
||||
where
|
||||
T: TokenReader,
|
||||
{
|
||||
type Item = Result<Item, InterpreterError>;
|
||||
type Item = InterpreterItem;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
|
|
|
@ -33,6 +33,7 @@ impl TypePrimitive {
|
|||
}
|
||||
}
|
||||
|
||||
/// Pretty-print the value, without type info
|
||||
pub fn as_str(&self) -> String {
|
||||
match self {
|
||||
Self::String(s) => s.clone(),
|
||||
|
|
|
@ -263,7 +263,7 @@ pub mod tokens;
|
|||
pub use context::Context;
|
||||
pub use debug::Debugger;
|
||||
pub use errors::InterpreterError;
|
||||
pub use faye::{Interpreter, InterpreterEvent};
|
||||
pub use faye::{Interpreter, InterpreterEvent, InterpreterItem};
|
||||
//pub use interpretor::{interpretor, Interpretor};
|
||||
pub use item::Item;
|
||||
//pub(crate) use item::ItemRuntimeUtil;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "muss-m3u8"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
edition = "2021"
|
||||
authors = ["NGnius <ngniusness@gmail.com>"]
|
||||
description = "Minimal playlist generator for MPS files"
|
||||
|
@ -13,7 +13,7 @@ readme = "README.md"
|
|||
|
||||
[dependencies]
|
||||
# local
|
||||
muss-interpreter = { version = "0.8.0", path = "../interpreter" }
|
||||
muss-interpreter = { version = "0.9.0", path = "../interpreter" }
|
||||
# external
|
||||
clap = { version = "3.0", features = ["derive"] }
|
||||
m3u8-rs = { version = "^3.0.0" }
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::path::Path;
|
|||
|
||||
use m3u8_rs::{MediaPlaylist, MediaSegment};
|
||||
|
||||
use mps_interpreter::{Interpreter, Item};
|
||||
use muss_interpreter::{Interpreter, Item};
|
||||
|
||||
fn main() {
|
||||
let args = cli::parse();
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
[Desktop Entry]
|
||||
Categories=AudioVideo;Player;
|
||||
Comment=
|
||||
Exec=mps %U
|
||||
Exec=muss %U
|
||||
GenericName=Music Player
|
||||
Icon=utilities-terminal
|
||||
Name=MPS
|
||||
Name=Muss
|
||||
StartupNotify=false
|
||||
Terminal=true
|
||||
Type=Application
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "muss-player"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
edition = "2021"
|
||||
license = "LGPL-2.1-only OR GPL-3.0-only"
|
||||
readme = "README.md"
|
||||
|
@ -12,7 +12,7 @@ fluent-uri = { version = "^0.1" }
|
|||
mpd = { version = "0.0.12", optional = true }
|
||||
|
||||
# local
|
||||
muss-interpreter = { path = "../interpreter", version = "0.8.0" }
|
||||
muss-interpreter = { path = "../interpreter", version = "0.9.0" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
#dbus = { version = "^0.9" }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use muss_interpreter::tokens::TokenReader;
|
||||
use muss_interpreter::{Item, InterpreterError};
|
||||
|
||||
use super::os_controls::SystemControlWrapper;
|
||||
use super::player_wrapper::{ControlAction, PlayerServer, PlayerAction};
|
||||
|
@ -20,9 +20,8 @@ pub struct Controller {
|
|||
|
||||
impl Controller {
|
||||
pub fn create<
|
||||
'a,
|
||||
F: FnOnce() -> Player<'a, T> + Send + 'static,
|
||||
T: TokenReader + 'static,
|
||||
F: FnOnce() -> Player<I> + Send + 'static,
|
||||
I: std::iter::Iterator<Item=Result<Item, InterpreterError>>,
|
||||
>(
|
||||
player_gen: F,
|
||||
) -> Self {
|
||||
|
@ -48,9 +47,8 @@ impl Controller {
|
|||
}
|
||||
|
||||
pub fn create_repl<
|
||||
'a,
|
||||
F: FnOnce() -> Player<'a, T> + Send + 'static,
|
||||
T: TokenReader + 'static,
|
||||
F: FnOnce() -> Player<I> + Send + 'static,
|
||||
I: std::iter::Iterator<Item=Result<Item, InterpreterError>>,
|
||||
>(
|
||||
player_gen: F,
|
||||
) -> Self {
|
||||
|
|
|
@ -59,7 +59,7 @@ impl SystemControlWrapper {
|
|||
self.dbus_ctrl = Some(tx);
|
||||
let control_clone1 = self.control.clone();
|
||||
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();
|
||||
// dbus setup
|
||||
//self.dbus_conn.set_supported_mime_types(vec![]);
|
||||
|
|
|
@ -10,16 +10,18 @@ use mpd::{Client, Song, error};
|
|||
|
||||
use super::uri::Uri;
|
||||
|
||||
use muss_interpreter::{tokens::TokenReader, Interpreter, Item};
|
||||
use muss_interpreter::{Item, InterpreterError};
|
||||
|
||||
//use super::PlaybackError;
|
||||
use super::PlayerError;
|
||||
use super::UriError;
|
||||
|
||||
//type Interpreter = std::iter::Iterator<Item=Result<Item, InterpreterError>>;
|
||||
|
||||
/// Playback functionality for a script.
|
||||
/// This takes the output of the runner and plays or saves it.
|
||||
pub struct Player<'a, T: TokenReader + 'a> {
|
||||
runner: Interpreter<'a, T>,
|
||||
pub struct Player<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> {
|
||||
runner: I,
|
||||
sink: Sink,
|
||||
#[allow(dead_code)]
|
||||
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>>,
|
||||
}
|
||||
|
||||
impl<'a, T: TokenReader + 'a> Player<'a, T> {
|
||||
pub fn new(runner: Interpreter<'a, T>) -> Result<Self, PlayerError> {
|
||||
impl<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> Player<I> {
|
||||
pub fn new(runner: I) -> Result<Self, PlayerError> {
|
||||
let (stream, output_handle) =
|
||||
OutputStream::try_default().map_err(PlayerError::from_err_playback)?;
|
||||
Ok(Self {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::{thread, thread::JoinHandle};
|
||||
|
||||
use muss_interpreter::tokens::TokenReader;
|
||||
use muss_interpreter::Item;
|
||||
//use muss_interpreter::tokens::TokenReader;
|
||||
use muss_interpreter::{Item, InterpreterError};
|
||||
|
||||
use super::Player;
|
||||
use super::PlayerError;
|
||||
|
@ -11,17 +11,17 @@ use super::PlayerError;
|
|||
/// This allows for message passing between the player and controller.
|
||||
///
|
||||
/// You will probably never directly interact with this, instead using Controller to communicate.
|
||||
pub struct PlayerServer<'a, T: TokenReader + 'a> {
|
||||
player: Player<'a, T>,
|
||||
pub struct PlayerServer<I: std::iter::Iterator<Item=Result<Item, InterpreterError>>> {
|
||||
player: Player<I>,
|
||||
control: Receiver<ControlAction>,
|
||||
event: Sender<PlayerAction>,
|
||||
playback: Sender<PlaybackAction>,
|
||||
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(
|
||||
player: Player<'a, T>,
|
||||
player: Player<I>,
|
||||
ctrl: Receiver<ControlAction>,
|
||||
event: Sender<PlayerAction>,
|
||||
playback: Sender<PlaybackAction>,
|
||||
|
@ -146,7 +146,7 @@ impl<'a, T: TokenReader + 'a> PlayerServer<'a, T> {
|
|||
self.on_end();
|
||||
}
|
||||
|
||||
pub fn spawn<F: FnOnce() -> Player<'a, T> + Send + 'static>(
|
||||
pub fn spawn<F: FnOnce() -> Player<I> + Send + 'static>(
|
||||
factory: F,
|
||||
ctrl_tx: Sender<ControlAction>,
|
||||
ctrl_rx: Receiver<ControlAction>,
|
||||
|
|
26
src/help.rs
26
src/help.rs
|
@ -2,7 +2,8 @@
|
|||
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.
|
||||
|
||||
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 =
|
||||
"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`)
|
||||
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.";
|
||||
|
|
279
src/repl.rs
279
src/repl.rs
|
@ -1,15 +1,33 @@
|
|||
//! Read, Execute, Print Loop functionality
|
||||
|
||||
use std::sync::{RwLock};
|
||||
use std::sync::mpsc::{self, Receiver};
|
||||
use std::io::{self, Write};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
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 super::channel_io::{channel_io, ChannelWriter};
|
||||
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 {
|
||||
terminal: Term,
|
||||
line_number: usize,
|
||||
|
@ -22,10 +40,25 @@ struct ReplState {
|
|||
selected_history: usize,
|
||||
current_line: Vec<char>,
|
||||
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 {
|
||||
fn new(chan_writer: ChannelWriter, term: Term) -> Self {
|
||||
fn new(chan_writer: ChannelWriter, term: Term, debug_list: Receiver<DebugItem>) -> Self {
|
||||
Self {
|
||||
terminal: term,
|
||||
line_number: 0,
|
||||
|
@ -38,13 +71,92 @@ impl ReplState {
|
|||
selected_history: 0,
|
||||
current_line: Vec::new(),
|
||||
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) {
|
||||
let term = Term::stdout();
|
||||
term.set_title("mps");
|
||||
term.set_title("muss");
|
||||
let (writer, reader) = channel_io();
|
||||
let volume = args.volume.clone();
|
||||
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;
|
||||
}
|
||||
};
|
||||
let (list_tx, list_rx) = mpsc::channel();
|
||||
let mut state = ReplState::new(writer, term, list_rx);
|
||||
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 {
|
||||
player.set_volume(vol);
|
||||
}
|
||||
|
@ -66,7 +209,6 @@ pub fn repl(args: CliArgs) {
|
|||
}
|
||||
player
|
||||
};
|
||||
let mut state = ReplState::new(writer, term);
|
||||
if let Some(playlist_file) = &args.playlist {
|
||||
if args.mpd.is_some() {
|
||||
writeln!(
|
||||
|
@ -74,21 +216,21 @@ pub fn repl(args: CliArgs) {
|
|||
"Playlist mode (output: `{}` & MPD)",
|
||||
playlist_file
|
||||
)
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
} else {
|
||||
writeln!(
|
||||
state.terminal,
|
||||
"Playlist mode (output: `{}`)",
|
||||
playlist_file
|
||||
)
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
}
|
||||
let mut player = player_builder();
|
||||
let mut playlist_writer =
|
||||
io::BufWriter::new(std::fs::File::create(playlist_file).unwrap_or_else(|_| {
|
||||
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) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
|
@ -102,17 +244,18 @@ pub fn repl(args: CliArgs) {
|
|||
playlist_writer
|
||||
.flush()
|
||||
.expect("Failed to flush playlist to file");
|
||||
handle_list_rx(state, args);
|
||||
});
|
||||
} else {
|
||||
if args.mpd.is_some() {
|
||||
writeln!(state.terminal, "Playback mode (output: audio device & MPD)")
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
} else {
|
||||
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);
|
||||
read_loop(&args, &mut state, || {
|
||||
read_loop(&args, &mut state, |state, args| {
|
||||
if args.wait {
|
||||
match ctrl.wait_for_empty() {
|
||||
Ok(_) => {}
|
||||
|
@ -130,11 +273,12 @@ pub fn repl(args: CliArgs) {
|
|||
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);
|
||||
loop {
|
||||
match state
|
||||
|
@ -145,22 +289,22 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
Key::Char(read_c) => {
|
||||
if state.cursor_rightward_position == 0 {
|
||||
write!(state.terminal, "{}", read_c)
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
state.statement_buf.push(read_c);
|
||||
state.current_line.push(read_c);
|
||||
} else {
|
||||
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
|
||||
..state.current_line.len()
|
||||
{
|
||||
write!(state.terminal, "{}", state.current_line[i])
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
}
|
||||
state
|
||||
.terminal
|
||||
.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.len() - state.cursor_rightward_position,
|
||||
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() {
|
||||
state
|
||||
.writer
|
||||
.write(state.statement_buf.iter().collect::<String>().as_bytes())
|
||||
.expect("Failed to write to MPS interpreter");
|
||||
execute();
|
||||
state.statement_buf.clear();
|
||||
let statement = state.statement_buf.iter().collect::<String>();
|
||||
let statement_result = statement.trim();
|
||||
if !statement_result.starts_with('?') {
|
||||
state
|
||||
.writer
|
||||
.write(state.statement_buf.iter().collect::<String>().as_bytes())
|
||||
.expect(INTERPRETER_WRITE_ERROR);
|
||||
execute(state, args);
|
||||
state.statement_buf.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
'\n' => {
|
||||
|
@ -207,7 +355,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
let statement_result = statement.trim();
|
||||
if statement_result.starts_with('?') {
|
||||
//println!("Got {}", statement_result.unwrap());
|
||||
repl_commands(statement_result);
|
||||
repl_commands(statement_result, state, args);
|
||||
state.statement_buf.clear();
|
||||
} else if state.bracket_depth == 0
|
||||
&& state.in_literal.is_none()
|
||||
|
@ -217,8 +365,8 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
state
|
||||
.writer
|
||||
.write(state.statement_buf.iter().collect::<String>().as_bytes())
|
||||
.expect("Failed to write to MPS interpreter");
|
||||
execute();
|
||||
.expect(INTERPRETER_WRITE_ERROR);
|
||||
execute(state, args);
|
||||
state.statement_buf.clear();
|
||||
}
|
||||
prompt(state, args);
|
||||
|
@ -264,9 +412,9 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
state
|
||||
.terminal
|
||||
.move_cursor_left(1)
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
write!(state.terminal, " ")
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
state
|
||||
.terminal
|
||||
.flush()
|
||||
|
@ -274,7 +422,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
state
|
||||
.terminal
|
||||
.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
|
||||
.terminal
|
||||
.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
|
||||
..state.current_line.len()
|
||||
{
|
||||
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
|
||||
.terminal
|
||||
.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
|
||||
.terminal
|
||||
.write_line("")
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
let statement = state.statement_buf.iter().collect::<String>();
|
||||
let statement_result = statement.trim();
|
||||
if statement_result.starts_with('?') {
|
||||
//println!("Got {}", statement_result.unwrap());
|
||||
repl_commands(statement_result);
|
||||
repl_commands(statement_result, state, args);
|
||||
state.statement_buf.clear();
|
||||
} else if state.bracket_depth == 0
|
||||
&& state.in_literal.is_none()
|
||||
|
@ -352,7 +500,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
.writer
|
||||
.write(complete_statement.as_bytes())
|
||||
.expect("Failed to write to MPS interpreter");
|
||||
execute();
|
||||
execute(state, args);
|
||||
state.statement_buf.clear();
|
||||
}
|
||||
state.statement_buf.push('\n');
|
||||
|
@ -387,7 +535,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
state
|
||||
.terminal
|
||||
.clear_line()
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
prompt(state, args);
|
||||
// clear stale input buffer
|
||||
state.statement_buf.clear();
|
||||
|
@ -402,7 +550,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
state
|
||||
.terminal
|
||||
.move_cursor_left(1)
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
state.cursor_rightward_position += 1;
|
||||
}
|
||||
}
|
||||
|
@ -411,7 +559,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
state
|
||||
.terminal
|
||||
.move_cursor_right(1)
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
state.cursor_rightward_position -= 1;
|
||||
}
|
||||
}
|
||||
|
@ -425,7 +573,7 @@ fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F)
|
|||
#[inline(always)]
|
||||
fn prompt(state: &mut ReplState, args: &CliArgs) {
|
||||
write!(state.terminal, "{}{}", state.line_number, args.prompt)
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
state.line_number += 1;
|
||||
state
|
||||
.terminal
|
||||
|
@ -440,13 +588,13 @@ fn display_history_line(state: &mut ReplState, args: &CliArgs) {
|
|||
state
|
||||
.terminal
|
||||
.clear_line()
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
prompt(state, args);
|
||||
let new_statement = state.history[state.history.len() - state.selected_history].trim();
|
||||
state
|
||||
.terminal
|
||||
.write(new_statement.as_bytes())
|
||||
.expect("Failed to write to terminal output");
|
||||
.expect(TERMINAL_WRITE_ERROR);
|
||||
// clear stale input buffer
|
||||
state.statement_buf.clear();
|
||||
state.current_line.clear();
|
||||
|
@ -462,14 +610,45 @@ fn error_prompt(error: muss_player::PlayerError, args: &CliArgs) {
|
|||
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();
|
||||
match words[0] {
|
||||
"?help" => println!("{}", super::help::HELP_STRING),
|
||||
"?function" | "?functions" => println!("{}", super::help::FUNCTIONS),
|
||||
"?filter" | "?filters" => println!("{}", super::help::FILTERS),
|
||||
"?sort" | "?sorter" | "?sorters" => println!("{}", super::help::SORTERS),
|
||||
"?proc" | "?procedure" | "?procedures" => println!("{}", super::help::PROCEDURES),
|
||||
_ => println!("Unknown command, try ?help"),
|
||||
"?help" => writeln!(state.terminal, "{}", super::help::HELP_STRING).expect(TERMINAL_WRITE_ERROR),
|
||||
"?function" | "?functions" => writeln!(state.terminal, "{}", super::help::FUNCTIONS).expect(TERMINAL_WRITE_ERROR),
|
||||
"?filter" | "?filters" => writeln!(state.terminal, "{}", super::help::FILTERS).expect(TERMINAL_WRITE_ERROR),
|
||||
"?sort" | "?sorter" | "?sorters" => writeln!(state.terminal, "{}", super::help::SORTERS).expect(TERMINAL_WRITE_ERROR),
|
||||
"?proc" | "?procedure" | "?procedures" => writeln!(state.terminal, "{}", super::help::PROCEDURES).expect(TERMINAL_WRITE_ERROR),
|
||||
"?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),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue