mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-07-17 15:56:57 +02:00
restruct: 92e
This commit is contained in:
parent
4ec2165e3d
commit
ae347eeef7
31 changed files with 2924 additions and 2663 deletions
192
src/app/config.rs
Normal file
192
src/app/config.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
use crate::{*, bind::*, view::*};
|
||||
|
||||
/// 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 init_new (dirs: Option<BaseDirectories>) -> Usually<Self> {
|
||||
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<BaseDirectories>) -> 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 => load_mode(&self.modes, &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<str>) -> Option<Arc<Mode<Arc<str>>>> {
|
||||
self.modes.clone().read().unwrap().get(mode.as_ref()).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_mode (modes: &Modes, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||
let mut mode = Mode::default();
|
||||
body.each(|item|mode.add(item))?;
|
||||
modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Collection of interaction modes.
|
||||
pub type Modes = Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>;
|
||||
|
||||
impl Mode<Arc<str>> {
|
||||
/// Add a definition to the mode.
|
||||
///
|
||||
/// Supported definitions:
|
||||
///
|
||||
/// - (name ...) -> name
|
||||
/// - (info ...) -> description
|
||||
/// - (keys ...) -> key bindings
|
||||
/// - (mode ...) -> submode
|
||||
/// - ... -> view
|
||||
///
|
||||
/// ```
|
||||
/// let mut mode: tek::Mode<std::sync::Arc<str>> = Default::default();
|
||||
/// mode.add("(name hello)").unwrap();
|
||||
/// ```
|
||||
pub fn add (&mut self, dsl: impl Language) -> Usually<()> {
|
||||
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() {
|
||||
//println!("Mode::add: {head} {:?}", expr.tail());
|
||||
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||
match head {
|
||||
"name" => self.add_name(tail)?,
|
||||
"info" => self.add_info(tail)?,
|
||||
"keys" => self.add_keys(tail)?,
|
||||
"mode" => self.add_mode(tail)?,
|
||||
_ => self.add_view(tail)?,
|
||||
};
|
||||
} else if let Ok(Some(word)) = dsl.word() {
|
||||
self.add_view(word);
|
||||
} else {
|
||||
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
|
||||
})
|
||||
|
||||
//DslParse(dsl, ||Err(format!("Mode::add: unexpected: {dsl:?}").into()))
|
||||
//.word(|word|self.add_view(word))
|
||||
//.expr(|expr|expr.head(|head|{
|
||||
////println!("Mode::add: {head} {:?}", expr.tail());
|
||||
//let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||
//match head {
|
||||
//"name" => self.add_name(tail),
|
||||
//"info" => self.add_info(tail),
|
||||
//"keys" => self.add_keys(tail)?,
|
||||
//"mode" => self.add_mode(tail)?,
|
||||
//_ => self.add_view(tail),
|
||||
//};
|
||||
//}))
|
||||
}
|
||||
|
||||
fn add_name (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.name.push(src.into())))
|
||||
}
|
||||
fn add_info (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.info.push(src.into())))
|
||||
}
|
||||
fn add_view (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.view.push(src.into())))
|
||||
}
|
||||
fn add_keys (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(Some(dsl.each(|expr|{ self.keys.push(expr.trim().into()); Ok(()) })?))
|
||||
}
|
||||
fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(Some(if let Some(id) = dsl.head()? {
|
||||
load_mode(&self.modes, &id, &dsl.tail())?;
|
||||
} else {
|
||||
return Err(format!("Mode::add: self: incomplete: {dsl:?}").into());
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Group of view and keys definitions.
|
||||
///
|
||||
/// ```
|
||||
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
|
||||
/// ```
|
||||
#[derive(Default, Debug)] pub struct Mode<D: Language + Ord> {
|
||||
pub path: PathBuf,
|
||||
pub name: Vec<D>,
|
||||
pub info: Vec<D>,
|
||||
pub view: Vec<D>,
|
||||
pub keys: Vec<D>,
|
||||
pub modes: Modes,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue