From 87f8f33a0be8f951bf70e73b2f291ecdafa02f5d Mon Sep 17 00:00:00 2001 From: rrhuffy Date: Sat, 26 Oct 2019 21:35:09 +0200 Subject: [PATCH] [modules/shell] Add shell module: Execute command in shell and print result --- README.md | 2 +- bumblebee/modules/shell.py | 91 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 bumblebee/modules/shell.py diff --git a/README.md b/README.md index 1076a9e..7139074 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage) [![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) -**Many, many thanks to all contributors! As of now, 50 of the modules are from various contributors (!), and only 19 from myself.** +**Many, many thanks to all contributors! As of now, 51 of the modules are from various contributors (!), and only 19 from myself.** ![Solarized Powerline](https://github.com/tobi-wan-kenobi/bumblebee-status/blob/master/screenshots/themes/powerline-solarized.png) diff --git a/bumblebee/modules/shell.py b/bumblebee/modules/shell.py new file mode 100644 index 0000000..b04aae5 --- /dev/null +++ b/bumblebee/modules/shell.py @@ -0,0 +1,91 @@ +# pylint: disable=C0111,R0903,W1401 + +""" Execute command in shell and print result + +Few command examples: + 'ping 1.1.1.1 -c 1 | grep -Po "(?<=time=)\d+(\.\d+)? ms"' + 'echo "BTC=$(curl -s rate.sx/1BTC | grep -Po \"^\d+\")USD"' + 'curl -s https://wttr.in/London?format=%l+%t+%h+%w' + 'pip3 freeze | wc -l' + 'any_custom_script.sh | grep arguments' + +Parameters: + * shell.command: Command to execute + Use single parentheses if evaluating anything inside + For example shell.command='echo $(date +"%H:%M:%S")' + But NOT shell.command="echo $(date +'%H:%M:%S')" + Second one will be evaluated only once at startup + * shell.interval: Update interval in minutes + (defaults to 1s == every bumblebee-status update) + * shell.async: Run update in async mode. Won't run next thread if + previous one didn't finished yet. Useful for long + running scripts to avoid bumblebee-status freezes + (defaults to False) +""" + +import subprocess +import threading + +import bumblebee.engine +import bumblebee.input +import bumblebee.output + + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + widget = bumblebee.output.Widget(full_text=self.get_output) + super(Module, self).__init__(engine, config, widget) + + if self.parameter('interval'): + self.interval(self.parameter('interval')) + + self._command = self.parameter('command') + self._async = bumblebee.util.asbool(self.parameter('async')) + if self._async: + self._output = 'Computing...' + self._current_thread = None + else: + self._output = '' + + # LMB and RMB will update output regardless of timer + engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd=self.update) + engine.input.register_callback(self, button=bumblebee.input.LEFT_MOUSE, cmd=self.update) + + def get_output(self, _): + return self._output + + def update(self, _): + # if requested then run not async version and just execute command in this thread + if not self._async: + self._output = self._get_command_output_or_error(self._command) + return + + # if previous thread didn't end yet then don't do anything + if self._current_thread: + return + + # spawn new thread to execute command and pass callback method to get output from it + self._current_thread = threading.Thread(target=self._run_command_in_thread, + args=(self._command, self._output_function)) + self._current_thread.start() + + @staticmethod + def _get_command_output_or_error(command): + try: + command_output = subprocess.check_output(command, + shell=True, + stderr=subprocess.STDOUT) + return command_output.decode('utf-8').strip() + except subprocess.CalledProcessError as exception: + exception_output = exception.output.decode('utf-8').replace('\n', '') + return 'Status:{} output:{}'.format(exception.returncode, exception_output) + + def _run_command_in_thread(self, command, output_callback): + output_callback(self._get_command_output_or_error(command)) + + def _output_function(self, text): + self._output = text + # clear this thread data, so next update will spawn a new one + self._current_thread = None + +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4