Skip to content

Commit

Permalink
Get the lazy command graph working
Browse files Browse the repository at this point in the history
Setup an interactive lazy command client, as well as clean-up the IPC
command interface to be able to work for the tests.
  • Loading branch information
flacjacket committed Jun 20, 2019
1 parent a744806 commit 85f95a5
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 75 deletions.
13 changes: 7 additions & 6 deletions libqtile/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
from .utils import get_cache_dir
from .log_utils import logger

from libqtile.command_client import CommandError, CommandException
from libqtile.lazy import LazyGraph
from libqtile.command_object import CommandError, CommandException
from libqtile.command_client import InteractiveCommandClient
from libqtile.lazy import LazyCommandObject


class _SelectError(Exception):
Expand Down Expand Up @@ -268,13 +269,13 @@ def call(self, selectors, name, *args, **kwargs):
raise CommandException(val)


class _LazyTree(LazyGraph):
def __getattr__(self, *args):
class _LazyTree(InteractiveCommandClient):
def __getattr__(self, *args, **kwargs):
warnings.warn("libqtile.command.lazy is deprecated, use libqtile.lazy.lazy", DeprecationWarning)
return super().__getattr__(*args)
return super().__getattr__(*args, **kwargs)


lazy = _LazyTree()
lazy = _LazyTree(LazyCommandObject())


class CommandObject(metaclass=abc.ABCMeta):
Expand Down
8 changes: 0 additions & 8 deletions libqtile/command_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@
from libqtile.command_object import CommandInterface


class CommandError(Exception):
pass


class CommandException(Exception):
pass


class SelectError(Exception):
def __init__(self, err_string: str, name: str, selectors: List[SelectorType]):
super().__init__(err_string)
Expand Down
20 changes: 18 additions & 2 deletions libqtile/command_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@
from libqtile.command_graph import CommandGraphCall, CommandGraphNode
from libqtile import ipc

SUCCESS = 0
ERROR = 1
EXCEPTION = 2


class CommandError(Exception):
pass


class CommandException(Exception):
pass


class CommandInterface(metaclass=ABCMeta):
"""
Expand Down Expand Up @@ -120,7 +132,11 @@ def execute(self, call: CommandGraphCall, args: Tuple, kwargs: Dict) -> Any:
status, result = self._client.send((
call.parent.selectors, call.name, args, kwargs
))
return result
if status == SUCCESS:
return result
if status == ERROR:
raise CommandError(result)
raise CommandException(result)

def has_command(self, node: CommandGraphNode, command: str) -> bool:
"""Check if the given command exists
Expand Down Expand Up @@ -166,5 +182,5 @@ def has_item(self, node: CommandGraphNode, object_type: str, item: str) -> bool:
True if the item is resolved on the given node
"""
items_call = node.call("items")
items = self.execute(items_call, (object_type,), {})
_, items = self.execute(items_call, (object_type,), {})
return item in items
99 changes: 73 additions & 26 deletions libqtile/lazy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,87 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from libqtile.command_graph import CommandGraphCall, CommandGraphRoot, CommandGraphObject, GraphType
from libqtile.command_client import SelectError
from typing import Dict, List, Optional, Tuple # noqa: F401

from libqtile.command_client import InteractiveCommandClient
from libqtile.command_object import CommandInterface
from libqtile.command_graph import CommandGraphCall, CommandGraphNode, SelectorType

class LazyGraph:
def __init__(self, *, node: GraphType = None):
if node is None:
self._current_node = CommandGraphRoot() # type: GraphType
else:
self._current_node = node

def __getattr__(self, name: str) -> "LazyGraph":
"""Get the child element of the currently selected object"""
if isinstance(self._current_node, CommandGraphCall):
raise SelectError("Cannot select children of call", name, self._current_node.selectors)
class LazyCall:
def __init__(self, call: CommandGraphCall, args: Tuple, kwargs: Dict) -> None:
"""The lazily evaluated command graph call
if name in self._current_node.children:
next_node = self._current_node.navigate(name, None) # type: GraphType
else:
next_node = self._current_node.call(name)
Parameters
----------
call : CommandGraphCall
The call that is made
args : Tuple
The args passed to the call when it is evaluated.
kwargs : Dict
The kwargs passed to the call when it is evaluated.
"""
self._call = call
self._args = args
self._kwargs = kwargs

return self.__class__(node=next_node)
self._layout = None # type: Optional[str]
self._when_floating = True

def __getitem__(self, name: str) -> "LazyGraph":
"""Get the selected element of the currently selected object"""
if not isinstance(self._current_node, CommandGraphObject):
raise SelectError("Unable to make selection on current node", name, self._current_node.selectors)
@property
def selectors(self) -> List[SelectorType]:
"""The selectors for the given call"""
return self._call.selectors

if self._current_node.selector is not None:
raise SelectError("Selection already made", name, self._current_node.selectors)
@property
def name(self) -> str:
"""The name of the given call"""
return self._call.name

next_node = self._current_node.parent.navigate(self._current_node.object_type, name)
@property
def args(self) -> Tuple:
"""The args to the given call"""
return self._args

return self.__class__(node=next_node)
@property
def kwargs(self) -> Dict:
"""The kwargs to the given call"""
return self._kwargs

def when(self, layout=None, when_floating=True):
self._layout = layout
self._when_floating = when_floating

lazy = LazyGraph()
def check(self, q) -> bool:
if self._layout is not None:
if self._layout == 'floating':
if q.current_window.floating:
return True
return False
if q.current_layout.name != self._layout:
if q.current_window and q.current_window.floating and not self._when_floating:
return False
return True


class LazyCommandObject(CommandInterface):
"""A lazy loading command object
Allows all commands and items to be resolved at run time, and returns
lazily evaluated commands.
"""

def execute(self, call: CommandGraphCall, args: Tuple, kwargs: Dict) -> LazyCall:
"""Lazily evaluate the given call"""
return LazyCall(call, args, kwargs)

def has_command(self, node: CommandGraphNode, command: str) -> bool:
"""Lazily resolve the given command"""
return True

def has_item(self, node: CommandGraphNode, object_type: str, item: str) -> bool:
"""Lazily resolve the given item"""
return True


lazy = InteractiveCommandClient(LazyCommandObject())
8 changes: 4 additions & 4 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def can_connect_x11(disp=':0'):
def can_connect_qtile(socket_path):
ipc_client = ipc.Client(socket_path)
ipc_command = command_object.IPCCommandObject(ipc_client)
client = command_client.Client(ipc_command)
client = command_client.InteractiveCommandClient(ipc_command)
val = client.status()
if val == 'OK':
return True
Expand Down Expand Up @@ -133,12 +133,12 @@ class BareConfig:
libqtile.config.Key(
["control"],
"k",
libqtile.command._Call([("layout", None)], "up")
libqtile.command.lazy.layout.up(),
),
libqtile.config.Key(
["control"],
"j",
libqtile.command._Call([("layout", None)], "down")
libqtile.command.lazy.layout.down(),
),
]
mouse = []
Expand Down Expand Up @@ -277,7 +277,7 @@ def run_qtile():
if can_connect_qtile(self.sockfile):
ipc_client = ipc.Client(self.sockfile)
ipc_command = command_object.IPCCommandObject(ipc_client)
self.c = command_client.Client(ipc_command)
self.c = command_client.InteractiveCommandClient(ipc_command)
return
if rpipe.poll(sleep_time):
error = rpipe.recv()
Expand Down
4 changes: 2 additions & 2 deletions test/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ class CallConfig:
keys = [
libqtile.config.Key(
["control"], "j",
libqtile.command._Call([("layout", None)], "down")
libqtile.command.lazy.layout.down(),
),
libqtile.config.Key(
["control"], "k",
libqtile.command._Call([("layout", None)], "up"),
libqtile.command.lazy.layout.up(),
),
]
mouse = []
Expand Down
33 changes: 17 additions & 16 deletions test/test_command_graph.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import pytest

from libqtile.command_graph import CommandGraphCall, CommandGraphContainer, CommandGraphRoot
from libqtile.command_graph import CommandGraphCall, CommandGraphObject, CommandGraphRoot


def test_root_path():
node = CommandGraphRoot()
assert node.path == ""

assert node.selectors == []
assert node.selector is None
assert node.parent is None

Expand All @@ -16,39 +15,41 @@ def test_resolve_nodes():

node_1 = root_node.navigate("layout", None) \
.navigate("screen", None)
assert node_1.path == "layout.screen"
assert isinstance(node_1, CommandGraphContainer)
assert node_1.selectors == [("layout", None), ("screen", None)]
assert isinstance(node_1, CommandGraphObject)

node_2 = node_1.navigate("layout", None) \
.navigate("window", None) \
.navigate("group", None)
assert node_2.path == "layout.screen.layout.window.group"
assert isinstance(node_2, CommandGraphContainer)
assert node_2.selectors == [
("layout", None), ("screen", None), ("layout", None), ("window", None), ("group", None)
]
assert isinstance(node_2, CommandGraphObject)

with pytest.raises(KeyError, match="Given node is not an object"):
node_1.navigate("widget", None)


def test_resolve_selections():
root_node = CommandGraphRoot()

node_1 = root_node.navigate("layout", None) \
.navigate("screen", "1")
assert node_1.path == "layout.screen[1]"
assert isinstance(node_1, CommandGraphContainer)
assert node_1.selectors == [("layout", None), ("screen", "1")]
assert isinstance(node_1, CommandGraphObject)


def test_resolve_command():
root_node = CommandGraphRoot()

command_1 = root_node.navigate("cmd_name", None)
assert command_1.path == "cmd_name"
command_1 = root_node.call("cmd_name")
assert command_1.selectors == []
assert command_1.name == "cmd_name"
assert isinstance(command_1, CommandGraphCall)

command_2 = root_node.navigate("layout", None) \
.navigate("screen", None) \
.navigate("cmd_name", None)
assert command_2.path == "layout.screen.cmd_name"
.call("cmd_name")
assert command_2.name == "cmd_name"
assert command_2.selectors == [("layout", None), ("screen", None)]
assert isinstance(command_2, CommandGraphCall)

with pytest.raises(KeyError, match="Given node is not an object"):
root_node.navigate("cmd_name", "1")
15 changes: 4 additions & 11 deletions test/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ class ManagerConfig:
libqtile.config.Key(
["control"],
"k",
libqtile.command._Call([("layout", None)], "up")
libqtile.command.lazy.layout.up(),
),
libqtile.config.Key(
["control"],
"j",
libqtile.command._Call([("layout", None)], "down")
libqtile.command.lazy.layout.down(),
),
]
mouse = []
Expand Down Expand Up @@ -938,13 +938,6 @@ def test_unmap_noscreen(qtile):
assert self.c.groups()["a"]["focus"] == "one"


# def test_init():
# with pytest.raises(libqtile.core.manager.QtileError):
# libqtile.config.Key([], "unknown", libqtile.command._Call("base", None, "foo"))
# with pytest.raises(libqtile.core.manager.QtileError):
# libqtile.config.Key(["unknown"], "x", libqtile.command._Call("base", None, "foo"))


class TScreen(libqtile.config.Screen):
def set_group(self, x, save_prev=True):
pass
Expand Down Expand Up @@ -994,12 +987,12 @@ class _Config:
libqtile.config.Key(
["control"],
"k",
libqtile.command._Call([("layout", None)], "up")
libqtile.command.lazy.layout.up(),
),
libqtile.config.Key(
["control"],
"j",
libqtile.command._Call([("layout", None)], "down")
libqtile.command.lazy.layout.down(),
),
]
mouse = []
Expand Down

0 comments on commit 85f95a5

Please sign in to comment.