Coverage for pymend\files.py: 0%

1"""File handling for pymend."""

2

3 import sys

4 from collections.abc import Sequence

5 from functools import lru_cache

6 from pathlib import Path

7 from typing import TYPE_CHECKING, Any, Optional

8

9 if sys.version_info >= (3, 11):

10 try:

11 import tomllib

12 except ImportError:

13 # Help users on older alphas

14 if not TYPE_CHECKING:

15 import tomli as tomllib

16 else:

17 raise

18 else:

19 import tomli as tomllib

20

21

22@lru_cache

23 def find_project_root(srcs: Sequence[str]) -> tuple[Path, str]:

24 """Return a directory containing .git, .hg, or pyproject.toml.

25

26 That directory will be a common parent of all files and directories

27 passed in `srcs`.

28

29 If no directory in the tree contains a marker that would specify it's the

30 project root, the root of the file system is returned.

31

32 Returns a two-tuple with the first element as the project root path and

33 the second element as a string describing the method by which the

34 project root was discovered.

35

36 Parameters

37 ----------

38 srcs : Sequence[str]

39 Source files that will be considered by pymend.

40

41 Returns

42 -------

43 directory : Path

44 Projects root path

45 method : str

46 Method by which the root path was determined.

47 """

48 if not srcs:

49 srcs = [str(Path.cwd().resolve())]

50

51 path_srcs = [Path(Path.cwd(), src).resolve() for src in srcs]

52

53 # A list of lists of parents for each 'src'. 'src' is included as a

54 # "parent" of itself if it is a directory

55 src_parents = [

56 list(path.parents) + ([path] if path.is_dir() else []) for path in path_srcs

57 ]

58

59 intersection: set[Path] = set[Path].intersection(

60 *(set(parents) for parents in src_parents)

61 )

62

63 common_base = max(

64 intersection,

65 key=lambda path: path.parts,

66 )

67

68 # Directory will always be set in the loop.

69 # This is just for pylint.

70 directory = Path()

71 for directory in (common_base, *common_base.parents):

72 if (directory / ".git").exists():

73 return directory, ".git directory"

74

75 if (directory / ".hg").is_dir():

76 return directory, ".hg directory"

77

78 if (directory / "pyproject.toml").is_file():

79 return directory, "pyproject.toml"

80

81 return directory, "file system root"

82

83

84 def find_pyproject_toml(path_search_start: tuple[str, ...]) -> Optional[str]:

85 """Find the absolute filepath to a pyproject.toml if it exists.

86

87 Parameters

88 ----------

89 path_search_start : tuple[str, ...]

90 Tuple of paths to consider in the search for pyproject.toml

91

92 Returns

93 -------

94 Optional[str]

95 Path to pypyproject.toml or None if it could not be found.

96 """

97 path_project_root, _ = find_project_root(path_search_start)

98 path_pyproject_toml = path_project_root / "pyproject.toml"

99 if path_pyproject_toml.is_file():

100 return str(path_pyproject_toml)

101

102 return None

103

104

105 def parse_pyproject_toml(path_config: str) -> dict[str, Any]:

106 """Parse a pyproject toml file, pulling out relevant parts for pymend.

107

108 If parsing fails, will raise a tomllib.TOMLDecodeError.

109

110 Parameters

111 ----------

112 path_config : str

113 Path to the pyproject.toml file.

114

115 Returns

116 -------

117 dict[str, Any]

118 Configuration dictionary parsed from pyproject.toml

119 """

120 with Path(path_config).open("rb") as f:

121 pyproject_toml: dict[str, Any] = tomllib.load(f)

122 config: dict[str, Any] = pyproject_toml.get("tool", {}).get("pymend", {})

123 return {k.replace("--", "").replace("-", "_"): v for k, v in config.items()}