"""Tests for distutils.filelist."""
import os
import re
import unittest
from distutils import debug
from distutils.log import WARN
from distutils.errors import DistutilsTemplateError
from distutils.filelist import glob_to_re, translate_pattern, FileList
from distutils import filelist

from test.support import captured_stdout, run_unittest
from distutils.tests import support

from . import py38compat as os_helper


MANIFEST_IN = """\
include ok
include xo
exclude xo
include foo.tmp
include buildout.cfg
global-include *.x
global-include *.txt
global-exclude *.tmp
recursive-include f *.oo
recursive-exclude global *.x
graft dir
prune dir3
"""


def make_local_path(s):
    """Converts '/' in a string to os.sep"""
    return s.replace('/', os.sep)


class FileListTestCase(support.LoggingSilencer, unittest.TestCase):
    def assertNoWarnings(self):
        self.assertEqual(self.get_logs(WARN), [])
        self.clear_logs()

    def assertWarnings(self):
        self.assertGreater(len(self.get_logs(WARN)), 0)
        self.clear_logs()

    def test_glob_to_re(self):
        sep = os.sep
        if os.sep == '\\':
            sep = re.escape(os.sep)

        for glob, regex in (
            # simple cases
            ('foo*', r'(?s:foo[^%(sep)s]*)\Z'),
            ('foo?', r'(?s:foo[^%(sep)s])\Z'),
            ('foo??', r'(?s:foo[^%(sep)s][^%(sep)s])\Z'),
            # special cases
            (r'foo\\*', r'(?s:foo\\\\[^%(sep)s]*)\Z'),
            (r'foo\\\*', r'(?s:foo\\\\\\[^%(sep)s]*)\Z'),
            ('foo????', r'(?s:foo[^%(sep)s][^%(sep)s][^%(sep)s][^%(sep)s])\Z'),
            (r'foo\\??', r'(?s:foo\\\\[^%(sep)s][^%(sep)s])\Z'),
        ):
            regex = regex % {'sep': sep}
            self.assertEqual(glob_to_re(glob), regex)

    def test_process_template_line(self):
        # testing  all MANIFEST.in template patterns
        file_list = FileList()
        mlp = make_local_path

        # simulated file list
        file_list.allfiles = [
            'foo.tmp',
            'ok',
            'xo',
            'four.txt',
            'buildout.cfg',
            # filelist does not filter out VCS directories,
            # it's sdist that does
            mlp('.hg/last-message.txt'),
            mlp('global/one.txt'),
            mlp('global/two.txt'),
            mlp('global/files.x'),
            mlp('global/here.tmp'),
            mlp('f/o/f.oo'),
            mlp('dir/graft-one'),
            mlp('dir/dir2/graft2'),
            mlp('dir3/ok'),
            mlp('dir3/sub/ok.txt'),
        ]

        for line in MANIFEST_IN.split('\n'):
            if line.strip() == '':
                continue
            file_list.process_template_line(line)

        wanted = [
            'ok',
            'buildout.cfg',
            'four.txt',
            mlp('.hg/last-message.txt'),
            mlp('global/one.txt'),
            mlp('global/two.txt'),
            mlp('f/o/f.oo'),
            mlp('dir/graft-one'),
            mlp('dir/dir2/graft2'),
        ]

        self.assertEqual(file_list.files, wanted)

    def test_debug_print(self):
        file_list = FileList()
        with captured_stdout() as stdout:
            file_list.debug_print('xxx')
        self.assertEqual(stdout.getvalue(), '')

        debug.DEBUG = True
        try:
            with captured_stdout() as stdout:
                file_list.debug_print('xxx')
            self.assertEqual(stdout.getvalue(), 'xxx\n')
        finally:
            debug.DEBUG = False

    def test_set_allfiles(self):
        file_list = FileList()
        files = ['a', 'b', 'c']
        file_list.set_allfiles(files)
        self.assertEqual(file_list.allfiles, files)

    def test_remove_duplicates(self):
        file_list = FileList()
        file_list.files = ['a', 'b', 'a', 'g', 'c', 'g']
        # files must be sorted beforehand (sdist does it)
        file_list.sort()
        file_list.remove_duplicates()
        self.assertEqual(file_list.files, ['a', 'b', 'c', 'g'])

    def test_translate_pattern(self):
        # not regex
        self.assertTrue(
            hasattr(translate_pattern('a', anchor=True, is_regex=False), 'search')
        )

        # is a regex
        regex = re.compile('a')
        self.assertEqual(translate_pattern(regex, anchor=True, is_regex=True), regex)

        # plain string flagged as regex
        self.assertTrue(
            hasattr(translate_pattern('a', anchor=True, is_regex=True), 'search')
        )

        # glob support
        self.assertTrue(
            translate_pattern('*.py', anchor=True, is_regex=False).search('filelist.py')
        )

    def test_exclude_pattern(self):
        # return False if no match
        file_list = FileList()
        self.assertFalse(file_list.exclude_pattern('*.py'))

        # return True if files match
        file_list = FileList()
        file_list.files = ['a.py', 'b.py']
        self.assertTrue(file_list.exclude_pattern('*.py'))

        # test excludes
        file_list = FileList()
        file_list.files = ['a.py', 'a.txt']
        file_list.exclude_pattern('*.py')
        self.assertEqual(file_list.files, ['a.txt'])

    def test_include_pattern(self):
        # return False if no match
        file_list = FileList()
        file_list.set_allfiles([])
        self.assertFalse(file_list.include_pattern('*.py'))

        # return True if files match
        file_list = FileList()
        file_list.set_allfiles(['a.py', 'b.txt'])
        self.assertTrue(file_list.include_pattern('*.py'))

        # test * matches all files
        file_list = FileList()
        self.assertIsNone(file_list.allfiles)
        file_list.set_allfiles(['a.py', 'b.txt'])
        file_list.include_pattern('*')
        self.assertEqual(file_list.allfiles, ['a.py', 'b.txt'])

    def test_process_template(self):
        mlp = make_local_path
        # invalid lines
        file_list = FileList()
        for action in (
            'include',
            'exclude',
            'global-include',
            'global-exclude',
            'recursive-include',
            'recursive-exclude',
            'graft',
            'prune',
            'blarg',
        ):
            self.assertRaises(
                DistutilsTemplateError, file_list.process_template_line, action
            )

        # include
        file_list = FileList()
        file_list.set_allfiles(['a.py', 'b.txt', mlp('d/c.py')])

        file_list.process_template_line('include *.py')
        self.assertEqual(file_list.files, ['a.py'])
        self.assertNoWarnings()

        file_list.process_template_line('include *.rb')
        self.assertEqual(file_list.files, ['a.py'])
        self.assertWarnings()

        # exclude
        file_list = FileList()
        file_list.files = ['a.py', 'b.txt', mlp('d/c.py')]

        file_list.process_template_line('exclude *.py')
        self.assertEqual(file_list.files, ['b.txt', mlp('d/c.py')])
        self.assertNoWarnings()

        file_list.process_template_line('exclude *.rb')
        self.assertEqual(file_list.files, ['b.txt', mlp('d/c.py')])
        self.assertWarnings()

        # global-include
        file_list = FileList()
        file_list.set_allfiles(['a.py', 'b.txt', mlp('d/c.py')])

        file_list.process_template_line('global-include *.py')
        self.assertEqual(file_list.files, ['a.py', mlp('d/c.py')])
        self.assertNoWarnings()

        file_list.process_template_line('global-include *.rb')
        self.assertEqual(file_list.files, ['a.py', mlp('d/c.py')])
        self.assertWarnings()

        # global-exclude
        file_list = FileList()
        file_list.files = ['a.py', 'b.txt', mlp('d/c.py')]

        file_list.process_template_line('global-exclude *.py')
        self.assertEqual(file_list.files, ['b.txt'])
        self.assertNoWarnings()

        file_list.process_template_line('global-exclude *.rb')
        self.assertEqual(file_list.files, ['b.txt'])
        self.assertWarnings()

        # recursive-include
        file_list = FileList()
        file_list.set_allfiles(['a.py', mlp('d/b.py'), mlp('d/c.txt'), mlp('d/d/e.py')])

        file_list.process_template_line('recursive-include d *.py')
        self.assertEqual(file_list.files, [mlp('d/b.py'), mlp('d/d/e.py')])
        self.assertNoWarnings()

        file_list.process_template_line('recursive-include e *.py')
        self.assertEqual(file_list.files, [mlp('d/b.py'), mlp('d/d/e.py')])
        self.assertWarnings()

        # recursive-exclude
        file_list = FileList()
        file_list.files = ['a.py', mlp('d/b.py'), mlp('d/c.txt'), mlp('d/d/e.py')]

        file_list.process_template_line('recursive-exclude d *.py')
        self.assertEqual(file_list.files, ['a.py', mlp('d/c.txt')])
        self.assertNoWarnings()

        file_list.process_template_line('recursive-exclude e *.py')
        self.assertEqual(file_list.files, ['a.py', mlp('d/c.txt')])
        self.assertWarnings()

        # graft
        file_list = FileList()
        file_list.set_allfiles(['a.py', mlp('d/b.py'), mlp('d/d/e.py'), mlp('f/f.py')])

        file_list.process_template_line('graft d')
        self.assertEqual(file_list.files, [mlp('d/b.py'), mlp('d/d/e.py')])
        self.assertNoWarnings()

        file_list.process_template_line('graft e')
        self.assertEqual(file_list.files, [mlp('d/b.py'), mlp('d/d/e.py')])
        self.assertWarnings()

        # prune
        file_list = FileList()
        file_list.files = ['a.py', mlp('d/b.py'), mlp('d/d/e.py'), mlp('f/f.py')]

        file_list.process_template_line('prune d')
        self.assertEqual(file_list.files, ['a.py', mlp('f/f.py')])
        self.assertNoWarnings()

        file_list.process_template_line('prune e')
        self.assertEqual(file_list.files, ['a.py', mlp('f/f.py')])
        self.assertWarnings()


class FindAllTestCase(unittest.TestCase):
    @os_helper.skip_unless_symlink
    def test_missing_symlink(self):
        with os_helper.temp_cwd():
            os.symlink('foo', 'bar')
            self.assertEqual(filelist.findall(), [])

    def test_basic_discovery(self):
        """
        When findall is called with no parameters or with
        '.' as the parameter, the dot should be omitted from
        the results.
        """
        with os_helper.temp_cwd():
            os.mkdir('foo')
            file1 = os.path.join('foo', 'file1.txt')
            os_helper.create_empty_file(file1)
            os.mkdir('bar')
            file2 = os.path.join('bar', 'file2.txt')
            os_helper.create_empty_file(file2)
            expected = [file2, file1]
            self.assertEqual(sorted(filelist.findall()), expected)

    def test_non_local_discovery(self):
        """
        When findall is called with another path, the full
        path name should be returned.
        """
        with os_helper.temp_dir() as temp_dir:
            file1 = os.path.join(temp_dir, 'file1.txt')
            os_helper.create_empty_file(file1)
            expected = [file1]
            self.assertEqual(filelist.findall(temp_dir), expected)

    @os_helper.skip_unless_symlink
    def test_symlink_loop(self):
        with os_helper.temp_dir() as temp_dir:
            link = os.path.join(temp_dir, 'link-to-parent')
            content = os.path.join(temp_dir, 'somefile')
            os_helper.create_empty_file(content)
            os.symlink('.', link)
            files = filelist.findall(temp_dir)
            assert len(files) == 1


def test_suite():
    return unittest.TestSuite(
        [
            unittest.TestLoader().loadTestsFromTestCase(FileListTestCase),
            unittest.TestLoader().loadTestsFromTestCase(FindAllTestCase),
        ]
    )


if __name__ == "__main__":
    run_unittest(test_suite())
