This commit is contained in:
i do not exist 2026-08-22 14:06:50 +03:00
parent 816150125d
commit 4fcbcdda86
5 changed files with 607 additions and 566 deletions

29
Cargo.lock generated
View file

@ -2137,7 +2137,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -2559,8 +2559,10 @@ version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [ dependencies = [
"backtrace",
"cfg-if", "cfg-if",
"libc", "libc",
"petgraph",
"redox_syscall 0.5.18", "redox_syscall 0.5.18",
"smallvec", "smallvec",
"windows-link", "windows-link",
@ -2620,6 +2622,16 @@ dependencies = [
"pest", "pest",
] ]
[[package]]
name = "petgraph"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.3" version = "0.11.3"
@ -3827,6 +3839,7 @@ dependencies = [
"livi", "livi",
"notify-debouncer-full", "notify-debouncer-full",
"palette", "palette",
"parking_lot 0.12.5",
"proptest", "proptest",
"proptest-derive", "proptest-derive",
"quanta", "quanta",
@ -3836,6 +3849,7 @@ dependencies = [
"tek_proc", "tek_proc",
"tengri", "tengri",
"toml 0.9.12+spec-1.1.0", "toml 0.9.12+spec-1.1.0",
"tracing-mutex",
"uuid", "uuid",
"wavers", "wavers",
"winit", "winit",
@ -3905,7 +3919,7 @@ dependencies = [
"parking_lot 0.12.5", "parking_lot 0.12.5",
"rustix 1.1.4", "rustix 1.1.4",
"signal-hook", "signal-hook",
"windows-sys 0.60.2", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -4246,6 +4260,17 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-mutex"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64aed473bc9271d160dce1fda5daebec76bb740f9db452bf6c9682344a5e44c0"
dependencies = [
"autocfg",
"lock_api",
"parking_lot 0.12.5",
]
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.23" version = "0.3.23"

View file

@ -36,6 +36,8 @@ gtk = { optional = true, version = "0.18.1" }
notify-debouncer-full = "0.7.0" notify-debouncer-full = "0.7.0"
hotpath = "0.23" hotpath = "0.23"
parking_lot = { version = "0.12.5", features = ["deadlock_detection"] }
tracing-mutex = { version = "0.3.3", features = ["parking_lot"] }
#once_cell = "1.19.0" #once_cell = "1.19.0"
#no_deadlocks = "1.3.2" #no_deadlocks = "1.3.2"

496
src/config.rs Normal file
View file

@ -0,0 +1,496 @@
use crate::*;
use std::path::PathBuf;
use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher};
/// Write initial contents of configuration.
pub fn config_init (config: &Config) -> UsuallyRef<'static, ()> {
//println!("\r\ninit {}", quanta::Clock::new().raw());
config.clear();
let path = Config::CONFIG;
let defaults = Config::DEFAULTS;
config.stamp.store(quanta::Clock::new().raw(), Relaxed);
if config.find_file().is_none() {
//println!("Creating {path:?}");
std::fs::write(config.place_file()?, defaults)?;
}
Ok(if let Some(path) = config.find_file() {
//println!("Loading {path:?}");
let src = std::fs::read_to_string(&path)?;
src.as_str().each((), &mut move|ctx, item: &str|{
config_add(config, item);
Ok(ctx)
});
} else {
return Err(format!("{path}: not found").into())
})
}
/// Add statements to configuration from [Dsl] source.
pub fn config_add <'a> (config: &'a Config, dsl: &'a str) -> UsuallyRef<'a, &'a Config> {
dsl.each(config, &mut move|ctx: &'a Config, item: &'a str|if let Some(expr) = item.expr()? {
//if let (Some(name), Some(body)) = (expr.tail()?.head()?, expr.tail()?.tail()?,) {
match expr.head()? {
Some("mode") => expr.tail()?.map(|tail|modes_add(&config.modes, tail)),
Some("keys") => expr.tail()?.map(|tail|load_bind(&config.binds, tail)),
Some("view") => expr.tail()?.map(|tail|load_view(&config.views, tail)),
_ => return Err(format!("Config::load: expected view/keys/mode expr, got: {item:?}").into())
}.transpose()?;
//}
Ok(config)
} else {
Err(format!("Config::add_one: tried to add empty item").into())
})
}
/// Register a mode.
pub fn modes_add <'a> (modes: &Modes, expr: &'a str) -> UsuallyRef<'a, ()> {
let name = expr.head()?.ok_or("mode: missing name")?;
let body = expr.tail()?.ok_or("mode: missing body")?;
modes.0.write().unwrap().insert(
name.into(),
Arc::new(body.each(
Mode::default(),
move|mut mode: Mode, item: &str|{
mode_add(&mut mode, item)?;
Ok(mode)
}
)?));
Ok(())
}
/// 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 mode_add <'a> (mode: &'a mut Mode, dsl: &'a str) -> PerhapsRef<'a, &'a mut Mode> {
Ok(Some(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 {
"mode" => {
let head = tail.head()?.ok_or("mode: missing name")?;
let body = tail.tail()?.ok_or("mode: missing body")?;
let modes = mode.modes.clone();
let submode = Mode::default();
let submode = body.each(submode, move|mut submode: Mode, item: &str|{
mode_add(&mut submode, &item);
Ok(submode)
})?;
modes.0.write().unwrap().insert(head.into(), Arc::new(submode));
mode
},
"keys" => {
dsl.each(mode, &mut |mode: &'a mut Mode, expr: &'a str|{
mode.keys.push(expr.trim().into());
Ok(mode)
})?
},
"name" => { mode.name.push(tail.into()); mode },
"info" => { mode.info.push(tail.into()); mode },
"view" => { mode.view.push(tail.into()); mode },
_ => { mode.view.push(expr.into()); mode },
}
} else if let Ok(Some(word)) = dsl.word() {
mode.view.push(word.into());
mode
} else {
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
}))
}
/// Load custom view definition.
pub fn load_view <'a> (views: &Views, expr: &'a str,) -> UsuallyRef<'a, ()> {
let name = expr.head()?.ok_or("view: missing name")?;
let body = expr.tail()?.ok_or("view: missing body")?;
views.write().unwrap().insert(
name.into(),
body.src()?.unwrap_or_default().into()
);
Ok(())
}
pub fn load_bind <'a> (binds: &Binds, expr: &'a str) -> Usually<()> {
let name = expr.head()?.ok_or("bind: missing name")?;
let body = expr.tail()?.ok_or("bind: missing body")?;
binds.write().unwrap().insert(name.into(), {
let mut map = Bind::new();
body.each((), &mut |_, item: &str|if item.expr().head() == Ok(Some("see")) {
// TODO
Ok(())
} else if let Ok(Some(_word)) = item.expr().head().word() {
let expr = item.expr()?;
let head = expr.head()?;
if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() {
map.add(TuiEvent(event), 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())
})?;
map
});
Ok(())
}
/// 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,
/// Error caught during reloading
pub error: RwLock<Option<Arc<str>>>,
/// Timestamp
pub stamp: AtomicU64,
/// Watcher
pub watch: RwLock<Option<PollWatcher>>
}
/// Collection of UI modes.
///
/// ```
/// let mut modes = tek::Modes::default();
/// assert_eq!(modes.len(), 0);
/// let _ = modes.add(&":foo", &"(mode (name Foo) (info Bar))");
/// assert_eq!(modes.len(), 1);
/// ```
#[derive(Default, Debug, Clone)]
pub struct Modes(Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>);
/// Group of view and keys definitions.
///
/// ```
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
/// ```
#[derive(Default, Debug)]
pub struct Mode {
pub path: PathBuf,
pub name: Vec<Arc<str>>,
pub info: Vec<Arc<str>>,
pub view: Vec<Arc<str>>,
pub keys: Vec<Arc<str>>,
pub modes: Modes,
}
impl Config {
/// Default configuration directory.
const CONFIG_DIR: &'static str = "tek";
/// Default configuration subdirectory.
const CONFIG_SUB: &'static str = "v0";
/// Default configuration file name.
const CONFIG: &'static str = "tek.edn";
/// Default configuration contents.
const DEFAULTS: &'static str = include_str!("tek.edn");
/// Create a new app configuration from a set of XDG base directories,
pub fn new (dirs: Option<BaseDirectories>) -> Self {
Self {
dirs: dirs.unwrap_or_else(||{
BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB)
}),
stamp: quanta::Clock::new().raw().into(),
..Default::default()
}
}
/// Create, initialize, and watch a new configuration.
pub fn watched <T: 'static> (callback: impl FnOnce(Arc<Config>)->T) -> Usually<T> {
let config = Self::init_new(None)?;
Self::watch(config.clone(), None)?;
let result = callback(config);
Ok(result)
}
/// Create and initialize a new configuration.
pub fn init_new (_dirs: Option<BaseDirectories>) -> UsuallyRef<'static, Arc<Self>> {
let config = Arc::new(Self::new(None));
config_init(&config)?;
Ok(config)
}
/// Watch a config's file for changes.
pub fn watch (config: Arc<Config>, poll: Option<Duration>) -> UsuallyRef<'static, ()> {
let handler = {
let config = config.clone();
move |result|match result {
Ok(_events) => if let Err(e) = config_init(&config) {
*config.error.write().unwrap() = Some(format!("{e:?}").into());
panic!("{e:?}");
} else {
//println!("config updated");
},
Err(errors) => {
panic!("{errors:?}");
}
}
};
let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new(
handler,
NotifyConfig::default()
.with_poll_interval(poll.unwrap_or(Duration::from_millis(250)))
)?;
if let Some(path) = config.get_file() {
//println!("watching: {path:?}");
watcher.watch(&path, RecursiveMode::NonRecursive)?;
*config.watch.write().unwrap() = Some(watcher);
Ok(())
} else {
Err(format!("no config path").into())
}
}
/// Find the config file.
fn find_file (&self) -> Option<PathBuf> {
self.dirs.find_config_file(Self::CONFIG)
}
/// Place config file in default location.
fn place_file (&self) -> Result<PathBuf, std::io::Error> {
self.dirs.place_config_file(Self::CONFIG)
}
/// Get path to config file.
fn get_file (&self) -> Option<PathBuf> {
self.dirs.get_config_file(Self::CONFIG)
}
/// Get a mode by name.
pub fn get_mode (&self, mode: impl AsRef<str>) -> Option<Arc<Mode>> {
self.modes.get(mode)
}
/// Print the configuration.
pub fn print (&self) {
print_config(self)
}
/// Make this configuration empty.
fn clear (&self) {
*self.modes.0.write().unwrap() = Default::default();
*self.views.write().unwrap() = Default::default();
*self.binds.write().unwrap() = Default::default();
}
}
pub use self::view::*;
mod view {
use crate::*;
/// Collection of custom view definitions.
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
}
pub use self::mode::*;
mod mode {
use crate::*;
impl Modes {
/// Get a mode by name.
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode>> {
self.0.read().unwrap().get(name.as_ref()).cloned()
}
/// Run something for each mode.
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
for (k, v) in self.0.read().unwrap().iter() {
let _ = ator(k.as_ref(), v.as_ref());
}
}
/// Count modes.
pub fn len (&self) -> usize {
self.0.read().unwrap().len()
}
}
}
pub use self::bind::*;
mod bind {
use crate::*;
/// Collection of input bindings.
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
///
/// ```
/// let lang = "(@x (nop)) (@y (nop) (nop))";
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
/// ```
#[derive(Debug)]
pub struct Bind<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// A sequence of zero or more commands (e.g. [AppCommand]),
/// optionally filtered by [Condition] to form layers.
///
/// ```
/// //FIXME: Why does it overflow?
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// Condition that must evaluate to true in order to enable an input layer.
///
/// ```
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
/// ```
#[derive(Clone)]
pub struct Condition(pub Arc<Box<dyn Fn()->bool + Send + Sync>>);
impl Bind<TuiEvent, Arc<str>> {
}
/// Default is always empty map regardless if `E` and `C` implement [Default].
impl<E, C> Default for Bind<E, C> {
fn default () -> Self { Self(Default::default()) }
}
impl<C: Default> Default for Binding<C> {
fn default () -> Self {
Self {
commands: Default::default(),
condition: Default::default(),
description: Default::default(),
source: Default::default(),
}
}
}
impl<E: Clone + Ord, C> Bind<E, C> {
/// Create a new event map
pub fn new () -> Self {
Default::default()
}
/// Add a binding to an owned event map.
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
self.add(event, binding);
self
}
/// Add a binding to an event map.
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
if !self.0.contains_key(&event) {
self.0.insert(event.clone(), Default::default());
}
self.0.get_mut(&event).unwrap().push(binding);
self
}
/// Return the binding(s) that correspond to an event.
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
self.0.get(event).map(|x|x.as_slice())
}
/// Return the first binding that corresponds to an event, considering conditions.
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
self.query(event)
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
.flatten()
}
}
impl_debug!(Condition |self, w| { write!(w, "*") });
}
pub fn print_config (config: &Config) {
use ::ansi_term::Color::*;
println!("{:?}", config.dirs);
for (k, v) in config.views.read().unwrap().iter() {
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")));
}
for (k, v) in config.binds.read().unwrap().iter() {
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
for (k, v) in v.0.iter() {
print!("{} ", &Yellow.paint(match &k.0 {
Event::Key(KeyEvent { modifiers, .. }) =>
format!("{:>16}", format!("{modifiers}")),
_ => unimplemented!()
}));
print!("{}", &Yellow.bold().paint(match &k.0 {
Event::Key(KeyEvent { code, .. }) =>
format!("{:<10}", format!("{code}")),
_ => unimplemented!()
}));
for v in v.iter() {
print!(" => {:?}", v.commands);
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
//println!(" {:?}", v.source);
}
}
}
config.modes.for_each(|k, v|{
println!();
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
print!("\n{}", Blue.paint("KEYS"));
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
println!();
v.modes.for_each(|k, v|{
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
print!( " INFO={:?}", v.info);
print!( " VIEW={:?}", v.view);
println!(" KEYS={:?}", v.keys);
});
print!("{}", Blue.paint("VIEW"));
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
println!();
});
}

View file

@ -1,8 +1,8 @@
use crate::{*, device::*}; use crate::{*, device::*};
pub fn draw_dialog <'a, L: Language + ?Sized> ( pub fn draw_dialog <'a> (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App)
to: &mut Tui, mut frags: std::str::Split<&str>, state: &App, dsl: &'a L -> Drawn<'a, u16>
) -> Drawn<'a, u16> { {
match frags.next() { match frags.next() {
Some("menu") => if let Dialog::Menu(selected, items) = &state.dialog { Some("menu") => if let Dialog::Menu(selected, items) = &state.dialog {
//Some(iter_south(move||items.0.iter().enumerate().map(move|(index, MenuItem(item, _))|{ //Some(iter_south(move||items.0.iter().enumerate().map(move|(index, MenuItem(item, _))|{
@ -28,7 +28,7 @@ pub fn draw_dialog <'a, L: Language + ?Sized> (
} else { } else {
None None
}.draw(to), }.draw(to),
_ => unimplemented!("App::interpret_word: {dsl:?} ({frags:?})"), _ => unimplemented!("draw_dialog: ({frags:?})"),
} }
} }

View file

@ -76,13 +76,13 @@ fn run_new_plain (config: Config) -> Usually<()> {
#[cfg(feature = "cli")] pub mod cli { #[cfg(feature = "cli")] pub mod cli {
use crate::*; use crate::*;
pub fn run_with_config <'a> (config: Arc<Config<'a>>) -> UsuallyRef<'a, ()> { pub fn run_with_config (config: Arc<Config>) -> Usually<()> {
Cli::parse().run(Some(config)) Cli::parse().run(Some(config))
} }
/// Command-line configuration. /// Command-line configuration.
impl Cli { impl Cli {
pub fn run <'a> (&self, mut config: Option<Arc<Config<'a>>>) -> UsuallyRef<'a, ()> { pub fn run (&self, mut config: Option<Arc<Config>>) -> Usually<()> {
if config.is_none() { if config.is_none() {
config = Some(Config::init_new(None)?); config = Some(Config::init_new(None)?);
} }
@ -231,308 +231,7 @@ fn run_new_plain (config: Config) -> Usually<()> {
} }
pub use self::config::*; pub use self::config::*;
mod config { mod config;
use crate::*;
use std::path::PathBuf;
use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher};
/// 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<'a> {
/// XDG base directories of running user.
pub dirs: BaseDirectories,
/// Active collection of interaction modes.
pub modes: Modes<'a>,
/// Active collection of event bindings.
pub binds: Binds,
/// Active collection of view definitions.
pub views: Views,
/// Error caught during reloading
pub error: RwLock<Option<Arc<str>>>,
/// Timestamp
pub stamp: AtomicU64,
/// Watcher
pub watch: RwLock<Option<PollWatcher>>
}
/// Collection of custom view definitions.
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
/// Collection of UI modes.
///
/// ```
/// let mut modes = tek::Modes::default();
/// assert_eq!(modes.len(), 0);
/// let _ = modes.add(&":foo", &"(mode (name Foo) (info Bar))");
/// assert_eq!(modes.len(), 1);
/// ```
#[derive(Default, Debug, Clone)]
pub struct Modes<'a>(Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<'a, Arc<str>>>>>>);
/// Group of view and keys definitions.
///
/// ```
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
/// ```
#[derive(Default, Debug)]
pub struct Mode<'a, 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<'a>,
}
impl<'a> Config<'a> {
/// Default configuration directory.
const CONFIG_DIR: &'static str = "tek";
/// Default configuration subdirectory.
const CONFIG_SUB: &'static str = "v0";
/// Default configuration file name.
const CONFIG: &'static str = "tek.edn";
/// Default configuration contents.
const DEFAULTS: &'static str = include_str!("tek.edn");
/// Create a new app configuration from a set of XDG base directories,
pub fn new (dirs: Option<BaseDirectories>) -> Self {
Self {
dirs: dirs.unwrap_or_else(||{
BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB)
}),
stamp: quanta::Clock::new().raw().into(),
..Default::default()
}
}
/// Create, initialize, and watch a new configuration.
pub fn watched <T> (callback: impl FnOnce(Arc<Self>)->T) -> UsuallyRef<'a, T> {
let config = Self::init_new(None)?;
Self::watch(config.clone(), None)?;
let result = callback(config);
Ok(result)
}
/// Create and initialize a new configuration.
pub fn init_new (_dirs: Option<BaseDirectories>) -> UsuallyRef<'a, Arc<Self>> {
let config = Arc::new(Self::new(None));
config.init()?;
Ok(config)
}
/// Watch a config's file for changes.
pub fn watch (config: Arc<Self>, poll: Option<Duration>) -> UsuallyRef<'a, ()> {
let handler = {
let config = config.clone();
move |result|match result {
Ok(_events) => if let Err(e) = config.init() {
*config.error.write().unwrap() = Some(format!("{e:?}").into());
panic!("{e:?}");
} else {
//println!("config updated");
},
Err(errors) => {
panic!("{errors:?}");
}
}
};
let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new(
handler,
NotifyConfig::default()
.with_poll_interval(poll.unwrap_or(Duration::from_millis(250)))
)?;
if let Some(path) = config.get_file() {
//println!("watching: {path:?}");
watcher.watch(&path, RecursiveMode::NonRecursive)?;
*config.watch.write().unwrap() = Some(watcher);
Ok(())
} else {
Err(format!("no config path").into())
}
}
/// Find the config file.
fn find_file (&self) -> Option<PathBuf> {
self.dirs.find_config_file(Self::CONFIG)
}
/// Place config file in default location.
fn place_file (&self) -> Result<PathBuf, std::io::Error> {
self.dirs.place_config_file(Self::CONFIG)
}
/// Get path to config file.
fn get_file (&self) -> Option<PathBuf> {
self.dirs.get_config_file(Self::CONFIG)
}
/// Write initial contents of configuration.
pub fn init (&self) -> UsuallyRef<'a, ()> {
//println!("\r\ninit {}", quanta::Clock::new().raw());
self.clear();
self.load(Self::CONFIG, Self::DEFAULTS, move|cfgs, dsl|{
cfgs.add(&dsl)?;
Ok(())
})
}
/// Write initial contents of a configuration file.
pub fn load <F: FnMut(&Self, &str)->UsuallyRef<'a, ()>> (
&self, path: &str, defaults: &str, mut each: F
) -> UsuallyRef<'a, ()> {
self.stamp.store(quanta::Clock::new().raw(), Relaxed);
if self.find_file().is_none() {
//println!("Creating {path:?}");
std::fs::write(self.place_file()?, defaults)?;
}
Ok(if let Some(path) = self.find_file() {
//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 (&'a self, dsl: impl Language) -> UsuallyRef<'a, &Self> {
dsl.each(|item|self.add_one(item))?;
Ok(self)
}
/// Make this configuration empty.
fn clear (&self) {
*self.modes.0.write().unwrap() = Default::default();
*self.views.write().unwrap() = Default::default();
*self.binds.write().unwrap() = Default::default();
}
/// Add one entry to the configuration.
fn add_one (&'a self, item: &'a (impl Language + ?Sized)) -> UsuallyRef<'a, ()> {
item.expr()?
.map(|expr|{
let head = expr.head()?;
let tail = expr.tail()?;
let name = tail.head()?;
let body = tail.tail()?;
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 Ok::<Option<()>, Box<dyn Error>>(None)
}
Ok(Some(()))
})
.transpose()?
.flatten()
.ok_or_else(||format!("Config::load: expected view/keys/mode expr, got: {item:?}").into())
}
/// Get a mode by name.
pub fn get_mode (&'a self, mode: impl AsRef<str>) -> Option<Arc<Mode<'a, Arc<str>>>> {
self.modes.get(mode)
}
/// Print the configuration.
pub fn print (&self) {
print_config(self)
}
}
impl<'a> Modes<'a> {
/// Register a mode.
pub fn add (
&self, name: &'a (impl AsRef<str> + ?Sized), body: impl Language
)
-> UsuallyRef<'a, ()>
{
let mut mode = Mode::default();
body.each(|item|mode.add(item))?;
self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
Ok(())
}
/// Get a mode by name.
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode<'a, Arc<str>>>> {
self.0.read().unwrap().get(name.as_ref()).cloned()
}
/// Run something for each mode.
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode<Arc<str>>)->T) {
for (k, v) in self.0.read().unwrap().iter() {
let _ = ator(k.as_ref(), v.as_ref());
}
}
/// Count modes.
pub fn len (&self) -> usize {
self.0.read().unwrap().len()
}
}
impl<'a> Mode<'a, 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: &'a (impl Language + ?Sized)) -> UsuallyRef<'a, ()> {
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());
})
}
/// Add a name to the mode.
fn add_name (&mut self, dsl: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, ()> {
Ok(dsl.text()?.map(|text|self.name.push(text.into())))
}
/// Add a description to the mode.
fn add_info (&mut self, dsl: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, ()> {
Ok(dsl.text()?.map(|text|self.info.push(text.into())))
}
/// Add a view definition to the mode.
fn add_view (&mut self, dsl: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, ()> {
Ok(dsl.text()?.map(|text|self.view.push(text.into())))
}
/// Add a keyboard input bindin to the mode.
fn add_keys (&mut self, dsl: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, ()> {
Ok(Some(dsl.src()?.each(|expr|{
self.keys.push(expr.trim().into());
Ok::<(), Box<dyn Error>>(())
})?))
}
/// Add a submode to the mode.
fn add_mode (&mut self, dsl: &'a (impl Language + ?Sized + 'a)) -> PerhapsRef<'a, ()> {
Ok(Some(if let Some(id) = dsl.head()? {
self.modes.add(id, &dsl.tail())?;
} else {
return Err(format!("Mode::add: self: incomplete: {dsl:?}").into());
}))
}
}
}
pub use self::app::*; pub use self::app::*;
mod app { mod app {
@ -606,7 +305,7 @@ mod app {
/// Performance counter /// Performance counter
pub perf: PerfModel, pub perf: PerfModel,
/// Available view modes and input bindings /// Available view modes and input bindings
pub config: Arc<Config<'static>>, pub config: Arc<Config>,
/// Currently selected mode /// Currently selected mode
pub mode: Option<Arc<str>>, pub mode: Option<Arc<str>>,
/// Undo history /// Undo history
@ -632,7 +331,10 @@ mod app {
/// let tek = tek::App::new(None, proj, conf, "hello"); /// let tek = tek::App::new(None, proj, conf, "hello");
/// ``` /// ```
pub fn new ( pub fn new (
exit: Option<Exit>, project: Arrangement, config: Arc<Config>, mode: impl AsRef<str> exit: Option<Exit>,
project: Arrangement,
config: Arc<Config>,
mode: impl AsRef<str>
) -> Self { ) -> Self {
App { App {
exit: exit.unwrap_or_default(), exit: exit.unwrap_or_default(),
@ -790,47 +492,6 @@ pub use self::bind::*;
mod bind { mod bind {
use crate::*; use crate::*;
/// Collection of input bindings.
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
///
/// ```
/// let lang = "(@x (nop)) (@y (nop) (nop))";
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
/// ```
#[derive(Debug)]
pub struct Bind<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// A sequence of zero or more commands (e.g. [AppCommand]),
/// optionally filtered by [Condition] to form layers.
///
/// ```
/// //FIXME: Why does it overflow?
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// Condition that must evaluate to true in order to enable an input layer.
///
/// ```
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
/// ```
#[derive(Clone)]
pub struct Condition(pub Arc<Box<dyn Fn()->bool + Send + Sync>>);
tui_keys!(self: App, input { tui_keys!(self: App, input {
let name = self.mode.as_ref(); let name = self.mode.as_ref();
let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone); let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone);
@ -983,89 +644,6 @@ mod bind {
} }
} }
pub(crate) fn load_bind <'a> (
binds: &Binds, name: impl AsRef<str>, body: impl Language
) -> Usually<()> {
binds.write().unwrap().insert(name.as_ref().into(), Bind::load(&body)?);
Ok(())
}
impl Bind<TuiEvent, Arc<str>> {
pub fn load <'a> (lang: &impl Language) -> Usually<Self> {
let mut map = Self::new();
lang.each(|item|if item.expr().head() == Ok(Some("see")) {
// TODO
Ok(())
} else if let Ok(Some(_word)) = item.expr().head().word() {
if let Some(event) = TuiKey::from_dsl(item.expr()?.head()?)?.to_crossterm() {
map.add(TuiEvent(event), 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())
})?;
Ok(map)
}
}
/// Default is always empty map regardless if `E` and `C` implement [Default].
impl<E, C> Default for Bind<E, C> {
fn default () -> Self { Self(Default::default()) }
}
impl<C: Default> Default for Binding<C> {
fn default () -> Self {
Self {
commands: Default::default(),
condition: Default::default(),
description: Default::default(),
source: Default::default(),
}
}
}
impl<E: Clone + Ord, C> Bind<E, C> {
/// Create a new event map
pub fn new () -> Self {
Default::default()
}
/// Add a binding to an owned event map.
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
self.add(event, binding);
self
}
/// Add a binding to an event map.
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
if !self.0.contains_key(&event) {
self.0.insert(event.clone(), Default::default());
}
self.0.get_mut(&event).unwrap().push(binding);
self
}
/// Return the binding(s) that correspond to an event.
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
self.0.get(event).map(|x|x.as_slice())
}
/// Return the first binding that corresponds to an event, considering conditions.
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
self.query(event)
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
.flatten()
}
}
impl_debug!(Condition |self, w| { write!(w, "*") });
/// A control axis. /// A control axis.
/// ///
/// ``` /// ```
@ -1309,17 +887,6 @@ pub use self::draw::*;
mod draw { mod draw {
use crate::*; use crate::*;
/// Load custom view definition.
pub(crate) fn load_view <'a> (
views: &Views, name: impl AsRef<str>, body: impl Language,
) -> UsuallyRef<'a, ()> {
views.write().unwrap().insert(
name.as_ref().into(),
body.src()?.unwrap_or_default().into()
);
Ok(())
}
/// The [Draw] implementation for [App] handles the loaded view, /// The [Draw] implementation for [App] handles the loaded view,
/// which is defined in terms of [dizzle] DSL. /// which is defined in terms of [dizzle] DSL.
/// ///
@ -1350,7 +917,13 @@ mod draw {
if let Some(mode) = self.mode.as_ref().and_then(|m|self.config.get_mode(m)) { if let Some(mode) = self.mode.as_ref().and_then(|m|self.config.get_mode(m)) {
let mut error = false; let mut error = false;
for (index, dsl) in mode.view.iter().enumerate() { for (index, dsl) in mode.view.iter().enumerate() {
if let Err(e) = self.draw_mode(to, dsl) { match self.draw_mode(to, dsl) {
Ok(None) => {},
Ok(Some(XYWH(.., w, h))) => {
self.size.0.store(w as usize, Relaxed);
self.size.1.store(h as usize, Relaxed);
},
Err(e) => {
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>"); let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
let message = format!( let message = format!(
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{src}", "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{src}",
@ -1361,6 +934,7 @@ mod draw {
break; break;
} }
} }
}
if !error { if !error {
*self.error.write().unwrap() = None; *self.error.write().unwrap() = None;
} }
@ -1368,15 +942,8 @@ mod draw {
Ok(()) Ok(())
} }
fn draw_mode <'a> (&'a self, to: &mut Tui, dsl: &'a impl Language) -> UsuallyRef<'a, ()> { fn draw_mode <'a, L: Language + 'a> (&'a self, to: &mut Tui, dsl: L) -> Drawn<'a, u16> {
Ok(match self.interpret(to, dsl) { self.interpret(to, dsl)
Err(e) => return Err(e),
Ok(None) => {},
Ok(Some(XYWH(.., w, h))) => {
self.size.0.store(w as usize, Relaxed);
self.size.1.store(h as usize, Relaxed);
},
})
} }
fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> { fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> {
@ -1388,21 +955,15 @@ mod draw {
} }
impl<'a> Interpret<'a, Tui, Option<XYWH<u16>>> for App { impl<'a> Interpret<'a, Tui, Option<XYWH<u16>>> for App {
fn interpret <L: Language + 'a> (&'a self, to: &mut Tui, dsl: L) -> Drawn<'a, u16> {
fn interpret_expr <L: Language + ?Sized> (&'a self, to: &mut Tui, src: &'a L) -> Drawn<u16> { if let Some(expr) = dsl.expr()? {
if let Some(src) = src.src()? { interpret_keyword!(self, to, expr, [
interpret_keyword!(self, to, src, [
kw_when, kw_either, kw_split, kw_align, kw_exact, kw_min, kw_max, kw_push, kw_full, kw_when, kw_either, kw_split, kw_align, kw_exact, kw_min, kw_max, kw_push, kw_full,
kw_tui_text, kw_tui_fg, kw_tui_bg kw_tui_text, kw_tui_fg, kw_tui_bg
]); ]);
Err(format!("interpret_expr: unexpected: {src:?}").into()) Err(format!("interpret_expr: unexpected: {expr:?}").into())
} else { } else if let Some(word) = dsl.word()? {
Err(format!("interpret_expr: no keyword: {src:?}").into()) let mut frags = word.src()?.unwrap().split("/");
}
}
fn interpret_word <L: Language + ?Sized> (&'a self, to: &mut Tui, lang: &'a L) -> Drawn<u16> {
let mut frags = lang.src()?.unwrap().split("/");
match frags.next() { match frags.next() {
//Some(":logo") => view_logo().draw(to), //Some(":logo") => view_logo().draw(to),
Some(":meters") => match frags.next() { Some(":meters") => match frags.next() {
@ -1423,7 +984,7 @@ mod draw {
Some("names") => self.view_scenes_names().draw(to), Some("names") => self.view_scenes_names().draw(to),
_ => panic!() _ => panic!()
}, },
Some(":dialog") => draw_dialog(to, frags, self, lang), Some(":dialog") => draw_dialog(to, frags, self),
Some(":templates") => view_templates(frags, self).draw(to), Some(":templates") => view_templates(frags, self).draw(to),
Some(":sessions") => view_sessions().draw(to), Some(":sessions") => view_sessions().draw(to),
Some(":browse/title") => view_browse_title(self).draw(to), Some(":browse/title") => view_browse_title(self).draw(to),
@ -1434,18 +995,20 @@ mod draw {
Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to), Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to),
Some(_) => { Some(_) => {
let views = self.config.views.read().unwrap(); let views = self.config.views.read().unwrap();
if let Some(lang) = views.get(lang.src()?.unwrap()) { if let Some(lang) = views.get(word.src()?.unwrap()) {
let lang = lang.clone(); let lang = lang.clone();
std::mem::drop(views); std::mem::drop(views);
self.interpret(to, &lang) self.draw_mode(to, lang)
} else { } else {
fg(Color::Rgb(128, 32, 32), format!("undefined: {lang:?}")).draw(to) fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
} }
}, },
_ => unreachable!() _ => unreachable!()
} }
} else {
Err(format!("not word/expr:\n{dsl:?}").into())
}
} }
} }
impl_has!(Sizer: |self: App|self.size); impl_has!(Sizer: |self: App|self.size);
@ -1458,11 +1021,13 @@ mod draw {
fn width_dec (&mut self); fn width_dec (&mut self);
} }
pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &App) -> impl Draw<'a, Tui> { pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &'a App)
-> impl Draw<'a, Tui> + use<'a>
{
let height = (state.config.modes.len() * 2) as u16; let height = (state.config.modes.len() * 2) as u16;
draw(move |to: &mut Tui|{ draw(move |to: &mut Tui|{
let mut index = 0; let mut index = 0;
state.config.modes.for_each(|id, profile| { state.config.modes.for_each(&mut |id: &str, profile: &Mode| {
let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) }; let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>"); let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>"); let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
@ -1597,53 +1162,6 @@ mod draw {
} }
pub fn print_config (config: &Config) {
use ::ansi_term::Color::*;
println!("{:?}", config.dirs);
for (k, v) in config.views.read().unwrap().iter() {
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")));
}
for (k, v) in config.binds.read().unwrap().iter() {
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
for (k, v) in v.0.iter() {
print!("{} ", &Yellow.paint(match &k.0 {
Event::Key(KeyEvent { modifiers, .. }) =>
format!("{:>16}", format!("{modifiers}")),
_ => unimplemented!()
}));
print!("{}", &Yellow.bold().paint(match &k.0 {
Event::Key(KeyEvent { code, .. }) =>
format!("{:<10}", format!("{code}")),
_ => unimplemented!()
}));
for v in v.iter() {
print!(" => {:?}", v.commands);
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
//println!(" {:?}", v.source);
}
}
}
config.modes.for_each(|k, v|{
println!();
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
print!("\n{}", Blue.paint("KEYS"));
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
println!();
v.modes.for_each(|k, v|{
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
print!( " INFO={:?}", v.info);
print!( " VIEW={:?}", v.view);
println!(" KEYS={:?}", v.keys);
});
print!("{}", Blue.paint("VIEW"));
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
println!();
});
}
pub fn print_status (project: &Arrangement) { pub fn print_status (project: &Arrangement) {
println!("Name: {:?}", &project.name); println!("Name: {:?}", &project.name);
println!("JACK: {:?}", &project.jack); println!("JACK: {:?}", &project.jack);