Coverage for pymend\files.py: 0%

41 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-04-20 19:09 +0200

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

2 

3import sys 

4from collections.abc import Sequence 

5from functools import lru_cache 

6from pathlib import Path 

7from typing import TYPE_CHECKING, Any, Optional 

8 

9if 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 

18else: 

19 import tomli as tomllib 

20 

21 

22@lru_cache 

23def 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 

84def 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 

105def 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()}