Coverage for sphinxlint/__main___BASE_7374.py: 0%
121 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-24 18:46 +0100
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-24 18:46 +0100
1import argparse
2import enum
3import multiprocessing
4import os
5import sys
6from itertools import chain, starmap
8from sphinxlint import check_file
9from sphinxlint.checkers import all_checkers
10from sphinxlint.sphinxlint import CheckersOptions
13class SortField(enum.Enum):
14 """Fields available for sorting error reports"""
16 FILENAME = 0
17 LINE = 1
18 ERROR_TYPE = 2
20 @staticmethod
21 def as_supported_options():
22 return ",".join(field.name.lower() for field in SortField)
25def parse_args(argv=None):
26 """Parse command line argument."""
27 if argv is None:
28 argv = sys.argv
29 parser = argparse.ArgumentParser(description=__doc__)
31 enabled_checkers_names = {
32 checker.name for checker in all_checkers.values() if checker.enabled
33 }
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(","))
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(","))
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)
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 )
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
123def walk(path, ignore_list):
124 """Wrapper around os.walk with an ignore list.
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:]
146def _check_file(todo):
147 """Wrapper to call check_file with arguments given by
148 multiprocessing.imap_unordered."""
149 return check_file(*todo)
152def 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
171def 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
182def 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
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
204 todo = [
205 (path, enabled_checkers, options)
206 for path in chain.from_iterable(walk(path, args.ignore) for path in args.paths)
207 ]
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()
219 return int(bool(count))
222if __name__ == "__main__":
223 sys.exit(main())