Add branching operator, explicitly load values from input dict.

This commit is contained in:
redxef 2022-11-09 11:54:59 +01:00
parent 932a36517b
commit 84673958e9
Signed by: redxef
GPG key ID: 7DAC3AA211CBD921
2 changed files with 95 additions and 32 deletions

View file

@ -51,19 +51,19 @@ It is then possible to construct a filter for any program.
Available Operators: Available Operators:
- and: `&` - and: `&`: logical and, ungreedy
- or: `|` - or: `|`: logical or, ungreedy
- eq: `=` - if: `?`: branch, if the first argument evaluates to `True` return the second, otherwise the third
- neq: `!=` - eq: `=`: equality
- gt: `>` - neq: `!=`: inequality
- lt: `<` - gt: `>`: greater than
- lt: `<`: less than
- load: `load`: load a key from the provided input `(load ".container.app_id")`
- has-key: `has-key`: check if a key is in the input: `(has-key ".container.app_id")`
The filter usually operates on the dictionary, and thus the *first* argument to every normal filter For example: `(> (load ".container.geometry.width") 300)` would match the first window where the width is greater than 300.
is the dictionary element, in `.` notation, as might be customary in `jq`.
For example: `(> ".container.geometry.width" 300)` would match the first window where the width is greater than 300. Multiple filters are combined via nesting: `(& (> (load ".container.geometry.width") 300) (= (load ".container.window_properties.class") "discord"))`.
Multiple filters are combined via nesting: `(& (> ".container.geometry.width" 300) (= ".container.window_properties.class" "discord"))`.
## Starting tray programs in a specific order ## Starting tray programs in a specific order

View file

@ -25,12 +25,19 @@ class Expression:
def __init__(self): def __init__(self):
pass pass
def reduce(self, ipc_data): def reduce(self, ipc_data):
if self.should_call:
return self.call(ipc_data)
return functools.reduce(self.reduce_function(ipc_data), self.children) return functools.reduce(self.reduce_function(ipc_data), self.children)
@property @property
def should_call(self):
return False
@property
def children(self): def children(self):
raise NotImplemented('TODO: implement in subclass') raise NotImplemented('TODO: implement in subclass')
def reduce_function(self, ipc_data): def reduce_function(self, ipc_data):
raise NotImplemented('TODO: implement in subclass') raise NotImplemented('TODO: implement in subclass')
def call(self, ipc_data):
raise NotImplementedError('TODO: implement in subclass')
class LiteralExpression(Expression): class LiteralExpression(Expression):
def __init__(self, value): def __init__(self, value):
@ -78,6 +85,26 @@ class OrExpression(Expression):
def reduce_function(self, ipc_data): def reduce_function(self, ipc_data):
return lambda a, b: a.reduce(ipc_data) or b.reduce(ipc_data) return lambda a, b: a.reduce(ipc_data) or b.reduce(ipc_data)
class IfExpression(Expression):
def __init__(self, children, *args, **kwargs):
self._children = children
super().__init__(*args, **kwargs)
def __repr__(self) -> str:
cs = ' '.join([repr(c) for c in self.children])
return f'(? {cs})'
@property
def should_call(self):
return True
@property
def children(self):
return self._children
def call(self, ipc_data):
if self._children[0].reduce(ipc_data):
i = 1
else:
i = 2
return self._children[i].reduce(ipc_data)
class EqExpression(Expression): class EqExpression(Expression):
def __init__(self, children, *args, **kwargs): def __init__(self, children, *args, **kwargs):
self._children = children self._children = children
@ -89,11 +116,9 @@ class EqExpression(Expression):
def children(self): def children(self):
return self._children return self._children
def reduce_function(self, ipc_data): def reduce_function(self, ipc_data):
def reduce(key, value): def reduce(v0, v1):
ipc_value = ipc_data print(f'reducing: {repr(self)}')
for k in key.reduce(ipc_data).strip('.').split('.'): return v0.reduce(ipc_data) == v1.reduce(ipc_data)
ipc_value = ipc_value[k]
return ipc_value == value.reduce(ipc_data)
return reduce return reduce
class NeqExpression(Expression): class NeqExpression(Expression):
@ -107,11 +132,8 @@ class NeqExpression(Expression):
def children(self): def children(self):
return self._children return self._children
def reduce_function(self, ipc_data): def reduce_function(self, ipc_data):
def reduce(key, value): def reduce(v0, v1):
ipc_value = ipc_data return v0.reduce(ipc_data) != v1.reduce(ipc_data)
for k in key.reduce(ipc_data).strip('.').split('.'):
ipc_value = ipc_value[k]
return ipc_value != value.reduce(ipc_data)
return reduce return reduce
class GtExpression(Expression): class GtExpression(Expression):
@ -125,11 +147,8 @@ class GtExpression(Expression):
def children(self): def children(self):
return self._children return self._children
def reduce_function(self, ipc_data): def reduce_function(self, ipc_data):
def reduce(key, value): def reduce(v0, v1):
ipc_value = ipc_data return v0.reduce(ipc_data) > v1.reduce(ipc_data)
for k in key.reduce(ipc_data).strip('.').split('.'):
ipc_value = ipc_value[k]
return ipc_value > value.reduce(ipc_data)
return reduce return reduce
class LtExpression(Expression): class LtExpression(Expression):
@ -143,20 +162,60 @@ class LtExpression(Expression):
def children(self): def children(self):
return self._children return self._children
def reduce_function(self, ipc_data): def reduce_function(self, ipc_data):
def reduce(key, value): def reduce(v0, v1):
ipc_value = ipc_data return v0.reduce(ipc_data) < v1.reduce(ipc_data)
for k in key.reduce(ipc_data).strip('.').split('.'):
ipc_value = ipc_value[k]
return ipc_value < value.reduce(ipc_data)
return reduce return reduce
class LoadExpression(Expression):
def __init__(self, value, *args, **kwargs):
self._value = value
super().__init__(*args, **kwargs)
def __repr__(self) -> str:
return f'(load {self._value})'
@property
def should_call(self):
return True
@property
def children(self):
return [self._value]
def call(self, ipc_data):
ipc_value = ipc_data
for k in self._value[0].children[0].strip('.').split('.'):
ipc_value = ipc_value[k]
return ipc_value
class HasKeyExpression(Expression):
def __init__(self, value, *args, **kwargs):
self._value = value
super().__init__(*args, **kwargs)
def __repr__(self) -> str:
return f'(has-key {self._value})'
@property
def should_call(self):
return True
@property
def children(self):
return [self._value]
def call(self, ipc_data):
ipc_value = ipc_data
for k in self._value[0].children[0].strip('.').split('.'):
try:
ipc_value = ipc_value[k]
except KeyError:
return False
return True
expression_mapping = { expression_mapping = {
'&': AndExpression, '&': AndExpression,
'|': OrExpression, '|': OrExpression,
'?': IfExpression,
'=': EqExpression, '=': EqExpression,
'!=': NeqExpression, '!=': NeqExpression,
'>': GtExpression, '>': GtExpression,
'<': LtExpression, '<': LtExpression,
'load': LoadExpression,
'has-key': HasKeyExpression,
} }
def group_tokens(tokens: list[str]) -> list[list[str]]: def group_tokens(tokens: list[str]) -> list[list[str]]:
@ -507,4 +566,8 @@ def config(ctx, config):
ctx.exit(asyncio.run(run(config, debug=debug))) ctx.exit(asyncio.run(run(config, debug=debug)))
if __name__ == '__main__': if __name__ == '__main__':
main() #main()
expr = parse('? False 1 0')
print(expr)
print(expr.reduce({}))