From f648cb607f1c84dedd6370e7a743e1fc7a2ca42c Mon Sep 17 00:00:00 2001 From: lasers Date: Tue, 21 Jan 2025 05:52:06 -0600 Subject: [PATCH 1/8] py3: add new helper: get_replacements_list (#2242) --- py3status/modules/cmus.py | 35 +++++++------ py3status/modules/deadbeef.py | 7 +++ py3status/modules/moc.py | 27 ++++++---- py3status/modules/playerctl.py | 10 +++- py3status/modules/spotify.py | 91 +++++++++------------------------- py3status/py3.py | 68 ++++++++++++++++++++++++- 6 files changed, 145 insertions(+), 93 deletions(-) diff --git a/py3status/modules/cmus.py b/py3status/modules/cmus.py index 11e3229a84..dc6f5b191a 100644 --- a/py3status/modules/cmus.py +++ b/py3status/modules/cmus.py @@ -16,6 +16,7 @@ *(default '[\?if=is_started [\?if=is_playing > ][\?if=is_paused \|\| ]' '[\?if=is_stopped .. ][[{artist}][\?soft - ][{title}]' '|\?show cmus: waiting for user input]]')* + replacements: specify a list/dict of string placeholders to modify (default None) sleep_timeout: sleep interval for this module. when cmus is not running, this interval will be used. this allows some flexible timing where one might want to refresh constantly with some placeholders... or to refresh @@ -100,6 +101,7 @@ class Py3status: r"[\?if=is_stopped .. ][[{artist}][\?soft - ][{title}]" r"|\?show cmus: waiting for user input]]" ) + replacements = None sleep_timeout = 20 def post_config_hook(self): @@ -109,6 +111,7 @@ def post_config_hook(self): self.color_stopped = self.py3.COLOR_STOPPED or self.py3.COLOR_BAD self.color_paused = self.py3.COLOR_PAUSED or self.py3.COLOR_DEGRADED self.color_playing = self.py3.COLOR_PLAYING or self.py3.COLOR_GOOD + self.replacements_init = self.py3.get_replacements_list(self.format) def _seconds_to_time(self, value): m, s = divmod(int(value), 60) @@ -165,14 +168,14 @@ def cmus(self): cached_until = self.sleep_timeout color = self.py3.COLOR_BAD - is_started, data = self._get_cmus_data() + is_started, cmus_data = self._get_cmus_data() if is_started: cached_until = self.cache_timeout - data = self._organize_data(data) - data = self._manipulate_data(data) + cmus_data = self._organize_data(cmus_data) + cmus_data = self._manipulate_data(cmus_data) - status = data.get("status") + status = cmus_data.get("status") if status == "playing": is_playing = True color = self.color_playing @@ -183,19 +186,23 @@ def cmus(self): is_stopped = True color = self.color_stopped + for x in self.replacements_init: + if x in cmus_data: + cmus_data[x] = self.py3.replace(cmus_data[x], x) + + cmus_data.update( + { + "is_paused": is_paused, + "is_playing": is_playing, + "is_started": is_started, + "is_stopped": is_stopped, + } + ) + return { "cached_until": self.py3.time_in(cached_until), "color": color, - "full_text": self.py3.safe_format( - self.format, - dict( - is_paused=is_paused, - is_playing=is_playing, - is_started=is_started, - is_stopped=is_stopped, - **data, - ), - ), + "full_text": self.py3.safe_format(self.format, cmus_data), } def on_click(self, event): diff --git a/py3status/modules/deadbeef.py b/py3status/modules/deadbeef.py index e20ddb9d17..50e1a0f38f 100644 --- a/py3status/modules/deadbeef.py +++ b/py3status/modules/deadbeef.py @@ -4,6 +4,7 @@ Configuration parameters: cache_timeout: refresh interval for this module (default 5) format: display format for this module (default '[{artist} - ][{title}]') + replacements: specify a list/dict of string placeholders to modify (default None) sleep_timeout: when deadbeef is not running, this interval will be used to allow faster refreshes with time-related placeholders and/or to refresh few times per minute rather than every few seconds @@ -58,6 +59,7 @@ class Py3status: # available configuration parameters cache_timeout = 5 format = "[{artist} - ][{title}]" + replacements = None sleep_timeout = 20 class Meta: @@ -89,6 +91,7 @@ def post_config_hook(self): self.color_paused = self.py3.COLOR_PAUSED or self.py3.COLOR_DEGRADED self.color_playing = self.py3.COLOR_PLAYING or self.py3.COLOR_GOOD self.color_stopped = self.py3.COLOR_STOPPED or self.py3.COLOR_BAD + self.replacements_init = self.py3.get_replacements_list(self.format) def _is_running(self): try: @@ -124,6 +127,10 @@ def deadbeef(self): else: color = self.color_paused + for x in self.replacements_init: + if x in beef_data: + beef_data[x] = self.py3.replace(beef_data[x], x) + return { "cached_until": self.py3.time_in(cached_until), "full_text": self.py3.safe_format(self.format, beef_data), diff --git a/py3status/modules/moc.py b/py3status/modules/moc.py index 585e962be1..21b53bdd96 100644 --- a/py3status/modules/moc.py +++ b/py3status/modules/moc.py @@ -14,6 +14,7 @@ format: display format for this module *(default '\?if=is_started [\?if=is_stopped \[\] moc|' '[\?if=is_paused \|\|][\?if=is_playing >] {title}]')* + replacements: specify a list/dict of string placeholders to modify (default None) sleep_timeout: when moc is not running, this interval will be used to allow one to refresh constantly with time placeholders and/or to refresh once every minute rather than every few seconds @@ -88,6 +89,7 @@ class Py3status: r"\?if=is_started [\?if=is_stopped \[\] moc|" r"[\?if=is_paused \|\|][\?if=is_playing >] {title}]" ) + replacements = None sleep_timeout = 20 def post_config_hook(self): @@ -97,6 +99,7 @@ def post_config_hook(self): self.color_stopped = self.py3.COLOR_STOPPED or self.py3.COLOR_BAD self.color_paused = self.py3.COLOR_PAUSED or self.py3.COLOR_DEGRADED self.color_playing = self.py3.COLOR_PLAYING or self.py3.COLOR_GOOD + self.replacements_init = self.py3.get_replacements_list(self.format) def _get_moc_data(self): try: @@ -133,19 +136,23 @@ def moc(self): is_stopped = True color = self.color_stopped + for x in self.replacements_init: + if x in moc_data: + moc_data[x] = self.py3.replace(moc_data[x], x) + + moc_data.update( + { + "is_paused": is_paused, + "is_playing": is_playing, + "is_started": is_started, + "is_stopped": is_stopped, + } + ) + return { "cached_until": self.py3.time_in(cached_until), "color": color, - "full_text": self.py3.safe_format( - self.format, - dict( - is_paused=is_paused, - is_playing=is_playing, - is_started=is_started, - is_stopped=is_stopped, - **data, - ), - ), + "full_text": self.py3.safe_format(self.format, moc_data), } def on_click(self, event): diff --git a/py3status/modules/playerctl.py b/py3status/modules/playerctl.py index 782cdd303e..437b4f6865 100644 --- a/py3status/modules/playerctl.py +++ b/py3status/modules/playerctl.py @@ -1,7 +1,7 @@ r""" Display song/video and control players supported by playerctl -Playerctl is a command-line utility for controlling media players +Playerctl is a command-line utility for controlling media players that implement the MPRIS D-Bus Interface Specification. With Playerctl you can bind player actions to keys and get metadata about the currently playing song or video. @@ -25,6 +25,7 @@ '[\?if=status=Stopped .. ][[{artist}][\?soft - ][{title}|{player}]]]')* format_player_separator: show separator if more than one player (default ' ') players: list of players to track. An empty list tracks all players (default []) + replacements: specify a list/dict of string placeholders to modify (default None) seek_delta: time (in seconds) to change the playback's position by (default 5) thresholds: specify color thresholds to use for different placeholders (default {"status": [("Playing", "good"), ("Paused", "degraded"), ("Stopped", "bad")]}) @@ -100,6 +101,7 @@ class Py3status: ) format_player_separator = " " players = [] + replacements = None seek_delta = 5 thresholds = {"status": [("Playing", "good"), ("Paused", "degraded"), ("Stopped", "bad")]} volume_delta = 10 @@ -117,6 +119,7 @@ class Meta: def post_config_hook(self): self.thresholds_init = self.py3.get_color_names_list(self.format_player) + self.replacements_init = self.py3.get_replacements_list(self.format_player) self.position = self.py3.format_contains(self.format_player, "position") self.cache_timeout = getattr(self, "cache_timeout", 1) @@ -283,6 +286,11 @@ def playerctl(self): if self.position and player_data["status"] == "Playing" and player_data["position"]: cached_until = self.cache_timeout + # Replace the values + for x in self.replacements_init: + if x in player_data: + player_data[x] = self.py3.replace(player_data[x], x) + # Set the color of a player for key in self.thresholds_init: if key in player_data: diff --git a/py3status/modules/spotify.py b/py3status/modules/spotify.py index 29b87e7a8a..4c7f358213 100644 --- a/py3status/modules/spotify.py +++ b/py3status/modules/spotify.py @@ -15,11 +15,8 @@ (default 'Spotify not running') format_stopped: define output if spotify is not playing (default 'Spotify stopped') - sanitize_titles: whether to remove meta data from album/track title - (default True) - sanitize_words: which meta data to remove - *(default ['bonus', 'demo', 'edit', 'explicit', 'extended', - 'feat', 'mono', 'remaster', 'stereo', 'version'])* + replacements: specify a list/dict of string placeholders to modify + (default None) Format placeholders: {album} album name @@ -45,6 +42,12 @@ button_previous = 5 format = "{title} by {artist} -> {time}" format_down = "no Spotify" + + # sanitize + replacements = { + "album": [("\s?[\(\[\-,;/][^)\],;/]*?(bonus|demo|edit|explicit|extended|feat|mono|remaster|stereo|version)[^)\],;/]*[\)\]]?", "")], + "title": [("\s?[\(\[\-,;/][^)\],;/]*?(bonus|demo|edit|explicit|extended|feat|mono|remaster|stereo|version)[^)\],;/]*[\)\]]?", "")] + } } ``` @@ -60,7 +63,6 @@ {'color': '#FF0000', 'full_text': 'Spotify stopped'} """ -import re from datetime import timedelta from time import sleep @@ -82,47 +84,11 @@ class Py3status: format = "{artist} : {title}" format_down = "Spotify not running" format_stopped = "Spotify stopped" - sanitize_titles = True - sanitize_words = [ - "bonus", - "demo", - "edit", - "explicit", - "extended", - "feat", - "mono", - "remaster", - "stereo", - "version", - ] + replacements = None def _spotify_cmd(self, action): return SPOTIFY_CMD.format(dbus_client=self.dbus_client, cmd=action) - def post_config_hook(self): - """ """ - # Match string after hyphen, comma, semicolon or slash containing any metadata word - # examples: - # - Remastered 2012 - # / Radio Edit - # ; Remastered - self.after_delimiter = self._compile_re(r"([\-,;/])([^\-,;/])*(META_WORDS_HERE).*") - - # Match brackets with their content containing any metadata word - # examples: - # (Remastered 2017) - # [Single] - # (Bonus Track) - self.inside_brackets = self._compile_re(r"([\(\[][^)\]]*?(META_WORDS_HERE)[^)\]]*?[\)\]])") - - def _compile_re(self, expression): - """ - Compile given regular expression for current sanitize words - """ - meta_words = "|".join(self.sanitize_words) - expression = expression.replace("META_WORDS_HERE", meta_words) - return re.compile(expression, re.IGNORECASE) - def _get_playback_status(self): """ Get the playback status. One of: "Playing", "Paused" or "Stopped". @@ -145,10 +111,6 @@ def _get_text(self): microtime = metadata.get("mpris:length") rtime = str(timedelta(seconds=microtime // 1_000_000)) title = metadata.get("xesam:title") - if self.sanitize_titles: - album = self._sanitize_title(album) - title = self._sanitize_title(title) - playback_status = self._get_playback_status() if playback_status == "Playing": color = self.py3.COLOR_PLAYING or self.py3.COLOR_GOOD @@ -160,29 +122,24 @@ def _get_text(self): self.py3.COLOR_PAUSED or self.py3.COLOR_DEGRADED, ) - return ( - self.py3.safe_format( - self.format, - dict( - title=title, - artist=artist, - album=album, - time=rtime, - playback=playback_status, - ), - ), - color, - ) + spotify_data = { + "title": title, + "artist": artist, + "album": album, + "time": rtime, + "playback": playback_status, + } + + for x in self.replacements_init: + if x in spotify_data: + spotify_data[x] = self.py3.replace(spotify_data[x], x) + + return (self.py3.safe_format(self.format, spotify_data), color) except Exception: return (self.format_down, self.py3.COLOR_OFFLINE or self.py3.COLOR_BAD) - def _sanitize_title(self, title): - """ - Remove redundant metadata from title and return it - """ - title = re.sub(self.inside_brackets, "", title) - title = re.sub(self.after_delimiter, "", title) - return title.strip() + def post_config_hook(self): + self.replacements_init = self.py3.get_replacements_list(self.format) def spotify(self): """ diff --git a/py3status/py3.py b/py3status/py3.py index 8550f89f1b..00b893dfad 100644 --- a/py3status/py3.py +++ b/py3status/py3.py @@ -1,4 +1,5 @@ import os +import re import shlex import sys import time @@ -102,6 +103,7 @@ def __init__(self, module=None): self._format_placeholders = {} self._format_placeholders_cache = {} self._module = module + self._replacements = None self._report_exception_cache = set() self._thresholds = None self._threshold_gradients = {} @@ -177,7 +179,7 @@ def _thresholds_init(self): except TypeError: pass self._thresholds[None] = [(x[0], self._get_color(x[1])) for x in thresholds] - return + elif isinstance(thresholds, dict): for key, value in thresholds.items(): if isinstance(value, list): @@ -187,6 +189,25 @@ def _thresholds_init(self): pass self._thresholds[key] = [(x[0], self._get_color(x[1])) for x in value] + def _replacements_init(self): + """ + Initiate and check any replacements set + """ + replacements = getattr(self._py3status_module, "replacements", []) + self._replacements = {} + + if isinstance(replacements, list): + self._replacements[None] = [ + (re.compile(x[0], re.IGNORECASE), x[1]) for x in replacements + ] + + elif isinstance(replacements, dict): + for key, value in replacements.items(): + if isinstance(value, list): + self._replacements[key] = [ + (re.compile(x[0], re.IGNORECASE), x[1]) for x in value + ] + def _get_module_info(self, module_name): """ THIS IS PRIVATE AND UNSUPPORTED. @@ -710,6 +731,28 @@ def get_color_names_list(self, format_string, matches=None): found.add(name) return list(found) + def get_replacements_list(self, format_string): + """ + If possible, returns a list of filtered placeholders in ``format_string``. + """ + replacements = getattr(self._py3status_module, "replacements", None) + if not replacements or not format_string: + return [] + + if format_string not in self._format_placeholders: + placeholders = self._formatter.get_placeholders(format_string) + self._format_placeholders[format_string] = placeholders + else: + placeholders = self._format_placeholders[format_string] + + # filter placeholders + found = set() + for replacement in replacements: + for placeholder in placeholders: + if placeholder == replacement: + found.add(placeholder) + return list(found or placeholders) + def get_placeholders_list(self, format_string, matches=None): """ Returns a list of placeholders in ``format_string``. @@ -1214,6 +1257,29 @@ def threshold_get_color(self, value, name=None): return color + def replace(self, value, name=None): + """ + Replace string using replacements. + + :param value: string value to be replaced + :param name: accepts a name + """ + # If first run, then process the replacements data. + if self._replacements is None: + self._replacements_init() + + if not value or not isinstance(value, str): + return value + + name_used = name + if name_used not in self._replacements: + name_used = None + + for pattern, replacement in self._replacements.get(name_used, []): + value = re.sub(pattern, replacement, value) + + return value + def request( self, url, From 3d85b4c7a8e950b30312f0ba1e2e00b6bdea1be8 Mon Sep 17 00:00:00 2001 From: lasers Date: Tue, 21 Jan 2025 05:55:47 -0600 Subject: [PATCH 2/8] conky module: add fallback delete parameter for py3.11 or less (#2275) --- py3status/modules/conky.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/py3status/modules/conky.py b/py3status/modules/conky.py index a66ae224fa..2fa1edc75c 100644 --- a/py3status/modules/conky.py +++ b/py3status/modules/conky.py @@ -369,9 +369,15 @@ def post_config_hook(self): tmp = f"conky.config = {config}\nconky.text = [[{text}]]" # write tmp output to '/tmp/py3status-conky_*', make a command - self.tmpfile = NamedTemporaryFile( - prefix="py3status_conky-", suffix=".conf", delete_on_close=False - ) + try: + # python 3.12+ + self.tmpfile = NamedTemporaryFile( + prefix="py3status_conky-", suffix=".conf", delete_on_close=False + ) + except TypeError: + self.tmpfile = NamedTemporaryFile( + prefix="py3status_conky-", suffix=".conf", delete=False + ) self.tmpfile.write(str.encode(tmp)) self.tmpfile.close() self.conky_command = f"conky -c {self.tmpfile.name}".split() From d5dc338d785244767f5421c068b6c38f828a0389 Mon Sep 17 00:00:00 2001 From: lasers Date: Tue, 21 Jan 2025 05:58:23 -0600 Subject: [PATCH 3/8] tests: stop warning py3status/tests/test_module_load.py:5 (#2276) --- tests/test_module_load.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_module_load.py b/tests/test_module_load.py index 2a60864b54..15017db5b0 100644 --- a/tests/test_module_load.py +++ b/tests/test_module_load.py @@ -5,8 +5,9 @@ class TestModule: static_variable = 123 - def __init__(self): - self.instance_variable = 321 + @classmethod + def setup_class(cls): + cls.instance_variable = 321 def post_config_hook(self): pass From 59ce6b104bf20decfde5edcae5b382a2401dfa5b Mon Sep 17 00:00:00 2001 From: lasers Date: Tue, 21 Jan 2025 05:59:11 -0600 Subject: [PATCH 4/8] whatismyip module: add an example with more url_geo choices (#2277) --- py3status/modules/whatismyip.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/py3status/modules/whatismyip.py b/py3status/modules/whatismyip.py index 98ee242d6a..a8a62a369d 100644 --- a/py3status/modules/whatismyip.py +++ b/py3status/modules/whatismyip.py @@ -35,6 +35,17 @@ color_degraded: Output is unexpected (IP/country mismatch, etc.) color_good: Online +Examples: +``` +# ip choices +whatismyip { + url_geo = "https://ifconfig.co/json" + # url_geo = "https://api.ip2location.io" + # url_geo = "https://ipinfo.io/json" + # url_geo = "http://ip-api.com/json" +} +``` + @author ultrabug, Cyril Levis (@cyrinux) SAMPLE OUTPUT From a16e778072c3b3e67e045d962eed43392ae20e8d Mon Sep 17 00:00:00 2001 From: lasers Date: Tue, 21 Jan 2025 05:59:56 -0600 Subject: [PATCH 5/8] getjson module: fix examples (#2278) --- py3status/modules/getjson.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/py3status/modules/getjson.py b/py3status/modules/getjson.py index 6f07418e57..1d6c6fa90e 100644 --- a/py3status/modules/getjson.py +++ b/py3status/modules/getjson.py @@ -29,20 +29,28 @@ Examples: ``` # straightforward key replacement -url = 'http://ip-api.com/json' -format = '{lat}, {lon}' +getjson { + url = "https://ifconfig.co/json" + format = "{latitude}, {longitude}" +} # access child objects -url = 'https://api.icndb.com/jokes/random' -format = '{value-joke}' +getjson { + url = 'https://api.icndb.com/jokes/random' + format = '{value-joke}' +} # access title from 0th element of articles list -url = 'https://newsapi.org/v1/articles?source=bbc-news&sortBy=top&apiKey={KEY}' -format = '{articles-0-title}' +getjson { + url = 'https://newsapi.org/v1/articles?source=bbc-news&sortBy=top&apiKey={KEY}' + format = '{articles-0-title}' +} # access if top-level object is a list -url = 'https://jsonplaceholder.typicode.com/posts/1/comments' -format = '{0-name}' +getjson { + url = 'https://jsonplaceholder.typicode.com/posts/1/comments' + format = '{0-name}' +} ``` @author vicyap From d78a60de482f3e91e7eb0bcca2163d9e71337d99 Mon Sep 17 00:00:00 2001 From: Eduardo Suarez-Santana Date: Tue, 21 Jan 2025 12:03:29 +0000 Subject: [PATCH 6/8] xsel module: fix TypeError (#2279) --- py3status/modules/xsel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py3status/modules/xsel.py b/py3status/modules/xsel.py index 3f4de928ca..47edbcb24f 100644 --- a/py3status/modules/xsel.py +++ b/py3status/modules/xsel.py @@ -50,7 +50,8 @@ class Py3status: def post_config_hook(self): self.selection_cache = None - self.log_file = Path(self.log_file).expanduser() + if self.log_file: + self.log_file = Path(self.log_file).expanduser() def xsel(self): selection = self.py3.command_output(self.command).strip() From 79fa9f074cc814ad3599a8e550b6ddc9c1952c50 Mon Sep 17 00:00:00 2001 From: Kevin Pulo Date: Tue, 21 Jan 2025 23:05:15 +1100 Subject: [PATCH 7/8] external_script module: allow scripts to specify urgent (#2282) --- py3status/modules/external_script.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/py3status/modules/external_script.py b/py3status/modules/external_script.py index 1f8a3dcf32..b7f2349e8e 100644 --- a/py3status/modules/external_script.py +++ b/py3status/modules/external_script.py @@ -5,6 +5,8 @@ two lines of output will be used. The first line is used as the displayed text. If the output has two or more lines, the second line is set as the text color (and should hence be a valid hex color code such as #FF0000 for red). +If the second line is `urgent`, or has `!` prefixing the hex color, then the +"urgent" flag will be set. The script should not have any parameters, but it could work. Configuration parameters: @@ -76,8 +78,14 @@ def external_script(self): output_lines = self.output.splitlines() if len(output_lines) > 1: output_color = output_lines[1] - if re.search(r"^#[0-9a-fA-F]{6}$", output_color): - response["color"] = output_color + if output_color == "urgent": + response["urgent"] = True + elif re.search(r"^!?#[0-9a-fA-F]{6}$", output_color): + if output_color[0] == "!": + response["urgent"] = True + response["color"] = output_color[1:] + else: + response["color"] = output_color except self.py3.CommandError as e: # something went wrong show error to user output = e.output or e.error From c2d623ae43c05c7528410700e67eb6b79a6dcaaf Mon Sep 17 00:00:00 2001 From: Kevin Pulo Date: Tue, 21 Jan 2025 23:20:49 +1100 Subject: [PATCH 8/8] external_script module: adjust how urgent is specified (#2283) --- py3status/modules/external_script.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/py3status/modules/external_script.py b/py3status/modules/external_script.py index b7f2349e8e..be9740c09d 100644 --- a/py3status/modules/external_script.py +++ b/py3status/modules/external_script.py @@ -3,10 +3,10 @@ Display output of any executable script set by `script_path`. Only the first two lines of output will be used. The first line is used as the displayed -text. If the output has two or more lines, the second line is set as the text -color (and should hence be a valid hex color code such as #FF0000 for red). -If the second line is `urgent`, or has `!` prefixing the hex color, then the -"urgent" flag will be set. +text. If the output has two or more lines, the second line contains additional +information as whitespace separated tokens. Valid tokens are: + `#rrggbb`: the text color as a hex color code (eg. `#FF0000` for red) + `urgent`: the word `urgent` to set the urgent flag The script should not have any parameters, but it could work. Configuration parameters: @@ -77,15 +77,12 @@ def external_script(self): ) output_lines = self.output.splitlines() if len(output_lines) > 1: - output_color = output_lines[1] - if output_color == "urgent": - response["urgent"] = True - elif re.search(r"^!?#[0-9a-fA-F]{6}$", output_color): - if output_color[0] == "!": + words = output_lines[1].split() + for word in words: + if re.search(r"^#[0-9a-fA-F]{6}$", word): + response["color"] = word + elif word == "urgent": response["urgent"] = True - response["color"] = output_color[1:] - else: - response["color"] = output_color except self.py3.CommandError as e: # something went wrong show error to user output = e.output or e.error