#!/usr/bin/env python
# -*- coding: utf-8 -*-

# $Id: test_settings.py 8879 2021-11-05 11:10:55Z milde $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.

"""
Tests of runtime settings.
"""
from __future__ import print_function

import sys
import os
import difflib
import pprint
import warnings
import unittest
import DocutilsTestSupport              # must be imported before docutils
from docutils import frontend, utils
from docutils.writers import html4css1, pep_html
from docutils.parsers import rst


def fixpath(path):
    return os.path.abspath(os.path.join(*(path.split('/'))))


class ConfigFileTests(unittest.TestCase):

    config_files = {'old': fixpath('data/config_old.txt'),
                    'one': fixpath('data/config_1.txt'),
                    'two': fixpath('data/config_2.txt'),
                    'list': fixpath('data/config_list.txt'),
                    'list2': fixpath('data/config_list_2.txt'),
                    'error': fixpath('data/config_error_handler.txt')}

    settings = {
        'old': {u'datestamp': u'%Y-%m-%d %H:%M UTC',
                u'generator': True,
                u'no_random': True,
                u'python_home': u'http://www.python.org',
                u'source_link': True,
                'stylesheet': None,
                u'stylesheet_path': [u'stylesheets/pep.css'],
                'template': fixpath(u'data/pep-html-template')},
        'one': {u'datestamp': u'%Y-%m-%d %H:%M UTC',
                u'generator': True,
                u'no_random': True,
                u'python_home': u'http://www.python.org',
                u'raw_enabled': False,
                'record_dependencies': utils.DependencyList(),
                u'source_link': True,
                'stylesheet': None,
                u'stylesheet_path': [u'stylesheets/pep.css'],
                u'tab_width': 8,
                u'template': fixpath(u'data/pep-html-template'),
                u'trim_footnote_reference_space': True,
               },
        'two': {u'footnote_references': u'superscript',
                u'generator': False,
                'record_dependencies': utils.DependencyList(),
                u'stylesheet': None,
                u'stylesheet_path': [u'test.css'],
                'trim_footnote_reference_space': None},
        'list': {u'expose_internals': [u'a', u'b', u'c', u'd', u'e'],
                 u'strip_classes': [u'spam', u'pan', u'fun', u'parrot'],
                 u'strip_elements_with_classes': [u'sugar', u'flour', u'milk',
                                                  u'safran']},
        'list2': {u'expose_internals': [u'a', u'b', u'c', u'd', u'e', u'f'],
                  u'strip_classes': [u'spam', u'pan', u'fun', u'parrot',
                                     u'ham', u'eggs'],
                  u'strip_elements_with_classes': [u'sugar', u'flour',
                                                   u'milk', u'safran',
                                                   u'eggs', u'salt']},
        'error': {u'error_encoding': u'ascii',
                  u'error_encoding_error_handler': u'strict'},
        }

    compare = difflib.Differ().compare
    """Comparison method shared by all tests."""

    def setUp(self):
        warnings.filterwarnings(action='ignore',
                                category=frontend.ConfigDeprecationWarning)
        self.option_parser = frontend.OptionParser(
            components=(pep_html.Writer, rst.Parser), read_config_files=None)

    def files_settings(self, *names):
        settings = frontend.Values()
        for name in names:
            settings.update(self.option_parser.get_config_file_settings(
                self.config_files[name]), self.option_parser)
        return settings.__dict__

    def expected_settings(self, *names):
        expected = {}
        for name in names:
            expected.update(self.settings[name])
        return expected

    def compare_output(self, result, expected):
        """`result` and `expected` should both be dicts."""
        self.assertTrue('record_dependencies' in result)
        if 'record_dependencies' not in expected:
            # Delete it if we don't want to test it.
            del result['record_dependencies']
        result = pprint.pformat(result) + '\n'
        expected = pprint.pformat(expected) + '\n'
        try:
            self.assertEqual(result, expected)
        except AssertionError:
            print('\n%s\n' % (self,), file=sys.stderr)
            print('-: expected\n+: result', file=sys.stderr)
            print(''.join(self.compare(expected.splitlines(1),
                                                     result.splitlines(1))), file=sys.stderr)
            raise

    def test_nofiles(self):
        self.compare_output(self.files_settings(),
                            self.expected_settings())

    def test_old(self):
        with warnings.catch_warnings(record=True) as wng:
            warnings.simplefilter("always") # check also for deprecation warning
            self.compare_output(self.files_settings('old'),
                                self.expected_settings('old'))
            self.assertEqual(len(wng), 1, "Expected a FutureWarning.")
            assert issubclass(wng[-1].category, FutureWarning)

    def test_one(self):
        self.compare_output(self.files_settings('one'),
                            self.expected_settings('one'))

    def test_multiple(self):
        self.compare_output(self.files_settings('one', 'two'),
                            self.expected_settings('one', 'two'))

    def test_old_and_new(self):
        self.compare_output(self.files_settings('old', 'two'),
                            self.expected_settings('old', 'two'))

    def test_list(self):
        self.compare_output(self.files_settings('list'),
                            self.expected_settings('list'))

    def test_list2(self):
        self.compare_output(self.files_settings('list', 'list2'),
                            self.expected_settings('list2'))

    def test_error_handler(self):
        self.compare_output(self.files_settings('error'),
                            self.expected_settings('error'))


class ConfigEnvVarFileTests(ConfigFileTests):

    """
    Repeats the tests of `ConfigFileTests` using the ``DOCUTILSCONFIG``
    environment variable and the standard Docutils config file mechanism.
    """

    def setUp(self):
        ConfigFileTests.setUp(self)
        self.orig_environ = os.environ
        os.environ = os.environ.copy()

    def files_settings(self, *names):
        files = [self.config_files[name] for name in names]
        os.environ['DOCUTILSCONFIG'] = os.pathsep.join(files)
        settings = self.option_parser.get_standard_config_settings()
        return settings.__dict__

    def tearDown(self):
        os.environ = self.orig_environ


class HelperFunctionsTests(unittest.TestCase):

    pathdict = {'foo': 'hallo', 'ham': u'h\xE4m', 'spam': u'spam'}
    keys = ['foo', 'ham']

    def setUp(self):
        self.option_parser = frontend.OptionParser(
            components=(rst.Parser,), read_config_files=None)

    def test_make_paths_absolute(self):
        pathdict = self.pathdict.copy()
        frontend.make_paths_absolute(pathdict, self.keys, base_path='base')
        self.assertEqual(pathdict['foo'], os.path.abspath('base/hallo'))
        self.assertEqual(pathdict['ham'], os.path.abspath(u'base/h\xE4m'))
        # not touched, because key not in keys:
        self.assertEqual(pathdict['spam'], u'spam')

    def test_make_paths_absolute_cwd(self):
        # With base_path None, the cwd is used as base path.
        # Settings values may-be `unicode` instances, therefore
        # os.getcwdu() is used and the converted path is a unicode instance:
        pathdict = self.pathdict.copy()
        frontend.make_paths_absolute(pathdict, self.keys)
        self.assertEqual(pathdict['foo'], os.path.abspath(u'hallo'))
        self.assertEqual(pathdict['ham'], os.path.abspath(u'h\xE4m'))
        # not touched, because key not in keys:
        self.assertEqual(pathdict['spam'], u'spam')

    boolean_settings = (
                (True, True ),
                ('1', True ),
                (u'on', True ),
                ('yes', True ),
                (u'true', True ),
                (u'0', False ),
                ('off', False ),
                (u'no', False ),
                ('false', False ),
               )
    def test_validate_boolean(self):
        for t in self.boolean_settings:
            self.assertEqual(
                frontend.validate_boolean(None, t[0], self.option_parser),
                             t[1])

    def test_validate_ternary(self):
        tests = (
                 ('500V', '500V'),
                 (u'parrot', u'parrot'),
                )
        for t in self.boolean_settings + tests:
            self.assertEqual(
                frontend.validate_ternary(None, t[0], self.option_parser),
                             t[1])

    def test_validate_colon_separated_string_list(self):
        tests = (
                    (u'a', ['a',] ),
                    ('a', ['a',] ),
                    (u'a:b', ['a', 'b'] ),
                    ('a:b', ['a', 'b'] ),
                    ([u'a',], ['a',] ),
                    ([u'a', u'b:c'], ['a', 'b', 'c'] ),
                )
        for t in tests:
            self.assertEqual(
                    frontend.validate_colon_separated_string_list(None, t[0], None),
                    t[1])

    def test_validate_comma_separated_list(self):
        tests = (
                    (u'a', ['a',] ),
                    ('a', ['a',] ),
                    (u'a,b', ['a', 'b'] ),
                    ('a,b', ['a', 'b'] ),
                    ([u'a',], ['a',] ),
                    ([u'a', u'b,c'], ['a', 'b', 'c'] ),
                    (['a', 'b,c'], ['a', 'b', 'c'] ),
                )
        for t in tests:
            self.assertEqual(
                    frontend.validate_comma_separated_list(None, t[0], None),
                    t[1])

    def test_validate_url_trailing_slash(self):
        tests = (
                    ('', './' ),
                    (None, './' ),
                    (u'http://example.org', u'http://example.org/' ),
                    ('http://example.org/', 'http://example.org/' ),
                )
        for t in tests:
            self.assertEqual(
                    frontend.validate_url_trailing_slash(None, t[0], None),
                    t[1])

    def test_validate_smartquotes_locales(self):
        tests = (
                 ('en:ssvv', [('en', 'ssvv')]),
                 (u'sd:«»°°', [(u'sd', u'«»°°')]),
                 ([('sd', u'«»°°'), u'ds:°°«»'], [('sd', u'«»°°'),
                                                  ('ds', u'°°«»')]),
                 (u'frs:« : »:((:))', [(u'frs', [u'« ', u' »',
                                                 u'((', u'))'])]),
                )
        for t in tests:
            self.assertEqual(
                    frontend.validate_smartquotes_locales(None, t[0], None),
                    t[1])

    def test_set_conditions_deprecation_warning(self):
        reporter = utils.Reporter('test', 1, 4)
        with warnings.catch_warnings(record=True) as wng:
            warnings.simplefilter("always")
            reporter.set_conditions('foo', 1, 4) # trigger warning
            self.assertEqual(len(wng), 1, "Expected a DeprecationWarning.")
            assert issubclass(wng[-1].category, DeprecationWarning)


if __name__ == '__main__':
    unittest.main()
