def test_config_file_error() -> None: error = ConfigFileError("A") assert str(error) == "A" error = ConfigFileError("A", FileNotFoundError(1, "B")) assert str(error) == "A: B (errno: 1)" error = ConfigFileError("A", ValueError("B")) assert str(error) == "A: B"
def save_config_file(config: Config) -> None: # pragma: no cover data = config.dump() try: with open(CONFIG_FILE, "w") as fp: json_dump(data, fp, sort_keys=True, indent=2) except ValueError as err: raise ConfigFileError("Bad JSON", err) except Exception as err: raise ConfigFileError(f"Cannot save {CONFIG_FILE}", err)
def load_config_file() -> Config: # pragma: no cover config = Config() try: with open(CONFIG_FILE, "r") as fp: data = json_load(fp) except FileNotFoundError: return config except ValueError as err: raise ConfigFileError("Bad JSON", err) except Exception as err: raise ConfigFileError(f"Cannot load {CONFIG_FILE}", err) else: config.update(data) return config
def edit_config_file(editor: str = None): # pragma: no cover editor = editor or os.environ.get("VISUAL") or os.environ.get("EDITOR") if editor: editor = shlex.split(editor)[0] # Prevent arbitrary code execution elif sys.platform.startswith("win"): editor = "notepad" else: editor = "vi" try: shutil.copy(CONFIG_FILE, TMP_CONFIG_FILE) subprocess.check_call([editor, str(TMP_CONFIG_FILE)]) with open(TMP_CONFIG_FILE, "r") as fp: raw_config = json_load(fp) except Exception as err: raise ConfigFileError(f"Cannot update {CONFIG_FILE}", err) else: config = Config() config.update(raw_config) save_config_file(config) return config finally: try: TMP_CONFIG_FILE.unlink() except FileNotFoundError: pass
def init_config_file() -> Config: # pragma: no cover try: CONFIG_DIR.mkdir(parents=True, exist_ok=True) except Exception as err: raise ConfigFileError(f"Cannot create {CONFIG_DIR}", err) else: config = Config() save_config_file(config) return config
def update(self, data: Dict[str, Any]) -> None: if type(data) != dict: raise ConfigFileError("Bad JSON: expecting an object") def validate_string(name): if type(getattr(self, name)) != str: raise ConfigValueError(name, "a string") def validate_boolean(name): if type(getattr(self, name)) != bool: raise ConfigValueError(name, "true or false") def validate_number(name): value = getattr(self, name) if not (type(value) == int and 1 <= value <= 100): raise ConfigValueError(name, "an integer between 1 and 100") def validate_view_keys(name): keys = getattr(self, name) if not (type(keys) in (list, frozenset) and len(keys) > 0 and set(keys).issubset(VIEW_KEYS)): raise ConfigValueError( name, f"non-empty array of strings in {list(VIEW_KEYS)}") def validate_json_keys(name): keys = getattr(self, name) if not (type(keys) in (list, frozenset) and len(keys) > 0 and set(keys).issubset(JSON_KEYS)): raise ConfigValueError( name, f"non-empty array of strings in {list(JSON_KEYS)}") for key, val in data.items(): if hasattr(self, key): setattr(self, key, val) else: raise ConfigKeyError(key) validate_string("approx_name_suffix") validate_boolean("always_output_json") validate_boolean("display_degree_symbol") validate_boolean("display_percent_symbol") validate_boolean("uppercase_hex_codes") validate_number("default_shades_count") validate_number("get_view_color_height") validate_number("get_view_color_width") validate_number("list_view_color_width") validate_view_keys("get_view_keys") validate_view_keys("list_view_keys") validate_json_keys("json_keys") self.get_view_keys = frozenset(self.get_view_keys) self.list_view_keys = frozenset(self.list_view_keys) self.json_keys = frozenset(self.json_keys)