use crate::script::{OutputStream, Script}; use gtk::prelude::*; use indexmap::IndexMap; use std::sync::{Arc, Mutex}; use tokio::spawn; #[derive(Debug)] enum DynamicStringSegment { Static(String), Dynamic(Script), } pub struct DynamicString { // pub label: gtk::Label, } impl DynamicString { pub fn new(input: &str, f: F) -> Self where F: FnMut(String) -> Continue + 'static, { let mut segments = vec![]; let mut chars = input.chars().collect::>(); while !chars.is_empty() { let char = &chars[..=1]; let (token, skip) = if let ['{', '{'] = char { const SKIP_BRACKETS: usize = 4; let str = chars .iter() .skip(2) .enumerate() .take_while(|(i, &c)| c != '}' && chars[i + 1] != '}') .map(|(_, c)| c) .collect::(); let len = str.len(); ( DynamicStringSegment::Dynamic(Script::from(str.as_str())), len + SKIP_BRACKETS, ) } else { let str = chars .iter() .enumerate() .take_while(|(i, &c)| !(c == '{' && chars[i + 1] == '{')) .map(|(_, c)| c) .collect::(); let len = str.len(); (DynamicStringSegment::Static(str), len) }; assert_ne!(skip, 0); segments.push(token); chars.drain(..skip); } let label_parts = Arc::new(Mutex::new(IndexMap::new())); let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); for (i, segment) in segments.into_iter().enumerate() { match segment { DynamicStringSegment::Static(str) => { label_parts .lock() .expect("Failed to get lock on label parts") .insert(i, str); } DynamicStringSegment::Dynamic(script) => { let tx = tx.clone(); let label_parts = label_parts.clone(); spawn(async move { script .run(|(out, _)| { if let OutputStream::Stdout(out) = out { let mut label_parts = label_parts .lock() .expect("Failed to get lock on label parts"); label_parts // .lock() // .expect("Failed to get lock on label parts") .insert(i, out); let string = label_parts .iter() .map(|(_, part)| part.as_str()) .collect::(); tx.send(string).expect("Failed to send update"); } }) .await; }); } } } // initialize { let label_parts = label_parts .lock() .expect("Failed to get lock on label parts") .iter() .map(|(_, part)| part.as_str()) .collect::(); tx.send(label_parts).expect("Failed to send update"); } rx.attach(None, f); // Self { label } Self {} } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test() { // TODO: see if we can run gtk tests in ci if gtk::init().is_ok() { let label = gtk::Label::new(None); DynamicString::new( "Uptime: {{1000:uptime -p | cut -d ' ' -f2-}}", move |string| { label.set_label(&string); Continue(true) }, ); } } }