eric7/PipInterface/piplicenses.py

branch
eric7
changeset 9002
31a7decd3393
child 9003
6bc210cd5726
equal deleted inserted replaced
9001:a00cd6b55728 9002:31a7decd3393
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # vim:fenc=utf-8 ff=unix ft=python ts=4 sw=4 sts=4 si et
4 """
5 pip-licenses
6
7 MIT License
8
9 Copyright (c) 2018 raimon
10
11 Permission is hereby granted, free of charge, to any person obtaining a copy
12 of this software and associated documentation files (the "Software"), to deal
13 in the Software without restriction, including without limitation the rights
14 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the Software is
16 furnished to do so, subject to the following conditions:
17
18 The above copyright notice and this permission notice shall be included in all
19 copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
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,
26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 SOFTWARE.
28 """
29 import argparse
30 import codecs
31 import glob
32 import os
33 import sys
34 from collections import Counter
35 from email import message_from_string
36 from email.parser import FeedParser
37 from enum import Enum, auto
38 from functools import partial
39 from typing import List, Optional, Sequence, Text
40
41 try:
42 from pip._internal.utils.misc import get_installed_distributions
43 except ImportError: # pragma: no cover
44 try:
45 from pip import get_installed_distributions
46 except ImportError:
47 def get_installed_distributions():
48 from pip._internal.metadata import (
49 get_default_environment, get_environment,
50 )
51 from pip._internal.metadata.pkg_resources import (
52 Distribution as _Dist,
53 )
54 from pip._internal.utils.compat import stdlib_pkgs
55
56 env = get_default_environment()
57 dists = env.iter_installed_distributions(
58 local_only=True,
59 skip=stdlib_pkgs,
60 include_editables=True,
61 editables_only=False,
62 user_only=False,
63 )
64 return [dist._dist for dist in dists]
65
66 from prettytable import PrettyTable
67
68 open = open # allow monkey patching
69
70 __pkgname__ = 'pip-licenses'
71 __version__ = '3.5.3'
72 __author__ = 'raimon'
73 __license__ = 'MIT'
74 __summary__ = ('Dump the software license list of '
75 'Python packages installed with pip.')
76 __url__ = 'https://github.com/raimon49/pip-licenses'
77
78
79 FIELD_NAMES = (
80 'Name',
81 'Version',
82 'License',
83 'LicenseFile',
84 'LicenseText',
85 'NoticeFile',
86 'NoticeText',
87 'Author',
88 'Description',
89 'URL',
90 )
91
92
93 SUMMARY_FIELD_NAMES = (
94 'Count',
95 'License',
96 )
97
98
99 DEFAULT_OUTPUT_FIELDS = (
100 'Name',
101 'Version',
102 )
103
104
105 SUMMARY_OUTPUT_FIELDS = (
106 'Count',
107 'License',
108 )
109
110
111 METADATA_KEYS = (
112 'home-page',
113 'author',
114 'license',
115 'summary',
116 'license_classifier',
117 )
118
119 # Mapping of FIELD_NAMES to METADATA_KEYS where they differ by more than case
120 FIELDS_TO_METADATA_KEYS = {
121 'URL': 'home-page',
122 'Description': 'summary',
123 'License-Metadata': 'license',
124 'License-Classifier': 'license_classifier',
125 }
126
127
128 SYSTEM_PACKAGES = (
129 __pkgname__,
130 'pip',
131 'setuptools',
132 'wheel',
133 )
134
135 LICENSE_UNKNOWN = 'UNKNOWN'
136
137
138 def get_packages(args: "CustomNamespace"):
139
140 def get_pkg_included_file(pkg, file_names):
141 """
142 Attempt to find the package's included file on disk and return the
143 tuple (included_file_path, included_file_contents).
144 """
145 included_file = LICENSE_UNKNOWN
146 included_text = LICENSE_UNKNOWN
147 pkg_dirname = "{}-{}.dist-info".format(
148 pkg.project_name.replace("-", "_"), pkg.version)
149 patterns = []
150 [patterns.extend(sorted(glob.glob(os.path.join(pkg.location,
151 pkg_dirname,
152 f))))
153 for f in file_names]
154 for test_file in patterns:
155 if os.path.exists(test_file):
156 included_file = test_file
157 with open(test_file, encoding='utf-8',
158 errors='backslashreplace') as included_file_handle:
159 included_text = included_file_handle.read()
160 break
161 return (included_file, included_text)
162
163 def get_pkg_info(pkg):
164 (license_file, license_text) = get_pkg_included_file(
165 pkg,
166 ('LICENSE*', 'LICENCE*', 'COPYING*')
167 )
168 (notice_file, notice_text) = get_pkg_included_file(
169 pkg,
170 ('NOTICE*',)
171 )
172 pkg_info = {
173 'name': pkg.project_name,
174 'version': pkg.version,
175 'namever': str(pkg),
176 'licensefile': license_file,
177 'licensetext': license_text,
178 'noticefile': notice_file,
179 'noticetext': notice_text,
180 }
181 metadata = None
182 if pkg.has_metadata('METADATA'):
183 metadata = pkg.get_metadata('METADATA')
184
185 if pkg.has_metadata('PKG-INFO') and metadata is None:
186 metadata = pkg.get_metadata('PKG-INFO')
187
188 if metadata is None:
189 for key in METADATA_KEYS:
190 pkg_info[key] = LICENSE_UNKNOWN
191
192 return pkg_info
193
194 feed_parser = FeedParser()
195 feed_parser.feed(metadata)
196 parsed_metadata = feed_parser.close()
197
198 for key in METADATA_KEYS:
199 pkg_info[key] = parsed_metadata.get(key, LICENSE_UNKNOWN)
200
201 if metadata is not None:
202 message = message_from_string(metadata)
203 pkg_info['license_classifier'] = \
204 find_license_from_classifier(message)
205
206 if args.filter_strings:
207 for k in pkg_info:
208 if isinstance(pkg_info[k], list):
209 for i, item in enumerate(pkg_info[k]):
210 pkg_info[k][i] = item. \
211 encode(args.filter_code_page, errors="ignore"). \
212 decode(args.filter_code_page)
213 else:
214 pkg_info[k] = pkg_info[k]. \
215 encode(args.filter_code_page, errors="ignore"). \
216 decode(args.filter_code_page)
217
218 return pkg_info
219
220 pkgs = get_installed_distributions()
221 ignore_pkgs_as_lower = [pkg.lower() for pkg in args.ignore_packages]
222 pkgs_as_lower = [pkg.lower() for pkg in args.packages]
223
224 fail_on_licenses = set()
225 if args.fail_on:
226 fail_on_licenses = set(map(str.strip, args.fail_on.split(";")))
227
228 allow_only_licenses = set()
229 if args.allow_only:
230 allow_only_licenses = set(map(str.strip, args.allow_only.split(";")))
231
232 for pkg in pkgs:
233 pkg_name = pkg.project_name
234
235 if pkg_name.lower() in ignore_pkgs_as_lower:
236 continue
237
238 if pkgs_as_lower and pkg_name.lower() not in pkgs_as_lower:
239 continue
240
241 if not args.with_system and pkg_name in SYSTEM_PACKAGES:
242 continue
243
244 pkg_info = get_pkg_info(pkg)
245
246 license_names = select_license_by_source(
247 args.from_,
248 pkg_info['license_classifier'],
249 pkg_info['license'])
250
251 if fail_on_licenses:
252 failed_licenses = license_names.intersection(fail_on_licenses)
253 if failed_licenses:
254 sys.stderr.write(
255 "fail-on license {} was found for package "
256 "{}:{}".format(
257 '; '.join(sorted(failed_licenses)),
258 pkg_info['name'],
259 pkg_info['version'])
260 )
261 sys.exit(1)
262
263 if allow_only_licenses:
264 uncommon_licenses = license_names.difference(allow_only_licenses)
265 if len(uncommon_licenses) == len(license_names):
266 sys.stderr.write(
267 "license {} not in allow-only licenses was found"
268 " for package {}:{}".format(
269 '; '.join(sorted(uncommon_licenses)),
270 pkg_info['name'],
271 pkg_info['version'])
272 )
273 sys.exit(1)
274
275 yield pkg_info
276
277
278 def create_licenses_list(
279 args: "CustomNamespace", output_fields=DEFAULT_OUTPUT_FIELDS):
280
281 licenses = []
282 for pkg in get_packages(args):
283 row = {}
284 for field in output_fields:
285 if field == 'License':
286 license_set = select_license_by_source(
287 args.from_, pkg['license_classifier'], pkg['license'])
288 license_str = '; '.join(sorted(license_set))
289 row[field] = license_str
290 ## row.append(license_str)
291 elif field == 'License-Classifier':
292 row[field] = ('; '.join(sorted(pkg['license_classifier']))
293 or LICENSE_UNKNOWN)
294 elif field.lower() in pkg:
295 row[field] = pkg[field.lower()]
296 else:
297 row[field] = pkg[FIELDS_TO_METADATA_KEYS[field]]
298 licenses.append(row)
299
300 return licenses
301
302
303 def create_summary_table(args: "CustomNamespace"):
304 counts = Counter(
305 '; '.join(sorted(select_license_by_source(
306 args.from_, pkg['license_classifier'], pkg['license'])))
307 for pkg in get_packages(args))
308
309 table = factory_styled_table_with_args(args, SUMMARY_FIELD_NAMES)
310 for license, count in counts.items():
311 table.add_row([count, license])
312 return table
313
314
315 class JsonTable():
316
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
458
459 def find_license_from_classifier(message):
460 licenses = []
461 for k, v in message.items():
462 if k == 'Classifier' and v.startswith('License'):
463 license = v.split(' :: ')[-1]
464
465 # Through the declaration of 'Classifier: License :: OSI Approved'
466 if license != 'OSI Approved':
467 licenses.append(license)
468
469 return licenses
470
471
472 def select_license_by_source(from_source, license_classifier, license_meta):
473 license_classifier_set = set(license_classifier) or {LICENSE_UNKNOWN}
474 if (from_source == FromArg.CLASSIFIER or
475 from_source == FromArg.MIXED and len(license_classifier) > 0):
476 return license_classifier_set
477 else:
478 return {license_meta}
479
480
481 def get_output_fields(args: "CustomNamespace"):
482 if args.summary:
483 return list(SUMMARY_OUTPUT_FIELDS)
484
485 output_fields = list(DEFAULT_OUTPUT_FIELDS)
486
487 if args.from_ == FromArg.ALL:
488 output_fields.append('License-Metadata')
489 output_fields.append('License-Classifier')
490 else:
491 output_fields.append('License')
492
493 if args.with_authors:
494 output_fields.append('Author')
495
496 if args.with_urls:
497 output_fields.append('URL')
498
499 if args.with_description:
500 output_fields.append('Description')
501
502 if args.with_license_file:
503 if not args.no_license_path:
504 output_fields.append('LicenseFile')
505
506 output_fields.append('LicenseText')
507
508 if args.with_notice_file:
509 output_fields.append('NoticeText')
510 if not args.no_license_path:
511 output_fields.append('NoticeFile')
512
513 return output_fields
514
515
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"):
532 output_fields = get_output_fields(args)
533
534 if args.summary:
535 table = create_summary_table(args)
536 else:
537 table = create_licenses_list(args, output_fields)
538
539 return json.dumps(table)
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
567
568 class CustomHelpFormatter(argparse.HelpFormatter): # pragma: no cover
569 def __init__(
570 self, prog: Text, indent_increment: int = 2,
571 max_help_position: int = 24, width: Optional[int] = None
572 ) -> None:
573 max_help_position = 30
574 super().__init__(
575 prog, indent_increment=indent_increment,
576 max_help_position=max_help_position, width=width)
577
578 def _format_action(self, action: argparse.Action) -> str:
579 flag_indent_argument: bool = False
580 text = self._expand_help(action)
581 separator_pos = text[:3].find('|')
582 if separator_pos != -1 and 'I' in text[:separator_pos]:
583 self._indent()
584 flag_indent_argument = True
585 help_str = super()._format_action(action)
586 if flag_indent_argument:
587 self._dedent()
588 return help_str
589
590 def _expand_help(self, action: argparse.Action) -> str:
591 if isinstance(action.default, Enum):
592 default_value = enum_key_to_value(action.default)
593 return self._get_help_string(action) % {'default': default_value}
594 return super()._expand_help(action)
595
596 def _split_lines(self, text: Text, width: int) -> List[str]:
597 separator_pos = text[:3].find('|')
598 if separator_pos != -1:
599 flag_splitlines: bool = 'R' in text[:separator_pos]
600 text = text[separator_pos + 1:]
601 if flag_splitlines:
602 return text.splitlines()
603 return super()._split_lines(text, width)
604
605
606 class CustomNamespace(argparse.Namespace):
607 from_: "FromArg"
608 order: "OrderArg"
609 format_: "FormatArg"
610 summary: bool
611 output_file: str
612 ignore_packages: List[str]
613 packages: List[str]
614 with_system: bool
615 with_authors: bool
616 with_urls: bool
617 with_description: bool
618 with_license_file: bool
619 no_license_path: bool
620 with_notice_file: bool
621 filter_strings: bool
622 filter_code_page: str
623 fail_on: Optional[str]
624 allow_only: Optional[str]
625
626
627 class CompatibleArgumentParser(argparse.ArgumentParser):
628 def parse_args(self, args: Optional[Sequence[Text]] = None,
629 namespace: CustomNamespace = None) -> CustomNamespace:
630 args = super().parse_args(args, namespace)
631 self._verify_args(args)
632 return args
633
634 def _verify_args(self, args: CustomNamespace):
635 if args.with_license_file is False and (
636 args.no_license_path is True or
637 args.with_notice_file is True):
638 self.error(
639 "'--no-license-path' and '--with-notice-file' require "
640 "the '--with-license-file' option to be set")
641 if args.filter_strings is False and \
642 args.filter_code_page != 'latin1':
643 self.error(
644 "'--filter-code-page' requires the '--filter-strings' "
645 "option to be set")
646 try:
647 codecs.lookup(args.filter_code_page)
648 except LookupError:
649 self.error(
650 "invalid code page '%s' given for '--filter-code-page, "
651 "check https://docs.python.org/3/library/codecs.html"
652 "#standard-encodings for valid code pages"
653 % args.filter_code_page)
654
655
656 class NoValueEnum(Enum):
657 def __repr__(self): # pragma: no cover
658 return '<%s.%s>' % (self.__class__.__name__, self.name)
659
660
661 class FromArg(NoValueEnum):
662 META = M = auto()
663 CLASSIFIER = C = auto()
664 MIXED = MIX = auto()
665 ALL = auto()
666
667
668 class OrderArg(NoValueEnum):
669 COUNT = C = auto()
670 LICENSE = L = auto()
671 NAME = N = auto()
672 AUTHOR = A = auto()
673 URL = U = auto()
674
675
676 class FormatArg(NoValueEnum):
677 PLAIN = P = auto()
678 PLAIN_VERTICAL = auto()
679 MARKDOWN = MD = M = auto()
680 RST = REST = R = auto()
681 CONFLUENCE = C = auto()
682 HTML = H = auto()
683 JSON = J = auto()
684 JSON_LICENSE_FINDER = JLF = auto()
685 CSV = auto()
686
687
688 def value_to_enum_key(value: str) -> str:
689 return value.replace('-', '_').upper()
690
691
692 def enum_key_to_value(enum_key: Enum) -> str:
693 return enum_key.name.replace('_', '-').lower()
694
695
696 def choices_from_enum(enum_cls: NoValueEnum) -> List[str]:
697 return [key.replace('_', '-').lower()
698 for key in enum_cls.__members__.keys()]
699
700
701 MAP_DEST_TO_ENUM = {
702 'from_': FromArg,
703 'order': OrderArg,
704 'format_': FormatArg,
705 }
706
707
708 class SelectAction(argparse.Action):
709 def __call__(
710 self, parser: argparse.ArgumentParser,
711 namespace: argparse.Namespace,
712 values: Text,
713 option_string: Optional[Text] = None,
714 ) -> None:
715 enum_cls = MAP_DEST_TO_ENUM[self.dest]
716 values = value_to_enum_key(values)
717 setattr(namespace, self.dest, getattr(enum_cls, values))
718
719
720 def create_parser():
721 parser = CompatibleArgumentParser(
722 description=__summary__,
723 formatter_class=CustomHelpFormatter)
724
725 common_options = parser.add_argument_group('Common options')
726 format_options = parser.add_argument_group('Format options')
727 verify_options = parser.add_argument_group('Verify options')
728
729 parser.add_argument(
730 '-v', '--version',
731 action='version',
732 version='%(prog)s ' + __version__)
733
734 common_options.add_argument(
735 '--from',
736 dest='from_',
737 action=SelectAction, type=str,
738 default=FromArg.MIXED, metavar='SOURCE',
739 choices=choices_from_enum(FromArg),
740 help='R|where to find license information\n'
741 '"meta", "classifier, "mixed", "all"\n'
742 '(default: %(default)s)')
743 common_options.add_argument(
744 '-o', '--order',
745 action=SelectAction, type=str,
746 default=OrderArg.NAME, metavar='COL',
747 choices=choices_from_enum(OrderArg),
748 help='R|order by column\n'
749 '"name", "license", "author", "url"\n'
750 '(default: %(default)s)')
751 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',
764 action='store_true',
765 default=False,
766 help='dump summary of each license')
767 common_options.add_argument(
768 '--output-file',
769 action='store', type=str,
770 help='save license list to file')
771 common_options.add_argument(
772 '-i', '--ignore-packages',
773 action='store', type=str,
774 nargs='+', metavar='PKG',
775 default=[],
776 help='ignore package name in dumped list')
777 common_options.add_argument(
778 '-p', '--packages',
779 action='store', type=str,
780 nargs='+', metavar='PKG',
781 default=[],
782 help='only include selected packages in output')
783 format_options.add_argument(
784 '-s', '--with-system',
785 action='store_true',
786 default=False,
787 help='dump with system packages')
788 format_options.add_argument(
789 '-a', '--with-authors',
790 action='store_true',
791 default=False,
792 help='dump with package authors')
793 format_options.add_argument(
794 '-u', '--with-urls',
795 action='store_true',
796 default=False,
797 help='dump with package urls')
798 format_options.add_argument(
799 '-d', '--with-description',
800 action='store_true',
801 default=False,
802 help='dump with short package description')
803 format_options.add_argument(
804 '-l', '--with-license-file',
805 action='store_true',
806 default=False,
807 help='dump with location of license file and '
808 'contents, most useful with JSON output')
809 format_options.add_argument(
810 '--no-license-path',
811 action='store_true',
812 default=False,
813 help='I|when specified together with option -l, '
814 'suppress location of license file output')
815 format_options.add_argument(
816 '--with-notice-file',
817 action='store_true',
818 default=False,
819 help='I|when specified together with option -l, '
820 'dump with location of license file and contents')
821 format_options.add_argument(
822 '--filter-strings',
823 action="store_true",
824 default=False,
825 help='filter input according to code page')
826 format_options.add_argument(
827 '--filter-code-page',
828 action="store", type=str,
829 default="latin1",
830 metavar="CODE",
831 help='I|specify code page for filtering '
832 '(default: %(default)s)')
833
834 verify_options.add_argument(
835 '--fail-on',
836 action='store', type=str,
837 default=None,
838 help='fail (exit with code 1) on the first occurrence '
839 'of the licenses of the semicolon-separated list')
840 verify_options.add_argument(
841 '--allow-only',
842 action='store', type=str,
843 default=None,
844 help='fail (exit with code 1) on the first occurrence '
845 'of the licenses not in the semicolon-separated list')
846
847 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
876
877 def main(): # pragma: no cover
878 parser = create_parser()
879 args = parser.parse_args()
880
881 output_string = create_output_string(args)
882
883 ## output_file = args.output_file
884 ## save_if_needs(output_file, output_string)
885 ##
886 print(output_string)
887 ## warn_string = create_warn_string(args)
888 ## if warn_string:
889 ## print(warn_string, file=sys.stderr)
890
891
892 if __name__ == '__main__': # pragma: no cover
893 main()

eric ide

mercurial