<commit_before>"""Contains the Config class with utility configuration methods"""

# config.py
# Mission Pinball Framework
# Written by <NAME> & <NAME>
# Released under the MIT License. (See license info at the end of this file.)

# Documentation and more info at http://missionpinball.com/mpf
import logging
import os
import sys

import yaml
from copy import deepcopy

from mpf.system.timing import Timing
import version

log = logging.getLogger('ConfigProcessor')


class CaseInsensitiveDict(dict):
    """A class based on Python's 'dict' class that internally stores all keys
    as lowercase. Set, get, contains, and del methods have been overwritten to
    automatically convert incoming calls to lowercase.
    """
    def __setitem__(self, key, value):
        try:
            super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)
        except AttributeError:
            super(CaseInsensitiveDict, self).__setitem__(key, value)

    def __getitem__(self, key):
        try:
            return super(CaseInsensitiveDict, self).__getitem__(key.lower())
        except AttributeError:
            return super(CaseInsensitiveDict, self).__getitem__(key)

    def __contains__(self, key):
        try:
            return super(CaseInsensitiveDict, self).__contains__(key.lower())
        except AttributeError:
            return super(CaseInsensitiveDict, self).__contains__(key)

    def __del__(self, key):
        try:
            return super(CaseInsensitiveDict, self).__del__(key.lower())
        except AttributeError:
            return super(CaseInsensitiveDict, self).__del__(key)


class Config(object):

    @staticmethod
    def load_config_yaml(config=None, yaml_file=None,
                         new_config_dict=None):
        """Merges a new config dictionary into an existing one.

        This method does what we call a "deep merge" which means it merges
        together subdictionaries instead of overwriting them. See the
        documentation for `meth:dict_merge` for a description of how this
        works.

        If the config dictionary you're merging in also contains links to
        additional config files, it will also merge those in.

        At this point this method loads YAML files, but it would be simple to
        load them from JSON, XML, INI, or existing python dictionaires.

        Args:
            config: The optional current version of the config dictionary that
                you're building up. If you don't pass a dictionary, this method
                will create one.
            yaml_file: A YAML file containing the settings to deep merge into
                the config dictionary. This method will try to find a file
                with that name and open it to read in the settings. It will
                first try to open it as a file directly (including any path
                that's there). If that doesn't work, it will try to open the
                file using the last path that worked. (This path is stored in
                `config['Config_path']`.)
            new_config_dict: A dictionary of settings to merge into the config
                dictionary.

        Note that you only need to specify a yaml_file or new_config_dictionary,
        not both.

        Returns: Python dictionary which is your source with all the new config
            options merged in.

        """

        if not config:
            config = dict()
        else:
            config = Config.keys_to_lower(config)

        new_updates = dict()

        # If we were passed a config dict, load from there
        if type(new_config_dict) == dict:
            new_updates = Config.keys_to_lower(new_config_dict)

        # If not, do we have a yaml_file?
        elif yaml_file:
            if os.path.isfile(yaml_file):
                Config.check_config_file_version(yaml_file)
                config_location = yaml_file
                # Pull out the path in case we need it later
                config['config_path'] = os.path.split(yaml_file)[0]
            elif ('config_path' in config and
                    os.path.isfile(os.path.join(config['config_path'],
                                                yaml_file))):
                config_location = os.path.join(config['config_path'],
                                               yaml_file)
            else:
                log.critical("Couldn't find file: %s.", yaml_file)
                sys.exit()

        if config_location:

            try:
                log.info("Loading configuration from file: %s", config_location)
                new_updates = Config.keys_to_lower(yaml.load(open(
                                                   config_location, 'r')))
            except yaml.YAMLError, exc:
                if hasattr(exc, 'problem_mark'):
                    mark = exc.problem_mark
                    log.critical("Error found in config file %s. Line %s, "
                                 "Position %s", config_location, mark.line+1,
                                 mark.column+1)
                    sys.exit()
            except:
                log.critical("Couldn't load from file: %s", yaml_file)
                sys.exit()

        config = Config.dict_merge(config, new_updates)

        # now check if there are any more updates to do.
        # iterate and remove them

        try:
            if 'config' in config:
                if yaml_file in config['config']:
                    config['config'].remove(yaml_file)
                if config['config']:
                    config = Config.load_config_yaml(config=config,
                                              yaml_file=config['config'][0])
        except:
            log.critical("No configuration file found, or config file is empty."
                         " But congrats! MPF works! :)")
            sys.exit()

        return config

    @staticmethod
    def check_config_file_version(file_location):
        """Checks a configuration file to see if it's the proper version for
        this version of MPF.

        Args:
            file_location: The path to the file to check.

        Returns: True if the config version of the file matches. False if not.

        This method checks that the a string 'config_version=x' exists in the
        first line of the file. If so, it checks that 'x' matches MPF's
        config_version specification.

        This check is done as integers.

        """
        with open(file_location) as f:
            file_version = f.readline().split('config_version=')[-1:][0]

            try:
                file_version = int(file_version)
            except ValueError:
                file_version = 0

            if file_version != int(version.__config_version__):
                log.error("Config file %s is version %s. MPF %s requires "
                            "version %s", file_location, file_version,
                            version.__version__, version.__config_version__)
                log.error("Run MPF with the -m option to automatically migrate"
                          " your config files")
                log.error("More info: %s", version.__config_version_url__)
                sys.exit()

    @staticmethod
    def keys_to_lower(source_dict):
        """Converts the keys of a dictionary to lowercase.

        Args:
            source_dict: The dictionary you want to convert.

        Returns:
            A dictionary with lowercase keys.
        """
        for k in source_dict.keys():
            if type(source_dict[k]) is dict:
                source_dict[k] = Config.keys_to_lower(source_dict[k])<commit_msg>error string cleanup<commit_after>"""Contains the Config class with utility configuration methods"""

# config.py
# Mission Pinball Framework
# Written by <NAME> & <NAME>
# Released under the MIT License. (See license info at the end of this file.)

# Documentation and more info at http://missionpinball.com/mpf
import logging
import os
import sys

import yaml
from copy import deepcopy

from mpf.system.timing import Timing
import version

log = logging.getLogger('ConfigProcessor')


class CaseInsensitiveDict(dict):
    """A class based on Python's 'dict' class that internally stores all keys
    as lowercase. Set, get, contains, and del methods have been overwritten to
    automatically convert incoming calls to lowercase.
    """
    def __setitem__(self, key, value):
        try:
            super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)
        except AttributeError:
            super(CaseInsensitiveDict, self).__setitem__(key, value)

    def __getitem__(self, key):
        try:
            return super(CaseInsensitiveDict, self).__getitem__(key.lower())
        except AttributeError:
            return super(CaseInsensitiveDict, self).__getitem__(key)

    def __contains__(self, key):
        try:
            return super(CaseInsensitiveDict, self).__contains__(key.lower())
        except AttributeError:
            return super(CaseInsensitiveDict, self).__contains__(key)

    def __del__(self, key):
        try:
            return super(CaseInsensitiveDict, self).__del__(key.lower())
        except AttributeError:
            return super(CaseInsensitiveDict, self).__del__(key)


class Config(object):

    @staticmethod
    def load_config_yaml(config=None, yaml_file=None,
                         new_config_dict=None):
        """Merges a new config dictionary into an existing one.

        This method does what we call a "deep merge" which means it merges
        together subdictionaries instead of overwriting them. See the
        documentation for `meth:dict_merge` for a description of how this
        works.

        If the config dictionary you're merging in also contains links to
        additional config files, it will also merge those in.

        At this point this method loads YAML files, but it would be simple to
        load them from JSON, XML, INI, or existing python dictionaires.

        Args:
            config: The optional current version of the config dictionary that
                you're building up. If you don't pass a dictionary, this method
                will create one.
            yaml_file: A YAML file containing the settings to deep merge into
                the config dictionary. This method will try to find a file
                with that name and open it to read in the settings. It will
                first try to open it as a file directly (including any path
                that's there). If that doesn't work, it will try to open the
                file using the last path that worked. (This path is stored in
                `config['Config_path']`.)
            new_config_dict: A dictionary of settings to merge into the config
                dictionary.

        Note that you only need to specify a yaml_file or new_config_dictionary,
        not both.

        Returns: Python dictionary which is your source with all the new config
            options merged in.

        """

        if not config:
            config = dict()
        else:
            config = Config.keys_to_lower(config)

        new_updates = dict()

        # If we were passed a config dict, load from there
        if type(new_config_dict) == dict:
            new_updates = Config.keys_to_lower(new_config_dict)

        # If not, do we have a yaml_file?
        elif yaml_file:
            if os.path.isfile(yaml_file):
                Config.check_config_file_version(yaml_file)
                config_location = yaml_file
                # Pull out the path in case we need it later
                config['config_path'] = os.path.split(yaml_file)[0]
            elif ('config_path' in config and
                    os.path.isfile(os.path.join(config['config_path'],
                                                yaml_file))):
                config_location = os.path.join(config['config_path'],
                                               yaml_file)
            else:
                log.critical("Couldn't find file: %s.", yaml_file)
                sys.exit()

        if config_location:

            try:
                log.info("Loading configuration from file: %s", config_location)
                new_updates = Config.keys_to_lower(yaml.load(open(
                                                   config_location, 'r')))
            except yaml.YAMLError, exc:
                if hasattr(exc, 'problem_mark'):
                    mark = exc.problem_mark
                    log.critical("Error found in config file %s. Line %s, "
                                 "Position %s", config_location, mark.line+1,
                                 mark.column+1)
                    sys.exit()
            except:
                log.critical("Couldn't load from file: %s", yaml_file)
                sys.exit()

        config = Config.dict_merge(config, new_updates)

        # now check if there are any more updates to do.
        # iterate and remove them

        try:
            if 'config' in config:
                if yaml_file in config['config']:
                    config['config'].remove(yaml_file)
                if config['config']:
                    config = Config.load_config_yaml(config=config,
                                              yaml_file=config['config'][0])
        except:
            log.critical("No configuration file found, or config file is empty."
                         " But congrats! MPF works! :)")
            sys.exit()

        return config

    @staticmethod
    def check_config_file_version(file_location):
        """Checks a configuration file to see if it's the proper version for
        this version of MPF.

        Args:
            file_location: The path to the file to check.

        Returns: True if the config version of the file matches. False if not.

        This method checks that the a string 'config_version=x' exists in the
        first line of the file. If so, it checks that 'x' matches MPF's
        config_version specification.

        This check is done as integers.

        """
        with open(file_location) as f:
            file_version = f.readline().split('config_version=')[-1:][0]

            try:
                file_version = int(file_version)
            except ValueError:
                file_version = 0

            if file_version != int(version.__config_version__):
                log.error("Config file %s is version %s. MPF %s requires "
                            "version %s", file_location, file_version,
                            version.__version__, version.__config_version__)
                log.error("Run MPF with the -m option to automatically migrate"
                          " your config filesconfig")
                log.error("More info: %s", version.__config_version_url__)
                sys.exit()

    @staticmethod
    def keys_to_lower(source_dict):
        """Converts the keys of a dictionary to lowercase.

        Args:
            source_dict: The dictionary you want to convert.

        Returns:
            A dictionary with lowercase keys.
        """
        for k in source_dict.keys():
            if type(source_dict[k]) is dict:
                source_dict[k] = Config.keys_to_lower(source_dict[k])
{'fraction_non_alphanumeric': 0.07230016313213702, 'fraction_numerical': 0.0009135399673735726, 'mean_word_length': 1.8418320044502132, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6470717', 'n_tokens_mistral': 3930, 'n_tokens_neox': 3705, 'n_words': 1361}