# -*- coding: UTF-8 -*- import os import sys import getopt from yaml import load, YAMLError try: from yaml import CSafeLoader as SafeLoader except ImportError: from yaml import SafeLoader from pathlib import Path class Config(): def __init__(self, scriptname, progver, commands): scriptname = os.path.realpath(scriptname) self.basedir = os.path.dirname(scriptname) self.progname = os.path.basename(scriptname) self.progver = progver self.commands = commands # options self.help = False self.version = False self.verbose = False self.config = "config" self.params = {} # command self.cmd = None # config params self.config_params = {} def usage(self, exitcode): usage = """Usage: """ + self.progname + """ [] [] Commands: """ for cmd, desc in self.commands: usage += " " + cmd + (' ' if len(cmd) >= 31 else ' ' * (31-len(cmd))) + desc + "\n" usage += """ Options: -h, --help Print this help -V, --version Print version number -c, --config dir Specify config directory to read [""" + self.config + """] -s, --set key=value Override config parameter key=value -v, --verbose Print verbose messages """ sys.stderr.write(usage) if exitcode is not None: sys.exit(exitcode) def parse(self, argv): try: (opt, args) = getopt.gnu_getopt(argv[1:], "hVvc:s:", [ "help", "version", "verbose", "config=", "set=" ]) except getopt.GetoptError as e: sys.stderr.write("Error: %s\n" % str(e)) self.usage(2) for o, a in opt: if o == '-h' or o == "--help": self.help = True elif o == '-V' or o == "--version": self.version = True elif o == '-v' or o == "--verbose": self.verbose = True elif o == '-c' or o == "--config": if (a == ''): sys.stderr.write("Error: option " + o + " requires a non-empty argument\n") self.usage(2) self.config = a elif o == '-s' or o == "--set": parts = a.partition('=') if (parts[1] != '='): sys.stderr.write("Error: option " + o + " requires argument in the format key=value\n") self.usage(2) if (parts[0] == ''): sys.stderr.write("Error: option " + o + " requires argument in the format key=value; key may not be an empty string\n") self.usage(2) self.params[parts[0]] = parts[2] if self.version: sys.stdout.write("Version: " + self.progver + "\n") if self.help: self.usage(None) if self.version or self.help: sys.exit(0) self.load_config() for c in args: if self.cmd != None: sys.stderr.write("Multiple commands specified: " + self.cmd + " vs " + c + "\n") self.usage(2) for cmd, _ in self.commands: if c == cmd: self.cmd = c break if self.cmd == None: sys.stderr.write("Unknown command specified: " + c + "\n") self.usage(2) if self.cmd == None: sys.stderr.write("Missing command\n") self.usage(2) for k, v in self.params.items(): self.config_params[k] = v if self.verbose: sys.stdout.write("Config parameters:\n") for k, v in self.config_params.items(): sys.stdout.write(" " + k + "=" + str(v) + "\n") def merge_config(self, src, dest): for key, value in src.items(): if isinstance(value, dict): node = dest.setdefault(key, {}) self.merge_config(value, node) else: dest[key] = value return dest def parse_file(self, filepath): try: obj = load(open(filepath, 'r'), SafeLoader) self.merge_config(obj, self.config_params) except YAMLError as exc: sys.stdout.write("Error: reading configuration file failed: %s" % (exc)) def load_config(self): configdir = self.config if self.config.startswith('/') else self.basedir + '/' + self.config if self.verbose: sys.stdout.write("Reading config directory " + configdir + "\n") if not Path(configdir).is_dir(): sys.stderr.write("Warning: config directory " + configdir + " does not exist\n") return with os.scandir(configdir) as it: for entry in it: if not entry.name.startswith('.') and (entry.name.endswith('.yml') or entry.name.endswith('.yaml')) and entry.is_file(): if self.verbose: sys.stdout.write("Parsing config file " + entry.path + "\n") self.parse_file(entry.path)