diff options
author | crupest <crupest@outlook.com> | 2024-11-11 01:12:29 +0800 |
---|---|---|
committer | Yuqian Yang <crupest@crupest.life> | 2025-01-14 22:36:08 +0800 |
commit | a5c2f62b0b3c4ec6ce46c57bcd99319a85900798 (patch) | |
tree | 0cb89834a1ed32bdf20597309c78c3a5cbca3c88 /tools/cru-py/cru/service/_config.py | |
parent | b9788b23bbd000d2a2d14402b4e2abacd5a57365 (diff) | |
download | crupest-a5c2f62b0b3c4ec6ce46c57bcd99319a85900798.tar.gz crupest-a5c2f62b0b3c4ec6ce46c57bcd99319a85900798.tar.bz2 crupest-a5c2f62b0b3c4ec6ce46c57bcd99319a85900798.zip |
HALF WORK: 2024.1.14
Diffstat (limited to 'tools/cru-py/cru/service/_config.py')
-rw-r--r-- | tools/cru-py/cru/service/_config.py | 247 |
1 files changed, 151 insertions, 96 deletions
diff --git a/tools/cru-py/cru/service/_config.py b/tools/cru-py/cru/service/_config.py index 6e1edda..12784c5 100644 --- a/tools/cru-py/cru/service/_config.py +++ b/tools/cru-py/cru/service/_config.py @@ -6,7 +6,7 @@ from cru.value import ( RandomStringValueGenerator, UuidValueGenerator, ) -from cru.parsing import SimpleLineConfigParser +from cru.parsing import ParseError, SimpleLineConfigParser from ._base import AppFeaturePath, AppCommandFeatureProvider, OWNER_NAME @@ -15,160 +15,215 @@ class AppConfigError(CruException): pass -class AppConfigDuplicateItemsError(AppConfigError): +class AppConfigDuplicateEntryError(AppConfigError): def __init__( - self, message: str, items: list[SimpleLineConfigParser.Item], *args, **kwargs + self, message: str, entries: list[SimpleLineConfigParser.Entry], *args, **kwargs ) -> None: super().__init__(message, *args, **kwargs) - self._items = items + self._entries = entries @property - def duplicate_items(self) -> list[SimpleLineConfigParser.Item]: - return self._items + def duplicate_entries(self) -> list[SimpleLineConfigParser.Entry]: + return self._entries @staticmethod - def duplicate_items_to_friendly_message( - items: list[SimpleLineConfigParser.Item], + def duplicate_entries_to_friendly_message( + entries: list[SimpleLineConfigParser.Entry], ) -> str: return "".join( - f"line {item.line_number}: {item.key}={item.value}\n" for item in items + f"line {entry.line_number}: {entry.key}={entry.value}\n" + for entry in entries ) def to_friendly_error(self) -> CruUserFriendlyException: e = CruUserFriendlyException( - f"Duplicate configuration items detected:\n" - f"{self.duplicate_items_to_friendly_message(self.duplicate_items)}" + f"Duplicate entries found in config file:\n" + f"{self.duplicate_entries_to_friendly_message(self.duplicate_entries)}" ) - e.__cause__ = self return e +class AppConfigFileNotFoundError(AppConfigError): + def __init__(self, message: str, file_path: str, *args, **kwargs) -> None: + super().__init__(message, *args, **kwargs) + self._file_path = file_path + + @property + def file_path(self) -> str: + return self._file_path + + def to_friendly_error(self) -> CruUserFriendlyException: + e = CruUserFriendlyException( + f"Config file not found at {self.file_path}. You may need to create one." + ) + return e + + +class AppConfigItemNotSetError(AppConfigError): + def __init__( + self, + message: str, + items: list[ConfigItem], + *args, + **kwargs, + ) -> None: + super().__init__(message, *args, **kwargs) + self._items = items + + +class AppConfigItemNotDefinedError(AppConfigError): + def __init__( + self, + message: str, + undefined_names: list[str], + configuration: Configuration, + *args, + **kwargs, + ) -> None: + super().__init__(message, *args, **kwargs) + self._undefined_names = undefined_names + self._configuration = configuration + + @property + def undefined_names(self) -> list[str]: + return self._undefined_names + + @property + def configuration(self) -> Configuration: + return self._configuration + + class ConfigManager(AppCommandFeatureProvider): def __init__(self) -> None: super().__init__("config-manager") configuration = Configuration() self._configuration = configuration - self._add_text_item("DOMAIN", "domain name") - self._add_text_item("EMAIL", "admin email address") - self._add_text_item( + self._init_app_defined_items() + + def _init_app_defined_items(self) -> None: + def _add_text(name: str, description: str) -> None: + self.configuration.add( + ConfigItem(f"{OWNER_NAME}_{name}", description, TEXT_VALUE_TYPE) + ) + + def _add_uuid(name: str, description: str) -> None: + self.configuration.add( + ConfigItem( + f"{OWNER_NAME}_{name}", + description, + TEXT_VALUE_TYPE, + default=UuidValueGenerator(), + ) + ) + + def _add_random_string( + name: str, description: str, length: int = 32, secure: bool = True + ) -> None: + self.configuration.add( + ConfigItem( + f"{OWNER_NAME}_{name}", + description, + TEXT_VALUE_TYPE, + default=RandomStringValueGenerator(length, secure), + ) + ) + + def _add_int(name: str, description: str) -> None: + self.configuration.add( + ConfigItem(f"{OWNER_NAME}_{name}", description, INTEGER_VALUE_TYPE) + ) + + _add_text("DOMAIN", "domain name") + _add_text("EMAIL", "admin email address") + _add_text( "AUTO_BACKUP_COS_SECRET_ID", "access key id for Tencent COS, used for auto backup", ) - self._add_text_item( + _add_text( "AUTO_BACKUP_COS_SECRET_KEY", "access key secret for Tencent COS, used for auto backup", ) - self._add_text_item( + _add_text( "AUTO_BACKUP_COS_REGION", "region for Tencent COS, used for auto backup" ) - self._add_text_item( + _add_text( "AUTO_BACKUP_BUCKET_NAME", "bucket name for Tencent COS, used for auto backup", ) - self._add_text_item("GITHUB_USERNAME", "github username for fetching todos") - self._add_int_item( - "GITHUB_PROJECT_NUMBER", "github project number for fetching todos" - ) - self._add_text_item("GITHUB_TOKEN", "github token for fetching todos") - self._add_text_item("GITHUB_TODO_COUNT", "github todo count") - self._add_uuid_item("V2RAY_TOKEN", "v2ray user id") - self._add_uuid_item("V2RAY_PATH", "v2ray path, which will be prefixed by _") - self._add_text_item("FORGEJO_MAILER_USER", "Forgejo SMTP user") - self._add_text_item("FORGEJO_MAILER_PASSWD", "Forgejo SMTP password") - self._add_random_string_item("2FAUTH_APP_KEY", "2FAuth App Key") - self._add_text_item("2FAUTH_MAIL_USERNAME", "2FAuth SMTP user") - self._add_text_item("2FAUTH_MAIL_PASSWORD", "2FAuth SMTP password") - - def _add_text_item(self, name: str, description: str) -> None: - self.configuration.add( - ConfigItem(f"{OWNER_NAME}_{name}", description, TEXT_VALUE_TYPE) - ) - - def _add_uuid_item(self, name: str, description: str) -> None: - self.configuration.add( - ConfigItem( - f"{OWNER_NAME}_{name}", - description, - TEXT_VALUE_TYPE, - default=UuidValueGenerator(), - ) - ) - - def _add_random_string_item( - self, name: str, description: str, length: int = 32, secure: bool = True - ) -> None: - self.configuration.add( - ConfigItem( - f"{OWNER_NAME}_{name}", - description, - TEXT_VALUE_TYPE, - default=RandomStringValueGenerator(length, secure), - ) - ) - - def _add_int_item(self, name: str, description: str) -> None: - self.configuration.add( - ConfigItem(f"{OWNER_NAME}_{name}", description, INTEGER_VALUE_TYPE) - ) + _add_text("GITHUB_USERNAME", "github username for fetching todos") + _add_int("GITHUB_PROJECT_NUMBER", "github project number for fetching todos") + _add_text("GITHUB_TOKEN", "github token for fetching todos") + _add_text("GITHUB_TODO_COUNT", "github todo count") + _add_uuid("V2RAY_TOKEN", "v2ray user id") + _add_uuid("V2RAY_PATH", "v2ray path, which will be prefixed by _") + _add_text("FORGEJO_MAILER_USER", "Forgejo SMTP user") + _add_text("FORGEJO_MAILER_PASSWD", "Forgejo SMTP password") + _add_random_string("2FAUTH_APP_KEY", "2FAuth App Key") + _add_text("2FAUTH_MAIL_USERNAME", "2FAuth SMTP user") + _add_text("2FAUTH_MAIL_PASSWORD", "2FAuth SMTP password") def setup(self) -> None: - self._config_path = self.app.data_dir.add_subpath( + self._config_file_path = self.app.data_dir.add_subpath( "config", False, description="Configuration file path." ) @property - def config_file_path(self) -> AppFeaturePath: - return self._config_path - - @property def configuration(self) -> Configuration: return self._configuration @property - def config_keys(self) -> list[str]: - return [item.name for item in self.configuration] - - @property - def config_map(self) -> dict[str, str]: - raise NotImplementedError() + def config_file_path(self) -> AppFeaturePath: + return self._config_file_path - def _parse_config_file(self) -> SimpleLineConfigParser.Result | None: + def _parse_config_file(self) -> SimpleLineConfigParser.Result: if not self.config_file_path.check_self(): - return None + raise AppConfigFileNotFoundError( + "Config file not found.", self.config_file_path.full_path_str + ) parser = SimpleLineConfigParser() return parser.parse(self.config_file_path.full_path.read_text()) def _check_duplicate( self, - result: SimpleLineConfigParser.Result - | dict[str, list[SimpleLineConfigParser.Item]], + parse_result: dict[str, list[SimpleLineConfigParser.Entry]], ) -> dict[str, str]: - if isinstance(result, SimpleLineConfigParser.Result): - result = result.cru_iter().group_by(lambda i: i.key) - - config = {} - error_items = [] - for key, items in result.items(): - config[key] = items[0].value - for item in items[1:]: - error_items.append(item) + config_dict = {} + duplicate_entries = [] + for key, entries in parse_result.items(): + config_dict[key] = entries[0].value + for entry in entries[1:]: + duplicate_entries.append(entry) - if len(error_items) > 0: - raise AppConfigDuplicateItemsError("Duplicate items found.", error_items) + if len(duplicate_entries) > 0: + raise AppConfigDuplicateEntryError( + "Duplicate entries found.", duplicate_entries + ) - return config + return config_dict - def _check_config_file(self) -> dict[str, str]: + def _check_defined( + self, + config_dict: dict[str, str], + allow_extra: bool = True, + ) -> dict[str, str]: # TODO: Continue here! raise NotImplementedError() + def _check_config_file(self) -> dict[str, str]: + try: + parsed = self._parse_config_file() + config = self._check_duplicate(parsed) + return config + except ParseError as e: + raise CruUserFriendlyException("Failed to parse config file.") from e + except AppConfigDuplicateEntryError as e: + raise e.to_friendly_error() from e + def reload_config_file(self) -> bool: self.configuration.reset_all() - if not self.config_file_path.check_self(): - return False - parser = SimpleLineConfigParser() - parse_result = parser.parse(self.config_file_path.full_path.read_text()) - config_dict = parse_result.cru_iter().group_by(lambda i: i.key) + config_dict = self._check_config_file() + for key, value in config_dict.items(): + self.configuration.set(key, value) return True def print_app_config_info(self): |