Coverage for sphinxlint/__main___REMOTE_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}, "

58 "supported values are {SortField.as_supported_options()}"

59 ) from None

60 setattr(namespace, self.dest, sort_fields)

61

62 parser.add_argument(

63 "-v",

64 "--verbose",

65 action="store_true",

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

67 )

68 parser.add_argument(

69 "-i",

70 "--ignore",

71 action="append",

72 help="ignore subdir or file path",

73 default=[],

74 )

75 parser.add_argument(

76 "-d",

77 "--disable",

78 action=DisableAction,

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

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

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

82 "single check.",

83 )

84 parser.add_argument(

85 "-e",

86 "--enable",

87 action=EnableAction,

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

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

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

91 "all but one check.",

92 )

93 parser.add_argument(

94 "--list",

95 action="store_true",

96 help="List enabled checkers and exit. "

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

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

99 )

100 parser.add_argument(

101 "--max-line-length",

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

103 default=80,

104 type=int,

105 )

106 parser.add_argument(

107 "-s",

108 "--sort-by",

109 action=StoreSortFieldAction,

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

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

112 )

113

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

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

116 try:

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

118 except KeyError as err:

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

120 sys.exit(2)

121 return enabled_checkers, args

122

123

124 def walk(path, ignore_list):

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

126

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

128 """

129 if os.path.isfile(path):

130 if path in ignore_list:

131 return

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

133 return

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

135 # ignore subdirs in ignore list

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

137 del dirs[:]

138 continue

139 for file in files:

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

141 # ignore files in ignore list

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

143 continue

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

145

146

147 def _check_file(todo):

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

149 multiprocessing.imap_unordered."""

150 return check_file(*todo)

151

152

153 def sort_errors(results, sorted_by):

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

155 if not sorted_by:

156 for result in results:

157 yield from result

158 return

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

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

161 # ordering specified by the user

162 for sort_field in reversed(sorted_by):

163 if sort_field == SortField.ERROR_TYPE:

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

165 elif sort_field == SortField.FILENAME:

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

167 elif sort_field == SortField.LINE:

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

169 yield from errors

170

171

172 def print_errors(errors):

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

174 qty = 0

175 for error in errors:

176 print(error)

177 qty += 1

178 if qty == 0:

179 print("No problems found.")

180 return qty

181

182

183 def main(argv=None):

184 enabled_checkers, args = parse_args(argv)

185 options = CheckersOptions.from_argparse(args)

186 if args.list:

187 if not enabled_checkers:

188 print("No checkers selected.")

189 return 0

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

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

192 if args.verbose:

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

194 else:

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

196 if not args.verbose:

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

198 return 0

199

200 for path in args.paths:

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

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

203 return 2

204

205 todo = [

206 (path, enabled_checkers, options)

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

208 ]

209

210 if len(todo) < 8:

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

212 else:

213 with multiprocessing.Pool() as pool:

214 count = print_errors(

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

216 )

217 pool.close()

218 pool.join()

219

220 return int(bool(count))

221

222

223 if __name__ == "__main__":

224 sys.exit(main())