def run_validation(self, value): try: host, port = value.rsplit(':', 1) except Exception: raise ValidationError("Must be a string of format 'IP:PORT'") if host != 'localhost': try: # Validate and normalize IP Address host = str(ipaddress.ip_address(host)) except ValueError as e: raise ValidationError(e) try: port = int(port) except Exception: raise ValidationError("'{}' is not a valid port".format(port)) class Address(namedtuple('Address', 'host port')): def __str__(self): return '{}:{}'.format(self.host, self.port) return Address(host, port)
def post_validation(self, config, key_name): super().post_validation(config, key_name) # 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. if (config['docs_dir'] + os.sep).startswith(config['site_dir'].rstrip(os.sep) + os.sep): raise ValidationError( ("The 'docs_dir' should not be within the 'site_dir' as this " "can mean the source files are overwritten by the output or " "it will be deleted if --clean is passed to mkdocs build." "(site_dir: '{}', docs_dir: '{}')").format( config['site_dir'], config['docs_dir'])) elif (config['site_dir'] + os.sep).startswith(config['docs_dir'].rstrip(os.sep) + os.sep): raise ValidationError( ("The 'site_dir' should not be within the 'docs_dir' as this " "leads to the build directory being copied into itself and " "duplicate nested files in the 'site_dir'." "(site_dir: '{}', docs_dir: '{}')").format( config['site_dir'], config['docs_dir']))
def run_validation(self, value): if not isinstance(value, (list, tuple)): raise ValidationError('Invalid Plugins configuration. Expected a list of plugins') plgins = plugins.PluginCollection() for item in value: if isinstance(item, dict): if len(item) > 1: raise ValidationError('Invalid Plugins configuration') name, cfg = item.popitem() cfg = cfg or {} # Users may define a null (None) config if not isinstance(cfg, dict): raise ValidationError('Invalid config options for ' 'the "{0}" plugin.'.format(name)) item = name else: cfg = {} if not isinstance(item, utils.string_types): raise ValidationError('Invalid Plugins configuration') plgins[item] = self.load_plugin(item, cfg) return plgins
def run_validation(self, value): if not isinstance(value, list): raise ValidationError("Expected a list, got {0}".format( type(value))) if len(value) == 0: return # TODO: Remove in 1.0 config_types = set(type(l) for l in value) if config_types.issubset(set([utils.text_type, dict, str])): return value if config_types.issubset(set([utils.text_type, list, str])): return legacy.pages_compat_shim(value) raise ValidationError("Invalid pages config. {0} {1}".format( config_types, set([ utils.text_type, dict, ])))
def run_validation(self, value): themes = utils.get_theme_names() if value in themes: if value in ['mkdocs', 'readthedocs']: return value self.warnings.append( ("The theme '{0}' will be removed in an upcoming MkDocs " "release. See http://www.mkdocs.org/about/release-notes/ " "for more details").format(value)) return value raise ValidationError("Unrecognised theme.")
def validate(self, value): if value is None and self.default is not None: value = {'name': self.default} if isinstance(value, str): value = {'name': value} themes = utils.get_theme_names() if isinstance(value, dict): if 'name' in value: if value['name'] is None or value['name'] in themes: return value raise ValidationError( "Unrecognised theme name: '{}'. The available installed themes " "are: {}".format(value['name'], ', '.join(themes))) raise ValidationError("No theme name set.") raise ValidationError( 'Invalid type "{}". Expected a string or key/value pairs.'.format( type(value)))
def run_validation(self, value): if not isinstance(value, (list, tuple)): raise ValidationError('Invalid Markdown Extensions configuration') extensions = [] for item in value: if isinstance(item, dict): if len(item) > 1: raise ValidationError( 'Invalid Markdown Extensions configuration') ext, cfg = item.popitem() extensions.append(ext) if cfg is None: continue if not isinstance(cfg, dict): raise ValidationError( 'Invalid config options for Markdown ' "Extension '{0}'.".format(ext)) self.configdata[ext] = cfg elif isinstance(item, utils.string_types): extensions.append(item) else: raise ValidationError( 'Invalid Markdown Extensions configuration') return utils.reduce_list(self.builtins + extensions)
def run_validation(self, value, *, top=True): if isinstance(value, list): for subitem in value: self._validate_nav_item(subitem) if top and not value: value = None elif isinstance(value, dict) and value and not top: # TODO: this should be an error. self.warnings.append(f"Expected nav to be a list, got {self._repr_item(value)}") for subitem in value.values(): self.run_validation(subitem, top=False) elif isinstance(value, str) and not top: pass else: raise ValidationError(f"Expected nav to be a list, got {self._repr_item(value)}") return value
def validate(self, value): """ Perform some initial validation. If the option is empty (None) and isn't required, leave it as such. If it is empty but has a default, use that. Finally, call the run_validation method on the subclass unless. """ if value is None: if self.default is not None: value = self.default elif not self.required: return elif self.required: raise ValidationError("Required configuration not provided.") return self.run_validation(value)
def post_validation(self, config, key_name): theme_config = config[key_name] # TODO: Remove when theme_dir is fully deprecated. if config['theme_dir'] is not None: if 'custom_dir' not in theme_config: # Only pass in 'theme_dir' if it is set and 'custom_dir' is not set. theme_config['custom_dir'] = config['theme_dir'] if not any(['theme' in c for c in config.user_configs]): # If the user did not define a theme, but did define theme_dir, then remove default set in validate. theme_config['name'] = None if not theme_config['name'] and 'custom_dir' not in theme_config: raise ValidationError( "At least one of 'theme.name' or 'theme.custom_dir' must be defined." ) # Ensure custom_dir is an absolute path if 'custom_dir' in theme_config and not os.path.isabs( theme_config['custom_dir']): theme_config['custom_dir'] = os.path.abspath( theme_config['custom_dir']) config[key_name] = theme.Theme(**theme_config)
def pre_validation(self, config, key_name): self.option.pre_validation(config, key_name) if config.get(key_name) is not None: if self.removed: raise ValidationError(self.message.format(key_name)) self.warnings.append(self.message.format(key_name)) if self.moved_to is not None: if '.' not in self.moved_to: target = config target_key = self.moved_to else: move_to, target_key = self.moved_to.rsplit('.', 1) target = config for key in move_to.split('.'): target = target.setdefault(key, {}) if not isinstance(target, dict): # We can't move it for the user return target[target_key] = config.pop(key_name)
def run_validation(self, value): raise ValidationError('For internal use only.')
def run_validation(self, value): if isinstance(value, list): return value else: raise ValidationError("Expected a list, got {0}".format( type(value)))
def parse_locale(locale): try: return Locale.parse(locale, sep='_') except (ValueError, UnknownLocaleError, TypeError) as e: raise ValidationError(f'Invalid value for locale: {str(e)}')
def on_config_event(plugin, config, **kwargs): """Configuration event for 'mkdocs-mdpo-plugin'. * Define properly `lc_messages`, `languages` and `locale_dir` configuration settings. * Loads `mkdocs-mdpo` extension. * Configures md4c extensions accordingly to Python-Markdown extensions. * Stores the Markdown extensions used in the build in the ``extensions.markdown`` property of the plugin instance. * Creates the persistent files cache for the project. """ if plugin.config['lc_messages'] is True: plugin.config['lc_messages'] = 'LC_MESSAGES' elif not plugin.config['lc_messages']: plugin.config['lc_messages'] = '' try: _using_material_theme = config['theme'].name == 'material' except KeyError: _using_material_theme = None # load language selection settings from material or mdpo configuration def _languages_required(): msg = ( 'You must define the languages you will translate the' ' content into using' f"{' either' if _using_material_theme else ' the'}" " 'plugins.mdpo.languages'" ) if _using_material_theme: msg += " or 'extra.alternate'" msg += ( ' configuration setting' f"{'s' if _using_material_theme else ''}." ) return ValidationError(msg) languages = plugin.config.get('languages') if not languages: if _using_material_theme: if 'extra' not in config: raise _languages_required() alternate = config['extra'].get('alternate') if not alternate: raise _languages_required() plugin.config['languages'] = [alt['lang'] for alt in alternate] else: raise _languages_required() else: if not isinstance(languages, list): raise ValidationError( 'Expected "languages" config setting to be a list', ) for i, language in enumerate(languages): if not isinstance(language, str): raise ValidationError( f'Expected "languages[{i}]" config setting to' f' be a string but is {language}', ) default_language = plugin.config.get('default_language') if not default_language: # use mkdocs>=v1.2.0 theme localization setting theme_locale = ( None if 'theme' not in config else config['theme']._vars.get('locale') ) if theme_locale and theme_locale in plugin.config['languages']: plugin.config['default_language'] = theme_locale else: if ( not isinstance(plugin.config['languages'], list) or not plugin.config['languages'] ): raise _languages_required() plugin.config['default_language'] = plugin.config['languages'][0] # ---------------------------------------------------------- # extensions configuration markdown_extensions = config.get('markdown_extensions') # configure MD4C extensions if markdown_extensions: if 'tables' not in markdown_extensions: if 'tables' in plugin.extensions.md4c: plugin.extensions.md4c.remove('tables') else: if 'tables' not in plugin.extensions.md4c: plugin.extensions.md4c.append('tables') if 'wikilinks' not in markdown_extensions: if 'wikilinks' in plugin.extensions.md4c: plugin.extensions.md4c.remove('wikilinks') else: if 'wikilinks' not in plugin.extensions.md4c: plugin.extensions.md4c.append('wikilinks') # spaces after '#' are optional in Python-Markdown for headers, # but the extension 'pymdownx.saneheaders' makes them mandatory if 'pymdownx.saneheaders' in markdown_extensions: if 'permissive_atx_headers' in plugin.extensions.md4c: plugin.extensions.md4c.remove('permissive_atx_headers') else: if 'permissive_atx_headers' not in plugin.extensions.md4c: plugin.extensions.md4c.append('permissive_atx_headers') # 'pymdownx.tasklist' enables 'tasklists' MD4C extentsion if 'pymdownx.tasklist' in markdown_extensions: if 'tasklists' not in plugin.extensions.md4c: plugin.extensions.md4c.append('tasklists') else: if 'tasklists' in plugin.extensions.md4c: plugin.extensions.md4c.remove('tasklists') # 'pymdownx.tilde' enables strikethrough syntax, but only works # if the MD4C extension is disabled if 'pymdownx.tilde' in markdown_extensions: if 'strikethrough' in plugin.extensions.md4c: plugin.extensions.md4c.remove('strikethrough') # configure internal 'mkdocs-mdpo' extension if 'mkdocs-mdpo' in markdown_extensions: # pragma: no cover config['markdown_extensions'].remove('mkdocs-mdpo') config['markdown_extensions'].append('mkdocs-mdpo') # install a i18n aware version of sitemap.xml if not provided by the user theme_custom_dir = config['theme']._vars.get('custom_dir', '.') if not os.path.isfile(os.path.join(theme_custom_dir, 'sitemap.xml')): custom_sitemap_dir = os.path.join( os.path.dirname(installation_path), 'custom_mdpo_sitemap', ) config['theme'].dirs.insert(0, custom_sitemap_dir) # check that cross language search configuration is valid if plugin.config.get('cross_language_search') is False: if 'search' not in config['plugins']: raise ValidationError( '"cross_language_search" setting is disabled but' ' no "search" plugin has been added to "plugins"', ) else: plugin_names = [p for p in config['plugins']] if plugin_names.index('search') > plugin_names.index('mdpo'): raise ValidationError( '"search" plugin must be placed before "mdpo"' ' plugin if you want to disable "cross_language_search".', ) elif not _using_material_theme and 'search' in config['plugins']: # if cross_language_search is active, add all languages to 'search' # plugin configuration lunr_languages = get_lunr_languages() search_langs = config['plugins']['search'].config.get('lang', []) for language in plugin.config['languages']: if language in lunr_languages: if language not in search_langs: config['plugins']['search'].config['lang'].append( language, ) logger.debug( f"[mdpo] Adding '{language}' to" " 'plugins.search.lang' option", ) elif language != 'en': # English does not need steemer logger.info( f"[mdpo] Language '{language}' is not supported by" " lunr.js, not adding it to 'plugins.search.lang'" ' option', ) # check that minimum translated messages required for each language # is a valid value min_translated = plugin.config.get('min_translated_messages') if min_translated is not None: try: if '%' in str(min_translated): min_translated = max(-100, -float(min_translated.strip('%'))) else: min_translated = max(int(min_translated), 0) except Exception: raise ValidationError( f"The value '{min_translated}' for" " 'min_translated_messages' config setting" ' is not a valid percentage or number.', ) else: plugin.config['min_translated_messages'] = min_translated # check that 'exclude' contains a valid list exclude = plugin.config.get('exclude', []) if not isinstance(exclude, list): raise ValidationError( 'Expected mdpo\'s "exclude" setting to be a list, but found' f' the value {str(exclude)} of type {type(exclude).__name__}', ) else: for i, path in enumerate(exclude): if not isinstance(path, str): raise ValidationError( f'Expected mdpo\'s setting "exclude[{i}]" value to' f' be a string, but found the value {str(path)} of' f' type {type(path).__name__}', ) plugin.config['exclude'] = exclude # translation of configuration settings valid_translate_settings = ['site_name', 'site_description'] for setting in plugin.config.get('translate', []): if setting not in valid_translate_settings: valid_translate_settings_readable = ' and '.join([ f"'{opt}'" for opt in valid_translate_settings ]) raise ValidationError( f"The setting '{setting}' is not supported for" " 'plugins.mdpo.translate' config setting. Valid settings" f' are {valid_translate_settings_readable}', ) elif ( setting == 'site_description' and not config.get('site_description') ): logger.warn( '[mdpo] "site_description" is configured to be translated' ' but was not defined in mkdocs.yml', ) plugin.config['translate'].remove('site_description') # store reference in plugin to markdown_extensions for later usage plugin.extensions.markdown = markdown_extensions
def run_validation(self, value): if isinstance(value, list): return list(os.path.normcase(path) for path in value) else: raise ValidationError("Expected a list, got {0}".format( type(value)))