24 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
24 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
25 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
25 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
27 SOFTWARE. |
27 SOFTWARE. |
28 """ |
28 """ |
|
29 |
|
30 # |
|
31 # Modified to be used within the eric-ide project. |
|
32 # |
|
33 # Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
34 # |
|
35 |
29 import argparse |
36 import argparse |
30 import codecs |
37 import codecs |
31 import glob |
38 import glob |
|
39 import json |
32 import os |
40 import os |
33 import sys |
41 import sys |
34 from collections import Counter |
42 from collections import Counter |
35 from email import message_from_string |
43 from email import message_from_string |
36 from email.parser import FeedParser |
44 from email.parser import FeedParser |
37 from enum import Enum, auto |
45 from enum import Enum, auto |
38 from functools import partial |
|
39 from typing import List, Optional, Sequence, Text |
46 from typing import List, Optional, Sequence, Text |
40 |
47 |
41 try: |
48 |
42 from pip._internal.utils.misc import get_installed_distributions |
49 def get_installed_distributions(local_only=True, user_only=False): |
43 except ImportError: # pragma: no cover |
|
44 try: |
50 try: |
45 from pip import get_installed_distributions |
51 from pip._internal.metadata import get_environment |
46 except ImportError: |
52 except ImportError: |
47 def get_installed_distributions(): |
53 # For backward compatibility with pip version 20.3.4 |
48 from pip._internal.metadata import ( |
54 from pip._internal.utils import misc |
49 get_default_environment, get_environment, |
55 return misc.get_installed_distributions( |
50 ) |
56 local_only=local_only, |
51 from pip._internal.metadata.pkg_resources import ( |
57 user_only=user_only |
52 Distribution as _Dist, |
58 ) |
53 ) |
59 else: |
54 from pip._internal.utils.compat import stdlib_pkgs |
60 from pip._internal.utils.compat import stdlib_pkgs |
55 |
61 dists = get_environment(None).iter_installed_distributions( |
56 env = get_default_environment() |
62 local_only=local_only, |
57 dists = env.iter_installed_distributions( |
63 user_only=user_only, |
58 local_only=True, |
64 skip=stdlib_pkgs, |
59 skip=stdlib_pkgs, |
65 include_editables=True, |
60 include_editables=True, |
66 editables_only=False, |
61 editables_only=False, |
67 ) |
62 user_only=False, |
68 return [d._dist for d in dists] |
63 ) |
69 |
64 return [dist._dist for dist in dists] |
|
65 |
|
66 from prettytable import PrettyTable |
|
67 |
|
68 open = open # allow monkey patching |
|
69 |
70 |
70 __pkgname__ = 'pip-licenses' |
71 __pkgname__ = 'pip-licenses' |
71 __version__ = '3.5.3' |
72 __version__ = '3.5.3' |
72 __author__ = 'raimon' |
73 __author__ = 'raimon' |
73 __license__ = 'MIT' |
74 __license__ = 'MIT' |
298 licenses.append(row) |
295 licenses.append(row) |
299 |
296 |
300 return licenses |
297 return licenses |
301 |
298 |
302 |
299 |
303 def create_summary_table(args: "CustomNamespace"): |
300 def create_summary_list(args: "CustomNamespace"): |
304 counts = Counter( |
301 counts = Counter( |
305 '; '.join(sorted(select_license_by_source( |
302 '; '.join(sorted(select_license_by_source( |
306 args.from_, pkg['license_classifier'], pkg['license']))) |
303 args.from_, pkg['license_classifier'], pkg['license']))) |
307 for pkg in get_packages(args)) |
304 for pkg in get_packages(args)) |
308 |
305 |
309 table = factory_styled_table_with_args(args, SUMMARY_FIELD_NAMES) |
306 licenses = [] |
310 for license, count in counts.items(): |
307 for license, count in counts.items(): |
311 table.add_row([count, license]) |
308 licenses.append({ |
312 return table |
309 "Count": count, |
313 |
310 "License": license, |
314 |
311 }) |
315 class JsonTable(): |
312 |
316 |
313 return licenses |
317 def _format_row(self, row, options): |
|
318 resrow = {} |
|
319 for (field, value) in zip(self._field_names, row): |
|
320 if field not in options["fields"]: |
|
321 continue |
|
322 |
|
323 resrow[field] = value |
|
324 |
|
325 return resrow |
|
326 |
|
327 def get_string(self, **kwargs): |
|
328 # import included here in order to limit dependencies |
|
329 # if not interested in JSON output, |
|
330 # then the dependency is not required |
|
331 import json |
|
332 |
|
333 options = self._get_options(kwargs) |
|
334 rows = self._get_rows(options) |
|
335 formatted_rows = self._format_rows(rows, options) |
|
336 |
|
337 lines = [] |
|
338 for row in formatted_rows: |
|
339 lines.append(row) |
|
340 |
|
341 return json.dumps(lines, indent=2, sort_keys=True) |
|
342 ## |
|
343 ## |
|
344 ##class JsonLicenseFinderTable(JsonPrettyTable): |
|
345 ## def _format_row(self, row, options): |
|
346 ## resrow = {} |
|
347 ## for (field, value) in zip(self._field_names, row): |
|
348 ## if field == 'Name': |
|
349 ## resrow['name'] = value |
|
350 ## |
|
351 ## if field == 'Version': |
|
352 ## resrow['version'] = value |
|
353 ## |
|
354 ## if field == 'License': |
|
355 ## resrow['licenses'] = [value] |
|
356 ## |
|
357 ## return resrow |
|
358 ## |
|
359 ## def get_string(self, **kwargs): |
|
360 ## # import included here in order to limit dependencies |
|
361 ## # if not interested in JSON output, |
|
362 ## # then the dependency is not required |
|
363 ## import json |
|
364 ## |
|
365 ## options = self._get_options(kwargs) |
|
366 ## rows = self._get_rows(options) |
|
367 ## formatted_rows = self._format_rows(rows, options) |
|
368 ## |
|
369 ## lines = [] |
|
370 ## for row in formatted_rows: |
|
371 ## lines.append(row) |
|
372 ## |
|
373 ## return json.dumps(lines, sort_keys=True) |
|
374 ## |
|
375 ## |
|
376 ##class CSVPrettyTable(PrettyTable): |
|
377 ## """PrettyTable-like class exporting to CSV""" |
|
378 ## |
|
379 ## def get_string(self, **kwargs): |
|
380 ## |
|
381 ## def esc_quotes(val): |
|
382 ## """ |
|
383 ## Meta-escaping double quotes |
|
384 ## https://tools.ietf.org/html/rfc4180 |
|
385 ## """ |
|
386 ## try: |
|
387 ## return val.replace('"', '""') |
|
388 ## except UnicodeDecodeError: # pragma: no cover |
|
389 ## return val.decode('utf-8').replace('"', '""') |
|
390 ## except UnicodeEncodeError: # pragma: no cover |
|
391 ## return val.encode('unicode_escape').replace('"', '""') |
|
392 ## |
|
393 ## options = self._get_options(kwargs) |
|
394 ## rows = self._get_rows(options) |
|
395 ## formatted_rows = self._format_rows(rows, options) |
|
396 ## |
|
397 ## lines = [] |
|
398 ## formatted_header = ','.join(['"%s"' % (esc_quotes(val), ) |
|
399 ## for val in self._field_names]) |
|
400 ## lines.append(formatted_header) |
|
401 ## for row in formatted_rows: |
|
402 ## formatted_row = ','.join(['"%s"' % (esc_quotes(val), ) |
|
403 ## for val in row]) |
|
404 ## lines.append(formatted_row) |
|
405 ## |
|
406 ## return '\n'.join(lines) |
|
407 ## |
|
408 ## |
|
409 ##class PlainVerticalTable(PrettyTable): |
|
410 ## """PrettyTable for outputting to a simple non-column based style. |
|
411 ## |
|
412 ## When used with --with-license-file, this style is similar to the default |
|
413 ## style generated from Angular CLI's --extractLicenses flag. |
|
414 ## """ |
|
415 ## |
|
416 ## def get_string(self, **kwargs): |
|
417 ## options = self._get_options(kwargs) |
|
418 ## rows = self._get_rows(options) |
|
419 ## |
|
420 ## output = '' |
|
421 ## for row in rows: |
|
422 ## for v in row: |
|
423 ## output += '{}\n'.format(v) |
|
424 ## output += '\n' |
|
425 ## |
|
426 ## return output |
|
427 ## |
|
428 ## |
|
429 def factory_styled_table_with_args( |
|
430 args: "CustomNamespace", output_fields=DEFAULT_OUTPUT_FIELDS): |
|
431 table = PrettyTable() |
|
432 table.field_names = output_fields |
|
433 table.align = 'l' |
|
434 table.border = args.format_ in (FormatArg.MARKDOWN, FormatArg.RST, |
|
435 FormatArg.CONFLUENCE, FormatArg.JSON) |
|
436 table.header = True |
|
437 |
|
438 if args.format_ == FormatArg.MARKDOWN: |
|
439 table.junction_char = '|' |
|
440 table.hrules = RULE_HEADER |
|
441 elif args.format_ == FormatArg.RST: |
|
442 table.junction_char = '+' |
|
443 table.hrules = RULE_ALL |
|
444 elif args.format_ == FormatArg.CONFLUENCE: |
|
445 table.junction_char = '|' |
|
446 table.hrules = RULE_NONE |
|
447 elif args.format_ == FormatArg.JSON: |
|
448 table = JsonPrettyTable(table.field_names) |
|
449 elif args.format_ == FormatArg.JSON_LICENSE_FINDER: |
|
450 table = JsonLicenseFinderTable(table.field_names) |
|
451 elif args.format_ == FormatArg.CSV: |
|
452 table = CSVPrettyTable(table.field_names) |
|
453 elif args.format_ == FormatArg.PLAIN_VERTICAL: |
|
454 table = PlainVerticalTable(table.field_names) |
|
455 |
|
456 return table |
|
457 |
314 |
458 |
315 |
459 def find_license_from_classifier(message): |
316 def find_license_from_classifier(message): |
460 licenses = [] |
317 licenses = [] |
461 for k, v in message.items(): |
318 for k, v in message.items(): |
511 output_fields.append('NoticeFile') |
368 output_fields.append('NoticeFile') |
512 |
369 |
513 return output_fields |
370 return output_fields |
514 |
371 |
515 |
372 |
516 def get_sortby(args: "CustomNamespace"): |
|
517 if args.summary and args.order == OrderArg.COUNT: |
|
518 return 'Count' |
|
519 elif args.summary or args.order == OrderArg.LICENSE: |
|
520 return 'License' |
|
521 elif args.order == OrderArg.NAME: |
|
522 return 'Name' |
|
523 elif args.order == OrderArg.AUTHOR and args.with_authors: |
|
524 return 'Author' |
|
525 elif args.order == OrderArg.URL and args.with_urls: |
|
526 return 'URL' |
|
527 |
|
528 return 'Name' |
|
529 |
|
530 |
|
531 def create_output_string(args: "CustomNamespace"): |
373 def create_output_string(args: "CustomNamespace"): |
532 output_fields = get_output_fields(args) |
374 output_fields = get_output_fields(args) |
533 |
375 |
534 if args.summary: |
376 if args.summary: |
535 table = create_summary_table(args) |
377 licenses = create_summary_list(args) |
536 else: |
378 else: |
537 table = create_licenses_list(args, output_fields) |
379 licenses = create_licenses_list(args, output_fields) |
538 |
380 |
539 return json.dumps(table) |
381 return json.dumps(licenses) |
540 ## |
|
541 ## sortby = get_sortby(args) |
|
542 ## |
|
543 ## if args.format_ == FormatArg.HTML: |
|
544 ## return table.get_html_string(fields=output_fields, sortby=sortby) |
|
545 ## else: |
|
546 ## return table.get_string(fields=output_fields, sortby=sortby) |
|
547 ## |
|
548 ## |
|
549 ##def create_warn_string(args: "CustomNamespace"): |
|
550 ## warn_messages = [] |
|
551 ## warn = partial(output_colored, '33') |
|
552 ## |
|
553 ## if args.with_license_file and not args.format_ == FormatArg.JSON: |
|
554 ## message = warn(('Due to the length of these fields, this option is ' |
|
555 ## 'best paired with --format=json.')) |
|
556 ## warn_messages.append(message) |
|
557 ## |
|
558 ## if args.summary and (args.with_authors or args.with_urls): |
|
559 ## message = warn(('When using this option, only --order=count or ' |
|
560 ## '--order=license has an effect for the --order ' |
|
561 ## 'option. And using --with-authors and --with-urls ' |
|
562 ## 'will be ignored.')) |
|
563 ## warn_messages.append(message) |
|
564 ## |
|
565 ## return '\n'.join(warn_messages) |
|
566 |
382 |
567 |
383 |
568 class CustomHelpFormatter(argparse.HelpFormatter): # pragma: no cover |
384 class CustomHelpFormatter(argparse.HelpFormatter): # pragma: no cover |
569 def __init__( |
385 def __init__( |
570 self, prog: Text, indent_increment: int = 2, |
386 self, prog: Text, indent_increment: int = 2, |
747 choices=choices_from_enum(OrderArg), |
551 choices=choices_from_enum(OrderArg), |
748 help='R|order by column\n' |
552 help='R|order by column\n' |
749 '"name", "license", "author", "url"\n' |
553 '"name", "license", "author", "url"\n' |
750 '(default: %(default)s)') |
554 '(default: %(default)s)') |
751 common_options.add_argument( |
555 common_options.add_argument( |
752 '-f', '--format', |
|
753 dest='format_', |
|
754 action=SelectAction, type=str, |
|
755 default=FormatArg.PLAIN, metavar='STYLE', |
|
756 choices=choices_from_enum(FormatArg), |
|
757 help='R|dump as set format style\n' |
|
758 '"plain", "plain-vertical" "markdown", "rst", \n' |
|
759 '"confluence", "html", "json", \n' |
|
760 '"json-license-finder", "csv"\n' |
|
761 '(default: %(default)s)') |
|
762 common_options.add_argument( |
|
763 '--summary', |
556 '--summary', |
764 action='store_true', |
557 action='store_true', |
765 default=False, |
558 default=False, |
766 help='dump summary of each license') |
559 help='dump summary of each license') |
767 common_options.add_argument( |
560 common_options.add_argument( |
843 default=None, |
647 default=None, |
844 help='fail (exit with code 1) on the first occurrence ' |
648 help='fail (exit with code 1) on the first occurrence ' |
845 'of the licenses not in the semicolon-separated list') |
649 'of the licenses not in the semicolon-separated list') |
846 |
650 |
847 return parser |
651 return parser |
848 ## |
|
849 ## |
|
850 ##def output_colored(code, text, is_bold=False): |
|
851 ## """ |
|
852 ## Create function to output with color sequence |
|
853 ## """ |
|
854 ## if is_bold: |
|
855 ## code = '1;%s' % code |
|
856 ## |
|
857 ## return '\033[%sm%s\033[0m' % (code, text) |
|
858 ## |
|
859 ## |
|
860 ##def save_if_needs(output_file, output_string): |
|
861 ## """ |
|
862 ## Save to path given by args |
|
863 ## """ |
|
864 ## if output_file is None: |
|
865 ## return |
|
866 ## |
|
867 ## try: |
|
868 ## with open(output_file, 'w', encoding='utf-8') as f: |
|
869 ## f.write(output_string) |
|
870 ## sys.stdout.write('created path: ' + output_file + '\n') |
|
871 ## sys.exit(0) |
|
872 ## except IOError: |
|
873 ## sys.stderr.write('check path: --output-file\n') |
|
874 ## sys.exit(1) |
|
875 |
652 |
876 |
653 |
877 def main(): # pragma: no cover |
654 def main(): # pragma: no cover |
878 parser = create_parser() |
655 parser = create_parser() |
879 args = parser.parse_args() |
656 args = parser.parse_args() |
880 |
657 |
881 output_string = create_output_string(args) |
658 output_string = create_output_string(args) |
882 |
659 |
883 ## output_file = args.output_file |
|
884 ## save_if_needs(output_file, output_string) |
|
885 ## |
|
886 print(output_string) |
660 print(output_string) |
887 ## warn_string = create_warn_string(args) |
|
888 ## if warn_string: |
|
889 ## print(warn_string, file=sys.stderr) |
|
890 |
661 |
891 |
662 |
892 if __name__ == '__main__': # pragma: no cover |
663 if __name__ == '__main__': # pragma: no cover |
893 main() |
664 main() |