bumblebee-status/bumblebee_status/core/config.py
tobi-wan-kenobi b42323013d fix(config): make config file keys case sensitive
Since the configparser library by default parses keys case insensitive
(all lowercase), certain mappings, especially in the pulseaudio modules,
could fail ("internal" pulseaudio device names are matched against
entries in the configuration).

fixes #992
2023-09-15 15:06:57 +02:00

355 lines
11 KiB
Python

import os
import ast
from configparser import RawConfigParser
import sys
import glob
import textwrap
import argparse
import logging
import core.theme
import util.store
import util.format
import modules.core
import modules.contrib
log = logging.getLogger(__name__)
MODULE_HELP = "Specify a space-separated list of modules to load. The order of the list determines their order in the i3bar (from left to right). Use <module>:<alias> to provide an alias in case you want to load the same module multiple times, but specify different parameters."
PARAMETER_HELP = (
"Provide configuration parameters in the form of <module>.<key>=<value>"
)
THEME_HELP = "Specify the theme to use for drawing modules"
def all_modules():
"""Returns a list of all available modules (either core or contrib)
:return: list of modules
:rtype: list of strings
"""
result = {}
for path in [modules.core.__file__, modules.contrib.__file__]:
path = os.path.dirname(path)
for mod in glob.iglob("{}/*.py".format(path)):
result[os.path.basename(mod).replace(".py", "")] = 1
res = list(result.keys())
res.sort()
return res
class print_usage(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs):
argparse.Action.__init__(self, option_strings, dest, nargs, **kwargs)
self._indent = " " * 2
def __call__(self, parser, namespace, value, option_string=None):
if value == "modules":
self._args = namespace
self._format = "plain"
self.print_modules()
elif value == "modules-rst":
self._args = namespace
self._format = "rst"
self.print_modules()
elif value == "themes":
self.print_themes()
sys.exit(0)
def print_themes(self):
print(", ".join(core.theme.themes()))
def print_modules(self):
basepath = os.path.abspath(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
)
rst = {}
if self._format == "rst":
print(".. THIS DOCUMENT IS AUTO-GENERATED, DO NOT MODIFY")
print(".. To change this document, please update the docstrings in the individual modules")
for m in all_modules():
try:
module_type = "core"
filename = os.path.join(basepath, "modules", "core", "{}.py".format(m))
if not os.path.exists(filename):
filename = os.path.join(
basepath, "modules", "contrib", "{}.py".format(m)
)
module_type = "contrib"
if not os.path.exists(filename):
log.warning("module {} not found".format(m))
continue
doc = None
with open(filename) as f:
tree = ast.parse(f.read())
doc = ast.get_docstring(tree)
if not doc:
log.warning("failed to find docstring for {}".format(m))
continue
if self._format == "rst":
if os.path.exists(
os.path.join(basepath, "..", "screenshots", "{}.png".format(m))
):
doc = "{}\n\n.. image:: ../screenshots/{}.png".format(doc, m)
rst[module_type] = rst.get(module_type, [])
rst[module_type].append({"module": m, "content": doc})
else:
print(
textwrap.fill(
"{}:".format(m),
80,
initial_indent=self._indent * 2,
subsequent_indent=self._indent * 2,
)
)
for line in doc.split("\n"):
print(
textwrap.fill(
line,
80,
initial_indent=self._indent * 3,
subsequent_indent=self._indent * 6,
)
)
except Exception as e:
log.warning(e)
if self._format == "rst":
print("List of modules\n===============")
for k in ["core", "contrib"]:
print("\n{}\n{}\n".format(k, "-" * len(k)))
for mod in rst[k]:
print("\n{}\n{}\n".format(mod["module"], "~" * len(mod["module"])))
print(mod["content"])
class Config(util.store.Store):
"""Represents the configuration of bumblebee-status (either via config file or via CLI)
:param args: The arguments passed via the commandline
"""
def __init__(self, args):
super(Config, self).__init__()
parser = argparse.ArgumentParser(
description="bumblebee-status is a modular, theme-able status line generator for the i3 window manager. https://github.com/tobi-wan-kenobi/bumblebee-status/wiki"
)
parser.add_argument(
"-c",
"--config-file",
action="store",
default=None,
help="Specify a configuration file to use"
)
parser.add_argument(
"-m", "--modules", nargs="+", action="append", default=[], help=MODULE_HELP
)
parser.add_argument(
"-p",
"--parameters",
nargs="+",
action="append",
default=[],
help=PARAMETER_HELP,
)
parser.add_argument("-t", "--theme", default=None, help=THEME_HELP)
parser.add_argument(
"-i",
"--iconset",
default="auto",
help="Specify the name of an iconset to use (overrides theme default)",
)
parser.add_argument(
"-a",
"--autohide",
nargs="+",
default=[],
help="Specify a list of modules to hide when not in warning/error state",
)
parser.add_argument(
"-e",
"--errorhide",
nargs="+",
default=[],
help="Specify a list of modules that are hidden when in state error"
)
parser.add_argument(
"-d", "--debug", action="store_true", help="Add debug fields to i3 output"
)
parser.add_argument(
"-f",
"--logfile",
help="destination for the debug log file, if -d|--debug is specified; defaults to stderr",
)
parser.add_argument(
"-r",
"--right-to-left",
action="store_true",
help="Draw widgets from right to left, rather than left to right (which is the default)",
)
parser.add_argument(
"-l",
"--list",
choices=["modules", "themes", "modules-rst"],
help="Display a list of available themes or available modules, along with their parameters",
action=print_usage,
)
self.__args = parser.parse_args(args)
if self.__args.config_file:
cfg = self.__args.config_file
cfg = os.path.expanduser(cfg)
self.load_config(cfg)
else:
for cfg in [
"~/.bumblebee-status.conf",
"~/.config/bumblebee-status.conf",
"~/.config/bumblebee-status/config",
]:
cfg = os.path.expanduser(cfg)
self.load_config(cfg)
parameters = [item for sub in self.__args.parameters for item in sub]
for param in parameters:
if not "=" in param:
log.error(
'missing value for parameter "{}" - ignoring this parameter'.format(
param
)
)
continue
key, value = param.split("=", 1)
self.set(key, value)
"""Loads parameters from an init-style configuration file
:param filename: path to the file to load
"""
def load_config(self, filename, content=None):
if os.path.exists(filename) or content != None:
log.info("loading {}".format(filename))
tmp = RawConfigParser()
tmp.optionxform = str
if content:
tmp.read_string(content)
else:
tmp.read(u"{}".format(filename))
if tmp.has_section("module-parameters"):
for key, value in tmp.items("module-parameters"):
self.set(key, value)
if tmp.has_section("core"):
for key, value in tmp.items("core"):
self.set(key, value)
"""Returns a list of configured modules
:return: list of configured (active) modules
:rtype: list of strings
"""
def modules(self):
list_of_modules = [item for sub in self.__args.modules for item in sub]
if list_of_modules == []:
list_of_modules = util.format.aslist(self.get('modules', []))
return list_of_modules
"""Returns the global update interval
:return: update interval in seconds
:rtype: float
"""
def interval(self, default=1):
return util.format.seconds(self.get("interval", default))
"""Returns the global popup menu font size
:return: popup menu font size
:rtype: int
"""
def popup_font_size(self, default=12):
return util.format.asint(self.get("popup_font_size", default))
"""Returns whether debug mode is enabled
:return: True if debug is enabled, False otherwise
:rtype: boolean
"""
def debug(self):
return self.__args.debug
"""Returns whether module order should be reversed/inverted
:return: True if modules should be reversed, False otherwise
:rtype: boolean
"""
def reverse(self):
return self.__args.right_to_left
"""Returns the logfile location
:return: location where the logfile should be written
:rtype: string
"""
def logfile(self):
return self.__args.logfile
"""Returns the configured theme name
:return: name of the configured theme
:rtype: string
"""
def theme(self):
return self.__args.theme or self.get("theme") or "default"
"""Returns the configured iconset name
:return: name of the configured iconset
:rtype: string
"""
def iconset(self):
return self.__args.iconset
"""Returns whether a module should be hidden if their state is not warning/critical
:return: True if module should be hidden automatically, False otherwise
:rtype: bool
"""
def autohide(self, name):
return name in self.__args.autohide or name in util.format.aslist(self.get("autohide", []))
"""Returns which modules should be hidden if they are in state error
:return: returns True if name should be hidden, False otherwise
:rtype: bool
"""
def errorhide(self, name):
return name in self.__args.errorhide
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4