<commit_before>"""Strategies for reading from & writing to config files."""
import logging
import json
import os
from abc import ABCMeta, abstractmethod
from collections import OrderedDict
from configparser import NoSectionError, RawConfigParser

from six import raise_from, text_type, with_metaclass

from .exc import SettingsFileNotFoundError, SettingsFileSectionNotFoundError
from .util import parse_file_name_and_section


__all__ = [
    'Strategy',
    'INIStrategy',
    'INIJSONStrategy',
]


log = logging.getLogger(__name__)


class RawValue(text_type):

    """Marker for values that couldn't be decoded when reading."""


class Strategy(with_metaclass(ABCMeta)):

    file_types = ()

    @abstractmethod
    def read_section(self, file_name, section=None):
        """Read settings from specified ``section`` of config file.

        This is where the strategy-specific file-reading logic goes.

        Returns:
            - Settings from the specified section or from the default
              section if the specified section isn't present.
            - Whether the section is present.

        """

    @abstractmethod
    def write_settings(self, settings, file_name, section=None):
        """Write settings to file."""

    def parse_file_name_and_section(self, file_name, section=None, extender=None,
                                    extender_section=None):
        """Parse file name and (maybe) section.

        Delegates to :func:`.util.parse_file_name_and_section` to parse
        the file name and section. If that function doesn't find a
        section, this method should return the default section for the
        strategy via :meth:`get_default_section` (if applicable).

        """
        file_name, section = parse_file_name_and_section(
            file_name, section, extender, extender_section)
        if section is None:
            section = self.get_default_section(file_name)
        return file_name, section

    def read_file(self, file_name, section=None, finalize=True):
        """Read settings from config file."""
        file_name, section = self.parse_file_name_and_section(file_name, section)

        if not os.path.isfile(file_name):
            raise SettingsFileNotFoundError(file_name)

        section_dict, section_present = self.read_section(file_name, section)

        settings = OrderedDict()

        if 'extends' in section_dict:
            extends = section_dict['extends']
            del section_dict['extends']
            if extends:
                extends, extends_section = self.parse_file_name_and_section(
                    extends, extender=file_name, extender_section=section)
                extended_settings, extended_section_present = self.read_file(
                    extends, extends_section, finalize=False)
                section_present = section_present or extended_section_present
                settings.update(extended_settings)

        settings.update(section_dict)

        if finalize:
            if not section_present:
                raise SettingsFileSectionNotFoundError(section)

        return settings, section_present

    def get_default_section(self, file_name):
        return None

    def decode_value(self, value):
        """Decode value read from file to Python object."""
        return value

    def encode_value(self, value):
        """Encode Python object as value that can be written to file."""
        return value


class LocalSettingsConfigParser(RawConfigParser):

    def options(self, section):
        # Order [DEFAULT] options before section options; the default
        # implementation orders them after.<commit_msg>Slightly improve Strategy.read_file() internals<commit_after>"""Strategies for reading from & writing to config files."""
import logging
import json
import os
from abc import ABCMeta, abstractmethod
from collections import OrderedDict
from configparser import NoSectionError, RawConfigParser

from six import raise_from, text_type, with_metaclass

from .exc import SettingsFileNotFoundError, SettingsFileSectionNotFoundError
from .util import parse_file_name_and_section


__all__ = [
    'Strategy',
    'INIStrategy',
    'INIJSONStrategy',
]


log = logging.getLogger(__name__)


class RawValue(text_type):

    """Marker for values that couldn't be decoded when reading."""


class Strategy(with_metaclass(ABCMeta)):

    file_types = ()

    @abstractmethod
    def read_section(self, file_name, section=None):
        """Read settings from specified ``section`` of config file.

        This is where the strategy-specific file-reading logic goes.

        Returns:
            - Settings from the specified section or from the default
              section if the specified section isn't present.
            - Whether the section is present.

        """

    @abstractmethod
    def write_settings(self, settings, file_name, section=None):
        """Write settings to file."""

    def parse_file_name_and_section(self, file_name, section=None, extender=None,
                                    extender_section=None):
        """Parse file name and (maybe) section.

        Delegates to :func:`.util.parse_file_name_and_section` to parse
        the file name and section. If that function doesn't find a
        section, this method should return the default section for the
        strategy via :meth:`get_default_section` (if applicable).

        """
        file_name, section = parse_file_name_and_section(
            file_name, section, extender, extender_section)
        if section is None:
            section = self.get_default_section(file_name)
        return file_name, section

    def read_file(self, file_name, section=None, finalize=True):
        """Read settings from config file."""
        file_name, section = self.parse_file_name_and_section(file_name, section)

        if not os.path.isfile(file_name):
            raise SettingsFileNotFoundError(file_name)

        settings = OrderedDict()
        section_dict, section_present = self.read_section(file_name, section)
        extends = section_dict.pop('extends', None)

        if extends:
            extends, extends_section = self.parse_file_name_and_section(
                extends, extender=file_name, extender_section=section)
            extended_settings, extended_section_present = self.read_file(
                extends, extends_section, finalize=False)
            section_present = section_present or extended_section_present
            settings.update(extended_settings)

        settings.update(section_dict)

        if finalize and not section_present:
            raise SettingsFileSectionNotFoundError(section)

        return settings, section_present

    def get_default_section(self, file_name):
        return None

    def decode_value(self, value):
        """Decode value read from file to Python object."""
        return value

    def encode_value(self, value):
        """Encode Python object as value that can be written to file."""
        return value


class LocalSettingsConfigParser(RawConfigParser):

    def options(self, section):
        # Order [DEFAULT] options before section options; the default
        # implementation orders them after.
{'fraction_non_alphanumeric': 0.07167518298577545, 'fraction_numerical': 0.0, 'mean_word_length': 2.9358695652173914, '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': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '486809', 'n_tokens_mistral': 1891, 'n_tokens_neox': 1818, 'n_words': 575}