From 252dae4b12ee57ed93bca2f50f699739e0fb6c5a Mon Sep 17 00:00:00 2001 From: PavelMuhortov Date: Thu, 24 Jun 2021 19:00:59 +0300 Subject: [PATCH] add confutil --- README.md | 54 +++++++++++++++++---- confutil.py | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 confutil.py diff --git a/README.md b/README.md index d284f6f..c861a49 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # utils +Small tools needed to solve immediate tasks independently or as part of a project -* [camsutil](https://git.hmp.today/pavel.muhortov/utils#camsutil) -* [cronutil](https://git.hmp.today/pavel.muhortov/utils#cronutil) -* [ffmpeger.py](https://git.hmp.today/pavel.muhortov/utils#ffmpeger-py) -* [procutil.py](https://git.hmp.today/pavel.muhortov/utils#procutil-py) -* [sendmail.py](https://git.hmp.today/pavel.muhortov/utils#sendmail-py) +* [`camsutil`](https://git.hmp.today/pavel.muhortov/utils#camsutil) +* [`cronutil`](https://git.hmp.today/pavel.muhortov/utils#cronutil) +* [`confutil`.py](https://git.hmp.today/pavel.muhortov/utils#confutil-py) +* [`ffmpeger`.py](https://git.hmp.today/pavel.muhortov/utils#ffmpeger-py) +* [`procutil`.py](https://git.hmp.today/pavel.muhortov/utils#procutil-py) +* [`sendmail`.py](https://git.hmp.today/pavel.muhortov/utils#sendmail-py) ____ -## camsutil +## `camsutil` **Description:** Creation of a request to the camera API based on the prepared template **Dependencies:** Python 3 (tested version 3.9.5) @@ -48,7 +50,7 @@ with open('img.jpg', 'wb') as output: ``` ____ -## cronutil +## `cronutil` **Description:** Control wrapper for the [schedule](https://github.com/dbader/schedule) package **Dependencies:** Python 3 (tested version 3.9.5) @@ -87,7 +89,39 @@ cron.start() ``` ____ -## ffmpeger.py +## `confutil`.py +**Description:** Parser of configs, arguments, parameters +**Dependencies:** Python 3 (tested version 3.9.5) + +Example config to parse: +```text +[main] +# This block contains basic parameters + + +[httpd] +# This block contains parameters for the http server + +# Address to which to bind listening +#address=0.0.0.0; +# Port to which to bind listening. Port below 1024 requires root privileges. +port=8800; +# Working directory (available to everyone) +directory=www; +``` + +Example usage in Python: +```Python +from os import path +from confutil import Parse + +conf = path.splitext(__file__)[0] + '.conf' +if path.exists(conf): + print(Parse(parameters=conf, block='httpd')) +``` + +____ +## `ffmpeger`.py **Description:** FFmpeg management from Python **Dependencies:** Python 3 (tested version 3.9.5), installed or downloaded ffmpeg, [procutil.py](https://git.hmp.today/pavel.muhortov/utils#procutil-py) in the same directory @@ -120,7 +154,7 @@ FFmpeg.run(src='null, anull', preset='240p', fps=10) ``` ____ -## procutil.py +## `procutil`.py **Description:** Find a running process from Python **Dependencies:** Python 3 (tested version 3.9.5) @@ -152,7 +186,7 @@ if processes: for process in processes: print(process) ``` -## sendmail.py +## `sendmail`.py **Description:** Sending email from Python **Dependencies:** Python 3 (tested version 3.9.5) diff --git a/confutil.py b/confutil.py new file mode 100644 index 0000000..59e20cc --- /dev/null +++ b/confutil.py @@ -0,0 +1,135 @@ +from argparse import ArgumentParser +from os import path, sep + + +class Parse: + """ + Parser of configs, arguments, parameters + """ + def __init__(self, parameters, block: str = None): + """ + Object constructor + :param parameters: dictionary as "key":"value" or + ArgumentParser class object or + string path to the file or + string as "var1=val1;var2=val2" + :param block: string name of target block from text + """ + self.data = {} + if type(parameters) is dict: + self._dict2dict(parameters) + if type(parameters) is ArgumentParser: + self._dict2dict(self.argv2dict(parameters)) + if type(parameters) is str: + if path.exists(parameters): + self._dict2dict(self.strs2dict(self.conf2strs(parameters), block)) + else: + self._dict2dict(self.strs2dict(parameters, block)) + + def __str__(self): + """ + Overrides method for print(object) + :return: string with contents of the object's dictionary + """ + string = '' + for key, val in self.data.items(): + string += str(type(val)) + ' ' + str(key) + ' = ' + str(val) + '\n' + return string + + def _dict2dict(self, dictionary: dict): + """ + Updates or adds dictionary data + :param dictionary: dictionary as "key":"value" + :return: None + """ + self.data.update(dictionary) + + def expand(self, store: str = None): + """ + Expand dictionary "key":"name.conf" to dictionary "key":{subkey: subval} + :param store: string path to directory with name.conf + :return: expanded dictionary as "key":{subkey: subval} + """ + for key in self.data: + if store: + config = store + sep + self.data[key] + else: + config = self.data[key] + with open(config) as file: + self.data[key] = Parse(file.read()).data + return self.data + + @classmethod + def argv2dict(cls, parser: ArgumentParser): + """ + Converts startup arguments to a dictionary + :param parser: argparse.ArgumentParser class object + :return: dictionary as "key":"value" + """ + parser = ArgumentParser(add_help=False, parents=[parser]) + return vars(parser.parse_args()) + + @classmethod + def conf2strs(cls, config: str): + """ + Builds a dictionary from a file containing parameters + :param config: string path to the file + :return: string as "var1=val1;\nvar2=val2;" + """ + with open(config) as file: + raw = file.read() + strs = '' + for line in raw.splitlines(): + if not line.lstrip().startswith('#'): + strs += line + '\n' + return strs + + @classmethod + def strs2dict(cls, strings: str, blockname: str): + """ + Builds a dictionary from a strings containing parameters + :param strings: string as "var1=val1;var2=val2;" + :param blockname: string name of target block from text + :return: dictionary as "key":"value" + """ + dictionary = {} + if blockname: + strings = cls.block(blockname, strings) + for line in strings.replace('\n', ';').split(';'): + if not line.lstrip().startswith('#') and "=" in line: + dictionary[line.split('=')[0].strip()] = line.split('=')[1].strip().split(';')[0].strip() + return dictionary + + @classmethod + def str2bool(cls, value: str): + """ + Converts a string value to boolean + :param value: string containing "true" or "false", "yes" or "no", "1" or "0" + :return: bool True or False + """ + return str(value).lower() in ("true", "yes", "1") + + @classmethod + def block(cls, blockname: str, text: str): + """ + Cuts a block of text between line [blockname] and line [next block] or EOF + :param blockname: string in [] after which the block starts + :param text: string of text from which the block is needed + :return: string of text between line [block name] and line [next block] + """ + level = 1 + save = False + result = '' + for line in text.splitlines(): + if line.startswith('[') and blockname in line: + level = line.count('[') + save = True + elif line.startswith('[') and '['*level in line: + save = False + elif save: + result += line + '\n' + return result + + +if __name__ == "__main__": + pass