diff --git a/interpreter/src/lang/type_primitives.rs b/interpreter/src/lang/type_primitives.rs index 14cf2df..14cfc37 100644 --- a/interpreter/src/lang/type_primitives.rs +++ b/interpreter/src/lang/type_primitives.rs @@ -53,6 +53,18 @@ impl TypePrimitive { } } + /// Pretty-print the type info, without the value + pub fn type_str(&self) -> &'static str { + match self { + Self::String(_) => "String", + Self::Int(_) => "Int", + Self::UInt(_) => "UInt", + Self::Float(_) => "Float", + Self::Bool(_) => "Bool", + Self::Empty => "Empty", + } + } + pub fn to_u64(self) -> Option { match self { Self::UInt(x) => Some(x), diff --git a/src/debug_state.rs b/src/debug_state.rs index c255e6f..3fc9446 100644 --- a/src/debug_state.rs +++ b/src/debug_state.rs @@ -3,11 +3,15 @@ use muss_interpreter::Item; use muss_player::EventTap; use muss_player::{PlaybackAction, PlayerAction, ControlAction}; +use crate::repl::TERMINAL_WRITE_ERROR; + +use std::io::Write; + #[derive(Clone)] pub struct InterpreterDebugState { pub debug_flag: InterpreterDebugFlag, - pub verbose: bool, + pub verbose: Verbosity, } #[derive(Copy, Clone)] @@ -17,6 +21,134 @@ pub enum InterpreterDebugFlag { Normal, } +#[derive(Clone)] +pub struct Verbosity { + field_handlers: Vec, + debug: bool, +} + +#[derive(Clone)] +pub enum VerboseField { + ShowFieldValue(String), + ShowFieldType(String), + ShowFieldTypeValue(String), +} + +impl VerboseField { + fn print_field(&self, item: &Item, terminal: &mut console::Term, args: &crate::cli::CliArgs) -> bool { + match self { + Self::ShowFieldValue(f) => { + if let Some(field) = item.field(f) { + let field_str = field.as_str(); + let max_len = terminal.size().1 as usize - (args.prompt.len() + f.len() + 6); + if field_str.len() >= max_len { + write!( + terminal, + " {}{}: `{}[...]`", + args.prompt, + f, + &field_str[..max_len - 5] + ) + .expect(TERMINAL_WRITE_ERROR); + } else { + write!(terminal, " {}{}: `{}`", args.prompt, f, field_str).expect(TERMINAL_WRITE_ERROR); + } + true + } else { + false + } + }, + Self::ShowFieldType(f) => { + if let Some(field) = item.field(f) { + let field_str = field.type_str(); + write!(terminal, " {}{}: {}", args.prompt, f, field_str).expect(TERMINAL_WRITE_ERROR); + true + } else { + false + } + }, + Self::ShowFieldTypeValue(f) => { + if let Some(field) = item.field(f) { + let field_str = format!("{}", field); + let max_len = terminal.size().1 as usize - (args.prompt.len() + f.len() + 4); + if field_str.len() >= max_len { + write!( + terminal, + " {}{}: {}[...]]", + args.prompt, + f, + &field_str[..max_len - 5] + ) + .expect(TERMINAL_WRITE_ERROR); + } else { + write!(terminal, " {}{}: {}", args.prompt, f, field_str).expect(TERMINAL_WRITE_ERROR); + } + true + } else { + false + } + } + } + } +} + +impl Verbosity { + pub fn print_item(&self, item: &Item, terminal: &mut console::Term, args: &crate::cli::CliArgs) { + Self::item_prompt(terminal, args); + if self.debug { + writeln!(terminal, "--\\/-- \\/ --\\/--").expect(TERMINAL_WRITE_ERROR); + if self.field_handlers.is_empty() { + let mut fields: Vec<_> = item.iter().collect(); + fields.sort(); + for field in fields { + VerboseField::ShowFieldValue(field.to_owned()).print_field(item, terminal, args); + writeln!(terminal, "").expect(TERMINAL_WRITE_ERROR); + } + } else { + for vf in &self.field_handlers { + let has_printed = vf.print_field(item, terminal, args); + if has_printed { + writeln!(terminal, "").expect(TERMINAL_WRITE_ERROR); + } + } + } + } else { + writeln!( + terminal, + "`{}` by `{}`", + item.field("title") + .unwrap_or(&muss_interpreter::lang::TypePrimitive::Empty) + .as_str(), + item.field("artist") + .unwrap_or(&muss_interpreter::lang::TypePrimitive::Empty) + .as_str(), + ) + .expect(TERMINAL_WRITE_ERROR); + } + } + + pub fn verbose_mut(&mut self) -> &'_ mut bool { + &mut self.debug + } + + pub fn verbose(&mut self) -> &'_ bool { + &self.debug + } + + pub fn handlers(&mut self, handlers: Vec) { + self.field_handlers = handlers; + } + + pub fn clear_handlers(&mut self) { + self.field_handlers.clear(); + } + + #[inline] + fn item_prompt(terminal: &mut console::Term, args: &crate::cli::CliArgs) { + write!(terminal, "I{}", args.prompt).expect(TERMINAL_WRITE_ERROR); + } +} + #[derive(Clone)] pub struct DebugState { pub now_playing: std::sync::Arc>>, @@ -31,7 +163,10 @@ impl DebugState { control_tx: std::sync::Arc::new(std::sync::Mutex::new(None)), interpreter: std::sync::Arc::new(std::sync::RwLock::new(InterpreterDebugState { debug_flag: InterpreterDebugFlag::Normal, - verbose: false, + verbose: Verbosity { + field_handlers: Vec::new(), + debug: false, + }, })), } } diff --git a/src/repl.rs b/src/repl.rs index dd7f7c0..67d204f 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -5,7 +5,6 @@ use std::sync::mpsc::{self, Receiver}; use console::{Key, Term}; -use muss_interpreter::lang::TypePrimitive; use muss_interpreter::{Debugger, Interpreter, InterpreterError, InterpreterEvent, Item}; use muss_player::{Controller, Player}; @@ -75,57 +74,6 @@ fn interpreter_event_callback<'a, T: muss_interpreter::tokens::TokenReader>( } } -#[inline] -fn item_prompt(terminal: &mut Term, args: &CliArgs) { - write!(terminal, "*I{}", args.prompt).expect(TERMINAL_WRITE_ERROR); -} - -fn pretty_print_item(item: &Item, terminal: &mut Term, args: &CliArgs, verbose: bool) { - item_prompt(terminal, args); - if verbose { - writeln!( - terminal, - "--\\/-- `{}` --\\/--", - 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" { - let field_str = item.field(field).unwrap_or(&TypePrimitive::Empty).as_str(); - let max_len = terminal.size().1 as usize - (args.prompt.len() + field.len() + 8); - if field_str.len() >= max_len { - writeln!( - terminal, - " {}: `{}[...]`", - field, - &field_str[..max_len - 5] - ) - .expect(TERMINAL_WRITE_ERROR); - } else { - writeln!(terminal, " {}: `{}`", field, field_str).expect(TERMINAL_WRITE_ERROR); - } - } - } - } else { - writeln!( - terminal, - "`{}` by `{}`", - 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::>(); let d_state = state.controller_debug.interpreter @@ -134,7 +82,7 @@ fn handle_list_rx(state: &mut ReplState, args: &CliArgs) { .clone(); for item in state.list_rx.try_iter() { match item { - Ok(item) => pretty_print_item(&item, &mut state.terminal, args, d_state.verbose), + Ok(item) => d_state.verbose.print_item(&item, &mut state.terminal, args), Err(e) => error_prompt( muss_player::PlayerError::Playback(muss_player::PlaybackError::from_err(e)), args, @@ -148,8 +96,11 @@ fn handle_list_rx(state: &mut ReplState, args: &CliArgs) { while let Ok(item) = state.list_rx.recv() { match item { Ok(item) => { - let verbose = state.controller_debug.interpreter.read().map(|x| x.verbose).unwrap_or(false); - pretty_print_item(&item, &mut state.terminal, args, verbose) + state.controller_debug.interpreter + .read() + .expect("Failed to get read lock for debug state info") + .verbose + .print_item(&item, &mut state.terminal, args) } Err(e) => error_prompt( muss_player::PlayerError::Playback(muss_player::PlaybackError::from_err(e)), @@ -680,7 +631,7 @@ fn error_prompt(error: muss_player::PlayerError, args: &CliArgs) { } fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) { - let words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect(); + let mut words: Vec<&str> = command_str.split(' ').map(|s| s.trim()).collect(); match words[0] { "?help" => { writeln!(state.terminal, "{}", super::help::HELP_STRING).expect(TERMINAL_WRITE_ERROR) @@ -729,10 +680,38 @@ fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) { let mut debug_state = state.controller_debug.interpreter .write() .expect("Failed to get write lock for debug state"); - debug_state.verbose = !debug_state.verbose; - debug_state.verbose + if *debug_state.verbose.verbose() && words.len() == 1 { + *debug_state.verbose.verbose_mut() = false; + } else if !*debug_state.verbose.verbose() && words.len() == 1 { + *debug_state.verbose.verbose_mut() = true; + debug_state.verbose.clear_handlers(); + } else { + *debug_state.verbose.verbose_mut() = true; + debug_state.verbose.handlers( + words.split_off(1) + .into_iter() + .filter_map(|arg| { + const HANDLER_SPLIT_CHAR: char = ':'; + if arg.contains(HANDLER_SPLIT_CHAR) { + let mut iter = arg.split(HANDLER_SPLIT_CHAR); + let handler = iter.next().unwrap(); + let field = if let Some(f) = iter.next() { f } else { return None }; + match handler { + "v" | "value" => Some(crate::debug_state::VerboseField::ShowFieldValue(field.to_owned())), + "t" | "type" => Some(crate::debug_state::VerboseField::ShowFieldType(field.to_owned())), + "vt" | "valuetype" => Some(crate::debug_state::VerboseField::ShowFieldTypeValue(field.to_owned())), + _ => None + } + } else { + Some(crate::debug_state::VerboseField::ShowFieldValue(arg.to_owned())) + } + }) + .collect() + ); + } + *debug_state.verbose.verbose() }; - writeln!(state.terminal, "Verbosed toggled to {}", verbose) + writeln!(state.terminal, "Verbose toggled to {}", verbose) .expect(TERMINAL_WRITE_ERROR); } "?commands" => { @@ -740,11 +719,11 @@ fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) { } "?now" => { if let Some(item) = crate::playlists::get_current_item(state) { - let verbose = state.controller_debug.interpreter + state.controller_debug.interpreter .read() .expect("Failed to get read lock for debug state") - .verbose; - pretty_print_item(&item, &mut state.terminal, args, verbose); + .verbose + .print_item(&item, &mut state.terminal, args); } else { writeln!(state.terminal, "Nothing playing").expect(TERMINAL_WRITE_ERROR) }