conf: improve matching rules a bit more

Handle "null", null, !null, "!null", !"null" and "!\"null\""
matches, copy some docs from wireplumber about the rules and
add some more cases.
This commit is contained in:
Wim Taymans 2024-03-18 17:53:44 +01:00
parent c52c56621d
commit 0e380de809
2 changed files with 140 additions and 21 deletions

View file

@ -434,13 +434,105 @@ The general rules object follows the following pattern:
}
]
```
Match rules are an array of rules.
The rules is an array of things to match and what actions to perform
when a match is found.
A rule is always a JSON object with two keys: matches and actions. The matches key is used to
define the conditions that need to be met for the rule to be evaluated as true, and the actions
key is used to define the actions that are performed when the rule is evaluated as true.
The matches key is always a JSON array of objects, where each object defines a condition that needs
to be met. Each condition is a list of key-value pairs, where the key is the name of the property
that is being matched, and the value is the value that the property needs to have. Within a condition,
all the key-value pairs are combined with a logical AND, and all the conditions in the matches
array are combined with a logical OR.
The actions key is always a JSON object, where each key-value pair defines an action that is
performed when the rule is evaluated as true. The action name is specific to the rule and is
defined by the rules documentation, but most frequently you will see the update-props action,
which is used to update the properties of the matched object.
In the matches array, it is also possible to use regular expressions to match property values.
For example, to match all nodes with a name that starts with my_, you can use the following condition:
```
matches = [
{
node.name = "~my_.*"
}
]
```
The ~ character signifies that the value is a regular expression. The exact syntax of the regular
expressions is the POSIX extended regex syntax, as described in the regex (7) man page.
In addition to regular expressions, you may also use the ! character to negate a condition. For
example, to match all nodes with a name that does not start with my_, you can use the following condition:
```
matches = [
{
node.name = "!~my_.*"
}
]
```
The ! character can be used with or without a regular expression. For example, to match all
nodes with a name that is not equal to my_node, you can use the following condition:
```
matches = [
{
node.name = "!my_node"
}
]
```
The null value has a special meaning; it checks if the property is not available
(or unset). To check if a property is not set:
```
matches = [
{
node.name = null
}
]
```
To check the existence of a property, one can use the !null condition, for example:
```
matches = [
{
node.name = "!null"
}
{
node.name = !null # simplified syntax
}
]
```
To handle the "null" string, one needs to escape the string. For example, to check
if a property has the string value "null", use:
```
matches = [
{
node.name = "null"
}
]
```
To handle anything but the "null" string, use:
```
matches = [
{
node.name = "!\"null\""
}
{
node.name = !"null" # simplified syntax
}
]
```
The available actions and their values depend on the specific rule
that is used. Usually it is possible to update some properties or set
some quirks on the object.
# CONTEXT PROPERTIES RULES @IDX@ pipewire.conf

View file

@ -594,6 +594,14 @@ static int load_module(struct pw_context *context, const char *key, const char *
* <key> = <value>
* ...
* }
*
* Some things that can match:
*
* null -> matches when the property is not found
* "null" -> matches when the property is found and has the string "null"
* !null -> matches when the property is found (any value)
* "!null" -> same as !null
* !"null" and "!\"null\"" matches anything that is not the string "null"
*/
static bool find_match(struct spa_json *arr, const struct spa_dict *props)
{
@ -606,47 +614,66 @@ static bool find_match(struct spa_json *arr, const struct spa_dict *props)
int len;
while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) {
bool success = false, is_null;
bool success = false, is_null, reg = false, parse_string = true;
int skip = 0;
if ((len = spa_json_next(&it[0], &value)) <= 0)
break;
if (len > 0 && value[0] == '!') {
/* first decode a string, when there was a string, we assume it
* can not be null but the "null" string, unless there is a modifier,
* see below. */
if (spa_json_is_string(value, len)) {
if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0)
continue;
value = val;
len = strlen(val);
parse_string = false;
}
/* parse the modifiers, after the modifier we unescape the string
* again to be able to detect and handle null and "null" */
if (len > skip && value[skip] == '!') {
success = !success;
skip++;
parse_string = true;
}
if (len > skip && value[skip] == '~') {
reg = true;
skip++;
parse_string = true;
}
str = spa_dict_lookup(props, key);
is_null = spa_json_is_null(value+skip, len-skip);
/* parse the remaining part of the string, if there was a modifier,
* we need to check for null again. Otherwise null was in quotes without
* a modifier. */
is_null = parse_string && spa_json_is_null(value+skip, len-skip);
if (is_null || str == NULL) {
if (is_null && str == NULL)
success = !success;
} else {
if (spa_json_parse_stringn(value+skip, len-skip, val, sizeof(val)) < 0)
/* only unescape string once or again after modifier */
if (!parse_string) {
memmove(val, value+skip, len-skip);
val[len-skip] = '\0';
} else if (spa_json_parse_stringn(value+skip, len-skip, val, sizeof(val)) < 0)
continue;
value = val;
len = strlen(val);
if (len > 0 && value[0] == '!') {
success = !success;
skip++;
}
if (value[skip] == '~') {
if (reg) {
regex_t preg;
int res;
skip++;
if ((res = regcomp(&preg, value+skip, REG_EXTENDED | REG_NOSUB)) != 0) {
if ((res = regcomp(&preg, val, REG_EXTENDED | REG_NOSUB)) != 0) {
char errbuf[1024];
regerror(res, &preg, errbuf, sizeof(errbuf));
pw_log_warn("invalid regex %s: %s", value+skip, errbuf);
pw_log_warn("invalid regex %s: %s", val, errbuf);
} else {
if (regexec(&preg, str, 0, NULL, 0) == 0)
success = !success;
regfree(&preg);
}
} else if (strncmp(str, value+skip, len-skip) == 0 &&
strlen(str) == (size_t)(len-skip)) {
} else if (strcmp(str, val) == 0) {
success = !success;
}
}