--- a/eric7/PipInterface/piplicenses.py Sun Mar 27 19:56:41 2022 +0200 +++ b/eric7/PipInterface/piplicenses.py Mon Mar 28 18:13:15 2022 +0200 @@ -26,46 +26,47 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + +# +# Modified to be used within the eric-ide project. +# +# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# + import argparse import codecs import glob +import json import os import sys from collections import Counter from email import message_from_string from email.parser import FeedParser from enum import Enum, auto -from functools import partial from typing import List, Optional, Sequence, Text -try: - from pip._internal.utils.misc import get_installed_distributions -except ImportError: # pragma: no cover + +def get_installed_distributions(local_only=True, user_only=False): try: - from pip import get_installed_distributions + from pip._internal.metadata import get_environment except ImportError: - def get_installed_distributions(): - from pip._internal.metadata import ( - get_default_environment, get_environment, - ) - from pip._internal.metadata.pkg_resources import ( - Distribution as _Dist, - ) - from pip._internal.utils.compat import stdlib_pkgs + # For backward compatibility with pip version 20.3.4 + from pip._internal.utils import misc + return misc.get_installed_distributions( + local_only=local_only, + user_only=user_only + ) + else: + from pip._internal.utils.compat import stdlib_pkgs + dists = get_environment(None).iter_installed_distributions( + local_only=local_only, + user_only=user_only, + skip=stdlib_pkgs, + include_editables=True, + editables_only=False, + ) + return [d._dist for d in dists] - env = get_default_environment() - dists = env.iter_installed_distributions( - local_only=True, - skip=stdlib_pkgs, - include_editables=True, - editables_only=False, - user_only=False, - ) - return [dist._dist for dist in dists] - -from prettytable import PrettyTable - -open = open # allow monkey patching __pkgname__ = 'pip-licenses' __version__ = '3.5.3' @@ -90,12 +91,6 @@ ) -SUMMARY_FIELD_NAMES = ( - 'Count', - 'License', -) - - DEFAULT_OUTPUT_FIELDS = ( 'Name', 'Version', @@ -217,7 +212,10 @@ return pkg_info - pkgs = get_installed_distributions() + pkgs = get_installed_distributions( + local_only=args.local_only, + user_only=args.user_only, + ) ignore_pkgs_as_lower = [pkg.lower() for pkg in args.ignore_packages] pkgs_as_lower = [pkg.lower() for pkg in args.packages] @@ -287,7 +285,6 @@ args.from_, pkg['license_classifier'], pkg['license']) license_str = '; '.join(sorted(license_set)) row[field] = license_str -## row.append(license_str) elif field == 'License-Classifier': row[field] = ('; '.join(sorted(pkg['license_classifier'])) or LICENSE_UNKNOWN) @@ -300,160 +297,20 @@ return licenses -def create_summary_table(args: "CustomNamespace"): +def create_summary_list(args: "CustomNamespace"): counts = Counter( '; '.join(sorted(select_license_by_source( args.from_, pkg['license_classifier'], pkg['license']))) for pkg in get_packages(args)) - table = factory_styled_table_with_args(args, SUMMARY_FIELD_NAMES) + licenses = [] for license, count in counts.items(): - table.add_row([count, license]) - return table - - -class JsonTable(): - - def _format_row(self, row, options): - resrow = {} - for (field, value) in zip(self._field_names, row): - if field not in options["fields"]: - continue - - resrow[field] = value - - return resrow - - def get_string(self, **kwargs): - # import included here in order to limit dependencies - # if not interested in JSON output, - # then the dependency is not required - import json - - options = self._get_options(kwargs) - rows = self._get_rows(options) - formatted_rows = self._format_rows(rows, options) - - lines = [] - for row in formatted_rows: - lines.append(row) - - return json.dumps(lines, indent=2, sort_keys=True) -## -## -##class JsonLicenseFinderTable(JsonPrettyTable): -## def _format_row(self, row, options): -## resrow = {} -## for (field, value) in zip(self._field_names, row): -## if field == 'Name': -## resrow['name'] = value -## -## if field == 'Version': -## resrow['version'] = value -## -## if field == 'License': -## resrow['licenses'] = [value] -## -## return resrow -## -## def get_string(self, **kwargs): -## # import included here in order to limit dependencies -## # if not interested in JSON output, -## # then the dependency is not required -## import json -## -## options = self._get_options(kwargs) -## rows = self._get_rows(options) -## formatted_rows = self._format_rows(rows, options) -## -## lines = [] -## for row in formatted_rows: -## lines.append(row) -## -## return json.dumps(lines, sort_keys=True) -## -## -##class CSVPrettyTable(PrettyTable): -## """PrettyTable-like class exporting to CSV""" -## -## def get_string(self, **kwargs): -## -## def esc_quotes(val): -## """ -## Meta-escaping double quotes -## https://tools.ietf.org/html/rfc4180 -## """ -## try: -## return val.replace('"', '""') -## except UnicodeDecodeError: # pragma: no cover -## return val.decode('utf-8').replace('"', '""') -## except UnicodeEncodeError: # pragma: no cover -## return val.encode('unicode_escape').replace('"', '""') -## -## options = self._get_options(kwargs) -## rows = self._get_rows(options) -## formatted_rows = self._format_rows(rows, options) -## -## lines = [] -## formatted_header = ','.join(['"%s"' % (esc_quotes(val), ) -## for val in self._field_names]) -## lines.append(formatted_header) -## for row in formatted_rows: -## formatted_row = ','.join(['"%s"' % (esc_quotes(val), ) -## for val in row]) -## lines.append(formatted_row) -## -## return '\n'.join(lines) -## -## -##class PlainVerticalTable(PrettyTable): -## """PrettyTable for outputting to a simple non-column based style. -## -## When used with --with-license-file, this style is similar to the default -## style generated from Angular CLI's --extractLicenses flag. -## """ -## -## def get_string(self, **kwargs): -## options = self._get_options(kwargs) -## rows = self._get_rows(options) -## -## output = '' -## for row in rows: -## for v in row: -## output += '{}\n'.format(v) -## output += '\n' -## -## return output -## -## -def factory_styled_table_with_args( - args: "CustomNamespace", output_fields=DEFAULT_OUTPUT_FIELDS): - table = PrettyTable() - table.field_names = output_fields - table.align = 'l' - table.border = args.format_ in (FormatArg.MARKDOWN, FormatArg.RST, - FormatArg.CONFLUENCE, FormatArg.JSON) - table.header = True - - if args.format_ == FormatArg.MARKDOWN: - table.junction_char = '|' - table.hrules = RULE_HEADER - elif args.format_ == FormatArg.RST: - table.junction_char = '+' - table.hrules = RULE_ALL - elif args.format_ == FormatArg.CONFLUENCE: - table.junction_char = '|' - table.hrules = RULE_NONE - elif args.format_ == FormatArg.JSON: - table = JsonPrettyTable(table.field_names) - elif args.format_ == FormatArg.JSON_LICENSE_FINDER: - table = JsonLicenseFinderTable(table.field_names) - elif args.format_ == FormatArg.CSV: - table = CSVPrettyTable(table.field_names) - elif args.format_ == FormatArg.PLAIN_VERTICAL: - table = PlainVerticalTable(table.field_names) - - return table + licenses.append({ + "Count": count, + "License": license, + }) + + return licenses def find_license_from_classifier(message): @@ -513,56 +370,15 @@ return output_fields -def get_sortby(args: "CustomNamespace"): - if args.summary and args.order == OrderArg.COUNT: - return 'Count' - elif args.summary or args.order == OrderArg.LICENSE: - return 'License' - elif args.order == OrderArg.NAME: - return 'Name' - elif args.order == OrderArg.AUTHOR and args.with_authors: - return 'Author' - elif args.order == OrderArg.URL and args.with_urls: - return 'URL' - - return 'Name' - - def create_output_string(args: "CustomNamespace"): output_fields = get_output_fields(args) if args.summary: - table = create_summary_table(args) + licenses = create_summary_list(args) else: - table = create_licenses_list(args, output_fields) + licenses = create_licenses_list(args, output_fields) - return json.dumps(table) -## -## sortby = get_sortby(args) -## -## if args.format_ == FormatArg.HTML: -## return table.get_html_string(fields=output_fields, sortby=sortby) -## else: -## return table.get_string(fields=output_fields, sortby=sortby) -## -## -##def create_warn_string(args: "CustomNamespace"): -## warn_messages = [] -## warn = partial(output_colored, '33') -## -## if args.with_license_file and not args.format_ == FormatArg.JSON: -## message = warn(('Due to the length of these fields, this option is ' -## 'best paired with --format=json.')) -## warn_messages.append(message) -## -## if args.summary and (args.with_authors or args.with_urls): -## message = warn(('When using this option, only --order=count or ' -## '--order=license has an effect for the --order ' -## 'option. And using --with-authors and --with-urls ' -## 'will be ignored.')) -## warn_messages.append(message) -## -## return '\n'.join(warn_messages) + return json.dumps(licenses) class CustomHelpFormatter(argparse.HelpFormatter): # pragma: no cover @@ -606,8 +422,9 @@ class CustomNamespace(argparse.Namespace): from_: "FromArg" order: "OrderArg" - format_: "FormatArg" summary: bool + local_only: bool + user_only:bool output_file: str ignore_packages: List[str] packages: List[str] @@ -673,18 +490,6 @@ URL = U = auto() -class FormatArg(NoValueEnum): - PLAIN = P = auto() - PLAIN_VERTICAL = auto() - MARKDOWN = MD = M = auto() - RST = REST = R = auto() - CONFLUENCE = C = auto() - HTML = H = auto() - JSON = J = auto() - JSON_LICENSE_FINDER = JLF = auto() - CSV = auto() - - def value_to_enum_key(value: str) -> str: return value.replace('-', '_').upper() @@ -701,7 +506,6 @@ MAP_DEST_TO_ENUM = { 'from_': FromArg, 'order': OrderArg, - 'format_': FormatArg, } @@ -749,17 +553,6 @@ '"name", "license", "author", "url"\n' '(default: %(default)s)') common_options.add_argument( - '-f', '--format', - dest='format_', - action=SelectAction, type=str, - default=FormatArg.PLAIN, metavar='STYLE', - choices=choices_from_enum(FormatArg), - help='R|dump as set format style\n' - '"plain", "plain-vertical" "markdown", "rst", \n' - '"confluence", "html", "json", \n' - '"json-license-finder", "csv"\n' - '(default: %(default)s)') - common_options.add_argument( '--summary', action='store_true', default=False, @@ -780,6 +573,17 @@ nargs='+', metavar='PKG', default=[], help='only include selected packages in output') + common_options.add_argument( + '--local-only', + action='store_true', + default=False, + help='include only local packages') + common_options.add_argument( + '--user-only', + action='store_true', + default=False, + help='include only packages of the user site dir') + format_options.add_argument( '-s', '--with-system', action='store_true', @@ -845,33 +649,6 @@ 'of the licenses not in the semicolon-separated list') return parser -## -## -##def output_colored(code, text, is_bold=False): -## """ -## Create function to output with color sequence -## """ -## if is_bold: -## code = '1;%s' % code -## -## return '\033[%sm%s\033[0m' % (code, text) -## -## -##def save_if_needs(output_file, output_string): -## """ -## Save to path given by args -## """ -## if output_file is None: -## return -## -## try: -## with open(output_file, 'w', encoding='utf-8') as f: -## f.write(output_string) -## sys.stdout.write('created path: ' + output_file + '\n') -## sys.exit(0) -## except IOError: -## sys.stderr.write('check path: --output-file\n') -## sys.exit(1) def main(): # pragma: no cover @@ -880,13 +657,7 @@ output_string = create_output_string(args) -## output_file = args.output_file -## save_if_needs(output_file, output_string) -## print(output_string) -## warn_string = create_warn_string(args) -## if warn_string: -## print(warn_string, file=sys.stderr) if __name__ == '__main__': # pragma: no cover