Source code for msa.config

from typing import List, Dict

COMMENT_CHAR = '#'
SECTION_HEADER_START_CHAR = '['
SECTION_HEADER_END_CHAR = ']'


[docs]class Section: """Holds a group of keys, each key can have multiple or no values assigned""" def __init__(self, name: str): self._name = name self._arr = {}
[docs] def set(self, key: str, index: int, val: str) -> None: if not self.has(key): self.create_key(key) if len(self.get_all(key)) < index+1: self._arr[key] += [""] * ((index + 1) - len(self._arr[key])) # if list is smaller than required, initialize values up to required index to "" self._arr[key][index] = val
[docs] def push(self, key: str, val: str) -> None: """Adds a value to the end of a key, even if there are empty values""" if not self.has(key): self.create_key(key) self._arr[key].append(val)
[docs] def create_key(self, key: str) -> None: self._arr[key] = []
[docs] def has(self, key: str) -> bool: keys = list(self._arr.keys()) if key in keys: return True return False
[docs] def get_entries(self) -> List[str]: """Returns a list of all existing keys, even if the keys are empty""" return list(self._arr.keys())
[docs] def get_all(self, key: str) -> List[str]: """Returns a list of all values within a given key""" try: return list(self._arr[key]) except KeyError: raise IndexError("Key "+str(key)+" does not exist")
def __getitem__(self, key: str) -> str: """For use with the [] operator, returns first value within a key""" try: return str(self._arr[key][0]) except (KeyError, IndexError): raise IndexError("Key "+str(key)+" either contains no values, or does not exist yet")
[docs]class Config: """A wrapper class for storing and accessing multiple sections""" def __init__(self, config_file: str, sections: Dict[str, Section]): self.filepath = config_file self.sections = sections # a dict where the keys are the names of the Sections
[docs]def load(filepath: str) -> Config: """Loads a configuration file into a Config object for use within the code""" file = open(filepath, "r") lines = file.readlines() file.close() cur_sect = None sections = {} for line in lines: line = line.strip() if len(line) == 0 or line[0] == COMMENT_CHAR: # checking for whitespace only continue elif line[0] == SECTION_HEADER_START_CHAR and line[-1] == SECTION_HEADER_END_CHAR: cur_sect = line[1:-1] if cur_sect not in sections: sections[cur_sect] = Section(cur_sect) elif "=" in line: split = line.index("=") key = line[:split].strip() val = line[split+1:].strip() index = 0 if "[" in key and key[-1] == "]": starti = key.index("[") try: index = int(key[starti+1:-1]) if index < 0: raise Exception() #Only used to run code in the exception block, alongside any exception raised by the previous line except Exception: raise ValueError("Key index must be a non-negative integer") else: key = key[:starti] if len(key) == 0: raise ValueError("Key must not be blank") sect = sections[cur_sect] sect.set(key, index, val) return Config(filepath, sections)
[docs]def save(config: Config, filepath: str) -> None: """Saves a Config object as a file to the provided file path""" if type(config) != Config: raise TypeError("Can only save instances of Config") file = open(filepath, "w") for section_name in sorted(config.sections.keys()): section = config.sections[section_name] file.write(SECTION_HEADER_START_CHAR + section_name + SECTION_HEADER_END_CHAR + "\n") for key in sorted(section.get_entries()): entries = section.get_all(key) use_index = False if len(entries) > 1: use_index = True for index, entry in enumerate(entries): line = key if use_index: line += "["+str(index)+"]" line += " = " + entry file.write(line + "\n") file.write("\n") file.close()
[docs]class ConfigError(Exception): """A type of exception to be used when encountering invalid configuration settings""" def __init__(self, sec: str, key: str, val: str, msg: str, index: int = 0): cache = str(sec)+"."+str(key) if index != 0: cache += "[" + str(index) + "]" cache += " \""+str(val)+"\" " + msg super().__init__(cache) self._sec = sec self._key = key self._val = val self._msg = msg self._index = index
[docs] def key(self) -> str: return self._key
[docs] def value(self) -> str: return self._val
[docs] def section(self) -> str: return self._sec
[docs] def message(self) -> str: return self._msg
[docs] def index(self) -> int: return self._index