mirror of
https://github.com/elkowar/eww.git
synced 2024-10-05 15:40:12 +03:00
Allow for interpolation in attrs
This commit is contained in:
parent
f555d2fe80
commit
52503fcf7a
@ -31,6 +31,7 @@ pub enum EwwCommand {
|
||||
},
|
||||
KillServer,
|
||||
PrintState(crossbeam_channel::Sender<String>),
|
||||
PrintDebug(crossbeam_channel::Sender<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@ -73,6 +74,10 @@ impl App {
|
||||
.join("\n");
|
||||
sender.send(output).context("sending response from main thread")
|
||||
}
|
||||
EwwCommand::PrintDebug(sender) => {
|
||||
let output = format!("state: {:#?}\n\nconfig: {:#?}", &self.eww_state, &self.eww_config);
|
||||
sender.send(output).context("sending response from main thread")
|
||||
}
|
||||
};
|
||||
|
||||
util::print_result_err("while handling event", &result);
|
||||
|
@ -4,7 +4,7 @@ use regex::Regex;
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
value::{AttrValue, VarName},
|
||||
value::{AttrName, AttrValue},
|
||||
with_text_pos_context,
|
||||
};
|
||||
use maplit::hashmap;
|
||||
@ -44,7 +44,7 @@ impl WidgetDefinition {
|
||||
pub struct WidgetUse {
|
||||
pub name: String,
|
||||
pub children: Vec<WidgetUse>,
|
||||
pub attrs: HashMap<String, AttrValue>,
|
||||
pub attrs: HashMap<AttrName, AttrValue>,
|
||||
pub text_pos: Option<roxmltree::TextPos>,
|
||||
}
|
||||
|
||||
@ -75,9 +75,6 @@ impl WidgetUse {
|
||||
};
|
||||
let text_pos = xml.text_pos();
|
||||
let widget_use = match xml {
|
||||
// TODO the matching here is stupid. This currently uses the inefficient function to parse simple single varrefs,
|
||||
// TODO and does the regex match twice in the from_text_with_var_refs part
|
||||
XmlNode::Text(text) if PATTERN.is_match(&text.text()) => WidgetUse::from_text_with_var_refs(&text.text()),
|
||||
XmlNode::Text(text) => WidgetUse::simple_text(AttrValue::parse_string(text.text())),
|
||||
XmlNode::Element(elem) => WidgetUse {
|
||||
name: elem.tag_name().to_owned(),
|
||||
@ -85,7 +82,12 @@ impl WidgetUse {
|
||||
attrs: elem
|
||||
.attributes()
|
||||
.iter()
|
||||
.map(|attr| (attr.name().to_owned(), AttrValue::parse_string(attr.value().to_owned())))
|
||||
.map(|attr| {
|
||||
(
|
||||
AttrName(attr.name().to_owned()),
|
||||
AttrValue::parse_string(attr.value().to_owned()),
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>(),
|
||||
..WidgetUse::default()
|
||||
},
|
||||
@ -98,19 +100,7 @@ impl WidgetUse {
|
||||
WidgetUse {
|
||||
name: "label".to_owned(),
|
||||
children: vec![],
|
||||
attrs: hashmap! { "text".to_owned() => text }, // TODO this hardcoded "text" is dumdum
|
||||
..WidgetUse::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_text_with_var_refs(text: &str) -> Self {
|
||||
WidgetUse {
|
||||
name: "text".to_owned(),
|
||||
children: parse_string_with_var_refs(text)
|
||||
.into_iter()
|
||||
.map(StringOrVarRef::to_attr_value)
|
||||
.map(WidgetUse::simple_text)
|
||||
.collect(),
|
||||
attrs: hashmap! { AttrName("text".to_owned()) => text }, // TODO this hardcoded "text" is dumdum
|
||||
..WidgetUse::default()
|
||||
}
|
||||
}
|
||||
@ -127,72 +117,12 @@ impl WidgetUse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum StringOrVarRef {
|
||||
String(String),
|
||||
VarRef(String),
|
||||
}
|
||||
|
||||
impl StringOrVarRef {
|
||||
fn to_attr_value(self) -> AttrValue {
|
||||
match self {
|
||||
StringOrVarRef::String(x) => AttrValue::Concrete(PrimitiveValue::from_string(x)),
|
||||
StringOrVarRef::VarRef(x) => AttrValue::VarRef(VarName(x)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this could be a fancy Iterator implementation, ig
|
||||
fn parse_string_with_var_refs(s: &str) -> Vec<StringOrVarRef> {
|
||||
let mut elements = Vec::new();
|
||||
|
||||
let mut cur_word = "".to_owned();
|
||||
let mut cur_varref: Option<String> = None;
|
||||
let mut curly_count = 0;
|
||||
for c in s.chars() {
|
||||
if let Some(ref mut varref) = cur_varref {
|
||||
if c == '}' {
|
||||
curly_count -= 1;
|
||||
if curly_count == 0 {
|
||||
elements.push(StringOrVarRef::VarRef(std::mem::take(varref)));
|
||||
cur_varref = None
|
||||
}
|
||||
} else {
|
||||
curly_count = 2;
|
||||
varref.push(c);
|
||||
}
|
||||
} else {
|
||||
if c == '{' {
|
||||
curly_count += 1;
|
||||
if curly_count == 2 {
|
||||
if !cur_word.is_empty() {
|
||||
elements.push(StringOrVarRef::String(std::mem::take(&mut cur_word)));
|
||||
}
|
||||
cur_varref = Some(String::new())
|
||||
}
|
||||
} else {
|
||||
cur_word.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(unfinished_varref) = cur_varref.take() {
|
||||
elements.push(StringOrVarRef::String(unfinished_varref));
|
||||
} else if !cur_word.is_empty() {
|
||||
elements.push(StringOrVarRef::String(cur_word.to_owned()));
|
||||
}
|
||||
elements
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use maplit::hashmap;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn mk_attr_str(s: &str) -> AttrValue {
|
||||
AttrValue::Concrete(PrimitiveValue::from_string(s.to_owned()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_text() {
|
||||
let expected_attr_value = AttrValue::Concrete(PrimitiveValue::from_string("my text".to_owned()));
|
||||
@ -202,31 +132,12 @@ mod test {
|
||||
WidgetUse {
|
||||
name: "label".to_owned(),
|
||||
children: Vec::new(),
|
||||
attrs: hashmap! { "text".to_owned() => expected_attr_value},
|
||||
attrs: hashmap! { AttrName("text".to_owned()) => expected_attr_value},
|
||||
..WidgetUse::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_with_var_refs() {
|
||||
let expected_attr_value1 = mk_attr_str("my text");
|
||||
let expected_attr_value2 = AttrValue::VarRef(VarName("var".to_owned()));
|
||||
let widget = WidgetUse::from_text_with_var_refs("my text{{var}}");
|
||||
assert_eq!(
|
||||
widget,
|
||||
WidgetUse {
|
||||
name: "layout".to_owned(),
|
||||
attrs: hashmap! { "halign".to_owned() => mk_attr_str("center"), "space-evenly".to_owned() => mk_attr_str("false")},
|
||||
children: vec![
|
||||
WidgetUse::simple_text(expected_attr_value1),
|
||||
WidgetUse::simple_text(expected_attr_value2),
|
||||
],
|
||||
..WidgetUse::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_widget_use() {
|
||||
let input = r#"
|
||||
@ -238,14 +149,11 @@ mod test {
|
||||
let document = roxmltree::Document::parse(input).unwrap();
|
||||
let xml = XmlNode::from(document.root_element().clone());
|
||||
|
||||
println!("{}", xml);
|
||||
assert_eq!(true, false);
|
||||
|
||||
let expected = WidgetUse {
|
||||
name: "widget_name".to_owned(),
|
||||
attrs: hashmap! {
|
||||
"attr1".to_owned() => AttrValue::Concrete(PrimitiveValue::from_string("hi".to_owned())),
|
||||
"attr2".to_owned() => AttrValue::Concrete(PrimitiveValue::from_string("12".to_owned())),
|
||||
AttrName("attr1".to_owned()) => AttrValue::Concrete(PrimitiveValue::from_string("hi".to_owned())),
|
||||
AttrName("attr2".to_owned()) => AttrValue::Concrete(PrimitiveValue::from_string("12".to_owned())),
|
||||
},
|
||||
children: vec![
|
||||
WidgetUse::new("child_widget".to_owned(), Vec::new()),
|
||||
@ -284,21 +192,4 @@ mod test {
|
||||
WidgetDefinition::from_xml_element(xml.as_element().unwrap().to_owned()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_string_or_var_ref_list() {
|
||||
let input = "{{foo}}{{bar}}baz{{bat}}quok{{test}}";
|
||||
let output = parse_string_with_var_refs(input);
|
||||
assert_eq!(
|
||||
output,
|
||||
vec![
|
||||
StringOrVarRef::VarRef("foo".to_owned()),
|
||||
StringOrVarRef::VarRef("bar".to_owned()),
|
||||
StringOrVarRef::String("baz".to_owned()),
|
||||
StringOrVarRef::VarRef("bat".to_owned()),
|
||||
StringOrVarRef::String("quok".to_owned()),
|
||||
StringOrVarRef::VarRef("test".to_owned()),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
use crate::{config::WindowName, value::VarName};
|
||||
use crate::{
|
||||
config::WindowName,
|
||||
value::{self, AttrName, VarName},
|
||||
};
|
||||
use anyhow::*;
|
||||
use itertools::Itertools;
|
||||
use std::{collections::HashMap, process::Command, sync::Arc};
|
||||
|
||||
use crate::value::{AttrValue, PrimitiveValue};
|
||||
@ -7,9 +11,10 @@ use crate::value::{AttrValue, PrimitiveValue};
|
||||
/// Handler that get's executed to apply the necessary parts of the eww state to
|
||||
/// a gtk widget. These are created and initialized in EwwState::resolve.
|
||||
pub struct StateChangeHandler {
|
||||
func: Box<dyn Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static>,
|
||||
constant_values: HashMap<String, PrimitiveValue>,
|
||||
unresolved_attrs: HashMap<String, VarName>,
|
||||
func: Box<dyn Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static>,
|
||||
constant_values: HashMap<AttrName, PrimitiveValue>,
|
||||
unresolved_attrs: HashMap<AttrName, VarName>,
|
||||
string_with_varrefs_resolvers: HashMap<AttrName, Box<dyn Fn(&HashMap<VarName, PrimitiveValue>) -> PrimitiveValue>>,
|
||||
}
|
||||
|
||||
impl StateChangeHandler {
|
||||
@ -24,6 +29,9 @@ impl StateChangeHandler {
|
||||
.with_context(|| format!("Unknown variable '{}' was referenced", var_ref))?;
|
||||
all_resolved_attrs.insert(attr_name.to_owned(), resolved.clone());
|
||||
}
|
||||
for (attr_name, resolver) in self.string_with_varrefs_resolvers.iter() {
|
||||
all_resolved_attrs.insert(attr_name.to_owned(), resolver(state));
|
||||
}
|
||||
|
||||
let result: Result<_> = (self.func)(all_resolved_attrs);
|
||||
if let Err(err) = result {
|
||||
@ -117,21 +125,35 @@ impl EwwState {
|
||||
&'a self,
|
||||
local_env: &'a HashMap<VarName, AttrValue>,
|
||||
value: &'a AttrValue,
|
||||
) -> Result<&'a PrimitiveValue> {
|
||||
) -> Result<PrimitiveValue> {
|
||||
match value {
|
||||
AttrValue::Concrete(primitive) => Ok(&primitive),
|
||||
AttrValue::Concrete(primitive) => Ok(primitive.clone()),
|
||||
AttrValue::VarRef(var_name) => match local_env.get(var_name) {
|
||||
// look up if variables are found in the local env, and resolve as far as possible
|
||||
Some(AttrValue::Concrete(primitive)) => Ok(primitive),
|
||||
Some(AttrValue::Concrete(primitive)) => Ok(primitive.clone()),
|
||||
Some(AttrValue::VarRef(var_name)) => self
|
||||
.variables_state
|
||||
.get(var_name)
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow!("Unknown variable '{}' referenced", var_name)),
|
||||
Some(AttrValue::StringWithVarRefs(content)) => content
|
||||
.iter()
|
||||
.map(|x| x.clone().to_attr_value())
|
||||
.map(|value| self.resolve_once(local_env, &value))
|
||||
.fold_results(String::new(), |acc, cur| format!("{}{}", acc, cur))
|
||||
.map(PrimitiveValue::from_string),
|
||||
None => self
|
||||
.variables_state
|
||||
.get(var_name)
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow!("Unknown variable '{}' referenced", var_name)),
|
||||
},
|
||||
AttrValue::StringWithVarRefs(content) => content
|
||||
.iter()
|
||||
.map(|x| x.clone().to_attr_value())
|
||||
.map(|value| self.resolve_once(local_env, &value))
|
||||
.fold_results(String::new(), |acc, cur| format!("{}{}", acc, cur))
|
||||
.map(PrimitiveValue::from_string),
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,11 +162,11 @@ impl EwwState {
|
||||
/// nesting var_refs from local-env. This means that no elements in the
|
||||
/// local_env may be var-refs into the local_env again, but only into the
|
||||
/// global state.
|
||||
pub fn resolve<F: Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static + Clone>(
|
||||
pub fn resolve<F: Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static + Clone>(
|
||||
&mut self,
|
||||
window_name: &WindowName,
|
||||
local_env: &HashMap<VarName, AttrValue>,
|
||||
mut needed_attributes: HashMap<String, AttrValue>,
|
||||
mut needed_attributes: HashMap<AttrName, AttrValue>,
|
||||
set_value: F,
|
||||
) {
|
||||
// Resolve first collects all variable references and creates a set of
|
||||
@ -159,8 +181,11 @@ impl EwwState {
|
||||
.entry(window_name.clone())
|
||||
.or_insert_with(EwwWindowState::default);
|
||||
|
||||
let mut string_with_varrefs_resolvers: HashMap<_, Box<dyn Fn(&HashMap<VarName, PrimitiveValue>) -> PrimitiveValue>> =
|
||||
HashMap::new();
|
||||
|
||||
let mut resolved_attrs = HashMap::new();
|
||||
let mut unresolved_attrs: HashMap<String, VarName> = HashMap::new();
|
||||
let mut unresolved_attrs: HashMap<AttrName, VarName> = HashMap::new();
|
||||
needed_attributes
|
||||
.drain()
|
||||
.for_each(|(attr_name, attr_value)| match attr_value {
|
||||
@ -168,8 +193,19 @@ impl EwwState {
|
||||
AttrValue::Concrete(primitive) => {
|
||||
resolved_attrs.insert(attr_name, primitive);
|
||||
}
|
||||
AttrValue::StringWithVarRefs(content) => {
|
||||
let content = content.resolve_one_level(local_env);
|
||||
let resolver = generate_string_with_var_refs_resolver(content);
|
||||
string_with_varrefs_resolvers.insert(attr_name, Box::new(resolver));
|
||||
}
|
||||
|
||||
AttrValue::VarRef(var_name) => match local_env.get(&var_name) {
|
||||
Some(AttrValue::StringWithVarRefs(content)) => {
|
||||
let content = content.clone().resolve_one_level(local_env);
|
||||
let resolver = generate_string_with_var_refs_resolver(content);
|
||||
string_with_varrefs_resolvers.insert(attr_name, Box::new(resolver));
|
||||
}
|
||||
|
||||
// look up if variables are found in the local env, and resolve as far as possible
|
||||
Some(AttrValue::Concrete(concrete_from_local)) => {
|
||||
resolved_attrs.insert(attr_name, concrete_from_local.clone());
|
||||
@ -185,12 +221,13 @@ impl EwwState {
|
||||
},
|
||||
});
|
||||
|
||||
if unresolved_attrs.is_empty() {
|
||||
if unresolved_attrs.is_empty() && string_with_varrefs_resolvers.is_empty() {
|
||||
// if there are no unresolved variables, we can set the value directly
|
||||
set_value(resolved_attrs)?;
|
||||
} else {
|
||||
// otherwise register and execute the handler
|
||||
let handler = StateChangeHandler {
|
||||
string_with_varrefs_resolvers,
|
||||
func: Box::new(set_value.clone()),
|
||||
constant_values: resolved_attrs,
|
||||
unresolved_attrs,
|
||||
@ -205,6 +242,26 @@ impl EwwState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_string_with_var_refs_resolver(
|
||||
string_with_varrefs: value::StringWithVarRefs,
|
||||
) -> impl Fn(&HashMap<VarName, PrimitiveValue>) -> PrimitiveValue {
|
||||
move |variables: &HashMap<VarName, PrimitiveValue>| {
|
||||
PrimitiveValue::from_string(
|
||||
string_with_varrefs
|
||||
.iter()
|
||||
.map(|entry| match entry {
|
||||
value::StringOrVarRef::VarRef(var_name) => variables
|
||||
.get(var_name)
|
||||
.expect(&format!("Impossible state: unknown variable {}.\n{:?}", var_name, variables))
|
||||
.clone()
|
||||
.into_inner(),
|
||||
value::StringOrVarRef::Primitive(s) => s.to_string(),
|
||||
})
|
||||
.join(""),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a command and get the output
|
||||
pub fn run_command(cmd: &str) -> Result<PrimitiveValue> {
|
||||
let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?;
|
||||
|
@ -7,6 +7,7 @@
|
||||
extern crate gio;
|
||||
extern crate gtk;
|
||||
|
||||
use crate::value::{PrimitiveValue, VarName};
|
||||
use anyhow::*;
|
||||
use eww_state::*;
|
||||
use log;
|
||||
@ -19,7 +20,6 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use value::{PrimitiveValue, VarName};
|
||||
|
||||
pub mod app;
|
||||
pub mod config;
|
||||
@ -103,6 +103,9 @@ pub enum OptAction {
|
||||
|
||||
#[structopt(name = "state", help = "Print the current eww-state")]
|
||||
ShowState,
|
||||
|
||||
#[structopt(name = "debug", help = "Print out the widget structure as seen by eww")]
|
||||
ShowDebug,
|
||||
}
|
||||
|
||||
impl OptAction {
|
||||
@ -116,6 +119,10 @@ impl OptAction {
|
||||
let (send, recv) = crossbeam_channel::unbounded();
|
||||
(app::EwwCommand::PrintState(send), Some(recv))
|
||||
}
|
||||
OptAction::ShowDebug => {
|
||||
let (send, recv) = crossbeam_channel::unbounded();
|
||||
(app::EwwCommand::PrintDebug(send), Some(recv))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
176
src/value.rs
176
src/value.rs
@ -1,176 +0,0 @@
|
||||
use anyhow::*;
|
||||
use derive_more;
|
||||
use lazy_static::lazy_static;
|
||||
use ref_cast::RefCast;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{convert::TryFrom, fmt};
|
||||
|
||||
use crate::impl_try_from;
|
||||
|
||||
#[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)]
|
||||
pub struct PrimitiveValue(String);
|
||||
|
||||
impl fmt::Display for PrimitiveValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "\"{}\"", self.0)
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for PrimitiveValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for PrimitiveValue {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
/// parses the value, trying to turn it into a number and a boolean first,
|
||||
/// before deciding that it is a string.
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
Ok(PrimitiveValue::from_string(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl_try_from!(PrimitiveValue {
|
||||
for String => |x| x.as_string();
|
||||
for f64 => |x| x.as_f64();
|
||||
for bool => |x| x.as_bool();
|
||||
});
|
||||
|
||||
impl From<bool> for PrimitiveValue {
|
||||
fn from(x: bool) -> Self {
|
||||
PrimitiveValue(format!("{}", x))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for PrimitiveValue {
|
||||
fn from(s: i32) -> Self {
|
||||
PrimitiveValue(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PrimitiveValue {
|
||||
fn from(s: &str) -> Self {
|
||||
PrimitiveValue(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveValue {
|
||||
pub fn from_string(s: String) -> Self {
|
||||
PrimitiveValue(s.to_string())
|
||||
}
|
||||
|
||||
/// This will never fail
|
||||
pub fn as_string(&self) -> Result<String> {
|
||||
Ok(self.0.to_owned())
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> Result<f64> {
|
||||
self.0
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("couldn't convert {:?} to f64: {}", &self, e))
|
||||
}
|
||||
|
||||
pub fn as_i32(&self) -> Result<i32> {
|
||||
self.0
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("couldn't convert {:?} to i32: {}", &self, e))
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Result<bool> {
|
||||
self.0
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("couldn't convert {:?} to bool: {}", &self, e))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
derive_more::AsRef,
|
||||
derive_more::From,
|
||||
derive_more::FromStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
RefCast,
|
||||
)]
|
||||
pub struct VarName(pub String);
|
||||
|
||||
impl std::borrow::Borrow<str> for VarName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VarName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum AttrValue {
|
||||
Concrete(PrimitiveValue),
|
||||
VarRef(VarName),
|
||||
}
|
||||
|
||||
impl AttrValue {
|
||||
pub fn as_string(&self) -> Result<String> {
|
||||
match self {
|
||||
AttrValue::Concrete(x) => x.as_string(),
|
||||
_ => Err(anyhow!("{:?} is not a string", self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> Result<f64> {
|
||||
match self {
|
||||
AttrValue::Concrete(x) => x.as_f64(),
|
||||
_ => Err(anyhow!("{:?} is not an f64", self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i32(&self) -> Result<i32> {
|
||||
match self {
|
||||
AttrValue::Concrete(x) => x.as_i32(),
|
||||
_ => Err(anyhow!("{:?} is not an i32", self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Result<bool> {
|
||||
match self {
|
||||
AttrValue::Concrete(x) => x.as_bool(),
|
||||
_ => Err(anyhow!("{:?} is not a bool", self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_var_ref(&self) -> Result<&VarName> {
|
||||
match self {
|
||||
AttrValue::VarRef(x) => Ok(x),
|
||||
_ => Err(anyhow!("{:?} is not a variable reference", self)),
|
||||
}
|
||||
}
|
||||
|
||||
/// parses the value, trying to turn it into VarRef,
|
||||
/// a number and a boolean first, before deciding that it is a string.
|
||||
pub fn parse_string(s: String) -> Self {
|
||||
lazy_static! {
|
||||
static ref PATTERN: Regex = Regex::new("^\\{\\{(.*)\\}\\}$").unwrap();
|
||||
};
|
||||
|
||||
if let Some(ref_name) = PATTERN.captures(&s).and_then(|cap| cap.get(1)).map(|x| x.as_str()) {
|
||||
AttrValue::VarRef(VarName(ref_name.to_owned()))
|
||||
} else {
|
||||
AttrValue::Concrete(PrimitiveValue::from_string(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<PrimitiveValue> for AttrValue {
|
||||
fn from(value: PrimitiveValue) -> Self {
|
||||
AttrValue::Concrete(value)
|
||||
}
|
||||
}
|
108
src/value/attr_value.rs
Normal file
108
src/value/attr_value.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use anyhow::*;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum AttrValue {
|
||||
Concrete(PrimitiveValue),
|
||||
StringWithVarRefs(StringWithVarRefs),
|
||||
VarRef(VarName),
|
||||
}
|
||||
|
||||
impl AttrValue {
|
||||
pub fn as_string(&self) -> Result<String> {
|
||||
match self {
|
||||
AttrValue::Concrete(x) => x.as_string(),
|
||||
_ => Err(anyhow!("{:?} is not a string", self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> Result<f64> {
|
||||
match self {
|
||||
AttrValue::Concrete(x) => x.as_f64(),
|
||||
_ => Err(anyhow!("{:?} is not an f64", self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i32(&self) -> Result<i32> {
|
||||
match self {
|
||||
AttrValue::Concrete(x) => x.as_i32(),
|
||||
_ => Err(anyhow!("{:?} is not an i32", self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Result<bool> {
|
||||
match self {
|
||||
AttrValue::Concrete(x) => x.as_bool(),
|
||||
_ => Err(anyhow!("{:?} is not a bool", self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_var_ref(&self) -> Result<&VarName> {
|
||||
match self {
|
||||
AttrValue::VarRef(x) => Ok(x),
|
||||
_ => Err(anyhow!("{:?} is not a variable reference", self)),
|
||||
}
|
||||
}
|
||||
|
||||
/// parses the value, trying to turn it into VarRef,
|
||||
/// a number and a boolean first, before deciding that it is a string.
|
||||
pub fn parse_string(s: String) -> Self {
|
||||
lazy_static! {
|
||||
static ref VAR_REF_PATTERN: Regex = Regex::new("\\{\\{(.*?)\\}\\}").unwrap();
|
||||
};
|
||||
|
||||
let pattern: &Regex = &*VAR_REF_PATTERN;
|
||||
if let Some(match_range) = pattern.find(&s) {
|
||||
if match_range.start() == 0 && match_range.end() == s.len() {
|
||||
// we can unwrap here, as we just verified that there is a valid match already
|
||||
let ref_name = VAR_REF_PATTERN.captures(&s).and_then(|cap| cap.get(1)).unwrap().as_str();
|
||||
AttrValue::VarRef(VarName(ref_name.to_owned()))
|
||||
} else {
|
||||
AttrValue::StringWithVarRefs(StringWithVarRefs::parse_string(&s))
|
||||
}
|
||||
} else {
|
||||
AttrValue::Concrete(PrimitiveValue::from_string(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<PrimitiveValue> for AttrValue {
|
||||
fn from(value: PrimitiveValue) -> Self {
|
||||
AttrValue::Concrete(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
#[test]
|
||||
fn test_parse_concrete_attr_value() {
|
||||
assert_eq!(
|
||||
AttrValue::Concrete(PrimitiveValue::from_string("foo".to_owned())),
|
||||
AttrValue::parse_string("foo".to_owned())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_var_ref_attr_value() {
|
||||
assert_eq!(
|
||||
AttrValue::VarRef(VarName("foo".to_owned())),
|
||||
AttrValue::parse_string("{{foo}}".to_owned())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_parse_string_with_var_refs_attr_value() {
|
||||
assert_eq!(
|
||||
AttrValue::StringWithVarRefs(
|
||||
vec![
|
||||
StringOrVarRef::VarRef(VarName("var".to_owned())),
|
||||
StringOrVarRef::primitive("something".to_owned())
|
||||
]
|
||||
.into()
|
||||
),
|
||||
AttrValue::parse_string("{{var}}something".to_owned())
|
||||
);
|
||||
}
|
||||
}
|
59
src/value/mod.rs
Normal file
59
src/value/mod.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use derive_more;
|
||||
use ref_cast::RefCast;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
pub mod attr_value;
|
||||
pub mod primitive;
|
||||
pub mod string_with_varrefs;
|
||||
pub use attr_value::*;
|
||||
pub use primitive::*;
|
||||
pub use string_with_varrefs::*;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Clone, Hash, PartialEq, Eq, derive_more::AsRef, derive_more::From, derive_more::FromStr, Serialize, Deserialize, RefCast,
|
||||
)]
|
||||
pub struct VarName(pub String);
|
||||
|
||||
impl std::borrow::Borrow<str> for VarName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VarName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for VarName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "VarName(\"{}\")", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Clone, Hash, PartialEq, Eq, derive_more::AsRef, derive_more::From, derive_more::FromStr, Serialize, Deserialize, RefCast,
|
||||
)]
|
||||
pub struct AttrName(pub String);
|
||||
|
||||
impl std::borrow::Borrow<str> for AttrName {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AttrName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for AttrName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "AttrName({})", self.0)
|
||||
}
|
||||
}
|
87
src/value/primitive.rs
Normal file
87
src/value/primitive.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use anyhow::*;
|
||||
use derive_more;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{convert::TryFrom, fmt};
|
||||
|
||||
use crate::impl_try_from;
|
||||
|
||||
#[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)]
|
||||
pub struct PrimitiveValue(String);
|
||||
|
||||
impl fmt::Display for PrimitiveValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for PrimitiveValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "\"{}\"", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for PrimitiveValue {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
/// parses the value, trying to turn it into a number and a boolean first,
|
||||
/// before deciding that it is a string.
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
Ok(PrimitiveValue::from_string(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl_try_from!(PrimitiveValue {
|
||||
for String => |x| x.as_string();
|
||||
for f64 => |x| x.as_f64();
|
||||
for bool => |x| x.as_bool();
|
||||
});
|
||||
|
||||
impl From<bool> for PrimitiveValue {
|
||||
fn from(x: bool) -> Self {
|
||||
PrimitiveValue(x.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for PrimitiveValue {
|
||||
fn from(s: i32) -> Self {
|
||||
PrimitiveValue(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PrimitiveValue {
|
||||
fn from(s: &str) -> Self {
|
||||
PrimitiveValue(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveValue {
|
||||
pub fn from_string(s: String) -> Self {
|
||||
PrimitiveValue(s.to_string())
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// This will never fail
|
||||
pub fn as_string(&self) -> Result<String> {
|
||||
Ok(self.0.to_owned())
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> Result<f64> {
|
||||
self.0
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("couldn't convert {:?} to f64: {}", &self, e))
|
||||
}
|
||||
|
||||
pub fn as_i32(&self) -> Result<i32> {
|
||||
self.0
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("couldn't convert {:?} to i32: {}", &self, e))
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Result<bool> {
|
||||
self.0
|
||||
.parse()
|
||||
.map_err(|e| anyhow!("couldn't convert {:?} to bool: {}", &self, e))
|
||||
}
|
||||
}
|
135
src/value/string_with_varrefs.rs
Normal file
135
src/value/string_with_varrefs.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use std::{collections::HashMap, iter::FromIterator};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use IntoIterator;
|
||||
|
||||
use super::{AttrValue, PrimitiveValue, VarName};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, derive_more::Into, derive_more::From)]
|
||||
pub struct StringWithVarRefs(Vec<StringOrVarRef>);
|
||||
|
||||
impl IntoIterator for StringWithVarRefs {
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
type Item = StringOrVarRef;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<StringOrVarRef> for StringWithVarRefs {
|
||||
fn from_iter<T: IntoIterator<Item = StringOrVarRef>>(iter: T) -> Self {
|
||||
let mut result = StringWithVarRefs(Vec::new());
|
||||
result.0.extend(iter);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl StringWithVarRefs {
|
||||
pub fn iter(&self) -> std::slice::Iter<StringOrVarRef> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn var_refs(&self) -> impl Iterator<Item = &VarName> {
|
||||
self.0.iter().filter_map(|x| x.as_var_ref())
|
||||
}
|
||||
|
||||
pub fn resolve_one_level(self, variables: &HashMap<VarName, AttrValue>) -> StringWithVarRefs {
|
||||
self.into_iter()
|
||||
.map(|entry| match entry {
|
||||
StringOrVarRef::VarRef(var_name) => match variables.get(&var_name).clone() {
|
||||
Some(AttrValue::Concrete(primitive)) => StringOrVarRef::Primitive(primitive.clone()),
|
||||
_ => StringOrVarRef::VarRef(var_name),
|
||||
},
|
||||
_ => entry,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// TODO this could be a fancy Iterator implementation, ig
|
||||
pub fn parse_string(s: &str) -> StringWithVarRefs {
|
||||
let mut elements = Vec::new();
|
||||
|
||||
let mut cur_word = "".to_owned();
|
||||
let mut cur_varref: Option<String> = None;
|
||||
let mut curly_count = 0;
|
||||
for c in s.chars() {
|
||||
if let Some(ref mut varref) = cur_varref {
|
||||
if c == '}' {
|
||||
curly_count -= 1;
|
||||
if curly_count == 0 {
|
||||
elements.push(StringOrVarRef::VarRef(VarName(std::mem::take(varref))));
|
||||
cur_varref = None
|
||||
}
|
||||
} else {
|
||||
curly_count = 2;
|
||||
varref.push(c);
|
||||
}
|
||||
} else {
|
||||
if c == '{' {
|
||||
curly_count += 1;
|
||||
if curly_count == 2 {
|
||||
if !cur_word.is_empty() {
|
||||
elements.push(StringOrVarRef::primitive(std::mem::take(&mut cur_word)));
|
||||
}
|
||||
cur_varref = Some(String::new())
|
||||
}
|
||||
} else {
|
||||
cur_word.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(unfinished_varref) = cur_varref.take() {
|
||||
elements.push(StringOrVarRef::primitive(unfinished_varref));
|
||||
} else if !cur_word.is_empty() {
|
||||
elements.push(StringOrVarRef::primitive(cur_word.to_owned()));
|
||||
}
|
||||
StringWithVarRefs(elements)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum StringOrVarRef {
|
||||
Primitive(PrimitiveValue),
|
||||
VarRef(VarName),
|
||||
}
|
||||
|
||||
impl StringOrVarRef {
|
||||
pub fn primitive(s: String) -> Self {
|
||||
StringOrVarRef::Primitive(PrimitiveValue::from_string(s))
|
||||
}
|
||||
|
||||
pub fn to_attr_value(self) -> AttrValue {
|
||||
match self {
|
||||
StringOrVarRef::Primitive(x) => AttrValue::Concrete(x),
|
||||
StringOrVarRef::VarRef(x) => AttrValue::VarRef(x),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_var_ref(&self) -> Option<&VarName> {
|
||||
match self {
|
||||
StringOrVarRef::VarRef(x) => Some(&x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(Test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_parse_string_or_var_ref_list() {
|
||||
let input = "{{foo}}{{bar}}baz{{bat}}quok{{test}}";
|
||||
let output = parse_string_with_var_refs(input);
|
||||
assert_eq!(
|
||||
output,
|
||||
vec![
|
||||
StringOrVarRef::VarRef("foo".to_owned()),
|
||||
StringOrVarRef::VarRef("bar".to_owned()),
|
||||
StringOrVarRef::String("baz".to_owned()),
|
||||
StringOrVarRef::VarRef("bat".to_owned()),
|
||||
StringOrVarRef::String("quok".to_owned()),
|
||||
StringOrVarRef::VarRef("test".to_owned()),
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
use crate::{
|
||||
config::{element, WindowName},
|
||||
eww_state::*,
|
||||
value::{AttrValue, VarName},
|
||||
value::{AttrName, AttrValue, VarName},
|
||||
};
|
||||
use anyhow::*;
|
||||
use gtk::prelude::*;
|
||||
use itertools::Itertools;
|
||||
|
||||
use std::{collections::HashMap, process::Command};
|
||||
use widget_definitions::*;
|
||||
@ -26,7 +27,7 @@ struct BuilderArgs<'a, 'b, 'c, 'd, 'e> {
|
||||
eww_state: &'a mut EwwState,
|
||||
local_env: &'b HashMap<VarName, AttrValue>,
|
||||
widget: &'c element::WidgetUse,
|
||||
unhandled_attrs: Vec<&'c str>,
|
||||
unhandled_attrs: Vec<&'c AttrName>,
|
||||
window_name: &'d WindowName,
|
||||
widget_definitions: &'e HashMap<String, element::WidgetDefinition>,
|
||||
}
|
||||
@ -72,11 +73,14 @@ pub fn widget_use_to_gtk_widget(
|
||||
.into_iter()
|
||||
.map(|(attr_name, attr_value)| {
|
||||
(
|
||||
VarName(attr_name),
|
||||
VarName(attr_name.0),
|
||||
match attr_value {
|
||||
AttrValue::VarRef(var_ref) => {
|
||||
local_env.get(&var_ref).cloned().unwrap_or_else(|| AttrValue::VarRef(var_ref))
|
||||
}
|
||||
AttrValue::StringWithVarRefs(content) => {
|
||||
AttrValue::StringWithVarRefs(content.resolve_one_level(local_env))
|
||||
}
|
||||
AttrValue::Concrete(value) => AttrValue::Concrete(value),
|
||||
},
|
||||
)
|
||||
@ -120,7 +124,7 @@ fn build_builtin_gtk_widget(
|
||||
local_env,
|
||||
widget,
|
||||
window_name,
|
||||
unhandled_attrs: widget.attrs.keys().map(|x| x.as_ref()).collect(),
|
||||
unhandled_attrs: widget.attrs.keys().collect(),
|
||||
widget_definitions,
|
||||
};
|
||||
let gtk_widget = match widget_to_gtk_widget(&mut bargs) {
|
||||
@ -164,9 +168,9 @@ fn build_builtin_gtk_widget(
|
||||
if !bargs.unhandled_attrs.is_empty() {
|
||||
eprintln!(
|
||||
"{}WARN: Unknown attribute used in {}: {}",
|
||||
widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(),
|
||||
widget.text_pos.map(|x| format!("{} | ", x)).unwrap_or_default(),
|
||||
widget.name,
|
||||
bargs.unhandled_attrs.join(", ")
|
||||
bargs.unhandled_attrs.iter().map(|x| x.to_string()).join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
@ -182,13 +186,14 @@ macro_rules! resolve_block {
|
||||
}) => {
|
||||
$({
|
||||
$(
|
||||
$args.unhandled_attrs.retain(|a| a != &::std::stringify!($attr_name).replace('_', "-"));
|
||||
$args.unhandled_attrs.retain(|a| &a.0 != &::std::stringify!($attr_name).replace('_', "-"));
|
||||
)*
|
||||
|
||||
let attr_map: Result<_> = try {
|
||||
::maplit::hashmap! {
|
||||
$(
|
||||
::std::stringify!($attr_name).to_owned() => resolve_block!(@get_value $args, &::std::stringify!($attr_name).replace('_', "-"), $(= $default)?)
|
||||
crate::value::AttrName(::std::stringify!($attr_name).to_owned()) =>
|
||||
resolve_block!(@get_value $args, &::std::stringify!($attr_name).replace('_', "-"), $(= $default)?)
|
||||
),*
|
||||
}
|
||||
};
|
||||
|
@ -240,7 +240,7 @@ fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
|
||||
let gtk_widget = gtk::Label::new(None);
|
||||
resolve_block!(bargs, gtk_widget, {
|
||||
// @prop - the text to display
|
||||
prop(text: as_string) { gtk_widget.set_text(&text) },
|
||||
prop(text: as_string) { gtk_widget.set_text(dbg!(&text)) },
|
||||
});
|
||||
Ok(gtk_widget)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user