use crate::*; /// Configuration: mode, view, and bind definitions. /// /// ``` /// let config = tek::Config::default(); /// ``` /// /// ``` /// // Some dizzle. /// // What indentation to use here lol? /// let source = stringify!((mode :menu (name Menu) /// (info Mode selector.) (keys :axis/y :confirm) /// (view (bg (g 0) (bsp/s :ports/out /// (bsp/n :ports/in /// (bg (g 30) (bsp/s (fixed/y 7 :logo) /// (fill :dialog/menu))))))))); /// // Add this definition to the config and try to load it. /// // A "mode" is basically a state machine /// // with associated input and output definitions. /// tek::Config::default().add(&source).unwrap().get_mode(":menu").unwrap(); /// ``` #[derive(Default, Debug)] pub struct Config { /// XDG base directories of running user. pub dirs: BaseDirectories, /// Active collection of interaction modes. pub modes: Modes, /// Active collection of event bindings. pub binds: Binds, /// Active collection of view definitions. pub views: Views, } impl Config { const CONFIG_DIR: &'static str = "tek"; const CONFIG_SUB: &'static str = "v0"; const CONFIG: &'static str = "tek.edn"; const DEFAULTS: &'static str = include_str!("../tek.edn"); pub fn watch (callback: impl FnOnce(Self)->T) -> Usually { let config = Self::init_new(None)?; let result = callback(config); Ok(result) } pub fn init_new (dirs: Option) -> Usually { let mut config = Self::new(None); config.init()?; Ok(config) } /// Create a new app configuration from a set of XDG base directories, pub fn new (dirs: Option) -> Self { let default = ||BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB); let dirs = dirs.unwrap_or_else(default); Self { dirs, ..Default::default() } } /// Write initial contents of configuration. pub fn init (&mut self) -> Usually<()> { self.init_one(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{ cfgs.add(&dsl)?; Ok(()) })?; Ok(()) } /// Write initial contents of a configuration file. pub fn init_one ( &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()) }) } /// Add statements to configuration from [Dsl] source. pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> { dsl.each(|item|self.add_one(item))?; Ok(self) } fn add_one (&self, item: impl Language) -> Usually<()> { if let Some(expr) = item.expr()? { let head = expr.head()?; let tail = expr.tail()?; let name = tail.head()?; let body = tail.tail()?; //println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); match head { Some("mode") if let Some(name) = name => self.modes.add(&name, &body)?, Some("keys") if let Some(name) = name => load_bind(&self.binds, &name, &body)?, Some("view") if let Some(name) = name => load_view(&self.views, &name, &body)?, _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) } Ok(()) } else { return Err(format!("Config::load: expected expr, got: {item:?}").into()) } } pub fn get_mode (&self, mode: impl AsRef) -> Option>>> { self.modes.get(mode) } } mod views; pub use self::views::*; mod modes; pub use self::modes::*; mod mode; pub use self::mode::*;