mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
refactor: config crate
This commit is contained in:
parent
d5865d941f
commit
17c6184f0e
8 changed files with 165 additions and 124 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -2390,6 +2390,7 @@ dependencies = [
|
||||||
"proptest",
|
"proptest",
|
||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"tek_config",
|
||||||
"tek_device",
|
"tek_device",
|
||||||
"tek_engine",
|
"tek_engine",
|
||||||
"tengri",
|
"tengri",
|
||||||
|
|
@ -2406,6 +2407,14 @@ dependencies = [
|
||||||
"tek",
|
"tek",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tek_config"
|
||||||
|
version = "0.2.1"
|
||||||
|
dependencies = [
|
||||||
|
"tengri",
|
||||||
|
"xdg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tek_device"
|
name = "tek_device"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"./crates/engine",
|
"./crates/engine",
|
||||||
"./crates/device",
|
"./crates/device",
|
||||||
|
"./crates/config",
|
||||||
"./crates/app",
|
"./crates/app",
|
||||||
"./crates/cli",
|
"./crates/cli",
|
||||||
]
|
]
|
||||||
|
|
@ -34,6 +35,7 @@ path = "./deps/rust-jack"
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
tek_device = { path = "./crates/device" }
|
tek_device = { path = "./crates/device" }
|
||||||
tek_engine = { path = "./crates/engine" }
|
tek_engine = { path = "./crates/engine" }
|
||||||
|
tek_config = { path = "./crates/config" }
|
||||||
tek = { path = "./crates/app" }
|
tek = { path = "./crates/app" }
|
||||||
tek_cli = { path = "./crates/cli" }
|
tek_cli = { path = "./crates/cli" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ version = { workspace = true }
|
||||||
tengri = { workspace = true }
|
tengri = { workspace = true }
|
||||||
tengri_proc = { workspace = true }
|
tengri_proc = { workspace = true }
|
||||||
|
|
||||||
tek_engine = { workspace = true }
|
tek_config = { workspace = true }
|
||||||
tek_device = { workspace = true }
|
tek_device = { workspace = true }
|
||||||
|
tek_engine = { workspace = true }
|
||||||
|
|
||||||
backtrace = { workspace = true }
|
backtrace = { workspace = true }
|
||||||
clap = { workspace = true, optional = true }
|
clap = { workspace = true, optional = true }
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
#![allow(unused, clippy::unit_arg)]
|
#![allow(unused, clippy::unit_arg)]
|
||||||
#![feature(adt_const_params, associated_type_defaults, if_let_guard, impl_trait_in_assoc_type,
|
#![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)]
|
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_deps; pub use self::app_deps::*;
|
||||||
mod app_jack; pub use self::app_jack::*;
|
mod app_jack; pub use self::app_jack::*;
|
||||||
mod app_bind; pub use self::app_bind::*;
|
mod app_bind; pub use self::app_bind::*;
|
||||||
mod app_data; pub use self::app_data::*;
|
mod app_data; pub use self::app_data::*;
|
||||||
mod app_view; pub use self::app_view::*;
|
mod app_view; pub use self::app_view::*;
|
||||||
|
|
||||||
#[cfg(test)] mod app_test;
|
|
||||||
/// Total state
|
/// Total state
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
|
@ -32,23 +31,6 @@ pub struct App {
|
||||||
/// Base color.
|
/// Base color.
|
||||||
pub color: ItemTheme,
|
pub color: ItemTheme,
|
||||||
}
|
}
|
||||||
/// Configuration
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Config {
|
|
||||||
pub dirs: BaseDirectories,
|
|
||||||
pub modes: Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>,
|
|
||||||
pub views: Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>,
|
|
||||||
pub binds: Arc<RwLock<BTreeMap<Arc<str>, EventMap<TuiEvent, Arc<str>>>>>,
|
|
||||||
}
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Mode<D: Dsl + Ord> {
|
|
||||||
pub path: PathBuf,
|
|
||||||
pub name: Vec<D>,
|
|
||||||
pub info: Vec<D>,
|
|
||||||
pub view: Vec<D>,
|
|
||||||
pub keys: Vec<D>,
|
|
||||||
pub modes: BTreeMap<D, Mode<D>>,
|
|
||||||
}
|
|
||||||
/// Various possible dialog modes.
|
/// Various possible dialog modes.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub enum Dialog {
|
pub enum Dialog {
|
||||||
|
|
@ -60,107 +42,6 @@ pub enum Dialog {
|
||||||
Browser(BrowserTarget, Arc<Browser>),
|
Browser(BrowserTarget, Arc<Browser>),
|
||||||
Options,
|
Options,
|
||||||
}
|
}
|
||||||
impl Config {
|
|
||||||
const CONFIG: &'static str = "tek.edn";
|
|
||||||
const DEFAULTS: &'static str = include_str!("../../tek.edn");
|
|
||||||
pub fn init () -> Usually<Self> {
|
|
||||||
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<str>, 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<str>, 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<str>, 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<Arc<str>>, 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!(Jack<'static>: |self: App|self.jack);
|
||||||
has!(Pool: |self: App|self.pool);
|
has!(Pool: |self: App|self.pool);
|
||||||
has!(Dialog: |self: App|self.dialog);
|
has!(Dialog: |self: App|self.dialog);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
pub use ::{
|
pub use ::{
|
||||||
tek_engine::*,
|
tek_engine::*,
|
||||||
tek_device,
|
tek_config::*,
|
||||||
tek_device::*,
|
tek_device::{self, *},
|
||||||
tengri::{
|
tengri::{
|
||||||
Usually, Perhaps, Has, MaybeHas, has, maybe_has,
|
Usually, Perhaps, Has, MaybeHas, has, maybe_has,
|
||||||
dsl::*, input::*, output::*, tui::*,
|
dsl::*, input::*, output::*, tui::*,
|
||||||
|
|
|
||||||
11
crates/config/Cargo.toml
Normal file
11
crates/config/Cargo.toml
Normal file
|
|
@ -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 }
|
||||||
137
crates/config/config.rs
Normal file
137
crates/config/config.rs
Normal file
|
|
@ -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<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>,
|
||||||
|
pub views: Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>,
|
||||||
|
pub binds: Arc<RwLock<BTreeMap<Arc<str>, EventMap<TuiEvent, Arc<str>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Mode<D: Dsl + Ord> {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub name: Vec<D>,
|
||||||
|
pub info: Vec<D>,
|
||||||
|
pub view: Vec<D>,
|
||||||
|
pub keys: Vec<D>,
|
||||||
|
pub modes: BTreeMap<D, Mode<D>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
const CONFIG: &'static str = "tek.edn";
|
||||||
|
const DEFAULTS: &'static str = include_str!("../../tek.edn");
|
||||||
|
pub fn init () -> Usually<Self> {
|
||||||
|
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<str>, 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<str>, 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<str>, 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<Arc<str>>, 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());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit d92e5efdd0e009ba0492a75b3aad0b3c142b9056
|
Subproject commit 062179393037dc948a09aa24cb0000b6ef1dea8e
|
||||||
Loading…
Add table
Add a link
Reference in a new issue