aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/service/_config.py
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2024-11-11 01:12:29 +0800
committerYuqian Yang <crupest@crupest.life>2025-01-14 22:36:08 +0800
commita5c2f62b0b3c4ec6ce46c57bcd99319a85900798 (patch)
tree0cb89834a1ed32bdf20597309c78c3a5cbca3c88 /tools/cru-py/cru/service/_config.py
parentb9788b23bbd000d2a2d14402b4e2abacd5a57365 (diff)
downloadcrupest-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.py247
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):