1import unittest
2import os
3from unittest import mock
4
5from mkdocs.structure.files import Files, File, get_files, _sort_files, _filter_paths
6from mkdocs.tests.base import load_config, tempdir, PathAssertionMixin
7
8
9class TestFiles(PathAssertionMixin, unittest.TestCase):
10
11    def test_file_eq(self):
12        file = File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
13        self.assertTrue(file == File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False))
14
15    def test_file_ne(self):
16        file = File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
17        # Different filename
18        self.assertTrue(file != File('b.md', '/path/to/docs', '/path/to/site', use_directory_urls=False))
19        # Different src_path
20        self.assertTrue(file != File('a.md', '/path/to/other', '/path/to/site', use_directory_urls=False))
21        # Different URL
22        self.assertTrue(file != File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=True))
23
24    def test_sort_files(self):
25        self.assertEqual(
26            _sort_files(['b.md', 'bb.md', 'a.md', 'index.md', 'aa.md']),
27            ['index.md', 'a.md', 'aa.md', 'b.md', 'bb.md']
28        )
29
30        self.assertEqual(
31            _sort_files(['b.md', 'index.html', 'a.md', 'index.md']),
32            ['index.html', 'index.md', 'a.md', 'b.md']
33        )
34
35        self.assertEqual(
36            _sort_files(['a.md', 'index.md', 'b.md', 'index.html']),
37            ['index.md', 'index.html', 'a.md', 'b.md']
38        )
39
40        self.assertEqual(
41            _sort_files(['.md', '_.md', 'a.md', 'index.md', '1.md']),
42            ['index.md', '.md', '1.md', '_.md', 'a.md']
43        )
44
45        self.assertEqual(
46            _sort_files(['a.md', 'b.md', 'a.md']),
47            ['a.md', 'a.md', 'b.md']
48        )
49
50        self.assertEqual(
51            _sort_files(['A.md', 'B.md', 'README.md']),
52            ['README.md', 'A.md', 'B.md']
53        )
54
55    def test_md_file(self):
56        f = File('foo.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
57        self.assertPathsEqual(f.src_path, 'foo.md')
58        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo.md')
59        self.assertPathsEqual(f.dest_path, 'foo.html')
60        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo.html')
61        self.assertEqual(f.url, 'foo.html')
62        self.assertEqual(f.name, 'foo')
63        self.assertTrue(f.is_documentation_page())
64        self.assertFalse(f.is_static_page())
65        self.assertFalse(f.is_media_file())
66        self.assertFalse(f.is_javascript())
67        self.assertFalse(f.is_css())
68
69    def test_md_file_use_directory_urls(self):
70        f = File('foo.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
71        self.assertPathsEqual(f.src_path, 'foo.md')
72        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo.md')
73        self.assertPathsEqual(f.dest_path, 'foo/index.html')
74        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html')
75        self.assertEqual(f.url, 'foo/')
76        self.assertEqual(f.name, 'foo')
77        self.assertTrue(f.is_documentation_page())
78        self.assertFalse(f.is_static_page())
79        self.assertFalse(f.is_media_file())
80        self.assertFalse(f.is_javascript())
81        self.assertFalse(f.is_css())
82
83    def test_md_file_nested(self):
84        f = File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
85        self.assertPathsEqual(f.src_path, 'foo/bar.md')
86        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.md')
87        self.assertPathsEqual(f.dest_path, 'foo/bar.html')
88        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html')
89        self.assertEqual(f.url, 'foo/bar.html')
90        self.assertEqual(f.name, 'bar')
91        self.assertTrue(f.is_documentation_page())
92        self.assertFalse(f.is_static_page())
93        self.assertFalse(f.is_media_file())
94        self.assertFalse(f.is_javascript())
95        self.assertFalse(f.is_css())
96
97    def test_md_file_nested_use_directory_urls(self):
98        f = File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
99        self.assertPathsEqual(f.src_path, 'foo/bar.md')
100        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.md')
101        self.assertPathsEqual(f.dest_path, 'foo/bar/index.html')
102        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar/index.html')
103        self.assertEqual(f.url, 'foo/bar/')
104        self.assertEqual(f.name, 'bar')
105        self.assertTrue(f.is_documentation_page())
106        self.assertFalse(f.is_static_page())
107        self.assertFalse(f.is_media_file())
108        self.assertFalse(f.is_javascript())
109        self.assertFalse(f.is_css())
110
111    def test_md_index_file(self):
112        f = File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
113        self.assertPathsEqual(f.src_path, 'index.md')
114        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/index.md')
115        self.assertPathsEqual(f.dest_path, 'index.html')
116        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/index.html')
117        self.assertEqual(f.url, 'index.html')
118        self.assertEqual(f.name, 'index')
119        self.assertTrue(f.is_documentation_page())
120        self.assertFalse(f.is_static_page())
121        self.assertFalse(f.is_media_file())
122        self.assertFalse(f.is_javascript())
123        self.assertFalse(f.is_css())
124
125    def test_md_readme_index_file(self):
126        f = File('README.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
127        self.assertPathsEqual(f.src_path, 'README.md')
128        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/README.md')
129        self.assertPathsEqual(f.dest_path, 'index.html')
130        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/index.html')
131        self.assertEqual(f.url, 'index.html')
132        self.assertEqual(f.name, 'index')
133        self.assertTrue(f.is_documentation_page())
134        self.assertFalse(f.is_static_page())
135        self.assertFalse(f.is_media_file())
136        self.assertFalse(f.is_javascript())
137        self.assertFalse(f.is_css())
138
139    def test_md_index_file_use_directory_urls(self):
140        f = File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
141        self.assertPathsEqual(f.src_path, 'index.md')
142        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/index.md')
143        self.assertPathsEqual(f.dest_path, 'index.html')
144        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/index.html')
145        self.assertEqual(f.url, '.')
146        self.assertEqual(f.name, 'index')
147        self.assertTrue(f.is_documentation_page())
148        self.assertFalse(f.is_static_page())
149        self.assertFalse(f.is_media_file())
150        self.assertFalse(f.is_javascript())
151        self.assertFalse(f.is_css())
152
153    def test_md_readme_index_file_use_directory_urls(self):
154        f = File('README.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
155        self.assertPathsEqual(f.src_path, 'README.md')
156        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/README.md')
157        self.assertPathsEqual(f.dest_path, 'index.html')
158        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/index.html')
159        self.assertEqual(f.url, '.')
160        self.assertEqual(f.name, 'index')
161        self.assertTrue(f.is_documentation_page())
162        self.assertFalse(f.is_static_page())
163        self.assertFalse(f.is_media_file())
164        self.assertFalse(f.is_javascript())
165        self.assertFalse(f.is_css())
166
167    def test_md_index_file_nested(self):
168        f = File('foo/index.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
169        self.assertPathsEqual(f.src_path, 'foo/index.md')
170        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/index.md')
171        self.assertPathsEqual(f.dest_path, 'foo/index.html')
172        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html')
173        self.assertEqual(f.url, 'foo/index.html')
174        self.assertEqual(f.name, 'index')
175        self.assertTrue(f.is_documentation_page())
176        self.assertFalse(f.is_static_page())
177        self.assertFalse(f.is_media_file())
178        self.assertFalse(f.is_javascript())
179        self.assertFalse(f.is_css())
180
181    def test_md_index_file_nested_use_directory_urls(self):
182        f = File('foo/index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
183        self.assertPathsEqual(f.src_path, 'foo/index.md')
184        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/index.md')
185        self.assertPathsEqual(f.dest_path, 'foo/index.html')
186        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html')
187        self.assertEqual(f.url, 'foo/')
188        self.assertEqual(f.name, 'index')
189        self.assertTrue(f.is_documentation_page())
190        self.assertFalse(f.is_static_page())
191        self.assertFalse(f.is_media_file())
192        self.assertFalse(f.is_javascript())
193        self.assertFalse(f.is_css())
194
195    def test_static_file(self):
196        f = File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=False)
197        self.assertPathsEqual(f.src_path, 'foo/bar.html')
198        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.html')
199        self.assertPathsEqual(f.dest_path, 'foo/bar.html')
200        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html')
201        self.assertEqual(f.url, 'foo/bar.html')
202        self.assertEqual(f.name, 'bar')
203        self.assertFalse(f.is_documentation_page())
204        self.assertTrue(f.is_static_page())
205        self.assertFalse(f.is_media_file())
206        self.assertFalse(f.is_javascript())
207        self.assertFalse(f.is_css())
208
209    def test_static_file_use_directory_urls(self):
210        f = File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=True)
211        self.assertPathsEqual(f.src_path, 'foo/bar.html')
212        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.html')
213        self.assertPathsEqual(f.dest_path, 'foo/bar.html')
214        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html')
215        self.assertEqual(f.url, 'foo/bar.html')
216        self.assertEqual(f.name, 'bar')
217        self.assertFalse(f.is_documentation_page())
218        self.assertTrue(f.is_static_page())
219        self.assertFalse(f.is_media_file())
220        self.assertFalse(f.is_javascript())
221        self.assertFalse(f.is_css())
222
223    def test_media_file(self):
224        f = File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False)
225        self.assertPathsEqual(f.src_path, 'foo/bar.jpg')
226        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.jpg')
227        self.assertPathsEqual(f.dest_path, 'foo/bar.jpg')
228        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.jpg')
229        self.assertEqual(f.url, 'foo/bar.jpg')
230        self.assertEqual(f.name, 'bar')
231        self.assertFalse(f.is_documentation_page())
232        self.assertFalse(f.is_static_page())
233        self.assertTrue(f.is_media_file())
234        self.assertFalse(f.is_javascript())
235        self.assertFalse(f.is_css())
236
237    def test_media_file_use_directory_urls(self):
238        f = File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True)
239        self.assertPathsEqual(f.src_path, 'foo/bar.jpg')
240        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.jpg')
241        self.assertPathsEqual(f.dest_path, 'foo/bar.jpg')
242        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.jpg')
243        self.assertEqual(f.url, 'foo/bar.jpg')
244        self.assertEqual(f.name, 'bar')
245        self.assertFalse(f.is_documentation_page())
246        self.assertFalse(f.is_static_page())
247        self.assertTrue(f.is_media_file())
248        self.assertFalse(f.is_javascript())
249        self.assertFalse(f.is_css())
250
251    def test_javascript_file(self):
252        f = File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=False)
253        self.assertPathsEqual(f.src_path, 'foo/bar.js')
254        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.js')
255        self.assertPathsEqual(f.dest_path, 'foo/bar.js')
256        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.js')
257        self.assertEqual(f.url, 'foo/bar.js')
258        self.assertEqual(f.name, 'bar')
259        self.assertFalse(f.is_documentation_page())
260        self.assertFalse(f.is_static_page())
261        self.assertTrue(f.is_media_file())
262        self.assertTrue(f.is_javascript())
263        self.assertFalse(f.is_css())
264
265    def test_javascript_file_use_directory_urls(self):
266        f = File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=True)
267        self.assertPathsEqual(f.src_path, 'foo/bar.js')
268        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.js')
269        self.assertPathsEqual(f.dest_path, 'foo/bar.js')
270        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.js')
271        self.assertEqual(f.url, 'foo/bar.js')
272        self.assertEqual(f.name, 'bar')
273        self.assertFalse(f.is_documentation_page())
274        self.assertFalse(f.is_static_page())
275        self.assertTrue(f.is_media_file())
276        self.assertTrue(f.is_javascript())
277        self.assertFalse(f.is_css())
278
279    def test_css_file(self):
280        f = File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=False)
281        self.assertPathsEqual(f.src_path, 'foo/bar.css')
282        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.css')
283        self.assertPathsEqual(f.dest_path, 'foo/bar.css')
284        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.css')
285        self.assertEqual(f.url, 'foo/bar.css')
286        self.assertEqual(f.name, 'bar')
287        self.assertFalse(f.is_documentation_page())
288        self.assertFalse(f.is_static_page())
289        self.assertTrue(f.is_media_file())
290        self.assertFalse(f.is_javascript())
291        self.assertTrue(f.is_css())
292
293    def test_css_file_use_directory_urls(self):
294        f = File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=True)
295        self.assertPathsEqual(f.src_path, 'foo/bar.css')
296        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.css')
297        self.assertPathsEqual(f.dest_path, 'foo/bar.css')
298        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.css')
299        self.assertEqual(f.url, 'foo/bar.css')
300        self.assertEqual(f.name, 'bar')
301        self.assertFalse(f.is_documentation_page())
302        self.assertFalse(f.is_static_page())
303        self.assertTrue(f.is_media_file())
304        self.assertFalse(f.is_javascript())
305        self.assertTrue(f.is_css())
306
307    def test_file_name_with_space(self):
308        f = File('foo bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
309        self.assertPathsEqual(f.src_path, 'foo bar.md')
310        self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo bar.md')
311        self.assertPathsEqual(f.dest_path, 'foo bar.html')
312        self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo bar.html')
313        self.assertEqual(f.url, 'foo%20bar.html')
314        self.assertEqual(f.name, 'foo bar')
315
316    def test_files(self):
317        fs = [
318            File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True),
319            File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=True),
320            File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=True),
321            File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True),
322            File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=True),
323            File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=True)
324        ]
325        files = Files(fs)
326        self.assertEqual([f for f in files], fs)
327        self.assertEqual(len(files), 6)
328        self.assertEqual(files.documentation_pages(), [fs[0], fs[1]])
329        self.assertEqual(files.static_pages(), [fs[2]])
330        self.assertEqual(files.media_files(), [fs[3], fs[4], fs[5]])
331        self.assertEqual(files.javascript_files(), [fs[4]])
332        self.assertEqual(files.css_files(), [fs[5]])
333        self.assertEqual(files.get_file_from_path('foo/bar.jpg'), fs[3])
334        self.assertEqual(files.get_file_from_path('foo/bar.jpg'), fs[3])
335        self.assertEqual(files.get_file_from_path('missing.jpg'), None)
336        self.assertTrue(fs[2].src_path in files)
337        self.assertTrue(fs[2].src_path in files)
338        extra_file = File('extra.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
339        self.assertFalse(extra_file.src_path in files)
340        files.append(extra_file)
341        self.assertEqual(len(files), 7)
342        self.assertTrue(extra_file.src_path in files)
343        self.assertEqual(files.documentation_pages(), [fs[0], fs[1], extra_file])
344
345    @tempdir(files=[
346        'favicon.ico',
347        'index.md'
348    ])
349    @tempdir(files=[
350        'base.html',
351        'favicon.ico',
352        'style.css',
353        'foo.md',
354        'README',
355        '.ignore.txt',
356        '.ignore/file.txt',
357        'foo/.ignore.txt',
358        'foo/.ignore/file.txt'
359    ])
360    def test_add_files_from_theme(self, tdir, ddir):
361        config = load_config(docs_dir=ddir, theme={'name': None, 'custom_dir': tdir})
362        env = config['theme'].get_env()
363        files = get_files(config)
364        self.assertEqual(
365            [file.src_path for file in files],
366            ['index.md', 'favicon.ico']
367        )
368        files.add_files_from_theme(env, config)
369        self.assertEqual(
370            [file.src_path for file in files],
371            ['index.md', 'favicon.ico', 'style.css']
372        )
373        # Ensure theme file does not override docs_dir file
374        self.assertEqual(
375            files.get_file_from_path('favicon.ico').abs_src_path,
376            os.path.normpath(os.path.join(ddir, 'favicon.ico'))
377        )
378
379    def test_filter_paths(self):
380        # Root level file
381        self.assertFalse(_filter_paths('foo.md', 'foo.md', False, ['bar.md']))
382        self.assertTrue(_filter_paths('foo.md', 'foo.md', False, ['foo.md']))
383
384        # Nested file
385        self.assertFalse(_filter_paths('foo.md', 'baz/foo.md', False, ['bar.md']))
386        self.assertTrue(_filter_paths('foo.md', 'baz/foo.md', False, ['foo.md']))
387
388        # Wildcard
389        self.assertFalse(_filter_paths('foo.md', 'foo.md', False, ['*.txt']))
390        self.assertTrue(_filter_paths('foo.md', 'foo.md', False, ['*.md']))
391
392        # Root level dir
393        self.assertFalse(_filter_paths('bar', 'bar', True, ['/baz']))
394        self.assertFalse(_filter_paths('bar', 'bar', True, ['/baz/']))
395        self.assertTrue(_filter_paths('bar', 'bar', True, ['/bar']))
396        self.assertTrue(_filter_paths('bar', 'bar', True, ['/bar/']))
397
398        # Nested dir
399        self.assertFalse(_filter_paths('bar', 'foo/bar', True, ['/bar']))
400        self.assertFalse(_filter_paths('bar', 'foo/bar', True, ['/bar/']))
401        self.assertTrue(_filter_paths('bar', 'foo/bar', True, ['bar/']))
402
403        # Files that look like dirs (no extension). Note that `is_dir` is `False`.
404        self.assertFalse(_filter_paths('bar', 'bar', False, ['bar/']))
405        self.assertFalse(_filter_paths('bar', 'foo/bar', False, ['bar/']))
406
407    def test_get_relative_url_use_directory_urls(self):
408        to_files = [
409            'index.md',
410            'foo/index.md',
411            'foo/bar/index.md',
412            'foo/bar/baz/index.md',
413            'foo.md',
414            'foo/bar.md',
415            'foo/bar/baz.md'
416        ]
417
418        to_file_urls = [
419            '.',
420            'foo/',
421            'foo/bar/',
422            'foo/bar/baz/',
423            'foo/',
424            'foo/bar/',
425            'foo/bar/baz/'
426        ]
427
428        from_file = File('img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True)
429        expected = [
430            'img.jpg',           # img.jpg relative to .
431            '../img.jpg',        # img.jpg relative to foo/
432            '../../img.jpg',     # img.jpg relative to foo/bar/
433            '../../../img.jpg',  # img.jpg relative to foo/bar/baz/
434            '../img.jpg',        # img.jpg relative to foo
435            '../../img.jpg',     # img.jpg relative to foo/bar
436            '../../../img.jpg'   # img.jpg relative to foo/bar/baz
437        ]
438
439        for i, filename in enumerate(to_files):
440            file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True)
441            self.assertEqual(from_file.url, 'img.jpg')
442            self.assertEqual(file.url, to_file_urls[i])
443            self.assertEqual(from_file.url_relative_to(file.url), expected[i])
444            self.assertEqual(from_file.url_relative_to(file), expected[i])
445
446        from_file = File('foo/img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True)
447        expected = [
448            'foo/img.jpg',    # foo/img.jpg relative to .
449            'img.jpg',        # foo/img.jpg relative to foo/
450            '../img.jpg',     # foo/img.jpg relative to foo/bar/
451            '../../img.jpg',  # foo/img.jpg relative to foo/bar/baz/
452            'img.jpg',        # foo/img.jpg relative to foo
453            '../img.jpg',     # foo/img.jpg relative to foo/bar
454            '../../img.jpg'   # foo/img.jpg relative to foo/bar/baz
455        ]
456
457        for i, filename in enumerate(to_files):
458            file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True)
459            self.assertEqual(from_file.url, 'foo/img.jpg')
460            self.assertEqual(file.url, to_file_urls[i])
461            self.assertEqual(from_file.url_relative_to(file.url), expected[i])
462            self.assertEqual(from_file.url_relative_to(file), expected[i])
463
464        from_file = File('index.html', '/path/to/docs', '/path/to/site', use_directory_urls=True)
465        expected = [
466            '.',         # . relative to .
467            '..',        # . relative to foo/
468            '../..',     # . relative to foo/bar/
469            '../../..',  # . relative to foo/bar/baz/
470            '..',        # . relative to foo
471            '../..',     # . relative to foo/bar
472            '../../..'   # . relative to foo/bar/baz
473        ]
474
475        for i, filename in enumerate(to_files):
476            file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True)
477            self.assertEqual(from_file.url, '.')
478            self.assertEqual(file.url, to_file_urls[i])
479            self.assertEqual(from_file.url_relative_to(file.url), expected[i])
480            self.assertEqual(from_file.url_relative_to(file), expected[i])
481
482        from_file = File('file.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
483        expected = [
484            'file/',           # file relative to .
485            '../file/',        # file relative to foo/
486            '../../file/',     # file relative to foo/bar/
487            '../../../file/',  # file relative to foo/bar/baz/
488            '../file/',        # file relative to foo
489            '../../file/',     # file relative to foo/bar
490            '../../../file/'   # file relative to foo/bar/baz
491        ]
492
493        for i, filename in enumerate(to_files):
494            file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True)
495            self.assertEqual(from_file.url, 'file/')
496            self.assertEqual(file.url, to_file_urls[i])
497            self.assertEqual(from_file.url_relative_to(file.url), expected[i])
498            self.assertEqual(from_file.url_relative_to(file), expected[i])
499
500    def test_get_relative_url(self):
501        to_files = [
502            'index.md',
503            'foo/index.md',
504            'foo/bar/index.md',
505            'foo/bar/baz/index.md',
506            'foo.md',
507            'foo/bar.md',
508            'foo/bar/baz.md'
509        ]
510
511        to_file_urls = [
512            'index.html',
513            'foo/index.html',
514            'foo/bar/index.html',
515            'foo/bar/baz/index.html',
516            'foo.html',
517            'foo/bar.html',
518            'foo/bar/baz.html'
519        ]
520
521        from_file = File('img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False)
522        expected = [
523            'img.jpg',           # img.jpg relative to .
524            '../img.jpg',        # img.jpg relative to foo/
525            '../../img.jpg',     # img.jpg relative to foo/bar/
526            '../../../img.jpg',  # img.jpg relative to foo/bar/baz/
527            'img.jpg',           # img.jpg relative to foo.html
528            '../img.jpg',        # img.jpg relative to foo/bar.html
529            '../../img.jpg'      # img.jpg relative to foo/bar/baz.html
530        ]
531
532        for i, filename in enumerate(to_files):
533            file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False)
534            self.assertEqual(from_file.url, 'img.jpg')
535            self.assertEqual(file.url, to_file_urls[i])
536            self.assertEqual(from_file.url_relative_to(file.url), expected[i])
537            self.assertEqual(from_file.url_relative_to(file), expected[i])
538
539        from_file = File('foo/img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False)
540        expected = [
541            'foo/img.jpg',    # foo/img.jpg relative to .
542            'img.jpg',        # foo/img.jpg relative to foo/
543            '../img.jpg',     # foo/img.jpg relative to foo/bar/
544            '../../img.jpg',  # foo/img.jpg relative to foo/bar/baz/
545            'foo/img.jpg',    # foo/img.jpg relative to foo.html
546            'img.jpg',        # foo/img.jpg relative to foo/bar.html
547            '../img.jpg'      # foo/img.jpg relative to foo/bar/baz.html
548        ]
549
550        for i, filename in enumerate(to_files):
551            file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False)
552            self.assertEqual(from_file.url, 'foo/img.jpg')
553            self.assertEqual(file.url, to_file_urls[i])
554            self.assertEqual(from_file.url_relative_to(file.url), expected[i])
555            self.assertEqual(from_file.url_relative_to(file), expected[i])
556
557        from_file = File('index.html', '/path/to/docs', '/path/to/site', use_directory_urls=False)
558        expected = [
559            'index.html',           # index.html relative to .
560            '../index.html',        # index.html relative to foo/
561            '../../index.html',     # index.html relative to foo/bar/
562            '../../../index.html',  # index.html relative to foo/bar/baz/
563            'index.html',           # index.html relative to foo.html
564            '../index.html',        # index.html relative to foo/bar.html
565            '../../index.html'      # index.html relative to foo/bar/baz.html
566        ]
567
568        for i, filename in enumerate(to_files):
569            file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False)
570            self.assertEqual(from_file.url, 'index.html')
571            self.assertEqual(file.url, to_file_urls[i])
572            self.assertEqual(from_file.url_relative_to(file.url), expected[i])
573            self.assertEqual(from_file.url_relative_to(file), expected[i])
574
575        from_file = File('file.html', '/path/to/docs', '/path/to/site', use_directory_urls=False)
576        expected = [
577            'file.html',           # file.html relative to .
578            '../file.html',        # file.html relative to foo/
579            '../../file.html',     # file.html relative to foo/bar/
580            '../../../file.html',  # file.html relative to foo/bar/baz/
581            'file.html',           # file.html relative to foo.html
582            '../file.html',        # file.html relative to foo/bar.html
583            '../../file.html'      # file.html relative to foo/bar/baz.html
584        ]
585
586        for i, filename in enumerate(to_files):
587            file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False)
588            self.assertEqual(from_file.url, 'file.html')
589            self.assertEqual(file.url, to_file_urls[i])
590            self.assertEqual(from_file.url_relative_to(file.url), expected[i])
591            self.assertEqual(from_file.url_relative_to(file), expected[i])
592
593    @tempdir(files=[
594        'index.md',
595        'bar.css',
596        'bar.html',
597        'bar.jpg',
598        'bar.js',
599        'bar.md',
600        '.dotfile',
601        'templates/foo.html'
602    ])
603    def test_get_files(self, tdir):
604        config = load_config(docs_dir=tdir, extra_css=['bar.css'], extra_javascript=['bar.js'])
605        files = get_files(config)
606        expected = ['index.md', 'bar.css', 'bar.html', 'bar.jpg', 'bar.js', 'bar.md']
607        self.assertIsInstance(files, Files)
608        self.assertEqual(len(files), len(expected))
609        self.assertEqual([f.src_path for f in files], expected)
610
611    @tempdir(files=[
612        'README.md',
613        'foo.md'
614    ])
615    def test_get_files_include_readme_without_index(self, tdir):
616        config = load_config(docs_dir=tdir)
617        files = get_files(config)
618        expected = ['README.md', 'foo.md']
619        self.assertIsInstance(files, Files)
620        self.assertEqual(len(files), len(expected))
621        self.assertEqual([f.src_path for f in files], expected)
622
623    @tempdir(files=[
624        'index.md',
625        'README.md',
626        'foo.md'
627    ])
628    def test_get_files_exclude_readme_with_index(self, tdir):
629        config = load_config(docs_dir=tdir)
630        files = get_files(config)
631        expected = ['index.md', 'foo.md']
632        self.assertIsInstance(files, Files)
633        self.assertEqual(len(files), len(expected))
634        self.assertEqual([f.src_path for f in files], expected)
635
636    @tempdir()
637    @tempdir(files={'test.txt': 'source content'})
638    def test_copy_file(self, src_dir, dest_dir):
639        file = File('test.txt', src_dir, dest_dir, use_directory_urls=False)
640        dest_path = os.path.join(dest_dir, 'test.txt')
641        self.assertPathNotExists(dest_path)
642        file.copy_file()
643        self.assertPathIsFile(dest_path)
644
645    @tempdir(files={'test.txt': 'destination content'})
646    @tempdir(files={'test.txt': 'source content'})
647    def test_copy_file_clean_modified(self, src_dir, dest_dir):
648        file = File('test.txt', src_dir, dest_dir, use_directory_urls=False)
649        file.is_modified = mock.Mock(return_value=True)
650        dest_path = os.path.join(dest_dir, 'test.txt')
651        file.copy_file(dirty=False)
652        self.assertPathIsFile(dest_path)
653        with open(dest_path, 'r', encoding='utf-8') as f:
654            self.assertEqual(f.read(), 'source content')
655
656    @tempdir(files={'test.txt': 'destination content'})
657    @tempdir(files={'test.txt': 'source content'})
658    def test_copy_file_dirty_modified(self, src_dir, dest_dir):
659        file = File('test.txt', src_dir, dest_dir, use_directory_urls=False)
660        file.is_modified = mock.Mock(return_value=True)
661        dest_path = os.path.join(dest_dir, 'test.txt')
662        file.copy_file(dirty=True)
663        self.assertPathIsFile(dest_path)
664        with open(dest_path, 'r', encoding='utf-8') as f:
665            self.assertEqual(f.read(), 'source content')
666
667    @tempdir(files={'test.txt': 'destination content'})
668    @tempdir(files={'test.txt': 'source content'})
669    def test_copy_file_dirty_not_modified(self, src_dir, dest_dir):
670        file = File('test.txt', src_dir, dest_dir, use_directory_urls=False)
671        file.is_modified = mock.Mock(return_value=False)
672        dest_path = os.path.join(dest_dir, 'test.txt')
673        file.copy_file(dirty=True)
674        self.assertPathIsFile(dest_path)
675        with open(dest_path, 'r', encoding='utf-8') as f:
676            self.assertEqual(f.read(), 'destination content')
677
678    def test_files_append_remove_src_paths(self):
679        fs = [
680            File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True),
681            File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=True),
682            File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=True),
683            File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True),
684            File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=True),
685            File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=True)
686        ]
687        files = Files(fs)
688        self.assertEqual(len(files), 6)
689        self.assertEqual(len(files.src_paths), 6)
690        extra_file = File('extra.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
691        self.assertFalse(extra_file.src_path in files)
692        files.append(extra_file)
693        self.assertEqual(len(files), 7)
694        self.assertEqual(len(files.src_paths), 7)
695        self.assertTrue(extra_file.src_path in files)
696        files.remove(extra_file)
697        self.assertEqual(len(files), 6)
698        self.assertEqual(len(files.src_paths), 6)
699        self.assertFalse(extra_file.src_path in files)
700