def test_context_base_url_homepage_use_directory_urls(self): nav_cfg = [ {'Home': 'index.md'} ] cfg = load_config(nav=nav_cfg) files = Files([ File('index.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['base_url'], '.')
def test_build_page_dirty_not_modified(self, site_dir, mock_write_file): cfg = load_config(site_dir=site_dir, nav=['index.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_build_page(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.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_context_base_url_nested_page(self): nav_cfg = [ {'Home': 'index.md'}, {'Nested': 'foo/bar.md'} ] cfg = load_config(nav=nav_cfg, use_directory_urls=False) 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_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(IOError, build._build_page, page, cfg, files, nav, cfg['theme'].get_env()) self.assertEqual( cm.output, ["ERROR:mkdocs.commands.build:Error building page 'index.md': Error message."] ) mock_write_file.assert_called_once()
def test_context_extra_css_js_from_homepage(self): nav_cfg = [ {'Home': 'index.md'} ] cfg = load_config( nav=nav_cfg, extra_css=['style.css'], extra_javascript=['script.js'], use_directory_urls=False ) files = Files([ File('index.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_nav_no_title(self): nav_cfg = [ 'test.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 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 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_context_extra_css_js_from_nested_page_use_directory_urls(self): nav_cfg = [ {'Home': 'index.md'}, {'Nested': 'foo/bar.md'} ] cfg = load_config( nav=nav_cfg, extra_css=['style.css'], extra_javascript=['script.js'] ) 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['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('index.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_error(self, site_dir, mock_write_file): 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>' with self.assertLogs('mkdocs', level='ERROR') as cm: self.assertRaises(IOError, build._build_page, page, cfg, files, nav, cfg['theme'].get_env()) self.assertEqual(cm.output, [ "ERROR:mkdocs.commands.build:Error building page 'test.md': Error message." ]) mock_write_file.assert_called_once()
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_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_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 on_nav(self, nav: Navigation, config: Config, files: Files) -> Navigation: """ Handler for nav event. Builds navigation for translated pages. For each language creates it's own navigation. :param nav: current navigation. :param config: project config. :param files: all project files. :return: new Navigation. """ if self.i18navs: return nav for language, lang_files in self.i18pages.items(): logger.debug(f"Building navigation for {language}") self.i18navs[language] = get_navigation(lang_files, config) self.i18navs[language].homepage = deepcopy( self._get_lang_homepage(language), ) return nav
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 nav(config, plugin, files): nav = get_navigation(files, config) nav = plugin.on_nav(nav, config) return nav
def test_active(self): nav_cfg = [{ 'Home': 'index.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('index.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 build(config, live_server=False, dirty=False): """ Perform a full site build. """ try: from time import time start = time() # Run `config` plugin events. config = config['plugins'].run_event('config', config) # Run `pre_build` plugin events. config['plugins'].run_event('pre_build', config=config) if not dirty: log.info("Cleaning site directory") utils.clean_directory(config['site_dir']) else: # pragma: no cover # Warn user about problems that may occur with --dirty option log.warning( "A 'dirty' build is being performed, this will likely lead to inaccurate navigation and other" " links within your site. This option is designed for site development purposes only." ) if not live_server: # pragma: no cover log.info("Building documentation to directory: %s", config['site_dir']) if dirty and site_directory_contains_stale_files( config['site_dir']): log.info( "The directory contains stale files. Use --clean to remove them." ) # First gather all data from all files/pages to ensure all data is consistent across all pages. files = get_files(config) env = config['theme'].get_env() files.add_files_from_theme(env, config) # Run `files` plugin events. files = config['plugins'].run_event('files', files, config=config) nav = get_navigation(files, config) # Run `nav` plugin events. nav = config['plugins'].run_event('nav', nav, config=config, files=files) log.debug("Reading markdown pages.") for file in files.documentation_pages(): log.debug("Reading: " + file.src_path) _populate_page(file.page, config, files, dirty) # Run `env` plugin events. env = config['plugins'].run_event('env', env, config=config, files=files) # Start writing files to site_dir now that all data is gathered. Note that order matters. Files # with lower precedence get written first so that files with higher precedence can overwrite them. log.debug("Copying static assets.") files.copy_static_files(dirty=dirty) for template in config['theme'].static_templates: _build_theme_template(template, env, files, config, nav) for template in config['extra_templates']: _build_extra_template(template, files, config, nav) log.debug("Building markdown pages.") doc_files = files.documentation_pages() for file in doc_files: _build_page(file.page, config, doc_files, nav, env, dirty) # Run `post_build` plugin events. config['plugins'].run_event('post_build', config=config) if config['strict'] and utils.warning_filter.count: raise SystemExit( '\nExited with {} warnings in strict mode.'.format( utils.warning_filter.count)) log.info('Documentation built in %.2f seconds', time() - start) except Exception as e: # Run `build_error` plugin events. config['plugins'].run_event('build_error', error=e) if isinstance(e, BuildError): raise SystemExit('\n' + str(e)) raise
def test_indented_nav(self): nav_cfg = [{ 'Home': 'index.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': '/license.html' }] }, { 'External': 'https://example.com/' }] expected = dedent(""" Page(title='Home', url='/') Section(title='API Guide') Page(title='Running', url='/api-guide/running/') Page(title='Testing', url='/api-guide/testing/') Page(title='Debugging', url='/api-guide/debugging/') Section(title='Advanced') Page(title='Part 1', url='/api-guide/advanced/part-1/') Section(title='About') Page(title='Release notes', url='/about/release-notes/') Link(title='License', url='/license.html') Link(title='External', url='https://example.com/') """) 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('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']), ]) site_navigation = get_navigation(files, cfg) self.assertEqual(str(site_navigation).strip(), expected) self.assertEqual(len(site_navigation.items), 4) self.assertEqual(len(site_navigation.pages), 6) self.assertEqual(repr(site_navigation.homepage), "Page(title='Home', url='/')") self.assertIsNone(site_navigation.items[0].parent) self.assertEqual(site_navigation.items[0].ancestors, []) self.assertIsNone(site_navigation.items[1].parent) self.assertEqual(site_navigation.items[1].ancestors, []) self.assertEqual(len(site_navigation.items[1].children), 4) self.assertEqual(repr(site_navigation.items[1].children[0].parent), "Section(title='API Guide')") self.assertEqual(site_navigation.items[1].children[0].ancestors, [site_navigation.items[1]]) self.assertEqual(repr(site_navigation.items[1].children[1].parent), "Section(title='API Guide')") self.assertEqual(site_navigation.items[1].children[1].ancestors, [site_navigation.items[1]]) self.assertEqual(repr(site_navigation.items[1].children[2].parent), "Section(title='API Guide')") self.assertEqual(site_navigation.items[1].children[2].ancestors, [site_navigation.items[1]]) self.assertEqual(repr(site_navigation.items[1].children[3].parent), "Section(title='API Guide')") self.assertEqual(site_navigation.items[1].children[3].ancestors, [site_navigation.items[1]]) self.assertEqual(len(site_navigation.items[1].children[3].children), 1) self.assertEqual( repr(site_navigation.items[1].children[3].children[0].parent), "Section(title='Advanced')") self.assertEqual( site_navigation.items[1].children[3].children[0].ancestors, [site_navigation.items[1].children[3], site_navigation.items[1]]) self.assertIsNone(site_navigation.items[2].parent) self.assertEqual(len(site_navigation.items[2].children), 2) self.assertEqual(repr(site_navigation.items[2].children[0].parent), "Section(title='About')") self.assertEqual(site_navigation.items[2].children[0].ancestors, [site_navigation.items[2]]) self.assertEqual(repr(site_navigation.items[2].children[1].parent), "Section(title='About')") self.assertEqual(site_navigation.items[2].children[1].ancestors, [site_navigation.items[2]]) self.assertIsNone(site_navigation.items[3].parent) self.assertEqual(site_navigation.items[3].ancestors, []) self.assertIsNone(site_navigation.items[3].children)