Merge branch 'conditions-on-tag'

Closes #137
This commit is contained in:
Daniel Eklöf 2022-04-24 21:07:33 +02:00
commit acc20913ef
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
14 changed files with 599 additions and 204 deletions

View file

@ -27,6 +27,46 @@
### Changed ### Changed
* Minimum required meson version is now 0.58. * Minimum required meson version is now 0.58.
* **BREAKING CHANGE**: overhaul of the `map` particle. Instead of
specifying a `tag` and then an array of `values`, you must now
simply use an array of `conditions`, that consist of:
`<tag> <operation> <value>`
where `<operation>` is one of:
`== != < <= > >=`
Note that boolean tags must be used as is:
`online`
`~online # use '~' to match for their falsehood`
As an example, if you previously had something like:
```
map:
tag: State
values:
unrecognized:
...
```
You would now write it as:
```
map:
conditions:
State == unrecognized:
...
```
For a more thorough explanation, see the updated map section in the
man page for yambar-particles([#137][137] and [#175][175]).
[137]: https://codeberg.org/dnkl/yambar/issues/137
[175]: https://codeberg.org/dnkl/yambar/issues/172
### Deprecated ### Deprecated
@ -48,6 +88,8 @@
### Security ### Security
### Contributors ### Contributors
* Horus
## 1.8.0 ## 1.8.0

View file

@ -67,10 +67,9 @@ bar:
- foreign-toplevel: - foreign-toplevel:
content: content:
map: map:
tag: activated conditions:
values: ~activated: {empty: {}}
false: {empty: {}} activated:
true:
- string: {text: "{app-id}: {title}"} - string: {text: "{app-id}: {title}"}
``` ```

View file

@ -102,10 +102,9 @@ bar:
content: content:
"": "":
map: map:
tag: state
default: {string: {text: "{name}"}} default: {string: {text: "{name}"}}
values: conditions:
focused: {string: {text: "{name}*"}} state == focused: {string: {text: "{name}*"}}
current: { string: {text: "{application}: {title}"}} current: { string: {text: "{application}: {title}"}}
``` ```

View file

@ -74,13 +74,12 @@ bar:
- removables: - removables:
content: content:
map: map:
tag: mounted conditions:
values: ~mounted:
false:
string: string:
on-click: udisksctl mount -b {device} on-click: udisksctl mount -b {device}
text: "{label}" text: "{label}"
true: mounted:
string: string:
on-click: udisksctl unmount -b {device} on-click: udisksctl unmount -b {device}
text: "{label}" text: "{label}"

View file

@ -79,10 +79,9 @@ bar:
title: {string: { text: "{seat} - {title}" }} title: {string: { text: "{seat} - {title}" }}
content: content:
map: map:
tag: occupied conditions:
values: ~occupied: {empty: {}}
false: {empty: {}} occupied:
true:
string: string:
margin: 5 margin: 5
text: "{id}: {state}" text: "{id}: {state}"

View file

@ -133,10 +133,9 @@ bar:
title|string|{{title}} title|string|{{title}}
content: content:
map: map:
tag: status conditions:
values: status == Paused: {empty: {}}
Paused: {empty: {}} status == Playing:
Playing:
content: {string: {text: "{artist} - {title}"}} content: {string: {text: "{artist} - {title}"}}
``` ```

View file

@ -68,20 +68,17 @@ in red.
``` ```
content: content:
map: map:
tag: carrier conditions:
values: ~carrier: {empty: {}}
false: {empty: {}} carrier:
true:
map: map:
tag: state
default: {string: {text: , font: *awesome, foreground: ffffff66}} default: {string: {text: , font: *awesome, foreground: ffffff66}}
values: conditions:
up: state == up:
map: map:
tag: ipv4
default: {string: {text: , font: *awesome}} default: {string: {text: , font: *awesome}}
values: conditions:
"": {string: {text: , font: *awesome, foreground: ffffff66}} ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}}
``` ```
## Use yaml anchors ## Use yaml anchors

View file

@ -208,48 +208,102 @@ content:
# MAP # MAP
This particle maps the values of a specific tag to different This particle maps the values of a specific tag to different
particles. In addition to explicit tag values, you can also specify a particles based on conditions. A condition takes either the form of:
<tag> <operation> <value>
Or, for boolean tags:
<tag>
Where <tag> is the tag you would like to map, <operation> is one of:
[- ==
:- !=
:- >=
:- >
:- <=
:- <
and <value> is the value you would like to compare it to.
For boolean tags, negation is done with a preceding '~':
~<tag>
To match for empty strings, use ' "" ':
<tag> == ""
In addition to explicit tag values, you can also specify a
default/fallback particle. default/fallback particle.
Note that conditions are evaluated in the order they appear. *If
multiple conditions are true, the first one will be used*. This means
that in a configuration such as:
```
tx-bitrate > 1000:
tx-bitrate > 1000000:
```
the second condition would never run, since whenever the second
condition is true, the first is also true. The correct way of doing
this would be to invert the order of the conditions:
```
tx-bitrate > 1000000:
tx-bitrate > 1000:
```
## CONFIGURATION ## CONFIGURATION
[[ *Name* [[ *Name*
:[ *Type* :[ *Type*
:[ *Req* :[ *Req*
:[ *Description* :[ *Description*
| tag | conditions
: string
: yes
: The tag (name of) which values should be mapped
| values
: associative array : associative array
: yes : yes
: An associative array of tag values mapped to particles : An associative array of conditions (see above) mapped to particles
| default | default
: particle : particle
: no : no
: Default particle to use, when tag's value does not match any of the : Default particle to use, none of the conditions are true
mapped values.
## EXAMPLES ## EXAMPLES
``` ```
content: content:
map: map:
tag: tag_name
default: default:
string: string:
text: this is the default particle; the tag's value is now {tag_name} text: this is the default particle; the tag's value is now {tag_name}
values: conditions:
one_value: tag == one_value:
string: string:
text: tag's value is now one_value text: tag's value is now one_value
another_value: tag == another_value:
string: string:
text: tag's value is now another_value text: tag's value is now another_value
``` ```
For a boolean tag:
```
content:
map:
conditions:
tag:
string:
text: tag is true
~tag:
string:
text: tag is false
```
# RAMP # RAMP
This particle uses a range tag to index into an array of This particle uses a range tag to index into an array of

View file

@ -46,73 +46,65 @@ bar:
foreground: 000000ff foreground: 000000ff
deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]} deco: {stack: [background: {color: bc2b3fff}, <<: *std_underline]}
- map: &i3_mode - map: &i3_mode
tag: mode
default: default:
- string: - string:
margin: 5 margin: 5
text: "{mode}" text: "{mode}"
deco: {background: {color: cc421dff}} deco: {background: {color: cc421dff}}
- empty: {right-margin: 7} - empty: {right-margin: 7}
values: conditions:
default: {empty: {}} mode == default: {empty: {}}
content: content:
"": "":
map: map:
tag: state conditions:
values: state == focused: {string: {<<: [*default, *focused]}}
focused: {string: {<<: [*default, *focused]}} state == unfocused: {string: {<<: *default}}
unfocused: {string: {<<: *default}} state == invisible: {string: {<<: [*default, *invisible]}}
invisible: {string: {<<: [*default, *invisible]}} state == urgent: {string: {<<: [*default, *urgent]}}
urgent: {string: {<<: [*default, *urgent]}}
main: main:
map: map:
tag: state conditions:
values: state == focused: {string: {<<: [*main, *focused]}}
focused: {string: {<<: [*main, *focused]}} state == unfocused: {string: {<<: *main}}
unfocused: {string: {<<: *main}} state == invisible: {string: {<<: [*main, *invisible]}}
invisible: {string: {<<: [*main, *invisible]}} state == urgent: {string: {<<: [*main, *urgent]}}
urgent: {string: {<<: [*main, *urgent]}}
surfing: surfing:
map: map:
tag: state conditions:
values: state == focused: {string: {<<: [*surfing, *focused]}}
focused: {string: {<<: [*surfing, *focused]}} state == unfocused: {string: {<<: *surfing}}
unfocused: {string: {<<: *surfing}} state == invisible: {string: {<<: [*surfing, *invisible]}}
invisible: {string: {<<: [*surfing, *invisible]}} state == urgent: {string: {<<: [*surfing, *urgent]}}
urgent: {string: {<<: [*surfing, *urgent]}}
misc: misc:
map: map:
tag: state conditions:
values: state == focused: {string: {<<: [*misc, *focused]}}
focused: {string: {<<: [*misc, *focused]}} state == unfocused: {string: {<<: *misc}}
unfocused: {string: {<<: *misc}} state == invisible: {string: {<<: [*misc, *invisible]}}
invisible: {string: {<<: [*misc, *invisible]}} state == urgent: {string: {<<: [*misc, *urgent]}}
urgent: {string: {<<: [*misc, *urgent]}}
mail: mail:
map: map:
tag: state conditions:
values: state == focused: {string: {<<: [*mail, *focused]}}
focused: {string: {<<: [*mail, *focused]}} state == unfocused: {string: {<<: *mail}}
unfocused: {string: {<<: *mail}} state == invisible: {string: {<<: [*mail, *invisible]}}
invisible: {string: {<<: [*mail, *invisible]}} state == urgent: {string: {<<: [*mail, *urgent]}}
urgent: {string: {<<: [*mail, *urgent]}}
music: music:
map: map:
tag: state conditions:
values: state == focused: {string: {<<: [*music, *focused]}}
focused: {string: {<<: [*music, *focused]}} state == unfocused: {string: {<<: *music}}
unfocused: {string: {<<: *music}} state == invisible: {string: {<<: [*music, *invisible]}}
invisible: {string: {<<: [*music, *invisible]}} state == urgent: {string: {<<: [*music, *urgent]}}
urgent: {string: {<<: [*music, *urgent]}}
- foreign-toplevel: - foreign-toplevel:
content: content:
map: map:
tag: activated conditions:
values: ~activated: {empty: {}}
false: {empty: {}} activated:
true:
- string: {text: "{app-id}", foreground: ffa0a0ff} - string: {text: "{app-id}", foreground: ffa0a0ff}
- string: {text: ": {title}"} - string: {text: ": {title}"}
center: center:
@ -123,32 +115,28 @@ bar:
spacing: 0 spacing: 0
items: items:
- map: - map:
tag: state conditions:
values: state == playing: {string: {text: "{artist}"}}
playing: {string: {text: "{artist}"}} state == paused: {string: {text: "{artist}", foreground: ffffff66}}
paused: {string: {text: "{artist}", foreground: ffffff66}}
- string: {text: " | ", foreground: ffffff66} - string: {text: " | ", foreground: ffffff66}
- map: - map:
tag: state conditions:
values: state == playing: {string: {text: "{album}"}}
playing: {string: {text: "{album}"}} state == paused: {string: {text: "{album}", foreground: ffffff66}}
paused: {string: {text: "{album}", foreground: ffffff66}}
- string: {text: " | ", foreground: ffffff66} - string: {text: " | ", foreground: ffffff66}
- map: - map:
tag: state conditions:
values: state == playing: {string: {text: "{title}", foreground: ffa0a0ff}}
playing: {string: {text: "{title}", foreground: ffa0a0ff}} state == paused: {string: {text: "{title}", foreground: ffffff66}}
paused: {string: {text: "{title}", foreground: ffffff66}}
content: content:
map: map:
margin: 10 margin: 10
tag: state conditions:
values: state == offline: {string: {text: offline, foreground: ff0000ff}}
offline: {string: {text: offline, foreground: ff0000ff}} state == stopped: {string: {text: stopped}}
stopped: {string: {text: stopped}} state == paused: {list: *artist_album_title}
paused: {list: *artist_album_title} state == playing: {list: *artist_album_title}
playing: {list: *artist_album_title}
right: right:
- removables: - removables:
@ -158,24 +146,21 @@ bar:
spacing: 5 spacing: 5
content: content:
map: map:
tag: mounted conditions:
values: ~mounted:
false:
map: map:
tag: optical
on-click: udisksctl mount -b {device} on-click: udisksctl mount -b {device}
values: conditions:
false: [{string: *drive}, {string: {text: "{label}"}}] ~optical: [{string: *drive}, {string: {text: "{label}"}}]
true: [{string: *optical}, {string: {text: "{label}"}}] optical: [{string: *optical}, {string: {text: "{label}"}}]
true: mounted:
map: map:
tag: optical
on-click: udisksctl unmount -b {device} on-click: udisksctl unmount -b {device}
values: conditions:
false: ~optical:
- string: {<<: *drive, deco: *std_underline} - string: {<<: *drive, deco: *std_underline}
- string: {text: "{label}"} - string: {text: "{label}"}
true: optical:
- string: {<<: *optical, deco: *std_underline} - string: {<<: *optical, deco: *std_underline}
- string: {text: "{label}"} - string: {text: "{label}"}
- sway-xkb: - sway-xkb:
@ -187,36 +172,31 @@ bar:
name: enp1s0 name: enp1s0
content: content:
map: map:
tag: carrier conditions:
values: ~carrier: {empty: {}}
false: {empty: {}} carrier:
true:
map: map:
tag: state
default: {string: {text: , font: *awesome, foreground: ffffff66}} default: {string: {text: , font: *awesome, foreground: ffffff66}}
values: conditions:
up: state == up:
map: map:
tag: ipv4
default: {string: {text: , font: *awesome}} default: {string: {text: , font: *awesome}}
values: conditions:
"": {string: {text: , font: *awesome, foreground: ffffff66}} ipv4 == "": {string: {text: , font: *awesome, foreground: ffffff66}}
- network: - network:
name: wlp2s0 name: wlp2s0
content: content:
map: map:
tag: state
default: {string: {text: , font: *awesome, foreground: ffffff66}} default: {string: {text: , font: *awesome, foreground: ffffff66}}
values: conditions:
down: {string: {text: , font: *awesome, foreground: ff0000ff}} state == down: {string: {text: , font: *awesome, foreground: ff0000ff}}
up: state == up:
map: map:
tag: ipv4
default: default:
- string: {text: , font: *awesome} - string: {text: , font: *awesome}
- string: {text: "{ssid}"} - string: {text: "{ssid}"}
values: conditions:
"": ipv4 == "":
- string: {text: , font: *awesome, foreground: ffffff66} - string: {text: , font: *awesome, foreground: ffffff66}
- string: {text: "{ssid}", foreground: ffffff66} - string: {text: "{ssid}", foreground: ffffff66}
- alsa: - alsa:
@ -224,16 +204,14 @@ bar:
mixer: Master mixer: Master
content: content:
map: map:
tag: online conditions:
values: ~online: {string: {text: , font: *awesome, foreground: ff0000ff}}
false: {string: {text: , font: *awesome, foreground: ff0000ff}} online:
true:
map: map:
on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle" on-click: /bin/sh -c "amixer -q sset Speaker unmute && amixer -q sset Headphone unmute && amixer -q sset Master toggle"
tag: muted conditions:
values: muted: {string: {text: , font: *awesome, foreground: ffffff66}}
true: {string: {text: , font: *awesome, foreground: ffffff66}} ~muted:
false:
ramp: ramp:
tag: volume tag: volume
items: items:
@ -266,19 +244,18 @@ bar:
- string: {text: "{capacity}% {estimate}"} - string: {text: "{capacity}% {estimate}"}
content: content:
map: map:
tag: state conditions:
values: state == unknown:
unknown:
<<: *discharging <<: *discharging
discharging: state == discharging:
<<: *discharging <<: *discharging
charging: state == charging:
- string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: , foreground: 00ff00ff, font: *awesome}
- string: {text: "{capacity}% {estimate}"} - string: {text: "{capacity}% {estimate}"}
full: state == full:
- string: {text: , foreground: 00ff00ff, font: *awesome} - string: {text: , foreground: 00ff00ff, font: *awesome}
- string: {text: "{capacity}% full"} - string: {text: "{capacity}% full"}
not charging: state == not charging:
- ramp: - ramp:
tag: capacity tag: capacity
items: items:

View file

@ -16,16 +16,15 @@ bar:
- base: &river_base - base: &river_base
left-margin: 10 left-margin: 10
right-margin: 13 right-margin: 13
tag: id
default: {string: {text: , font: *hack}} default: {string: {text: , font: *hack}}
values: conditions:
1: {string: {text: ﳐ, font: *hack}} id == 1: {string: {text: ﳐ, font: *hack}}
2: {string: {text: , font: *hack}} id == 2: {string: {text: , font: *hack}}
3: {string: {text: , font: *hack}} id == 3: {string: {text: , font: *hack}}
4: {string: {text: , font: *hack}} id == 4: {string: {text: , font: *hack}}
5: {string: {text: , font: *hack}} id == 5: {string: {text: , font: *hack}}
10: {string: {text: "scratchpad", font: *hack}} id == 10: {string: {text: "scratchpad", font: *hack}}
11: {string: {text: "work", font: *hack}} id == 11: {string: {text: "work", font: *hack}}
content: content:
map: map:
@ -33,28 +32,25 @@ bar:
left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))" left: sh -c "riverctl set-focused-tags $((1 << ({id} - 1)))"
right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))" right: sh -c "riverctl toggle-focused-tags $((1 << ({id} -1)))"
middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))" middle: sh -c "riverctl toggle-view-tags $((1 << ({id} -1)))"
tag: state conditions:
values: state == urgent:
urgent:
map: map:
<<: *river_base <<: *river_base
deco: {background: {color: D08770ff}} deco: {background: {color: D08770ff}}
focused: state == focused:
map: map:
<<: *river_base <<: *river_base
deco: *bg_default deco: *bg_default
visible: state == visible:
map: map:
tag: occupied conditions:
values: ~occupied: {map: {<<: *river_base}}
false: {map: {<<: *river_base}} occupied: {map: {<<: *river_base, deco: *bg_default}}
true: {map: {<<: *river_base, deco: *bg_default}} state == unfocused:
unfocused:
map: map:
<<: *river_base <<: *river_base
invisible: state == invisible:
map: map:
tag: occupied conditions:
values: ~occupied: {empty: {}}
false: {empty: {}} occupied: {map: {<<: *river_base, deco: {underline: {size: 3, color: ea6962ff}}}}
true: {map: {<<: *river_base, deco: {underline: {size: 3, color: ea6962ff}}}}

View file

@ -1,5 +1,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <errno.h>
#include <assert.h> #include <assert.h>
#define LOG_MODULE "map" #define LOG_MODULE "map"
@ -10,13 +11,228 @@
#include "../plugin.h" #include "../plugin.h"
#include "dynlist.h" #include "dynlist.h"
enum map_op{
MAP_OP_EQ,
MAP_OP_NE,
MAP_OP_LE,
MAP_OP_LT,
MAP_OP_GE,
MAP_OP_GT,
/* The next two are for bool types */
MAP_OP_SELF,
MAP_OP_NOT,
};
struct map_condition {
char *tag;
enum map_op op;
char *value;
};
static char *
trim(char *s)
{
while (*s == ' ')
s++;
char *end = s + strlen(s) - 1;
while (*end == ' ') {
*end = '\0';
end--;
}
return s;
}
bool
int_condition(const long tag_value, const long cond_value, enum map_op op)
{
switch (op) {
case MAP_OP_EQ: return tag_value == cond_value;
case MAP_OP_NE: return tag_value != cond_value;
case MAP_OP_LE: return tag_value <= cond_value;
case MAP_OP_LT: return tag_value < cond_value;
case MAP_OP_GE: return tag_value >= cond_value;
case MAP_OP_GT: return tag_value > cond_value;
default: return false;
}
}
bool
float_condition(const double tag_value, const double cond_value, enum map_op op)
{
switch (op) {
case MAP_OP_EQ: return tag_value == cond_value;
case MAP_OP_NE: return tag_value != cond_value;
case MAP_OP_LE: return tag_value <= cond_value;
case MAP_OP_LT: return tag_value < cond_value;
case MAP_OP_GE: return tag_value >= cond_value;
case MAP_OP_GT: return tag_value > cond_value;
default: return false;
}
}
bool
str_condition(const char* tag_value, const char* cond_value, enum map_op op)
{
switch (op) {
case MAP_OP_EQ: return strcmp(tag_value, cond_value) == 0;
case MAP_OP_NE: return strcmp(tag_value, cond_value) != 0;
case MAP_OP_LE: return strcmp(tag_value, cond_value) <= 0;
case MAP_OP_LT: return strcmp(tag_value, cond_value) < 0;
case MAP_OP_GE: return strcmp(tag_value, cond_value) >= 0;
case MAP_OP_GT: return strcmp(tag_value, cond_value) > 0;
default: return false;
}
}
bool
eval_map_condition(const struct map_condition* map_cond, const struct tag_set *tags)
{
const struct tag *tag = tag_for_name(tags, map_cond->tag);
if (tag == NULL) {
LOG_WARN("tag %s not found", map_cond->tag);
return false;
}
switch (tag->type(tag)) {
case TAG_TYPE_INT: {
errno = 0;
char *end;
const long cond_value = strtol(map_cond->value, &end, 0);
if (errno == ERANGE) {
LOG_WARN("value %s is too large", map_cond->value);
return false;
} else if (*end != '\0') {
LOG_WARN("failed to parse %s into int", map_cond->value);
return false;
}
const long tag_value = tag->as_int(tag);
return int_condition(tag_value, cond_value, map_cond->op);
}
case TAG_TYPE_FLOAT: {
errno = 0;
char *end;
const double cond_value = strtod(map_cond->value, &end);
if (errno == ERANGE) {
LOG_WARN("value %s is too large", map_cond->value);
return false;
} else if (*end != '\0') {
LOG_WARN("failed to parse %s into float", map_cond->value);
return false;
}
const double tag_value = tag->as_float(tag);
return float_condition(tag_value, cond_value, map_cond->op);
}
case TAG_TYPE_BOOL:
if (map_cond->op == MAP_OP_SELF)
return tag->as_bool(tag);
else if (map_cond->op == MAP_OP_NOT)
return !tag->as_bool(tag);
else {
LOG_WARN("boolean tag '%s' should be used directly", map_cond->tag);
return false;
}
case TAG_TYPE_STRING: {
const char* tag_value = tag->as_string(tag);
return str_condition(tag_value, map_cond->value, map_cond->op);
}
}
return false;
}
struct map_condition*
map_condition_from_str(const char *str)
{
struct map_condition *cond = malloc(sizeof(*cond));
/* This strdup is just to discard the 'const' qualifier */
char *str_cpy = strdup(str);
char *op_str = strpbrk(str_cpy, "=!<>~");
/* we've already checked things in the verify functions, so these should all work */
char *tag = str_cpy;
char *value = NULL;
if (op_str == NULL) {
cond->tag = strdup(trim(tag));
cond->op = MAP_OP_SELF;
cond->value = NULL;
free(str_cpy);
return cond;
}
switch (op_str[0]) {
case '=':
cond->op = MAP_OP_EQ;
value = op_str + 2;
break;
case '!':
cond->op = MAP_OP_NE;
value = op_str + 2;
break;
case '<':
if (op_str[1] == '=') {
cond->op = MAP_OP_LE;
value = op_str + 2;
} else {
cond->op = MAP_OP_LT;
value = op_str + 1;
}
break;
case '>':
if (op_str[1] == '=') {
cond->op = MAP_OP_GE;
value = op_str + 2;
} else {
cond->op = MAP_OP_GT;
value = op_str + 1;
}
break;
case '~':
tag = op_str + 1;
cond->op = MAP_OP_NOT;
break;
}
/* NULL terminate the tag value */
op_str[0] = '\0';
cond->tag = strdup(trim(tag));
cond->value = NULL;
if (value != NULL) {
value = trim(value);
const size_t value_len = strlen(value);
if (value[0] == '"' && value[value_len - 1] == '"') {
value[value_len - 1] = '\0';
++value;
}
cond->value = strdup(value);
}
free(str_cpy);
return cond;
}
void
free_map_condition(struct map_condition* mc)
{
free(mc->tag);
free(mc->value);
free(mc);
}
struct particle_map { struct particle_map {
const char *tag_value; struct map_condition *condition;
struct particle *particle; struct particle *particle;
}; };
struct private { struct private {
char *tag;
struct particle *default_particle; struct particle *default_particle;
struct particle_map *map; struct particle_map *map;
size_t count; size_t count;
@ -92,21 +308,12 @@ static struct exposable *
instantiate(const struct particle *particle, const struct tag_set *tags) instantiate(const struct particle *particle, const struct tag_set *tags)
{ {
const struct private *p = particle->private; const struct private *p = particle->private;
const struct tag *tag = tag_for_name(tags, p->tag);
if (tag == NULL) {
return p->default_particle != NULL
? p->default_particle->instantiate(p->default_particle, tags)
: dynlist_exposable_new(NULL, 0, 0, 0);
}
const char *tag_value = tag->as_string(tag);
struct particle *pp = NULL; struct particle *pp = NULL;
for (size_t i = 0; i < p->count; i++) { for (size_t i = 0; i < p->count; i++) {
const struct particle_map *e = &p->map[i]; const struct particle_map *e = &p->map[i];
if (strcmp(e->tag_value, tag_value) != 0) if (!eval_map_condition(e->condition, tags))
continue; continue;
pp = e->particle; pp = e->particle;
@ -144,28 +351,25 @@ particle_destroy(struct particle *particle)
for (size_t i = 0; i < p->count; i++) { for (size_t i = 0; i < p->count; i++) {
struct particle *pp = p->map[i].particle; struct particle *pp = p->map[i].particle;
pp->destroy(pp); pp->destroy(pp);
free((char *)p->map[i].tag_value); free_map_condition(p->map[i].condition);
} }
free(p->map); free(p->map);
free(p->tag);
free(p); free(p);
particle_default_destroy(particle); particle_default_destroy(particle);
} }
static struct particle * static struct particle *
map_new(struct particle *common, const char *tag, map_new(struct particle *common, const struct particle_map particle_map[],
const struct particle_map particle_map[], size_t count, size_t count, struct particle *default_particle)
struct particle *default_particle)
{ {
struct private *priv = calloc(1, sizeof(*priv)); struct private *priv = calloc(1, sizeof(*priv));
priv->tag = strdup(tag);
priv->default_particle = default_particle; priv->default_particle = default_particle;
priv->count = count; priv->count = count;
priv->map = malloc(count * sizeof(priv->map[0])); priv->map = malloc(count * sizeof(priv->map[0]));
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
priv->map[i].tag_value = strdup(particle_map[i].tag_value); priv->map[i].condition = particle_map[i].condition;
priv->map[i].particle = particle_map[i].particle; priv->map[i].particle = particle_map[i].particle;
} }
@ -176,7 +380,103 @@ map_new(struct particle *common, const char *tag,
} }
static bool static bool
verify_map_values(keychain_t *chain, const struct yml_node *node) verify_map_condition_syntax(keychain_t *chain, const struct yml_node *node,
const char *line)
{
/* We need this strdup to discard the 'const' qualifier */
char *cond = strdup(line);
char *op_str = strpbrk(cond, " =!<>~");
if (op_str == NULL) {
char *tag = trim(cond);
if (tag[0] == '\0')
goto syntax_fail_missing_tag;
free(cond);
return true;
}
op_str = trim(op_str);
char *tag = cond;
char *value = NULL;
switch (op_str[0]) {
case '=':
if (op_str[1] != '=')
goto syntax_fail_invalid_op;
value = op_str + 2;
break;
case '!':
if (op_str[1] != '=')
goto syntax_fail_invalid_op;
value = op_str + 2;
break;
case '<':
if (op_str[1] == '=')
value = op_str + 2;
else
value = op_str + 1;
break;
case '>':
if (op_str[1] == '=')
value = op_str + 2;
else
value = op_str + 1;
break;
case '~':
tag = op_str + 1;
if (strpbrk(tag, " =!<>~") != NULL)
goto syntax_fail_bad_not;
value = NULL;
break;
default:
goto syntax_fail_invalid_op;
}
/* NULL terminate the tag value */
op_str[0] = '\0';
tag = trim(tag);
if (tag[0] == '\0')
goto syntax_fail_missing_tag;
value = value != NULL ? trim(value) : NULL;
if (value != NULL && value[0] == '\0')
goto syntax_fail_missing_value;
free(cond);
return true;
syntax_fail_invalid_op:
LOG_ERR("%s: \"%s\" invalid operator", conf_err_prefix(chain, node), line);
goto err;
syntax_fail_missing_tag:
LOG_ERR("%s: \"%s\" missing tag", conf_err_prefix(chain, node), line);
goto err;
syntax_fail_missing_value:
LOG_ERR("%s: \"%s\" missing value", conf_err_prefix(chain, node), line);
goto err;
syntax_fail_bad_not:
LOG_ERR("%s: \"%s\" '~' cannot be used with other operators",
conf_err_prefix(chain, node), line);
goto err;
err:
free(cond);
return false;
}
static bool
verify_map_conditions(keychain_t *chain, const struct yml_node *node)
{ {
if (!yml_is_dict(node)) { if (!yml_is_dict(node)) {
LOG_ERR( LOG_ERR(
@ -195,6 +495,9 @@ verify_map_values(keychain_t *chain, const struct yml_node *node)
return false; return false;
} }
if (!verify_map_condition_syntax(chain, it.key, key))
return false;
if (!conf_verify_particle(chain_push(chain, key), it.value)) if (!conf_verify_particle(chain_push(chain, key), it.value))
return false; return false;
@ -207,11 +510,10 @@ verify_map_values(keychain_t *chain, const struct yml_node *node)
static struct particle * static struct particle *
from_conf(const struct yml_node *node, struct particle *common) from_conf(const struct yml_node *node, struct particle *common)
{ {
const struct yml_node *tag = yml_get_value(node, "tag"); const struct yml_node *conditions = yml_get_value(node, "conditions");
const struct yml_node *values = yml_get_value(node, "values");
const struct yml_node *def = yml_get_value(node, "default"); const struct yml_node *def = yml_get_value(node, "default");
struct particle_map particle_map[yml_dict_length(values)]; struct particle_map particle_map[yml_dict_length(conditions)];
struct conf_inherit inherited = { struct conf_inherit inherited = {
.font = common->font, .font = common->font,
@ -220,28 +522,25 @@ from_conf(const struct yml_node *node, struct particle *common)
}; };
size_t idx = 0; size_t idx = 0;
for (struct yml_dict_iter it = yml_dict_iter(values); for (struct yml_dict_iter it = yml_dict_iter(conditions);
it.key != NULL; it.key != NULL;
yml_dict_next(&it), idx++) yml_dict_next(&it), idx++)
{ {
particle_map[idx].tag_value = yml_value_as_string(it.key); particle_map[idx].condition = map_condition_from_str(yml_value_as_string(it.key));
particle_map[idx].particle = conf_to_particle(it.value, inherited); particle_map[idx].particle = conf_to_particle(it.value, inherited);
} }
struct particle *default_particle = def != NULL struct particle *default_particle = def != NULL
? conf_to_particle(def, inherited) : NULL; ? conf_to_particle(def, inherited) : NULL;
return map_new( return map_new(common, particle_map, yml_dict_length(conditions), default_particle);
common, yml_value_as_string(tag), particle_map, yml_dict_length(values),
default_particle);
} }
static bool static bool
verify_conf(keychain_t *chain, const struct yml_node *node) verify_conf(keychain_t *chain, const struct yml_node *node)
{ {
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"tag", true, &conf_verify_string}, {"conditions", true, &verify_map_conditions},
{"values", true, &verify_map_values},
{"default", false, &conf_verify_particle}, {"default", false, &conf_verify_particle},
PARTICLE_COMMON_ATTRS, PARTICLE_COMMON_ATTRS,
}; };

28
tag.c
View file

@ -32,6 +32,30 @@ tag_name(const struct tag *tag)
return priv->name; return priv->name;
} }
static enum tag_type
bool_type(const struct tag *tag)
{
return TAG_TYPE_BOOL;
}
static enum tag_type
int_type(const struct tag *tag)
{
return TAG_TYPE_INT;
}
static enum tag_type
float_type(const struct tag *tag)
{
return TAG_TYPE_FLOAT;
}
static enum tag_type
string_type(const struct tag *tag)
{
return TAG_TYPE_STRING;
}
static long static long
unimpl_min_max(const struct tag *tag) unimpl_min_max(const struct tag *tag)
{ {
@ -264,6 +288,7 @@ tag_new_int_realtime(struct module *owner, const char *name, long value,
tag->owner = owner; tag->owner = owner;
tag->destroy = &destroy_int_and_float; tag->destroy = &destroy_int_and_float;
tag->name = &tag_name; tag->name = &tag_name;
tag->type = &int_type;
tag->min = &int_min; tag->min = &int_min;
tag->max = &int_max; tag->max = &int_max;
tag->realtime = &int_realtime; tag->realtime = &int_realtime;
@ -287,6 +312,7 @@ tag_new_bool(struct module *owner, const char *name, bool value)
tag->owner = owner; tag->owner = owner;
tag->destroy = &destroy_int_and_float; tag->destroy = &destroy_int_and_float;
tag->name = &tag_name; tag->name = &tag_name;
tag->type = &bool_type;
tag->min = &unimpl_min_max; tag->min = &unimpl_min_max;
tag->max = &unimpl_min_max; tag->max = &unimpl_min_max;
tag->realtime = &no_realtime; tag->realtime = &no_realtime;
@ -310,6 +336,7 @@ tag_new_float(struct module *owner, const char *name, double value)
tag->owner = owner; tag->owner = owner;
tag->destroy = &destroy_int_and_float; tag->destroy = &destroy_int_and_float;
tag->name = &tag_name; tag->name = &tag_name;
tag->type = &float_type;
tag->min = &unimpl_min_max; tag->min = &unimpl_min_max;
tag->max = &unimpl_min_max; tag->max = &unimpl_min_max;
tag->realtime = &no_realtime; tag->realtime = &no_realtime;
@ -333,6 +360,7 @@ tag_new_string(struct module *owner, const char *name, const char *value)
tag->owner = owner; tag->owner = owner;
tag->destroy = &destroy_string; tag->destroy = &destroy_string;
tag->name = &tag_name; tag->name = &tag_name;
tag->type = &string_type;
tag->min = &unimpl_min_max; tag->min = &unimpl_min_max;
tag->max = &unimpl_min_max; tag->max = &unimpl_min_max;
tag->realtime = &no_realtime; tag->realtime = &no_realtime;

8
tag.h
View file

@ -3,6 +3,13 @@
#include <stddef.h> #include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
enum tag_type {
TAG_TYPE_BOOL,
TAG_TYPE_INT,
TAG_TYPE_FLOAT,
TAG_TYPE_STRING,
};
enum tag_realtime_unit { enum tag_realtime_unit {
TAG_REALTIME_NONE, TAG_REALTIME_NONE,
TAG_REALTIME_SECS, TAG_REALTIME_SECS,
@ -17,6 +24,7 @@ struct tag {
void (*destroy)(struct tag *tag); void (*destroy)(struct tag *tag);
const char *(*name)(const struct tag *tag); const char *(*name)(const struct tag *tag);
enum tag_type (*type)(const struct tag *tag);
const char *(*as_string)(const struct tag *tag); const char *(*as_string)(const struct tag *tag);
long (*as_int)(const struct tag *tag); long (*as_int)(const struct tag *tag);
bool (*as_bool)(const struct tag *tag); bool (*as_bool)(const struct tag *tag);

View file

@ -62,10 +62,9 @@ bar:
- clock: - clock:
content: content:
map: map:
tag: date
default: {string: {text: default value}} default: {string: {text: default value}}
values: conditions:
1234: {string: {text: specific value}} date == 1234: {string: {text: specific value}}
- clock: - clock:
content: content:
progress-bar: progress-bar: