licomp-toolkit 0.6.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- licomp_toolkit/__init__.py +0 -0
- licomp_toolkit/__main__.py +322 -0
- licomp_toolkit/config.py +40 -0
- licomp_toolkit/data/reply_schema.json +305 -0
- licomp_toolkit/display_compatibility.py +21 -0
- licomp_toolkit/exception.py +11 -0
- licomp_toolkit/expr_parser.py +139 -0
- licomp_toolkit/format.py +362 -0
- licomp_toolkit/lic_expr.py +325 -0
- licomp_toolkit/license_policy.py +564 -0
- licomp_toolkit/return_codes.py +12 -0
- licomp_toolkit/schema_checker.py +67 -0
- licomp_toolkit/suggester.py +162 -0
- licomp_toolkit/toolkit.py +504 -0
- licomp_toolkit/utils.py +81 -0
- licomp_toolkit-0.6.3.dist-info/METADATA +164 -0
- licomp_toolkit-0.6.3.dist-info/RECORD +21 -0
- licomp_toolkit-0.6.3.dist-info/WHEEL +5 -0
- licomp_toolkit-0.6.3.dist-info/entry_points.txt +2 -0
- licomp_toolkit-0.6.3.dist-info/licenses/LICENSES/GPL-3.0-or-later.txt +232 -0
- licomp_toolkit-0.6.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2024 Henrik Sandklef
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
class DisplayCompatibility:
|
|
6
|
+
|
|
7
|
+
def __init__(self, licomp_toolkit):
|
|
8
|
+
self.licomp_toolkit = licomp_toolkit
|
|
9
|
+
|
|
10
|
+
def display_compatibility(self, licenses, usecase, provisioning, resources):
|
|
11
|
+
compats = {}
|
|
12
|
+
for outbound in licenses:
|
|
13
|
+
compats[outbound] = {}
|
|
14
|
+
for inbound in licenses:
|
|
15
|
+
ret = self.licomp_toolkit.outbound_inbound_compatibility(outbound,
|
|
16
|
+
inbound,
|
|
17
|
+
usecase,
|
|
18
|
+
provisioning,
|
|
19
|
+
resources)
|
|
20
|
+
compats[outbound][inbound] = ret
|
|
21
|
+
return compats
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Henrik Sandklef
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
class LicompToolkitException(Exception):
|
|
6
|
+
|
|
7
|
+
def __init__(self, message, error_code, orig_exception=None):
|
|
8
|
+
self.message = message
|
|
9
|
+
super().__init__(self.message)
|
|
10
|
+
self.error_code = error_code
|
|
11
|
+
self.original_exception = orig_exception
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Henrik Sandklef
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from license_expression import Licensing
|
|
8
|
+
from licomp.interface import LicompException
|
|
9
|
+
from licomp.return_codes import ReturnCodes
|
|
10
|
+
|
|
11
|
+
AND = "AND"
|
|
12
|
+
OR = "OR"
|
|
13
|
+
|
|
14
|
+
COMPATIBILITY_TYPE = 'compatibility_type'
|
|
15
|
+
|
|
16
|
+
class LicenseExpressionParser():
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.licensing = Licensing([])
|
|
20
|
+
|
|
21
|
+
self.CLOSE_PARENTHESIS = ")"
|
|
22
|
+
self.LICENSE_SYMBOL = "LicenseSymbol"
|
|
23
|
+
self.LICENSE_WITH_SYMBOL = "LicenseWithExceptionSymbol"
|
|
24
|
+
|
|
25
|
+
def parse_license_expression(self, expression):
|
|
26
|
+
if not expression:
|
|
27
|
+
raise LicompException("No license provided: " + str(expression), ReturnCodes.LICOMP_PARSE_ERROR)
|
|
28
|
+
p = self.__parse_expression(self.licensing.parse(expression).pretty().replace('\n', ' '))
|
|
29
|
+
return p
|
|
30
|
+
|
|
31
|
+
def __is_license_with_exception(self, expression):
|
|
32
|
+
return expression.strip().startswith(self.LICENSE_WITH_SYMBOL)
|
|
33
|
+
|
|
34
|
+
def __is_license(self, expression, with_exception=False):
|
|
35
|
+
if with_exception:
|
|
36
|
+
return expression.strip().startswith(self.LICENSE_SYMBOL) or expression.strip().startswith(self.LICENSE_WITH_SYMBOL)
|
|
37
|
+
return expression.strip().startswith(self.LICENSE_SYMBOL)
|
|
38
|
+
|
|
39
|
+
def __is_operator(self, expression):
|
|
40
|
+
return expression.startswith(AND) or expression.startswith(OR)
|
|
41
|
+
|
|
42
|
+
def __get_operator(self, expression):
|
|
43
|
+
if expression.startswith(AND):
|
|
44
|
+
return AND
|
|
45
|
+
if expression.startswith(OR):
|
|
46
|
+
return OR
|
|
47
|
+
raise Exception("BAD EXPRESSION----")
|
|
48
|
+
|
|
49
|
+
def __get_operands_string(self, expression):
|
|
50
|
+
# length of the operator and parenthesis
|
|
51
|
+
op = self.__get_operator(expression)
|
|
52
|
+
op_size = len(op) + 1
|
|
53
|
+
|
|
54
|
+
# nr characters until closing (operator) parenthesis
|
|
55
|
+
left_parens = 1
|
|
56
|
+
operand_size = 1
|
|
57
|
+
for c in expression[op_size:]:
|
|
58
|
+
operand_size += 1
|
|
59
|
+
if c == '(':
|
|
60
|
+
left_parens += 1
|
|
61
|
+
elif c == ')':
|
|
62
|
+
left_parens -= 1
|
|
63
|
+
|
|
64
|
+
if left_parens == 0:
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
rest = expression[op_size:operand_size + 1]
|
|
68
|
+
remains = expression[operand_size + 4:]
|
|
69
|
+
return rest, remains
|
|
70
|
+
|
|
71
|
+
def is_close(self, expression):
|
|
72
|
+
return expression.startswith(self.CLOSE_PARENTHESIS)
|
|
73
|
+
|
|
74
|
+
def __cleanup_license(self, operand):
|
|
75
|
+
stripped_operand = operand.strip()
|
|
76
|
+
|
|
77
|
+
if self.__is_license_with_exception(operand):
|
|
78
|
+
trimmed_operand = stripped_operand.replace(f"{self.LICENSE_WITH_SYMBOL}('", '', 1)
|
|
79
|
+
else:
|
|
80
|
+
trimmed_operand = stripped_operand.replace(f"{self.LICENSE_SYMBOL}('", '', 1)
|
|
81
|
+
closing_paren_index = trimmed_operand.find(")")
|
|
82
|
+
op = trimmed_operand[:closing_paren_index - 1]
|
|
83
|
+
remains = trimmed_operand[closing_paren_index + 1:].strip()
|
|
84
|
+
if remains.startswith(","):
|
|
85
|
+
remains = remains[1:]
|
|
86
|
+
return op, remains.strip()
|
|
87
|
+
|
|
88
|
+
def __parse_expression(self, expression):
|
|
89
|
+
logging.debug(f'__parse_expression: {expression}')
|
|
90
|
+
|
|
91
|
+
if self.__is_operator(expression):
|
|
92
|
+
operator = self.__get_operator(expression)
|
|
93
|
+
operands = []
|
|
94
|
+
ops, remains = self.__get_operands_string(expression)
|
|
95
|
+
while ops != "":
|
|
96
|
+
if self.__is_license(ops.strip(), with_exception=True):
|
|
97
|
+
operand, rem = self.__cleanup_license(ops)
|
|
98
|
+
operands.append({
|
|
99
|
+
COMPATIBILITY_TYPE: "license",
|
|
100
|
+
'license': operand,
|
|
101
|
+
})
|
|
102
|
+
ops = rem
|
|
103
|
+
|
|
104
|
+
elif self.__is_operator(ops.strip()):
|
|
105
|
+
operand = self.__parse_expression(ops.strip())
|
|
106
|
+
_ops, _remains = self.__get_operands_string(ops.strip())
|
|
107
|
+
operands.append(operand)
|
|
108
|
+
ops = _remains
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
raise LicompException(f'Failed parsing expression "{ops}". Complete expression "{expression}"', ReturnCodes.LICOMP_PARSE_ERROR)
|
|
112
|
+
return {
|
|
113
|
+
COMPATIBILITY_TYPE: 'expression',
|
|
114
|
+
"operator": operator,
|
|
115
|
+
"operands": operands,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
elif self.__is_license(expression, with_exception=True):
|
|
119
|
+
cleaned_up, rem = self.__cleanup_license(expression.strip())
|
|
120
|
+
return {
|
|
121
|
+
COMPATIBILITY_TYPE: "license",
|
|
122
|
+
'license': cleaned_up,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
elif self.__is_close(expression):
|
|
126
|
+
return ""
|
|
127
|
+
|
|
128
|
+
raise Exception("Bottom reached in __parse_expression (expr_parser.py)")
|
|
129
|
+
|
|
130
|
+
def to_string(self, parsed_license):
|
|
131
|
+
license_type = parsed_license['compatibility_type']
|
|
132
|
+
if license_type == 'license':
|
|
133
|
+
return parsed_license['license']
|
|
134
|
+
|
|
135
|
+
operator = parsed_license['operator']
|
|
136
|
+
license_expression = []
|
|
137
|
+
for operand in parsed_license['operands']:
|
|
138
|
+
license_expression.append(f' ( {self.to_string(operand)} ) ')
|
|
139
|
+
return str(self.licensing.parse(operator.join(license_expression)))
|
licomp_toolkit/format.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Henrik Sandklef
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
class LicompToolkitFormatter():
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def formatter(fmt):
|
|
12
|
+
if fmt.lower() == 'json':
|
|
13
|
+
return JsonLicompToolkitFormatter()
|
|
14
|
+
if fmt.lower() == 'text':
|
|
15
|
+
return TextLicompToolkitFormatter()
|
|
16
|
+
if fmt.lower() == 'yaml' or fmt.lower() == 'yml':
|
|
17
|
+
return YamlLicompToolkitFormatter()
|
|
18
|
+
if fmt.lower() == 'dot':
|
|
19
|
+
return DotLicompToolkitFormatter()
|
|
20
|
+
|
|
21
|
+
def format_compatibilities(self, compat, verbose=False):
|
|
22
|
+
raise Exception(f'{self.__class__.__name__} cannot format compatibilities.')
|
|
23
|
+
|
|
24
|
+
def _pre_format_display_compatibilities(self, compats):
|
|
25
|
+
licenses = list(compats.keys())
|
|
26
|
+
finished = {}
|
|
27
|
+
for outbound in licenses:
|
|
28
|
+
finished[outbound] = {}
|
|
29
|
+
for inbound in licenses:
|
|
30
|
+
finished[outbound][inbound] = False
|
|
31
|
+
|
|
32
|
+
for outbound in licenses:
|
|
33
|
+
for inbound in licenses:
|
|
34
|
+
if finished[outbound][inbound] and finished[inbound][outbound]:
|
|
35
|
+
continue
|
|
36
|
+
if outbound == inbound:
|
|
37
|
+
finished[outbound][inbound] = ['yes']
|
|
38
|
+
finished[inbound][outbound] = ['yes']
|
|
39
|
+
else:
|
|
40
|
+
outbound_compat = compats[outbound][inbound]['summary']['compatibility_statuses']
|
|
41
|
+
inbound_compat = compats[inbound][outbound]['summary']['compatibility_statuses']
|
|
42
|
+
finished[outbound][inbound] = list(outbound_compat.keys())
|
|
43
|
+
finished[inbound][outbound] = list(inbound_compat.keys())
|
|
44
|
+
|
|
45
|
+
return finished
|
|
46
|
+
|
|
47
|
+
def format_licomp_resources(self, licomp_resources):
|
|
48
|
+
raise Exception(f'{self.__class__.__name__} cannot format licomp resources.')
|
|
49
|
+
|
|
50
|
+
def format_licomp_licenses(self, licomp_licenses):
|
|
51
|
+
raise Exception(f'{self.__class__.__name__} cannot format licomp licenses.')
|
|
52
|
+
|
|
53
|
+
def format_licomp_versions(self, licomp_versions):
|
|
54
|
+
raise Exception(f'{self.__class__.__name__} cannot format licomp versions.')
|
|
55
|
+
|
|
56
|
+
class JsonLicompToolkitFormatter(LicompToolkitFormatter):
|
|
57
|
+
|
|
58
|
+
def format_compatibilities(self, compat, verbose=False):
|
|
59
|
+
return json.dumps(compat, indent=4)
|
|
60
|
+
|
|
61
|
+
def format_licomp_resources(self, licomp_resources):
|
|
62
|
+
return json.dumps(licomp_resources, indent=4)
|
|
63
|
+
|
|
64
|
+
def format_licomp_licenses(self, licomp_licenses):
|
|
65
|
+
return json.dumps(licomp_licenses, indent=4)
|
|
66
|
+
|
|
67
|
+
def format_licomp_versions(self, licomp_versions):
|
|
68
|
+
return json.dumps(licomp_versions, indent=4)
|
|
69
|
+
|
|
70
|
+
def format_display_compatibilities(self, compats, settings={}): # noqa: B006
|
|
71
|
+
# settings
|
|
72
|
+
# discard_unsupported: True - will remove unsupported licenses from the output
|
|
73
|
+
display_compats = self._pre_format_display_compatibilities(compats)
|
|
74
|
+
return json.dumps(display_compats, indent=4)
|
|
75
|
+
|
|
76
|
+
def format_policy_report(self, report, verbose=False):
|
|
77
|
+
return json.dumps(report, indent=4)
|
|
78
|
+
|
|
79
|
+
class YamlLicompToolkitFormatter(LicompToolkitFormatter):
|
|
80
|
+
|
|
81
|
+
def format_compatibilities(self, compat, verbose=False):
|
|
82
|
+
return yaml.safe_dump(compat, indent=4)
|
|
83
|
+
|
|
84
|
+
def format_policy_report(self, report, verbose=False):
|
|
85
|
+
return yaml.safe_dump(report, indent=4)
|
|
86
|
+
|
|
87
|
+
def format_licomp_resources(self, licomp_resources):
|
|
88
|
+
return yaml.safe_dump(licomp_resources, indent=4)
|
|
89
|
+
|
|
90
|
+
def format_licomp_licenses(self, licomp_licenses):
|
|
91
|
+
return yaml.safe_dump(licomp_licenses, indent=4)
|
|
92
|
+
|
|
93
|
+
def format_licomp_versions(self, licomp_versions):
|
|
94
|
+
return yaml.safe_dump(licomp_versions, indent=4)
|
|
95
|
+
|
|
96
|
+
def format_display_compatibilities(self, compats, settings={}): # noqa: B006
|
|
97
|
+
display_compats = self._pre_format_display_compatibilities(compats)
|
|
98
|
+
return yaml.safe_dump(display_compats, indent=4)
|
|
99
|
+
|
|
100
|
+
class TextLicompToolkitFormatter(LicompToolkitFormatter):
|
|
101
|
+
|
|
102
|
+
def _format_licomp_resource(self, licomp_resource):
|
|
103
|
+
name = licomp_resource['name']
|
|
104
|
+
version = licomp_resource['version']
|
|
105
|
+
usecases = ','.join(licomp_resource['usecases'])
|
|
106
|
+
provisionings = ','.join(licomp_resource['provisionings'])
|
|
107
|
+
resource_type = licomp_resource['type']
|
|
108
|
+
return f'{name}:{version}:{usecases}:{provisionings}:{resource_type}'
|
|
109
|
+
|
|
110
|
+
def format_licomp_resources(self, licomp_resources):
|
|
111
|
+
return '\n'.join([self._format_licomp_resource(x) for x in licomp_resources])
|
|
112
|
+
|
|
113
|
+
def format_licomp_licenses(self, licomp_licenses):
|
|
114
|
+
return '\n'.join(licomp_licenses)
|
|
115
|
+
|
|
116
|
+
def __get_responses(self, results, indent=''):
|
|
117
|
+
output = []
|
|
118
|
+
for res in ['yes', 'no', 'schneben']:
|
|
119
|
+
result = results.get(res)
|
|
120
|
+
if not result:
|
|
121
|
+
count = 0
|
|
122
|
+
else:
|
|
123
|
+
count = result['count']
|
|
124
|
+
output.append(f'{indent}{res}: {count}')
|
|
125
|
+
|
|
126
|
+
return output
|
|
127
|
+
|
|
128
|
+
def __compatibility_statuses(self, statuses, indent=''):
|
|
129
|
+
output = []
|
|
130
|
+
for status, values in statuses.items():
|
|
131
|
+
resources = []
|
|
132
|
+
for value_object in values:
|
|
133
|
+
resources.append(value_object['resource_name'])
|
|
134
|
+
output.append(f'{indent}{status}: {", ".join(resources)}')
|
|
135
|
+
|
|
136
|
+
return output
|
|
137
|
+
|
|
138
|
+
def __statuses(self, statuses, indent=''):
|
|
139
|
+
output = []
|
|
140
|
+
for status, values in statuses.items():
|
|
141
|
+
resources = []
|
|
142
|
+
for value_object in values:
|
|
143
|
+
resources.append(value_object['resource_name'])
|
|
144
|
+
output.append(f'{indent}{status}: {", ".join(resources)}')
|
|
145
|
+
|
|
146
|
+
return output
|
|
147
|
+
|
|
148
|
+
def _format_compat_value(self, compat):
|
|
149
|
+
return {'yes': 'compatible'}.get(compat, 'incompatible')
|
|
150
|
+
|
|
151
|
+
def _format_compat_pref(self, compat, pref_lic=None):
|
|
152
|
+
PAREN_OPEN = '('
|
|
153
|
+
PAREN_CLOSE = ')'
|
|
154
|
+
compat_string = self._format_compat_value(compat)
|
|
155
|
+
if pref_lic:
|
|
156
|
+
return f'{PAREN_OPEN}{compat_string}, {pref_lic}{PAREN_CLOSE}'
|
|
157
|
+
else:
|
|
158
|
+
return f'{PAREN_OPEN}{compat_string}{PAREN_CLOSE}'
|
|
159
|
+
|
|
160
|
+
def format_compatibilities_general(self, compat_object, indent='', policy_report=False, preferred_license=None):
|
|
161
|
+
compatibility_check = compat_object["compatibility_check"]
|
|
162
|
+
output = []
|
|
163
|
+
|
|
164
|
+
if compatibility_check == "outbound-license -> inbound-license":
|
|
165
|
+
compat_object = compat_object
|
|
166
|
+
details = compat_object["compatibility_details"]
|
|
167
|
+
summary = details["summary"]
|
|
168
|
+
preferred_info = 'no'
|
|
169
|
+
if policy_report and preferred_license == compat_object["policy_check"]["inbound"]["preferences"]["license"]:
|
|
170
|
+
preferred_info = 'yes'
|
|
171
|
+
output.append(f'{indent}{compat_object["outbound_license"]} -> {compat_object["inbound_license"]}')
|
|
172
|
+
if policy_report:
|
|
173
|
+
output.append(f'{indent} preferred: {preferred_info}')
|
|
174
|
+
output.append(f'{indent} compatibility: {compat_object["compatibility"]}')
|
|
175
|
+
output.append(f'{indent} compatibility details: ')
|
|
176
|
+
output += self.__compatibility_statuses(summary['compatibility_statuses'], f'{indent} ')
|
|
177
|
+
|
|
178
|
+
if compatibility_check == "outbound-license -> inbound-expression":
|
|
179
|
+
operator = compat_object["operator"]
|
|
180
|
+
inner_output = []
|
|
181
|
+
preferred_info = 'no'
|
|
182
|
+
if policy_report and preferred_license == compat_object['policy_check']['inbound']['preferences']['license']:
|
|
183
|
+
preferred_info = 'yes'
|
|
184
|
+
for operand in compat_object["operands"]:
|
|
185
|
+
res = self.format_compatibilities_general(operand['compatibility_object'], indent=f'{indent} ', policy_report=policy_report, preferred_license=preferred_license)
|
|
186
|
+
inner_output.append(res)
|
|
187
|
+
if policy_report:
|
|
188
|
+
output.append(f'{indent}{operator} {self._format_compat_pref(compat_object["compatibility"], preferred_license)}')
|
|
189
|
+
output.append(f'{indent} check: {compatibility_check}')
|
|
190
|
+
output.append(f'{indent} outbound: {compat_object["outbound_license"]}')
|
|
191
|
+
output.append(f'{indent} inbound: {compat_object["inbound_license"]}')
|
|
192
|
+
output.append(f'{indent} comaptibility: {compat_object["compatibility"]}')
|
|
193
|
+
output.append(f'{indent} preferred: {preferred_info}')
|
|
194
|
+
output.append(f'{indent} details:')
|
|
195
|
+
output += inner_output
|
|
196
|
+
|
|
197
|
+
if compatibility_check == "outbound-expression -> inbound-license":
|
|
198
|
+
operator = compat_object["operator"]
|
|
199
|
+
output.append(f'{indent}{operator} {self._format_compat_pref(compat_object["compatibility"])}')
|
|
200
|
+
for operand in compat_object["operands"]:
|
|
201
|
+
res = self.format_compatibilities_general(operand['compatibility_object'], indent=f'{indent} ', policy_report=policy_report, preferred_license=preferred_license)
|
|
202
|
+
output.append(res)
|
|
203
|
+
if compatibility_check == "outbound-expression -> inbound-expression":
|
|
204
|
+
operator = compat_object["operator"]
|
|
205
|
+
compat = compat_object["compatibility"]
|
|
206
|
+
preferred_info = 'no'
|
|
207
|
+
if policy_report and preferred_license == compat_object['policy_check']['inbound']['preferences']['license']:
|
|
208
|
+
preferred_info = 'yes'
|
|
209
|
+
output.append(f'{indent}{operator} {self._format_compat_pref(compat)}')
|
|
210
|
+
output.append(f'{indent} check: {compatibility_check}')
|
|
211
|
+
output.append(f'{indent} outbound: {compat_object["outbound_license"]}')
|
|
212
|
+
output.append(f'{indent} inbound: {compat_object["inbound_license"]}')
|
|
213
|
+
output.append(f'{indent} comaptibility: {compat_object["compatibility"]}')
|
|
214
|
+
output.append(f'{indent} preferred: {preferred_info}')
|
|
215
|
+
output.append(f'{indent} details:')
|
|
216
|
+
for operand in compat_object['operands']:
|
|
217
|
+
res = self.format_compatibilities_general(operand['compatibility_object'], indent=f'{indent} ', policy_report=policy_report, preferred_license=preferred_license)
|
|
218
|
+
output.append(f'{res}')
|
|
219
|
+
|
|
220
|
+
return "\n".join(output)
|
|
221
|
+
|
|
222
|
+
def format_compatibilities_object(self, compat_object):
|
|
223
|
+
return self.format_compatibilities_general(compat_object, indent='')
|
|
224
|
+
|
|
225
|
+
def format_policy_report(self, report, verbose=False):
|
|
226
|
+
output = []
|
|
227
|
+
preferred_inbound = report['compatibility_report']['policy_check']['inbound']['preferences']['license']
|
|
228
|
+
preferred_outbound = report['compatibility_report']['policy_check']['outbound']['preferences']['license']
|
|
229
|
+
|
|
230
|
+
policy_info = report['meta']['policy_type']
|
|
231
|
+
if policy_info == 'policy_file':
|
|
232
|
+
policy_info = f'{policy_info} {report["meta"]["policy_file"]}'
|
|
233
|
+
|
|
234
|
+
output.append(f'outbound: {report["outbound"]}')
|
|
235
|
+
output.append(f'inbound: {report["inbound"]}')
|
|
236
|
+
res = ', '.join(report['resources'])
|
|
237
|
+
output.append(f'resources: {res}')
|
|
238
|
+
output.append(f'provisioning: {report["provisioning"]}')
|
|
239
|
+
output.append(f'usecase: {report["usecase"]}')
|
|
240
|
+
output.append(f'policy: {policy_info}')
|
|
241
|
+
output.append(f'compatibility: {report["compatibility"]}')
|
|
242
|
+
output.append(f'preferred inbound: {preferred_inbound}')
|
|
243
|
+
output.append(f'preferred outbound: {preferred_outbound}')
|
|
244
|
+
|
|
245
|
+
if verbose:
|
|
246
|
+
output.append('report:')
|
|
247
|
+
output.append(self.format_compatibilities_general(report["compatibility_report"], indent=' ', policy_report=True, preferred_license=preferred_inbound))
|
|
248
|
+
return "\n".join(output)
|
|
249
|
+
|
|
250
|
+
def format_compatibilities(self, compat, verbose=False):
|
|
251
|
+
output = []
|
|
252
|
+
output.append(f'outbound: {compat["outbound"]}')
|
|
253
|
+
output.append(f'inbound: {compat["inbound"]}')
|
|
254
|
+
output.append(f'resources: {", ".join(compat["resources"])}')
|
|
255
|
+
output.append(f'provisioning: {compat["provisioning"]}')
|
|
256
|
+
output.append(f'usecase: {compat["usecase"]}')
|
|
257
|
+
output.append(f'compatibility: {compat["compatibility"]}')
|
|
258
|
+
if verbose:
|
|
259
|
+
output.append('report:')
|
|
260
|
+
output.append(self.format_compatibilities_general(compat["compatibility_report"], ' ', policy_report=False))
|
|
261
|
+
|
|
262
|
+
return "\n".join(output)
|
|
263
|
+
|
|
264
|
+
def format_licomp_versions(self, licomp_versions):
|
|
265
|
+
lt = 'licomp-toolkit'
|
|
266
|
+
res = [f'{lt}: {licomp_versions[lt]}']
|
|
267
|
+
for k, v in licomp_versions['licomp-resources'].items():
|
|
268
|
+
res.append(f'{k}: {v}')
|
|
269
|
+
return '\n'.join(res)
|
|
270
|
+
|
|
271
|
+
def format_display_compatibilities(self, compats):
|
|
272
|
+
display_compats = self._pre_format_display_compatibilities(compats)
|
|
273
|
+
licenses = list(display_compats.keys())
|
|
274
|
+
|
|
275
|
+
lines = []
|
|
276
|
+
for outbound in licenses:
|
|
277
|
+
for inbound in licenses:
|
|
278
|
+
lines.append(f'{outbound:30s} {"---->":10s} {inbound:30s}: {", ".join(display_compats[outbound][inbound])}')
|
|
279
|
+
return '\n'.join(lines)
|
|
280
|
+
|
|
281
|
+
class DotLicompToolkitFormatter(LicompToolkitFormatter):
|
|
282
|
+
|
|
283
|
+
def _compat_line_color(self, compats):
|
|
284
|
+
_line_map = {
|
|
285
|
+
'unknown': 'style="dotted"',
|
|
286
|
+
'depends': 'style="dotted"',
|
|
287
|
+
'unsupported': 'style="dotted"',
|
|
288
|
+
'mixed': 'style="dotted"',
|
|
289
|
+
}
|
|
290
|
+
_color_map = {
|
|
291
|
+
'yes': 'darkgreen',
|
|
292
|
+
'no': 'darkred',
|
|
293
|
+
}
|
|
294
|
+
same = True
|
|
295
|
+
value = None
|
|
296
|
+
for compat in compats:
|
|
297
|
+
if compat == 'unsupported':
|
|
298
|
+
continue
|
|
299
|
+
if not value:
|
|
300
|
+
value = compat
|
|
301
|
+
else:
|
|
302
|
+
if compat != value:
|
|
303
|
+
same = False
|
|
304
|
+
|
|
305
|
+
if same:
|
|
306
|
+
line = _line_map.get(value, '')
|
|
307
|
+
color = _color_map.get(value, 'yellow')
|
|
308
|
+
else:
|
|
309
|
+
line = _line_map['mixed']
|
|
310
|
+
color = 'darkblue'
|
|
311
|
+
|
|
312
|
+
return line, color
|
|
313
|
+
|
|
314
|
+
def _license_license_compat(self, outbound, inbound, outbound_compat, inbound_compat):
|
|
315
|
+
out_line, out_color = self._compat_line_color(outbound_compat)
|
|
316
|
+
in_line, in_color = self._compat_line_color(inbound_compat)
|
|
317
|
+
BR_START = '['
|
|
318
|
+
BR_END = ']'
|
|
319
|
+
if out_line == in_line and out_color == in_color:
|
|
320
|
+
return (f' "{outbound}" -> "{inbound}" {BR_START}dir="both" color="{out_color}" {out_line}{BR_END}')
|
|
321
|
+
else:
|
|
322
|
+
return '\n'.join([f' "{outbound}" -> "{inbound}" {BR_START}color="{out_color}" {out_line}{BR_END}',
|
|
323
|
+
f' "{inbound}" -> "{outbound}" {BR_START}color="{in_color}" {in_line}{BR_END}'])
|
|
324
|
+
|
|
325
|
+
def format_display_compatibilities(self, compats, settings={}): # noqa: B006
|
|
326
|
+
display_compats = self._pre_format_display_compatibilities(compats)
|
|
327
|
+
licenses = list(display_compats.keys())
|
|
328
|
+
|
|
329
|
+
discard_unsupported = settings.get('discard_unsupported')
|
|
330
|
+
|
|
331
|
+
lines = []
|
|
332
|
+
finished = {}
|
|
333
|
+
usecase = compats[licenses[0]][licenses[0]]['compatibilities'][0]['usecase']
|
|
334
|
+
lines.append('digraph depends {')
|
|
335
|
+
lines.append(f' graph [label="License Compatibility Graph ({usecase})" labelloc=t]')
|
|
336
|
+
lines.append(' node [shape=plaintext]')
|
|
337
|
+
for outbound in licenses:
|
|
338
|
+
finished[outbound] = {}
|
|
339
|
+
for inbound in licenses:
|
|
340
|
+
if inbound not in finished:
|
|
341
|
+
finished[inbound] = {}
|
|
342
|
+
if inbound == outbound:
|
|
343
|
+
continue
|
|
344
|
+
|
|
345
|
+
if display_compats[outbound][inbound] == []:
|
|
346
|
+
if discard_unsupported:
|
|
347
|
+
continue
|
|
348
|
+
if display_compats[inbound][outbound] == []:
|
|
349
|
+
if discard_unsupported:
|
|
350
|
+
continue
|
|
351
|
+
|
|
352
|
+
if finished[outbound].get(inbound, False):
|
|
353
|
+
continue
|
|
354
|
+
elif finished[inbound].get(outbound, False):
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
lines.append(self._license_license_compat(outbound, inbound, display_compats[outbound][inbound], display_compats[inbound][outbound]))
|
|
358
|
+
finished[outbound][inbound] = True
|
|
359
|
+
finished[inbound][outbound] = True
|
|
360
|
+
|
|
361
|
+
lines.append('}')
|
|
362
|
+
return '\n'.join(lines)
|