示例#1
0
文件: style.py 项目: joegnis/nitpick
    def include_multiple_styles(self, chosen_styles: StrOrList) -> None:
        """Include a list of styles (or just one) into this style tree."""
        style_uris = [chosen_styles] if isinstance(
            chosen_styles, str) else chosen_styles  # type: List[str]
        for style_uri in style_uris:
            style_path = self.get_style_path(style_uri)  # type: Optional[Path]
            if not style_path:
                continue

            toml = TomlFormat(path=style_path)
            try:
                toml_dict = toml.as_data
            except TomlDecodeError as err:
                Nitpick.current_app().add_style_error(
                    style_path.name, pretty_exception(err, "Invalid TOML"))
                # If the TOML itself could not be parsed, we can't go on
                return

            try:
                display_name = str(
                    style_path.relative_to(Nitpick.current_app().root_dir))
            except ValueError:
                display_name = style_uri
            self.validate_style(display_name, toml_dict)
            self._all_styles.add(toml_dict)

            sub_styles = search_dict(NITPICK_STYLES_INCLUDE_JMEX, toml_dict,
                                     [])  # type: StrOrList
            if sub_styles:
                self.include_multiple_styles(sub_styles)
示例#2
0
文件: base.py 项目: joegnis/nitpick
    def check_exists(self) -> YieldFlake8Error:
        """Check if the file should exist."""
        for _ in self.multiple_files:
            config_data_exists = bool(self.file_dict or self.nitpick_file_dict)
            should_exist = Nitpick.current_app(
            ).config.nitpick_files_section.get(
                TomlFormat.group_name_for(self.file_name), True)  # type: bool
            file_exists = self.file_path.exists()

            if config_data_exists and not file_exists:
                suggestion = self.suggest_initial_contents()
                phrases = [" was not found"]
                message = Nitpick.current_app(
                ).config.nitpick_files_section.get(self.file_name)
                if message and isinstance(message, str):
                    phrases.append(message)
                if suggestion:
                    phrases.append("Create it with this content:")
                yield self.flake8_error(1, ". ".join(phrases), suggestion)
            elif not should_exist and file_exists:
                # Only display this message if the style is valid.
                if not Nitpick.current_app().style_errors:
                    yield self.flake8_error(2, " should be deleted")
            elif file_exists and config_data_exists:
                yield from self.check_rules()
示例#3
0
文件: style.py 项目: joegnis/nitpick
 def validate_style(self, style_file_name: str, original_data: JsonDict):
     """Validate a style file (TOML) against a Marshmallow schema."""
     self.rebuild_dynamic_schema(original_data)
     style_errors = self._dynamic_schema_class().validate(original_data)
     if style_errors:
         Nitpick.current_app().add_style_error(
             style_file_name, "Invalid config:",
             flatten_marshmallow_errors(style_errors))
示例#4
0
文件: plugin.py 项目: joegnis/nitpick
    def parse_options(option_manager: OptionManager, options, args):  # pylint: disable=unused-argument
        """Create the Nitpick app, set logging from the verbose flags, set offline mode.

        This function is called only once by flake8, so it's a good place to create the app.
        """
        log_mapping = {1: logging.INFO, 2: logging.DEBUG}
        logging.basicConfig(level=log_mapping.get(options.verbose, logging.WARNING))

        Nitpick.create_app(offline=bool(options.nitpick_offline or Nitpick.get_env(Nitpick.Flags.OFFLINE)))
        LOGGER.info("Offline mode: %s", Nitpick.current_app().offline)
示例#5
0
def test_offline_flag_env_variable(tmpdir):
    """Test if the offline flag or environment variable was set."""
    with tmpdir.as_cwd():
        _call_main([])
        assert Nitpick.current_app().offline is False

        _call_main(["--nitpick-offline"])
        assert Nitpick.current_app().offline is True

        os.environ["NITPICK_OFFLINE"] = "1"
        _call_main([])
        assert Nitpick.current_app().offline is True
示例#6
0
def test_flag_format_env_variable():
    """Test flag formatting and env variable."""
    class OtherFlags(Enum):
        """Some flags to be used on the assertions below."""

        MULTI_WORD = 1
        SOME_OPTION = 2

    assert Nitpick.format_flag(OtherFlags.MULTI_WORD) == "--nitpick-multi-word"
    os.environ["NITPICK_SOME_OPTION"] = "something"
    assert Nitpick.format_env(OtherFlags.SOME_OPTION) == "NITPICK_SOME_OPTION"
    assert Nitpick.get_env(OtherFlags.SOME_OPTION) == "something"
    assert Nitpick.get_env(OtherFlags.MULTI_WORD) == ""
示例#7
0
文件: config.py 项目: joegnis/nitpick
    def merge_styles(self) -> YieldFlake8Error:
        """Merge one or multiple style files."""
        if not self.validate_pyproject_tool_nitpick():
            # If the project is misconfigured, don't even continue.
            return

        configured_styles = self.tool_nitpick_dict.get("style",
                                                       "")  # type: StrOrList
        style = Style()
        style.find_initial_styles(configured_styles)

        self.style_dict = style.merge_toml_dict()
        if not Nitpick.current_app().style_errors:
            # Don't show duplicated errors: if there are style errors already, don't validate the merged style.
            style.validate_style(MERGED_STYLE_TOML, self.style_dict)

        from nitpick.plugin import NitpickChecker  # pylint: disable=import-outside-toplevel

        minimum_version = search_dict(NITPICK_MINIMUM_VERSION_JMEX,
                                      self.style_dict, None)
        if minimum_version and version_to_tuple(
                NitpickChecker.version) < version_to_tuple(minimum_version):
            yield self.flake8_error(
                3,
                "The style file you're using requires {}>={}".format(
                    PROJECT_NAME, minimum_version) +
                " (you have {}). Please upgrade".format(
                    NitpickChecker.version),
            )

        self.nitpick_section = self.style_dict.get("nitpick", {})
        self.nitpick_files_section = self.nitpick_section.get("files", {})
示例#8
0
文件: base.py 项目: joegnis/nitpick
    def _set_current_data(self, file_name: str) -> None:
        """Set data for the current file name, either if there are multiple or single files."""
        if self.has_multiple_files:
            self.file_name = file_name

        self.error_prefix = "File {}".format(self.file_name)
        self.file_path = Nitpick.current_app().root_dir / self.file_name

        # Configuration for this file as a TOML dict, taken from the style file.
        self.file_dict = Nitpick.current_app().config.style_dict.get(
            TomlFormat.group_name_for(self.file_name), {})

        # Nitpick configuration for this file as a TOML dict, taken from the style file.
        self.nitpick_file_dict = search_dict(
            'files."{}"'.format(self.file_name),
            Nitpick.current_app().config.nitpick_section, {})
示例#9
0
文件: plugin.py 项目: joegnis/nitpick
    def check_files(self, present: bool) -> YieldFlake8Error:
        """Check files that should be present or absent."""
        key = "present" if present else "absent"
        message = "exist" if present else "be deleted"
        absent = not present
        for file_name, extra_message in Nitpick.current_app().config.nitpick_files_section.get(key, {}).items():
            file_path = Nitpick.current_app().root_dir / file_name  # type: Path
            exists = file_path.exists()
            if (present and exists) or (absent and not exists):
                continue

            full_message = "File {} should {}".format(file_name, message)
            if extra_message:
                full_message += ": {}".format(extra_message)

            yield self.flake8_error(3 if present else 4, full_message)
示例#10
0
文件: plugin.py 项目: joegnis/nitpick
 def add_options(option_manager: OptionManager):
     """Add the offline option."""
     option_manager.add_option(
         Nitpick.format_flag(Nitpick.Flags.OFFLINE),
         action="store_true",
         # dest="offline",
         help=Nitpick.Flags.OFFLINE.value,
     )
示例#11
0
文件: config.py 项目: joegnis/nitpick
 def validate_pyproject_tool_nitpick(self) -> bool:
     """Validate the ``pyroject.toml``'s ``[tool.nitpick]`` section against a Marshmallow schema."""
     pyproject_path = Nitpick.current_app(
     ).root_dir / PyProjectTomlFile.file_name  # type: Path
     if pyproject_path.exists():
         self.pyproject_toml = TomlFormat(path=pyproject_path)
         self.tool_nitpick_dict = search_dict(TOOL_NITPICK_JMEX,
                                              self.pyproject_toml.as_data,
                                              {})
         pyproject_errors = ToolNitpickSectionSchema().validate(
             self.tool_nitpick_dict)
         if pyproject_errors:
             Nitpick.current_app().add_style_error(
                 PyProjectTomlFile.file_name,
                 "Invalid data in [{}]:".format(TOOL_NITPICK),
                 flatten_marshmallow_errors(pyproject_errors),
             )
             return False
     return True
示例#12
0
文件: base.py 项目: joegnis/nitpick
 def __init__(self) -> None:
     if self.has_multiple_files:
         key = "{}.file_names".format(self.__class__.__name__)
         self._multiple_files = search_dict(
             key,
             Nitpick.current_app().config.nitpick_section,
             [])  # type: List[str]
     else:
         self._multiple_files = [self.file_name]
         self._set_current_data(self.file_name)
示例#13
0
    def flake8(self, offline=False, file_index: int = 0) -> "ProjectMock":
        """Simulate a manual flake8 run.

        - Recreate the global app.
        - Change the working dir to the mocked project root.
        - Lint one of the project files. If no index is provided, use the default file that's always created.
        """
        os.chdir(str(self.root_dir))
        Nitpick.create_app(offline)

        npc = NitpickChecker(filename=str(self.files_to_lint[file_index]))
        self._original_errors = list(npc.run())

        self._errors = set()
        for flake8_error in self._original_errors:
            line, col, message, class_ = flake8_error
            if not (line == 0 and col == 0 and message.startswith(ERROR_PREFIX) and class_ is NitpickChecker):
                raise AssertionError()
            self._errors.add(message)
        return self
示例#14
0
文件: plugin.py 项目: joegnis/nitpick
    def run(self) -> YieldFlake8Error:
        """Run the check plugin."""
        has_errors = False
        for err in Nitpick.current_app().init_errors:
            has_errors = True
            yield Nitpick.as_flake8_warning(err)
        if has_errors:
            return []

        current_python_file = Path(self.filename)
        if current_python_file.absolute() != Nitpick.current_app().main_python_file.absolute():
            # Only report warnings once, for the main Python file of this project.
            LOGGER.debug("Ignoring file: %s", self.filename)
            return []
        LOGGER.debug("Nitpicking file: %s", self.filename)

        yield from itertools.chain(
            Nitpick.current_app().config.merge_styles(), self.check_files(True), self.check_files(False)
        )

        has_errors = False
        for err in Nitpick.current_app().style_errors:
            has_errors = True
            yield Nitpick.as_flake8_warning(err)
        if has_errors:
            return []

        for checker_class in get_subclasses(BaseFile):
            checker = checker_class()
            yield from checker.check_exists()

        return []
示例#15
0
文件: style.py 项目: joegnis/nitpick
    def fetch_style_from_url(self, url: str) -> Optional[Path]:
        """Fetch a style file from a URL, saving the contents in the cache dir."""
        if Nitpick.current_app().offline:
            # No style will be fetched in offline mode
            return None

        if self._first_full_path and not is_url(url):
            prefix, rest = self._first_full_path.split(":/")
            domain_plus_url = str(rest).strip("/").rstrip("/") + "/" + url
            new_url = "{}://{}".format(prefix, domain_plus_url)
        else:
            new_url = url

        parsed_url = list(urlparse(new_url))
        if not parsed_url[2].endswith(TOML_EXTENSION):
            parsed_url[2] += TOML_EXTENSION
        new_url = urlunparse(parsed_url)

        if new_url in self._already_included:
            return None

        if not Nitpick.current_app().cache_dir:
            raise FileNotFoundError("Cache dir does not exist")

        try:
            response = requests.get(new_url)
        except requests.ConnectionError:
            click.secho(
                "Your network is unreachable. Fix your connection or use {} / {}=1"
                .format(Nitpick.format_flag(Nitpick.Flags.OFFLINE),
                        Nitpick.format_env(Nitpick.Flags.OFFLINE)),
                fg="red",
                err=True,
            )
            return None
        if not response.ok:
            raise FileNotFoundError("Error {} fetching style URL {}".format(
                response, new_url))

        # Save the first full path to be used by the next files without parent.
        if not self._first_full_path:
            self._first_full_path = new_url.rsplit("/", 1)[0]

        contents = response.text
        style_path = Nitpick.current_app().cache_dir / "{}.toml".format(
            slugify(new_url))
        Nitpick.current_app().cache_dir.mkdir(parents=True, exist_ok=True)
        style_path.write_text(contents)

        LOGGER.info("Loading style from URL %s into %s", new_url, style_path)
        self._already_included.add(new_url)

        return style_path
示例#16
0
文件: style.py 项目: joegnis/nitpick
    def find_initial_styles(self, configured_styles: StrOrList):
        """Find the initial style(s) and include them."""
        if configured_styles:
            chosen_styles = configured_styles
            log_message = "Styles configured in {}: %s".format(
                PyProjectTomlFile.file_name)
        else:
            paths = climb_directory_tree(Nitpick.current_app().root_dir,
                                         [NITPICK_STYLE_TOML])
            if paths:
                chosen_styles = str(sorted(paths)[0])
                log_message = "Found style climbing the directory tree: %s"
            else:
                chosen_styles = self.get_default_style_url()
                log_message = "Loading default Nitpick style %s"
        LOGGER.info(log_message, chosen_styles)

        self.include_multiple_styles(chosen_styles)
示例#17
0
文件: style.py 项目: joegnis/nitpick
    def merge_toml_dict(self) -> JsonDict:
        """Merge all included styles into a TOML (actually JSON) dictionary."""
        app = Nitpick.current_app()
        if not app.cache_dir:
            return {}
        merged_dict = self._all_styles.merge()
        merged_style_path = app.cache_dir / MERGED_STYLE_TOML  # type: Path
        toml = TomlFormat(data=merged_dict)

        attempt = 1
        while attempt < 5:
            try:
                app.cache_dir.mkdir(parents=True, exist_ok=True)
                merged_style_path.write_text(toml.reformatted)
                break
            except OSError:
                attempt += 1

        return merged_dict
示例#18
0
文件: mixin.py 项目: joegnis/nitpick
    def flake8_error(self,
                     number: int,
                     message: str,
                     suggestion: str = None,
                     add_to_base_number=True) -> Flake8Error:
        """Return a flake8 error as a tuple."""
        # pylint: disable=import-outside-toplevel
        from nitpick.app import Nitpick
        from nitpick.exceptions import NitpickError

        error = NitpickError()
        error.error_base_number = self.error_base_number
        error.error_prefix = self.error_prefix
        error.number = number
        error.message = message
        if suggestion:
            error.suggestion = suggestion
        error.add_to_base_number = add_to_base_number
        return Nitpick.as_flake8_warning(error)
示例#19
0
 def check_rules(self) -> YieldFlake8Error:
     """Check missing key/value pairs in pyproject.toml."""
     if Nitpick.current_app().config.pyproject_toml:
         comparison = Nitpick.current_app(
         ).config.pyproject_toml.compare_with_flatten(self.file_dict)
         yield from self.warn_missing_different(comparison)
示例#20
0
"""Main module."""
from nitpick.app import Nitpick

__version__ = "0.21.2"

Nitpick.create_app()