Repository: open-dynaMIX/raiseorlaunch

Issue:
crashes when Spotify is closed but not when Spotify is open
This syntax worked to raise Spotify if it was already running, but crashed instead of launching Spotify when it was not running. If I'm doing something invalid, it would be nice if it failed with a more helpful diagnostic message explaining what was invalid. 

Thanks.

```
h> raiseorlaunch -r -c Spotify
expected string or bytes-like object
Traceback (most recent call last):
  File "/usr/bin/raiseorlaunch", line 8, in <module>
    sys.exit(main())
  File "/usr/lib/python3.8/site-packages/raiseorlaunch/__main__.py", line 222, in main
    rol.run()
  File "/usr/lib/python3.8/site-packages/raiseorlaunch/raiseorlaunch.py", line 551, in run
    self._handle_not_running()
  File "/usr/lib/python3.8/site-packages/raiseorlaunch/raiseorlaunch.py", line 515, in _handle_not_running
    self.i3.main(timeout=self.event_time_limit)
  File "/usr/lib/python3.8/site-packages/i3ipc/connection.py", line 518, in main
    raise loop_exception
  File "/usr/lib/python3.8/site-packages/i3ipc/connection.py", line 497, in main
    while not self._event_socket_poll():
  File "/usr/lib/python3.8/site-packages/i3ipc/connection.py", line 477, in _event_socket_poll
    raise e
  File "/usr/lib/python3.8/site-packages/i3ipc/connection.py", line 474, in _event_socket_poll
    self._pubsub.emit(event_name, event)
  File "/usr/lib/python3.8/site-packages/i3ipc/_private/pubsub.py", line 28, in emit
    s['handler'](self.conn, data)
  File "/usr/lib/python3.8/site-packages/raiseorlaunch/raiseorlaunch.py", line 527, in _callback_new_window
    if self._compare_running(window):
  File "/usr/lib/python3.8/site-packages/raiseorlaunch/raiseorlaunch.py", line 180, in _compare_running
    if pattern and not self._match_regex(pattern, value):
  File "/usr/lib/python3.8/site-packages/raiseorlaunch/raiseorlaunch.py", line 163, in _match_regex
    return True if re.match(*matchlist) else False
  File "/usr/lib/python3.8/re.py", line 189, in match
    return _compile(pattern, flags).match(string)
TypeError: expected string or bytes-like object
```

Discussion:

Patch:
diff --git a/raiseorlaunch/raiseorlaunch.py b/raiseorlaunch/raiseorlaunch.py
index 9ac4338..e164d5d 100644
--- a/raiseorlaunch/raiseorlaunch.py
+++ b/raiseorlaunch/raiseorlaunch.py
@@ -144,8 +144,17 @@ class Raiseorlaunch(object):
         Returns:
             str: '<Con: class="{}" instance="{}" title="{}" id={}>'
         """
-        return '<Con: class="{}" instance="{}" title="{}" id={}>'.format(
-            window.window_class, window.window_instance, window.name, window.id
+
+        def quote(value):
+            if isinstance(value, str):
+                return '"{}"'.format(value)
+            return value
+
+        return "<Con: class={} instance={} title={} id={}>".format(
+            quote(window.window_class),
+            quote(window.window_instance),
+            quote(window.name),
+            window.id,
         )
 
     def _match_regex(self, regex, string_to_match):
@@ -177,7 +186,8 @@ class Raiseorlaunch(object):
             (self.wm_instance, window.window_instance),
             (self.wm_title, window.name),
         ]:
-            if pattern and not self._match_regex(pattern, value):
+
+            if pattern and (not value or not self._match_regex(pattern, value)):
                 return False
 
         logger.debug("Window match: {}".format(self._log_format_con(window)))
@@ -238,6 +248,14 @@ class Raiseorlaunch(object):
         window_list = self._get_window_list()
         found = []
         for leave in window_list:
+            if (
+                leave.window_class
+                == leave.window_instance
+                == leave.window_title
+                is None
+            ):
+                logger.debug("Window without any properties found.")
+                continue
             if self._compare_running(leave):
                 found.append(leave)
 


Test patch:
diff --git a/tests/test_raiseorlaunch.py b/tests/test_raiseorlaunch.py
index a5c6c8e..34a1b36 100644
--- a/tests/test_raiseorlaunch.py
+++ b/tests/test_raiseorlaunch.py
@@ -76,8 +76,8 @@ def test__log_format_con(minimal_args, Con, mocker):
 
     rol = Raiseorlaunch(**minimal_args)
     assert (
-        rol._log_format_con(Con())
-        == '<Con: class="some_class" instance="some_instance" title="some_name" id=some_id>'
+        rol._log_format_con(Con(window_class=None))
+        == '<Con: class=None instance="some_instance" title="some_name" id=some_id>'
     )
 
 
@@ -107,7 +107,8 @@ def test__match_regex(minimal_args, ignore_case, regex, string, success, mocker)
             {"window_class": "Qutebrowser"},
             True,
         ),
-        ({"wm_class": "something"}, {"window_class": "Qutebrowser"}, False),
+        ({"wm_class": "foo"}, {"window_class": "Qutebrowser"}, False),
+        ({"wm_class": "foo"}, {"window_class": None}, False),
     ],
 )
 def test__compare_running(minimal_args, rol, Con, config, con_values, success):
@@ -125,8 +126,8 @@ def test__compare_running(minimal_args, rol, Con, config, con_values, success):
         (
             False,
             None,
-            4,
-            ["Home", "i3 - improved tiling wm - qutebrowser", "notes", "htop"],
+            5,
+            ["Home", None, "i3 - improved tiling wm - qutebrowser", "notes", "htop"],
         ),
         (True, None, 2, ["notes", "htop"]),
         (False, "workspace_1", 2, ["i3 - improved tiling wm - qutebrowser", "htop"]),
@@ -219,7 +220,11 @@ def test_leave_fullscreen_on_workspace(workspace, exceptions, called, rol, mocke
 )
 def test__choose_if_multiple(target_workspace, multi, rol):
     rol.target_workspace = target_workspace
-    cons = [c for c in rol._get_window_list() if c.window_instance.startswith("test")]
+    cons = [
+        c
+        for c in rol._get_window_list()
+        if c.window_instance and c.window_instance.startswith("test")
+    ]
     if not multi:
         cons = [cons[0]]
     con = rol._choose_if_multiple(cons)
@@ -242,7 +247,9 @@ def test__handle_running(
     rol.cycle = cycle
     rol.scratch = scratch
     running = [
-        c for c in rol._get_window_list() if c.window_instance.startswith("test")
+        c
+        for c in rol._get_window_list()
+        if c.window_instance and c.window_instance.startswith("test")
     ]
     if not multi:
         running = [running[0]]
diff --git a/tests/tree.py b/tests/tree.py
index 1d898c7..1682d10 100644
--- a/tests/tree.py
+++ b/tests/tree.py
@@ -325,7 +325,55 @@ tree = {
                                     "sticky": False,
                                     "floating": "auto_off",
                                     "swallows": [],
-                                }
+                                },
+                                {
+                                    "id": 94083980242960,
+                                    "type": "con",
+                                    "orientation": "none",
+                                    "scratchpad_state": "none",
+                                    "percent": 0.5,
+                                    "urgent": False,
+                                    "focused": False,
+                                    "output": "eDP1",
+                                    "layout": "splith",
+                                    "workspace_layout": "default",
+                                    "last_split_layout": "splith",
+                                    "border": "normal",
+                                    "current_border_width": -1,
+                                    "rect": {
+                                        "x": 0,
+                                        "y": 50,
+                                        "width": 960,
+                                        "height": 1030,
+                                    },
+                                    "deco_rect": {
+                                        "x": 0,
+                                        "y": 0,
+                                        "width": 960,
+                                        "height": 24,
+                                    },
+                                    "window_rect": {
+                                        "x": 0,
+                                        "y": 0,
+                                        "width": 0,
+                                        "height": 0,
+                                    },
+                                    "geometry": {
+                                        "x": 0,
+                                        "y": 0,
+                                        "width": 0,
+                                        "height": 0,
+                                    },
+                                    "name": None,
+                                    "window": None,
+                                    "nodes": [],
+                                    "floating_nodes": [],
+                                    "focus": [],
+                                    "fullscreen_mode": 0,
+                                    "sticky": False,
+                                    "floating": "auto_off",
+                                    "swallows": [],
+                                },
                             ],
                             "floating_nodes": [],
                             "focus": [94271844826512, 94271844692688],