diff --git a/licomp_toolkit/__main__.py b/licomp_toolkit/__main__.py index 3449457..3e8de23 100755 --- a/licomp_toolkit/__main__.py +++ b/licomp_toolkit/__main__.py @@ -42,6 +42,26 @@ def validate(self, args): LicompToolkitSchemaChecker().validate_file(args.file_name, deep=True) return None, ReturnCodes.LICOMP_OK.value, None + def _resources_to_use(self, args): + lt = LicompToolkit() + resources = args.resources + new_resources = [] + unsupported = [] + if args.resources == ['all']: + new_resources = list(lt.licomp_resources().keys()) + return new_resources, [] + for resource in resources: + if 'licomp' not in resource: + resource = f'licomp_{resource}' + else: + resource = resource.replace('-', '_') + + if not self.resource_avilable(resource): + unsupported.append(resource) + else: + new_resources.append(resource) + return new_resources, unsupported + def verify(self, args): formatter = LicompToolkitFormatter.formatter(self.args.output_format) try: @@ -50,22 +70,9 @@ def verify(self, args): else: detailed_report = True - resources = args.resources - new_resources = [] - unsupported = [] - for resource in resources: - if 'licomp' not in resource: - resource = f'licomp_{resource}' - else: - resource = resource.replace('-', '_') - - if not self.resource_avilable(resource): - unsupported.append(resource) - else: - new_resources.append(resource) + resources, unsupported = self._resources_to_use(args) if unsupported: return f'Resource(s) {", ".join(unsupported)} is/are not supported', ReturnCodes.LICOMP_UNSUPPORTED_RESOURCE.value, True - resources = new_resources expr_checker = ExpressionExpressionChecker() compatibilities = expr_checker.check_compatibility(self.__normalize_license(args.out_license), self.__normalize_license(args.in_license), @@ -106,22 +113,30 @@ def simplify(self, args): def supported_resources(self, args): formatter = LicompToolkitFormatter.formatter(args.output_format) - return formatter.format_licomp_resources([f'{x.name()}:{x.version()}' for x in self.licomp_toolkit.licomp_resources().values()]), ReturnCodes.LICOMP_OK.value, False + return formatter.format_licomp_resources(self.licomp_toolkit.licomp_resources_long()), ReturnCodes.LICOMP_OK.value, False + + def _supports_helper(self, key, value, output_format): + licomp_resources = [x for x in self.licomp_toolkit.licomp_resources_long() if value in x[key]] + formatter = LicompToolkitFormatter.formatter(output_format) + return formatter.format_licomp_resources(licomp_resources) def supports_license(self, args): lic = args.license - licomp_resources = [f'{x.name()}:{x.version()}' for x in self.licomp_toolkit.licomp_resources().values() if lic in x.supported_licenses()] - formatter = LicompToolkitFormatter.formatter(args.output_format) - return formatter.format_licomp_resources(licomp_resources), ReturnCodes.LICOMP_OK.value, None + if lic not in self.licomp_toolkit.supported_licenses(): + return None, ReturnCodes.LICOMP_UNSUPPORTED_LICENSE.value, f'License "{args.license}" not supported.' + return self._supports_helper('licenses', lic, args.output_format), ReturnCodes.LICOMP_OK.value, None def supports_usecase(self, args): - try: - usecase = UseCase.string_to_usecase(args.usecase) - licomp_resources = [f'{x.name()}:{x.version()}' for x in self.licomp_toolkit.licomp_resources().values() if usecase in x.supported_usecases()] - formatter = LicompToolkitFormatter.formatter(args.output_format) - return formatter.format_licomp_resources(licomp_resources), ReturnCodes.LICOMP_OK.value, None - except KeyError: - return None, f'Use case "{args.usecase}" not supported. Supported use cases: {self.supported_usecases(args)[0]}' + usecase = args.usecase + if usecase not in [UseCase.usecase_to_string(x) for x in self.licomp_toolkit.supported_usecases()]: + return None, ReturnCodes.LICOMP_UNSUPPORTED_USECASE.value, f'Use case "{args.usecase}" not supported. Supported use cases: {self.supported_usecases(args)[0]}' + return self._supports_helper('usecases', usecase, args.output_format), ReturnCodes.LICOMP_OK.value, None + + def supports_provisioning(self, args): + provisioning = args.provisioning + if provisioning not in [Provisioning.provisioning_to_string(x) for x in self.licomp_toolkit.supported_provisionings()]: + return None, ReturnCodes.LICOMP_UNSUPPORTED_PROVISIONING.value, f'Provisioning "{args.provisioning}" not supported. Supported provisionings: {self.supported_provisionings(args)[0]}' + return self._supports_helper('provisionings', provisioning, args.output_format), ReturnCodes.LICOMP_OK.value, None def outbound_candidate(self, args): suggester = OutboundSuggester() @@ -152,15 +167,6 @@ def display_compatibility(self, args): {'discard_unsupported': args.discard_unsupported_licenses}) return formatted, ReturnCodes.LICOMP_OK.value, None - def supports_provisioning(self, args): - try: - provisioning = Provisioning.string_to_provisioning(args.provisioning) - licomp_resources = [f'{x.name()}:{x.version()}' for x in self.licomp_toolkit.licomp_resources().values() if provisioning in x.supported_provisionings()] - formatter = LicompToolkitFormatter.formatter(args.output_format) - return formatter.format_licomp_resources(licomp_resources), ReturnCodes.LICOMP_OK.value, None - except KeyError: - return None, f'Provisioning "{args.provisioning}" not supported. Supported provisionings: {self.supported_provisionings(args)[0]}' - def versions(self, args): formatter = LicompToolkitFormatter.formatter(args.output_format) return formatter.format_licomp_versions(self.licomp_toolkit.versions()), ReturnCodes.LICOMP_OK.value, False @@ -169,7 +175,7 @@ def resource_avilable(self, resource): return resource in self.licomp_toolkit.licomp_resources().keys() def _working_return_code(return_code): - return return_code < ReturnCodes.LICOMP_LAST_SUCCESSFUL_CODE.value + return return_code >= 0 and return_code < ReturnCodes.LICOMP_LAST_SUCCESSFUL_CODE.value def main(): logging.debug("Licomp Toolkit") @@ -186,7 +192,7 @@ def main(): parser.add_argument('-r', '--resources', type=str, action='append', - help='use only specified licomp resource', + help='use specified licomp resource. For a list use the commands \'supported-resources\'. Use \'all\' to use all', default=[]) parser.add_argument('-nv', '--no-verbose', @@ -245,6 +251,11 @@ def main(): if _working_return_code(code): if res: print(res) + else: + if err: + print(err, file=sys.stderr) + else: + pass else: print(res, file=sys.stderr) diff --git a/licomp_toolkit/format.py b/licomp_toolkit/format.py index fb2d1cb..c25e1f1 100644 --- a/licomp_toolkit/format.py +++ b/licomp_toolkit/format.py @@ -93,25 +93,33 @@ def format_display_compatibilities(self, compats, settings={}): class TextLicompToolkitFormatter(LicompToolkitFormatter): + def _format_licomp_resource(self, licomp_resource): + name = licomp_resource['name'] + version = licomp_resource['version'] + usecases = ','.join(licomp_resource['usecases']) + provisionings = ','.join(licomp_resource['provisionings']) + resource_type = licomp_resource['type'] + return f'{name}:{version}:{usecases}:{provisionings}:{resource_type}' + def format_licomp_resources(self, licomp_resources): - return "\n".join(licomp_resources) + return '\n'.join([self._format_licomp_resource(x) for x in licomp_resources]) def format_licomp_licenses(self, licomp_licenses): - return "\n".join(licomp_licenses) + return '\n'.join(licomp_licenses) - def __get_responses(self, results, indent=""): + def __get_responses(self, results, indent=''): output = [] for res in ['yes', 'no', 'schneben']: result = results.get(res) if not result: count = 0 else: - count = result["count"] + count = result['count'] output.append(f'{indent}{res}: {count}') return output - def __compatibility_statuses(self, statuses, indent=""): + def __compatibility_statuses(self, statuses, indent=''): output = [] for status, values in statuses.items(): resources = [] @@ -121,7 +129,7 @@ def __compatibility_statuses(self, statuses, indent=""): return output - def __statuses(self, statuses, indent=""): + def __statuses(self, statuses, indent=''): output = [] for status, values in statuses.items(): resources = [] diff --git a/licomp_toolkit/lic_expr.py b/licomp_toolkit/lic_expr.py index f396a2c..7310a8e 100644 --- a/licomp_toolkit/lic_expr.py +++ b/licomp_toolkit/lic_expr.py @@ -241,7 +241,7 @@ def summarise_compatibilities(self, operator, operands): OR: self.__summarise_compatibilities_or, }[operator](operands) -class ExpressionExpressionChecker(): +class _OBSOLETE_ExpressionExpressionChecker(): def __init__(self): self.le_checker = LicenseExpressionChecker() @@ -251,7 +251,6 @@ def __parsed_expression_to_name(self, parsed_expression): return parsed_expression[parsed_expression[COMPATIBILITY_TYPE]] def check_compatibility(self, outbound, inbound, usecase, provisioning, detailed_report=False): - inbound_parsed = self.le_parser.parse_license_expression(inbound) outbound_parsed = self.le_parser.parse_license_expression(outbound) diff --git a/licomp_toolkit/toolkit.py b/licomp_toolkit/toolkit.py index faf2f3d..c86df09 100644 --- a/licomp_toolkit/toolkit.py +++ b/licomp_toolkit/toolkit.py @@ -41,25 +41,15 @@ class LicompToolkit(Licomp): def __init__(self): Licomp.__init__(self) - self.LICOMP_RESOURCES = {} - self.LICOMP_RESOURCE_NAMES = { - "osadl": { - "package": "licomp_osadl.osadl", - "class": "LicompOsadl", - }, - "reclicense": { - "package": "licomp_reclicense.reclicense", - "class": "LicompReclicense", - }, - "hermione": { - "package": "licomp_hermione.hermione", - "class": "LicompHermione", - }, - "dwheeler": { - "package": "licomp_dwheeler.dwheeler", - "class": "LicompDw", - }, - } + self._licomp_resources = {} + for licomp in [LicompReclicense, LicompOsadl]: + licomp_instance = licomp() + self._licomp_resources[licomp_instance.name()] = licomp_instance + + self._licomp_resources_optional = {} + for licomp in [LicompHermione, LicompProprietary, LicompDw, GnuQuickGuideLicense]: + licomp_instance = licomp() + self._licomp_resources_optional[licomp_instance.name()] = licomp_instance def supported_api_version(self): return my_supported_api_version @@ -76,24 +66,53 @@ def __add_meta(self, compatibilities): compatibilities["meta"]['disclaimer'] = disclaimer def licomp_resources(self): - if not self.LICOMP_RESOURCES: - for licomp in [LicompReclicense, LicompOsadl, LicompHermione, LicompProprietary, LicompDw, GnuQuickGuideLicense]: - licomp_instance = licomp() - self.LICOMP_RESOURCES[licomp_instance.name()] = licomp_instance - return self.LICOMP_RESOURCES + return self._licomp_resources | self._licomp_resources_optional + + def licomp_standard_resources(self): + return self._licomp_resources + + def licomp_optional_resources(self): + return self._licomp_resources_optional + + def licomp_resource_long(self, resource): + return { + 'name': resource.name(), + 'version': resource.version(), + 'usecases': [UseCase.usecase_to_string(x) for x in resource.supported_usecases()], + 'provisionings': [Provisioning.provisioning_to_string(x) for x in resource.supported_provisionings()], + 'licenses': resource.supported_licenses(), + 'type': self._resource_type(resource), + } + + def licomp_resources_long(self): + _resources = [] + for resource in self.licomp_resources().values(): + _resources.append(self.licomp_resource_long(resource)) + return _resources + + def _resource_type(self, resource): + if self._resource_is_standard(resource): + return 'standard' + return 'optional' + + def _resource_is_optional(self, resource): + return resource.name() in self._licomp_resources_optional + + def _resource_is_standard(self, resource): + return not self._resource_is_optional(resource) def __summarize_compatibility(self, compatibilities, outbound, inbound, usecase, provisioning, resources): compatibilities["summary"] = {} statuses = {} compats = {} compatibilities['nr_licomp'] = len(resources) - # for resource_name in self.licomp_resources(): + # for resource_name in self._licomp_resources(): for compat in compatibilities["compatibilities"]: logging.debug(f': {compat}') logging.debug(f': {compat["resource_name"]}') self.__add_to_list(statuses, compat['status'], compat) self.__add_to_list(compats, compat['compatibility_status'], compat) - compatibilities["summary"]["resources"] = [f'{x.name()}:{x.version()}' for x in self.licomp_resources().values()] + compatibilities["summary"]["resources"] = self.licomp_resources_long() compatibilities["summary"]["outbound"] = outbound compatibilities["summary"]["inbound"] = inbound compatibilities["summary"]["usecase"] = UseCase.usecase_to_string(usecase) @@ -337,7 +356,7 @@ def check_compatibility(self, outbound, inbound, usecase, provisioning, resource except KeyError: raise LicompException(f'Provisioning {provisioning} not supported.', ReturnCodes.LICOMP_UNSUPPORTED_PROVISIONING) - licomp_resources = list(self.licomp_toolkit.licomp_resources().keys()) + licomp_resources = list(self.licomp_toolkit.licomp_standard_resources().keys()) if not resources: resources = licomp_resources else: diff --git a/licomp_toolkit_test.tmp b/licomp_toolkit_test.tmp new file mode 100644 index 0000000..e101afb --- /dev/null +++ b/licomp_toolkit_test.tmp @@ -0,0 +1,6 @@ +digraph depends { + graph [label="License Compatibility Graph (library)" labelloc=t] + node [shape=plaintext] + "MIT" -> "BSD-3-Clause" [color="darkblue" style="dotted"] + "BSD-3-Clause" -> "MIT" [color="darkgreen" ] +} diff --git a/tests/python/test_resources.py b/tests/python/test_resources.py index 8284958..d21c2fc 100644 --- a/tests/python/test_resources.py +++ b/tests/python/test_resources.py @@ -14,11 +14,13 @@ lt = LicompToolkit() resource_names = [x for x in lt.licomp_resources()] +standard_resource_names = [x for x in lt.licomp_standard_resources()] +optional_resource_names = [x for x in lt.licomp_optional_resources()] def test_supported_resources(): assert len(lt.licomp_resources()) == 6 -def test_supported_resources_reclicense(): +def test_supported_resources(): assert "licomp_reclicense" in resource_names assert "licomp_osadl" in resource_names assert "licomp_proprietary" in resource_names @@ -26,3 +28,18 @@ def test_supported_resources_reclicense(): assert "licomp_dwheeler" in resource_names assert "licomp_gnuguide" in resource_names +def test_supported_resources_standard(): + assert "licomp_reclicense" in standard_resource_names + assert "licomp_osadl" in standard_resource_names + assert "licomp_proprietary" not in standard_resource_names + assert "licomp_hermione" not in standard_resource_names + assert "licomp_dwheeler" not in standard_resource_names + assert "licomp_gnuguide" not in standard_resource_names + +def test_supported_resources_standard(): + assert "licomp_reclicense" not in optional_resource_names + assert "licomp_osadl" not in optional_resource_names + assert "licomp_proprietary" in optional_resource_names + assert "licomp_hermione" in optional_resource_names + assert "licomp_dwheeler" in optional_resource_names + assert "licomp_gnuguide" in optional_resource_names diff --git a/tests/shell/test-cli.sh b/tests/shell/test-cli.sh index cc5f975..e3b6ace 100755 --- a/tests/shell/test-cli.sh +++ b/tests/shell/test-cli.sh @@ -72,7 +72,7 @@ test_version() test_supp_unsupp() { echo "# test supported/unsupported licenses" - test_licomp_tk "verify -il MIT -ol MIT" "${EXTRACT_COMPAT}.summary.results.yes.count" 5 + test_licomp_tk "verify -il MIT -ol MIT" "${EXTRACT_COMPAT}.summary.results.yes.count" 1 test_licomp_tk "verify -il MIT -ol MIT2" "${EXTRACT_COMPAT}.summary.results.nr_valid" 0 test_licomp_tk "verify -il MIT2 -ol MIT" "${EXTRACT_COMPAT}.summary.results.nr_valid" 0 test_licomp_tk "verify -il MIT2 -ol MIT2" "${EXTRACT_COMPAT}.summary.results.nr_valid" 0 @@ -94,16 +94,16 @@ test_snippet_bindist() echo "# snippet vs bin dist" test_licomp_tk "-u snippet verify -il BSD-3-Clause -ol LGPL-2.1-or-later" "${EXTRACT_COMPAT}.summary.results.nr_valid" 1 test_licomp_tk "-u snippet verify -il BSD-3-Clause -ol LGPL-2.1-or-later" "${EXTRACT_COMPAT}.summary.results.yes.count" 1 - test_licomp_tk "verify -il BSD-3-Clause -ol LGPL-2.1-or-later" "${EXTRACT_COMPAT}.summary.results.nr_valid" 4 - test_licomp_tk "verify -il BSD-3-Clause -ol LGPL-2.1-or-later" "${EXTRACT_COMPAT}.summary.results.yes.count" 4 + test_licomp_tk "verify -il BSD-3-Clause -ol LGPL-2.1-or-later" "${EXTRACT_COMPAT}.summary.results.nr_valid" 1 + test_licomp_tk "verify -il BSD-3-Clause -ol LGPL-2.1-or-later" "${EXTRACT_COMPAT}.summary.results.yes.count" 1 } test_supports_license() { echo "# supports license" test_licomp_tk_text " -of text supports-license MIT" " | wc -l" 6 - test_licomp_tk_text " -of json supports-license MIT" " | jq .[] | wc -l" 6 - test_licomp_tk_text " supports-license MIT" " | jq .[] | wc -l" 6 + test_licomp_tk_text " -of json supports-license MIT" " | jq .[].name | wc -l" 6 + test_licomp_tk_text " supports-license MIT" " | jq .[].name | wc -l" 6 } test_supports_provisioning() diff --git a/tests/shell/test_returns.sh b/tests/shell/test_returns.sh index 460ea2f..dcffdf6 100755 --- a/tests/shell/test_returns.sh +++ b/tests/shell/test_returns.sh @@ -47,7 +47,7 @@ test_verify() run_comp_test 0 "verify -il BSD-3-Clause -ol GPL-2.0-only" # Success and mixed compatibility - run_comp_test 9 "verify -ol 0BSD -il MS-PL" + run_comp_test 2 "verify -ol 0BSD -il MS-PL" # Success and incompatible run_comp_test 2 "verify -il GPL-2.0-only -ol BSD-3-Clause" diff --git a/tests/shell/test_validate.sh b/tests/shell/test_validate.sh index e4a2851..b92df2a 100755 --- a/tests/shell/test_validate.sh +++ b/tests/shell/test_validate.sh @@ -38,7 +38,8 @@ validate_reply() OUTBOUND="$2" VERIFY_EXPECTED=$3 VALIDATE_EXPECTED=$4 - PYTHONPATH=$IMPLEMENTAIONS:${PYTHONPATH}:. python3 licomp_toolkit/__main__.py verify -il "$INBOUND" -ol "$OUTBOUND" > $REPLY_FILE + RESOURCE_ARGS="$5" + PYTHONPATH=$IMPLEMENTAIONS:${PYTHONPATH}:. python3 licomp_toolkit/__main__.py $RESOURCE_ARGS verify -il "$INBOUND" -ol "$OUTBOUND" > $REPLY_FILE RET=$? # echo " ---------------------------||||| RET: $RET == $VERIFY_EXPECTED" printf "%-80s" "verify -il \"$INBOUND\" -ol \"$OUTBOUND\"" @@ -63,9 +64,7 @@ compatibles() validate_reply "BSD-3-Clause OR GPL-2.0-only" "BSD-2-Clause AND ISC" 0 0 validate_reply "BSD-3-Clause OR GPL-2.0-only" "BSD-2-Clause AND Apache-2.0" 0 0 validate_reply "BSD-2-Clause OR Apache-2.0" "GPL-2.0-only" 0 0 - # 9 is mixed - validate_reply "Apache-2.0" "GPL-3.0-only" 9 0 - # 9 is no + validate_reply "Apache-2.0" "GPL-3.0-only" 0 0 validate_reply "GPL-3.0-only" "Apache-2.0" 2 0 } @@ -77,7 +76,8 @@ incompatibles() validate_reply "BSD-3-Clause AND GPL-2.0-only" "BSD-2-Clause AND Apache-2.0" 2 0 validate_reply "GPL-2.0-only" "BSD-2-Clause AND Apache-2.0" 2 0 validate_reply "BSD-2-Clause AND Apache-2.0" "GPL-2.0-only" 2 0 - validate_reply "Apache-2.0" "GPL-2.0-only" 2 0 + validate_reply "Apache-2.0" "GPL-2.0-only" 2 0 + validate_reply "Apache-2.0" "GPL-3.0-only" 9 0 " -r all" } echo "Compatibles"