diff --git a/doc/CUSTOMIZING.md b/doc/CUSTOMIZING.md index b390745..b0edcc3 100644 --- a/doc/CUSTOMIZING.md +++ b/doc/CUSTOMIZING.md @@ -113,4 +113,64 @@ inherit values from another (B), then one more config (C) would inherit values from A and have values both from A and from B. Only omitted values are replaced, in case some value is present in the child -config it will never be replaced. \ No newline at end of file +config it will never be replaced. + +### Overriding + +Continuing to counter issues with default config customization, in patch 0.3.1 +two more important parameters have been introduced with one of them being +`Override =`. + +With `Override`, you can replace existing config by its ID or path (relative to +config directory, see example below) *entirely*, combined with `Inherit` (see +above) you could could use it to alter just a part of existing config without +a need to copy it entirely. + +#### Existing config + +```ini +[Presence] +ID = MyAwesomeConfig + +[Activity] +State = Having fun at [loc_prompt] +``` + +#### Overriding config + +```ini +[Presence] +Override = MyAwesomeConfig + +[Activity] +State = Spending time together at [loc_prompt] +``` + +Sometimes however, if an existing config has no ID assigned and you don't want +to edit it, you can use a path (relative to config directory) instead: + +```ini +[Presence] +# Full path would be game/Submods/Discord Presence Submod/config/default/... +# but you only need part AFTER config/, without a leading slash (/) +Override = default/configs/default.conf +``` + +### Disabling + +Another parameter introduced in 0.3.1 is `Disable =`, which if set to `True` +will disable the config and prevent it from being chosen. This however does not +affect inheriting and overriding and you can still use it as a base for other +configs or override some other config and disable it. + +For example, if there is a default config you want to disable, you'd create +another config and use `Override` like this: + +```ini +[Presence] +Override = default/configs/some-config-to-disable.conf +Disable = True +``` + +This config will replace a config you specified in `Override` and disable it, +making it never be chosen. \ No newline at end of file diff --git a/mod/config.rpy b/mod/config.rpy index 8c86044..c252e4c 100644 --- a/mod/config.rpy +++ b/mod/config.rpy @@ -41,6 +41,11 @@ init 90 python in _fom_presence_config: ui_message_report="Could not load some presence configs, see log/submod_log.log." ) + _ERROR_CONFIG_OVERRIDE = error.Error( + log_message_report="Presence config {0} overrides nonexistent config ID: {1}.", + ui_message_report="Could not load some presence configs, see log/submod_log.log." + ) + _WARNING_CONFIG_CLASH = error.Error( log_message_report="Config from file {0} has conflicting name with some other config: {1}.", ui_message_report="There were some warnings during loading some of the presence configs, see log/submod_log.log.", @@ -324,6 +329,8 @@ init 90 python in _fom_presence_config: self.dynamic = parser.get_value("Presence", "Dynamic", _parse_bool, True) self.id = parser.get_value("Presence", "ID", str, None) self.inherit_id = parser.get_value("Presence", "Inherit", str, None) + self.override_id = parser.get_value("Presence", "Override", str, None) + self.disable = parser.get_value("Presence", "Disable", _parse_bool, False) self.app_id = parser.get_value("Client", "ApplicationID", int) @@ -339,7 +346,7 @@ init 90 python in _fom_presence_config: self.stop_ts = parser.get_value("Timestamps", "End", _parse_ts_supplier, _none_supplier) self._activity = None - self._inherit_applied = False + self._file = None @staticmethod def from_file(path): @@ -358,28 +365,33 @@ init 90 python in _fom_presence_config: c = configparser.ConfigParser() with _open_with_encoding(path, "r", encoding="utf-8") as f: c.readfp(f, path.replace("\\", "/").split("/")[:-1]) - return Config(_ParserWrapper(c)) - def inherit(self, config, force=False): + config = Config(_ParserWrapper(c)) + config._file = path + return config + + @property + def file(self): + """ + Returns path to file this config was loaded from. + + OUT: + str: + Path to config file. + """ + + return self._file + + def copy_from(self, config): """ Copies values from another config (only omitted, None or - _none_supplier values) over to this config. Unless force parameter - is set to True, does nothing on next call. + _none_supplier values) over to this config. IN: config -> Config: - Config to copy values from. Inherit method is not called, - inheritance is not done recursively by this method, users - should care about this themselves. - - force -> bool, default False: - If True, skips inheritance status checks and applies values - over again. + Config to copy values from. """ - if self._inherit_applied and not force: - return - if self.app_id is None: self.app_id = config.app_id if self.details is _none_supplier: @@ -399,23 +411,6 @@ init 90 python in _fom_presence_config: if self.stop_ts is _none_supplier: self.stop_ts = config.stop_ts - self._inherit_applied = True - - @property - def inherited(self): - """ - Returns inheritance status flag value. - - OUT: - True: - If this config has inherited from other config. - - False: - If this config has not inherited from another config. - """ - - return self._inherit_applied - def to_activity(self): """ Creates Activity instance from the values stored in Config. @@ -469,8 +464,11 @@ init 90 python in _fom_presence_config: error context. Reported errors are not resolved on successful loads. """ - del _configs[:] - _config_id_map.clear() + configs = dict() + id_map = dict() + + inherited = set() + overridden = set() for _dir, _, files in os.walk(_config_dir): for _file in files: @@ -481,46 +479,88 @@ init 90 python in _fom_presence_config: ): continue + _file = os.path.join(_dir, _file) + rel_file = _file[len(_config_dir) + 1:] + try: - _file = os.path.join(_dir, _file) config = Config.from_file(_file) if config.condition is not None: eval(config.condition, dict(), store.__dict__) - - _configs.append((_file, config)) - if config.id is not None: - if config.id in _config_id_map: - _WARNING_CONFIG_CLASH.report(_file[len(_config_dir) + 1:], config.id) - _config_id_map[config.id] = config + config._file = rel_file except Exception as e: - _ERROR_CONFIG_LOADING.report(_file[len(_config_dir) + 1:], e) + _ERROR_CONFIG_LOADING.report(file_rel, e) + continue + + configs[rel_file] = config + if config.id is None: + config.id = rel_file + + ov = id_map.get(config.id) + if ov is not None: + _WARNING_CONFIG_CLASH.report(rel_file, config.id) + + id_map[config.id] = config + id_map[rel_file] = config - # Once configs are loaded, we now copy inherited values. def inherit(config): # Prevent loops and infinite recursions. - if config.inherited: + if config in inherited: return True if config.inherit_id is not None: - parent = _config_id_map.get(config.inherit_id) + parent = id_map.get(config.inherit_id) if parent is None: - _ERROR_CONFIG_INHERITANCE.report(_file[len(_config_dir) + 1:], config.inherit_id) + _ERROR_CONFIG_INHERITANCE.report(config.file, config.inherit_id) return False # Inheritance is done recursively. if not inherit(parent): return False - config.inherit(parent) + config.copy_from(parent) + + # Add to list of applied inheritances. + inherited.add(config) + return True + + def override(config): + # Prevent loops and infinite recursions. + if config in overridden: + return True + + if config.override_id is not None: + ov = id_map.get(config.override_id) + if ov is None: + _ERROR_CONFIG_OVERRIDE.report(config.file, config.override_id) + return False + + if not override(ov): + return False + + remove_config(ov) + config.id = ov.id + id_map[ov.id] = config + overridden.add(config) return True - idx = 0 - while idx < len(_configs): - _file, config = _configs[idx] - if not inherit(config): - del _configs[idx] - else: - idx += 1 + def remove_config(config): + del configs[config.file] + if config.id is not None: + del id_map[config.id] + + for rel_file, config in list(configs.items()): + if rel_file not in configs: + continue + + if not (inherit(config) and override(config)): + remove_config(config) + continue + + del _configs[:] + _config_id_map.clear() + + _configs.extend(list(configs.items())) + _config_id_map.update(id_map) # Sort configs on reload to save precious time on every loop. _configs.sort(key=lambda it: it[1].priority, reverse=True) @@ -537,7 +577,7 @@ init 90 python in _fom_presence_config: """ for _file, conf in _configs: - if conf.condition is not None: + if not conf.disable and conf.condition is not None: try: if bool(eval(conf.condition, dict(), store.__dict__)): return conf diff --git a/mod/header.rpy b/mod/header.rpy index 2324d52..6c5e70e 100644 --- a/mod/header.rpy +++ b/mod/header.rpy @@ -9,7 +9,7 @@ init -990 python in mas_submod_utils: author="Friends of Monika", name="Discord Presence Submod", description="Show everyone who's the person you're spending your time with~", - version="0.3.0", + version="0.3.1", settings_pane="fom_presence_settings_pane", version_updates={ "friends_of_monika_discord_presence_submod_v0_0_1": "friends_of_monika_discord_presence_submod_v0_0_2", @@ -18,7 +18,8 @@ init -990 python in mas_submod_utils: "friends_of_monika_discord_presence_submod_v0_0_4": "friends_of_monika_discord_presence_submod_v0_1_2", "friends_of_monika_discord_presence_submod_v0_1_2": "friends_of_monika_discord_presence_submod_v0_2_0", "friends_of_monika_discord_presence_submod_v0_2_0": "friends_of_monika_discord_presence_submod_v0_2_1", - "friends_of_monika_discord_presence_submod_v0_2_1": "friends_of_monika_discord_presence_submod_v0_3_0" + "friends_of_monika_discord_presence_submod_v0_2_1": "friends_of_monika_discord_presence_submod_v0_3_0", + "friends_of_monika_discord_presence_submod_v0_3_0": "friends_of_monika_discord_presence_submod_v0_3_1" } ) diff --git a/mod/updates.rpy b/mod/updates.rpy index 09ef7af..8fc9063 100644 --- a/mod/updates.rpy +++ b/mod/updates.rpy @@ -70,4 +70,7 @@ label friends_of_monika_discord_presence_submod_v0_2_1(version="v0_2_1"): return label friends_of_monika_discord_presence_submod_v0_3_0(version="v0_3_0"): + return + +label friends_of_monika_discord_presence_submod_v0_3_1(version="v0_3_1"): return \ No newline at end of file