Coverage for sphinxlint/__main___BASE_7374.py: 0%

1 import argparse

2 import enum

3 import multiprocessing

4 import os

5 import sys

6 from itertools import chain, starmap

7

8 from sphinxlint import check_file

9 from sphinxlint.checkers import all_checkers

10 from sphinxlint.sphinxlint import CheckersOptions

11

12

13 class SortField(enum.Enum):

14 """Fields available for sorting error reports"""

15

16 FILENAME = 0

17 LINE = 1

18 ERROR_TYPE = 2

19

20 @staticmethod

21 def as_supported_options():

22 return ",".join(field.name.lower() for field in SortField)

23

24

25 def parse_args(argv=None):

26 """Parse command line argument."""

27 if argv is None:

28 argv = sys.argv

29 parser = argparse.ArgumentParser(description=__doc__)

30

31 enabled_checkers_names = {

32 checker.name for checker in all_checkers.values() if checker.enabled

33 }

34

35 class EnableAction(argparse.Action):

36 def __call__(self, parser, namespace, values, option_string=None):

37 if values == "all":

38 enabled_checkers_names.update(set(all_checkers.keys()))

39 else:

40 enabled_checkers_names.update(values.split(","))

41

42 class DisableAction(argparse.Action):

43 def __call__(self, parser, namespace, values, option_string=None):

44 if values == "all":

45 enabled_checkers_names.clear()

46 else:

47 enabled_checkers_names.difference_update(values.split(","))

48

49 class StoreSortFieldAction(argparse.Action):

50 def __call__(self, parser, namespace, values, option_string=None):

51 sort_fields = []

52 for field_name in values.split(","):

53 try:

54 sort_fields.append(SortField[field_name.upper()])

55 except KeyError:

56 raise ValueError(

57 f"Unsupported sort field: {field_name}, supported values are {SortField.as_supported_options()}"

58 ) from None

59 setattr(namespace, self.dest, sort_fields)

60

61 parser.add_argument(

62 "-v",

63 "--verbose",

64 action="store_true",

65 help="verbose (print all checked file names)",

66 )

67 parser.add_argument(

68 "-i",

69 "--ignore",

70 action="append",

71 help="ignore subdir or file path",

72 default=[],

73 )

74 parser.add_argument(

75 "-d",

76 "--disable",

77 action=DisableAction,

78 help='comma-separated list of checks to disable. Give "all" to disable them all. '

79 "Can be used in conjunction with --enable (it's evaluated left-to-right). "

80 '"--disable all --enable trailing-whitespace" can be used to enable a '

81 "single check.",

82 )

83 parser.add_argument(

84 "-e",

85 "--enable",

86 action=EnableAction,

87 help='comma-separated list of checks to enable. Give "all" to enable them all. '

88 "Can be used in conjunction with --disable (it's evaluated left-to-right). "

89 '"--enable all --disable trailing-whitespace" can be used to enable '

90 "all but one check.",

91 )

92 parser.add_argument(

93 "--list",

94 action="store_true",

95 help="List enabled checkers and exit. "

96 "Can be used to see which checkers would be used with a given set of "

97 "--enable and --disable options.",

98 )

99 parser.add_argument(

100 "--max-line-length",

101 help="Maximum number of characters on a single line.",

102 default=80,

103 type=int,

104 )

105 parser.add_argument(

106 "-s",

107 "--sort-by",

108 action=StoreSortFieldAction,

109 help="comma-separated list of fields used to sort errors by. Available "

110 f"fields are: {SortField.as_supported_options()}",

111 )

112

113 parser.add_argument("paths", default=".", nargs="*")

114 args = parser.parse_args(argv[1:])

115 try:

116 enabled_checkers = {all_checkers[name] for name in enabled_checkers_names}

117 except KeyError as err:

118 print(f"Unknown checker: {err.args[0]}.")

119 sys.exit(2)

120 return enabled_checkers, args

121

122

123 def walk(path, ignore_list):

124 """Wrapper around os.walk with an ignore list.

125

126 It also allows giving a file, thus yielding just that file.

127 """

128 if os.path.isfile(path):

129 if path in ignore_list:

130 return

131 yield path if path[:2] != "./" else path[2:]

132 return

133 for root, dirs, files in os.walk(path):

134 # ignore subdirs in ignore list

135 if any(ignore in root for ignore in ignore_list):

136 del dirs[:]

137 continue

138 for file in files:

139 file = os.path.join(root, file)

140 # ignore files in ignore list

141 if any(ignore in file for ignore in ignore_list):

142 continue

143 yield file if file[:2] != "./" else file[2:]

144

145

146 def _check_file(todo):

147 """Wrapper to call check_file with arguments given by

148 multiprocessing.imap_unordered."""

149 return check_file(*todo)

150

151

152 def sort_errors(results, sorted_by):

153 """Flattens and potentially sorts errors based on user prefernces"""

154 if not sorted_by:

155 for results in results:

156 yield from results

157 return

158 errors = list(error for errors in results for error in errors)

159 # sorting is stable in python, so we can sort in reverse order to get the

160 # ordering specified by the user

161 for sort_field in reversed(sorted_by):

162 if sort_field == SortField.ERROR_TYPE:

163 errors.sort(key=lambda error: error.checker_name)

164 elif sort_field == SortField.FILENAME:

165 errors.sort(key=lambda error: error.filename)

166 elif sort_field == SortField.LINE:

167 errors.sort(key=lambda error: error.line_no)

168 yield from errors

169

170

171 def print_errors(errors):

172 """Print errors (or a message if nothing is to be printed)."""

173 qty = 0

174 for error in errors:

175 print(error)

176 qty += 1

177 if qty == 0:

178 print("No problems found.")

179 return qty

180

181

182 def main(argv=None):

183 enabled_checkers, args = parse_args(argv)

184 options = CheckersOptions.from_argparse(args)

185 if args.list:

186 if not enabled_checkers:

187 print("No checkers selected.")

188 return 0

189 print(f"{len(enabled_checkers)} checkers selected:")

190 for check in sorted(enabled_checkers, key=lambda fct: fct.name):

191 if args.verbose:

192 print(f"- {check.name}: {check.__doc__}")

193 else:

194 print(f"- {check.name}: {check.__doc__.splitlines()[0]}")

195 if not args.verbose:

196 print("\n(Use `--list --verbose` to know more about each check)")

197 return 0

198

199 for path in args.paths:

200 if not os.path.exists(path):

201 print(f"Error: path {path} does not exist")

202 return 2

203

204 todo = [

205 (path, enabled_checkers, options)

206 for path in chain.from_iterable(walk(path, args.ignore) for path in args.paths)

207 ]

208

209 if len(todo) < 8:

210 count = print_errors(sort_errors(starmap(check_file, todo), args.sort_by))

211 else:

212 with multiprocessing.Pool() as pool:

213 count = print_errors(

214 sort_errors(pool.imap_unordered(_check_file, todo), args.sort_by)

215 )

216 pool.close()

217 pool.join()

218

219 return int(bool(count))

220

221

222 if __name__ == "__main__":

223 sys.exit(main())