<commit_before>"""
Handle settings storage and fetching

Also has a helper function to check loaded settings for faults.
"""

from datetime import datetime
from collections import OrderedDict
from copy import deepcopy
from pathlib import Path

import npc
from npc import util
from npc.linters.settings import lint

class Settings:
    """
    Load and store settings

    Default settings are loaded from settings/settings-default.json in the
    install path. Additional settings are loaded from the paths in
    `settings_paths`.

    Do not access settings values directly. Use the get() method.

    Attributes:
        install_base (str): Directory path containing this file
        default_settings_path (str): Path to the default settings file
        user_settings_path (str): Path to the user settings directory for the
            current user. Only correct on *nix systems.
        campaign_settings_path (str): Path to the campaign settings directory.
        settings_files (list): List of allowed settings file names.
        settings_paths (list): List of allowed settings paths.
        data (dict): Dictionary of settings data. Should not be referenced
            directly. Instead, use the get() method.
    """

    def __init__(self, verbose=False):
        """
        Loads all settings files.

        The default settings are loaded first, followed by user settings and
        finally campaign settings. Keys from later files overwrite those from
        earlier files.

        Only the default settings need to exist. If a different file cannot be
        found or opened, it will be silently ignored without crashing.

        Args:
            verbose (bool): Whether to show additional error messages that are
                usually ignored. These involve unloadable optional settings
                files and keys that cannot be found. The file
                `settings/settings.json` should never be found, but will still
                be reported.
        """

        self.module_base = Path(__file__).parent
        self.install_base = Path(self.module_base).parent

        self.default_settings_path = self.module_base
        self.user_settings_path = Path('~/.config/npc/').expanduser()
        self.campaign_settings_path = Path('.npc/')

        self.settings_files = [
            'settings.json',
            'settings-changeling.json',
            'settings-werewolf.json',
            'settings-gui.json'
        ]
        self.settings_paths = [self.default_settings_path, self.user_settings_path, self.campaign_settings_path]

        self.verbose = verbose
        loaded_data = util.load_json(self.default_settings_path.joinpath('settings-default.json'))

        # massage template names into real paths
        self.data = self._expand_templates(base_path=self.install_base, settings_data=loaded_data)

        # merge additional settings files
        for settings_path in self.settings_paths:
            for file in self.settings_files:
                try:
                    self.load_more(settings_path.joinpath(file))
                except OSError as err:
                    # All of these files are optional, so normally we silently
                    # ignore these errors
                    if self.verbose:
                        util.print_err(err.strerror, err.filename)

    def _expand_templates(self, base_path, settings_data):
        """
        Expand all known template paths in a settings file

        Args:<commit_msg>Pick settings format based on precedence<commit_after>"""
Handle settings storage and fetching

Also has a helper function to check loaded settings for faults.
"""

from datetime import datetime
from collections import OrderedDict
from copy import deepcopy
from pathlib import Path

import npc
from npc import util
from npc.linters.settings import lint

class Settings:
    """
    Load and store settings

    Default settings are loaded from settings/settings-default.json in the
    install path. Additional settings are loaded from the paths in
    `settings_paths`.

    Do not access settings values directly. Use the get() method.

    Attributes:
        install_base (str): Directory path containing this file
        default_settings_path (str): Path to the default settings file
        user_settings_path (str): Path to the user settings directory for the
            current user. Only correct on *nix systems.
        campaign_settings_path (str): Path to the campaign settings directory.
        settings_file_names (list): List of allowed settings file names.
        settings_paths (list): List of allowed settings paths.
        data (dict): Dictionary of settings data. Should not be referenced
            directly. Instead, use the get() method.
    """

    def __init__(self, verbose=False):
        """
        Loads all settings files.

        The default settings are loaded first, followed by user settings and
        finally campaign settings. Keys from later files overwrite those from
        earlier files.

        Only the default settings need to exist. If a different file cannot be
        found or opened, it will be silently ignored without crashing.

        Args:
            verbose (bool): Whether to show additional error messages that are
                usually ignored. These involve unloadable optional settings
                files and keys that cannot be found. The file
                `settings/settings.json` should never be found, but will still
                be reported.
        """

        self.module_base = Path(__file__).parent
        self.install_base = Path(self.module_base).parent

        self.default_settings_path = self.module_base
        self.user_settings_path = Path('~/.config/npc/').expanduser()
        self.campaign_settings_path = Path('.npc/')

        self.settings_file_names = [
            'settings',
            'settings-changeling',
            'settings-werewolf',
            'settings-gui'
        ]
        self.settings_file_suffixes = ['.json', '.yaml']
        self.settings_paths = [self.default_settings_path, self.user_settings_path, self.campaign_settings_path]

        self.verbose = verbose

        loaded_data = util.load_settings(self._best_settings_path(self.default_settings_path, 'settings-default'))

        # massage template names into real paths
        self.data = self._expand_templates(base_path=self.install_base, settings_data=loaded_data)

        # merge additional settings files
        for settings_path in self.settings_paths:
            for file in self.settings_file_names:
                try:
                    self.load_more(self._best_settings_path(settings_path, file))
                except OSError as err:
                    # All of these files are optional, so normally we silently
                    # ignore these errors
                    if self.verbose:
                        util.print_err(err.strerror, err.filename)

    def _best_settings_path(self, dir_name, file_name):
        """
        Get the most preferred path to a settings file based on suffix

        The list of suffixes in `self.settings_file_suffixes` is in order of
        preference, so we want to check them in that order. If both files exist,
        only the most preferred should be returned.

        Args:
            dir_name (Path): Directory name to search in
            file_name (PathLike): Settings file bare name. Any suffix present
                will be ignored.

        Returns:
            Path object for the best file that could be found

        Raises:
            OSError if no file exists with any usable suffix
        """
        base_path = dir_name.joinpath(file_name)
        for suffix in self.settings_file_suffixes:
            attempt = base_path.with_suffix(suffix)
            if attempt.exists():
                return attempt
        raise OSError(2, 'No yaml or json found', base_path)

    def _expand_templates(self, base_path, settings_data):
        """
        Expand all known template paths in a settings file

        Args:
{'fraction_non_alphanumeric': 0.06084917304369292, 'fraction_numerical': 0.00012342631449024932, 'mean_word_length': 2.352503103020273, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6571778', 'n_tokens_mistral': 2016, 'n_tokens_neox': 1917, 'n_words': 763}