Source code for rstcheck_core.inline_config

"""Inline config comment functionality."""
import functools
import logging
import re
import typing as t

from . import _compat as _t, types


logger = logging.getLogger(__name__)


RSTCHECK_CONFIG_COMMENT_REGEX = re.compile(r"\.\. rstcheck: (.*)=(.*)$")
VALID_INLINE_CONFIG_KEYS = (
    "ignore-directives",
    "ignore-roles",
    "ignore-substitutions",
    "ignore-languages",
)
ValidInlineConfigKeys = t.Union[
    _t.Literal["ignore-directives"],
    _t.Literal["ignore-roles"],
    _t.Literal["ignore-substitutions"],
    _t.Literal["ignore-languages"],
]

RSTCHECK_FLOW_CONTROL_COMMENT_REGEX = re.compile(r"\.\. rstcheck: ([a-z-]*)$")
VALID_INLINE_FLOW_CONTROLS = ("ignore-next-code-block",)


[docs]@functools.lru_cache() def get_inline_config_from_source( source: str, source_origin: types.SourceFileOrString, warn_unknown_settings: bool = False ) -> t.List[types.InlineConfig]: """Get rstcheck inline configs from source. Unknown configs are ignored. :param source: Source to get config from :param source_origin: Origin of the source with the inline ignore comments :param warn_unknown_settings: If a warning should be logged on unknown settings; defaults to :py:obj:`False` :return: A list of inline configs """ configs: t.List[types.InlineConfig] = [] for idx, line in enumerate(source.splitlines()): match = RSTCHECK_CONFIG_COMMENT_REGEX.search(line) if match is None: continue key = match.group(1).strip() value = match.group(2).strip() if key not in VALID_INLINE_CONFIG_KEYS: if warn_unknown_settings: logger.warning( f"Unknown inline config '{key}' found. " f"Source: '{source_origin}' at line {idx + 1}" ) continue configs.append(types.InlineConfig(key=key, value=value)) return configs
def _filter_config_and_split_values( target_config: ValidInlineConfigKeys, source: str, source_origin: types.SourceFileOrString, warn_unknown_settings: bool = False, ) -> t.Generator[str, None, None]: """Get specified configs and comma split them. :param target_config: Config target to filter for :param source: Source to get config from :param source_origin: Origin of the source with the inline ignore comments :param warn_unknown_settings: If a warning should be logged on unknown settings; defaults to :py:obj:`False` :return: None :yield: Single values for the ``target_config`` """ inline_configs = get_inline_config_from_source(source, source_origin, warn_unknown_settings) for inline_config in inline_configs: if inline_config["key"] == target_config: for language in inline_config["value"].split(","): yield language.strip()
[docs]def find_ignored_directives( source: str, source_origin: types.SourceFileOrString, warn_unknown_settings: bool = False ) -> t.Generator[str, None, None]: """Search the rst source for rstcheck inline ignore-directives comments. Directives are ignored via comment. For example, to ignore directive1, directive2, and directive3: .. testsetup:: from rstcheck_core.inline_config import find_ignored_directives >>> list(find_ignored_directives(''' ... Example ... ======= ... ... .. rstcheck: ignore-directives=directive1,directive3 ... ... .. rstcheck: ignore-directives=directive2 ... ''', "<string>")) ['directive1', 'directive3', 'directive2'] :param source: Rst source code :param source_origin: Origin of the source with the inline ignore comments :return: None :yield: Found directives to ignore """ yield from _filter_config_and_split_values( "ignore-directives", source, source_origin, warn_unknown_settings )
[docs]def find_ignored_roles( source: str, source_origin: types.SourceFileOrString, warn_unknown_settings: bool = False ) -> t.Generator[str, None, None]: """Search the rst source for rstcheck inline ignore-roles comments. Roles are ignored via comment. For example, to ignore role1, role2, and role3: .. testsetup:: from rstcheck_core.inline_config import find_ignored_roles >>> list(find_ignored_roles(''' ... Example ... ======= ... ... .. rstcheck: ignore-roles=role1,role3 ... ... .. rstcheck: ignore-roles=role2 ... ''', "<string>")) ['role1', 'role3', 'role2'] :param source: Rst source code :param source_origin: Origin of the source with the inline ignore comments :return: None :yield: Found roles to ignore """ yield from _filter_config_and_split_values( "ignore-roles", source, source_origin, warn_unknown_settings )
[docs]def find_ignored_substitutions( source: str, source_origin: types.SourceFileOrString, warn_unknown_settings: bool = False ) -> t.Generator[str, None, None]: """Search the rst source for rstcheck inline ignore-substitutions comments. Substitutions are ignored via comment. For example, to ignore substitution1, substitution2, and substitution3: .. testsetup:: from rstcheck_core.inline_config import find_ignored_substitutions >>> list(find_ignored_substitutions(''' ... Example ... ======= ... ... .. rstcheck: ignore-substitutions=substitution1,substitution3 ... ... .. rstcheck: ignore-substitutions=substitution2 ... ''', "<string>")) ['substitution1', 'substitution3', 'substitution2'] :param source: Rst source code :param source_origin: Origin of the source with the inline ignore comments :return: None :yield: Found substitutions to ignore """ yield from _filter_config_and_split_values( "ignore-substitutions", source, source_origin, warn_unknown_settings )
[docs]def find_ignored_languages( source: str, source_origin: types.SourceFileOrString, warn_unknown_settings: bool = False ) -> t.Generator[str, None, None]: """Search the rst source for rstcheck inline ignore-languages comments. Languages are ignored via comment. For example, to ignore C++, JSON, and Python: .. testsetup:: from rstcheck_core.inline_config import find_ignored_languages >>> list(find_ignored_languages(''' ... Example ... ======= ... ... .. rstcheck: ignore-languages=cpp,json ... ... .. rstcheck: ignore-languages=python ... ''', "<string>")) ['cpp', 'json', 'python'] :param source: Rst source code :param source_origin: Origin of the source with the inline ignore comments :return: None :yield: Found languages to ignore """ yield from _filter_config_and_split_values( "ignore-languages", source, source_origin, warn_unknown_settings )
[docs]@functools.lru_cache() def get_inline_flow_control_from_source( source: str, source_origin: types.SourceFileOrString, warn_unknown_settings: bool = False ) -> t.List[types.InlineFlowControl]: """Get rstcheck inline flow control from source. Unknown flow controls are ignored. :param source: Source to get config from :param source_origin: Origin of the source with the inline flow control :param warn_unknown_settings: If a warning should be logged on unknown settings; defaults to :py:obj:`False` :return: A list of inline flow controls """ configs: t.List[types.InlineFlowControl] = [] for idx, line in enumerate(source.splitlines()): match = RSTCHECK_FLOW_CONTROL_COMMENT_REGEX.search(line) if match is None: continue value = match.group(1).strip() line_number = idx + 1 if value not in VALID_INLINE_FLOW_CONTROLS: if warn_unknown_settings: logger.warning( f"Unknown inline flow control '{value}' found. " f"Source: '{source_origin}' at line {line_number}" ) continue configs.append(types.InlineFlowControl(value=value, line_number=line_number)) return configs
[docs]def find_code_block_ignore_lines( source: str, source_origin: types.SourceFileOrString, warn_unknown_settings: bool = False, ) -> t.Generator[int, None, None]: """Get lines of ``ignore-next-code-block`` flow control comments. :param source: Source to get config from :param source_origin: Origin of the source with the inline ignore comments :param warn_unknown_settings: If a warning should be logged on unknown settings; defaults to :py:obj:`False` :return: None :yield: Single values for the ``target_config`` """ flow_controls = get_inline_flow_control_from_source( source, source_origin, warn_unknown_settings ) for flow_control in flow_controls: if flow_control["value"] == "ignore-next-code-block": yield flow_control["line_number"]