示例#1
0
def load_config(config_file=None, **kwargs):
    """
    Load the configuration for a given file object or name

    The config_file can either be a file object, string or None. If it is None
    the default `mkdocs.yml` filename will loaded.

    Extra kwargs are passed to the configuration to replace any default values
    unless they themselves are None.
    """
    options = kwargs.copy()

    # Filter None values from the options. This usually happens with optional
    # parameters from Click.
    for key, value in options.copy().items():
        if value is None:
            options.pop(key)

    config_file = _open_config_file(config_file)
    options['config_file_path'] = getattr(config_file, 'name', '')

    # Initialise the config with the default schema .
    from mkdocs import config
    cfg = Config(schema=config.DEFAULT_SCHEMA,
                 config_file_path=options['config_file_path'])
    # First load the config file
    cfg.load_file(config_file)
    # Then load the options to overwrite anything in the config.
    cfg.load_dict(options)

    errors, warnings = cfg.validate()

    for config_name, warning in warnings:
        log.warning("Config value: '%s'. Warning: %s", config_name, warning)

    for config_name, error in errors:
        log.error("Config value: '%s'. Error: %s", config_name, error)

    for key, value in cfg.items():
        log.debug("Config value: '%s' = %r", key, value)

    if len(errors) > 0:
        raise exceptions.ConfigurationError(
            "Aborted with {} Configuration Errors!".format(len(errors)))
    elif cfg['strict'] and len(warnings) > 0:
        raise exceptions.ConfigurationError(
            "Aborted with {} Configuration Warnings in 'strict' mode!".format(
                len(warnings)))

    return cfg
示例#2
0
def _mkdocs_config(config: dict) -> mkdocs_config.Config:
    config_instance = mkdocs_config.Config(schema=mkdocs_config.DEFAULT_SCHEMA)
    config_instance.load_dict(config)

    errors, warnings = config_instance.validate()
    if errors:
        raise _mkdocs_exceptions.ConfigurationError(
            "Aborted with {} Configuration Errors!".format(len(errors)))
    elif config.get("strict", False) and warnings:
        raise _mkdocs_exceptions.ConfigurationError(
            "Aborted with {} Configuration Warnings in 'strict' mode!".format(
                len(warnings)))

    config_instance.config_file_path = config["config_file_path"]
    return config_instance
示例#3
0
def _generate_site_navigation(pages_config, url_context, use_directory_urls=True):
    """
    Returns a list of Page and Header instances that represent the
    top level site navigation.
    """
    nav_items = []
    pages = []
    previous = None

    for config_line in pages_config:
        if isinstance(config_line, str):
            path = config_line
            title, child_title = None, None
        elif len(config_line) in (1, 2, 3):
            # Pad any items that don't exist with 'None'
            padded_config = (list(config_line) + [None, None])[:3]
            path, title, child_title = padded_config
        else:
            msg = (
                "Line in 'page' config contained %d items.  "
                "Expected 1, 2 or 3 strings." % len(config_line)
            )
            raise exceptions.ConfigurationError(msg)

        if title is None:
            filename = path.split(os.path.sep)[0]
            title = filename_to_title(filename)

        if child_title is None and os.path.sep in path:
            filename = path.split(os.path.sep)[-1]
            child_title = filename_to_title(filename)

        url = utils.get_url_path(path, use_directory_urls)

        if not child_title:
            # New top level page.
            page = Page(title=title, url=url, path=path, url_context=url_context)
            nav_items.append(page)
        elif not nav_items or (nav_items[-1].title != title):
            # New second level page.
            page = Page(title=child_title, url=url, path=path, url_context=url_context)
            header = Header(title=title, children=[page])
            nav_items.append(header)
            page.ancestors = [header]
        else:
            # Additional second level page.
            page = Page(title=child_title, url=url, path=path, url_context=url_context)
            header = nav_items[-1]
            header.children.append(page)
            page.ancestors = [header]

        # Add in previous and next information.
        if previous:
            page.previous_page = previous
            previous.next_page = page
        previous = page

        pages.append(page)

    return (nav_items, pages)
示例#4
0
def get_themes():
    """Return a dict of theme names and their locations"""

    themes = {}
    builtins = pkg_resources.get_entry_map(dist='mkdocs',
                                           group='mkdocs.themes')

    for theme in pkg_resources.iter_entry_points(group='mkdocs.themes'):

        if theme.name in builtins and theme.dist.key != 'mkdocs':
            raise exceptions.ConfigurationError(
                "The theme {0} is a builtin theme but {1} provides a theme "
                "with the same name".format(theme.name, theme.dist.key))

        elif theme.name in themes:
            multiple_packages = [themes[theme.name].dist.key, theme.dist.key]
            log.warning(
                "The theme %s is provided by the Python packages "
                "'%s'. The one in %s will be used.", theme.name,
                ','.join(multiple_packages), theme.dist.key)

        themes[theme.name] = theme

    themes = dict(
        (name, os.path.dirname(os.path.abspath(theme.load().__file__)))
        for name, theme in themes.items())

    return themes
示例#5
0
文件: base.py 项目: hwmrocker/mkdocs
def _open_config_file(config_file):

    # Default to the standard config filename.
    if config_file is None:
        for possible_file_ending in ('yml', 'yaml'):
            config_file = os.path.abspath(
                'mkdocs.{}'.format(possible_file_ending))
            if os.path.exists(config_file):
                break
        else:
            config_file = os.path.abspath('mkdocs.yml')

    # If closed file descriptor, get file path to reopen later.
    if hasattr(config_file, 'closed') and config_file.closed:
        config_file = config_file.name

    log.debug("Loading configuration file: {}".format(config_file))

    # If it is a string, we can assume it is a path and attempt to open it.
    if isinstance(config_file, str):
        if os.path.exists(config_file):
            config_file = open(config_file, 'rb')
        else:
            raise exceptions.ConfigurationError(
                "Config file '{}' does not exist.".format(config_file))

    # Ensure file descriptor is at begining
    config_file.seek(0)

    return config_file
示例#6
0
 def load_file(self, config_file):
     try:
         return self.load_dict(utils.yaml_load(config_file))
     except YAMLError as e:
         # MkDocs knows and understands ConfigurationErrors
         raise exceptions.ConfigurationError(
             "MkDocs encountered an error parsing the configuration file: {}".format(e)
         )
示例#7
0
def _mkdocs_config(config: dict) -> mkdocs_config.Config:
    config_instance = mkdocs_config.Config(schema=mkdocs_schema())
    config_instance.load_dict(config)

    errors, warnings = config_instance.validate()
    if errors:
        print(errors)
        raise _mkdocs_exceptions.ConfigurationError(
            f"Aborted with {len(errors)} Configuration Errors!")
    elif config.get("strict", False) and warnings:  # pragma: no cover
        print(warnings)
        raise _mkdocs_exceptions.ConfigurationError(
            f"Aborted with {len(warnings)} Configuration Warnings in 'strict' mode!"
        )

    config_instance.config_file_path = config["config_file_path"]
    return config_instance
示例#8
0
 def load_file(self, config_file):
     """ Load config options from the open file descriptor of a YAML file. """
     try:
         return self.load_dict(utils.yaml_load(config_file))
     except YAMLError as e:
         # MkDocs knows and understands ConfigurationErrors
         raise exceptions.ConfigurationError(
             f"MkDocs encountered an error parsing the configuration file: {e}"
         )
示例#9
0
    def load_dict(self, patch):

        if not isinstance(patch, dict):
            raise exceptions.ConfigurationError(
                "The configuration is invalid. The expected type was a key "
                "value mapping (a python dict) but we got an object of type: "
                "{}".format(type(patch)))

        self.user_configs.append(patch)
        self.data.update(patch)
示例#10
0
文件: base.py 项目: palikhov/mkdocs
    def load_dict(self, patch):
        """ Load config options from a dictionary. """

        if not isinstance(patch, dict):
            raise exceptions.ConfigurationError(
                "The configuration is invalid. The expected type was a key "
                "value mapping (a python dict) but we got an object of type: "
                f"{type(patch)}")

        self.user_configs.append(patch)
        self.data.update(patch)
示例#11
0
文件: __init__.py 项目: waylan/mkdocs
def yaml_load(source, loader=None):
    """ Return dict of source YAML file using loader, recursively deep merging inherited parent. """
    Loader = loader or get_yaml_loader()
    result = yaml.load(source, Loader=Loader)
    if result is not None and 'INHERIT' in result:
        relpath = result.pop('INHERIT')
        abspath = os.path.normpath(os.path.join(os.path.dirname(source.name), relpath))
        if not os.path.exists(abspath):
            raise exceptions.ConfigurationError(
                f"Inherited config file '{relpath}' does not exist at '{abspath}'.")
        log.debug(f"Loading inherited configuration file: {abspath}")
        with open(abspath, 'rb') as fd:
            parent = yaml_load(fd, Loader)
        result = merge(parent, result)
    return result
示例#12
0
def _open_config_file(config_file):
    """
    A context manager which yields an open file descriptor ready to be read.

    Accepts a filename as a string, an open or closed file descriptor, or None.
    When None, it defaults to `mkdocs.yml` in the CWD. If a closed file descriptor
    is received, a new file descriptor is opened for the same file.

    The file descriptor is automatically closed when the context manager block is existed.
    """

    # Default to the standard config filename.
    if config_file is None:
        paths_to_try = ['mkdocs.yml', 'mkdocs.yaml']
    # If it is a string, we can assume it is a path and attempt to open it.
    elif isinstance(config_file, str):
        paths_to_try = [config_file]
    # If closed file descriptor, get file path to reopen later.
    elif getattr(config_file, 'closed', False):
        paths_to_try = [config_file.name]
    else:
        paths_to_try = None

    if paths_to_try:
        # config_file is not a file descriptor, so open it as a path.
        for path in paths_to_try:
            path = os.path.abspath(path)
            log.debug(f"Loading configuration file: {path}")
            try:
                config_file = open(path, 'rb')
                break
            except FileNotFoundError:
                continue
        else:
            raise exceptions.ConfigurationError(
                f"Config file '{paths_to_try[0]}' does not exist.")
    else:
        log.debug(f"Loading configuration file: {config_file}")
        # Ensure file descriptor is at beginning
        config_file.seek(0)

    try:
        yield config_file
    finally:
        if hasattr(config_file, 'close'):
            config_file.close()
示例#13
0
def _open_config_file(config_file):

    # Default to the standard config filename.
    if config_file is None:
        config_file = os.path.abspath('mkdocs.yml')

    log.debug("Loading configuration file: %s", config_file)

    # If it is a string, we can assume it is a path and attempt to open it.
    if isinstance(config_file, utils.string_types):
        if os.path.exists(config_file):
            config_file = open(config_file, 'rb')
        else:
            raise exceptions.ConfigurationError(
                "Config file '{0}' does not exist.".format(config_file))

    return config_file
示例#14
0
def get_themes():
    """ Return a dict of all installed themes as (name, entry point) pairs. """

    themes = {}
    builtins = pkg_resources.get_entry_map(dist='mkdocs', group='mkdocs.themes')

    for theme in pkg_resources.iter_entry_points(group='mkdocs.themes'):

        if theme.name in builtins and theme.dist.key != 'mkdocs':
            raise exceptions.ConfigurationError(
                "The theme {0} is a builtin theme but {1} provides a theme "
                "with the same name".format(theme.name, theme.dist.key))

        elif theme.name in themes:
            multiple_packages = [themes[theme.name].dist.key, theme.dist.key]
            log.warning("The theme %s is provided by the Python packages "
                        "'%s'. The one in %s will be used.",
                        theme.name, ','.join(multiple_packages), theme.dist.key)

        themes[theme.name] = theme

    return themes
示例#15
0
def get_themes():
    """ Return a dict of all installed themes as {name: EntryPoint}. """

    themes = {}
    eps = set(importlib_metadata.entry_points(group='mkdocs.themes'))
    builtins = {ep.name for ep in eps if ep.dist.name == 'mkdocs'}

    for theme in eps:

        if theme.name in builtins and theme.dist.name != 'mkdocs':
            raise exceptions.ConfigurationError(
                f"The theme '{theme.name}' is a builtin theme but the package '{theme.dist.name}' "
                "attempts to provide a theme with the same name.")
        elif theme.name in themes:
            log.warning(
                f"A theme named '{theme.name}' is provided by the Python packages '{theme.dist.name}' "
                f"and '{themes[theme.name].dist.name}'. The one in '{theme.dist.name}' will be used."
            )

        themes[theme.name] = theme

    return themes
示例#16
0
def _generate_site_navigation(pages_config, url_context, use_dir_urls=True):
    """
    Returns a list of Page and Header instances that represent the
    top level site navigation.
    """
    nav_items = []
    pages = []

    previous = None

    for config_line in pages_config:

        for page_or_header in _follow(
                config_line, url_context, use_dir_urls):

            if isinstance(page_or_header, Header):

                if page_or_header.is_top_level:
                    nav_items.append(page_or_header)

            elif isinstance(page_or_header, Page):

                if page_or_header.is_top_level:
                    nav_items.append(page_or_header)

                pages.append(page_or_header)

                if previous:
                    page_or_header.previous_page = previous
                    previous.next_page = page_or_header
                previous = page_or_header

    if len(pages) == 0:
        raise exceptions.ConfigurationError(
            "No pages found in the pages config. "
            "Remove it entirely to enable automatic page discovery.")

    return (nav_items, pages)
示例#17
0
def _open_config_file(config_file):
    """
    A context manager which yields an open file descriptor ready to be read.

    Accepts a filename as a string, an open or closed file descriptor, or None.
    When None, it defaults to `mkdocs.yml` in the CWD. If a closed file descriptor
    is received, a new file descriptor is opened for the same file.

    The file descriptor is automaticaly closed when the context manager block is existed.
    """

    # Default to the standard config filename.
    if config_file is None:
        config_file = os.path.abspath('mkdocs.yml')

    # If closed file descriptor, get file path to reopen later.
    if hasattr(config_file, 'closed') and config_file.closed:
        config_file = config_file.name

    log.debug(f"Loading configuration file: {config_file}")

    # If it is a string, we can assume it is a path and attempt to open it.
    if isinstance(config_file, str):
        if os.path.exists(config_file):
            config_file = open(config_file, 'rb')
        else:
            raise exceptions.ConfigurationError(
                f"Config file '{config_file}' does not exist.")

    # Ensure file descriptor is at begining
    config_file.seek(0)
    try:
        yield config_file
    finally:
        if hasattr(config_file, 'close'):
            config_file.close()
示例#18
0
文件: nav.py 项目: vavrusa/mkdocs
def _generate_site_navigation(pages_config,
                              url_context,
                              use_directory_urls=True):
    """
    Returns a list of Page and Header instances that represent the
    top level site navigation.
    """
    nav_items = []
    pages = []
    previous = None

    for config_line in pages_config:
        if isinstance(config_line, str):
            path = os.path.normpath(config_line)
            title, child_title = None, None
        elif len(config_line) in (1, 2, 3):
            # Pad any items that don't exist with 'None'
            padded_config = (list(config_line) + [None, None])[:3]
            path, title, child_title = padded_config
            path = os.path.normpath(path)
        else:
            msg = ("Line in 'page' config contained %d items.  "
                   "Expected 1, 2 or 3 strings." % len(config_line))
            raise exceptions.ConfigurationError(msg)

        # If both the title and child_title are None, then we
        # have just been given a path. If that path contains a /
        # then lets automatically nest it.
        if title is None and child_title is None and os.path.sep in path:
            filename = path.split(os.path.sep)[-1]
            child_title = filename_to_title(filename)

        if title is None:
            filename = path.split(os.path.sep)[0]
            title = filename_to_title(filename)

        # If we don't have a child title but the other title is the same, we
        # should be within a section and the child title needs to be inferred
        # from the filename.
        if len(nav_items) and title == nav_items[
                -1].title == title and child_title is None:
            filename = path.split(os.path.sep)[-1]
            child_title = filename_to_title(filename)

        url = utils.get_url_path(path, use_directory_urls)

        if not child_title:
            # New top level page.
            page = Page(title=title,
                        url=url,
                        path=path,
                        url_context=url_context)
            nav_items.append(page)
        elif not nav_items or (nav_items[-1].title != title):
            # New second level page.
            page = Page(title=child_title,
                        url=url,
                        path=path,
                        url_context=url_context)
            header = Header(title=title, children=[page])
            nav_items.append(header)
            page.ancestors = [header]
        else:
            # Additional second level page.
            page = Page(title=child_title,
                        url=url,
                        path=path,
                        url_context=url_context)
            header = nav_items[-1]
            header.children.append(page)
            page.ancestors = [header]

        # Add in previous and next information.
        if previous:
            page.previous_page = previous
            previous.next_page = page
        previous = page

        pages.append(page)

    return (nav_items, pages)
示例#19
0
def _follow(config_line, url_context, use_dir_urls, header=None, title=None):

    if isinstance(config_line, utils.string_types):
        path = os.path.normpath(config_line)
        page = _path_to_page(path, title, url_context, use_dir_urls)

        if header:
            page.ancestors = header.ancestors + [
                header,
            ]
            header.children.append(page)

        yield page
        raise StopIteration

    elif not isinstance(config_line, dict):
        msg = ("Line in 'page' config is of type {0}, dict or string "
               "expected. Config: {1}").format(type(config_line), config_line)
        raise exceptions.ConfigurationError(msg)

    if len(config_line) > 1:
        raise exceptions.ConfigurationError(
            "Page configs should be in the format 'name: markdown.md'. The "
            "config contains an invalid entry: {0}".format(config_line))
    elif len(config_line) == 0:
        log.warning("Ignoring empty line in the pages config.")
        raise StopIteration

    next_cat_or_title, subpages_or_path = next(iter(config_line.items()))

    if isinstance(subpages_or_path, utils.string_types):
        path = subpages_or_path
        for sub in _follow(path,
                           url_context,
                           use_dir_urls,
                           header=header,
                           title=next_cat_or_title):
            yield sub
        raise StopIteration

    elif not isinstance(subpages_or_path, list):
        msg = ("Line in 'page' config is of type {0}, list or string "
               "expected for sub pages. Config: {1}").format(
                   type(config_line), config_line)
        raise exceptions.ConfigurationError(msg)

    subpages = subpages_or_path

    if len(subpages) and isinstance(
            subpages[0],
            utils.string_types) and subpages[0].endswith('index.md'):
        # The first child is an index page. Use it as the URL of the header.
        path = os.path.normpath(subpages.pop(0))
        url = utils.get_url_path(path, use_dir_urls)
        next_header = HeaderPage(next_cat_or_title,
                                 url,
                                 path,
                                 url_context,
                                 children=[])
    else:
        next_header = Header(title=next_cat_or_title, children=[])

    if header:
        next_header.ancestors = [header]
        header.children.append(next_header)
    yield next_header

    for subpage in subpages:
        for sub in _follow(subpage, url_context, use_dir_urls, next_header):
            yield sub