diff --git a/ci-build.sh b/ci-build.sh index db59a73..61195aa 100755 --- a/ci-build.sh +++ b/ci-build.sh @@ -2,13 +2,13 @@ python3 --version py.test --version -pep8 --version +python3 -mpep8 --version # Target directory for all build files BUILD=${1:-ci-build} mkdir -p $BUILD -pep8 --ignore E501 i3pystatus tests +python3 -mpep8 --ignore E501 i3pystatus tests # Check that the setup.py script works rm -rf ${BUILD}/test-install ${BUILD}/test-install-bin diff --git a/i3pystatus/__init__.py b/i3pystatus/__init__.py index d18ca03..6d578cb 100644 --- a/i3pystatus/__init__.py +++ b/i3pystatus/__init__.py @@ -5,6 +5,16 @@ from i3pystatus.core.modules import Module, IntervalModule from i3pystatus.core.settings import SettingsBase 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__ = [ diff --git a/i3pystatus/core/command.py b/i3pystatus/core/command.py new file mode 100644 index 0000000..3a7813b --- /dev/null +++ b/i3pystatus/core/command.py @@ -0,0 +1,29 @@ +# from subprocess import CalledProcessError +import subprocess + + +def run_through_shell(command, enable_shell=False): + """ + Retrieves output of command + Returns tuple success (boolean)/ stdout(string) / stderr (string) + + Don't use this function with programs that outputs lots of data since the output is saved + in one variable + """ + returncode = None + try: + proc = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=enable_shell) + + out, stderr = proc.communicate() + out = out.decode("UTF-8") + stderr = stderr.decode("UTF-8") + + returncode = proc.returncode + + except OSError as e: + out = e.strerror + stderr = e.strerror + except subprocess.CalledProcessError as e: + out = e.output + + return returncode, out, stderr diff --git a/i3pystatus/core/settings.py b/i3pystatus/core/settings.py index eae708c..6d67ae6 100644 --- a/i3pystatus/core/settings.py +++ b/i3pystatus/core/settings.py @@ -1,6 +1,7 @@ from i3pystatus.core.util import KeyConstraintDict from i3pystatus.core.exceptions import ConfigKeyError, ConfigMissingError import inspect +import logging class SettingsBase: @@ -18,7 +19,7 @@ class SettingsBase: """ settings = ( - ("enable_log", "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: @@ -31,7 +32,8 @@ class SettingsBase: required = tuple() """required can list settings which are required""" - enable_log = False + log_level = logging.NOTSET + logger = None def __init__(self, *args, **kwargs): def get_argument_dict(args, kwargs): @@ -71,6 +73,8 @@ class SettingsBase: self.__name__ = "{}.{}".format( self.__module__, self.__class__.__name__) + self.logger = logging.getLogger(self.__name__) + self.logger.setLevel(self.log_level) self.init() def init(self): diff --git a/i3pystatus/mail/__init__.py b/i3pystatus/mail/__init__.py index abf0ba1..e683f0e 100644 --- a/i3pystatus/mail/__init__.py +++ b/i3pystatus/mail/__init__.py @@ -1,7 +1,5 @@ -import subprocess - from i3pystatus import SettingsBase, IntervalModule -from i3pystatus.core.util import internet, require +from i3pystatus.core.command import run_through_shell class Backend(SettingsBase): @@ -69,7 +67,9 @@ class Mail(IntervalModule): def on_leftclick(self): if self.email_client: - subprocess.Popen(self.email_client.split()) + retcode, _, stderr = run_through_shell(self.email_client) + if retcode != 0 and stderr: + self.logger.error(stderr) def on_rightclick(self): self.run() diff --git a/i3pystatus/shell.py b/i3pystatus/shell.py index 571521f..5cea607 100644 --- a/i3pystatus/shell.py +++ b/i3pystatus/shell.py @@ -1,5 +1,8 @@ from i3pystatus import IntervalModule -from subprocess import check_output, CalledProcessError +from i3pystatus.core.command import run_through_shell +import logging + +# logger = logging.getLogger(__name__) class Shell(IntervalModule): @@ -19,21 +22,17 @@ class Shell(IntervalModule): required = ("command",) def run(self): - try: - out = check_output(self.command, shell=True) - color = self.color - except CalledProcessError as e: - out = e.output - color = self.error_color + retvalue, out, stderr = run_through_shell(self.command, enable_shell=True) - out = out.decode("UTF-8").replace("\n", " ") - try: - if out[-1] == " ": - out = out[:-1] - except: - out = "" + if retvalue != 0: + self.logger.error(stderr if stderr else "Unknown error") + + if out: + out.replace("\n", " ").strip() + elif stderr: + out = stderr self.output = { - "full_text": out, - "color": color + "full_text": out if out else "Command `%s` returned %d" % (self.command, retvalue), + "color": self.color if retvalue == 0 else self.error_color } diff --git a/tests/test_core_modules.py b/tests/test_core_modules.py index 84b40cb..2ab34f8 100644 --- a/tests/test_core_modules.py +++ b/tests/test_core_modules.py @@ -11,7 +11,8 @@ class IntervalModuleMetaTest(unittest.TestCase): def test_no_settings(self): class NoSettings(IntervalModule): pass - self.assertTrue('interval' in NoSettings.settings) + for element in ('interval', ): + self.assertIn(element, NoSettings.settings) def test_no_interval_setting(self): class NoIntervalSetting(IntervalModule): @@ -20,9 +21,9 @@ class IntervalModuleMetaTest(unittest.TestCase): (('option', 'desc'), 'interval')) def test_settings_with_interval(self): - class SettingsInteval(IntervalModule): + class SettingsInterval(IntervalModule): settings = ('option', 'interval') - self.assertEqual(SettingsInteval.settings, ('option', 'interval')) + self.assertEqual(SettingsInterval.settings, ('option', 'interval')) def test_settings_with_interval_desc(self): class SetttingsIntervalDesc(IntervalModule): diff --git a/tests/test_shell.py b/tests/test_shell.py new file mode 100644 index 0000000..ec9e388 --- /dev/null +++ b/tests/test_shell.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest +import logging + +from i3pystatus.shell import Shell +from i3pystatus.core.command import run_through_shell + + +class ShellModuleMetaTest(unittest.TestCase): + + valid_output = "hello world" + + def test_shell_correct_output(self): + # ShellTest test + # http://python.readthedocs.org/en/latest/library/unittest.html + retcode, out, err = run_through_shell("echo '%s'" % (self.valid_output), enable_shell=True) + self.assertTrue(retcode == 0) + self.assertEqual(out.strip(), self.valid_output) + + def test_program_failure(self): + success, out, err = run_through_shell("thisshouldtriggeranerror") + self.assertFalse(success)