Skip to content

Commit

Permalink
fix: improve the way to find the root dir of the project
Browse files Browse the repository at this point in the history
Fixes #72
  • Loading branch information
andreoliwa committed Aug 13, 2019
1 parent 4dd32d4 commit fa3460a
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 30 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ If a key is defined in more than one file, the value from the last file will pre

### Style file syntax

NOTE: The project is still experimental; the style file syntax might slightly change before the 1.0 stable release.

A style file contains basically the configuration options you want to enforce in all your projects.

They are just the config to the tool, prefixed with the name of the config file.
Expand Down
48 changes: 35 additions & 13 deletions src/nitpick/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
from pathlib import Path
from shutil import rmtree
from typing import Optional
from typing import Optional, Set

from nitpick.constants import (
CACHE_DIR_NAME,
Expand Down Expand Up @@ -68,18 +68,40 @@ def find_root_dir(self, starting_file: PathOrStr) -> bool:
if hasattr(self, "root_dir"):
return True

found_files = climb_directory_tree(
starting_file, ROOT_FILES + (PyProjectTomlFile.file_name, SetupCfgFile.file_name)
)
if not found_files:
# If none of the root files were found, try again with manage.py.
# On Django projects, it can be in another dir inside the root dir.
found_files = climb_directory_tree(starting_file, [MANAGE_PY])
if not found_files:
LOGGER.error("No files found while climbing directory tree from %s", str(starting_file))
return False

self.root_dir = found_files[0].parent # pylint: disable=attribute-defined-outside-init
root_dirs = set() # type: Set[Path]
seen = set() # type: Set[Path]

starting_dir = Path(starting_file).parent.absolute()
while True:
project_files = climb_directory_tree(
starting_dir, ROOT_FILES + (PyProjectTomlFile.file_name, SetupCfgFile.file_name)
)
if project_files and project_files & seen:
break
seen.update(project_files or [])

if not project_files:
# If none of the root files were found, try again with manage.py.
# On Django projects, it can be in another dir inside the root dir.
project_files = climb_directory_tree(starting_file, [MANAGE_PY])
if not project_files or project_files & seen:
break
seen.update(project_files)

for found in project_files or []:
root_dirs.add(found.parent)

# Climb up one directory to search for more project files
starting_dir = starting_dir.parent
if not starting_dir:
break

if not root_dirs:
LOGGER.error("No files found while climbing directory tree from %s", str(starting_file))
return False

# If multiple roots are found, get the top one (grandparent dir)
self.root_dir = sorted(root_dirs)[0] # pylint: disable=attribute-defined-outside-init
self.clear_cache_dir()
return True

Expand Down
6 changes: 3 additions & 3 deletions src/nitpick/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""
import collections
from pathlib import Path
from typing import Any, Dict, Iterable, List, MutableMapping, Optional, Tuple, Union
from typing import Any, Dict, Iterable, List, MutableMapping, Optional, Set, Tuple, Union

import jmespath
from jmespath.parser import ParsedResult
Expand Down Expand Up @@ -100,7 +100,7 @@ def merge(self) -> JsonDict:
return unflatten(self._all_flattened, separator=UNIQUE_SEPARATOR)


def climb_directory_tree(starting_path: PathOrStr, file_patterns: Iterable[str]) -> Optional[List[Path]]:
def climb_directory_tree(starting_path: PathOrStr, file_patterns: Iterable[str]) -> Optional[Set[Path]]:
"""Climb the directory tree looking for file patterns."""
current_dir = Path(starting_path).absolute() # type: Path
if current_dir.is_file():
Expand All @@ -110,7 +110,7 @@ def climb_directory_tree(starting_path: PathOrStr, file_patterns: Iterable[str])
for root_file in file_patterns:
found_files = list(current_dir.glob(root_file))
if found_files:
return found_files
return set(found_files)
current_dir = current_dir.parent
return None

Expand Down
8 changes: 2 additions & 6 deletions src/nitpick/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from nitpick import __version__
from nitpick.config import NitpickConfig
from nitpick.constants import PROJECT_NAME, ROOT_PYTHON_FILES
from nitpick.constants import PROJECT_NAME
from nitpick.files.base import BaseFile
from nitpick.generic import get_subclasses
from nitpick.mixin import NitpickMixin
Expand Down Expand Up @@ -41,11 +41,7 @@ def run(self) -> YieldFlake8Error:
return []

if not self.config.find_main_python_file():
yield self.flake8_error(
2,
"None of those Python files was found in the root dir"
+ " {}: {}".format(self.config.root_dir, ", ".join(ROOT_PYTHON_FILES)),
)
yield self.flake8_error(2, "No Python file was found under the root dir {!r}".format(self.config.root_dir))
return []

current_python_file = Path(self.filename)
Expand Down
2 changes: 1 addition & 1 deletion src/nitpick/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def find_initial_styles(self, configured_styles: StrOrList):
else:
paths = climb_directory_tree(self.config.root_dir, [NITPICK_STYLE_TOML])
if paths:
chosen_styles = str(paths[0])
chosen_styles = str(sorted(paths)[0])
log_message = "Found style climbing the directory tree: %s"
else:
chosen_styles = DEFAULT_NITPICK_STYLE_URL
Expand Down
17 changes: 10 additions & 7 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Config tests."""
from nitpick.constants import ROOT_PYTHON_FILES
from tests.helpers import ProjectMock


Expand All @@ -10,13 +9,17 @@ def test_no_root_dir(request):
)


def test_multiple_root_dirs(request):
"""Multiple possible "root dirs" found (e.g.: a requirements.txt file inside a docs dir)."""
ProjectMock(request, setup_py=False).touch_file("docs/requirements.txt").touch_file("docs/conf.py").pyproject_toml(
""
).style("").lint().assert_no_errors()


def test_no_main_python_file_root_dir(request):
"""No main Python file on the root dir."""
project = ProjectMock(request, setup_py=False).pyproject_toml("").save_file("whatever.sh", "", lint=True).lint()
project.assert_single_error(
"NIP102 None of those Python files was found in the root dir "
+ "{}: {}".format(project.root_dir, ", ".join(ROOT_PYTHON_FILES))
)
project.assert_single_error("NIP102 No Python file was found under the root dir {!r}".format(project.root_dir))


def test_django_project_structure(request):
Expand All @@ -31,8 +34,8 @@ def test_django_project_structure(request):
[flake8]
some = thing
"""
).save_file(
"my_django_project/manage.py", ""
).touch_file(
"my_django_project/manage.py"
).style(
"""
["pyproject.toml".tool.black]
Expand Down

0 comments on commit fa3460a

Please sign in to comment.