From c560c078902fefe3e033a5d7d09cfc3ba1624b33 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 10 May 2020 12:52:20 +0200 Subject: [PATCH 01/27] [packaging/pip] Make binary utility tools functional --- bumblebee_status/discover.py | 11 +++++++++++ bumblebee_status/modules/contrib/pacman.py | 6 +++--- bumblebee_status/modules/core/xrandr.py | 8 ++++---- setup.cfg | 3 --- setup.py | 1 + 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/bumblebee_status/discover.py b/bumblebee_status/discover.py index 5c9e1bc..503be7d 100644 --- a/bumblebee_status/discover.py +++ b/bumblebee_status/discover.py @@ -8,5 +8,16 @@ def discover(): ) sys.path.append(libdir) +def utility(name): + current_path = os.path.dirname(os.path.abspath(__file__)) + + for path in [ + os.path.join(current_path, "..", "bin"), + os.path.join(current_path, "..", "..", "..", "..", "share", "bumblebee-status", "utility"), + "/usr/share/bumblebee-status/bin/", + ]: + if os.path.exists(os.path.abspath(os.path.join(path, name))): + return os.path.abspath(os.path.join(path, name)) + raise Exception("{} not found".format(name)) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/modules/contrib/pacman.py b/bumblebee_status/modules/contrib/pacman.py index 8566112..b50ed53 100644 --- a/bumblebee_status/modules/contrib/pacman.py +++ b/bumblebee_status/modules/contrib/pacman.py @@ -22,15 +22,15 @@ import core.decorators import util.cli import util.format +from bumblebee_status.discover import utility + # list of repositories. # the last one should always be other repos = ["core", "extra", "community", "multilib", "testing", "other"] def get_pacman_info(widget, path): - cmd = "{}/../../bin/pacman-updates".format(path) - if not os.path.exists(cmd): - cmd = "/usr/share/bumblebee-status/bin/pacman-update" + cmd = utility("pacman-updates") result = util.cli.execute(cmd, ignore_errors=True) count = len(repos) * [0] diff --git a/bumblebee_status/modules/core/xrandr.py b/bumblebee_status/modules/core/xrandr.py index f3eabb6..04278ba 100644 --- a/bumblebee_status/modules/core/xrandr.py +++ b/bumblebee_status/modules/core/xrandr.py @@ -24,6 +24,8 @@ import core.module import core.input import core.decorators +from bumblebee_status.discover import utility + import util.cli import util.format @@ -36,8 +38,7 @@ except: class Module(core.module.Module): @core.decorators.every(seconds=5) # takes up to 5s to detect a new screen def __init__(self, config, theme): - widgets = [] - super().__init__(config, theme, widgets) + super().__init__(config, theme, []) self._autoupdate = util.format.asbool(self.parameter("autoupdate", True)) self._needs_update = True @@ -85,10 +86,9 @@ class Module(core.module.Module): def _toggle(self, event): self._refresh(self, event) - path = os.path.dirname(os.path.abspath(__file__)) if util.format.asbool(self.parameter("overwrite_i3config", False)) == True: - toggle_cmd = "{}/../../bin/toggle-display.sh".format(path) + toggle_cmd = utility("toggle-display.sh") else: toggle_cmd = "xrandr" diff --git a/setup.cfg b/setup.cfg index 5caf4e6..9352f13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,9 +39,6 @@ packages = find: scripts = ./bumblebee-status ./bumblebee-ctl - ./bin/load-i3-bars.sh - ./bin/pacman-updates - ./bin/toggle-display.sh [versioneer] VCS = git diff --git a/setup.py b/setup.py index 79ccd29..5a100d4 100755 --- a/setup.py +++ b/setup.py @@ -56,5 +56,6 @@ setup( data_files=[ ("share/bumblebee-status/themes", glob.glob("themes/*.json")), ("share/bumblebee-status/themes/icons", glob.glob("themes/icons/*.json")), + ("share/bumblebee-status/utility", glob.glob("bin/*")), ], ) From b803f385a167b0bc5a11c5e17e3b9883464c7dd6 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 10 May 2020 12:56:30 +0200 Subject: [PATCH 02/27] [tests] Fix failing tests --- bumblebee_status/__init__.py | 3 +++ bumblebee_status/core/module.py | 1 + 2 files changed, 4 insertions(+) diff --git a/bumblebee_status/__init__.py b/bumblebee_status/__init__.py index e69de29..8df5d95 100644 --- a/bumblebee_status/__init__.py +++ b/bumblebee_status/__init__.py @@ -0,0 +1,3 @@ +import bumblebee_status.discover + +bumblebee_status.discover.discover() diff --git a/bumblebee_status/core/module.py b/bumblebee_status/core/module.py index 618cd6a..eb15833 100644 --- a/bumblebee_status/core/module.py +++ b/bumblebee_status/core/module.py @@ -2,6 +2,7 @@ import os import importlib import logging +import core.config import core.input import core.widget import core.decorators From f78981b2261319d88900cbf7fb66157ade3760ca Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 10 May 2020 13:01:40 +0200 Subject: [PATCH 03/27] [packaging/pip] Add test_suite Hope it works like this... --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5a100d4..f9f2ee4 100755 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ setup( version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), zip_safe=False, + test_suite="tests", data_files=[ ("share/bumblebee-status/themes", glob.glob("themes/*.json")), ("share/bumblebee-status/themes/icons", glob.glob("themes/icons/*.json")), From 560083ce0c00aa228dc28a0d05f82ae84df2c3b7 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 10 May 2020 13:05:20 +0200 Subject: [PATCH 04/27] [docs] Update API doc to new package structure --- docs/api.rst | 3 +- docs/src/bumblebee_status.core.rst | 78 ++++++++++++++++++++++++++++++ docs/src/bumblebee_status.rst | 31 ++++++++++++ docs/src/bumblebee_status.util.rst | 70 +++++++++++++++++++++++++++ docs/src/core.rst | 78 ------------------------------ docs/src/util.rst | 70 --------------------------- 6 files changed, 180 insertions(+), 150 deletions(-) create mode 100644 docs/src/bumblebee_status.core.rst create mode 100644 docs/src/bumblebee_status.rst create mode 100644 docs/src/bumblebee_status.util.rst delete mode 100644 docs/src/core.rst delete mode 100644 docs/src/util.rst diff --git a/docs/api.rst b/docs/api.rst index a089602..7c22a2b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4,5 +4,4 @@ API Reference .. toctree:: :maxdepth: 4 - src/core - src/util + src/bumblebee_status diff --git a/docs/src/bumblebee_status.core.rst b/docs/src/bumblebee_status.core.rst new file mode 100644 index 0000000..14b545c --- /dev/null +++ b/docs/src/bumblebee_status.core.rst @@ -0,0 +1,78 @@ +bumblebee\_status.core package +============================== + +Submodules +---------- + +bumblebee\_status.core.config module +------------------------------------ + +.. automodule:: bumblebee_status.core.config + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.core.decorators module +---------------------------------------- + +.. automodule:: bumblebee_status.core.decorators + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.core.event module +----------------------------------- + +.. automodule:: bumblebee_status.core.event + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.core.input module +----------------------------------- + +.. automodule:: bumblebee_status.core.input + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.core.module module +------------------------------------ + +.. automodule:: bumblebee_status.core.module + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.core.output module +------------------------------------ + +.. automodule:: bumblebee_status.core.output + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.core.theme module +----------------------------------- + +.. automodule:: bumblebee_status.core.theme + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.core.widget module +------------------------------------ + +.. automodule:: bumblebee_status.core.widget + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: bumblebee_status.core + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/bumblebee_status.rst b/docs/src/bumblebee_status.rst new file mode 100644 index 0000000..1519661 --- /dev/null +++ b/docs/src/bumblebee_status.rst @@ -0,0 +1,31 @@ +bumblebee\_status package +========================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + bumblebee_status.core + bumblebee_status.util + +Submodules +---------- + +bumblebee\_status.discover module +--------------------------------- + +.. automodule:: bumblebee_status.discover + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: bumblebee_status + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/bumblebee_status.util.rst b/docs/src/bumblebee_status.util.rst new file mode 100644 index 0000000..98cf474 --- /dev/null +++ b/docs/src/bumblebee_status.util.rst @@ -0,0 +1,70 @@ +bumblebee\_status.util package +============================== + +Submodules +---------- + +bumblebee\_status.util.algorithm module +--------------------------------------- + +.. automodule:: bumblebee_status.util.algorithm + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.util.cli module +--------------------------------- + +.. automodule:: bumblebee_status.util.cli + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.util.format module +------------------------------------ + +.. automodule:: bumblebee_status.util.format + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.util.graph module +----------------------------------- + +.. automodule:: bumblebee_status.util.graph + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.util.location module +-------------------------------------- + +.. automodule:: bumblebee_status.util.location + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.util.popup module +----------------------------------- + +.. automodule:: bumblebee_status.util.popup + :members: + :undoc-members: + :show-inheritance: + +bumblebee\_status.util.store module +----------------------------------- + +.. automodule:: bumblebee_status.util.store + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: bumblebee_status.util + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/src/core.rst b/docs/src/core.rst deleted file mode 100644 index e1b2eed..0000000 --- a/docs/src/core.rst +++ /dev/null @@ -1,78 +0,0 @@ -core package -============ - -Submodules ----------- - -core.config module ------------------- - -.. automodule:: core.config - :members: - :undoc-members: - :show-inheritance: - -core.decorators module ----------------------- - -.. automodule:: core.decorators - :members: - :undoc-members: - :show-inheritance: - -core.event module ------------------ - -.. automodule:: core.event - :members: - :undoc-members: - :show-inheritance: - -core.input module ------------------ - -.. automodule:: core.input - :members: - :undoc-members: - :show-inheritance: - -core.module module ------------------- - -.. automodule:: core.module - :members: - :undoc-members: - :show-inheritance: - -core.output module ------------------- - -.. automodule:: core.output - :members: - :undoc-members: - :show-inheritance: - -core.theme module ------------------ - -.. automodule:: core.theme - :members: - :undoc-members: - :show-inheritance: - -core.widget module ------------------- - -.. automodule:: core.widget - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: core - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/src/util.rst b/docs/src/util.rst deleted file mode 100644 index e127fca..0000000 --- a/docs/src/util.rst +++ /dev/null @@ -1,70 +0,0 @@ -util package -============ - -Submodules ----------- - -util.algorithm module ---------------------- - -.. automodule:: util.algorithm - :members: - :undoc-members: - :show-inheritance: - -util.cli module ---------------- - -.. automodule:: util.cli - :members: - :undoc-members: - :show-inheritance: - -util.format module ------------------- - -.. automodule:: util.format - :members: - :undoc-members: - :show-inheritance: - -util.graph module ------------------ - -.. automodule:: util.graph - :members: - :undoc-members: - :show-inheritance: - -util.location module --------------------- - -.. automodule:: util.location - :members: - :undoc-members: - :show-inheritance: - -util.popup module ------------------ - -.. automodule:: util.popup - :members: - :undoc-members: - :show-inheritance: - -util.store module ------------------ - -.. automodule:: util.store - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: util - :members: - :undoc-members: - :show-inheritance: From 106bb9e8c32b553eddb1011ef17d4a288e8d09b5 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 10 May 2020 13:07:08 +0200 Subject: [PATCH 05/27] [util/graph] Remove docstrings in prep for more complete documentation --- bumblebee_status/util/graph.py | 65 ---------------------------------- 1 file changed, 65 deletions(-) diff --git a/bumblebee_status/util/graph.py b/bumblebee_status/util/graph.py index b305477..c6b12e6 100644 --- a/bumblebee_status/util/graph.py +++ b/bumblebee_status/util/graph.py @@ -2,22 +2,13 @@ MAX_PERCENTS = 100.0 class Bar(object): - """superclass""" - bars = None def __init__(self, value): - """ - Args: - - value (float): value between 0. and 100. meaning percents - """ self.value = value class HBar(Bar): - """horizontal bar (1 char)""" - bars = [ "\u2581", "\u2582", @@ -30,20 +21,10 @@ class HBar(Bar): ] def __init__(self, value): - """ - Args: - - value (float): value between 0. and 100. meaning percents - """ super(HBar, self).__init__(value) self.step = MAX_PERCENTS / len(HBar.bars) def get_char(self): - """ - Decide which char to draw - - Return: str - """ for i in range(len(HBar.bars)): left = i * self.step right = (i + 1) * self.step @@ -53,13 +34,10 @@ class HBar(Bar): def hbar(value): - """wrapper function""" return HBar(value).get_char() class VBar(Bar): - """vertical bar (can be more than 1 char)""" - bars = [ "\u258f", "\u258e", @@ -72,23 +50,11 @@ class VBar(Bar): ] def __init__(self, value, width=1): - """ - Args: - - value (float): value between 0. and 100. meaning percents - - width (int): width - """ super(VBar, self).__init__(value) self.step = MAX_PERCENTS / (len(VBar.bars) * width) self.width = width def get_chars(self): - """ - Decide which char to draw - - Return: str - """ if self.value == 100: return self.bars[-1] * self.width if self.width == 1: @@ -111,16 +77,10 @@ class VBar(Bar): def vbar(value, width): - """wrapper function""" return VBar(value, width).get_chars() class BrailleGraph(object): - """ - graph using Braille chars - scaled to passed values - """ - chars = { (0, 0): " ", (1, 0): "\u2840", @@ -150,11 +110,6 @@ class BrailleGraph(object): } def __init__(self, values): - """ - Args: - - values (list): list of values - """ self.values = values # length of values list must be even # because one Braille char displays two values @@ -165,15 +120,6 @@ class BrailleGraph(object): @staticmethod def get_height(value, unit): - """ - Compute height of a value relative to unit - - Args: - - value (number): value - - unit (number): unit - """ if value < unit / 10.0: return 0 elif value <= unit: @@ -186,11 +132,6 @@ class BrailleGraph(object): return 4 def get_steps(self): - """ - Convert the list of values to a list of steps - - Return: list - """ maxval = max(self.values) unit = maxval / 4.0 if unit == 0: @@ -201,11 +142,6 @@ class BrailleGraph(object): return stepslist def get_chars(self): - """ - Decide which chars to draw - - Return: str - """ chars = [] for part in self.parts: chars.append(BrailleGraph.chars[part]) @@ -213,7 +149,6 @@ class BrailleGraph(object): def braille(values): - """wrapper function""" return BrailleGraph(values).get_chars() From 8ca036bc4cec083b26873845d84a478a4e497691 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 10 May 2020 13:15:47 +0200 Subject: [PATCH 06/27] [util/graph] Add API documentation --- bumblebee_status/util/graph.py | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bumblebee_status/util/graph.py b/bumblebee_status/util/graph.py index c6b12e6..33c4a67 100644 --- a/bumblebee_status/util/graph.py +++ b/bumblebee_status/util/graph.py @@ -20,11 +20,20 @@ class HBar(Bar): "\u2588", ] + """This class is a helper class used to draw horizontal bars - please use hbar directly + + :param value: percentage value to draw (float, between 0 and 100) + """ def __init__(self, value): super(HBar, self).__init__(value) self.step = MAX_PERCENTS / len(HBar.bars) def get_char(self): + """Returns the character representing the current object's value + + :return: character representing the value passed during initialization + :rtype: string with one character + """ for i in range(len(HBar.bars)): left = i * self.step right = (i + 1) * self.step @@ -34,6 +43,12 @@ class HBar(Bar): def hbar(value): + """"Retrieves the horizontal bar character representing the input value + + :param value: percentage value to draw (float, between 0 and 100) + :return: character representing the value passed during initialization + :rtype: string with one character + """ return HBar(value).get_char() @@ -49,11 +64,21 @@ class VBar(Bar): "\u2588", ] + """This class is a helper class used to draw vertical bars - please use vbar directly + + :param value: percentage value to draw (float, between 0 and 100) + :param width: maximum width of the bar in characters + """ def __init__(self, value, width=1): super(VBar, self).__init__(value) self.step = MAX_PERCENTS / (len(VBar.bars) * width) self.width = width + """Returns the characters representing the current object's value + + :return: characters representing the value passed during initialization + :rtype: string + """ def get_chars(self): if self.value == 100: return self.bars[-1] * self.width @@ -77,6 +102,14 @@ class VBar(Bar): def vbar(value, width): + """Returns the characters representing the current object's value + + :param value: percentage value to draw (float, between 0 and 100) + :param width: maximum width of the bar in characters + + :return: characters representing the value passed during initialization + :rtype: string + """ return VBar(value, width).get_chars() @@ -109,6 +142,10 @@ class BrailleGraph(object): (4, 4): "\u28ff", } + """This class is a helper class used to draw braille graphs - please use braille directly + + :param values: values to draw + """ def __init__(self, values): self.values = values # length of values list must be even From b4f8870a951ea5308f9b5c7cff188f6ec2eb3b26 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 10 May 2020 13:25:13 +0200 Subject: [PATCH 07/27] [doc] update API docs --- bumblebee_status/core/config.py | 64 ++++++++++++++++++++++++++++++++- bumblebee_status/discover.py | 6 +++- bumblebee_status/util/graph.py | 4 +++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/bumblebee_status/core/config.py b/bumblebee_status/core/config.py index 82fcb6a..f779a14 100644 --- a/bumblebee_status/core/config.py +++ b/bumblebee_status/core/config.py @@ -27,7 +27,11 @@ THEME_HELP = "Specify the theme to use for drawing modules" def all_modules(): - """Return a list of available modules""" + """Returns a list of all available modules (either core or contrib) + + :return: list of modules + :rtype: list of strings + """ result = {} for path in [modules.core.__file__, modules.contrib.__file__]: @@ -127,6 +131,11 @@ class print_usage(argparse.Action): class Config(util.store.Store): + """Represents the configuration of bumblebee-status (either via config file or via CLI) + + :param args: The arguments passed via the commandline + """ + def __init__(self, args): super(Config, self).__init__() @@ -202,6 +211,11 @@ class Config(util.store.Store): key, value = param.split("=", 1) self.set(key, value) + """Loads parameters from an init-style configuration file + + :param filename: path to the file to load + """ + def load_config(self, filename): if os.path.exists(filename): log.info("loading {}".format(filename)) @@ -212,27 +226,75 @@ class Config(util.store.Store): for key, value in tmp.items("module-parameters"): self.set(key, value) + """Returns a list of configured modules + + :return: list of configured (active) modules + :rtype: list of strings + """ + def modules(self): return [item for sub in self.__args.modules for item in sub] + """Returns the global update interval + + :return: update interval in seconds + :rtype: float + """ + def interval(self, default=1): return util.format.seconds(self.get("interval", default)) + """Returns whether debug mode is enabled + + :return: True if debug is enabled, False otherwise + :rtype: boolean + """ + def debug(self): return self.__args.debug + """Returns whether module order should be reversed/inverted + + :return: True if modules should be reversed, False otherwise + :rtype: boolean + """ + def reverse(self): return self.__args.right_to_left + """Returns the logfile location + + :return: location where the logfile should be written + :rtype: string + """ + def logfile(self): return self.__args.logfile + """Returns the configured theme name + + :return: name of the configured theme + :rtype: string + """ + def theme(self): return self.__args.theme + """Returns the configured iconset name + + :return: name of the configured iconset + :rtype: string + """ + def iconset(self): return self.__args.iconset + """Returns which modules should be hidden if their state is not warning/critical + + :return: list of modules to hide automatically + :rtype: list of strings + """ + def autohide(self, name): return name in self.__args.autohide diff --git a/bumblebee_status/discover.py b/bumblebee_status/discover.py index 503be7d..73e6c65 100644 --- a/bumblebee_status/discover.py +++ b/bumblebee_status/discover.py @@ -8,16 +8,20 @@ def discover(): ) sys.path.append(libdir) + def utility(name): current_path = os.path.dirname(os.path.abspath(__file__)) for path in [ os.path.join(current_path, "..", "bin"), - os.path.join(current_path, "..", "..", "..", "..", "share", "bumblebee-status", "utility"), + os.path.join( + current_path, "..", "..", "..", "..", "share", "bumblebee-status", "utility" + ), "/usr/share/bumblebee-status/bin/", ]: if os.path.exists(os.path.abspath(os.path.join(path, name))): return os.path.abspath(os.path.join(path, name)) raise Exception("{} not found".format(name)) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/bumblebee_status/util/graph.py b/bumblebee_status/util/graph.py index 33c4a67..fa927e2 100644 --- a/bumblebee_status/util/graph.py +++ b/bumblebee_status/util/graph.py @@ -24,6 +24,7 @@ class HBar(Bar): :param value: percentage value to draw (float, between 0 and 100) """ + def __init__(self, value): super(HBar, self).__init__(value) self.step = MAX_PERCENTS / len(HBar.bars) @@ -69,6 +70,7 @@ class VBar(Bar): :param value: percentage value to draw (float, between 0 and 100) :param width: maximum width of the bar in characters """ + def __init__(self, value, width=1): super(VBar, self).__init__(value) self.step = MAX_PERCENTS / (len(VBar.bars) * width) @@ -79,6 +81,7 @@ class VBar(Bar): :return: characters representing the value passed during initialization :rtype: string """ + def get_chars(self): if self.value == 100: return self.bars[-1] * self.width @@ -146,6 +149,7 @@ class BrailleGraph(object): :param values: values to draw """ + def __init__(self, values): self.values = values # length of values list must be even From f2aa00613085eb69de60353fab75bd6620417fed Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sun, 10 May 2020 13:26:52 +0200 Subject: [PATCH 08/27] [github] re-enable pip publish workflow --- .github/workflows/pythonpublish.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/pythonpublish.yml diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml new file mode 100644 index 0000000..365c526 --- /dev/null +++ b/.github/workflows/pythonpublish.yml @@ -0,0 +1,27 @@ +--- +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From 3fdd2d2be62e6a221547f9bf3679bbaf0890d1b8 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Mon, 11 May 2020 15:10:20 +0200 Subject: [PATCH 09/27] [modules/arch-update] Gracefully handle exit code 2 fixes #624 --- bumblebee_status/modules/contrib/arch-update.py | 15 +++++++++------ bumblebee_status/util/cli.py | 14 ++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/bumblebee_status/modules/contrib/arch-update.py b/bumblebee_status/modules/contrib/arch-update.py index 66287be..a1ea550 100644 --- a/bumblebee_status/modules/contrib/arch-update.py +++ b/bumblebee_status/modules/contrib/arch-update.py @@ -33,13 +33,16 @@ class Module(core.module.Module): return self.__packages == 0 and not self.__error def update(self): - try: - result = util.cli.execute("checkupdates") - self.__packages = len(result.split("\n")) - 1 - self.__error = False - except Exception as e: - logging.exception(e) + self.__error = False + code, result = util.cli.execute("checkupdates", ignore_errors=True, return_exitcode=True) + + if code == 0: + self.__packages = len(result.split("\n")) + elif code == 2: + self.__packages = 0 + else: self.__error = True + log.error("checkupdates exited with {}: {}".format(code, result)) def state(self, widget): if self.__error: diff --git a/bumblebee_status/util/cli.py b/bumblebee_status/util/cli.py index fd54093..31a2955 100644 --- a/bumblebee_status/util/cli.py +++ b/bumblebee_status/util/cli.py @@ -4,7 +4,7 @@ import subprocess import logging -def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None): +def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None, return_exitcode=False): """Executes a commandline utility and returns its output :param cmd: the command (as string) to execute, returns the program's output @@ -12,11 +12,12 @@ def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None) :param ignore_errors: set to True to return a string when an exception is thrown, otherwise might throw, defaults to False :param include_stderr: set to True to include stderr output in the return value, defaults to False :param env: provide a dict here to specify a custom execution environment, defaults to None + :param return_exitcode: set to True to return a pair, where the first member is the exit code and the message the second, defaults to False :raises RuntimeError: the command either didn't exist or didn't exit cleanly, and ignore_errors was set to False - :return: output of cmd, or stderr, if ignore_errors is True and the command failed - :rtype: string + :return: output of cmd, or stderr, if ignore_errors is True and the command failed; or a tuple of exitcode and the previous, if return_exitcode is set to True + :rtype: string or tuple (if return_exitcode is set to True) """ args = shlex.split(cmd) logging.debug(cmd) @@ -35,10 +36,11 @@ def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None) if proc.returncode != 0: err = "{} exited with code {}".format(cmd, proc.returncode) if ignore_errors: - return err + return (proc.returncode, err) if return_exitcode else err raise RuntimeError(err) - return out.decode("utf-8") - return "" + res = out.decode("utf-8") + return (proc.returncode, res) if return_exitcode else res + return (0, "") if return_exitcode else "" # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From eef2ab31411cdbbcf0411dbe736610f9af7955b6 Mon Sep 17 00:00:00 2001 From: LtPeriwinkle Date: Mon, 11 May 2020 15:49:45 -0700 Subject: [PATCH 10/27] Create night-powerline.json similar to grayish theme, but muted the good/warning/bad colors so they are less distracting and added a third cycle --- themes/night-powerline.json | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 themes/night-powerline.json diff --git a/themes/night-powerline.json b/themes/night-powerline.json new file mode 100644 index 0000000..01929b9 --- /dev/null +++ b/themes/night-powerline.json @@ -0,0 +1,63 @@ +{ + "icons": [ "awesome-fonts" ], + "defaults": { + "separator-block-width": 0, + "warning": { + "fg": "#afafaf", + "bg": "#4d401d" + }, + "critical": { + "fg": "#afafaf", + "bg": "#6e0b0a" + } + }, + "cycle": [ + { "fg": "#afafaf", "bg": "#0f0f0f" }, + { "fg": "#afafaf", "bg": "#1f1f1f" }, + { "fg": "#afafaf", "bg": "#2b2b2b" }, + { "fg": "#afafaf", "bg": "#1e1e1e" } + ], + "dnf": { + "good": { + "fg": "#afafaf", + "bg": "#26362d" + } + }, + "apt": { + "good": { + "fg": "#afafaf", + "bg": "#26362d" + } + }, + "pacman": { + "good": { + "fg": "#b2b2b2", + "bg": "#26362d" + } + }, + "battery": { + "charged": { + "fg": "#afafaf", + "bg": "#26362d" + }, + "AC": { + "fg": "#afafaf", + "bg": "#26362d" + } + }, + "pomodoro": { + "paused": { + "fg": "#afafaf", + "bg": "#b58900" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#afafaf", + "bg": "#26362d" + } + } + +} From a964abaa11e236f6b0e0751d725df79555143eb8 Mon Sep 17 00:00:00 2001 From: LtPeriwinkle Date: Mon, 11 May 2020 15:50:52 -0700 Subject: [PATCH 11/27] Revert "Create night-powerline.json" This reverts commit eef2ab31411cdbbcf0411dbe736610f9af7955b6. --- themes/night-powerline.json | 63 ------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 themes/night-powerline.json diff --git a/themes/night-powerline.json b/themes/night-powerline.json deleted file mode 100644 index 01929b9..0000000 --- a/themes/night-powerline.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "icons": [ "awesome-fonts" ], - "defaults": { - "separator-block-width": 0, - "warning": { - "fg": "#afafaf", - "bg": "#4d401d" - }, - "critical": { - "fg": "#afafaf", - "bg": "#6e0b0a" - } - }, - "cycle": [ - { "fg": "#afafaf", "bg": "#0f0f0f" }, - { "fg": "#afafaf", "bg": "#1f1f1f" }, - { "fg": "#afafaf", "bg": "#2b2b2b" }, - { "fg": "#afafaf", "bg": "#1e1e1e" } - ], - "dnf": { - "good": { - "fg": "#afafaf", - "bg": "#26362d" - } - }, - "apt": { - "good": { - "fg": "#afafaf", - "bg": "#26362d" - } - }, - "pacman": { - "good": { - "fg": "#b2b2b2", - "bg": "#26362d" - } - }, - "battery": { - "charged": { - "fg": "#afafaf", - "bg": "#26362d" - }, - "AC": { - "fg": "#afafaf", - "bg": "#26362d" - } - }, - "pomodoro": { - "paused": { - "fg": "#afafaf", - "bg": "#b58900" - }, - "work": { - "fg": "#1d2021", - "bg": "#b8bb26" - }, - "break": { - "fg": "#afafaf", - "bg": "#26362d" - } - } - -} From 0123bb83f9d9b90268a580290b41a5161ccc1245 Mon Sep 17 00:00:00 2001 From: LtPeriwinkle Date: Mon, 11 May 2020 15:54:54 -0700 Subject: [PATCH 12/27] Create night-powerline.json similar to greyish-powerline but made the good/warning/bad colors less obtrusive and added a third cycle color --- themes/night-powerline.json | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 themes/night-powerline.json diff --git a/themes/night-powerline.json b/themes/night-powerline.json new file mode 100644 index 0000000..01929b9 --- /dev/null +++ b/themes/night-powerline.json @@ -0,0 +1,63 @@ +{ + "icons": [ "awesome-fonts" ], + "defaults": { + "separator-block-width": 0, + "warning": { + "fg": "#afafaf", + "bg": "#4d401d" + }, + "critical": { + "fg": "#afafaf", + "bg": "#6e0b0a" + } + }, + "cycle": [ + { "fg": "#afafaf", "bg": "#0f0f0f" }, + { "fg": "#afafaf", "bg": "#1f1f1f" }, + { "fg": "#afafaf", "bg": "#2b2b2b" }, + { "fg": "#afafaf", "bg": "#1e1e1e" } + ], + "dnf": { + "good": { + "fg": "#afafaf", + "bg": "#26362d" + } + }, + "apt": { + "good": { + "fg": "#afafaf", + "bg": "#26362d" + } + }, + "pacman": { + "good": { + "fg": "#b2b2b2", + "bg": "#26362d" + } + }, + "battery": { + "charged": { + "fg": "#afafaf", + "bg": "#26362d" + }, + "AC": { + "fg": "#afafaf", + "bg": "#26362d" + } + }, + "pomodoro": { + "paused": { + "fg": "#afafaf", + "bg": "#b58900" + }, + "work": { + "fg": "#1d2021", + "bg": "#b8bb26" + }, + "break": { + "fg": "#afafaf", + "bg": "#26362d" + } + } + +} From 583d76f2202fc280365d5a27f595f6b0d1473b8c Mon Sep 17 00:00:00 2001 From: LtPeriwinkle Date: Mon, 11 May 2020 16:03:22 -0700 Subject: [PATCH 13/27] Create night-powerline.png screenshot for night-powerline theme --- screenshots/themes/night-powerline.png | Bin 0 -> 11974 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 screenshots/themes/night-powerline.png diff --git a/screenshots/themes/night-powerline.png b/screenshots/themes/night-powerline.png new file mode 100644 index 0000000000000000000000000000000000000000..dca9d6560c33302ad7ae0688381b1c8819122fde GIT binary patch literal 11974 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 Date: Tue, 12 May 2020 07:14:18 +0200 Subject: [PATCH 14/27] [docs] add night-powerline --- docs/themes.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/themes.rst b/docs/themes.rst index fa0493e..759ea86 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -88,6 +88,11 @@ List of available themes Nord Powerline (-t nord-powerline) (contributed by `uselessthird `__) +.. figure:: ../screenshots/themes/night-powerline.png + :alt: Night Powerline + + Night Powerline (-t night-powerline) (contributed by `LtPeriwinkle `__) + .. figure:: ../screenshots/themes/default.png :alt: Default From 526560ea54eed31af311df0f16f5314e860a7e80 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 14 May 2020 20:35:09 +0200 Subject: [PATCH 15/27] [core/decorators] use difflib to make scrolling reset detection fuzzy when scrolling text that is subject to *slight* changes (e.g. a song that contains the current position within the song), allow for slight variations in the displayed text. fixes #629 --- bumblebee_status/core/decorators.py | 6 +++++- tests/core/test_decorators.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/core/decorators.py b/bumblebee_status/core/decorators.py index 8c87308..5b8619e 100644 --- a/bumblebee_status/core/decorators.py +++ b/bumblebee_status/core/decorators.py @@ -1,3 +1,4 @@ +import difflib import util.format @@ -28,7 +29,10 @@ def scrollable(func): if not text: return text - if text != widget.get("__content__", text): + if ( + difflib.SequenceMatcher(a=text, b=widget.get("__content__", text)).ratio() + < 0.9 + ): widget.set("scrolling.start", 0) widget.set("scrolling.direction", "right") widget.set("__content__", text) diff --git a/tests/core/test_decorators.py b/tests/core/test_decorators.py index 9ad7986..8b30338 100644 --- a/tests/core/test_decorators.py +++ b/tests/core/test_decorators.py @@ -70,5 +70,16 @@ class config(unittest.TestCase): self.module.text = "wxyz" self.assertEqual("wx", self.module.get(self.widget)) + def test_minimum_changed_data(self): + self.module.text = "this is a sample song (0:00)" + self.module.set("scrolling.width", 10) + self.assertEqual(self.module.text[0:10], self.module.get(self.widget)) + self.module.text = "this is a sample song (0:01)" + self.assertEqual(self.module.text[1:11], self.module.get(self.widget)) + self.module.text = "this is a sample song (0:12)" + self.assertEqual(self.module.text[2:12], self.module.get(self.widget)) + self.module.text = "this is a different song (0:12)" + self.assertEqual(self.module.text[0:10], self.module.get(self.widget)) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From bc382e67d347be587c8b9792c3e077061e089ca5 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Thu, 14 May 2020 20:36:01 +0200 Subject: [PATCH 16/27] [general] black code some more --- bumblebee-status | 1 + bumblebee_status/modules/contrib/arch-update.py | 4 +++- bumblebee_status/util/cli.py | 9 ++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/bumblebee-status b/bumblebee-status index ad9c67f..dcaa8bd 100755 --- a/bumblebee-status +++ b/bumblebee-status @@ -10,6 +10,7 @@ import logging import threading import bumblebee_status.discover + bumblebee_status.discover.discover() import core.config diff --git a/bumblebee_status/modules/contrib/arch-update.py b/bumblebee_status/modules/contrib/arch-update.py index a1ea550..990cdbb 100644 --- a/bumblebee_status/modules/contrib/arch-update.py +++ b/bumblebee_status/modules/contrib/arch-update.py @@ -34,7 +34,9 @@ class Module(core.module.Module): def update(self): self.__error = False - code, result = util.cli.execute("checkupdates", ignore_errors=True, return_exitcode=True) + code, result = util.cli.execute( + "checkupdates", ignore_errors=True, return_exitcode=True + ) if code == 0: self.__packages = len(result.split("\n")) diff --git a/bumblebee_status/util/cli.py b/bumblebee_status/util/cli.py index 31a2955..209291f 100644 --- a/bumblebee_status/util/cli.py +++ b/bumblebee_status/util/cli.py @@ -4,7 +4,14 @@ import subprocess import logging -def execute(cmd, wait=True, ignore_errors=False, include_stderr=False, env=None, return_exitcode=False): +def execute( + cmd, + wait=True, + ignore_errors=False, + include_stderr=False, + env=None, + return_exitcode=False, +): """Executes a commandline utility and returns its output :param cmd: the command (as string) to execute, returns the program's output From 1b53e7ecf21034904a08fe884c62dc785fbd005d Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 15 May 2020 10:07:07 +0200 Subject: [PATCH 17/27] [util/popup] add docstrings --- bumblebee_status/util/popup.py | 53 +++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/bumblebee_status/util/popup.py b/bumblebee_status/util/popup.py index 23f3587..dee67f6 100644 --- a/bumblebee_status/util/popup.py +++ b/bumblebee_status/util/popup.py @@ -2,51 +2,82 @@ import logging -try: - import Tkinter as tk -except ImportError: - # python 3 - import tkinter as tk +import tkinter as tk import functools class menu(object): + """Draws a hierarchical popup menu + + :param parent: If given, this menu is a leave of the "parent" menu + :param leave: If set to True, close this menu when mouse leaves the area (defaults to True) + """ + def __init__(self, parent=None, leave=True): if not parent: self._root = tk.Tk() self._root.withdraw() self._menu = tk.Menu(self._root, tearoff=0) - self._menu.bind("", self._on_focus_out) + self._menu.bind("", self.__on_focus_out) else: self._root = parent.root() self._root.withdraw() self._menu = tk.Menu(self._root, tearoff=0) - self._menu.bind("", self._on_focus_out) + self._menu.bind("", self.__on_focus_out) if leave: - self._menu.bind("", self._on_focus_out) + self._menu.bind("", self.__on_focus_out) + + """Returns the root node of this menu + + :return: root node + """ def root(self): return self._root + """Returns the menu + + :return: menu + """ + def menu(self): return self._menu - def _on_focus_out(self, event=None): + def __on_focus_out(self, event=None): self._root.destroy() - def _on_click(self, callback): + def __on_click(self, callback): self._root.destroy() callback() + """Adds a cascading submenu to the current menu + + :param menuitem: label to display for the submenu + :param submenu: submenu to show + """ + def add_cascade(self, menuitem, submenu): self._menu.add_cascade(label=menuitem, menu=submenu.menu()) + """Adds an item to the current menu + + :param menuitem: label to display for the entry + :param callback: method to invoke on click + """ + def add_menuitem(self, menuitem, callback): self._menu.add_command( - label=menuitem, command=functools.partial(self._on_click, callback) + label=menuitem, command=functools.partial(self.__on_click, callback) ) + """Shows this menu + + :param event: i3wm event that triggered the menu (dict that contains "x" and "y" fields) + :param offset_x: x-axis offset from mouse position for the menu (defaults to 0) + :param offset_y: y-axis offset from mouse position for the menu (defaults to 0) + """ + def show(self, event, offset_x=0, offset_y=0): try: self._menu.tk_popup(event["x"] + offset_x, event["y"] + offset_y) From 835ed070e5304f34018aed417a6f5fa53d7f59b3 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 15 May 2020 10:22:10 +0200 Subject: [PATCH 18/27] [core/module] Add docstrings --- bumblebee_status/core/module.py | 109 +++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/bumblebee_status/core/module.py b/bumblebee_status/core/module.py index eb15833..d8faa1c 100644 --- a/bumblebee_status/core/module.py +++ b/bumblebee_status/core/module.py @@ -15,6 +15,17 @@ except Exception as e: log = logging.getLogger(__name__) +"""Loads a module by name + +:param module_name: Name of the module to load +:param config: Configuration to apply to the module (defaults to an empty configuration) +:param theme: Theme for this module, defaults to None, which means whatever is configured in "config" + +:return: A module object representing the module, or an Error module if loading failed +:rtype: class bumblebee_status.module.Module +""" + + def load(module_name, config=core.config.Config([]), theme=None): error = None module_short, alias = (module_name.split(":") + [module_name])[0:2] @@ -36,6 +47,13 @@ def load(module_name, config=core.config.Config([]), theme=None): class Module(core.input.Object): + """Represents a module (single piece of functionality) of the bar + + :param config: Configuration to apply to the module (defaults to an empty configuration) + :param theme: Theme for this module, defaults to None, which means whatever is configured in "config" + :param widgets: A list of bumblebee_status.widget.Widget objects that the module is comprised of + """ + def __init__(self, config=core.config.Config([]), theme=None, widgets=[]): super().__init__() self.__config = config @@ -52,23 +70,55 @@ class Module(core.input.Object): for widget in self.__widgets: widget.module = self + """Override this to determine when to show this module + + :return: True if the module should be hidden, False otherwise + :rtype: boolean + """ + def hidden(self): return False + """Retrieve CLI/configuration parameters for this module. For example, if + the module is called "test" and the user specifies "-p test.x=123" on the + commandline, using self.parameter("x") retrieves the value 123. + + :param key: Name of the parameter to retrieve + :param default: Default value, if parameter is not set by user (defaults to None) + + :return: Parameter value, or default + :rtype: string + """ + def parameter(self, key, default=None): value = default for prefix in [self.name, self.module_name, self.alias]: value = self.__config.get("{}.{}".format(prefix, key), value) - # TODO retrieve from config file return value + """Set a parameter for this module + + :param key: Name of the parameter to set + :param value: New value of the parameter + """ + def set(self, key, value): self.__config.set("{}.{}".format(self.name, key), value) + """Override this method to define tasks that should be done during each + update interval (for instance, querying an API, calling a CLI tool to get new + date, etc. + """ + def update(self): pass + """Wrapper method that ensures that all exceptions thrown by the + update() method are caught and displayed in a bumblebee_status.module.Error + module + """ + def update_wrapper(self): try: self.update() @@ -78,17 +128,42 @@ class Module(core.input.Object): self.__widgets = [module.widget()] self.update = module.update + """Retrieves the list of widgets for this module + + :return: A list of widgets + :rtype: list of bumblebee_status.widget.Widgets + """ + def widgets(self): return self.__widgets + """Removes all widgets of this module""" + def clear_widgets(self): del self.__widgets[:] + """Adds a widget to the module + + :param full_text: Text or callable (method) that defines the text of the widget (defaults to "") + :param name: Name of the widget, defaults to None, which means autogenerate + + :return: The new widget + :rtype: bumblebee_status.widget.Widget + """ + def add_widget(self, full_text="", name=None): widget = core.widget.Widget(full_text=full_text, name=name, module=self) self.widgets().append(widget) return widget + """Convenience method to retrieve a named widget + + :param name: Name of widget to retrieve, defaults to None (in which case the first widget is returned) + + :return: The widget with the corresponding name, None if not found + :rtype: bumblebee_status.widget.Widget + """ + def widget(self, name=None): if not name: return self.widgets()[0] @@ -98,9 +173,27 @@ class Module(core.input.Object): return w return None + """Override this method to define states for the module + + :param widget: Widget for which state should be returned + + :return: a list of states for this widget + :rtype: list of strings + """ + def state(self, widget): return [] + """Convenience method that sets warning and critical state for numbers + + :param value: Current value to calculate state against + :param warn: Warning threshold + :parm crit: Critical threshold + + :return: None if value is below both thresholds, "critical", "warning" as appropriate otherwise + :rtype: string + """ + def threshold_state(self, value, warn, crit): if value > float(self.parameter("critical", crit)): return "critical" @@ -110,14 +203,28 @@ class Module(core.input.Object): class Error(Module): + """Represents an "error" module + + :param module: The module name that produced the error + :param error: The error message to display + :param config: Configuration to apply to the module (defaults to an empty configuration) + :param theme: Theme for this module, defaults to None, which means whatever is configured in "config" + """ + def __init__(self, module, error, config=core.config.Config([]), theme=None): super().__init__(config, theme, core.widget.Widget(self.full_text)) self.__module = module self.__error = error + """Returns the error message + :param widget: the error widget to display + """ + def full_text(self, widget): return "{}: {}".format(self.__module, self.__error) + """Overriden state, always returns critical (it *is* an error, after all""" + def state(self, widget): return ["critical"] From bfe5d8f493694a813fb503b147f2ae4655aaeceb Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 15 May 2020 19:48:10 +0200 Subject: [PATCH 19/27] [module/battery] exception typo see #628 --- bumblebee_status/modules/contrib/battery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumblebee_status/modules/contrib/battery.py b/bumblebee_status/modules/contrib/battery.py index d11358e..512866d 100644 --- a/bumblebee_status/modules/contrib/battery.py +++ b/bumblebee_status/modules/contrib/battery.py @@ -115,7 +115,7 @@ class Module(core.module.Module): for battery in glob.glob("/sys/class/power_supply/BAT*") ] if len(self._batteries) == 0: - raise Exceptions("no batteries configured/found") + raise Exception("no batteries configured/found") core.input.register( self, button=core.input.LEFT_MOUSE, cmd="gnome-power-statistics" ) From 3bb2fb82479aa28d3037f4229def52e9db5df6eb Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Fri, 15 May 2020 19:59:13 +0200 Subject: [PATCH 20/27] [core/module] add missing registration of input events via CLI see #628 --- bumblebee-status | 1 + bumblebee_status/core/module.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/bumblebee-status b/bumblebee-status index dcaa8bd..9e4ea82 100755 --- a/bumblebee-status +++ b/bumblebee-status @@ -102,6 +102,7 @@ def main(): for module in config.modules(): modules.append(core.module.load(module, config, theme)) + modules[-1].register_callbacks() if config.reverse(): modules.reverse() diff --git a/bumblebee_status/core/module.py b/bumblebee_status/core/module.py index d8faa1c..e01d652 100644 --- a/bumblebee_status/core/module.py +++ b/bumblebee_status/core/module.py @@ -201,6 +201,18 @@ class Module(core.input.Object): return "warning" return None + def register_callbacks(self): + actions = [ + { "name": "left-click", "id": core.input.LEFT_MOUSE }, + { "name": "right-click", "id": core.input.RIGHT_MOUSE }, + { "name": "middle-click", "id": core.input.MIDDLE_MOUSE }, + { "name": "wheel-up", "id": core.input.WHEEL_UP }, + { "name": "wheel-down", "id": core.input.WHEEL_DOWN }, + ] + for action in actions: + if self.parameter(action["name"]): + core.input.register(self, action["id"], self.parameter(action["name"])) + class Error(Module): """Represents an "error" module From a8df4a5f9d8bff9881ee0a2009e7f5fb37d5b2b5 Mon Sep 17 00:00:00 2001 From: Pavle Portic Date: Fri, 15 May 2020 20:31:00 +0200 Subject: [PATCH 21/27] Handle n+1 characters in the scrolling decorator --- .gitignore | 3 +++ bumblebee_status/core/decorators.py | 9 +++++++-- tests/core/test_decorators.py | 9 +++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4996eaa..2bf1ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,6 @@ ENV/ # Visual studio project files .vscode/ + +# mypy cache +.mypy_cache diff --git a/bumblebee_status/core/decorators.py b/bumblebee_status/core/decorators.py index 5b8619e..b117f7b 100644 --- a/bumblebee_status/core/decorators.py +++ b/bumblebee_status/core/decorators.py @@ -1,6 +1,10 @@ import difflib +import logging + import util.format +log = logging.getLogger(__name__) + def never(init): def call_init(obj, *args, **kwargs): @@ -49,9 +53,10 @@ def scrollable(func): direction = widget.get("scrolling.direction", "right") if direction == "left": - scroll_speed = -scroll_speed - if start + scroll_speed <= 0: # bounce back + if start - scroll_speed < 0: # bounce back widget.set("scrolling.direction", "right") + else: + scroll_speed = -scroll_speed next_start = start + scroll_speed if next_start + width > len(text): diff --git a/tests/core/test_decorators.py b/tests/core/test_decorators.py index 8b30338..4ad1410 100644 --- a/tests/core/test_decorators.py +++ b/tests/core/test_decorators.py @@ -81,5 +81,14 @@ class config(unittest.TestCase): self.module.text = "this is a different song (0:12)" self.assertEqual(self.module.text[0:10], self.module.get(self.widget)) + def test_n_plus_one(self): + self.module.text = "10 letters" + self.module.set("scrolling.width", 9) + self.assertEqual(self.module.text[0:9], self.module.get(self.widget)) + self.assertEqual(self.module.text[1:10], self.module.get(self.widget)) + self.assertEqual(self.module.text[0:9], self.module.get(self.widget)) + self.assertEqual(self.module.text[1:10], self.module.get(self.widget)) + self.assertEqual(self.module.text[0:9], self.module.get(self.widget)) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From eea3c758dedc1f5c24da3e6a7108019bbb252a39 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 16 May 2020 11:41:34 +0200 Subject: [PATCH 22/27] [core/input] Invoke commands in a subshell add shell capability to util.cli and make sure that the input module uses that to reliably spawn whatever command the user wants to run. see #628 --- bumblebee_status/core/input.py | 2 +- bumblebee_status/util/cli.py | 5 ++++- tests/core/test_input.py | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bumblebee_status/core/input.py b/bumblebee_status/core/input.py index 66519d5..71d481e 100644 --- a/bumblebee_status/core/input.py +++ b/bumblebee_status/core/input.py @@ -38,7 +38,7 @@ def __event_id(obj_id, button): def __execute(cmd): try: - util.cli.execute(cmd, wait=False) + util.cli.execute(cmd, wait=False, shell=True) except Exception as e: logging.error("failed to invoke callback: {}".format(e)) diff --git a/bumblebee_status/util/cli.py b/bumblebee_status/util/cli.py index 209291f..2b07c75 100644 --- a/bumblebee_status/util/cli.py +++ b/bumblebee_status/util/cli.py @@ -11,6 +11,7 @@ def execute( include_stderr=False, env=None, return_exitcode=False, + shell=False, ): """Executes a commandline utility and returns its output @@ -20,13 +21,14 @@ def execute( :param include_stderr: set to True to include stderr output in the return value, defaults to False :param env: provide a dict here to specify a custom execution environment, defaults to None :param return_exitcode: set to True to return a pair, where the first member is the exit code and the message the second, defaults to False + :param shell: set to True to run command in a separate shell, defaults to False :raises RuntimeError: the command either didn't exist or didn't exit cleanly, and ignore_errors was set to False :return: output of cmd, or stderr, if ignore_errors is True and the command failed; or a tuple of exitcode and the previous, if return_exitcode is set to True :rtype: string or tuple (if return_exitcode is set to True) """ - args = shlex.split(cmd) + args = cmd if shell else shlex.split(cmd) logging.debug(cmd) try: proc = subprocess.Popen( @@ -34,6 +36,7 @@ def execute( stdout=subprocess.PIPE, stderr=subprocess.STDOUT if include_stderr else subprocess.PIPE, env=env, + shell=shell, ) except FileNotFoundError as e: raise RuntimeError("{} not found".format(cmd)) diff --git a/tests/core/test_input.py b/tests/core/test_input.py index 667184a..ba5641e 100644 --- a/tests/core/test_input.py +++ b/tests/core/test_input.py @@ -70,7 +70,9 @@ class config(unittest.TestCase): self.inputObject, self.someEvent["button"], self.someCommand ) core.input.trigger(self.someEvent) - cli.execute.assert_called_once_with(self.someCommand, wait=False) + cli.execute.assert_called_once_with( + self.someCommand, wait=False, shell=True + ) def test_non_existent_callback(self): with unittest.mock.patch("core.input.util.cli") as cli: From 7584366adc5f489bb5b73d0bb1be249d24735045 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 16 May 2020 11:55:25 +0200 Subject: [PATCH 23/27] [tests] add more tests --- docs/other/NOTES.md | 1 + tests/core/test_config.py | 4 ++-- tests/core/test_decorators.py | 6 +++++- tests/core/test_module.py | 20 ++++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/other/NOTES.md b/docs/other/NOTES.md index 1b4e699..3ba0c14 100644 --- a/docs/other/NOTES.md +++ b/docs/other/NOTES.md @@ -14,6 +14,7 @@ - themes: use colors to improve theme readability - convert some stuff to simple attributes to reduce LOCs - use widget index for bumblebee-ctl as alternative (??) +- use pytest? # documentation Add info about error widget and events for error logging diff --git a/tests/core/test_config.py b/tests/core/test_config.py index 27931ff..e468f3a 100644 --- a/tests/core/test_config.py +++ b/tests/core/test_config.py @@ -52,8 +52,8 @@ class config(unittest.TestCase): def test_logfile(self): cfg = core.config.Config(["-f", "my-custom-logfile"]) - self.assertEquals(None, self.defaultConfig.logfile()) - self.assertEquals("my-custom-logfile", cfg.logfile()) + self.assertEqual(None, self.defaultConfig.logfile()) + self.assertEqual("my-custom-logfile", cfg.logfile()) def test_all_modules(self): modules = core.config.all_modules() diff --git a/tests/core/test_decorators.py b/tests/core/test_decorators.py index 4ad1410..e9c755a 100644 --- a/tests/core/test_decorators.py +++ b/tests/core/test_decorators.py @@ -7,6 +7,7 @@ import core.config class TestModule(core.module.Module): + @core.decorators.never def __init__(self, config=None, theme=None): config = core.config.Config([]) super().__init__(config, theme, core.widget.Widget(self.get)) @@ -16,7 +17,6 @@ class TestModule(core.module.Module): def get(self, widget): return self.text - class config(unittest.TestCase): def setUp(self): self.module = TestModule() @@ -24,6 +24,10 @@ class config(unittest.TestCase): self.width = 10 self.module.set("scrolling.width", self.width) + def test_never(self): + self.module = TestModule() + self.assertEqual("never", self.module.parameter("interval")) + def test_no_text(self): self.assertEqual("", self.module.text) self.assertEqual("", self.module.get(self.widget)) diff --git a/tests/core/test_module.py b/tests/core/test_module.py index 71f59ad..aad7ca5 100644 --- a/tests/core/test_module.py +++ b/tests/core/test_module.py @@ -6,6 +6,7 @@ import shlex import core.module import core.widget import core.config +import core.input class TestModule(core.module.Module): @@ -137,5 +138,24 @@ class module(unittest.TestCase): self.assertEqual(None, module.threshold_state(80, 80, 100)) self.assertEqual(None, module.threshold_state(10, 80, 100)) + def test_configured_callbacks(self): + cfg = core.config.Config([]) + module = TestModule(config=cfg, widgets=[self.someWidget, self.anotherWidget]) + + cmd = "sample-tool arg1 arg2 arg3" + module.set("left-click", cmd) + module.register_callbacks() + + with unittest.mock.patch("core.input.util.cli") as cli: + cli.execute.return_value = "" + core.input.trigger({ + "button": core.input.LEFT_MOUSE, + "instance": module.id, + }) + + cli.execute.assert_called_once_with( + cmd, wait=False, shell=True + ) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From ef4d87af8e52751fa3b6a87fb2977f6920c4fa75 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 16 May 2020 12:00:48 +0200 Subject: [PATCH 24/27] [CI] update CI config --- .codeclimate.yml | 1 - .travis.yml | 10 +--------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index d0c140e..ec80874 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -13,4 +13,3 @@ ratings: - "**.py" exclude_paths: - tests/ -- thirdparty/ diff --git a/.travis.yml b/.travis.yml index 0e08cf9..78eff09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,17 +8,9 @@ python: - "3.8" before_install: - sudo apt-get -qq update - - sudo apt-get install -y task libdbus-1-dev install: - - pip install i3ipc - - pip install psutil - - pip install netifaces - - pip install -U coverage==4.3 + - pip install coverage - pip install codeclimate-test-reporter - - pip install taskw - - pip install pytz - - pip install tzlocal - - pip install dbus-python - pip install coverage script: - coverage run -m unittest discover -v From 02a80840a13d3a355bb1b828e3f01bf3f909db15 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 16 May 2020 12:00:55 +0200 Subject: [PATCH 25/27] [core] more black'ing --- bumblebee_status/core/module.py | 10 +++++----- tests/core/test_decorators.py | 1 + tests/core/test_module.py | 11 ++++------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/bumblebee_status/core/module.py b/bumblebee_status/core/module.py index e01d652..81f2604 100644 --- a/bumblebee_status/core/module.py +++ b/bumblebee_status/core/module.py @@ -203,11 +203,11 @@ class Module(core.input.Object): def register_callbacks(self): actions = [ - { "name": "left-click", "id": core.input.LEFT_MOUSE }, - { "name": "right-click", "id": core.input.RIGHT_MOUSE }, - { "name": "middle-click", "id": core.input.MIDDLE_MOUSE }, - { "name": "wheel-up", "id": core.input.WHEEL_UP }, - { "name": "wheel-down", "id": core.input.WHEEL_DOWN }, + {"name": "left-click", "id": core.input.LEFT_MOUSE}, + {"name": "right-click", "id": core.input.RIGHT_MOUSE}, + {"name": "middle-click", "id": core.input.MIDDLE_MOUSE}, + {"name": "wheel-up", "id": core.input.WHEEL_UP}, + {"name": "wheel-down", "id": core.input.WHEEL_DOWN}, ] for action in actions: if self.parameter(action["name"]): diff --git a/tests/core/test_decorators.py b/tests/core/test_decorators.py index e9c755a..ab924da 100644 --- a/tests/core/test_decorators.py +++ b/tests/core/test_decorators.py @@ -17,6 +17,7 @@ class TestModule(core.module.Module): def get(self, widget): return self.text + class config(unittest.TestCase): def setUp(self): self.module = TestModule() diff --git a/tests/core/test_module.py b/tests/core/test_module.py index aad7ca5..7628aac 100644 --- a/tests/core/test_module.py +++ b/tests/core/test_module.py @@ -148,14 +148,11 @@ class module(unittest.TestCase): with unittest.mock.patch("core.input.util.cli") as cli: cli.execute.return_value = "" - core.input.trigger({ - "button": core.input.LEFT_MOUSE, - "instance": module.id, - }) - - cli.execute.assert_called_once_with( - cmd, wait=False, shell=True + core.input.trigger( + {"button": core.input.LEFT_MOUSE, "instance": module.id,} ) + cli.execute.assert_called_once_with(cmd, wait=False, shell=True) + # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 From a697500491febf4dc5cfea531ae21f8703c130d8 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 16 May 2020 15:16:23 +0200 Subject: [PATCH 26/27] [core] add debugging around click events log commandline outputs and errors. allow input handlers to be configured as "waiting" for debugging purposes. see #628 --- bumblebee_status/core/input.py | 8 ++++---- bumblebee_status/core/module.py | 11 ++++++++++- bumblebee_status/util/cli.py | 2 ++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/bumblebee_status/core/input.py b/bumblebee_status/core/input.py index 71d481e..b775091 100644 --- a/bumblebee_status/core/input.py +++ b/bumblebee_status/core/input.py @@ -36,20 +36,20 @@ def __event_id(obj_id, button): return "{}::{}".format(obj_id, button_name(button)) -def __execute(cmd): +def __execute(cmd, wait=False): try: - util.cli.execute(cmd, wait=False, shell=True) + util.cli.execute(cmd, wait=wait, shell=True) except Exception as e: logging.error("failed to invoke callback: {}".format(e)) -def register(obj, button=None, cmd=None): +def register(obj, button=None, cmd=None, wait=False): event_id = __event_id(obj.id if obj is not None else "", button) logging.debug("registering callback {}".format(event_id)) if callable(cmd): core.event.register(event_id, cmd) else: - core.event.register(event_id, lambda _: __execute(cmd)) + core.event.register(event_id, lambda _: __execute(cmd, wait)) def trigger(event): diff --git a/bumblebee_status/core/module.py b/bumblebee_status/core/module.py index 81f2604..ba06848 100644 --- a/bumblebee_status/core/module.py +++ b/bumblebee_status/core/module.py @@ -7,6 +7,8 @@ import core.input import core.widget import core.decorators +import util.format + try: error = ModuleNotFoundError("") except Exception as e: @@ -211,7 +213,14 @@ class Module(core.input.Object): ] for action in actions: if self.parameter(action["name"]): - core.input.register(self, action["id"], self.parameter(action["name"])) + core.input.register( + self, + action["id"], + self.parameter(action["name"]), + util.format.asbool( + self.parameter("{}-wait".format(action["name"]), False) + ), + ) class Error(Module): diff --git a/bumblebee_status/util/cli.py b/bumblebee_status/util/cli.py index 2b07c75..3e4face 100644 --- a/bumblebee_status/util/cli.py +++ b/bumblebee_status/util/cli.py @@ -45,10 +45,12 @@ def execute( out, _ = proc.communicate() if proc.returncode != 0: err = "{} exited with code {}".format(cmd, proc.returncode) + logging.warning(err) if ignore_errors: return (proc.returncode, err) if return_exitcode else err raise RuntimeError(err) res = out.decode("utf-8") + logging.debug(res) return (proc.returncode, res) if return_exitcode else res return (0, "") if return_exitcode else "" From 1355a7e5d30720de325291c6f0b699f055c35852 Mon Sep 17 00:00:00 2001 From: tobi-wan-kenobi Date: Sat, 16 May 2020 15:28:15 +0200 Subject: [PATCH 27/27] [core/decorators] add docstrings --- bumblebee_status/core/decorators.py | 33 +++++++++++++++++++++++++++++ docs/other/NOTES.md | 1 + 2 files changed, 34 insertions(+) diff --git a/bumblebee_status/core/decorators.py b/bumblebee_status/core/decorators.py index b117f7b..8c60c70 100644 --- a/bumblebee_status/core/decorators.py +++ b/bumblebee_status/core/decorators.py @@ -6,6 +6,15 @@ import util.format log = logging.getLogger(__name__) +"""Specifies that a module should never update (i.e. has static content). +This means that its update() method will never be invoked + +:param init: The __init__() method of the module + +:return: Wrapped method that sets the module's interval to "never" +""" + + def never(init): def call_init(obj, *args, **kwargs): init(obj, *args, **kwargs) @@ -15,6 +24,16 @@ def never(init): return call_init +"""Specifies the interval for executing the module's update() method + +:param hours: Hours between two update() invocations, defaults to 0 +:param minutes: Minutes between two update() invocations, defaults to 0 +:param seconds: Seconds between two update() invocations, defaults to 0 + +:return: Wrapped method that sets the module's interval correspondingly +""" + + def every(hours=0, minutes=0, seconds=0): def decorator_init(init): def call_init(obj, *args, **kwargs): @@ -27,6 +46,20 @@ def every(hours=0, minutes=0, seconds=0): return decorator_init +"""Specifies that the module's content should scroll, if required + +The exact behaviour of this method is governed by a number of parameters, +specifically: The module's parameter "scrolling.width" specifies the width when +scrolling starts, "scrolling.makewide" defines whether the module should be expanded +to "scrolling.width" automatically, if the content is shorter, the parameter +"scrolling.bounce" defines whether it scrolls like a marquee (False) or should bounce +when the end of the content is reached. "scrolling.speed" defines the number of characters +to scroll each iteration. + +:param func: Function for which the result should be scrolled +""" + + def scrollable(func): def wrapper(module, widget): text = func(module, widget) diff --git a/docs/other/NOTES.md b/docs/other/NOTES.md index 3ba0c14..4ff62c4 100644 --- a/docs/other/NOTES.md +++ b/docs/other/NOTES.md @@ -9,6 +9,7 @@ - use __ for private ## Improvements +- app launcher (list of apps, themeable) ## TODO - themes: use colors to improve theme readability