diff --git a/Cargo.lock b/Cargo.lock index 9d6c381f..cf2b08e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2390,6 +2390,7 @@ dependencies = [ "proptest", "proptest-derive", "rand 0.8.5", + "tek_config", "tek_device", "tek_engine", "tengri", @@ -2406,6 +2407,14 @@ dependencies = [ "tek", ] +[[package]] +name = "tek_config" +version = "0.2.1" +dependencies = [ + "tengri", + "xdg", +] + [[package]] name = "tek_device" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 5d15805f..1506700f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ resolver = "2" members = [ "./crates/engine", "./crates/device", + "./crates/config", "./crates/app", "./crates/cli", ] @@ -34,6 +35,7 @@ path = "./deps/rust-jack" [workspace.dependencies] tek_device = { path = "./crates/device" } tek_engine = { path = "./crates/engine" } +tek_config = { path = "./crates/config" } tek = { path = "./crates/app" } tek_cli = { path = "./crates/cli" } diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 06ce641b..397f379b 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -7,8 +7,9 @@ version = { workspace = true } tengri = { workspace = true } tengri_proc = { workspace = true } -tek_engine = { workspace = true } +tek_config = { workspace = true } tek_device = { workspace = true } +tek_engine = { workspace = true } backtrace = { workspace = true } clap = { workspace = true, optional = true } diff --git a/crates/app/app.rs b/crates/app/app.rs index 5f9cac56..bdd705c7 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -1,13 +1,12 @@ #![allow(unused, clippy::unit_arg)] #![feature(adt_const_params, associated_type_defaults, if_let_guard, impl_trait_in_assoc_type, type_alias_impl_trait, trait_alias, type_changing_struct_update, closure_lifetime_binder)] +#[cfg(test)] mod app_test; mod app_deps; pub use self::app_deps::*; mod app_jack; pub use self::app_jack::*; mod app_bind; pub use self::app_bind::*; mod app_data; pub use self::app_data::*; mod app_view; pub use self::app_view::*; - -#[cfg(test)] mod app_test; /// Total state #[derive(Default, Debug)] pub struct App { @@ -32,23 +31,6 @@ pub struct App { /// Base color. pub color: ItemTheme, } -/// Configuration -#[derive(Default, Debug)] -pub struct Config { - pub dirs: BaseDirectories, - pub modes: Arc, Arc>>>>>, - pub views: Arc, Arc>>>, - pub binds: Arc, EventMap>>>>, -} -#[derive(Default, Debug)] -pub struct Mode { - pub path: PathBuf, - pub name: Vec, - pub info: Vec, - pub view: Vec, - pub keys: Vec, - pub modes: BTreeMap>, -} /// Various possible dialog modes. #[derive(Debug, Clone, Default)] pub enum Dialog { @@ -60,107 +42,6 @@ pub enum Dialog { Browser(BrowserTarget, Arc), Options, } -impl Config { - const CONFIG: &'static str = "tek.edn"; - const DEFAULTS: &'static str = include_str!("../../tek.edn"); - pub fn init () -> Usually { - let mut cfgs: Self = Default::default(); - cfgs.dirs = BaseDirectories::with_profile("tek", "v0"); - cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?; - Ok(cfgs) - } - pub fn init_file ( - &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> - ) -> Usually<()> { - if self.dirs.find_config_file(path).is_none() { - println!("Creating {path:?}"); - std::fs::write(self.dirs.place_config_file(path)?, defaults); - } - Ok(if let Some(path) = self.dirs.find_config_file(path) { - println!("Loading {path:?}"); - let src = std::fs::read_to_string(&path)?; - src.as_str().each(move|item|each(self, item))?; - } else { - return Err(format!("{path}: not found").into()) - }) - } - pub fn load (&mut self, dsl: impl Dsl) -> Usually<()> { - dsl.each(|item|if let Some(expr) = item.expr()? { - let head = expr.head()?; - let tail = expr.tail()?; - let name = tail.head()?; - let body = tail.tail()?; - println!("{} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); - match head { - Some("view") if let Some(name) = name => self.load_view(name.into(), body), - Some("keys") if let Some(name) = name => self.load_bind(name.into(), body), - Some("mode") if let Some(name) = name => self.load_mode(name.into(), body), - _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) - } - } else { - return Err(format!("Config::load: expected expr, got: {item:?}").into()) - }) - } - pub fn load_view (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { - self.views.write().unwrap().insert(id, dsl.src()?.unwrap_or_default().into()); - Ok(()) - } - pub fn load_bind (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { - let mut map = EventMap::new(); - dsl.each(|item|if item.expr().head() == Ok(Some("see")) { - // TODO - Ok(()) - } else if let Ok(Some(word)) = item.expr().head().word() { - if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? { - map.add(key, Binding { - commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(), - condition: None, - description: None, - source: None - }); - Ok(()) - } else if Some(":char") == item.expr()?.head()? { - // TODO - return Ok(()) - } else { - return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into()) - } - } else { - return Err(format!("Config::load_bind: unexpected: {item:?}").into()) - })?; - self.binds.write().unwrap().insert(id, map); - Ok(()) - } - pub fn load_mode (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { - let mut mode = Mode::default(); - dsl.each(|item|Self::load_mode_one(&mut mode, item))?; - self.modes.write().unwrap().insert(id.into(), Arc::new(mode)); - Ok(()) - } - pub fn load_mode_one (mode: &mut Mode>, item: impl Dsl) -> Usually<()> { - Ok(if let Ok(Some(key)) = item.expr().head() { - match key { - "name" => mode.name.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), - "info" => mode.info.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), - "keys" => { - item.expr()?.tail()?.each(|item|{mode.keys.push(item.trim().into()); Ok(())})?; - }, - "mode" => if let Some(id) = item.expr()?.tail()?.head()? { - let mut submode = Mode::default(); - Self::load_mode_one(&mut submode, item.expr()?.tail()?.tail()?)?; - mode.modes.insert(id.into(), submode); - } else { - return Err(format!("load_mode_one: incomplete: {item:?}").into()); - }, - _ => mode.view.push(item.expr()?.unwrap().into()), - } - } else if let Ok(Some(word)) = item.word() { - mode.view.push(word.into()); - } else { - return Err(format!("load_mode_one: unexpected: {item:?}").into()); - }) - } -} has!(Jack<'static>: |self: App|self.jack); has!(Pool: |self: App|self.pool); has!(Dialog: |self: App|self.dialog); diff --git a/crates/app/app_deps.rs b/crates/app/app_deps.rs index 508feda5..40bdf08f 100644 --- a/crates/app/app_deps.rs +++ b/crates/app/app_deps.rs @@ -1,7 +1,7 @@ pub use ::{ tek_engine::*, - tek_device, - tek_device::*, + tek_config::*, + tek_device::{self, *}, tengri::{ Usually, Perhaps, Has, MaybeHas, has, maybe_has, dsl::*, input::*, output::*, tui::*, diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml new file mode 100644 index 00000000..b5605dcd --- /dev/null +++ b/crates/config/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tek_config" +edition = { workspace = true } +version = { workspace = true } + +[lib] +path = "config.rs" + +[dependencies] +xdg = { workspace = true } +tengri = { workspace = true } diff --git a/crates/config/config.rs b/crates/config/config.rs new file mode 100644 index 00000000..ac485ef7 --- /dev/null +++ b/crates/config/config.rs @@ -0,0 +1,137 @@ +#![feature(if_let_guard)] + +use ::{ + std::{ + sync::{Arc, RwLock}, + collections::BTreeMap, + path::PathBuf + }, + tengri::{ + Usually, + input::{EventMap, Binding}, + tui::TuiEvent, + dsl::{Dsl, DslWord, DslExpr} + }, + xdg::BaseDirectories, +}; + +/// Configuration +#[derive(Default, Debug)] +pub struct Config { + pub dirs: BaseDirectories, + pub modes: Arc, Arc>>>>>, + pub views: Arc, Arc>>>, + pub binds: Arc, EventMap>>>>, +} + +#[derive(Default, Debug)] +pub struct Mode { + pub path: PathBuf, + pub name: Vec, + pub info: Vec, + pub view: Vec, + pub keys: Vec, + pub modes: BTreeMap>, +} + +impl Config { + const CONFIG: &'static str = "tek.edn"; + const DEFAULTS: &'static str = include_str!("../../tek.edn"); + pub fn init () -> Usually { + let mut cfgs: Self = Default::default(); + cfgs.dirs = BaseDirectories::with_profile("tek", "v0"); + cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?; + Ok(cfgs) + } + pub fn init_file ( + &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> + ) -> Usually<()> { + if self.dirs.find_config_file(path).is_none() { + println!("Creating {path:?}"); + std::fs::write(self.dirs.place_config_file(path)?, defaults); + } + Ok(if let Some(path) = self.dirs.find_config_file(path) { + println!("Loading {path:?}"); + let src = std::fs::read_to_string(&path)?; + src.as_str().each(move|item|each(self, item))?; + } else { + return Err(format!("{path}: not found").into()) + }) + } + pub fn load (&mut self, dsl: impl Dsl) -> Usually<()> { + dsl.each(|item|if let Some(expr) = item.expr()? { + let head = expr.head()?; + let tail = expr.tail()?; + let name = tail.head()?; + let body = tail.tail()?; + println!("{} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); + match head { + Some("view") if let Some(name) = name => self.load_view(name.into(), body), + Some("keys") if let Some(name) = name => self.load_bind(name.into(), body), + Some("mode") if let Some(name) = name => self.load_mode(name.into(), body), + _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) + } + } else { + return Err(format!("Config::load: expected expr, got: {item:?}").into()) + }) + } + pub fn load_view (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { + self.views.write().unwrap().insert(id, dsl.src()?.unwrap_or_default().into()); + Ok(()) + } + pub fn load_bind (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { + let mut map = EventMap::new(); + dsl.each(|item|if item.expr().head() == Ok(Some("see")) { + // TODO + Ok(()) + } else if let Ok(Some(word)) = item.expr().head().word() { + if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? { + map.add(key, Binding { + commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(), + condition: None, + description: None, + source: None + }); + Ok(()) + } else if Some(":char") == item.expr()?.head()? { + // TODO + return Ok(()) + } else { + return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into()) + } + } else { + return Err(format!("Config::load_bind: unexpected: {item:?}").into()) + })?; + self.binds.write().unwrap().insert(id, map); + Ok(()) + } + pub fn load_mode (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { + let mut mode = Mode::default(); + dsl.each(|item|Self::load_mode_one(&mut mode, item))?; + self.modes.write().unwrap().insert(id.into(), Arc::new(mode)); + Ok(()) + } + pub fn load_mode_one (mode: &mut Mode>, item: impl Dsl) -> Usually<()> { + Ok(if let Ok(Some(key)) = item.expr().head() { + match key { + "name" => mode.name.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "info" => mode.info.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "keys" => { + item.expr()?.tail()?.each(|item|{mode.keys.push(item.trim().into()); Ok(())})?; + }, + "mode" => if let Some(id) = item.expr()?.tail()?.head()? { + let mut submode = Mode::default(); + Self::load_mode_one(&mut submode, item.expr()?.tail()?.tail()?)?; + mode.modes.insert(id.into(), submode); + } else { + return Err(format!("load_mode_one: incomplete: {item:?}").into()); + }, + _ => mode.view.push(item.expr()?.unwrap().into()), + } + } else if let Ok(Some(word)) = item.word() { + mode.view.push(word.into()); + } else { + return Err(format!("load_mode_one: unexpected: {item:?}").into()); + }) + } +} diff --git a/deps/tengri b/deps/tengri index d92e5efd..06217939 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit d92e5efdd0e009ba0492a75b3aad0b3c142b9056 +Subproject commit 062179393037dc948a09aa24cb0000b6ef1dea8e