We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Nested dot folders copied upon build
Sometimes it's necessary to include files in a theme which MkDocs should conditionally include in the templates, but not copy to the `site` directory when the documentation is built. As discussed in #1980, MkDocs will ignore files that start with a `.`, but this only applies to top-level directories. It would be great if MkDocs could exclude nested dot directories from the build.

A concrete example: the next version of the Material theme will bundle the FontAwesome files as SVGs for inlining during build time, but MkDocs copies all of them to the `site` directory. The folder structure is:

```
assets/images/icons/fontawesome/...
```

The current workaround is to add a `*.html` to the files bundles with the theme, resulting in a `*.svg.html` extension for each of the 1.500 icons. While this works, it's semantically incorrect.

If MkDocs would ignore nested dot directories, we could just use:

```
assets/images/icons/.fontawesome/...
```

As pointed out by @waylan, the code in question is here:

https://github.com/mkdocs/mkdocs/blob/2fca717794c0c2e581b8ba17149bc292edeb83e4/mkdocs/structure/files.py#L67-L69

I'm not proficient in Python, but since this looks like a glob implementation, I would guess that `**/.*` would solve the problem

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mkdocs/structure/files.py`
Content:
```
1 import fnmatch
2 import os
3 import logging
4 from functools import cmp_to_key
5 from urllib.parse import quote as urlquote
6 
7 from mkdocs import utils
8 
9 
10 log = logging.getLogger(__name__)
11 log.addFilter(utils.warning_filter)
12 
13 
14 class Files:
15     """ A collection of File objects. """
16     def __init__(self, files):
17         self._files = files
18         self.src_paths = {file.src_path: file for file in files}
19 
20     def __iter__(self):
21         return iter(self._files)
22 
23     def __len__(self):
24         return len(self._files)
25 
26     def __contains__(self, path):
27         return path in self.src_paths
28 
29     def get_file_from_path(self, path):
30         """ Return a File instance with File.src_path equal to path. """
31         return self.src_paths.get(os.path.normpath(path))
32 
33     def append(self, file):
34         """ Append file to Files collection. """
35         self._files.append(file)
36         self.src_paths[file.src_path] = file
37 
38     def copy_static_files(self, dirty=False):
39         """ Copy static files from source to destination. """
40         for file in self:
41             if not file.is_documentation_page():
42                 file.copy_file(dirty)
43 
44     def documentation_pages(self):
45         """ Return iterable of all Markdown page file objects. """
46         return [file for file in self if file.is_documentation_page()]
47 
48     def static_pages(self):
49         """ Return iterable of all static page file objects. """
50         return [file for file in self if file.is_static_page()]
51 
52     def media_files(self):
53         """ Return iterable of all file objects which are not documentation or static pages. """
54         return [file for file in self if file.is_media_file()]
55 
56     def javascript_files(self):
57         """ Return iterable of all javascript file objects. """
58         return [file for file in self if file.is_javascript()]
59 
60     def css_files(self):
61         """ Return iterable of all CSS file objects. """
62         return [file for file in self if file.is_css()]
63 
64     def add_files_from_theme(self, env, config):
65         """ Retrieve static files from Jinja environment and add to collection. """
66         def filter(name):
67             patterns = ['.*', '*.py', '*.pyc', '*.html', '*readme*', 'mkdocs_theme.yml']
68             patterns.extend('*{}'.format(x) for x in utils.markdown_extensions)
69             patterns.extend(config['theme'].static_templates)
70             for pattern in patterns:
71                 if fnmatch.fnmatch(name.lower(), pattern):
72                     return False
73             return True
74         for path in env.list_templates(filter_func=filter):
75             # Theme files do not override docs_dir files
76             path = os.path.normpath(path)
77             if path not in self:
78                 for dir in config['theme'].dirs:
79                     # Find the first theme dir which contains path
80                     if os.path.isfile(os.path.join(dir, path)):
81                         self.append(File(path, dir, config['site_dir'], config['use_directory_urls']))
82                         break
83 
84 
85 class File:
86     """
87     A MkDocs File object.
88 
89     Points to the source and destination locations of a file.
90 
91     The `path` argument must be a path that exists relative to `src_dir`.
92 
93     The `src_dir` and `dest_dir` must be absolute paths on the local file system.
94 
95     The `use_directory_urls` argument controls how destination paths are generated. If `False`, a Markdown file is
96     mapped to an HTML file of the same name (the file extension is changed to `.html`). If True, a Markdown file is
97     mapped to an HTML index file (`index.html`) nested in a directory using the "name" of the file in `path`. The
98     `use_directory_urls` argument has no effect on non-Markdown files.
99 
100     File objects have the following properties, which are Unicode strings:
101 
102     File.src_path
103         The pure path of the source file relative to the source directory.
104 
105     File.abs_src_path
106         The absolute concrete path of the source file.
107 
108     File.dest_path
109         The pure path of the destination file relative to the destination directory.
110 
111     File.abs_dest_path
112         The absolute concrete path of the destination file.
113 
114     File.url
115         The url of the destination file relative to the destination directory as a string.
116     """
117     def __init__(self, path, src_dir, dest_dir, use_directory_urls):
118         self.page = None
119         self.src_path = os.path.normpath(path)
120         self.abs_src_path = os.path.normpath(os.path.join(src_dir, self.src_path))
121         self.name = self._get_stem()
122         self.dest_path = self._get_dest_path(use_directory_urls)
123         self.abs_dest_path = os.path.normpath(os.path.join(dest_dir, self.dest_path))
124         self.url = self._get_url(use_directory_urls)
125 
126     def __eq__(self, other):
127 
128         def sub_dict(d):
129             return {key: value for key, value in d.items() if key in ['src_path', 'abs_src_path', 'url']}
130 
131         return (isinstance(other, self.__class__) and sub_dict(self.__dict__) == sub_dict(other.__dict__))
132 
133     def __ne__(self, other):
134         return not self.__eq__(other)
135 
136     def _get_stem(self):
137         """ Return the name of the file without it's extension. """
138         filename = os.path.basename(self.src_path)
139         stem, ext = os.path.splitext(filename)
140         return 'index' if stem in ('index', 'README') else stem
141 
142     def _get_dest_path(self, use_directory_urls):
143         """ Return destination path based on source path. """
144         if self.is_documentation_page():
145             if use_directory_urls:
146                 parent, filename = os.path.split(self.src_path)
147                 if self.name == 'index':
148                     # index.md or README.md => index.html
149                     return os.path.join(parent, 'index.html')
150                 else:
151                     # foo.md => foo/index.html
152                     return os.path.join(parent, self.name, 'index.html')
153             else:
154                 # foo.md => foo.html
155                 root, ext = os.path.splitext(self.src_path)
156                 return root + '.html'
157         return self.src_path
158 
159     def _get_url(self, use_directory_urls):
160         """ Return url based in destination path. """
161         url = self.dest_path.replace(os.path.sep, '/')
162         dirname, filename = os.path.split(url)
163         if use_directory_urls and filename == 'index.html':
164             if dirname == '':
165                 url = '.'
166             else:
167                 url = dirname + '/'
168         return urlquote(url)
169 
170     def url_relative_to(self, other):
171         """ Return url for file relative to other file. """
172         return utils.get_relative_url(self.url, other.url if isinstance(other, File) else other)
173 
174     def copy_file(self, dirty=False):
175         """ Copy source file to destination, ensuring parent directories exist. """
176         if dirty and not self.is_modified():
177             log.debug("Skip copying unmodified file: '{}'".format(self.src_path))
178         else:
179             log.debug("Copying media file: '{}'".format(self.src_path))
180             utils.copy_file(self.abs_src_path, self.abs_dest_path)
181 
182     def is_modified(self):
183         if os.path.isfile(self.abs_dest_path):
184             return os.path.getmtime(self.abs_dest_path) < os.path.getmtime(self.abs_src_path)
185         return True
186 
187     def is_documentation_page(self):
188         """ Return True if file is a Markdown page. """
189         return os.path.splitext(self.src_path)[1] in utils.markdown_extensions
190 
191     def is_static_page(self):
192         """ Return True if file is a static page (html, xml, json). """
193         return os.path.splitext(self.src_path)[1] in (
194             '.html',
195             '.htm',
196             '.xml',
197             '.json',
198         )
199 
200     def is_media_file(self):
201         """ Return True if file is not a documentation or static page. """
202         return not (self.is_documentation_page() or self.is_static_page())
203 
204     def is_javascript(self):
205         """ Return True if file is a JavaScript file. """
206         return os.path.splitext(self.src_path)[1] in (
207             '.js',
208             '.javascript',
209         )
210 
211     def is_css(self):
212         """ Return True if file is a CSS file. """
213         return os.path.splitext(self.src_path)[1] in (
214             '.css',
215         )
216 
217 
218 def get_files(config):
219     """ Walk the `docs_dir` and return a Files collection. """
220     files = []
221     exclude = ['.*', '/templates']
222 
223     for source_dir, dirnames, filenames in os.walk(config['docs_dir'], followlinks=True):
224         relative_dir = os.path.relpath(source_dir, config['docs_dir'])
225 
226         for dirname in list(dirnames):
227             path = os.path.normpath(os.path.join(relative_dir, dirname))
228             # Skip any excluded directories
229             if _filter_paths(basename=dirname, path=path, is_dir=True, exclude=exclude):
230                 dirnames.remove(dirname)
231         dirnames.sort()
232 
233         for filename in _sort_files(filenames):
234             path = os.path.normpath(os.path.join(relative_dir, filename))
235             # Skip any excluded files
236             if _filter_paths(basename=filename, path=path, is_dir=False, exclude=exclude):
237                 continue
238             # Skip README.md if an index file also exists in dir
239             if filename.lower() == 'readme.md' and 'index.md' in filenames:
240                 log.warning("Both index.md and readme.md found. Skipping readme.md from {}".format(source_dir))
241                 continue
242             files.append(File(path, config['docs_dir'], config['site_dir'], config['use_directory_urls']))
243 
244     return Files(files)
245 
246 
247 def _sort_files(filenames):
248     """ Always sort `index` or `README` as first filename in list. """
249 
250     def compare(x, y):
251         if x == y:
252             return 0
253         if os.path.splitext(y)[0] in ['index', 'README']:
254             return 1
255         if os.path.splitext(x)[0] in ['index', 'README'] or x < y:
256             return -1
257         return 1
258 
259     return sorted(filenames, key=cmp_to_key(compare))
260 
261 
262 def _filter_paths(basename, path, is_dir, exclude):
263     """ .gitignore style file filtering. """
264     for item in exclude:
265         # Items ending in '/' apply only to directories.
266         if item.endswith('/') and not is_dir:
267             continue
268         # Items starting with '/' apply to the whole path.
269         # In any other cases just the basename is used.
270         match = path if item.startswith('/') else basename
271         if fnmatch.fnmatch(match, item.strip('/')):
272             return True
273     return False
274
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mkdocs/structure/files.py b/mkdocs/structure/files.py
--- a/mkdocs/structure/files.py
+++ b/mkdocs/structure/files.py
@@ -64,7 +64,8 @@
     def add_files_from_theme(self, env, config):
         """ Retrieve static files from Jinja environment and add to collection. """
         def filter(name):
-            patterns = ['.*', '*.py', '*.pyc', '*.html', '*readme*', 'mkdocs_theme.yml']
+            # '.*' filters dot files/dirs at root level whereas '*/.*' filters nested levels
+            patterns = ['.*', '*/.*', '*.py', '*.pyc', '*.html', '*readme*', 'mkdocs_theme.yml']
             patterns.extend('*{}'.format(x) for x in utils.markdown_extensions)
             patterns.extend(config['theme'].static_templates)
             for pattern in patterns:

{"golden_diff": "diff --git a/mkdocs/structure/files.py b/mkdocs/structure/files.py\n--- a/mkdocs/structure/files.py\n+++ b/mkdocs/structure/files.py\n@@ -64,7 +64,8 @@\n     def add_files_from_theme(self, env, config):\n         \"\"\" Retrieve static files from Jinja environment and add to collection. \"\"\"\n         def filter(name):\n-            patterns = ['.*', '*.py', '*.pyc', '*.html', '*readme*', 'mkdocs_theme.yml']\n+            # '.*' filters dot files/dirs at root level whereas '*/.*' filters nested levels\n+            patterns = ['.*', '*/.*', '*.py', '*.pyc', '*.html', '*readme*', 'mkdocs_theme.yml']\n             patterns.extend('*{}'.format(x) for x in utils.markdown_extensions)\n             patterns.extend(config['theme'].static_templates)\n             for pattern in patterns:\n", "issue": "Nested dot folders copied upon build\nSometimes it's necessary to include files in a theme which MkDocs should conditionally include in the templates, but not copy to the `site` directory when the documentation is built. As discussed in #1980, MkDocs will ignore files that start with a `.`, but this only applies to top-level directories. It would be great if MkDocs could exclude nested dot directories from the build.\r\n\r\nA concrete example: the next version of the Material theme will bundle the FontAwesome files as SVGs for inlining during build time, but MkDocs copies all of them to the `site` directory. The folder structure is:\r\n\r\n```\r\nassets/images/icons/fontawesome/...\r\n```\r\n\r\nThe current workaround is to add a `*.html` to the files bundles with the theme, resulting in a `*.svg.html` extension for each of the 1.500 icons. While this works, it's semantically incorrect.\r\n\r\nIf MkDocs would ignore nested dot directories, we could just use:\r\n\r\n```\r\nassets/images/icons/.fontawesome/...\r\n```\r\n\r\nAs pointed out by @waylan, the code in question is here:\r\n\r\nhttps://github.com/mkdocs/mkdocs/blob/2fca717794c0c2e581b8ba17149bc292edeb83e4/mkdocs/structure/files.py#L67-L69\r\n\r\nI'm not proficient in Python, but since this looks like a glob implementation, I would guess that `**/.*` would solve the problem\n", "before_files": [{"content": "import fnmatch\nimport os\nimport logging\nfrom functools import cmp_to_key\nfrom urllib.parse import quote as urlquote\n\nfrom mkdocs import utils\n\n\nlog = logging.getLogger(__name__)\nlog.addFilter(utils.warning_filter)\n\n\nclass Files:\n    \"\"\" A collection of File objects. \"\"\"\n    def __init__(self, files):\n        self._files = files\n        self.src_paths = {file.src_path: file for file in files}\n\n    def __iter__(self):\n        return iter(self._files)\n\n    def __len__(self):\n        return len(self._files)\n\n    def __contains__(self, path):\n        return path in self.src_paths\n\n    def get_file_from_path(self, path):\n        \"\"\" Return a File instance with File.src_path equal to path. \"\"\"\n        return self.src_paths.get(os.path.normpath(path))\n\n    def append(self, file):\n        \"\"\" Append file to Files collection. \"\"\"\n        self._files.append(file)\n        self.src_paths[file.src_path] = file\n\n    def copy_static_files(self, dirty=False):\n        \"\"\" Copy static files from source to destination. \"\"\"\n        for file in self:\n            if not file.is_documentation_page():\n                file.copy_file(dirty)\n\n    def documentation_pages(self):\n        \"\"\" Return iterable of all Markdown page file objects. \"\"\"\n        return [file for file in self if file.is_documentation_page()]\n\n    def static_pages(self):\n        \"\"\" Return iterable of all static page file objects. \"\"\"\n        return [file for file in self if file.is_static_page()]\n\n    def media_files(self):\n        \"\"\" Return iterable of all file objects which are not documentation or static pages. \"\"\"\n        return [file for file in self if file.is_media_file()]\n\n    def javascript_files(self):\n        \"\"\" Return iterable of all javascript file objects. \"\"\"\n        return [file for file in self if file.is_javascript()]\n\n    def css_files(self):\n        \"\"\" Return iterable of all CSS file objects. \"\"\"\n        return [file for file in self if file.is_css()]\n\n    def add_files_from_theme(self, env, config):\n        \"\"\" Retrieve static files from Jinja environment and add to collection. \"\"\"\n        def filter(name):\n            patterns = ['.*', '*.py', '*.pyc', '*.html', '*readme*', 'mkdocs_theme.yml']\n            patterns.extend('*{}'.format(x) for x in utils.markdown_extensions)\n            patterns.extend(config['theme'].static_templates)\n            for pattern in patterns:\n                if fnmatch.fnmatch(name.lower(), pattern):\n                    return False\n            return True\n        for path in env.list_templates(filter_func=filter):\n            # Theme files do not override docs_dir files\n            path = os.path.normpath(path)\n            if path not in self:\n                for dir in config['theme'].dirs:\n                    # Find the first theme dir which contains path\n                    if os.path.isfile(os.path.join(dir, path)):\n                        self.append(File(path, dir, config['site_dir'], config['use_directory_urls']))\n                        break\n\n\nclass File:\n    \"\"\"\n    A MkDocs File object.\n\n    Points to the source and destination locations of a file.\n\n    The `path` argument must be a path that exists relative to `src_dir`.\n\n    The `src_dir` and `dest_dir` must be absolute paths on the local file system.\n\n    The `use_directory_urls` argument controls how destination paths are generated. If `False`, a Markdown file is\n    mapped to an HTML file of the same name (the file extension is changed to `.html`). If True, a Markdown file is\n    mapped to an HTML index file (`index.html`) nested in a directory using the \"name\" of the file in `path`. The\n    `use_directory_urls` argument has no effect on non-Markdown files.\n\n    File objects have the following properties, which are Unicode strings:\n\n    File.src_path\n        The pure path of the source file relative to the source directory.\n\n    File.abs_src_path\n        The absolute concrete path of the source file.\n\n    File.dest_path\n        The pure path of the destination file relative to the destination directory.\n\n    File.abs_dest_path\n        The absolute concrete path of the destination file.\n\n    File.url\n        The url of the destination file relative to the destination directory as a string.\n    \"\"\"\n    def __init__(self, path, src_dir, dest_dir, use_directory_urls):\n        self.page = None\n        self.src_path = os.path.normpath(path)\n        self.abs_src_path = os.path.normpath(os.path.join(src_dir, self.src_path))\n        self.name = self._get_stem()\n        self.dest_path = self._get_dest_path(use_directory_urls)\n        self.abs_dest_path = os.path.normpath(os.path.join(dest_dir, self.dest_path))\n        self.url = self._get_url(use_directory_urls)\n\n    def __eq__(self, other):\n\n        def sub_dict(d):\n            return {key: value for key, value in d.items() if key in ['src_path', 'abs_src_path', 'url']}\n\n        return (isinstance(other, self.__class__) and sub_dict(self.__dict__) == sub_dict(other.__dict__))\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def _get_stem(self):\n        \"\"\" Return the name of the file without it's extension. \"\"\"\n        filename = os.path.basename(self.src_path)\n        stem, ext = os.path.splitext(filename)\n        return 'index' if stem in ('index', 'README') else stem\n\n    def _get_dest_path(self, use_directory_urls):\n        \"\"\" Return destination path based on source path. \"\"\"\n        if self.is_documentation_page():\n            if use_directory_urls:\n                parent, filename = os.path.split(self.src_path)\n                if self.name == 'index':\n                    # index.md or README.md => index.html\n                    return os.path.join(parent, 'index.html')\n                else:\n                    # foo.md => foo/index.html\n                    return os.path.join(parent, self.name, 'index.html')\n            else:\n                # foo.md => foo.html\n                root, ext = os.path.splitext(self.src_path)\n                return root + '.html'\n        return self.src_path\n\n    def _get_url(self, use_directory_urls):\n        \"\"\" Return url based in destination path. \"\"\"\n        url = self.dest_path.replace(os.path.sep, '/')\n        dirname, filename = os.path.split(url)\n        if use_directory_urls and filename == 'index.html':\n            if dirname == '':\n                url = '.'\n            else:\n                url = dirname + '/'\n        return urlquote(url)\n\n    def url_relative_to(self, other):\n        \"\"\" Return url for file relative to other file. \"\"\"\n        return utils.get_relative_url(self.url, other.url if isinstance(other, File) else other)\n\n    def copy_file(self, dirty=False):\n        \"\"\" Copy source file to destination, ensuring parent directories exist. \"\"\"\n        if dirty and not self.is_modified():\n            log.debug(\"Skip copying unmodified file: '{}'\".format(self.src_path))\n        else:\n            log.debug(\"Copying media file: '{}'\".format(self.src_path))\n            utils.copy_file(self.abs_src_path, self.abs_dest_path)\n\n    def is_modified(self):\n        if os.path.isfile(self.abs_dest_path):\n            return os.path.getmtime(self.abs_dest_path) < os.path.getmtime(self.abs_src_path)\n        return True\n\n    def is_documentation_page(self):\n        \"\"\" Return True if file is a Markdown page. \"\"\"\n        return os.path.splitext(self.src_path)[1] in utils.markdown_extensions\n\n    def is_static_page(self):\n        \"\"\" Return True if file is a static page (html, xml, json). \"\"\"\n        return os.path.splitext(self.src_path)[1] in (\n            '.html',\n            '.htm',\n            '.xml',\n            '.json',\n        )\n\n    def is_media_file(self):\n        \"\"\" Return True if file is not a documentation or static page. \"\"\"\n        return not (self.is_documentation_page() or self.is_static_page())\n\n    def is_javascript(self):\n        \"\"\" Return True if file is a JavaScript file. \"\"\"\n        return os.path.splitext(self.src_path)[1] in (\n            '.js',\n            '.javascript',\n        )\n\n    def is_css(self):\n        \"\"\" Return True if file is a CSS file. \"\"\"\n        return os.path.splitext(self.src_path)[1] in (\n            '.css',\n        )\n\n\ndef get_files(config):\n    \"\"\" Walk the `docs_dir` and return a Files collection. \"\"\"\n    files = []\n    exclude = ['.*', '/templates']\n\n    for source_dir, dirnames, filenames in os.walk(config['docs_dir'], followlinks=True):\n        relative_dir = os.path.relpath(source_dir, config['docs_dir'])\n\n        for dirname in list(dirnames):\n            path = os.path.normpath(os.path.join(relative_dir, dirname))\n            # Skip any excluded directories\n            if _filter_paths(basename=dirname, path=path, is_dir=True, exclude=exclude):\n                dirnames.remove(dirname)\n        dirnames.sort()\n\n        for filename in _sort_files(filenames):\n            path = os.path.normpath(os.path.join(relative_dir, filename))\n            # Skip any excluded files\n            if _filter_paths(basename=filename, path=path, is_dir=False, exclude=exclude):\n                continue\n            # Skip README.md if an index file also exists in dir\n            if filename.lower() == 'readme.md' and 'index.md' in filenames:\n                log.warning(\"Both index.md and readme.md found. Skipping readme.md from {}\".format(source_dir))\n                continue\n            files.append(File(path, config['docs_dir'], config['site_dir'], config['use_directory_urls']))\n\n    return Files(files)\n\n\ndef _sort_files(filenames):\n    \"\"\" Always sort `index` or `README` as first filename in list. \"\"\"\n\n    def compare(x, y):\n        if x == y:\n            return 0\n        if os.path.splitext(y)[0] in ['index', 'README']:\n            return 1\n        if os.path.splitext(x)[0] in ['index', 'README'] or x < y:\n            return -1\n        return 1\n\n    return sorted(filenames, key=cmp_to_key(compare))\n\n\ndef _filter_paths(basename, path, is_dir, exclude):\n    \"\"\" .gitignore style file filtering. \"\"\"\n    for item in exclude:\n        # Items ending in '/' apply only to directories.\n        if item.endswith('/') and not is_dir:\n            continue\n        # Items starting with '/' apply to the whole path.\n        # In any other cases just the basename is used.\n        match = path if item.startswith('/') else basename\n        if fnmatch.fnmatch(match, item.strip('/')):\n            return True\n    return False\n", "path": "mkdocs/structure/files.py"}], "after_files": [{"content": "import fnmatch\nimport os\nimport logging\nfrom functools import cmp_to_key\nfrom urllib.parse import quote as urlquote\n\nfrom mkdocs import utils\n\n\nlog = logging.getLogger(__name__)\nlog.addFilter(utils.warning_filter)\n\n\nclass Files:\n    \"\"\" A collection of File objects. \"\"\"\n    def __init__(self, files):\n        self._files = files\n        self.src_paths = {file.src_path: file for file in files}\n\n    def __iter__(self):\n        return iter(self._files)\n\n    def __len__(self):\n        return len(self._files)\n\n    def __contains__(self, path):\n        return path in self.src_paths\n\n    def get_file_from_path(self, path):\n        \"\"\" Return a File instance with File.src_path equal to path. \"\"\"\n        return self.src_paths.get(os.path.normpath(path))\n\n    def append(self, file):\n        \"\"\" Append file to Files collection. \"\"\"\n        self._files.append(file)\n        self.src_paths[file.src_path] = file\n\n    def copy_static_files(self, dirty=False):\n        \"\"\" Copy static files from source to destination. \"\"\"\n        for file in self:\n            if not file.is_documentation_page():\n                file.copy_file(dirty)\n\n    def documentation_pages(self):\n        \"\"\" Return iterable of all Markdown page file objects. \"\"\"\n        return [file for file in self if file.is_documentation_page()]\n\n    def static_pages(self):\n        \"\"\" Return iterable of all static page file objects. \"\"\"\n        return [file for file in self if file.is_static_page()]\n\n    def media_files(self):\n        \"\"\" Return iterable of all file objects which are not documentation or static pages. \"\"\"\n        return [file for file in self if file.is_media_file()]\n\n    def javascript_files(self):\n        \"\"\" Return iterable of all javascript file objects. \"\"\"\n        return [file for file in self if file.is_javascript()]\n\n    def css_files(self):\n        \"\"\" Return iterable of all CSS file objects. \"\"\"\n        return [file for file in self if file.is_css()]\n\n    def add_files_from_theme(self, env, config):\n        \"\"\" Retrieve static files from Jinja environment and add to collection. \"\"\"\n        def filter(name):\n            # '.*' filters dot files/dirs at root level whereas '*/.*' filters nested levels\n            patterns = ['.*', '*/.*', '*.py', '*.pyc', '*.html', '*readme*', 'mkdocs_theme.yml']\n            patterns.extend('*{}'.format(x) for x in utils.markdown_extensions)\n            patterns.extend(config['theme'].static_templates)\n            for pattern in patterns:\n                if fnmatch.fnmatch(name.lower(), pattern):\n                    return False\n            return True\n        for path in env.list_templates(filter_func=filter):\n            # Theme files do not override docs_dir files\n            path = os.path.normpath(path)\n            if path not in self:\n                for dir in config['theme'].dirs:\n                    # Find the first theme dir which contains path\n                    if os.path.isfile(os.path.join(dir, path)):\n                        self.append(File(path, dir, config['site_dir'], config['use_directory_urls']))\n                        break\n\n\nclass File:\n    \"\"\"\n    A MkDocs File object.\n\n    Points to the source and destination locations of a file.\n\n    The `path` argument must be a path that exists relative to `src_dir`.\n\n    The `src_dir` and `dest_dir` must be absolute paths on the local file system.\n\n    The `use_directory_urls` argument controls how destination paths are generated. If `False`, a Markdown file is\n    mapped to an HTML file of the same name (the file extension is changed to `.html`). If True, a Markdown file is\n    mapped to an HTML index file (`index.html`) nested in a directory using the \"name\" of the file in `path`. The\n    `use_directory_urls` argument has no effect on non-Markdown files.\n\n    File objects have the following properties, which are Unicode strings:\n\n    File.src_path\n        The pure path of the source file relative to the source directory.\n\n    File.abs_src_path\n        The absolute concrete path of the source file.\n\n    File.dest_path\n        The pure path of the destination file relative to the destination directory.\n\n    File.abs_dest_path\n        The absolute concrete path of the destination file.\n\n    File.url\n        The url of the destination file relative to the destination directory as a string.\n    \"\"\"\n    def __init__(self, path, src_dir, dest_dir, use_directory_urls):\n        self.page = None\n        self.src_path = os.path.normpath(path)\n        self.abs_src_path = os.path.normpath(os.path.join(src_dir, self.src_path))\n        self.name = self._get_stem()\n        self.dest_path = self._get_dest_path(use_directory_urls)\n        self.abs_dest_path = os.path.normpath(os.path.join(dest_dir, self.dest_path))\n        self.url = self._get_url(use_directory_urls)\n\n    def __eq__(self, other):\n\n        def sub_dict(d):\n            return {key: value for key, value in d.items() if key in ['src_path', 'abs_src_path', 'url']}\n\n        return (isinstance(other, self.__class__) and sub_dict(self.__dict__) == sub_dict(other.__dict__))\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def _get_stem(self):\n        \"\"\" Return the name of the file without it's extension. \"\"\"\n        filename = os.path.basename(self.src_path)\n        stem, ext = os.path.splitext(filename)\n        return 'index' if stem in ('index', 'README') else stem\n\n    def _get_dest_path(self, use_directory_urls):\n        \"\"\" Return destination path based on source path. \"\"\"\n        if self.is_documentation_page():\n            if use_directory_urls:\n                parent, filename = os.path.split(self.src_path)\n                if self.name == 'index':\n                    # index.md or README.md => index.html\n                    return os.path.join(parent, 'index.html')\n                else:\n                    # foo.md => foo/index.html\n                    return os.path.join(parent, self.name, 'index.html')\n            else:\n                # foo.md => foo.html\n                root, ext = os.path.splitext(self.src_path)\n                return root + '.html'\n        return self.src_path\n\n    def _get_url(self, use_directory_urls):\n        \"\"\" Return url based in destination path. \"\"\"\n        url = self.dest_path.replace(os.path.sep, '/')\n        dirname, filename = os.path.split(url)\n        if use_directory_urls and filename == 'index.html':\n            if dirname == '':\n                url = '.'\n            else:\n                url = dirname + '/'\n        return urlquote(url)\n\n    def url_relative_to(self, other):\n        \"\"\" Return url for file relative to other file. \"\"\"\n        return utils.get_relative_url(self.url, other.url if isinstance(other, File) else other)\n\n    def copy_file(self, dirty=False):\n        \"\"\" Copy source file to destination, ensuring parent directories exist. \"\"\"\n        if dirty and not self.is_modified():\n            log.debug(\"Skip copying unmodified file: '{}'\".format(self.src_path))\n        else:\n            log.debug(\"Copying media file: '{}'\".format(self.src_path))\n            utils.copy_file(self.abs_src_path, self.abs_dest_path)\n\n    def is_modified(self):\n        if os.path.isfile(self.abs_dest_path):\n            return os.path.getmtime(self.abs_dest_path) < os.path.getmtime(self.abs_src_path)\n        return True\n\n    def is_documentation_page(self):\n        \"\"\" Return True if file is a Markdown page. \"\"\"\n        return os.path.splitext(self.src_path)[1] in utils.markdown_extensions\n\n    def is_static_page(self):\n        \"\"\" Return True if file is a static page (html, xml, json). \"\"\"\n        return os.path.splitext(self.src_path)[1] in (\n            '.html',\n            '.htm',\n            '.xml',\n            '.json',\n        )\n\n    def is_media_file(self):\n        \"\"\" Return True if file is not a documentation or static page. \"\"\"\n        return not (self.is_documentation_page() or self.is_static_page())\n\n    def is_javascript(self):\n        \"\"\" Return True if file is a JavaScript file. \"\"\"\n        return os.path.splitext(self.src_path)[1] in (\n            '.js',\n            '.javascript',\n        )\n\n    def is_css(self):\n        \"\"\" Return True if file is a CSS file. \"\"\"\n        return os.path.splitext(self.src_path)[1] in (\n            '.css',\n        )\n\n\ndef get_files(config):\n    \"\"\" Walk the `docs_dir` and return a Files collection. \"\"\"\n    files = []\n    exclude = ['.*', '/templates']\n\n    for source_dir, dirnames, filenames in os.walk(config['docs_dir'], followlinks=True):\n        relative_dir = os.path.relpath(source_dir, config['docs_dir'])\n\n        for dirname in list(dirnames):\n            path = os.path.normpath(os.path.join(relative_dir, dirname))\n            # Skip any excluded directories\n            if _filter_paths(basename=dirname, path=path, is_dir=True, exclude=exclude):\n                dirnames.remove(dirname)\n        dirnames.sort()\n\n        for filename in _sort_files(filenames):\n            path = os.path.normpath(os.path.join(relative_dir, filename))\n            # Skip any excluded files\n            if _filter_paths(basename=filename, path=path, is_dir=False, exclude=exclude):\n                continue\n            # Skip README.md if an index file also exists in dir\n            if filename.lower() == 'readme.md' and 'index.md' in filenames:\n                log.warning(\"Both index.md and readme.md found. Skipping readme.md from {}\".format(source_dir))\n                continue\n            files.append(File(path, config['docs_dir'], config['site_dir'], config['use_directory_urls']))\n\n    return Files(files)\n\n\ndef _sort_files(filenames):\n    \"\"\" Always sort `index` or `README` as first filename in list. \"\"\"\n\n    def compare(x, y):\n        if x == y:\n            return 0\n        if os.path.splitext(y)[0] in ['index', 'README']:\n            return 1\n        if os.path.splitext(x)[0] in ['index', 'README'] or x < y:\n            return -1\n        return 1\n\n    return sorted(filenames, key=cmp_to_key(compare))\n\n\ndef _filter_paths(basename, path, is_dir, exclude):\n    \"\"\" .gitignore style file filtering. \"\"\"\n    for item in exclude:\n        # Items ending in '/' apply only to directories.\n        if item.endswith('/') and not is_dir:\n            continue\n        # Items starting with '/' apply to the whole path.\n        # In any other cases just the basename is used.\n        match = path if item.startswith('/') else basename\n        if fnmatch.fnmatch(match, item.strip('/')):\n            return True\n    return False\n", "path": "mkdocs/structure/files.py"}]}