From b8d0bdc814a1c7ff2e5ab3080fbe36d70756c569 Mon Sep 17 00:00:00 2001 From: Laurie Clark-Michalek Date: Mon, 6 Jun 2011 20:31:07 +0000 Subject: Added working config file, along with logging via logbook --- archey3 | 374 +++++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 202 insertions(+), 172 deletions(-) diff --git a/archey3 b/archey3 index a3348bf..d11a836 100755 --- a/archey3 +++ b/archey3 @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # # archey3 [version 0.2-2] # @@ -18,27 +18,6 @@ # fs:x = return space of partition at x # mpd:stat,hostname,port = returns value of an mpd stat (options: artists|albums|songs) -DISPLAY = [ - 'distro', # Display Operating System - 'uname:n', # Display Machine Hostname - 'uname:r', # Display Kernel Version - 'uptime', # Display System Uptime - 'sensors:coretemp-*', # Display System Core tempature - 'wm', # Display Window Manager - 'de', # Display Desktop Environment - 'packages', # Display Number of Packages Installed - 'ram', # Display RAM Usage - 'uname:p', # Display CPU Model - 'env:shell', # Display Current Shell - 'env:editor', # Display Editor, using EDITOR env - 'mpd:songs', -# 'fs:/boot', # Display /boot Partition Usage -# 'fs:/home', # Display /home Partition Usage -# 'fs:/MOUNT/POINT', # Display * Partition, Edit To Your Needs - 'fs:/', # Display / Partition Usage -] - - # Import libraries import subprocess, optparse, re, sys, configparser from subprocess import Popen, PIPE @@ -46,6 +25,9 @@ from optparse import OptionParser from getpass import getuser from time import ctime, sleep from os import getenv +import re +import os.path +from logbook import Logger UNAME_FLAG_MEANINGS = { 'a': 'System Infomation', @@ -80,7 +62,7 @@ LOGOS = {'Arch Linux': '''{c1} \x1b[0m''' } -FUNC_MAPPINGS = { +CLASS_MAPPINGS = { 'distro': 'distroCheck', 'uname': 'unameDisplay', 'uptime': 'uptimeDisplay', @@ -141,19 +123,27 @@ COLORS = { } class display(object): - __slots__ = ['command_line', 'arg1', 'arg2', 'arg3', 'stdindata', - 'proccess', '_parent', 'config'] - command_line = '' arg1 = '' arg2 = '' arg3 = '' stdindata = '' - def __init__(self, config, parent=None): + def __init__(self, args, config, logger, parent=None): self.config = config + self.logger = logger self._parent = parent + @staticmethod + def call_command(command): + """ + Calls a command, waits for it to exit and returns all text from stdout. + Discards all other information. + """ + proc = Popen(command.split(), stdout=PIPE) + proc.wait() + return proc.communicate()[0].decode() + def run_command(self): if self.command_line: if '{arg3}' in self.command_line: @@ -166,14 +156,15 @@ class display(object): else: cmd = self.command_line try: - self.proccess = Popen(cmd.split(), stdin=PIPE, stdout=PIPE, + self.process = Popen(cmd.split(), stdin=PIPE, stdout=PIPE, stderr=PIPE) except Exception as e: pass def render(self): - (stdoutdata, stderrdata) = self.proccess.communicate(self.stdindata + (stdoutdata, stderrdata) = self.process.communicate(self.stdindata or None) + return self.format_output(stdoutdata.decode()) def color_me(self, output, number=None, low=30, low_color='green', @@ -192,16 +183,21 @@ class display(object): return '{0}{1}{2}'.format(self._parent.color(color), output, self._parent.color('clear')) + + def process_exists(self, key): + global PROCESSES + return PROCESSES(key) + class fsDisplay(display): command_line = "df -TPh {arg1}" conversions = { 'binary': { - 'KB': 2 ** 10, - 'MB': 2 ** 20, - 'GB': 2 ** 30, - 'TB': 2 ** 40, + 'KiB': 2 ** 10, + 'MiB': 2 ** 20, + 'GiB': 2 ** 30, + 'TiB': 2 ** 40, }, 'si': { 'KB': 10 ** 3, @@ -211,17 +207,21 @@ class fsDisplay(display): }, } - def __init__(self, config, path='/', parent=None): - self.arg1 = path - self.config = config - self._parent = parent + def __init__(self, **kwargs): + super().__init__(**kwargs) + try: + self.arg1 = kwargs["args"][0] + except IndexError: + self.logger.error( + "Did not any arguments, require one, the fs to display") + raise def format_output(self, instring): values = [line for line in instring.split('\n') if line][1].split() used = values[3] total = values[2] fstype = values[1] - conversion_type = self.config.get('fs', 'unit').lower() + conversion_type = self.config.get('fs', 'unit', fallback="si").lower() conversions = self.conversions[conversion_type] mount = '/root' if self.arg1 == '/' else self.arg1 @@ -260,11 +260,18 @@ class ramDisplay(display): class sensorDisplay(display): command_line = "sensors {arg1}" - def __init__(self, config, sensors='', parent=None): - self.config = config - self._parent = parent - arg_from_conf = config.get('sensor', 'sensor', 'coretemp-*') - arg_from_arg = sensors + def __init__(self, **kwargs): + super().__init__(**kwargs) + + arg_from_conf = self.config.get('sensor', 'sensor', + fallback='coretemp-*') + try: + arg_from_arg = kwargs["args"][0] + except IndexError: + self.logger.error( + "Did not get any arguments, require one, the sensor to display.") + raise + if arg_from_arg: self.arg1 = arg_from_arg else: @@ -292,10 +299,15 @@ class sensorDisplay(display): return out class envDisplay(display): - def __init__(self, config, env='SHELL', parent=None): - self.arg1 = env - self.config = config - self._parent = parent + def __init__(self, **kwargs): + try: + self.arg1 = kwargs["args"][0] + except IndexError: + self.logger.error("Did not get any arguments, require one," + + " the env variable to display.") + raise + + super().__init__(**kwargs) def render(self): argvalue = getenv(self.arg1.upper()) @@ -304,10 +316,17 @@ class envDisplay(display): class unameDisplay(display): command_line = "uname {arg1}" - def __init__(self, config, flag=False, parent=None): - self.config = config - self._parent = parent - arg_from_conf = config.get('uname', 'argument', '') + def __init__(self, **kwargs): + super().__init__(**kwargs) + + try: + flag = kwargs["args"][0] + except IndexError: + self.logger.error("Did not get any arguments, require one," + + " the flag to pass to uname") + raise + + arg_from_conf = self.config.get('uname', 'argument', fallback="") arg_from_arg = flag if arg_from_arg: self.arg1 = '-' + arg_from_arg @@ -346,28 +365,26 @@ class packageDisplay(display): class distroCheck(display): def render(self): try: - rc = open("/etc/pacman.conf") + _ = open("/etc/pacman.conf") except IOError: - distro = render(unameDisplay("o"))[1] + distro = self.call_command("uname -o") else: distro = "Arch Linux" - distro = ' '.join([distro, render(unameDisplay('m'))[1]]) + distro = '{0} {1}'.format(distro, self.call_command("uname -m")) return "OS", distro -class proccessCheck(display): - __slots__ = display.__slots__ + ['_processes'] +class processCheck(display): command_line = "ps -u {arg1}" render = lambda self: self - def __init__(self, config, parent=None): + def __init__(self, **kwargs): self.arg1 = getuser() - self.config = config - self._parent = parent + super().__init__(**kwargs) def run_command(self): super().run_command() - out = str(self.proccess.communicate()[0]) + out = str(self.process.communicate()[0]) self._processes = set([line.split()[3] for line in out.split('\\n') if\ len(line.split()) == 4]) @@ -378,22 +395,22 @@ class proccessCheck(display): class wmDisplay(display): def render(self): - if not self.config.get('wm', 'manual', False): - return "WM", self.config.get('de', 'manual') + if self.config.get('wm', 'manual', fallback=False): + return "WM", self.config.get('wm', 'manual') wm = '' for key in WM_DICT.keys(): - if processes(key): + if self.process_exists(key): wm = key break return "WM", WM_DICT[wm] class deDisplay(display): def render(self): - if not self.config.get('de', 'manual', False): + if self.config.get('de', 'manual', fallback=False): return "DE", self.config.get('de', 'manual') de = '' for key in DE_DICT.keys(): - if processes(key): + if self.process_exists(key): de = key break return "DE", DE_DICT[de] @@ -405,39 +422,16 @@ class mpdDisplay(display): """ command_line = "mpc stats --host {arg1} --port {arg2}" - def __init__(self, config, stat='songs', parent=None): - self.stat = stat - self.hostname = config.get('mpd', 'host', 'localhost') - self.port = int(config.get('mpd', 'port', 8800)) - self.arg1 = hostname - self.arg2 = port - self.method = config.get('mpd', 'method', 'mpc') - self.config = config - self._parent = parent - - def run_command(self): - if self.method == 'python3mpd': - self.proccess = mpd.MPDClient() - try: - self.proccess.connect(self.hostname, self.port) - except Exception: - self.method == 'mpc' - super().run_command() - else: - super().run_command() - - def render(self): - if self.method == 'python3mpd': - try: - return '{statname} in MPD database'.format( - statname=self.stat.title()), self.proccess.stats()[self.stat] - except Exception: - return False - else: - if hasattr(self, "proccess"): - return super().render() - else: - return None + def __init__(self, **kwargs): + super().__init__(**kwargs) + + try: + self.stat = kwargs["args"][0] + except IndexError: + self.logger.error("Did not get any arguments, require one," + + " the stat to display.") + self.arg1 = self.config.get('mpd', 'host', fallback='localhost') + self.arg2 = self.config.getint('mpd', 'port', fallback=8800) def format_output(self, instring): lines = instring.split('\n') @@ -447,7 +441,7 @@ class mpdDisplay(display): stats['albums'] = lines[1].split(':')[1].strip() stats['songs'] = lines[2].split(':')[1].strip() #if people don't have mpc installed then return None) - except ValueError: + except: return False return ('{statname} in MPD database'.format(statname=self.stat.title()), @@ -462,49 +456,52 @@ class ArcheyConfigParser(configparser.SafeConfigParser): defaults = {'core': {'align': 'top', 'color': 'blue', - 'modules': - """ - os, hostname, kernel, uptime, wm, de, pacman, ram, cpu, - env:editor, df:/, mpd:albums - """ + 'display_modules': + """\ +distro(), uname(n), distro(r), uptime(), wm(), de(), packages(), ram(),\ + uname(p), env(editor), df(/), mpd(albums)""" }, } - def __init__(self): - super(ArcheyConfigParser, self).__init__() - - def read(self, file_location="$XDG_CONFIG_HOME/archey3.cfg"): + def read(self, file_location=None): """ Loads the config options stored in at file_location. If file_location does not exist, it will attempt to load from the default config location ($XDG_CONFIG_HOME/archey3.cfg). If that does not exist, it will write a default config file to $XDG_CONFIG_HOME/archey3.cfg. """ - config_location = os.path.expandvars(os.path.expanduser(file_location)) + + config_location = os.path.expandvars(os.path.expanduser( + file_location or "$XDG_CONFIG_HOME/archey3.cfg")) + loaded = super(ArcheyConfigParser, self).read(config_location) - if file_location == "$XDG_CONFIG_HOME/archey3.cfg": + + if file_location == None and not loaded: self.load_default_config() - self.write_config(file_location) + self.write_config(config_location) if not loaded: #Try with default loaded = super(ArcheyConfigParser, self).read() + return loaded def load_default_config(self): """ Loads the config options stored at self.defaults. """ - for section, values in self.defaults.iteritems(): - for option, value in values.iteritems(): + for section, values in self.defaults.items(): + if not self.has_section(section): + self.add_section(section) + + for option, value in values.items(): #strip any excess spaces - while value.find(' ') != -1: - value = value.replace(' ', ' ') + value = re.sub("( +)", " ", value) self.set(section, option, value) def write_config(self, location): """ Writes the current config to the given location. """ - with open(location, 'wb') as configfile: + with open(location, 'w') as configfile: self.write(configfile) @@ -530,59 +527,78 @@ def screenshot(): print('Screenshot failed with return code {0}.'.format( e.returncode)) -def render(instance): - try: - instance.run_command() - return instance.render() - except: - return None - #------------ Display object --------- class Archey(object): - def __init__(self, display=None, colorscheme='blue'): - self.display = display or [] + DISPLAY_PARSING_REGEX = "(?P\w+)\((|(?P[\w, /]+))\)" + + def __init__(self, config, options): + self.config = config + self.log_level = int(options.log_level) + self.logger = Logger("Core", self.log_level) + + self.display = config.get("core", "display_modules") + colorscheme = options.color or config.get("core", "color") for key in COLORS.keys(): if key == colorscheme: self.colorcode = COLORS[key] - self.distro_name = ' '.join(render(distroCheck())[1].split()[:-1]) + global PROCESSES + PROCESSES = self.render_class(processCheck, ()) + + self.distro_name = ' '.join( + self.render_class(distroCheck, ())[1].split()[:-1]) def render(self): results = self.prepare_results() - print(LOGOS[self.distro_name].format(c1=self.color(1), - c2=self.color(2), - results = results - )) + return LOGOS[self.distro_name].format(c1=self.color(1), + c2=self.color(2), + results = results + ) def prepare_results(self): + """ + Renders all classes found in the display array, and then returns them + as a list. The returned list will be exactly 18 items long, with any + left over spaces being filled with empty strings. + """ outputs = [] # Run functions found in 'display' array. - for func in self.display: - if len(func.split(':')) > 1: - cls = eval(FUNC_MAPPINGS[func.split(':')[0]]) - args = tuple(func.split(':')[1].split(',')) - else: - cls = eval(FUNC_MAPPINGS[func]) - args = () + for func_name, args in self.parse_display(): + cls = eval(CLASS_MAPPINGS[func_name]) - out = render(cls(*args, parent=self)) - if isinstance(out, list): - for o in out: - outputs.append(o) - elif out: - outputs.append(out) - else: - continue + line = self.render_class(cls, args) + if hasattr(line, "__iter__") and len(line) != 2: + outputs.extend(line) + elif line: + outputs.append(line) - for i in range(18): - if i < len(outputs): - outputs[i] = self.format_item(outputs[i]) + outputs = [self.format_item(line) for line in outputs] + return outputs + [""] * (18 - len(outputs)) + + def parse_display(self): + """ + Iterates over the display attribute of the Archey class, and tries to + parse them using the DISPLAY_PARSING_REGEX. + """ + for func in self.display.split(","): + func = func.strip() + + info = re.match(self.DISPLAY_PARSING_REGEX, func) + if not info: + self.logger.error( + "Could not parse display string {0}".format(func)) + continue + + groups = info.groupdict() + if groups["args"]: + args = [arg.strip() for arg in groups["args"].split(",")] else: - outputs.append('') - - return outputs + args = () + + yield groups["func"], args + raise StopIteration def format_item(self, item): title = item[0].rstrip(':') @@ -615,17 +631,35 @@ class Archey(object): second_bitty_bit = '3{0}m'.format(COLORS[code]) return first_bitty_bit + second_bitty_bit - + + def render_class(self, cls, args): + """ + Returns the result of the run_command method for the class passed. + """ + logger = Logger(cls.__name__, self.log_level) + try: + instance = cls(args=args, config=self.config, logger=logger, + parent=self) + except Exception as e: + self.logger.error( + "Could not instantiate {0}, failed with error {1}".format( + cls.__name__, e)) + return + try: + instance.run_command() + return instance.render() + except Exception as e: + self.logger.error( + "Could not render line for {0}, failed with error {1}".format( + cls.__name__, e)) def main(): - global processes - processes = render(proccessCheck()) - parser = OptionParser( - usage='%prog [-c COLOR] [-s, --screenshot] [-d, --display]', - description="""Archey is a utility to take """, - version="%prog 0.2-1") - parser.add_option('-c', + usage='%prog', + description="""%prog is a utility to display system info and take\ + screenshots""", + version="%prog 0.3") + parser.add_option('-c', '--color', action='store', default='blue', type='choice', dest='color', choices=('black', 'red', @@ -639,22 +673,18 @@ def main(): cyan, white [Default: blue]""") parser.add_option('-s', '--screenshot', action='store_true', dest='screenshot', help='Take a screenshot') - parser.add_option('-d', '--display', - action='store', dest='display', - help="""Define info to be displayed by archey. DISPLAY - must be in the format "archey_display_module:arg1,arg2,arg - 3.second_display_module". Availible display modules are un - ame, fs, env, uptime, sensors, ram, de and wm. - See the DISPLAY list in the archey source file for more - info.""") + parser.add_option('--config', + action='store', dest='config', default=None, + help="Set the location of the config file to load.") + parser.add_option('--debug', + action='store', dest='log_level', default=1) (options, args) = parser.parse_args() - if options.display: - global DISPLAY - DISPLAY = options.display.split('.') + config = ArcheyConfigParser() + config.read(options.config) - archey = Archey(display=DISPLAY, colorscheme=options.color) - archey.render() + archey = Archey(config=config, options=options) + print(archey.render()) if options.screenshot: screenshot() -- cgit v1.2.3