def load_config(filename='mkdocs.yml', options=None): options = options or {} if 'config' in options: filename = options.pop('config') if not os.path.exists(filename): raise ConfigurationError("Config file '%s' does not exist." % filename) with open(filename, 'r') as fp: user_config = yaml.load(fp) if not isinstance(user_config, dict): raise ConfigurationError("The mkdocs.yml file is invalid. See http://www.mkdocs.org/user-guide/configuration/ for more information.") user_config.update(options) return validate_config(user_config)
def on_config(self, config: Config, **kwargs) -> Config: """ Hook for the [`on_config` event](https://www.mkdocs.org/user-guide/plugins/#on_config). In this hook, we instantiate our [`MkdocstringsExtension`][mkdocstrings.extension.MkdocstringsExtension] and add it to the list of Markdown extensions used by `mkdocs`. We pass this plugin's configuration dictionary to the extension when instantiating it (it will need it later when processing markdown to get handlers and their global configurations). """ if not config["site_url"]: raise ConfigurationError( "mkdocstrings.plugin: configuration item 'site_url' is required for cross-references" ) log.debug("mkdocstrings.plugin: Adding extension to the list") theme_name = None if config["theme"].name is None: theme_name = os.path.dirname(config["theme"].dirs[0]) else: theme_name = config["theme"].name extension_config = { "theme_name": theme_name, "mdx": config["markdown_extensions"], "mdx_configs": config["mdx_configs"], "mkdocstrings": self.config, } self.mkdocstrings_extension = MkdocstringsExtension( config=extension_config) config["markdown_extensions"].append(self.mkdocstrings_extension) return config
def load_config(filename='mkdocs.yml', options=None): options = options or {} if 'config' in options: filename = options['config'] if not os.path.exists(filename): raise ConfigurationError("Config file '%s' does not exist." % filename) with open(filename, 'r') as fp: user_config = yaml.load(fp) user_config.update(options) return validate_config(user_config)
def on_pre_build(self, config, **kwargs): """Validates plugin user configuration input Args: config (dict): plugin configuration """ if self.config.get("toc_depth", 0) > 6: raise ConfigurationError( "toc_depth is set to %s, but max is 6. Update plugin settings in mkdocs.yml." % self.config.get("toc_depth"))
def load_config(config_file=None, **kwargs): options = dict((k, v) for k, v in kwargs.items() if v is not None) if config_file is None: config_file = 'mkdocs.yml' if os.path.exists(config_file): config_file = open(config_file, 'rb') else: raise ConfigurationError( "Config file '{0}' does not exist.".format(config_file)) options['config'] = config_file user_config = utils.yaml_load(config_file) if not isinstance(user_config, dict): raise ConfigurationError( "The mkdocs.yml file is invalid. For more information see: " "http://www.mkdocs.org/user-guide/configuration/ ({0})".format( user_config)) user_config.update(options) return validate_config(user_config)
def on_config(self, config): """ See https://www.mkdocs.org/user-guide/plugins/#on_config Args: config Returns: Config """ plugins = [p for p in config.get("plugins")] for post_load_plugin in ["macros", "markdownextradata"]: if post_load_plugin in plugins: if plugins.index("table-reader") > plugins.index(post_load_plugin): raise ConfigurationError(f"[table-reader]: Incompatible plugin order: Define 'table-reader' before '{post_load_plugin}' in your mkdocs.yml.")
def on_config(self, config): """ See https://www.mkdocs.org/user-guide/plugins/#on_config Args: config Returns: Config """ plugins = [p for p in config.get("plugins")] if "macros" in plugins: if plugins.index("table-reader") > plugins.index("macros"): raise ConfigurationError( "[table-reader]: Incompatible plugin order: Define 'table-reader' before 'macros' in your mkdocs.yml." )
def pages_compat_shim(original_pages): """ Support legacy pages configuration Re-write the pages config fron MkDocs <=0.12 to match the new nested structure added in 0.13. Given a pages configuration in the old style of: pages: - ['index.md', 'Home'] - ['user-guide/writing-your-docs.md', 'User Guide'] - ['user-guide/styling-your-docs.md', 'User Guide'] - ['about/license.md', 'About', 'License'] - ['about/release-notes.md', 'About'] - ['help/contributing.md', 'Help', 'Contributing'] - ['support.md'] - ['cli.md', 'CLI Guide'] Rewrite it to look like: pages: - Home: index.md - User Guide: - user-guide/writing-your-docs.md - user-guide/styling-your-docs.md - About: - License: about/license.md - about/release-notes.md - Help: - Contributing: about/contributing.md - support.md - CLI Guide: cli.md TODO: Remove in 1.0 """ log.warning("The pages config in the mkdocs.yml uses the deprecated " "structure. This will be removed in the next release of " "MkDocs. See for details on updating: " "http://www.mkdocs.org/about/release-notes/") new_pages = [] for config_line in original_pages: if isinstance(config_line, six.string_types): config_line = [ config_line, ] if len(config_line) not in (1, 2, 3): msg = ("Line in 'page' config contained {0} items. In Line {1}. " "Expected 1, 2 or 3 strings.".format( config_line, len(config_line))) raise ConfigurationError(msg) # First we need to pad out the config line as it could contain # 1-3 items. path, category, title = (list(config_line) + [None, None])[:3] if len(new_pages) > 0: # Get the previous top-level page so we can see if the category # matches up with the one we have now. prev_cat, subpages = next(iter(new_pages[-1].items())) else: # We are on the first page prev_cat, subpages = None, [] # If the category is different, add a new top level category. If the # previous category is None, the it's another top level one too. if prev_cat is None or prev_cat != category: subpages = [] new_pages.append({category: subpages}) # Add the current page to the determined category. subpages.append({title: path}) # We need to do a bit of cleaning up to match the new structure. In the # above example, pages can either be `- file.md` or `- Title: file.md`. # For pages without a title we currently have `- None: file.md` - so we # need to remove those Nones by changing from a dict to just a string with # the path. for i, category in enumerate(new_pages): # Categories are a dictionary with one key as the name and the value # is a list of pages. So, grab that from the dict. category, pages = next(iter(category.items())) # If we only have one page, then we can assume it is a top level # category and no further nesting is required unless that single page # has a title itself, if len(pages) == 1: title, path = pages.pop().popitem() # If we have a title, it should be a sub page if title is not None: pages.append({title: path}) # if we have a category, but no title it should be a top-level page elif category is not None: new_pages[i] = {category: path} # if we have no category or title, it must be a top level page with # an atomatic title. else: new_pages[i] = path else: # We have more than one page, so the category is valid. We just # need to iterate through and convert any {None: path} dicts to # be just the path string. for j, page in enumerate(pages): title, path = page.popitem() if title: pages[j] = {title: path} else: pages[j] = path return new_pages
def validate_config(user_config): config = DEFAULT_CONFIG.copy() theme_in_config = 'theme' in user_config config.update(user_config) if not config['site_name']: raise ConfigurationError("Config must contain 'site_name' setting.") # Validate that the docs_dir and site_dir don't contain the # other as this will lead to copying back and forth on each # and eventually make a deep nested mess. abs_site_dir = os.path.abspath(config['site_dir']) abs_docs_dir = os.path.abspath(config['docs_dir']) if abs_docs_dir.startswith(abs_site_dir): raise ConfigurationError( "The 'docs_dir' can't be within the 'site_dir'.") elif abs_site_dir.startswith(abs_docs_dir): raise ConfigurationError( "The 'site_dir' can't be within the 'docs_dir'.") # If not specified, then the 'pages' config simply includes all # markdown files in the docs dir, without generating any header items # for them. pages = [] extra_css = [] extra_javascript = [] for (dirpath, dirnames, filenames) in os.walk(config['docs_dir']): for filename in sorted(filenames): fullpath = os.path.join(dirpath, filename) relpath = os.path.relpath(fullpath, config['docs_dir']) if utils.is_markdown_file(filename): # index pages should always be the first listed page. if os.path.splitext(relpath)[0] == 'index': pages.insert(0, relpath) else: pages.append(relpath) elif utils.is_css_file(filename): extra_css.append(relpath) elif utils.is_javascript_file(filename): extra_javascript.append(relpath) if config['pages'] is None: config['pages'] = pages if config['extra_css'] is None: config['extra_css'] = extra_css if config['extra_javascript'] is None: config['extra_javascript'] = extra_javascript package_dir = os.path.dirname(__file__) theme_dir = [os.path.join(package_dir, 'themes', config['theme'])] if config['theme_dir'] is not None: # If the user has given us a custom theme but not a # builtin theme name then we don't want to merge them. if not theme_in_config: theme_dir = [] theme_dir.insert(0, config['theme_dir']) config['theme_dir'] = theme_dir if config['repo_url'] is not None and config['repo_name'] is None: repo_host = 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() if config['include_next_prev'] is None: config['include_next_prev'] = len(config['pages']) > 1 if config['include_nav'] is None: config['include_nav'] = len(config['pages']) > 1 # To Do: # The docs dir must exist. # The theme dir must exist. # Ensure 'theme' is one of 'mkdocs', 'readthedocs', 'custom' # A homepage 'index' must exist. # The theme 'base.html' file must exist. # Cannot set repo_name without setting repo_url. # Cannot set 'include_next_prev: true' when only one page exists. # Cannot set 'include_nav: true' when only one page exists. # Error if any config keys provided that are not in the DEFAULT_CONFIG. return config
def validate_config(user_config): config = DEFAULT_CONFIG.copy() theme_in_config = 'theme' in user_config config.update(user_config) if not config['site_name']: raise ConfigurationError("Config must contain 'site_name' setting.") # Validate that the docs_dir and site_dir don't contain the # other as this will lead to copying back and forth on each # and eventually make a deep nested mess. abs_site_dir = os.path.abspath(config['site_dir']) abs_docs_dir = os.path.abspath(config['docs_dir']) if abs_docs_dir.startswith(abs_site_dir): raise ConfigurationError( "The 'docs_dir' can't be within the 'site_dir'.") elif abs_site_dir.startswith(abs_docs_dir): raise ConfigurationError( "The 'site_dir' can't be within the 'docs_dir'.") # If not specified, then the 'pages' config simply includes all # markdown files in the docs dir, without generating any header items # for them. pages = [] extra_css = [] extra_javascript = [] for (dirpath, _, filenames) in os.walk(config['docs_dir']): for filename in sorted(filenames): fullpath = os.path.join(dirpath, filename) relpath = os.path.normpath( os.path.relpath(fullpath, config['docs_dir'])) if utils.is_markdown_file(filename): # index pages should always be the first listed page. if os.path.splitext(relpath)[0] == 'index': pages.insert(0, relpath) else: pages.append(relpath) elif utils.is_css_file(filename): extra_css.append(relpath) elif utils.is_javascript_file(filename): extra_javascript.append(relpath) if config['pages'] is None: config['pages'] = pages else: """ If the user has provided the pages config, then iterate through and check for Windows style paths. If they are found, output a warning and continue. """ for page_config in config['pages']: if isinstance(page_config, str): path = page_config elif len(page_config) in (1, 2, 3): path = page_config[0] if ntpath.sep in path: log.warning("The config path contains Windows style paths (\\ " " backward slash) and will have comparability " "issues if it is used on another platform.") break if config['extra_css'] is None: config['extra_css'] = extra_css if config['extra_javascript'] is None: config['extra_javascript'] = extra_javascript package_dir = os.path.dirname(__file__) theme_dir = [ os.path.join(package_dir, 'themes', config['theme']), ] if config['theme_dir'] is not None: # If the user has given us a custom theme but not a # builtin theme name then we don't want to merge them. if not theme_in_config: theme_dir = [] theme_dir.insert(0, config['theme_dir']) config['theme_dir'] = theme_dir # Add the search assets to the theme_dir, this means that # they will then we copied into the output directory but can # be overwritten by themes if needed. config['theme_dir'].append(os.path.join(package_dir, 'assets', 'search')) if config['repo_url'] is not None and config['repo_name'] is None: repo_host = 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() if config['include_next_prev'] is None: config['include_next_prev'] = len(config['pages']) > 1 if config['include_nav'] is None: config['include_nav'] = len(config['pages']) > 1 # To Do: # The docs dir must exist. # The theme dir must exist. # Ensure 'theme' is one of 'mkdocs', 'readthedocs', 'custom' # A homepage 'index' must exist. # The theme 'base.html' file must exist. # Cannot set repo_name without setting repo_url. # Cannot set 'include_next_prev: true' when only one page exists. # Cannot set 'include_nav: true' when only one page exists. # Error if any config keys provided that are not in the DEFAULT_CONFIG. return config
def on_config(self, config: config_options.Config, **kwargs) -> Dict[str, Any]: """ Determine which locale to use. The config event is the first event called on build and is run immediately after the user configuration is loaded and validated. Any alterations to the config should be made here. https://www.mkdocs.org/user-guide/plugins/#on_config Args: config (dict): global configuration object Returns: dict: global configuration object """ if not self.config.get('enabled'): return config assert self.config['type'] in [ "date", "datetime", "iso_date", "iso_datetime", "timeago", "custom" ] self.util = Util(config=self.config) # Save last commit timestamp for entire site self.last_site_revision_timestamp = self.util.get_git_commit_timestamp( config.get('docs_dir')) # Get locale from plugin configuration plugin_locale = self.config.get("locale", None) # theme locale if "theme" in config and "locale" in config.get("theme"): custom_theme = config.get("theme") theme_locale = custom_theme._vars.get("locale") logging.debug("Locale '%s' extracted from the custom theme: '%s'" % (theme_locale, custom_theme.name)) elif "theme" in config and "language" in config.get("theme"): custom_theme = config.get("theme") theme_locale = custom_theme._vars.get("language") logging.debug("Locale '%s' extracted from the custom theme: '%s'" % (theme_locale, custom_theme.name)) else: theme_locale = None logging.debug( "No locale found in theme configuration (or no custom theme set)" ) # First prio: plugin locale if plugin_locale: locale_set = plugin_locale logging.debug("Using locale from plugin configuration: %s" % locale_set) # Second prio: theme locale elif theme_locale: locale_set = theme_locale logging.debug( "Locale not set in plugin. Fallback to theme configuration: %s" % locale_set) # Lastly, fallback is English else: locale_set = "en" logging.debug("No locale set. Fallback to: %s" % locale_set) # Validate locale locale_set = str(locale_set) assert len( locale_set ) == 2, "locale must be a 2 letter code, see https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes" # set locale also in plugin configuration self.config["locale"] = locale_set # Add pointers to support files for timeago.js if self.config.get("type") == "timeago": config["extra_javascript"] = ["js/timeago_mkdocs_material.js" ] + config["extra_javascript"] config["extra_javascript"] = ["js/timeago.min.js" ] + config["extra_javascript"] config["extra_css"] = ["css/timeago.css"] + config["extra_css"] # Compatibility with mkdocs-static-i18n plugins = [*OrderedDict(config["plugins"])] if "i18n" in plugins: if plugins.index("git-revision-date-localized") < plugins.index( "i18n"): msg = "[git-revision-date-localized] should be defined after the i18n plugin in your mkdocs.yml file. " msg += "This is because i18n adds a 'locale' variable to markdown pages that this plugin supports." raise ConfigurationError(msg) return config
def check_position(plugin, plugins): if plugin in plugins: if plugins.index("enumerate-headings") < plugins.index(plugin): raise ConfigurationError( "[enumerate-headings-plugin] enumerate-headings should be defined after the %s plugin in your mkdocs.yml file" % plugin)