use std::path::Path;
use playr_core::settings::toml::de::DeValue;
use playr_core::settings::toml::Spanned;
use playr_core::settings::{kind, Settings};
use crate::action::{Key, Keymap};
use crate::command::{self, view_name};
use crate::View;
pub use playr_core::settings::default_path;
pub const DEFAULT_KEYS: &str = include_str!("keys.toml");
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub settings: Settings,
pub keys: Keymap,
}
impl Default for Config {
fn default() -> Self {
let mut config = Config {
settings: Settings::default(),
keys: Keymap::empty(),
};
if let Err(errors) = config.apply(DEFAULT_KEYS) {
panic!("bad default key bindings: {errors:?}");
}
config
}
}
impl Config {
pub fn parse(text: &str) -> Result<Config, Vec<String>> {
let mut config = Config::default();
config.apply(text).map(|()| config)
}
pub fn load(path: &Path, required: bool) -> Result<Config, Vec<String>> {
let text = match std::fs::read_to_string(path) {
Ok(text) => text,
Err(e) if !required && e.kind() == std::io::ErrorKind::NotFound => {
return Ok(Config::default())
}
Err(e) => return Err(vec![format!("{}: {e}", path.display())]),
};
Config::parse(&text).map_err(|errors| {
errors
.into_iter()
.map(|e| format!("{}: {e}", path.display()))
.collect()
})
}
fn apply(&mut self, text: &str) -> Result<(), Vec<String>> {
let (tables, mut errors) = self.settings.apply(text, &["keys"]);
for (name, value) in tables {
match value.get_ref() {
DeValue::Table(keys) => {
for (key, target) in keys {
match target.get_ref() {
DeValue::Table(bindings) => match command::view_named(key.get_ref()) {
Some(view) => {
for (key, target) in bindings {
if let Err(e) = self.bind(Some(view), key, target) {
errors.add(target.span().start, e);
}
}
}
None => errors.add(
key.span().start,
format!(
"[keys.{}] is not a view; views: library, selection, playlists, sampler",
key.get_ref()
),
),
},
_ => {
if let Err(e) = self.bind(None, key, target) {
errors.add(target.span().start, e);
}
}
}
}
}
v => errors.add(
value.span().start,
format!("{} cannot be {}", name.get_ref(), kind(v)),
),
}
}
errors.finish()
}
fn bind(
&mut self,
view: Option<View>,
key: &Spanned<std::borrow::Cow<str>>,
target: &Spanned<DeValue>,
) -> Result<(), String> {
let name = key.get_ref();
let key = Key::parse(name)?;
let DeValue::String(target) = target.get_ref() else {
return Err(format!(
"{name} must be a command string, not {}",
kind(target.get_ref())
));
};
let action = command::key_target(target, view).map_err(|e| {
match command::only_view(target).filter(|v| view != Some(*v)) {
Some(v) => format!("{e}; put {name} under [keys.{}]", view_name(v)),
None => e,
}
})?;
self.keys.bind(view, key, action);
Ok(())
}
}