Skip to content

Commit ac47da2

Browse files
Merge pull request #3275 from nexB/fix-html-output
Fix licenses in HTML output
2 parents 50206ef + 5e5b7bb commit ac47da2

File tree

5 files changed

+97
-38
lines changed

5 files changed

+97
-38
lines changed

src/formattedcode/output_html.py

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99
import io
10+
import os
11+
import logging
1012
from operator import itemgetter
1113
from os.path import abspath
1214
from os.path import dirname
@@ -41,6 +43,24 @@
4143
TEMPLATES_DIR = join(dirname(__file__), 'templates')
4244

4345

46+
TRACE = False
47+
48+
49+
def logger_debug(*args):
50+
pass
51+
52+
53+
logger = logging.getLogger(__name__)
54+
55+
if TRACE:
56+
import sys
57+
logging.basicConfig(stream=sys.stdout)
58+
logger.setLevel(logging.DEBUG)
59+
60+
def logger_debug(*args):
61+
return logger.debug(' '.join(isinstance(a, str) and a or repr(a) for a in args))
62+
63+
4464
@output_impl
4565
class HtmlOutput(OutputPlugin):
4666

@@ -59,9 +79,18 @@ def is_enabled(self, html, **kwargs):
5979
def process_codebase(self, codebase, html, **kwargs):
6080
results = self.get_files(codebase, **kwargs)
6181
version = codebase.get_or_create_current_header().tool_version
82+
license_references = []
83+
if hasattr(codebase.attributes, 'license_references'):
84+
license_references = codebase.attributes.license_references
6285
template_loc = join(TEMPLATES_DIR, 'html', 'template.html')
6386
output_file = html
64-
write_templated(output_file, results, version, template_loc)
87+
write_templated(
88+
output_file=output_file,
89+
results=results,
90+
license_references=license_references,
91+
version=version,
92+
template_loc=template_loc,
93+
)
6594

6695

6796
@output_impl
@@ -98,20 +127,34 @@ def is_enabled(self, custom_output, custom_template, **kwargs):
98127
def process_codebase(self, codebase, custom_output, custom_template, **kwargs):
99128
results = self.get_files(codebase, **kwargs)
100129
version = codebase.get_or_create_current_header().tool_version
130+
license_references = []
131+
if hasattr(codebase.attributes, 'license_references'):
132+
license_references = codebase.attributes.license_references
101133
template_loc = custom_template
102134
output_file = custom_output
103-
write_templated(output_file, results, version, template_loc)
135+
write_templated(
136+
output_file=output_file,
137+
results=results,
138+
license_references=license_references,
139+
version=version,
140+
template_loc=template_loc
141+
)
104142

105143

106-
def write_templated(output_file, results, version, template_loc):
144+
def write_templated(output_file, results, license_references, version, template_loc):
107145
"""
108146
Write scan output `results` to the `output_file` opened file using a template
109147
file at `template_loc`.
110148
Raise an exception on errors.
111149
"""
112150
template = get_template(template_loc)
113151

114-
for template_chunk in generate_output(results, version, template):
152+
for template_chunk in generate_output(
153+
results=results,
154+
license_references=license_references,
155+
version=version,
156+
template=template,
157+
):
115158
assert isinstance(template_chunk, str)
116159
try:
117160
output_file.write(template_chunk)
@@ -138,23 +181,18 @@ def get_template(location):
138181
return env.get_template(template_name)
139182

140183

141-
def generate_output(results, version, template):
184+
def generate_output(results, license_references, version, template):
142185
"""
143186
Yield unicode strings from incrementally rendering `results` and `version`
144187
with the Jinja `template` object.
145188
"""
146189
# FIXME: This code is highly coupled with actual scans and may not
147190
# support adding new scans at all
148-
149-
from licensedcode.cache import get_licenses_db
150-
licenses_db = get_licenses_db()
151-
152191
converted = {}
153192
converted_infos = {}
154193
converted_packages = {}
155-
licenses = {}
156194

157-
LICENSES = 'licenses'
195+
LICENSES = 'license_detections'
158196
COPYRIGHTS = 'copyrights'
159197
PACKAGES = 'package_data'
160198

@@ -175,6 +213,8 @@ def generate_output(results, version, template):
175213
for match in get_matches_from_detection_mappings(scanned_file[LICENSES]):
176214
# make copy
177215
match = dict(match)
216+
if TRACE:
217+
logger_debug(f"match: {match}")
178218
license_expression = match['license_expression']
179219
results.append({
180220
'start': match['start_line'],
@@ -183,11 +223,6 @@ def generate_output(results, version, template):
183223
'value': license_expression,
184224
})
185225

186-
# FIXME: we should NOT rely on license objects: only use what is in the JSON instead
187-
if license_expression not in licenses:
188-
licenses[license_expression] = match
189-
# we were modifying the scan data in place ....
190-
match['object'] = licenses_db.get(license_expression)
191226
if results:
192227
converted[path] = sorted(results, key=itemgetter('start'))
193228

@@ -204,15 +239,13 @@ def generate_output(results, version, template):
204239
if PACKAGES in scanned_file:
205240
converted_packages[path] = scanned_file[PACKAGES]
206241

207-
licenses = dict(sorted(licenses.items()))
208-
209242
files = {
210243
'license_copyright': converted,
211244
'infos': converted_infos,
212245
'package_data': converted_packages
213246
}
214247

215-
return template.generate(files=files, licenses=licenses, version=version)
248+
return template.generate(files=files, license_references=license_references, version=version)
216249

217250

218251
@output_impl
@@ -224,6 +257,7 @@ class HtmlAppOutput(OutputPlugin):
224257
PluggableCommandLineOption(('--html-app',),
225258
type=FileOptionType(mode='w', encoding='utf-8', lazy=True),
226259
metavar='FILE',
260+
hidden=True,
227261
help='(DEPRECATED: use the ScanCode Workbench app instead ) '
228262
'Write scan output as a mini HTML application to FILE.',
229263
help_group=OUTPUT_GROUP,

src/formattedcode/templates/html/template.html

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -259,36 +259,42 @@
259259
</table>
260260
{% endif %}
261261

262-
{% if licenses %}
262+
{% if license_references %}
263263
<table>
264-
<caption>Licenses</caption>
264+
<caption>License References</caption>
265265
<thead>
266266
<tr>
267267
<th>key</th>
268268
<th>short_name</th>
269269
<th>category</th>
270270
<th>owner</th>
271-
<th>reference_url</th>
271+
<th>scancode_url</th>
272+
<th>licensedb_url</th>
272273
<th>homepage_url</th>
273-
<th>text_url</th>
274+
<th>text_urls</th>
274275
<th>spdx_license_key</th>
275276
<th>spdx_url</th>
276277
</tr>
277278
</thead>
278279
<tbody>
279-
{% for key, license in licenses.items() %}
280-
<tr id="license_{{ license.key }}">
281-
<td>{{ license.key }}</td>
282-
<td>{{ license.short_name }}</td>
283-
<td>{{ license.category }}</td>
284-
<td>{{ license.owner }}</td>
285-
<td>{{ license.reference_url|urlize(target='_blank') }}</td>
286-
<td>{{ license.homepage_url|urlize(target='_blank') }}</td>
287-
<td>{{ license.text_url|urlize(target='_blank') }}</td>
288-
<td>{{ license.spdx_license_key }}</td>
289-
<td>{{ license.spdx_url|urlize(target='_blank') }}</td>
290-
</tr>
291-
{% endfor %}
280+
{% for license_reference in license_references %}
281+
<tr id="license_{{ license_reference.key }}">
282+
<td>{{ license_reference.key }}</td>
283+
<td>{{ license_reference.short_name }}</td>
284+
<td>{{ license_reference.category }}</td>
285+
<td>{{ license_reference.owner }}</td>
286+
<td>{{ license_reference.scancode_url|urlize(target='_blank') }}</td>
287+
<td>{{ license_reference.licensedb_url|urlize(target='_blank') }}</td>
288+
<td>{{ license_reference.homepage_url|urlize(target='_blank') }}</td>
289+
<td>
290+
{% for text_url in license_reference.text_urls %}
291+
{{ text_url|urlize(target='_blank') }}
292+
{% endfor %}
293+
</td>
294+
<td>{{ license_reference.spdx_license_key }}</td>
295+
<td>{{ license_reference.spdx_url|urlize(target='_blank') }}</td>
296+
</tr>
297+
{% endfor %}
292298
</tbody>
293299
</table>
294300
{% endif %}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
license: gpl-2.0

tests/formattedcode/test_output_templated.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@
2929
test_env.test_data_dir = os.path.join(os.path.dirname(__file__), 'data')
3030

3131

32+
@pytest.mark.scanslow
33+
def test_has_licenses_in_html_format_output():
34+
test_file = test_env.get_test_loc('templated/simple-license.txt')
35+
result_file = test_env.get_temp_file('html')
36+
run_scan_click(['--license', test_file, '--html', result_file])
37+
results = open(result_file).read()
38+
assert 'gpl-2.0' in results
39+
assert __version__ in results
40+
41+
42+
@pytest.mark.scanslow
43+
def test_has_license_references_in_html_format_output():
44+
test_file = test_env.get_test_loc('templated/simple-license.txt')
45+
result_file = test_env.get_temp_file('html')
46+
run_scan_click(['--license', '--license-references', test_file, '--html', result_file])
47+
results = open(result_file).read()
48+
assert 'http://www.gnu.org/licenses/gpl-2.0.html' in results
49+
assert __version__ in results
50+
51+
3252
@pytest.mark.scanslow
3353
def test_paths_are_posix_paths_in_html_app_format_output():
3454
test_dir = test_env.get_test_loc('templated/simple')

tests/scancode/data/help/help.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ Options:
6161
--cyclonedx-xml FILE Write scan output in CycloneDX XML format to FILE.
6262
--spdx-rdf FILE Write scan output as SPDX RDF to FILE.
6363
--spdx-tv FILE Write scan output as SPDX Tag/Value to FILE.
64-
--html-app FILE (DEPRECATED: use the ScanCode Workbench app instead )
65-
Write scan output as a mini HTML application to FILE.
6664

6765
output filters:
6866
--ignore-author <pattern> Ignore a file (and all its findings) if an

0 commit comments

Comments
 (0)