Exemple #1
0
def _build_template(name, template, files, config, nav):
    """
    Return rendered output for given template as a string.
    """

    # Run `pre_template` plugin events.
    template = config['plugins'].run_event(
        'pre_template', template, template_name=name, config=config
    )

    if utils.is_error_template(name):
        # Force absolute URLs in the nav of error pages and account for the
        # possability that the docs root might be different than the server root.
        # See https://github.com/mkdocs/mkdocs/issues/77.
        # However, if site_url is not set, assume the docs root and server root
        # are the same. See https://github.com/mkdocs/mkdocs/issues/1598.
        base_url = utils.urlparse(config['site_url'] or '/').path
    else:
        base_url = utils.get_relative_url('.', name)

    context = get_context(nav, files, config, base_url=base_url)

    # Run `template_context` plugin events.
    context = config['plugins'].run_event(
        'template_context', context, template_name=name, config=config
    )

    output = template.render(context)

    # Run `post_template` plugin events.
    output = config['plugins'].run_event(
        'post_template', output, template_name=name, config=config
    )

    return output
Exemple #2
0
    def post_validation(self, config, key_name):
        repo_host = utils.urlparse(config['repo_url']).netloc.lower()
        edit_uri = config.get('edit_uri')

        # derive repo_name from repo_url if unset
        if config['repo_url'] is not None and config.get('repo_name') is None:
            if repo_host == 'github.com':
                config['repo_name'] = 'GitHub'
            elif repo_host == 'bitbucket.org':
                config['repo_name'] = 'Bitbucket'
            elif repo_host == 'gitlab.com':
                config['repo_name'] = 'GitLab'
            else:
                config['repo_name'] = repo_host.split('.')[0].title()

        # derive edit_uri from repo_name if unset
        if config['repo_url'] is not None and edit_uri is None:
            if repo_host == 'github.com' or repo_host == 'gitlab.com':
                edit_uri = 'edit/master/docs/'
            elif repo_host == 'bitbucket.org':
                edit_uri = 'src/default/docs/'
            else:
                edit_uri = ''

        # ensure a well-formed edit_uri
        if edit_uri:
            if not edit_uri.startswith(('?', '#')) \
                    and not config['repo_url'].endswith('/'):
                config['repo_url'] += '/'
            if not edit_uri.endswith('/'):
                edit_uri += '/'

        config['edit_uri'] = edit_uri
Exemple #3
0
    def path_to_url(self, url):
        scheme, netloc, path, params, query, fragment = urlparse(url)

        if scheme or netloc or not path or AMP_SUBSTITUTE in url or '.' not in os.path.split(path)[-1]:
            # Ignore URLs unless they are a relative link to a source file.
            # AMP_SUBSTITUTE is used internally by Markdown only for email.
            # No '.' in the last part of a path indicates path does not point to a file.
            return url

        # Determine the filepath of the target.
        target_path = os.path.join(os.path.dirname(self.file.src_path), path)
        target_path = os.path.normpath(target_path).lstrip(os.sep)

        if not self.is_source_code_link(target_path):
            return url

        source_url = convert_path_to_source_url(
            target_path,
            repos_prefix=self.config['repos_prefix'],
            repos_info=self.config['repos_info'],
            default_url_template=self.config['default_url_template'],
            github_url_template=self.config['github_url_template'],
        )

        return source_url or url
Exemple #4
0
    def post_validation(self, config, key_name):
        repo_host = utils.urlparse(config['repo_url']).netloc.lower()
        edit_uri = config.get('edit_uri')

        # derive repo_name from repo_url if unset
        if config['repo_url'] is not None and config.get('repo_name') is None:
            if repo_host == 'github.com':
                config['repo_name'] = 'GitHub'
            elif repo_host == 'bitbucket.org':
                config['repo_name'] = 'Bitbucket'
            else:
                config['repo_name'] = repo_host.split('.')[0].title()

        # derive edit_uri from repo_name if unset
        if config['repo_url'] is not None and edit_uri is None:
            if repo_host == 'github.com':
                edit_uri = 'edit/master/docs/'
            elif repo_host == 'bitbucket.org':
                edit_uri = 'src/default/docs/'
            else:
                edit_uri = ''

        # ensure a well-formed edit_uri
        if edit_uri:
            if not edit_uri.startswith(('?', '#')) \
                    and not config['repo_url'].endswith('/'):
                config['repo_url'] += '/'
            if not edit_uri.endswith('/'):
                edit_uri += '/'

        config['edit_uri'] = edit_uri
Exemple #5
0
    def path_to_url(self, url):
        scheme, netloc, path, params, query, fragment = urlparse(url)

        if scheme or netloc or not path or AMP_SUBSTITUTE in url or '.' not in os.path.split(
                path)[-1]:
            # Ignore URLs unless they are a relative link to a source file.
            # AMP_SUBSTITUTE is used internally by Markdown only for email.
            # No '.' in the last part of a path indicates path does not point to a file.
            return url

        # Determine the filepath of the target.
        target_path = os.path.join(os.path.dirname(self.file.src_path), path)
        target_path = os.path.normpath(target_path).lstrip(os.sep)

        # Validate that the target exists in files collection.
        if target_path not in self.files:
            msg = (
                "Documentation file '{}' contains a link to '{}' which does not exist "
                "in the documentation directory.".format(
                    self.file.src_path, target_path))
            # In strict mode raise an error at this point.
            if self.strict:
                raise MarkdownNotFound(msg)
            # Otherwise, when strict mode isn't enabled, log a warning
            # to the user and leave the URL as it is.
            log.warning(msg)
            return url
        target_file = self.files.get_file_from_path(target_path)
        path = target_file.url_relative_to(self.file)
        components = (scheme, netloc, path, params, query, fragment)
        return urlunparse(components)
Exemple #6
0
def _build_template(name, template, files, config, nav):
    """
    Return rendered output for given template as a string.
    """

    # Run `pre_template` plugin events.
    template = config['plugins'].run_event(
        'pre_template', template, template_name=name, config=config
    )

    if utils.is_error_template(name):
        # Force absolute URLs in the nav of error pages and account for the
        # possability that the docs root might be different than the server root.
        # See https://github.com/mkdocs/mkdocs/issues/77.
        # However, if site_url is not set, assume the docs root and server root
        # are the same. See https://github.com/mkdocs/mkdocs/issues/1598.
        base_url = utils.urlparse(config['site_url'] or '/').path
    else:
        base_url = utils.get_relative_url('.', name)

    context = get_context(nav, files, config, base_url=base_url)

    # Run `template_context` plugin events.
    context = config['plugins'].run_event(
        'template_context', context, template_name=name, config=config
    )

    output = template.render(context)

    # Run `post_template` plugin events.
    output = config['plugins'].run_event(
        'post_template', output, template_name=name, config=config
    )

    return output
Exemple #7
0
    def path_to_url(self, url):
        scheme, netloc, path, params, query, fragment = urlparse(url)

        if (scheme or netloc or not path or url.startswith('/')
                or AMP_SUBSTITUTE in url or '.' not in os.path.split(path)[-1]):
            # Ignore URLs unless they are a relative link to a source file.
            # AMP_SUBSTITUTE is used internally by Markdown only for email.
            # No '.' in the last part of a path indicates path does not point to a file.
            return url

        # Determine the filepath of the target.
        target_path = os.path.join(os.path.dirname(self.file.src_path), urlunquote(path))
        target_path = os.path.normpath(target_path).lstrip(os.sep)

        # Validate that the target exists in files collection.
        if target_path not in self.files:
            log.warning(
                "Documentation file '{}' contains a link to '{}' which is not found "
                "in the documentation files.".format(self.file.src_path, target_path)
            )
            return url
        target_file = self.files.get_file_from_path(target_path)
        path = target_file.url_relative_to(self.file)
        components = (scheme, netloc, path, params, query, fragment)
        return urlunparse(components)
Exemple #8
0
    def path_to_url(self, url):
        scheme, netloc, path, params, query, fragment = urlparse(url)

        if (scheme or netloc or not path or url.startswith('/')
                or AMP_SUBSTITUTE in url or '.' not in os.path.split(path)[-1]):
            # Ignore URLs unless they are a relative link to a source file.
            # AMP_SUBSTITUTE is used internally by Markdown only for email.
            # No '.' in the last part of a path indicates path does not point to a file.
            return url

        # Determine the filepath of the target.
        target_path = os.path.join(os.path.dirname(self.file.src_path), urlunquote(path))
        target_path = os.path.normpath(target_path).lstrip(os.sep)

        # Validate that the target exists in files collection.
        if target_path not in self.files:
            log.warning(
                "Documentation file '{}' contains a link to '{}' which is not found "
                "in the documentation files.".format(self.file.src_path, target_path)
            )
            return url
        target_file = self.files.get_file_from_path(target_path)
        path = target_file.url_relative_to(self.file)
        components = (scheme, netloc, path, params, query, fragment)
        return urlunparse(components)
Exemple #9
0
 def _set_canonical_url(self, base):
     if base:
         if not base.endswith('/'):
             base += '/'
         self.canonical_url = urljoin(base, self.url)
         self.abs_url = urlparse(self.canonical_url).path
     else:
         self.canonical_url = None
         self.abs_url = None
Exemple #10
0
 def _set_canonical_url(self, base):
     if base:
         if not base.endswith('/'):
             base += '/'
         self.canonical_url = urljoin(base, self.url)
         self.abs_url = urlparse(self.canonical_url).path
     else:
         self.canonical_url = None
         self.abs_url = None
Exemple #11
0
    def post_validation(self, config, key_name):

        if config['repo_url'] is not None and config.get('repo_name') is None:
            repo_host = utils.urlparse(config['repo_url']).netloc.lower()
            if repo_host == 'github.com':
                config['repo_name'] = 'GitHub'
            elif repo_host == 'bitbucket.org':
                config['repo_name'] = 'Bitbucket'
            else:
                config['repo_name'] = repo_host.split('.')[0].title()
Exemple #12
0
    def post_validation(self, config, key_name):

        if config['repo_url'] is not None and config.get('repo_name') is None:
            repo_host = utils.urlparse(
                config['repo_url']).netloc.lower()
            if repo_host == 'github.com':
                config['repo_name'] = 'GitHub'
            elif repo_host == 'bitbucket.org':
                config['repo_name'] = 'Bitbucket'
            else:
                config['repo_name'] = repo_host.split('.')[0].title()
Exemple #13
0
    def run_validation(self, value):

        try:
            parsed_url = utils.urlparse(value)
        except (AttributeError, TypeError):
            raise ValidationError("Unable to parse the URL.")

        if parsed_url.scheme:
            return value

        raise ValidationError(
            "The URL isn't valid, it should include the http:// (scheme)")
Exemple #14
0
    def run_validation(self, value):

        try:
            parsed_url = utils.urlparse(value)
        except (AttributeError, TypeError):
            raise ValidationError("Unable to parse the URL.")

        if parsed_url.scheme:
            return value

        raise ValidationError(
            "The URL isn't valid, it should include the http:// (scheme)")
Exemple #15
0
def get_navigation(files, config):
    """ Build site navigation from config and files."""
    nav_config = config['nav'] or nest_paths(
        f.src_path for f in files.documentation_pages())
    items = _data_to_navigation(nav_config, files, config)
    if not isinstance(items, list):
        items = [items]

    # Get only the pages from the navigation, ignoring any sections and links.
    pages = _get_by_type(items, Page)

    # Include next, previous and parent links.
    _add_previous_and_next_links(pages)
    _add_parent_links(items)

    missing_from_config = [
        file for file in files.documentation_pages() if file.page is None
    ]
    if missing_from_config:
        log.info(
            'The following pages exist in the docs directory, but are not '
            'included in the "nav" configuration:\n  - {}'.format(
                '\n  - '.join([file.src_path
                               for file in missing_from_config])))
        # Any documentation files not found in the nav should still have an associated page.
        # However, these page objects are only accessable from File instances as `file.page`.
        for file in missing_from_config:
            Page(None, file, config)

    links = _get_by_type(items, Link)
    for link in links:
        scheme, netloc, path, params, query, fragment = urlparse(link.url)
        if scheme or netloc:
            log.debug("An external link to '{}' is included in "
                      "the 'nav' configuration.".format(link.url))
        elif link.url.startswith('/'):
            log.debug(
                "An absolute path to '{}' is included in the 'nav' configuration, "
                "which presumably points to an external resource.".format(
                    link.url))
        else:
            msg = (
                "A relative path to '{}' is included in the 'nav' configuration, "
                "which is not found in the documentation files".format(
                    link.url))
            log.warning(msg)
    return Navigation(items, pages)
Exemple #16
0
def munge(url):

    scheme, netloc, path, params, query, fragment = (utils.urlparse(url))

    if scheme or netloc or not path:
        # Ignore URLs unless they are a relative link to a markdown file.
        return url

    if not path.endswith(".md"):
        return url
    else:
        path = path.replace(".md", ".html")

    # Convert the .md hyperlink to a relative hyperlink to the HTML page.
    fragments = (scheme, netloc, path, params, query, fragment)
    url = utils.urlunparse(fragments)
    return url
Exemple #17
0
def get_navigation(files, config):
    """ Build site navigation from config and files."""
    nav_config = config['nav'] or nest_paths(f.src_path for f in files.documentation_pages())
    items = _data_to_navigation(nav_config, files, config)
    if not isinstance(items, list):
        items = [items]

    # Get only the pages from the navigation, ignoring any sections and links.
    pages = _get_by_type(items, Page)

    # Include next, previous and parent links.
    _add_previous_and_next_links(pages)
    _add_parent_links(items)

    missing_from_config = [file for file in files.documentation_pages() if file.page is None]
    if missing_from_config:
        log.info(
            'The following pages exist in the docs directory, but are not '
            'included in the "nav" configuration:\n  - {}'.format(
                '\n  - '.join([file.src_path for file in missing_from_config]))
        )
        # Any documentation files not found in the nav should still have an associated page.
        # However, these page objects are only accessable from File instances as `file.page`.
        for file in missing_from_config:
            Page(None, file, config)

    links = _get_by_type(items, Link)
    for link in links:
        scheme, netloc, path, params, query, fragment = urlparse(link.url)
        if scheme or netloc:
            log.debug(
                "An external link to '{}' is included in "
                "the 'nav' configuration.".format(link.url)
            )
        elif link.url.startswith('/'):
            log.debug(
                "An absolute path to '{}' is included in the 'nav' configuration, "
                "which presumably points to an external resource.".format(link.url)
            )
        else:
            msg = (
                "A relative path to '{}' is included in the 'nav' configuration, "
                "which is not found in the documentation files".format(link.url)
            )
            log.warning(msg)
    return Navigation(items, pages)
def munge(url):

    scheme, netloc, path, params, query, fragment = (
        utils.urlparse(url))

    if scheme or netloc or not path:
        # Ignore URLs unless they are a relative link to a markdown file.
        return url

    if not path.endswith(".md"):
        return url
    else:
        path = path.replace(".md",".html");

    # Convert the .md hyperlink to a relative hyperlink to the HTML page.
    fragments = (scheme, netloc, path, params, query, fragment)
    url = utils.urlunparse(fragments)
    return url
Exemple #19
0
def build_error_template(template, env, config, site_navigation):
    """
    Build error template.

    Force absolute URLs in the nav of error pages and account for the
    possability that the docs root might be different than the server root.
    See https://github.com/mkdocs/mkdocs/issues/77
    """

    site_navigation.url_context.force_abs_urls = True
    default_base = site_navigation.url_context.base_path
    site_navigation.url_context.base_path = utils.urlparse(config['site_url']).path

    build_template(template, env, config, site_navigation)

    # Reset nav behavior to the default
    site_navigation.url_context.force_abs_urls = False
    site_navigation.url_context.base_path = default_base
Exemple #20
0
def path_to_url(url, nav, strict):

    scheme, netloc, path, params, query, fragment = (utils.urlparse(url))

    if scheme or netloc or not path or AMP_SUBSTITUTE in url:
        # Ignore URLs unless they are a relative link to a markdown file.
        # AMP_SUBSTITUTE is used internally by Markdown only for email,which is
        # not a relative link. As urlparse errors on them, skip explicitly
        return url

    if nav and not utils.is_markdown_file(path):
        path = utils.create_relative_media_url(nav, path)
    elif nav:
        # If the site navigation has been provided, then validate
        # the internal hyperlink, making sure the target actually exists.
        target_file = nav.file_context.make_absolute(path)

        if target_file.startswith(os.path.sep):
            target_file = target_file[1:]

        if target_file not in nav.source_files:
            source_file = nav.file_context.current_file
            msg = ('The page "%s" contained a hyperlink to "%s" which '
                   'is not listed in the "pages" configuration.') % (
                       source_file, target_file)

            # In strict mode raise an error at this point.
            if strict:
                raise MarkdownNotFound(msg)
            # Otherwise, when strict mode isn't enabled, log a warning
            # to the user and leave the URL as it is.

            # suppress the Warning
            # log.warning(msg)
            # return url
        path = utils.get_url_path(target_file, nav.use_directory_urls)
        path = nav.url_context.make_relative(path)
    else:
        path = utils.get_url_path(path).lstrip('/')

    # Convert the .md hyperlink to a relative hyperlink to the HTML page.
    fragments = (scheme, netloc, path, params, query, fragment)
    url = utils.urlunparse(fragments)
    return url
Exemple #21
0
def path_to_url(url, nav, strict):

    scheme, netloc, path, params, query, fragment = (
        utils.urlparse(url))

    if scheme or netloc or not path or AMP_SUBSTITUTE in url:
        # Ignore URLs unless they are a relative link to a markdown file.
        # AMP_SUBSTITUTE is used internally by Markdown only for email,which is
        # not a relative link. As urlparse errors on them, skip explicitly
        return url

    if nav and not utils.is_markdown_file(path):
        path = utils.create_relative_media_url(nav, path)
    elif nav:
        # If the site navigation has been provided, then validate
        # the internal hyperlink, making sure the target actually exists.
        target_file = nav.file_context.make_absolute(path)

        if target_file.startswith(os.path.sep):
            target_file = target_file[1:]

        if target_file not in nav.source_files:
            source_file = nav.file_context.current_file
            msg = (
                'The page "%s" contained a hyperlink to "%s" which '
                'is not listed in the "pages" configuration.'
            ) % (source_file, target_file)

            # In strict mode raise an error at this point.
            if strict:
                raise MarkdownNotFound(msg)
            # Otherwise, when strict mode isn't enabled, log a warning
            # to the user and leave the URL as it is.
            log.warning(msg)
            return url
        path = utils.get_url_path(target_file, nav.use_directory_urls)
        path = nav.url_context.make_relative(path)
    else:
        path = utils.get_url_path(path).lstrip('/')

    # Convert the .md hyperlink to a relative hyperlink to the HTML page.
    fragments = (scheme, netloc, path, params, query, fragment)
    url = utils.urlunparse(fragments)
    return url
Exemple #22
0
def build_error_template(template, env, config, site_navigation):
    """
    Build error template.

    Force absolute URLs in the nav of error pages and account for the
    possability that the docs root might be different than the server root.
    See https://github.com/mkdocs/mkdocs/issues/77
    """

    site_navigation.url_context.force_abs_urls = True
    default_base = site_navigation.url_context.base_path
    site_navigation.url_context.base_path = utils.urlparse(
        config['site_url']).path

    build_template(template, env, config, site_navigation)

    # Reset nav behavior to the default
    site_navigation.url_context.force_abs_urls = False
    site_navigation.url_context.base_path = default_base
Exemple #23
0
    def run(self, root):
        """
        Update urls on anchors and images to make them relative

        Iterates through the full document tree looking for specific
        tags and then makes them relative based on the site navigation
        """
        for element in root.iter():
            if element.tag == 'a':
                key = 'href'
            elif element.tag == 'img':
                key = 'src'
            else:
                continue

            url = element.get(key)
            scheme, netloc, path, params, query, fragment = urlparse(url) # can optimize reusing as components in path_to_url()
            new_url = self.path_to_url(url) # components
            element.set(key, new_url)
            if (key == 'href' and (scheme or netloc or not path or url.startswith('/'))):
                element.set('rel', 'external')

        return root
Exemple #24
0
def build_pages(config, dump_json=False, dirty=False):
    """
    Builds all the pages and writes them into the build directory.
    """
    site_navigation = nav.SiteNavigation(config['pages'], config['use_directory_urls'])
    loader = jinja2.FileSystemLoader(config['theme_dir'] + [config['mkdocs_templates'], ])
    env = jinja2.Environment(loader=loader)

    # TODO: remove DeprecationContext in v1.0 when all deprecated vars have been removed
    from jinja2.runtime import Context
    deprecated_vars = {
        'page_title': 'page.title',
        'content': 'page.content',
        'toc': 'page.toc',
        'meta': 'page.meta',
        'canonical_url': 'page.canonical_url',
        'previous_page': 'page.previous_page',
        'next_page': 'page.next_page',
        'current_page': 'page',
        'include_nav': 'nav|length>1',
        'include_next_prev': '(page.next_page or page.previous_page)',
        'site_name': 'config.site_name',
        'site_author': 'config.site_author',
        'page_description': 'config.site_description',
        'repo_url': 'config.repo_url',
        'repo_name': 'config.repo_name',
        'site_url': 'config.site_url',
        'copyright': 'config.copyright',
        'google_analytics': 'config.google_analytics',
        'homepage_url': 'nav.homepage.url',
        'favicon': '{{ base_url }}/img/favicon.ico',
    }

    class DeprecationContext(Context):
        def resolve(self, key):
            """ Log a warning when accessing any deprecated variable name. """
            if key in deprecated_vars:
                log.warn(
                    "Template variable warning: '{0}' is being deprecated "
                    "and will not be available in a future version. Use "
                    "'{1}' instead.".format(key, deprecated_vars[key])
                )
            return super(DeprecationContext, self).resolve(key)

    env.context_class = DeprecationContext
    # TODO: end remove DeprecationContext

    env.filters['tojson'] = filters.tojson
    search_index = search.SearchIndex()

    # Force absolute URLs in the nav of error pages and account for the
    # possability that the docs root might be different than the server root.
    # See https://github.com/mkdocs/mkdocs/issues/77
    site_navigation.url_context.force_abs_urls = True
    default_base = site_navigation.url_context.base_path
    site_navigation.url_context.base_path = utils.urlparse(config['site_url']).path
    build_template('404.html', env, config, site_navigation)
    # Reset nav behavior to the default
    site_navigation.url_context.force_abs_urls = False
    site_navigation.url_context.base_path = default_base

    if not build_template('search.html', env, config, site_navigation):
        log.debug("Search is enabled but the theme doesn't contain a "
                  "search.html file. Assuming the theme implements search "
                  "within a modal.")

    build_template('sitemap.xml', env, config, site_navigation)

    build_extra_templates(config['extra_templates'], config, site_navigation)

    for page in site_navigation.walk_pages():

        try:

            # When --dirty is used, only build the page if the markdown has been modified since the
            # previous build of the output.
            input_path, output_path = get_complete_paths(config, page)
            if dirty and (utils.modified_time(input_path) < utils.modified_time(output_path)):
                continue

            log.debug("Building page %s", page.input_path)
            build_result = _build_page(page, config, site_navigation, env,
                                       dump_json)
            html_content, table_of_contents, _ = build_result
            search_index.add_entry_from_context(
                page, html_content, table_of_contents)
        except Exception:
            log.error("Error building page %s", page.input_path)
            raise

    search_index = search_index.generate_search_index()
    json_output_path = os.path.join(config['site_dir'], 'mkdocs', 'search_index.json')
    utils.write_file(search_index.encode('utf-8'), json_output_path)
Exemple #25
0
def build_pages(config, dump_json=False, dirty=False):
    """
    Builds all the pages and writes them into the build directory.
    """
    site_navigation = nav.SiteNavigation(config['pages'], config['use_directory_urls'])
    loader = jinja2.FileSystemLoader(config['theme_dir'] + [config['mkdocs_templates'], ])
    env = jinja2.Environment(loader=loader)

    # TODO: remove DeprecationContext in v1.0 when all deprecated vars have been removed
    from jinja2.runtime import Context
    deprecated_vars = [
        'page_title',
        'content',
        'toc',
        'meta',
        'current_page',
        'canonical_url',
        'previous_page',
        'next_page'
    ]

    class DeprecationContext(Context):
        def resolve(self, key):
            """ Log a warning when acessing any deprecated variable name. """
            if key in deprecated_vars:
                replacement = "page" if key == 'current_page' else "page.{0}".format(key)
                log.warn(
                    "Template variable warning: '{0}' is being deprecated and will not be "
                    "available in a future version. Use '{1}' instead.".format(key, replacement)
                )
            return super(DeprecationContext, self).resolve(key)

    env.context_class = DeprecationContext
    # TODO: end remove DeprecationContext

    env.filters['tojson'] = filters.tojson
    search_index = search.SearchIndex()

    # Force absolute URLs in the nav of error pages and account for the
    # possability that the docs root might be different than the server root.
    # See https://github.com/mkdocs/mkdocs/issues/77
    site_navigation.url_context.force_abs_urls = True
    default_base = site_navigation.url_context.base_path
    site_navigation.url_context.base_path = utils.urlparse(config['site_url']).path
    build_template('404.html', env, config, site_navigation)
    # Reset nav behavior to the default
    site_navigation.url_context.force_abs_urls = False
    site_navigation.url_context.base_path = default_base

    if not build_template('search.html', env, config, site_navigation):
        log.debug("Search is enabled but the theme doesn't contain a "
                  "search.html file. Assuming the theme implements search "
                  "within a modal.")

    build_template('sitemap.xml', env, config, site_navigation)

    build_extra_templates(config['extra_templates'], config, site_navigation)

    # append extra pages not including in [email protected]
    append_extra_pages(config, env, dump_json, site_navigation)

    for page in site_navigation.walk_pages():

        try:

            # When --dirty is used, only build the page if the markdown has been modified since the
            # previous build of the output.
            input_path, output_path = get_complete_paths(config, page)
            if dirty and (utils.modified_time(input_path) < utils.modified_time(output_path)):
                continue

            log.debug("Building page %s", page.input_path)
            build_result = _build_page(page, config, site_navigation, env,
                                       dump_json)
            html_content, table_of_contents, _ = build_result
            search_index.add_entry_from_context(
                page, html_content, table_of_contents)
        except Exception:
            log.error("Error building page %s", page.input_path)
            raise

    search_index = search_index.generate_search_index()
    json_output_path = os.path.join(config['site_dir'], 'mkdocs', 'search_index.json')
    utils.write_file(search_index.encode('utf-8'), json_output_path)