src/eric7/PipInterface/piplicenses.py

branch
eric7
changeset 9590
8fad82cb88ab
parent 9310
8ab45a4a6d96
child 9653
e67609152c5e
equal deleted inserted replaced
9589:09218eb3ae21 9590:8fad82cb88ab
39 # - changed 'create_summary_table' to 'create_summary_list' 39 # - changed 'create_summary_table' to 'create_summary_list'
40 # - added 'create_summary_by_license_list' together with the 40 # - added 'create_summary_by_license_list' together with the
41 # '--summary-by-license' switch to count each individual 41 # '--summary-by-license' switch to count each individual
42 # license used 42 # license used
43 # - changed 'create_output_string' to return a JSON string 43 # - changed 'create_output_string' to return a JSON string
44 # - added 'get_installed_distributions' to get the distributions
45 # via pip in order to filter for 'local only' and/or 'user only'
44 # 46 #
47
48 from __future__ import annotations
45 49
46 import argparse 50 import argparse
47 import codecs 51 import codecs
48 import glob
49 import json 52 import json
50 import os 53 import re
51 import sys 54 import sys
52 from collections import Counter 55 from collections import Counter
53 from email import message_from_string
54 from email.parser import FeedParser
55 from enum import Enum, auto 56 from enum import Enum, auto
56 from typing import List, Optional, Sequence, Text 57 from pathlib import Path
58 from typing import TYPE_CHECKING, List, Type, cast
59
60 if TYPE_CHECKING:
61 from typing import Iterator, Optional, Sequence
57 62
58 63
59 def get_installed_distributions(local_only=True, user_only=False): 64 def get_installed_distributions(local_only=True, user_only=False):
65 """
66 Function to get the installed packages via pip.
67
68 Note: importlib_metadata.distributions() does not respect
69 'local_only' and 'user_only' keyword parameters.
70
71 @param local_only DESCRIPTION (defaults to True)
72 @type TYPE (optional)
73 @param user_only DESCRIPTION (defaults to False)
74 @type TYPE (optional)
75 @return DESCRIPTION
76 @rtype TYPE
77 """
60 try: 78 try:
61 from pip._internal.metadata import get_environment 79 from pip._internal.metadata import get_environment
62 except ImportError: 80 except ImportError:
63 # For backward compatibility with pip version 20.3.4 81 # For backward compatibility with pip version 20.3.4
64 from pip._internal.utils import misc 82 from pip._internal.utils import misc
76 editables_only=False, 94 editables_only=False,
77 ) 95 )
78 return [d._dist for d in dists] 96 return [d._dist for d in dists]
79 97
80 98
81 __pkgname__ = 'pip-licenses' 99 __pkgname__ = "pip-licenses"
82 __version__ = '3.5.4' 100 __version__ = "4.0.2"
83 __author__ = 'raimon' 101 __author__ = "raimon"
84 __license__ = 'MIT' 102 __license__ = "MIT"
85 __summary__ = ('Dump the software license list of ' 103 __summary__ = (
86 'Python packages installed with pip.') 104 "Dump the software license list of Python packages installed with pip."
87 __url__ = 'https://github.com/raimon49/pip-licenses' 105 )
106 __url__ = "https://github.com/raimon49/pip-licenses"
88 107
89 108
90 FIELD_NAMES = ( 109 FIELD_NAMES = (
91 'Name', 110 'Name',
92 'Version', 111 'Version',
99 'Description', 118 'Description',
100 'URL', 119 'URL',
101 ) 120 )
102 121
103 122
123 SUMMARY_FIELD_NAMES = (
124 "Count",
125 "License",
126 )
127
128
104 DEFAULT_OUTPUT_FIELDS = ( 129 DEFAULT_OUTPUT_FIELDS = (
105 'Name', 130 'Name',
106 'Version', 131 'Version',
107 ) 132 )
108 133
138 ) 163 )
139 164
140 LICENSE_UNKNOWN = 'UNKNOWN' 165 LICENSE_UNKNOWN = 'UNKNOWN'
141 166
142 167
143 def get_packages(args: "CustomNamespace"): 168 def get_packages(
144 169 args: CustomNamespace,
145 def get_pkg_included_file(pkg, file_names): 170 ) -> Iterator[dict[str, str | list[str]]]:
171 def get_pkg_included_file(
172 pkg, file_names_rgx: str
173 ) -> tuple[str, str]:
146 """ 174 """
147 Attempt to find the package's included file on disk and return the 175 Attempt to find the package's included file on disk and return the
148 tuple (included_file_path, included_file_contents). 176 tuple (included_file_path, included_file_contents).
149 """ 177 """
150 included_file = LICENSE_UNKNOWN 178 included_file = LICENSE_UNKNOWN
151 included_text = LICENSE_UNKNOWN 179 included_text = LICENSE_UNKNOWN
152 pkg_dirname = "{}-{}.dist-info".format( 180
153 pkg.project_name.replace("-", "_"), pkg.version) 181 pkg_files = pkg.files or ()
154 patterns = [] 182 pattern = re.compile(file_names_rgx)
155 [patterns.extend(sorted(glob.glob(os.path.join(pkg.location, 183 matched_rel_paths = filter(
156 pkg_dirname, 184 lambda file: pattern.match(file.name), pkg_files
157 f)))) 185 )
158 for f in file_names] 186 for rel_path in matched_rel_paths:
159 for test_file in patterns: 187 abs_path = Path(pkg.locate_file(rel_path))
160 if os.path.exists(test_file) and not os.path.isdir(test_file): 188 if not abs_path.is_file():
161 included_file = test_file 189 continue
162 with open(test_file, encoding='utf-8', 190 included_file = str(abs_path)
163 errors='backslashreplace') as included_file_handle: 191 with open(
164 included_text = included_file_handle.read() 192 abs_path, encoding="utf-8", errors="backslashreplace"
165 break 193 ) as included_file_handle:
194 included_text = included_file_handle.read()
195 break
166 return (included_file, included_text) 196 return (included_file, included_text)
167 197
168 def get_pkg_info(pkg): 198 def get_pkg_info(pkg) -> dict[str, str | list[str]]:
169 (license_file, license_text) = get_pkg_included_file( 199 (license_file, license_text) = get_pkg_included_file(
170 pkg, 200 pkg, "LICEN[CS]E.*|COPYING.*"
171 ('LICENSE*', 'LICENCE*', 'COPYING*')
172 ) 201 )
173 (notice_file, notice_text) = get_pkg_included_file( 202 (notice_file, notice_text) = get_pkg_included_file(pkg, "NOTICE.*")
174 pkg, 203 pkg_info: dict[str, str | list[str]] = {
175 ('NOTICE*',) 204 "name": pkg.metadata["name"],
205 "version": pkg.version,
206 "namever": "{} {}".format(pkg.metadata["name"], pkg.version),
207 "licensefile": license_file,
208 "licensetext": license_text,
209 "noticefile": notice_file,
210 "noticetext": notice_text,
211 }
212 metadata = pkg.metadata
213 for key in METADATA_KEYS:
214 pkg_info[key] = metadata.get(key, LICENSE_UNKNOWN) # type: ignore[attr-defined] # noqa: E501
215
216 classifiers: list[str] = metadata.get_all("classifier", [])
217 pkg_info["license_classifier"] = find_license_from_classifier(
218 classifiers
176 ) 219 )
177 pkg_info = {
178 'name': pkg.project_name,
179 'version': pkg.version,
180 'namever': str(pkg),
181 'licensefile': license_file,
182 'licensetext': license_text,
183 'noticefile': notice_file,
184 'noticetext': notice_text,
185 }
186 metadata = None
187 if pkg.has_metadata('METADATA'):
188 metadata = pkg.get_metadata('METADATA')
189
190 if pkg.has_metadata('PKG-INFO') and metadata is None:
191 metadata = pkg.get_metadata('PKG-INFO')
192
193 if metadata is None:
194 for key in METADATA_KEYS:
195 pkg_info[key] = LICENSE_UNKNOWN
196
197 return pkg_info
198
199 feed_parser = FeedParser()
200 feed_parser.feed(metadata)
201 parsed_metadata = feed_parser.close()
202
203 for key in METADATA_KEYS:
204 pkg_info[key] = parsed_metadata.get(key, LICENSE_UNKNOWN)
205
206 if metadata is not None:
207 message = message_from_string(metadata)
208 pkg_info['license_classifier'] = \
209 find_license_from_classifier(message)
210 220
211 if args.filter_strings: 221 if args.filter_strings:
222
223 def filter_string(item: str) -> str:
224 return item.encode(
225 args.filter_code_page, errors="ignore"
226 ).decode(args.filter_code_page)
227
212 for k in pkg_info: 228 for k in pkg_info:
213 if isinstance(pkg_info[k], list): 229 if isinstance(pkg_info[k], list):
214 for i, item in enumerate(pkg_info[k]): 230 pkg_info[k] = list(map(filter_string, pkg_info[k]))
215 pkg_info[k][i] = item. \
216 encode(args.filter_code_page, errors="ignore"). \
217 decode(args.filter_code_page)
218 else: 231 else:
219 pkg_info[k] = pkg_info[k]. \ 232 pkg_info[k] = filter_string(cast(str, pkg_info[k]))
220 encode(args.filter_code_page, errors="ignore"). \
221 decode(args.filter_code_page)
222 233
223 return pkg_info 234 return pkg_info
224 235
225 pkgs = get_installed_distributions( 236 pkgs = get_installed_distributions(
226 local_only=args.local_only, 237 local_only=args.local_only,
236 allow_only_licenses = set() 247 allow_only_licenses = set()
237 if args.allow_only: 248 if args.allow_only:
238 allow_only_licenses = set(map(str.strip, args.allow_only.split(";"))) 249 allow_only_licenses = set(map(str.strip, args.allow_only.split(";")))
239 250
240 for pkg in pkgs: 251 for pkg in pkgs:
241 pkg_name = pkg.project_name 252 pkg_name = pkg.metadata["name"]
242 253
243 if pkg_name.lower() in ignore_pkgs_as_lower: 254 if pkg_name.lower() in ignore_pkgs_as_lower:
244 continue 255 continue
245 256
246 if pkgs_as_lower and pkg_name.lower() not in pkgs_as_lower: 257 if pkgs_as_lower and pkg_name.lower() not in pkgs_as_lower:
251 262
252 pkg_info = get_pkg_info(pkg) 263 pkg_info = get_pkg_info(pkg)
253 264
254 license_names = select_license_by_source( 265 license_names = select_license_by_source(
255 args.from_, 266 args.from_,
256 pkg_info['license_classifier'], 267 cast(List[str], pkg_info["license_classifier"]),
257 pkg_info['license']) 268 cast(str, pkg_info["license"]),
269 )
258 270
259 if fail_on_licenses: 271 if fail_on_licenses:
260 failed_licenses = license_names.intersection(fail_on_licenses) 272 failed_licenses = license_names.intersection(fail_on_licenses)
261 if failed_licenses: 273 if failed_licenses:
262 sys.stderr.write( 274 sys.stderr.write(
263 "fail-on license {} was found for package " 275 "fail-on license {} was found for package "
264 "{}:{}".format( 276 "{}:{}".format(
265 '; '.join(sorted(failed_licenses)), 277 "; ".join(sorted(failed_licenses)),
266 pkg_info['name'], 278 pkg_info["name"],
267 pkg_info['version']) 279 pkg_info["version"],
280 )
268 ) 281 )
269 sys.exit(1) 282 sys.exit(1)
270 283
271 if allow_only_licenses: 284 if allow_only_licenses:
272 uncommon_licenses = license_names.difference(allow_only_licenses) 285 uncommon_licenses = license_names.difference(allow_only_licenses)
273 if len(uncommon_licenses) == len(license_names): 286 if len(uncommon_licenses) == len(license_names):
274 sys.stderr.write( 287 sys.stderr.write(
275 "license {} not in allow-only licenses was found" 288 "license {} not in allow-only licenses was found"
276 " for package {}:{}".format( 289 " for package {}:{}".format(
277 '; '.join(sorted(uncommon_licenses)), 290 "; ".join(sorted(uncommon_licenses)),
278 pkg_info['name'], 291 pkg_info["name"],
279 pkg_info['version']) 292 pkg_info["version"],
293 )
280 ) 294 )
281 sys.exit(1) 295 sys.exit(1)
282 296
283 yield pkg_info 297 yield pkg_info
284 298
290 for pkg in get_packages(args): 304 for pkg in get_packages(args):
291 row = {} 305 row = {}
292 for field in output_fields: 306 for field in output_fields:
293 if field == 'License': 307 if field == 'License':
294 license_set = select_license_by_source( 308 license_set = select_license_by_source(
295 args.from_, pkg['license_classifier'], pkg['license']) 309 args.from_,
296 license_str = '; '.join(sorted(license_set)) 310 cast(List[str], pkg["license_classifier"]),
311 cast(str, pkg["license"]),
312 )
313 license_str = "; ".join(sorted(license_set))
297 row[field] = license_str 314 row[field] = license_str
298 elif field == 'License-Classifier': 315 elif field == 'License-Classifier':
299 row[field] = ('; '.join(sorted(pkg['license_classifier'])) 316 row[field] = (
300 or LICENSE_UNKNOWN) 317 "; ".join(sorted(pkg["license_classifier"]))
318 or LICENSE_UNKNOWN
319 )
301 elif field.lower() in pkg: 320 elif field.lower() in pkg:
302 row[field] = pkg[field.lower()] 321 row[field] = cast(str, pkg[field.lower()])
303 else: 322 else:
304 row[field] = pkg[FIELDS_TO_METADATA_KEYS[field]] 323 row[field] = cast(str, pkg[FIELDS_TO_METADATA_KEYS[field]])
305 licenses.append(row) 324 licenses.append(row)
306 325
307 return licenses 326 return licenses
308 327
309 328
310 def create_summary_list(args: "CustomNamespace"): 329 def create_summary_list(args: "CustomNamespace"):
311 counts = Counter( 330 counts = Counter(
312 '; '.join(sorted(select_license_by_source( 331 "; ".join(
313 args.from_, pkg['license_classifier'], pkg['license']))) 332 sorted(
314 for pkg in get_packages(args)) 333 select_license_by_source(
334 args.from_,
335 cast(List[str], pkg["license_classifier"]),
336 cast(str, pkg["license"]),
337 )
338 )
339 )
340 for pkg in get_packages(args)
341 )
315 342
316 licenses = [] 343 licenses = []
317 for license, count in counts.items(): 344 for license, count in counts.items():
318 licenses.append({ 345 licenses.append({
319 "Count": count, 346 "Count": count,
338 }) 365 })
339 366
340 return licenses 367 return licenses
341 368
342 369
343 def find_license_from_classifier(message): 370 def find_license_from_classifier(classifiers: list[str]) -> list[str]:
344 licenses = [] 371 licenses = []
345 for k, v in message.items(): 372 for classifier in filter(lambda c: c.startswith("License"), classifiers):
346 if k == 'Classifier' and v.startswith('License'): 373 license = classifier.split(" :: ")[-1]
347 license = v.split(' :: ')[-1] 374
348 375 # Through the declaration of 'Classifier: License :: OSI Approved'
349 # Through the declaration of 'Classifier: License :: OSI Approved' 376 if license != "OSI Approved":
350 if license != 'OSI Approved': 377 licenses.append(license)
351 licenses.append(license)
352 378
353 return licenses 379 return licenses
354 380
355 381
356 def select_license_by_source(from_source, license_classifier, license_meta): 382 def select_license_by_source(
383 from_source: FromArg, license_classifier: list[str], license_meta: str
384 ) -> set[str]:
357 license_classifier_set = set(license_classifier) or {LICENSE_UNKNOWN} 385 license_classifier_set = set(license_classifier) or {LICENSE_UNKNOWN}
358 if (from_source == FromArg.CLASSIFIER or 386 if (
359 from_source == FromArg.MIXED and len(license_classifier) > 0): 387 from_source == FromArg.CLASSIFIER
388 or from_source == FromArg.MIXED
389 and len(license_classifier) > 0
390 ):
360 return license_classifier_set 391 return license_classifier_set
361 else: 392 else:
362 return {license_meta} 393 return {license_meta}
363 394
364 395
365 def get_output_fields(args: "CustomNamespace"): 396 def get_output_fields(args: CustomNamespace) -> list[str]:
366 if args.summary: 397 if args.summary:
367 return list(SUMMARY_OUTPUT_FIELDS) 398 return list(SUMMARY_OUTPUT_FIELDS)
368 399
369 output_fields = list(DEFAULT_OUTPUT_FIELDS) 400 output_fields = list(DEFAULT_OUTPUT_FIELDS)
370 401
371 if args.from_ == FromArg.ALL: 402 if args.from_ == FromArg.ALL:
372 output_fields.append('License-Metadata') 403 output_fields.append("License-Metadata")
373 output_fields.append('License-Classifier') 404 output_fields.append("License-Classifier")
374 else: 405 else:
375 output_fields.append('License') 406 output_fields.append("License")
376 407
377 if args.with_authors: 408 if args.with_authors:
378 output_fields.append('Author') 409 output_fields.append("Author")
379 410
380 if args.with_urls: 411 if args.with_urls:
381 output_fields.append('URL') 412 output_fields.append("URL")
382 413
383 if args.with_description: 414 if args.with_description:
384 output_fields.append('Description') 415 output_fields.append("Description")
385 416
386 if args.with_license_file: 417 if args.with_license_file:
387 if not args.no_license_path: 418 if not args.no_license_path:
388 output_fields.append('LicenseFile') 419 output_fields.append("LicenseFile")
389 420
390 output_fields.append('LicenseText') 421 output_fields.append("LicenseText")
391 422
392 if args.with_notice_file: 423 if args.with_notice_file:
393 output_fields.append('NoticeText') 424 output_fields.append("NoticeText")
394 if not args.no_license_path: 425 if not args.no_license_path:
395 output_fields.append('NoticeFile') 426 output_fields.append("NoticeFile")
396 427
397 return output_fields 428 return output_fields
398 429
399 430
400 def create_output_string(args: "CustomNamespace"): 431 def create_output_string(args: "CustomNamespace"):
410 return json.dumps(licenses) 441 return json.dumps(licenses)
411 442
412 443
413 class CustomHelpFormatter(argparse.HelpFormatter): # pragma: no cover 444 class CustomHelpFormatter(argparse.HelpFormatter): # pragma: no cover
414 def __init__( 445 def __init__(
415 self, prog: Text, indent_increment: int = 2, 446 self,
416 max_help_position: int = 24, width: Optional[int] = None 447 prog: str,
448 indent_increment: int = 2,
449 max_help_position: int = 24,
450 width: Optional[int] = None,
417 ) -> None: 451 ) -> None:
418 max_help_position = 30 452 max_help_position = 30
419 super().__init__( 453 super().__init__(
420 prog, indent_increment=indent_increment, 454 prog,
421 max_help_position=max_help_position, width=width) 455 indent_increment=indent_increment,
456 max_help_position=max_help_position,
457 width=width,
458 )
422 459
423 def _format_action(self, action: argparse.Action) -> str: 460 def _format_action(self, action: argparse.Action) -> str:
424 flag_indent_argument: bool = False 461 flag_indent_argument: bool = False
425 text = self._expand_help(action) 462 text = self._expand_help(action)
426 separator_pos = text[:3].find('|') 463 separator_pos = text[:3].find("|")
427 if separator_pos != -1 and 'I' in text[:separator_pos]: 464 if separator_pos != -1 and "I" in text[:separator_pos]:
428 self._indent() 465 self._indent()
429 flag_indent_argument = True 466 flag_indent_argument = True
430 help_str = super()._format_action(action) 467 help_str = super()._format_action(action)
431 if flag_indent_argument: 468 if flag_indent_argument:
432 self._dedent() 469 self._dedent()
433 return help_str 470 return help_str
434 471
435 def _expand_help(self, action: argparse.Action) -> str: 472 def _expand_help(self, action: argparse.Action) -> str:
436 if isinstance(action.default, Enum): 473 if isinstance(action.default, Enum):
437 default_value = enum_key_to_value(action.default) 474 default_value = enum_key_to_value(action.default)
438 return self._get_help_string(action) % {'default': default_value} 475 return cast(str, self._get_help_string(action)) % {
476 "default": default_value
477 }
439 return super()._expand_help(action) 478 return super()._expand_help(action)
440 479
441 def _split_lines(self, text: Text, width: int) -> List[str]: 480 def _split_lines(self, text: str, width: int) -> List[str]:
442 separator_pos = text[:3].find('|') 481 separator_pos = text[:3].find("|")
443 if separator_pos != -1: 482 if separator_pos != -1:
444 flag_splitlines: bool = 'R' in text[:separator_pos] 483 flag_splitlines: bool = "R" in text[:separator_pos]
445 text = text[separator_pos + 1:] 484 text = text[separator_pos + 1:] # fmt: skip
446 if flag_splitlines: 485 if flag_splitlines:
447 return text.splitlines() 486 return text.splitlines()
448 return super()._split_lines(text, width) 487 return super()._split_lines(text, width)
449 488
450 489
470 fail_on: Optional[str] 509 fail_on: Optional[str]
471 allow_only: Optional[str] 510 allow_only: Optional[str]
472 511
473 512
474 class CompatibleArgumentParser(argparse.ArgumentParser): 513 class CompatibleArgumentParser(argparse.ArgumentParser):
475 def parse_args(self, args: Optional[Sequence[Text]] = None, 514 def parse_args( # type: ignore[override]
476 namespace: CustomNamespace = None) -> CustomNamespace: 515 self,
477 args = super().parse_args(args, namespace) 516 args: None | Sequence[str] = None,
478 self._verify_args(args) 517 namespace: None | CustomNamespace = None,
479 return args 518 ) -> CustomNamespace:
480 519 args_ = cast(CustomNamespace, super().parse_args(args, namespace))
481 def _verify_args(self, args: CustomNamespace): 520 self._verify_args(args_)
521 return args_
522
523 def _verify_args(self, args: CustomNamespace) -> None:
482 if args.with_license_file is False and ( 524 if args.with_license_file is False and (
483 args.no_license_path is True or 525 args.no_license_path is True or args.with_notice_file is True
484 args.with_notice_file is True): 526 ):
485 self.error( 527 self.error(
486 "'--no-license-path' and '--with-notice-file' require " 528 "'--no-license-path' and '--with-notice-file' require "
487 "the '--with-license-file' option to be set") 529 "the '--with-license-file' option to be set"
488 if args.filter_strings is False and \ 530 )
489 args.filter_code_page != 'latin1': 531 if args.filter_strings is False and args.filter_code_page != "latin1":
490 self.error( 532 self.error(
491 "'--filter-code-page' requires the '--filter-strings' " 533 "'--filter-code-page' requires the '--filter-strings' "
492 "option to be set") 534 "option to be set"
535 )
493 try: 536 try:
494 codecs.lookup(args.filter_code_page) 537 codecs.lookup(args.filter_code_page)
495 except LookupError: 538 except LookupError:
496 self.error( 539 self.error(
497 "invalid code page '%s' given for '--filter-code-page, " 540 "invalid code page '%s' given for '--filter-code-page, "
498 "check https://docs.python.org/3/library/codecs.html" 541 "check https://docs.python.org/3/library/codecs.html"
499 "#standard-encodings for valid code pages" 542 "#standard-encodings for valid code pages"
500 % args.filter_code_page) 543 % args.filter_code_page
544 )
501 545
502 546
503 class NoValueEnum(Enum): 547 class NoValueEnum(Enum):
504 def __repr__(self): # pragma: no cover 548 def __repr__(self): # pragma: no cover
505 return '<%s.%s>' % (self.__class__.__name__, self.name) 549 return '<%s.%s>' % (self.__class__.__name__, self.name)
526 570
527 def enum_key_to_value(enum_key: Enum) -> str: 571 def enum_key_to_value(enum_key: Enum) -> str:
528 return enum_key.name.replace('_', '-').lower() 572 return enum_key.name.replace('_', '-').lower()
529 573
530 574
531 def choices_from_enum(enum_cls: NoValueEnum) -> List[str]: 575 def choices_from_enum(enum_cls: Type[NoValueEnum]) -> List[str]:
532 return [key.replace('_', '-').lower() 576 return [
533 for key in enum_cls.__members__.keys()] 577 key.replace("_", "-").lower() for key in enum_cls.__members__.keys()
578 ]
534 579
535 580
536 MAP_DEST_TO_ENUM = { 581 MAP_DEST_TO_ENUM = {
537 'from_': FromArg, 582 'from_': FromArg,
538 'order': OrderArg, 583 'order': OrderArg,
539 } 584 }
540 585
541 586
542 class SelectAction(argparse.Action): 587 class SelectAction(argparse.Action):
543 def __call__( 588 def __call__( # type: ignore[override]
544 self, parser: argparse.ArgumentParser, 589 self,
590 parser: argparse.ArgumentParser,
545 namespace: argparse.Namespace, 591 namespace: argparse.Namespace,
546 values: Text, 592 values: str,
547 option_string: Optional[Text] = None, 593 option_string: Optional[str] = None,
548 ) -> None: 594 ) -> None:
549 enum_cls = MAP_DEST_TO_ENUM[self.dest] 595 enum_cls = MAP_DEST_TO_ENUM[self.dest]
550 values = value_to_enum_key(values) 596 values = value_to_enum_key(values)
551 setattr(namespace, self.dest, getattr(enum_cls, values)) 597 setattr(namespace, self.dest, getattr(enum_cls, values))
552 598
553 599
554 def create_parser(): 600 def create_parser():
555 parser = CompatibleArgumentParser( 601 parser = CompatibleArgumentParser(
556 description=__summary__, 602 description=__summary__, formatter_class=CustomHelpFormatter
557 formatter_class=CustomHelpFormatter) 603 )
558 604
559 common_options = parser.add_argument_group('Common options') 605 common_options = parser.add_argument_group('Common options')
560 format_options = parser.add_argument_group('Format options') 606 format_options = parser.add_argument_group('Format options')
561 verify_options = parser.add_argument_group('Verify options') 607 verify_options = parser.add_argument_group('Verify options')
562 608
563 parser.add_argument( 609 parser.add_argument(
564 '-v', '--version', 610 "-v", "--version", action="version", version="%(prog)s " + __version__
565 action='version', 611 )
566 version='%(prog)s ' + __version__) 612
567 613 common_options.add_argument(
568 common_options.add_argument( 614 "--from",
569 '--from', 615 dest="from_",
570 dest='from_', 616 action=SelectAction,
571 action=SelectAction, type=str, 617 type=str,
572 default=FromArg.MIXED, metavar='SOURCE', 618 default=FromArg.MIXED,
619 metavar="SOURCE",
573 choices=choices_from_enum(FromArg), 620 choices=choices_from_enum(FromArg),
574 help='R|where to find license information\n' 621 help="R|where to find license information\n"
575 '"meta", "classifier, "mixed", "all"\n' 622 '"meta", "classifier, "mixed", "all"\n'
576 '(default: %(default)s)') 623 "(default: %(default)s)",
577 common_options.add_argument( 624 )
578 '-o', '--order', 625 common_options.add_argument(
579 action=SelectAction, type=str, 626 "-o",
580 default=OrderArg.NAME, metavar='COL', 627 "--order",
628 action=SelectAction,
629 type=str,
630 default=OrderArg.NAME,
631 metavar="COL",
581 choices=choices_from_enum(OrderArg), 632 choices=choices_from_enum(OrderArg),
582 help='R|order by column\n' 633 help="R|order by column\n"
583 '"name", "license", "author", "url"\n' 634 '"name", "license", "author", "url"\n'
584 '(default: %(default)s)') 635 "(default: %(default)s)",
636 )
585 common_options.add_argument( 637 common_options.add_argument(
586 '--summary', 638 '--summary',
587 action='store_true', 639 action='store_true',
588 default=False, 640 default=False,
589 help='dump summary of each license') 641 help='dump summary of each license')
595 common_options.add_argument( 647 common_options.add_argument(
596 '--output-file', 648 '--output-file',
597 action='store', type=str, 649 action='store', type=str,
598 help='save license list to file') 650 help='save license list to file')
599 common_options.add_argument( 651 common_options.add_argument(
600 '-i', '--ignore-packages', 652 "-i",
601 action='store', type=str, 653 "--ignore-packages",
602 nargs='+', metavar='PKG', 654 action="store",
655 type=str,
656 nargs="+",
657 metavar="PKG",
603 default=[], 658 default=[],
604 help='ignore package name in dumped list') 659 help="ignore package name in dumped list",
605 common_options.add_argument( 660 )
606 '-p', '--packages', 661 common_options.add_argument(
607 action='store', type=str, 662 "-p",
608 nargs='+', metavar='PKG', 663 "--packages",
664 action="store",
665 type=str,
666 nargs="+",
667 metavar="PKG",
609 default=[], 668 default=[],
610 help='only include selected packages in output') 669 help="only include selected packages in output",
670 )
611 common_options.add_argument( 671 common_options.add_argument(
612 '--local-only', 672 '--local-only',
613 action='store_true', 673 action='store_true',
614 default=False, 674 default=False,
615 help='include only local packages') 675 help='include only local packages')
618 action='store_true', 678 action='store_true',
619 default=False, 679 default=False,
620 help='include only packages of the user site dir') 680 help='include only packages of the user site dir')
621 681
622 format_options.add_argument( 682 format_options.add_argument(
623 '-s', '--with-system', 683 "-s",
624 action='store_true', 684 "--with-system",
625 default=False,
626 help='dump with system packages')
627 format_options.add_argument(
628 '-a', '--with-authors',
629 action='store_true',
630 default=False,
631 help='dump with package authors')
632 format_options.add_argument(
633 '-u', '--with-urls',
634 action='store_true',
635 default=False,
636 help='dump with package urls')
637 format_options.add_argument(
638 '-d', '--with-description',
639 action='store_true',
640 default=False,
641 help='dump with short package description')
642 format_options.add_argument(
643 '-l', '--with-license-file',
644 action='store_true',
645 default=False,
646 help='dump with location of license file and '
647 'contents, most useful with JSON output')
648 format_options.add_argument(
649 '--no-license-path',
650 action='store_true',
651 default=False,
652 help='I|when specified together with option -l, '
653 'suppress location of license file output')
654 format_options.add_argument(
655 '--with-notice-file',
656 action='store_true',
657 default=False,
658 help='I|when specified together with option -l, '
659 'dump with location of license file and contents')
660 format_options.add_argument(
661 '--filter-strings',
662 action="store_true", 685 action="store_true",
663 default=False, 686 default=False,
664 help='filter input according to code page') 687 help="dump with system packages",
665 format_options.add_argument( 688 )
666 '--filter-code-page', 689 format_options.add_argument(
667 action="store", type=str, 690 "-a",
691 "--with-authors",
692 action="store_true",
693 default=False,
694 help="dump with package authors",
695 )
696 format_options.add_argument(
697 "-u",
698 "--with-urls",
699 action="store_true",
700 default=False,
701 help="dump with package urls",
702 )
703 format_options.add_argument(
704 "-d",
705 "--with-description",
706 action="store_true",
707 default=False,
708 help="dump with short package description",
709 )
710 format_options.add_argument(
711 "-l",
712 "--with-license-file",
713 action="store_true",
714 default=False,
715 help="dump with location of license file and "
716 "contents, most useful with JSON output",
717 )
718 format_options.add_argument(
719 "--no-license-path",
720 action="store_true",
721 default=False,
722 help="I|when specified together with option -l, "
723 "suppress location of license file output",
724 )
725 format_options.add_argument(
726 "--with-notice-file",
727 action="store_true",
728 default=False,
729 help="I|when specified together with option -l, "
730 "dump with location of license file and contents",
731 )
732 format_options.add_argument(
733 "--filter-strings",
734 action="store_true",
735 default=False,
736 help="filter input according to code page",
737 )
738 format_options.add_argument(
739 "--filter-code-page",
740 action="store",
741 type=str,
668 default="latin1", 742 default="latin1",
669 metavar="CODE", 743 metavar="CODE",
670 help='I|specify code page for filtering ' 744 help="I|specify code page for filtering " "(default: %(default)s)",
671 '(default: %(default)s)') 745 )
672 746
673 verify_options.add_argument( 747 verify_options.add_argument(
674 '--fail-on', 748 "--fail-on",
675 action='store', type=str, 749 action="store",
750 type=str,
676 default=None, 751 default=None,
677 help='fail (exit with code 1) on the first occurrence ' 752 help="fail (exit with code 1) on the first occurrence "
678 'of the licenses of the semicolon-separated list') 753 "of the licenses of the semicolon-separated list",
754 )
679 verify_options.add_argument( 755 verify_options.add_argument(
680 '--allow-only', 756 "--allow-only",
681 action='store', type=str, 757 action="store",
758 type=str,
682 default=None, 759 default=None,
683 help='fail (exit with code 1) on the first occurrence ' 760 help="fail (exit with code 1) on the first occurrence "
684 'of the licenses not in the semicolon-separated list') 761 "of the licenses not in the semicolon-separated list",
762 )
685 763
686 return parser 764 return parser
687 765
688 766
689 def main(): # pragma: no cover 767 def main(): # pragma: no cover
690 os.environ["_PIP_USE_IMPORTLIB_METADATA"] = "False"
691 # patched for 3.11+ compatibility
692
693 parser = create_parser() 768 parser = create_parser()
694 args = parser.parse_args() 769 args = parser.parse_args()
695 770
696 output_string = create_output_string(args) 771 output_string = create_output_string(args)
697 772

eric ide

mercurial