def test_populate_page_read_plugin_error(self, docs_dir, mock_open): cfg = load_config(docs_dir=docs_dir) file = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) page = Page('Foo', file, cfg) with self.assertLogs('mkdocs', level='ERROR') as cm: self.assertRaises(PluginError, build._populate_page, page, cfg, Files([file])) self.assertEqual( cm.output, [ "ERROR:mkdocs.commands.build:Error reading page 'index.md':" ] ) self.assert_mock_called_once(mock_open)
def test_context_base_url_nested_page_use_directory_urls(self): nav_cfg = [{'Home': 'index.md'}, {'Nested': 'foo/bar.md'}] cfg = load_config(nav=nav_cfg) files = Files([ File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('foo/bar.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) nav = get_navigation(files, cfg) context = build.get_context(nav, files, cfg, nav.pages[1]) self.assertEqual(context['base_url'], '../..')
def test_build_page_custom_template(self, site_dir): cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[]) files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) nav = get_navigation(files, cfg) page = files.documentation_pages()[0].page # Fake populate page page.title = 'Title' page.meta = {'template': '404.html'} page.markdown = 'page content' page.content = '<p>page content</p>' build._build_page(page, cfg, files, nav, self._get_env_with_null_translations(cfg)) self.assertPathIsFile(site_dir, 'index.html')
def test_skip_missing_extra_template(self): cfg = load_config() files = Files([ File('foo.html', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), ]) with self.assertLogs('mkdocs', level='INFO') as cm: build._build_extra_template('missing.html', files, cfg, mock.Mock()) self.assertEqual(cm.output, [ "WARNING:mkdocs.commands.build:Template skipped: 'missing.html' not found in docs_dir." ])
def test_get_url_status__local_page_nested(plugin): index_page = Mock(spec=Page, markdown='# Heading\nContent') nested1_page = Mock(spec=Page, markdown='# Nested\n## Nested One\nContent') nested1_sibling_page = Mock(spec=Page, markdown='# Nested Sibling') nested2_page = Mock(spec=Page, markdown='# Nested\n## Nested Two\nContent') nested2_sibling_page = Mock(spec=Page, markdown='# Nested Sibling') files = Files([ Mock(spec=File, src_path='index.md', dest_path='index.html', url='index.html', page=index_page), Mock(spec=File, src_path='foo/bar/nested.md', dest_path='foo/bar/nested.html', url='foo/bar/nested.html', page=nested1_page), Mock(spec=File, src_path='foo/bar/sibling.md', dest_path='foo/bar/sibling.html', url='foo/bar/sibling.html', page=nested1_sibling_page), Mock(spec=File, src_path='foo/baz/nested.md', dest_path='foo/baz/nested.html', url='foo/baz/nested.html', page=nested2_page), Mock(spec=File, src_path='foo/baz/sibling.md', dest_path='foo/baz/sibling.html', url='foo/baz/sibling.html', page=nested2_sibling_page), ]) assert plugin.get_url_status('nested.html#nested-one', 'foo/bar/sibling.md', set(), files, False) == 0 assert plugin.get_url_status('nested.html#nested-two', 'foo/bar/sibling.md', set(), files, False) == 404 assert plugin.get_url_status('nested.html#nested-two', 'foo/baz/sibling.md', set(), files, False) == 0 assert plugin.get_url_status('nested.html#nested-one', 'foo/baz/sibling.md', set(), files, False) == 404 assert plugin.get_url_status('foo/bar/nested.html#nested-one', 'index.md', set(), files, False) == 0 assert plugin.get_url_status('foo/baz/nested.html#nested-two', 'index.md', set(), files, False) == 0
def test_populate_page_read_error(self, docs_dir, mock_open): cfg = load_config(docs_dir=docs_dir) file = File('missing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) page = Page('Foo', file, cfg) with self.assertLogs('mkdocs', level='ERROR') as cm: self.assertRaises(OSError, build._populate_page, page, cfg, Files([file])) self.assertEqual( cm.output, [ 'ERROR:mkdocs.structure.pages:File not found: missing.md', "ERROR:mkdocs.commands.build:Error reading page 'missing.md': Error message." ] ) self.assert_mock_called_once(mock_open)
def test_files(self): fs = [ File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=True) ] files = Files(fs) self.assertEqual([f for f in files], fs) self.assertEqual(len(files), 6) self.assertEqual(files.documentation_pages(), [fs[0], fs[1]]) self.assertEqual(files.static_pages(), [fs[2]]) self.assertEqual(files.media_files(), [fs[3], fs[4], fs[5]]) self.assertEqual(files.javascript_files(), [fs[4]]) self.assertEqual(files.css_files(), [fs[5]]) self.assertEqual(files.get_file_from_path('foo/bar.jpg'), fs[3]) self.assertEqual(files.get_file_from_path('foo/bar.jpg'), fs[3]) self.assertEqual(files.get_file_from_path('missing.jpg'), None) self.assertTrue(fs[2].src_path in files) self.assertTrue(fs[2].src_path in files) extra_file = File('extra.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) self.assertFalse(extra_file.src_path in files) files.append(extra_file) self.assertEqual(len(files), 7) self.assertTrue(extra_file.src_path in files) self.assertEqual(files.documentation_pages(), [fs[0], fs[1], extra_file])
def test_active(self): nav_cfg = [ {'Home': 'test.md'}, {'API Guide': [ {'Running': 'api-guide/running.md'}, {'Testing': 'api-guide/testing.md'}, {'Debugging': 'api-guide/debugging.md'}, {'Advanced': [ {'Part 1': 'api-guide/advanced/part-1.md'}, ]}, ]}, {'About': [ {'Release notes': 'about/release-notes.md'}, {'License': 'about/license.md'} ]} ] cfg = load_config(nav=nav_cfg, site_url='http://example.com/') files = Files([ File('test.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('api-guide/running.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('api-guide/testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('api-guide/debugging.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('api-guide/advanced/part-1.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('about/release-notes.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('about/license.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), ]) site_navigation = get_navigation(files, cfg) # Confirm nothing is active self.assertTrue(all(page.active is False for page in site_navigation.pages)) self.assertTrue(all(item.active is False for item in site_navigation.items)) # Activate site_navigation.items[1].children[3].children[0].active = True # Confirm ancestors are activated self.assertTrue(site_navigation.items[1].children[3].children[0].active) self.assertTrue(site_navigation.items[1].children[3].active) self.assertTrue(site_navigation.items[1].active) # Confirm non-ancestors are not activated self.assertFalse(site_navigation.items[0].active) self.assertFalse(site_navigation.items[1].children[0].active) self.assertFalse(site_navigation.items[1].children[1].active) self.assertFalse(site_navigation.items[1].children[2].active) self.assertFalse(site_navigation.items[2].active) self.assertFalse(site_navigation.items[2].children[0].active) self.assertFalse(site_navigation.items[2].children[1].active) # Deactivate site_navigation.items[1].children[3].children[0].active = False # Confirm ancestors are deactivated self.assertFalse(site_navigation.items[1].children[3].children[0].active) self.assertFalse(site_navigation.items[1].children[3].active) self.assertFalse(site_navigation.items[1].active)
def test_build_page(self, site_dir): cfg = load_config(site_dir=site_dir, nav=['test.md'], plugins=[]) files = Files([ File('test.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) nav = get_navigation(files, cfg) page = files.documentation_pages()[0].page # Fake populate page page.title = 'Title' page.markdown = 'page content' page.content = '<p>page content</p>' build._build_page(page, cfg, files, nav, cfg['theme'].get_env()) self.assertPathIsFile(site_dir, 'index.html')
def test_markdown_to_nav(tmp_path_factory, use_directory_urls, golden): src_dir, dest_dir = map(tmp_path_factory.mktemp, ["src", "dest"]) files = [File(f, src_dir, dest_dir, use_directory_urls) for f in golden.get("files") or ()] files = Files(files) globber = plugin.MkDocsGlobber(files) get_md = lambda root: golden["navs"].get("/" + root) implicit_index = golden.get("implicit_index") output = None with golden.may_raise(exceptions.LiterateNavError): with golden.capture_logs("mkdocs.plugins.mkdocs_literate_nav"): output = parser.markdown_to_nav(get_md, globber, implicit_index=implicit_index) assert output == golden.out.get("output")
def _sort_translated_files(self) -> None: """Sort all translated pages based on their path.""" def sort_key(page_file: File) -> int: """ Finds url's path length. :param page_file: file to check. :return: length of file's url. """ return len(page_file.url.split("/")) for key, pages in self.i18pages.items(): self.i18pages[key] = Files(sorted(pages, key=sort_key))
def test_context_extra_css_js_from_homepage(self): nav_cfg = [{'Home': 'test.md'}] cfg = load_config(nav=nav_cfg, extra_css=['style.css'], extra_javascript=['script.js'], use_directory_urls=False) files = Files([ File('test.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), ]) nav = get_navigation(files, cfg) context = build.get_context(nav, files, cfg, nav.pages[0]) self.assertEqual(context['extra_css'], ['style.css']) self.assertEqual(context['extra_javascript'], ['script.js'])
def test_context_extra_css_js_from_nested_page_use_directory_urls(self): nav_cfg = [{'Home': 'test.md'}, {'Nested': 'foo/bar.md'}] cfg = load_config(nav=nav_cfg, extra_css=['style.css'], extra_javascript=['script.js']) files = Files([ File('test.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('foo/bar.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) nav = get_navigation(files, cfg) context = build.get_context(nav, files, cfg, nav.pages[1]) self.assertEqual(context['extra_css'], ['../../style.css']) self.assertEqual(context['extra_javascript'], ['../../script.js'])
def test_nav_from_files(self): expected = dedent(""" Page(title=[blank], url='/') Page(title=[blank], url='/about/') """) cfg = load_config(site_url='http://example.com/') files = Files([ File('test.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('about.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 2) self.assertEqual(len(site_navigation.pages), 2) self.assertEqual(repr(site_navigation.homepage), "Page(title=[blank], url='/')")
def test_build_page_plugin_error(self, site_dir, mock_write_file): cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[]) files = Files([File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]) nav = get_navigation(files, cfg) page = files.documentation_pages()[0].page # Fake populate page page.title = 'Title' page.markdown = 'page content' page.content = '<p>page content</p>' with self.assertLogs('mkdocs', level='ERROR') as cm: self.assertRaises(PluginError, build._build_page, page, cfg, files, nav, cfg['theme'].get_env()) self.assertEqual( cm.output, ["ERROR:mkdocs.commands.build:Error building page 'index.md':"] ) self.assert_mock_called_once(mock_write_file)
def test_nav_no_title(self): nav_cfg = ['index.md', {'About': 'about.md'}] expected = dedent(""" Page(title=[blank], url='/') Page(title='About', url='/about/') """) cfg = load_config(nav=nav_cfg, site_url='http://example.com/') files = Files([ File(nav_cfg[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File(nav_cfg[1]['About'], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 2) self.assertEqual(len(site_navigation.pages), 2)
def on_files(self, files, config): ansi_colours_css = File( path="ansi-colours.css", src_dir=os.path.join(here, "templates"), dest_dir=os.path.join(config["site_dir"], "css"), use_directory_urls=False, ) files = Files( [ NotebookFile(f, **config) if str(f.abs_src_path).endswith("ipynb") else f for f in files ] + [ansi_colours_css] ) return files
def test_nav_missing_page(self): nav_cfg = [{'Home': 'index.md'}] expected = dedent(""" Page(title='Home', url='/') """) cfg = load_config(nav=nav_cfg, site_url='http://example.com/') files = Files([ File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('page_not_in_nav.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 1) self.assertEqual(len(site_navigation.pages), 1) for file in files: self.assertIsInstance(file.page, Page)
def test_nav_no_directory_urls(self): nav_cfg = [{'Home': 'index.md'}, {'About': 'about.md'}] expected = dedent(""" Page(title='Home', url='/index.html') Page(title='About', url='/about.html') """) cfg = load_config(nav=nav_cfg, use_directory_urls=False, site_url='http://example.com/') files = Files([ File( list(item.values())[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for item in nav_cfg ]) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 2) self.assertEqual(len(site_navigation.pages), 2)
def current(cls) -> "FilesEditor": """The instance of FilesEditor associated with the currently ongoing MkDocs build. If used as part of a MkDocs build (*gen-files* plugin), it's an instance using virtual files that feed back into the build. If not, this still tries to load the MkDocs config to find out the *docs_dir*, and then actually performs any file writes that happen via `.open()`. This is global (not thread-safe). """ if cls._current: return cls._current if not cls._default: config = load_config("mkdocs.yml") config["plugins"].run_event("config", config) cls._default = FilesEditor(Files([]), config) return cls._default
def test_nested_ungrouped_nav_no_titles(self): nav_cfg = ['index.md', 'about/contact.md', 'about/sub/license.md'] expected = dedent(""" Page(title=[blank], url='/') Page(title=[blank], url='/about/contact/') Page(title=[blank], url='/about/sub/license/') """) cfg = load_config(nav=nav_cfg, site_url='http://example.com/') files = Files([ File(item, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for item in nav_cfg ]) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 3) self.assertEqual(len(site_navigation.pages), 3) self.assertEqual(repr(site_navigation.homepage), "Page(title=[blank], url='/')")
def test_files_append_remove_src_paths(self): fs = [ File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=True), File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=True) ] files = Files(fs) self.assertEqual(len(files), 6) self.assertEqual(len(files.src_paths), 6) extra_file = File('extra.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) self.assertFalse(extra_file.src_path in files) files.append(extra_file) self.assertEqual(len(files), 7) self.assertEqual(len(files.src_paths), 7) self.assertTrue(extra_file.src_path in files) files.remove(extra_file) self.assertEqual(len(files), 6) self.assertEqual(len(files.src_paths), 6) self.assertFalse(extra_file.src_path in files)
def test_build_page_dirty_not_modified(self, site_dir, mock_write_file): cfg = load_config(site_dir=site_dir, nav=['test.md'], plugins=[]) files = Files([ File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) nav = get_navigation(files, cfg) page = files.documentation_pages()[0].page # Fake populate page page.title = 'Title' page.markdown = 'page content' page.content = '<p>page content</p>' build._build_page(page, cfg, files, nav, cfg['theme'].get_env(), dirty=True) mock_write_file.assert_called_once()
def test_nested_ungrouped_nav(self): nav_cfg = [ {'Home': 'test.md'}, {'Contact': 'about/contact.md'}, {'License Title': 'about/sub/license.md'}, ] expected = dedent(""" Page(title='Home', url='/') Page(title='Contact', url='/about/contact/') Page(title='License Title', url='/about/sub/license/') """) cfg = load_config(nav=nav_cfg, site_url='http://example.com/') files = Files( [File(list(item.values())[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for item in nav_cfg] ) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 3) self.assertEqual(len(site_navigation.pages), 3)
def test_nav_external_links(self): nav_cfg = [{ 'Home': 'index.md' }, { 'Local': '/local.html' }, { 'External': 'http://example.com/external.html' }] expected = dedent(""" Page(title='Home', url='/') Link(title='Local', url='/local.html') Link(title='External', url='http://example.com/external.html') """) cfg = load_config(nav=nav_cfg, site_url='http://example.com/') files = Files([ File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 3) self.assertEqual(len(site_navigation.pages), 1)
def test_build_page_dirty_modified(self, site_dir, docs_dir, mock_write_file): cfg = load_config(docs_dir=docs_dir, site_dir=site_dir, nav=['index.md'], plugins=[]) files = Files([ File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) ]) nav = get_navigation(files, cfg) page = files.documentation_pages()[0].page # Fake populate page page.title = 'Title' page.markdown = 'new page content' page.content = '<p>new page content</p>' build._build_page(page, cfg, files, nav, self._get_env_with_null_translations(cfg), dirty=True) mock_write_file.assert_not_called()
def test_nav_from_nested_files(self): expected = dedent(""" Page(title=[blank], url='/') Section(title='About') Page(title=[blank], url='/about/license/') Page(title=[blank], url='/about/release-notes/') Section(title='Api guide') Page(title=[blank], url='/api-guide/debugging/') Page(title=[blank], url='/api-guide/running/') Page(title=[blank], url='/api-guide/testing/') Section(title='Advanced') Page(title=[blank], url='/api-guide/advanced/part-1/') """) cfg = load_config(site_url='http://example.com/') files = Files([ File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('about/license.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('about/release-notes.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('api-guide/debugging.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('api-guide/running.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('api-guide/testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), File('api-guide/advanced/part-1.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), ]) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 3) self.assertEqual(len(site_navigation.pages), 7) self.assertEqual(repr(site_navigation.homepage), "Page(title=[blank], url='/')")
def on_files(self, files, config): print(files) out_files = [] md_files = [] # Scan the list of files to extract tags from meta for f in files: if not f.is_documentation_page(): print(f.src_path) out_files.append(f) continue metadata = get_metadata(f.src_path, config["docs_dir"]) print(metadata) file_date = metadata.get('date') if file_date: file_datetime = datetime.datetime.strptime(file_date, self.date_format) f.datestamp = file_datetime else: f.datestamp = datetime.datetime.fromtimestamp(os.path.getmtime(os.path.join( config["docs_dir"], f.src_path ))) md_files.append(f) print(f.datestamp) sorted_files = sorted(md_files, key=attrgetter('datestamp'), reverse=self.reverse) print("REVERSE:", self.reverse) out_files.extend(sorted_files) return Files(out_files)
def test_populate_page(self, docs_dir): cfg = load_config(docs_dir=docs_dir) file = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) page = Page('Foo', file, cfg) build._populate_page(page, cfg, Files([file])) self.assertEqual(page.content, '<p>page content</p>')
def test_build_extra_template(self): cfg = load_config() files = Files([ File('foo.html', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), ]) build._build_extra_template('foo.html', files, cfg, mock.Mock())