From be7e733605ad0e8dcbc1c78d4ebb6cf4d3e6a372 Mon Sep 17 00:00:00 2001 From: enkore Date: Sun, 7 Jun 2015 20:27:11 +0200 Subject: [PATCH 01/32] Update README.rst --- README.rst | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 3d7e823..21c399f 100644 --- a/README.rst +++ b/README.rst @@ -110,16 +110,13 @@ Contributors Contribute ---------- -To contribute a module, make sure it uses one of the Module classes. Most modules -use IntervalModule, which just calls a function repeatedly in a specified interval. +To contribute a module, make sure it uses one of the ``Module`` classes. Most modules +use ``IntervalModule``, which just calls a function repeatedly in a specified interval. -The output attribute should be set to a dictionary which represents your modules output, +The ``output`` attribute should be set to a dictionary which represents your modules output, the protocol is documented `here `_. -To update this readme run ``python -m i3pystatus.mkdocs`` in the -repository root and you're done :) - Developer documentation is available in the source code and `here -`_. +`_. **Patches and pull requests are very welcome :-)** From 362991ac6678546c6005ebfe629c55a38e53f29e Mon Sep 17 00:00:00 2001 From: enkore Date: Sun, 7 Jun 2015 20:27:57 +0200 Subject: [PATCH 02/32] README: (pretty confident it's growing :-) --- README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 21c399f..21f0784 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,3 @@ -.. Always edit README.tpl.rst. Do not change the module reference manually. - i3pystatus ========== @@ -9,7 +7,7 @@ i3pystatus .. image:: https://travis-ci.org/enkore/i3pystatus.svg?branch=master :target: https://travis-ci.org/enkore/i3pystatus -i3pystatus is a (hopefully growing) collection of python scripts for +i3pystatus is a growing collection of python scripts for status output compatible to i3status / i3bar of the i3 window manager. Installation From 63c7cc6523a9534f13d05bf5ffe216ad6fed95d1 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 8 Jun 2015 02:32:39 +0200 Subject: [PATCH 03/32] Fix minor bug with required inherited settings in subclasses redefining them --- i3pystatus/core/settings.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/i3pystatus/core/settings.py b/i3pystatus/core/settings.py index ecabdd0..e2daa2b 100644 --- a/i3pystatus/core/settings.py +++ b/i3pystatus/core/settings.py @@ -23,12 +23,18 @@ class SettingsBaseMeta(type): name(setting) in seen or seen.add(name(setting)))] settings = tuple() - required = tuple() + required = set() # getmro returns base classes according to Method Resolution Order, # which always includes the class itself as the first element. for base in inspect.getmro(cls): settings += tuple(getattr(base, "settings", [])) - required += tuple(getattr(base, "required", [])) + required |= set(getattr(base, "required", [])) + # if a derived class defines a default for a setting it is not + # required anymore. + for base in inspect.getmro(cls): + for r in list(required): + if hasattr(base, r): + required.remove(r) return unique(settings), required From 3a6319c1b956910d8cdbe2040796633c9bce29fb Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 8 Jun 2015 02:40:48 +0200 Subject: [PATCH 04/32] Change default log level to WARNING (30) --- i3pystatus/core/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i3pystatus/core/settings.py b/i3pystatus/core/settings.py index e2daa2b..2ed7f2f 100644 --- a/i3pystatus/core/settings.py +++ b/i3pystatus/core/settings.py @@ -54,7 +54,7 @@ class SettingsBase(metaclass=SettingsBaseMeta): __PROTECTED_SETTINGS = ["password", "email", "username"] settings = ( - ("log_level", "Set to true to log error to .i3pystatus- file"), + ("log_level", "Set to true to log error to .i3pystatus- file."), ) """settings should be tuple containing two types of elements: @@ -67,7 +67,7 @@ class SettingsBase(metaclass=SettingsBaseMeta): required = tuple() """required can list settings which are required""" - log_level = logging.NOTSET + log_level = logging.WARNING logger = None def __init__(self, *args, **kwargs): From e9b7c6c43912a747aaa0904ffa3eae1598209b6e Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 8 Jun 2015 02:41:04 +0200 Subject: [PATCH 05/32] Don't print exceptions to stderr, log them instead, always force output --- i3pystatus/core/threading.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/i3pystatus/core/threading.py b/i3pystatus/core/threading.py index 2465551..f6eee9b 100644 --- a/i3pystatus/core/threading.py +++ b/i3pystatus/core/threading.py @@ -67,23 +67,26 @@ class ExceptionWrapper(Wrapper): try: self.workload() except: - sys.stderr.write("Exception in {thread} at {time}\n".format( + message = "\n> Exception in {thread} at {time}, module {name}".format( thread=threading.current_thread().name, - time=time.strftime("%c") - )) - traceback.print_exc(file=sys.stderr) - sys.stderr.flush() - if hasattr(self.workload, "output"): - self.workload.output = { - "full_text": "{}: {}".format(self.workload.__class__.__name__, - self.format_error(str(sys.exc_info()[1]))), - "color": "#FF0000", - } + time=time.strftime("%c"), + name=self.workload.__class__.__name__ + ) + if hasattr(self.workload, "logger"): + self.workload.logger.error(message, exc_info=True) + self.workload.output = { + "full_text": "{}: {}".format(self.workload.__class__.__name__, + self.format_error(str(sys.exc_info()[1]))), + "color": "#FF0000", + } def format_error(self, exception_message): if hasattr(self.workload, 'max_error_len'): error_len = self.workload.max_error_len - return exception_message[:error_len] + '...' if len(exception_message) > error_len else exception_message + if len(exception_message) > error_len: + return exception_message[:error_len] + '…' + else: + return exception_message else: return exception_message From c5fe29d258f8fa6a1ca89c839ef830ce35613757 Mon Sep 17 00:00:00 2001 From: enkore Date: Mon, 8 Jun 2015 02:41:34 +0200 Subject: [PATCH 06/32] Always log to file in home dir --- i3pystatus/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index 6d578cb..1d022a8 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -8,13 +8,6 @@ from i3pystatus.core.util import formatp import logging import os -h = logging.FileHandler(".i3pystatus-" + str(os.getpid()), delay=True) - -logger = logging.getLogger("i3pystatus") -logger.addHandler(h) -logger.setLevel(logging.CRITICAL) - - __path__ = extend_path(__path__, __name__) __all__ = [ @@ -24,6 +17,12 @@ __all__ = [ "formatp", ] +logpath = os.path.join(os.path.expanduser("~"), ".i3pystatus-%s" % os.getpid()) +handler = logging.FileHandler(logpath, delay=True) +logger = logging.getLogger("i3pystatus") +logger.addHandler(handler) +logger.setLevel(logging.CRITICAL) + def main(): from i3pystatus.clock import Clock From 475c788c0c3e19dcd340eb3773857af34dcecf83 Mon Sep 17 00:00:00 2001 From: microarm15 Date: Mon, 15 Jun 2015 18:57:41 +0200 Subject: [PATCH 07/32] add total rcv/snt Mbytes to network module --- i3pystatus/network.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 87179a8..e8d117b 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -198,6 +198,14 @@ class NetworkTraffic(): def get_packets_received(self): return self.pnic.packets_recv - self.pnic_before.packets_recv + def get_rx_tot_Mbytes(self, interface): + with open("/sys/class/net/{}/statistics/rx_bytes".format(interface)) as f: + return int(f.readline().split('\n')[0])/(1024*1024) + + def get_tx_tot_Mbytes(self, interface): + with open("/sys/class/net/{}/statistics/tx_bytes".format(interface)) as f: + return int(f.readline().split('\n')[0])/(1024*1024) + def get_usage(self, interface): self.update_counters(interface) usage = dict(bytes_sent=0, bytes_recv=0, packets_sent=0, packets_recv=0) @@ -209,6 +217,8 @@ class NetworkTraffic(): usage["bytes_recv"] = self.get_bytes_received() usage["packets_sent"] = self.get_packets_sent() usage["packets_recv"] = self.get_packets_received() + usage["rx_tot_Mbytes"] = self.get_rx_tot_Mbytes(interface) + usage["tx_tot_Mbytes"] = self.get_tx_tot_Mbytes(interface) round_dict(usage, self.round_size) return usage @@ -249,6 +259,8 @@ class Network(IntervalModule, ColorRangeModule): * `{bytes_recv}` — bytes received per second (divided by divisor) * `{packets_sent}` — bytes sent per second (divided by divisor) * `{packets_recv}` — bytes received per second (divided by divisor) + ^ `{rx_tot_Mbytes}` — total Mbytes received + ^ `{tx_tot_Mbytes}` — total Mbytes sent """ settings = ( ("format_up", "format string"), @@ -313,7 +325,8 @@ class Network(IntervalModule, ColorRangeModule): # Don't require importing psutil unless using the functionality it offers. if any(s in self.format_up or s in self.format_down for s in - ['bytes_sent', 'bytes_recv', 'packets_sent', 'packets_recv', 'network_graph', 'kbs']): + ['bytes_sent', 'bytes_recv', 'packets_sent', 'packets_recv', 'network_graph', + 'rx_tot_Mbytes', 'tx_tot_Mbytes', 'kbs']): self.network_traffic = NetworkTraffic(self.unknown_up, self.divisor, self.round_size) else: self.network_traffic = None @@ -343,6 +356,7 @@ class Network(IntervalModule, ColorRangeModule): def run(self): format_values = dict(kbs="", network_graph="", bytes_sent="", bytes_recv="", packets_sent="", packets_recv="", + rx_tot_Mbytes="", tx_tot_Mbytes="", interface="", v4="", v4mask="", v4cidr="", v6="", v6mask="", v6cidr="", mac="", essid="", freq="", quality="", quality_bar="") if self.network_traffic: From 16ab492b849f901adf7ed4bfd6dd375061a153a3 Mon Sep 17 00:00:00 2001 From: microarm15 Date: Mon, 15 Jun 2015 20:15:45 +0200 Subject: [PATCH 08/32] add whitespace around the * operator --- i3pystatus/network.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i3pystatus/network.py b/i3pystatus/network.py index e8d117b..57cbf53 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -200,11 +200,11 @@ class NetworkTraffic(): def get_rx_tot_Mbytes(self, interface): with open("/sys/class/net/{}/statistics/rx_bytes".format(interface)) as f: - return int(f.readline().split('\n')[0])/(1024*1024) + return int(f.readline().split('\n')[0])/(1024 * 1024) def get_tx_tot_Mbytes(self, interface): with open("/sys/class/net/{}/statistics/tx_bytes".format(interface)) as f: - return int(f.readline().split('\n')[0])/(1024*1024) + return int(f.readline().split('\n')[0])/(1024 * 1024) def get_usage(self, interface): self.update_counters(interface) @@ -259,8 +259,8 @@ class Network(IntervalModule, ColorRangeModule): * `{bytes_recv}` — bytes received per second (divided by divisor) * `{packets_sent}` — bytes sent per second (divided by divisor) * `{packets_recv}` — bytes received per second (divided by divisor) - ^ `{rx_tot_Mbytes}` — total Mbytes received - ^ `{tx_tot_Mbytes}` — total Mbytes sent + * `{rx_tot_Mbytes}` — total Mbytes received + * `{tx_tot_Mbytes}` — total Mbytes sent """ settings = ( ("format_up", "format string"), From 4742f7458321a28ee679405c4b95e620c893fb86 Mon Sep 17 00:00:00 2001 From: microarm15 Date: Mon, 15 Jun 2015 21:28:05 +0200 Subject: [PATCH 09/32] add whitespace around the / operator --- i3pystatus/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 57cbf53..7059873 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -200,11 +200,11 @@ class NetworkTraffic(): def get_rx_tot_Mbytes(self, interface): with open("/sys/class/net/{}/statistics/rx_bytes".format(interface)) as f: - return int(f.readline().split('\n')[0])/(1024 * 1024) + return int(f.readline().split('\n')[0]) / (1024 * 1024) def get_tx_tot_Mbytes(self, interface): with open("/sys/class/net/{}/statistics/tx_bytes".format(interface)) as f: - return int(f.readline().split('\n')[0])/(1024 * 1024) + return int(f.readline().split('\n')[0]) / (1024 * 1024) def get_usage(self, interface): self.update_counters(interface) From 46c6076f9516ff7aec2c893ef857c977a6a8aab0 Mon Sep 17 00:00:00 2001 From: microarm15 Date: Tue, 16 Jun 2015 19:10:48 +0200 Subject: [PATCH 10/32] check whether files exist --- i3pystatus/network.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 7059873..207490c 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -199,12 +199,18 @@ class NetworkTraffic(): return self.pnic.packets_recv - self.pnic_before.packets_recv def get_rx_tot_Mbytes(self, interface): - with open("/sys/class/net/{}/statistics/rx_bytes".format(interface)) as f: - return int(f.readline().split('\n')[0]) / (1024 * 1024) + try: + with open("/sys/class/net/{}/statistics/rx_bytes".format(interface)) as f: + return int(f.readline().split('\n')[0]) / (1024 * 1024) + except FileNotFoundError: + return False def get_tx_tot_Mbytes(self, interface): - with open("/sys/class/net/{}/statistics/tx_bytes".format(interface)) as f: - return int(f.readline().split('\n')[0]) / (1024 * 1024) + try: + with open("/sys/class/net/{}/statistics/tx_bytes".format(interface)) as f: + return int(f.readline().split('\n')[0]) / (1024 * 1024) + except FileNotFoundError: + return False def get_usage(self, interface): self.update_counters(interface) From 9c246bc60ff0cf1817b8de9005d68e55324fd133 Mon Sep 17 00:00:00 2001 From: David Wahlstrom Date: Sun, 7 Jun 2015 22:51:03 -0700 Subject: [PATCH 11/32] net_speed: a module to display internet speeds This module provides a glimpse/snapshot of current internet based speeds. To keep things simple (and due to bugs in speedtest_cli), this module simply uses requests to download a small(ish) image and times the download. This is not a perfect/definitive test, but it should be a decent indicator of how the internet bound traffic looks on the clients network. --- docs/conf.py | 3 +- i3pystatus/net_speed.py | 75 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 i3pystatus/net_speed.py diff --git a/docs/conf.py b/docs/conf.py index 63c1547..bf1bbc4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,7 +32,8 @@ MOCK_MODULES = [ "requests", "bs4", "dota2py", - "novaclient.v2" + "novaclient.v2", + "speedtest_cli" ] for mod_name in MOCK_MODULES: diff --git a/i3pystatus/net_speed.py b/i3pystatus/net_speed.py new file mode 100644 index 0000000..20d3a83 --- /dev/null +++ b/i3pystatus/net_speed.py @@ -0,0 +1,75 @@ +from i3pystatus import IntervalModule +import speedtest_cli +import requests +import time +import os +from urllib.parse import urlparse + + +class NetSpeed(IntervalModule): + """ + Attempts to provide an estimation of internet speeds. + Requires: speedtest_cli + """ + + settings = ( + ("url", "Target URL to download a file from. Uses speedtest_cli to " + "find the 'best' server if none is supplied."), + "format" + ) + color = "#FFFFFF" + interval = 300 + url = None + format = "{speed} ({hosting_provider})" + + def run(self): + if not self.url: + config = speedtest_cli.getConfig() + servers = speedtest_cli.closestServers(config['client']) + best = speedtest_cli.getBestServer(servers) + # 1500x1500 is about 4.3MB, which seems like a reasonable place to + # start, i guess... + url = '%s/random1500x1500.jpg' % os.path.dirname(best['url']) + + with open('/dev/null', 'wb') as devnull: + start = time.time() + req = requests.get(url, stream=True) + devnull.write(req.content) + end = time.time() + total_length = int(req.headers.get('content-length')) + devnull.close() + + # chop off the float after the 4th decimal point + # note: not rounding, simply cutting + # note: dl_time is in seconds + dl_time = float(end - start) + + if total_length < 999: + unit = "Bps" + bps = total_length / dl_time + + if total_length >= 1000 < 999999: + unit = "KBps" + bps = (total_length / 1024.0) / dl_time + + if total_length >= 1000000 < 999999999: + unit = "MBps" + bps = (total_length / (1024.0 * 1024.0)) / dl_time + + if total_length >= 10000000: + unit = "GBps" + bps = (total_length / (1024.0 * 1024.0 * 1024.0)) / dl_time + + bps = "%.2f" % bps + speed = "%s %s" % (bps, unit) + hosting_provider = '.'.join(urlparse(url).hostname.split('.')[-2:]) + + cdict = { + "speed": speed, + "hosting_provider": hosting_provider, + } + + self.output = { + "full_text": self.format.format(**cdict), + "color": self.color + } From 3ad6fc495c877d36bb014062094f6e52edf1ac23 Mon Sep 17 00:00:00 2001 From: David Wahlstrom Date: Mon, 8 Jun 2015 13:20:55 -0700 Subject: [PATCH 12/32] net_speed: allow bps or Bps This patch provides a feature that allows the user to select between Bits or Bytes when displaying the calculated network speed. Also fixes an issue that would sometimes cause the widget to break when speedtest_cli couldn't find a good server. --- i3pystatus/net_speed.py | 108 ++++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/i3pystatus/net_speed.py b/i3pystatus/net_speed.py index 20d3a83..fce158d 100644 --- a/i3pystatus/net_speed.py +++ b/i3pystatus/net_speed.py @@ -4,6 +4,9 @@ import requests import time import os from urllib.parse import urlparse +import contextlib +import sys +from io import StringIO class NetSpeed(IntervalModule): @@ -15,59 +18,92 @@ class NetSpeed(IntervalModule): settings = ( ("url", "Target URL to download a file from. Uses speedtest_cli to " "find the 'best' server if none is supplied."), + ("units", "Valid values are B, b, bytes, or bits"), "format" ) color = "#FFFFFF" interval = 300 url = None + units = 'bits' format = "{speed} ({hosting_provider})" def run(self): + + # since speedtest_cli likes to print crap, we need to squelch it + @contextlib.contextmanager + def nostdout(): + save_stdout = sys.stdout + sys.stdout = StringIO() + yield + sys.stdout = save_stdout + if not self.url: - config = speedtest_cli.getConfig() - servers = speedtest_cli.closestServers(config['client']) - best = speedtest_cli.getBestServer(servers) - # 1500x1500 is about 4.3MB, which seems like a reasonable place to - # start, i guess... - url = '%s/random1500x1500.jpg' % os.path.dirname(best['url']) + with nostdout(): + try: + config = speedtest_cli.getConfig() + servers = speedtest_cli.closestServers(config['client']) + best = speedtest_cli.getBestServer(servers) + # 1500x1500 is about 4.3MB, which seems like a reasonable place to + # start, i guess... + url = '%s/random1500x1500.jpg' % os.path.dirname(best['url']) + except KeyError: + url = None - with open('/dev/null', 'wb') as devnull: - start = time.time() - req = requests.get(url, stream=True) - devnull.write(req.content) - end = time.time() - total_length = int(req.headers.get('content-length')) - devnull.close() + if not url: + cdict = { + "speed": 0, + "hosting_provider": 'null', + } + else: + with open('/dev/null', 'wb') as devnull: + start = time.time() + req = requests.get(url, stream=True) + devnull.write(req.content) + end = time.time() + total_length = int(req.headers.get('content-length')) + devnull.close() - # chop off the float after the 4th decimal point - # note: not rounding, simply cutting - # note: dl_time is in seconds - dl_time = float(end - start) + # chop off the float after the 4th decimal point + # note: not rounding, simply cutting + # note: dl_time is in seconds + dl_time = float(end - start) - if total_length < 999: - unit = "Bps" - bps = total_length / dl_time + if self.units == 'bits' or self.units == 'b': + unit = 'bps' + kilo = 1000 + mega = 1000000 + giga = 1000000000 + factor = 8 + elif self.units == 'bytes' or self.units == 'B': + unit = 'Bps' + kilo = 8000 + mega = 8000000 + giga = 8000000000 + factor = 1 - if total_length >= 1000 < 999999: - unit = "KBps" - bps = (total_length / 1024.0) / dl_time + if total_length < kilo: + bps = float(total_length / dl_time) - if total_length >= 1000000 < 999999999: - unit = "MBps" - bps = (total_length / (1024.0 * 1024.0)) / dl_time + if total_length >= kilo and total_length < mega: + unit = "K" + unit + bps = float((total_length / 1024.0) / dl_time) - if total_length >= 10000000: - unit = "GBps" - bps = (total_length / (1024.0 * 1024.0 * 1024.0)) / dl_time + if total_length >= mega and total_length < giga: + unit = "M" + unit + bps = float((total_length / (1024.0 * 1024.0)) / dl_time) - bps = "%.2f" % bps - speed = "%s %s" % (bps, unit) - hosting_provider = '.'.join(urlparse(url).hostname.split('.')[-2:]) + if total_length >= giga: + unit = "G" + unit + bps = float((total_length / (1024.0 * 1024.0 * 1024.0)) / dl_time) - cdict = { - "speed": speed, - "hosting_provider": hosting_provider, - } + bps = "%.2f" % (bps * factor) + speed = "%s %s" % (bps, unit) + hosting_provider = '.'.join(urlparse(url).hostname.split('.')[-2:]) + + cdict = { + "speed": speed, + "hosting_provider": hosting_provider, + } self.output = { "full_text": self.format.format(**cdict), From b2175b19311a47497cabbd9cbfff99a3dc7bb015 Mon Sep 17 00:00:00 2001 From: David Wahlstrom Date: Tue, 16 Jun 2015 07:33:31 -0700 Subject: [PATCH 13/32] dota2wins: truncate win % to 2 decimals In order to avoid filling i3pystatus with super long decimals, this patch sets the dota2win module to use only 2 decimal places for it's win percent. --- i3pystatus/dota2wins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3pystatus/dota2wins.py b/i3pystatus/dota2wins.py index 9237c24..fd7fc9e 100644 --- a/i3pystatus/dota2wins.py +++ b/i3pystatus/dota2wins.py @@ -96,7 +96,7 @@ class Dota2wins(IntervalModule): "screenname": screenname, "wins": wins, "losses": losses, - "win_percent": win_percent, + "win_percent": "%.2f" % win_percent, } self.output = { From ff519fbde068046b917f69c985e509e5f97a3205 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 10:32:10 +0200 Subject: [PATCH 14/32] Remove shebangs and coding tags --- docs/conf.py | 1 - i3pystatus/battery.py | 3 --- i3pystatus/clock.py | 3 --- i3pystatus/cpu_freq.py | 1 - i3pystatus/cpu_usage.py | 2 -- i3pystatus/cpu_usage_bar.py | 1 - i3pystatus/mail/imap.py | 3 --- i3pystatus/mail/maildir.py | 3 --- i3pystatus/mail/mbox.py | 3 --- i3pystatus/mail/notmuchmail.py | 3 --- i3pystatus/mail/thunderbird.py | 3 --- i3pystatus/network.py | 1 - i3pystatus/pomodoro.py | 3 --- tests/test_cpu_freq.py | 2 -- 14 files changed, 32 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index bf1bbc4..38a49b2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # i3pystatus documentation build configuration file, created by # sphinx-quickstart on Mon Oct 14 17:41:37 2013. diff --git a/i3pystatus/battery.py b/i3pystatus/battery.py index c42636d..4c0f76f 100644 --- a/i3pystatus/battery.py +++ b/i3pystatus/battery.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os import re import configparser diff --git a/i3pystatus/clock.py b/i3pystatus/clock.py index bb0780f..dc6532d 100644 --- a/i3pystatus/clock.py +++ b/i3pystatus/clock.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os import locale import time diff --git a/i3pystatus/cpu_freq.py b/i3pystatus/cpu_freq.py index e78d9d5..7e283e6 100644 --- a/i3pystatus/cpu_freq.py +++ b/i3pystatus/cpu_freq.py @@ -1,4 +1,3 @@ -# coding=utf-8 from i3pystatus import IntervalModule diff --git a/i3pystatus/cpu_usage.py b/i3pystatus/cpu_usage.py index 55ffc1c..5df3dc7 100644 --- a/i3pystatus/cpu_usage.py +++ b/i3pystatus/cpu_usage.py @@ -1,5 +1,3 @@ -# -*- coding:utf-8 -*- - from collections import defaultdict from string import Formatter diff --git a/i3pystatus/cpu_usage_bar.py b/i3pystatus/cpu_usage_bar.py index f65aa80..2bf06e5 100644 --- a/i3pystatus/cpu_usage_bar.py +++ b/i3pystatus/cpu_usage_bar.py @@ -1,4 +1,3 @@ -# -*- coding:utf-8 -*- from i3pystatus.core.color import ColorRangeModule from i3pystatus.cpu_usage import CpuUsage from i3pystatus.core.util import make_bar, make_vertical_bar diff --git a/i3pystatus/mail/imap.py b/i3pystatus/mail/imap.py index 6f91937..1ac0c5c 100644 --- a/i3pystatus/mail/imap.py +++ b/i3pystatus/mail/imap.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import sys import imaplib diff --git a/i3pystatus/mail/maildir.py b/i3pystatus/mail/maildir.py index 7673972..9c1e12a 100644 --- a/i3pystatus/mail/maildir.py +++ b/i3pystatus/mail/maildir.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os from i3pystatus.mail import Backend diff --git a/i3pystatus/mail/mbox.py b/i3pystatus/mail/mbox.py index 951d3f6..e782ffe 100644 --- a/i3pystatus/mail/mbox.py +++ b/i3pystatus/mail/mbox.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import sys from i3pystatus.mail import Backend import subprocess diff --git a/i3pystatus/mail/notmuchmail.py b/i3pystatus/mail/notmuchmail.py index 8953761..dc8988e 100644 --- a/i3pystatus/mail/notmuchmail.py +++ b/i3pystatus/mail/notmuchmail.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - # note that this needs the notmuch python bindings. For more info see: # http://notmuchmail.org/howto/#index4h2 import notmuch diff --git a/i3pystatus/mail/thunderbird.py b/i3pystatus/mail/thunderbird.py index 3760706..61c7710 100644 --- a/i3pystatus/mail/thunderbird.py +++ b/i3pystatus/mail/thunderbird.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -# # This plugin listens for dbus signals emitted by the # thunderbird-dbus-sender extension for TB: # https://github.com/janoliver/thunderbird-dbus-sender diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 207490c..296e1a4 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import netifaces from i3pystatus import IntervalModule diff --git a/i3pystatus/pomodoro.py b/i3pystatus/pomodoro.py index 90c5825..7d6bad1 100644 --- a/i3pystatus/pomodoro.py +++ b/i3pystatus/pomodoro.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import os import subprocess import locale diff --git a/tests/test_cpu_freq.py b/tests/test_cpu_freq.py index a3a2e03..dee926c 100644 --- a/tests/test_cpu_freq.py +++ b/tests/test_cpu_freq.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# coding=utf-8 """ Basic tests for the cpu_freq module """ From 5ce510c549ce39e96ba36669b85f3e8c786f6864 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 14:04:06 +0200 Subject: [PATCH 15/32] setting_util.py: handle EOFError gracefully (^D) --- setting_util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setting_util.py b/setting_util.py index 97cf663..ab9b1c0 100755 --- a/setting_util.py +++ b/setting_util.py @@ -10,6 +10,7 @@ from i3pystatus import Module, SettingsBase from i3pystatus.core import ClassFinder from collections import defaultdict, OrderedDict + def signal_handler(signal, frame): sys.exit(0) signal.signal(signal.SIGINT, signal_handler) @@ -17,7 +18,11 @@ signal.signal(signal.SIGINT, signal_handler) def get_int_in_range(prompt, _range): while True: - answer = input(prompt) + try: + answer = input(prompt) + except EOFError: + print() + sys.exit(0) try: n = int(answer.strip()) if n in _range: From 1b1ab5ef88164d868d21fb46b7ed37e127e452f4 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 14:04:32 +0200 Subject: [PATCH 16/32] setting_util.py: tell the world what you are around for --- setting_util.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setting_util.py b/setting_util.py index ab9b1c0..bc1442b 100755 --- a/setting_util.py +++ b/setting_util.py @@ -64,6 +64,11 @@ choices = [k for k in credential_modules.keys()] for idx, module in enumerate(choices, start=1): print("%s - %s" % (idx, module)) +print("""setting_util.py - part of i3pystatus +This allows you to edit keyring-protected settings of +i3pystatus modules, which are stored globally (independent +of your i3pystatus configuration) in your keyring. +""") index = get_int_in_range("Choose module:\n> ", range(1, len(choices) + 1)) module_name = choices[index - 1] module = credential_modules[module_name] From 18d50199eef78bcfea77a54884c83ac09b3672c2 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 14:05:26 +0200 Subject: [PATCH 17/32] setting_util.py: extract some common code, reprint choices on input iteration handy if you don't have scrollback, also, good manners, since the choices are always directly above the input prompt, no matter how often you fail to hit a number on the keyboard with your forehead. --- setting_util.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/setting_util.py b/setting_util.py index bc1442b..0825385 100755 --- a/setting_util.py +++ b/setting_util.py @@ -36,6 +36,14 @@ modules = [os.path.basename(m.replace('.py', '')) for m in glob.glob(os.path.join(os.path.dirname(__file__), "i3pystatus", "*.py")) if not os.path.basename(m).startswith('_')] + +def enumerate_choices(choices): + lines = [] + for index, choice in enumerate(choices, start=1): + lines.append(" %d - %s\n" % (index, choice)) + return "".join(lines) + + protected_settings = SettingsBase._SettingsBase__PROTECTED_SETTINGS class_finder = ClassFinder(Module) credential_modules = defaultdict(dict) @@ -60,24 +68,28 @@ for module_name in modules: except ImportError: continue -choices = [k for k in credential_modules.keys()] -for idx, module in enumerate(choices, start=1): - print("%s - %s" % (idx, module)) print("""setting_util.py - part of i3pystatus This allows you to edit keyring-protected settings of i3pystatus modules, which are stored globally (independent of your i3pystatus configuration) in your keyring. """) -index = get_int_in_range("Choose module:\n> ", range(1, len(choices) + 1)) + +choices = list(credential_modules.keys()) +prompt = "Choose a module to edit:\n" +prompt += enumerate_choices(choices) +prompt += "> " + +index = get_int_in_range(prompt, range(1, len(choices) + 1)) module_name = choices[index - 1] module = credential_modules[module_name] -for idx, setting in enumerate(module['credentials'], start=1): - print("%s - %s" % (idx, setting)) +prompt = "Choose setting of %s to edit:\n" % module_name +prompt += enumerate_choices(module["credentials"]) +prompt += "> " choices = module['credentials'] -index = get_int_in_range("Choose setting for %s:\n> " % module_name, range(1, len(choices) + 1)) +index = get_int_in_range(prompt, range(1, len(choices) + 1)) setting = choices[index - 1] answer = getpass.getpass("Enter value for %s:\n> " % setting) From dee25535e7e8763c51afd5728e918063edb44f2d Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 14:07:43 +0200 Subject: [PATCH 18/32] Fix some warning from some glib-thing. --- i3pystatus/core/desktop.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/i3pystatus/core/desktop.py b/i3pystatus/core/desktop.py index 5deacef..6143283 100644 --- a/i3pystatus/core/desktop.py +++ b/i3pystatus/core/desktop.py @@ -30,6 +30,8 @@ class DesktopNotification(BaseDesktopNotification): try: + import gi + gi.require_version('Notify', '0.7') from gi.repository import Notify except ImportError: pass From 56f6fd6a8b61db5e983b028473f463baca7ca114 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 14:52:24 +0200 Subject: [PATCH 19/32] Create package i3pystatus.tools for additional command line tools --- i3pystatus/tools/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 i3pystatus/tools/__init__.py diff --git a/i3pystatus/tools/__init__.py b/i3pystatus/tools/__init__.py new file mode 100644 index 0000000..e69de29 From 2a6d6bbea01a5896cb7d5c562dc311ef93a22b5e Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 14:52:37 +0200 Subject: [PATCH 20/32] Move and ln-s setting_util there --- i3pystatus/tools/setting_util.py | 110 +++++++++++++++++++++++++++++++ setting_util.py | 103 +---------------------------- 2 files changed, 111 insertions(+), 102 deletions(-) create mode 100755 i3pystatus/tools/setting_util.py mode change 100755 => 120000 setting_util.py diff --git a/i3pystatus/tools/setting_util.py b/i3pystatus/tools/setting_util.py new file mode 100755 index 0000000..ebf142f --- /dev/null +++ b/i3pystatus/tools/setting_util.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +#!/usr/bin/env python +import glob +import inspect +import os +import getpass +import sys +import signal +from collections import defaultdict, OrderedDict + +import keyring + +from i3pystatus import Module, SettingsBase +from i3pystatus.core import ClassFinder + + +def signal_handler(signal, frame): + sys.exit(0) + + +def get_int_in_range(prompt, _range): + while True: + try: + answer = input(prompt) + except EOFError: + print() + sys.exit(0) + try: + n = int(answer.strip()) + if n in _range: + return n + else: + print("Value out of range!") + except ValueError: + print("Invalid input!") + +modules = [os.path.basename(m.replace('.py', '')) + for m in glob.glob(os.path.join(os.path.dirname(__file__), "i3pystatus", "*.py")) + if not os.path.basename(m).startswith('_')] + + +def enumerate_choices(choices): + lines = [] + for index, choice in enumerate(choices, start=1): + lines.append(" %d - %s\n" % (index, choice)) + return "".join(lines) + + +protected_settings = SettingsBase._SettingsBase__PROTECTED_SETTINGS +class_finder = ClassFinder(Module) +credential_modules = defaultdict(dict) +for module_name in modules: + try: + module = class_finder.get_module(module_name) + clazz = class_finder.get_class(module) + members = [m[0] for m in inspect.getmembers(clazz) if not m[0].startswith('_')] + if any([hasattr(clazz, setting) for setting in protected_settings]): + credential_modules[clazz.__name__]['credentials'] = list(set(protected_settings) & set(members)) + credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__) + elif hasattr(clazz, 'required'): + protected = [] + required = getattr(clazz, 'required') + for setting in protected_settings: + if setting in required: + protected.append(setting) + if protected: + credential_modules[clazz.__name__]['credentials'] = protected + credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__) + + except ImportError: + continue + + +def main(): + signal.signal(signal.SIGINT, signal_handler) + + print("""%s - part of i3pystatus +This allows you to edit keyring-protected settings of +i3pystatus modules, which are stored globally (independent +of your i3pystatus configuration) in your keyring. +""" % os.path.baename(sys.argv[0])) + + choices = list(credential_modules.keys()) + prompt = "Choose a module to edit:\n" + prompt += enumerate_choices(choices) + prompt += "> " + + index = get_int_in_range(prompt, range(1, len(choices) + 1)) + module_name = choices[index - 1] + module = credential_modules[module_name] + + prompt = "Choose setting of %s to edit:\n" % module_name + prompt += enumerate_choices(module["credentials"]) + prompt += "> " + + choices = module['credentials'] + index = get_int_in_range(prompt, range(1, len(choices) + 1)) + setting = choices[index - 1] + + answer = getpass.getpass("Enter value for %s:\n> " % setting) + answer2 = getpass.getpass("Re-enter value\n> ") + if answer == answer2: + key = "%s.%s" % (module['key'], setting) + keyring.set_password(key, getpass.getuser(), answer) + print("%s set!" % setting) + else: + print("Values don't match - nothing set.") + +if __name__ == "__main__": + main() diff --git a/setting_util.py b/setting_util.py deleted file mode 100755 index 0825385..0000000 --- a/setting_util.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python -import glob -import inspect -import os -import keyring -import getpass -import sys -import signal -from i3pystatus import Module, SettingsBase -from i3pystatus.core import ClassFinder -from collections import defaultdict, OrderedDict - - -def signal_handler(signal, frame): - sys.exit(0) -signal.signal(signal.SIGINT, signal_handler) - - -def get_int_in_range(prompt, _range): - while True: - try: - answer = input(prompt) - except EOFError: - print() - sys.exit(0) - try: - n = int(answer.strip()) - if n in _range: - return n - else: - print("Value out of range!") - except ValueError: - print("Invalid input!") - -modules = [os.path.basename(m.replace('.py', '')) - for m in glob.glob(os.path.join(os.path.dirname(__file__), "i3pystatus", "*.py")) - if not os.path.basename(m).startswith('_')] - - -def enumerate_choices(choices): - lines = [] - for index, choice in enumerate(choices, start=1): - lines.append(" %d - %s\n" % (index, choice)) - return "".join(lines) - - -protected_settings = SettingsBase._SettingsBase__PROTECTED_SETTINGS -class_finder = ClassFinder(Module) -credential_modules = defaultdict(dict) -for module_name in modules: - try: - module = class_finder.get_module(module_name) - clazz = class_finder.get_class(module) - members = [m[0] for m in inspect.getmembers(clazz) if not m[0].startswith('_')] - if any([hasattr(clazz, setting) for setting in protected_settings]): - credential_modules[clazz.__name__]['credentials'] = list(set(protected_settings) & set(members)) - credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__) - elif hasattr(clazz, 'required'): - protected = [] - required = getattr(clazz, 'required') - for setting in protected_settings: - if setting in required: - protected.append(setting) - if protected: - credential_modules[clazz.__name__]['credentials'] = protected - credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__) - - except ImportError: - continue - - -print("""setting_util.py - part of i3pystatus -This allows you to edit keyring-protected settings of -i3pystatus modules, which are stored globally (independent -of your i3pystatus configuration) in your keyring. -""") - -choices = list(credential_modules.keys()) -prompt = "Choose a module to edit:\n" -prompt += enumerate_choices(choices) -prompt += "> " - -index = get_int_in_range(prompt, range(1, len(choices) + 1)) -module_name = choices[index - 1] -module = credential_modules[module_name] - -prompt = "Choose setting of %s to edit:\n" % module_name -prompt += enumerate_choices(module["credentials"]) -prompt += "> " - -choices = module['credentials'] -index = get_int_in_range(prompt, range(1, len(choices) + 1)) -setting = choices[index - 1] - -answer = getpass.getpass("Enter value for %s:\n> " % setting) -answer2 = getpass.getpass("Re-enter value\n> ") -if answer == answer2: - key = "%s.%s" % (module['key'], setting) - keyring.set_password(key, getpass.getuser(), answer) - print("%s set!" % setting) -else: - print("Values don't match - nothing set.") diff --git a/setting_util.py b/setting_util.py new file mode 120000 index 0000000..27d2457 --- /dev/null +++ b/setting_util.py @@ -0,0 +1 @@ +i3pystatus/tools/setting_util.py \ No newline at end of file From db88ea7a72e5031c82968ebaeeccb9534f4bce0e Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 14:52:50 +0200 Subject: [PATCH 21/32] Add i3pystatus-setting-util entry point to setup.py --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8cf3168..7a5cac8 100755 --- a/setup.py +++ b/setup.py @@ -18,13 +18,15 @@ setup(name="i3pystatus", packages=[ "i3pystatus", "i3pystatus.core", + "i3pystatus.tools", "i3pystatus.mail", "i3pystatus.pulseaudio", "i3pystatus.updates", ], entry_points={ "console_scripts": [ - "i3pystatus = i3pystatus:main" + "i3pystatus = i3pystatus:main", + "i3pystatus-setting-util = i3pystatus.tools.setting_util:main" ] }, zip_safe=True, From f5598b8b7c333d09f47f73dfa5717a83f35ab84d Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 15:10:24 +0200 Subject: [PATCH 22/32] make setting_util zip_safe and location-independent --- ci-build.sh | 1 + docs/module_docs.py | 2 +- i3pystatus/tools/setting_util.py | 36 +++++++++++++++++++------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/ci-build.sh b/ci-build.sh index 8d78c65..fa142a3 100755 --- a/ci-build.sh +++ b/ci-build.sh @@ -17,6 +17,7 @@ mkdir ${BUILD}/test-install ${BUILD}/test-install-bin PYTHONPATH=${BUILD}/test-install python3 setup.py --quiet install --install-lib ${BUILD}/test-install --install-scripts ${BUILD}/test-install-bin test -f ${BUILD}/test-install-bin/i3pystatus +test -f ${BUILD}/test-install-bin/i3pystatus-setting-util PYTHONPATH=${BUILD}/test-install py.test --junitxml ${BUILD}/testlog.xml tests diff --git a/docs/module_docs.py b/docs/module_docs.py index 5aff0eb..cda2b0f 100644 --- a/docs/module_docs.py +++ b/docs/module_docs.py @@ -12,7 +12,7 @@ import i3pystatus.core.modules from i3pystatus.core.imputil import ClassFinder from i3pystatus.core.color import ColorRangeModule -IGNORE_MODULES = ("__main__", "core") +IGNORE_MODULES = ("__main__", "core", "tools") def is_module(obj): diff --git a/i3pystatus/tools/setting_util.py b/i3pystatus/tools/setting_util.py index ebf142f..0a7f888 100755 --- a/i3pystatus/tools/setting_util.py +++ b/i3pystatus/tools/setting_util.py @@ -1,15 +1,16 @@ #!/usr/bin/env python -#!/usr/bin/env python import glob import inspect import os import getpass import sys import signal +import pkgutil from collections import defaultdict, OrderedDict import keyring +import i3pystatus from i3pystatus import Module, SettingsBase from i3pystatus.core import ClassFinder @@ -34,10 +35,6 @@ def get_int_in_range(prompt, _range): except ValueError: print("Invalid input!") -modules = [os.path.basename(m.replace('.py', '')) - for m in glob.glob(os.path.join(os.path.dirname(__file__), "i3pystatus", "*.py")) - if not os.path.basename(m).startswith('_')] - def enumerate_choices(choices): lines = [] @@ -46,12 +43,21 @@ def enumerate_choices(choices): return "".join(lines) -protected_settings = SettingsBase._SettingsBase__PROTECTED_SETTINGS -class_finder = ClassFinder(Module) -credential_modules = defaultdict(dict) -for module_name in modules: - try: - module = class_finder.get_module(module_name) +def get_modules(): + for importer, modname, ispkg in pkgutil.iter_modules(i3pystatus.__path__): + if modname not in ["core", "tools"]: + yield modname + + +def get_credential_modules(): + protected_settings = SettingsBase._SettingsBase__PROTECTED_SETTINGS + class_finder = ClassFinder(Module) + credential_modules = defaultdict(dict) + for module_name in get_modules(): + try: + module = class_finder.get_module(module_name) + except ImportError: + continue clazz = class_finder.get_class(module) members = [m[0] for m in inspect.getmembers(clazz) if not m[0].startswith('_')] if any([hasattr(clazz, setting) for setting in protected_settings]): @@ -66,9 +72,7 @@ for module_name in modules: if protected: credential_modules[clazz.__name__]['credentials'] = protected credential_modules[clazz.__name__]['key'] = "%s.%s" % (clazz.__module__, clazz.__name__) - - except ImportError: - continue + return credential_modules def main(): @@ -78,7 +82,9 @@ def main(): This allows you to edit keyring-protected settings of i3pystatus modules, which are stored globally (independent of your i3pystatus configuration) in your keyring. -""" % os.path.baename(sys.argv[0])) +""" % os.path.basename(sys.argv[0])) + + credential_modules = get_credential_modules() choices = list(credential_modules.keys()) prompt = "Choose a module to edit:\n" From 9c1ae61c25066e2d0b0218474b6100e3054bf4e5 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 15:16:40 +0200 Subject: [PATCH 23/32] Update docs re. credentials --- docs/configuration.rst | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 109df38..de3f0e9 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -120,12 +120,18 @@ Also change your i3wm config to the following: workspace_buttons yes } -.. note:: - Don't name your config file ``i3pystatus.py`` +.. note:: Don't name your config file ``i3pystatus.py``, as it would + make ``i3pystatus`` un-importable and lead to errors. -Settings that require credentials can utilize the keyring module to keep sensitive information out of config files. -To take advantage of this feature, simply use the setting_util.py script to set the credentials for a module. Once this -is done you can add the module to your config without specifying the credentials, eg: +Credentials +----------- + +Settings that require credentials can utilize the keyring module to +keep sensitive information out of config files. To take advantage of +this feature, simply use the ``i3pystatus-setting-util`` script +installed along i3pystatus to set the credentials for a module. Once +this is done you can add the module to your config without specifying +the credentials, e.g.: .. code:: python @@ -141,4 +147,10 @@ If you don't want to use the default you can set a specific keyring like so: from keyring.backends.file import PlaintextKeyring status.register('github', keyring_backend=PlaintextKeyring()) -i3pystatus will locate and set the credentials during the module loading process. Currently supported credentals are "password", "email" and "username". +i3pystatus will locate and set the credentials during the module +loading process. Currently supported credentals are "password", +"email" and "username". + +.. note:: Credential handling requires the PyPI package + ``keyring``. Many distributions have it pre-packaged available as + ``python-keyring``. From 087cec56f0b004d5a6ddd75be564c9fba05172b9 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 15:17:26 +0200 Subject: [PATCH 24/32] typo --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index de3f0e9..78a81bc 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -148,7 +148,7 @@ If you don't want to use the default you can set a specific keyring like so: status.register('github', keyring_backend=PlaintextKeyring()) i3pystatus will locate and set the credentials during the module -loading process. Currently supported credentals are "password", +loading process. Currently supported credentials are "password", "email" and "username". .. note:: Credential handling requires the PyPI package From bbab4c0db0b9221a8a2d346cac72de2777c6f776 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 15:50:10 +0200 Subject: [PATCH 25/32] setting util: add -l -v options --- i3pystatus/tools/setting_util.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/i3pystatus/tools/setting_util.py b/i3pystatus/tools/setting_util.py index 0a7f888..39e64da 100755 --- a/i3pystatus/tools/setting_util.py +++ b/i3pystatus/tools/setting_util.py @@ -50,6 +50,8 @@ def get_modules(): def get_credential_modules(): + verbose = "-v" in sys.argv + protected_settings = SettingsBase._SettingsBase__PROTECTED_SETTINGS class_finder = ClassFinder(Module) credential_modules = defaultdict(dict) @@ -57,6 +59,8 @@ def get_credential_modules(): try: module = class_finder.get_module(module_name) except ImportError: + if verbose: + print("ImportError while importing", module_name) continue clazz = class_finder.get_class(module) members = [m[0] for m in inspect.getmembers(clazz) if not m[0].startswith('_')] @@ -82,10 +86,24 @@ def main(): This allows you to edit keyring-protected settings of i3pystatus modules, which are stored globally (independent of your i3pystatus configuration) in your keyring. + +Options: + -l: list all stored settings (no values are printed) + -v: print informational messages """ % os.path.basename(sys.argv[0])) credential_modules = get_credential_modules() + if "-l" in sys.argv: + for name, module in credential_modules.items(): + print(name) + for credential in enumerate_choices(module["credentials"]): + if keyring.get_password("%s.%s" % (module["key"], credential), getpass.getuser()): + print(" - %s: set" % credential) + else: + print(" (none stored)") + return + choices = list(credential_modules.keys()) prompt = "Choose a module to edit:\n" prompt += enumerate_choices(choices) From 66b568afc68a96308e0e8af0e4aa345b01491b6e Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 15:55:33 +0200 Subject: [PATCH 26/32] Fix trailing whitespace --- i3pystatus/tools/setting_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3pystatus/tools/setting_util.py b/i3pystatus/tools/setting_util.py index 39e64da..60bed5d 100755 --- a/i3pystatus/tools/setting_util.py +++ b/i3pystatus/tools/setting_util.py @@ -101,7 +101,7 @@ Options: if keyring.get_password("%s.%s" % (module["key"], credential), getpass.getuser()): print(" - %s: set" % credential) else: - print(" (none stored)") + print(" (none stored)") return choices = list(credential_modules.keys()) From 30a330834feeeab69eccb0a5b432a03d051030b6 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 16:05:03 +0200 Subject: [PATCH 27/32] Merge formatting and configuration chapters --- docs/configuration.rst | 64 ++++++++++++++++++++++++++++++++++++++++++ docs/formatting.rst | 63 ----------------------------------------- docs/index.rst | 1 - 3 files changed, 64 insertions(+), 64 deletions(-) delete mode 100644 docs/formatting.rst diff --git a/docs/configuration.rst b/docs/configuration.rst index 78a81bc..840bcb0 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -154,3 +154,67 @@ loading process. Currently supported credentials are "password", .. note:: Credential handling requires the PyPI package ``keyring``. Many distributions have it pre-packaged available as ``python-keyring``. + +Formatting +---------- + +All modules let you specifiy the exact output formatting using a +`format string `_, which +gives you a great deal of flexibility. + +If a module gives you a float, it probably has a ton of +uninteresting decimal places. Use ``{somefloat:.0f}`` to get the integer +value, ``{somefloat:0.2f}`` gives you two decimal places after the +decimal dot + +.. _formatp: + +formatp +~~~~~~~ + +Some modules use an extended format string syntax (the :py:mod:`.mpd` +module, for example). Given the format string below the output adapts +itself to the available data. + +:: + + [{artist}/{album}/]{title}{status} + +Only if both the artist and album is known they're displayed. If only one or none +of them is known the entire group between the brackets is excluded. + +"is known" is here defined as "value evaluating to True in Python", i.e. an empty +string or 0 (or 0.0) counts as "not known". + +Inside a group always all format specifiers must evaluate to true (logical and). + +You can nest groups. The inner group will only become part of the output if both +the outer group and the inner group are eligible for output. + +.. _TimeWrapper: + +TimeWrapper +~~~~~~~~~~~ + +Some modules that output times use :py:class:`.TimeWrapper` to format +these. TimeWrapper is a mere extension of the standard formatting +method. + +The time format that should be used is specified using the format specifier, i.e. +with some_time being 3951 seconds a format string like ``{some_time:%h:%m:%s}`` +would produce ``1:5:51``. + +* ``%h``, ``%m`` and ``%s`` are the hours, minutes and seconds without + leading zeros (i.e. 0 to 59 for minutes and seconds) +* ``%H``, ``%M`` and ``%S`` are padded with a leading zero to two digits, + i.e. 00 to 59 +* ``%l`` and ``%L`` produce hours non-padded and padded but only if hours + is not zero. If the hours are zero it produces an empty string. +* ``%%`` produces a literal % +* ``%E`` (only valid on beginning of the string) if the time is null, + don't format anything but rather produce an empty string. If the + time is non-null it is removed from the string. +* When the module in question also uses formatp, 0 seconds counts as + "not known". +* The formatted time is stripped, i.e. spaces on both ends of the + result are removed. diff --git a/docs/formatting.rst b/docs/formatting.rst deleted file mode 100644 index 8de1777..0000000 --- a/docs/formatting.rst +++ /dev/null @@ -1,63 +0,0 @@ -Formatting -========== - -All modules let you specifiy the exact output formatting using a -`format string `_, which -gives you a great deal of flexibility. - -If a module gives you a float, it probably has a ton of -uninteresting decimal places. Use ``{somefloat:.0f}`` to get the integer -value, ``{somefloat:0.2f}`` gives you two decimal places after the -decimal dot - -.. _formatp: - -formatp -------- - -Some modules use an extended format string syntax (the :py:mod:`.mpd` -module, for example). Given the format string below the output adapts -itself to the available data. - -:: - - [{artist}/{album}/]{title}{status} - -Only if both the artist and album is known they're displayed. If only one or none -of them is known the entire group between the brackets is excluded. - -"is known" is here defined as "value evaluating to True in Python", i.e. an empty -string or 0 (or 0.0) counts as "not known". - -Inside a group always all format specifiers must evaluate to true (logical and). - -You can nest groups. The inner group will only become part of the output if both -the outer group and the inner group are eligible for output. - -.. _TimeWrapper: - -TimeWrapper ------------ - -Some modules that output times use :py:class:`.TimeWrapper` to format -these. TimeWrapper is a mere extension of the standard formatting -method. - -The time format that should be used is specified using the format specifier, i.e. -with some_time being 3951 seconds a format string like ``{some_time:%h:%m:%s}`` -would produce ``1:5:51``. - -* ``%h``, ``%m`` and ``%s`` are the hours, minutes and seconds without - leading zeros (i.e. 0 to 59 for minutes and seconds) -* ``%H``, ``%M`` and ``%S`` are padded with a leading zero to two digits, - i.e. 00 to 59 -* ``%l`` and ``%L`` produce hours non-padded and padded but only if hours - is not zero. If the hours are zero it produces an empty string. -* ``%%`` produces a literal % -* ``%E`` (only valid on beginning of the string) if the time is null, - don't format anything but rather produce an empty string. If the - time is non-null it is removed from the string. -* When the module in question also uses formatp, 0 seconds counts as - "not known". -* The formatted time is stripped, i.e. spaces on both ends of the - result are removed. diff --git a/docs/index.rst b/docs/index.rst index 42867b4..421b47e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,7 +8,6 @@ Contents: :maxdepth: 4 configuration - formatting i3pystatus changelog module From 74bffab94519a480b61cc78b4dbc63e64e9e5375 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 16:05:09 +0200 Subject: [PATCH 28/32] Add Logging chapter --- docs/configuration.rst | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/configuration.rst b/docs/configuration.rst index 840bcb0..ecb21a0 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -218,3 +218,41 @@ would produce ``1:5:51``. "not known". * The formatted time is stripped, i.e. spaces on both ends of the result are removed. + +Logging +------- + +Errors do happen and to ease debugging i3pystatus includes a logging +facility. By default i3pystatus will log exceptions raised by modules +to files in your home directory named +``.i3pystatus-``. Some modules might log additional +information. + +.. rubric:: Log level + +Every module has a ``log_level`` option which sets the *minimum* +severity required for an event to be logged. + +The numeric values of logging levels are given in the following +table. + ++--------------+---------------+ +| Level | Numeric value | ++==============+===============+ +| ``CRITICAL`` | 50 | ++--------------+---------------+ +| ``ERROR`` | 40 | ++--------------+---------------+ +| ``WARNING`` | 30 | ++--------------+---------------+ +| ``INFO`` | 20 | ++--------------+---------------+ +| ``DEBUG`` | 10 | ++--------------+---------------+ +| ``NOTSET`` | 0 | ++--------------+---------------+ + +Exceptions raised by modules are of severity ``ERROR`` by default. The +default ``log_level`` in i3pystatus (some modules might redefine the +default, see the reference of the module in question) is 30 +(``WARNING``). From 8ea4bdeb55c9af9e411898fb6fbe9959bfafb9a8 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 16:12:10 +0200 Subject: [PATCH 29/32] Minor formatting change --- docs/i3pystatus.rst | 6 ++++-- docs/module_docs.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/i3pystatus.rst b/docs/i3pystatus.rst index dacbbb7..21dd060 100644 --- a/docs/i3pystatus.rst +++ b/docs/i3pystatus.rst @@ -4,18 +4,20 @@ Module reference .. Don't list *every* module here, e.g. cpu-usage suffices, because the other variants are listed below that one. +.. rubric:: Module overview: + :System: `clock`_ - `disk`_ - `load`_ - `mem`_ - `cpu_usage`_ :Audio: `alsa`_ - `pulseaudio`_ :Hardware: `battery`_ - `backlight`_ - `temp`_ :Network: `network`_ :Music: `now_playing`_ - `mpd`_ -:Websites & stuff: `weather`_ - `bitcoin`_ - `reddit`_ - `parcel`_ +:Websites: `weather`_ - `bitcoin`_ - `reddit`_ - `parcel`_ :Other: `mail`_ - `pyload`_ - `text`_ - `updates`_ :Advanced: `file`_ - `regex`_ - `runwatch`_ - `shell`_ .. autogen:: i3pystatus Module - .. note:: List of all modules: + .. rubric:: Module list: .. _mailbackends: diff --git a/docs/module_docs.py b/docs/module_docs.py index cda2b0f..e415fa9 100644 --- a/docs/module_docs.py +++ b/docs/module_docs.py @@ -132,7 +132,7 @@ def generate_automodules(path, name, basecls): contents = [] for mod in modules: - contents.append(" * :py:mod:`~{}`".format(mod[0])) + contents.append("* :py:mod:`~{}`".format(mod[0])) contents.append("") for mod in modules: From 09b158a6e17056584ee763ab419865e1b869188d Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 17:39:20 +0200 Subject: [PATCH 30/32] anchors --- docs/changelog.rst | 3 ++- docs/configuration.rst | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5641eaf..bf31e4e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,9 +6,10 @@ master branch +++++++++++++ * Errors can now be logged to ``~/.i3pystatus-`` - - ``log_level`` setting + - See :ref:`logging` * Added new callback system * Added credentials storage + - See :ref:`credentials` * Added deadbeef module * Added github module * Added whosonlocation module diff --git a/docs/configuration.rst b/docs/configuration.rst index ecb21a0..bc970a5 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -123,6 +123,8 @@ Also change your i3wm config to the following: .. note:: Don't name your config file ``i3pystatus.py``, as it would make ``i3pystatus`` un-importable and lead to errors. +.. _credentials: + Credentials ----------- @@ -219,6 +221,8 @@ would produce ``1:5:51``. * The formatted time is stripped, i.e. spaces on both ends of the result are removed. +.. _logging: + Logging ------- From 7df811b9a1c6869ba05c0fdf60274516b49e83d3 Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 18:11:40 +0200 Subject: [PATCH 31/32] Add callbacks chapter --- docs/changelog.rst | 1 + docs/configuration.rst | 38 ++++++++++++++++++++++++++++++++++++++ i3pystatus/core/modules.py | 8 ++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index bf31e4e..e9bc004 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,6 +8,7 @@ master branch * Errors can now be logged to ``~/.i3pystatus-`` - See :ref:`logging` * Added new callback system + - See :ref:`callbacks` * Added credentials storage - See :ref:`credentials` * Added deadbeef module diff --git a/docs/configuration.rst b/docs/configuration.rst index bc970a5..fb47b96 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -260,3 +260,41 @@ Exceptions raised by modules are of severity ``ERROR`` by default. The default ``log_level`` in i3pystatus (some modules might redefine the default, see the reference of the module in question) is 30 (``WARNING``). + +.. _callbacks: + +Callbacks +--------- + +Callbacks are used for click-events (merged into i3bar since i3 4.6, +mouse wheel events are merged since 4.8), that is, you click (or +scroll) on the output of a module in your i3bar and something +happens. What happens is defined by these settings for each module +individually: + +- ``on_leftclick`` +- ``on_rightclick`` +- ``on_upscroll`` +- ``on_downscroll`` + +The global default action for all settings is ``None`` (do nothing), +but many modules define other defaults, which are documented in the +module reference. + +The settings can be of different types, namely + +- a list referring to a method of the module, e.g. + + .. code:: python + + status.register("clock", + on_leftclick=["scroll_format", 1]) + + ``scroll_format`` is a method of the ``clock`` module, the ``1`` is + passed as a parameter and indicates the direction in this case. +- as a special case of the above: a string referring to a method, no + parameters are passed. +- a list where the first element is a callable and the following + elements are passed as arguments to the callable +- again a special case of the above: just a callable, no parameters +- a string which is run in a shell diff --git a/i3pystatus/core/modules.py b/i3pystatus/core/modules.py index 9a848a2..7e355b0 100644 --- a/i3pystatus/core/modules.py +++ b/i3pystatus/core/modules.py @@ -9,10 +9,10 @@ class Module(SettingsBase): position = 0 settings = ( - ('on_leftclick', "Callback called on left click (string)"), - ('on_rightclick', "Callback called on right click (string)"), - ('on_upscroll', "Callback called on scrolling up (string)"), - ('on_downscroll', "Callback called on scrolling down (string)"), + ('on_leftclick', "Callback called on left click (see :ref:`callbacks`)"), + ('on_rightclick', "Callback called on right click (see :ref:`callbacks`)"), + ('on_upscroll', "Callback called on scrolling up (see :ref:`callbacks`)"), + ('on_downscroll', "Callback called on scrolling down (see :ref:`callbacks`)"), ) on_leftclick = None From 394e80ad4e2c47ef4917362b63550af8e593866f Mon Sep 17 00:00:00 2001 From: enkore Date: Wed, 17 Jun 2015 18:11:49 +0200 Subject: [PATCH 32/32] Some minor docstuff ; functionally equivalent --- i3pystatus/bitcoin.py | 14 ++------------ i3pystatus/network.py | 1 + 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/i3pystatus/bitcoin.py b/i3pystatus/bitcoin.py index e27568f..8c89c9f 100644 --- a/i3pystatus/bitcoin.py +++ b/i3pystatus/bitcoin.py @@ -40,8 +40,6 @@ class Bitcoin(IntervalModule): ("colorize", "Enable color change on price increase/decrease"), ("color_up", "Color for price increases"), ("color_down", "Color for price decreases"), - ("leftclick", "URL to visit or command to run on left click"), - ("rightclick", "URL to visit or command to run on right click"), ("interval", "Update interval."), ("symbol", "Symbol for bitcoin sign"), "status" @@ -54,16 +52,14 @@ class Bitcoin(IntervalModule): colorize = False color_up = "#00FF00" color_down = "#FF0000" - leftclick = "electrum" - rightclick = "https://bitcoinaverage.com/" interval = 600 status = { "price_up": "▲", "price_down": "▼", } - on_leftclick = "handle_leftclick" - on_rightclick = "handle_rightclick" + on_leftclick = "electrum" + on_rightclick = [user_open, "https://bitcoinaverage.com/"] _price_prev = 0 @@ -128,9 +124,3 @@ class Bitcoin(IntervalModule): "full_text": self.format.format(**fdict), "color": color, } - - def handle_leftclick(self): - user_open(self.leftclick) - - def handle_rightclick(self): - user_open(self.rightclick) diff --git a/i3pystatus/network.py b/i3pystatus/network.py index 296e1a4..aa39099 100644 --- a/i3pystatus/network.py +++ b/i3pystatus/network.py @@ -342,6 +342,7 @@ class Network(IntervalModule, ColorRangeModule): self.kbs_arr = [0.0] * self.graph_width def cycle_interface(self, increment=1): + """Cycle through available interfaces in `increment` steps. Sign indicates direction.""" interfaces = [i for i in netifaces.interfaces() if i not in self.ignore_interfaces] if self.interface in interfaces: next_index = (interfaces.index(self.interface) + increment) % len(interfaces)