summaryrefslogtreecommitdiff
path: root/archey3
diff options
context:
space:
mode:
Diffstat (limited to 'archey3')
-rwxr-xr-xarchey3374
1 files 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<func>\w+)\((|(?P<args>[\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()