From 9ee0b38e5c3b3f8d512d23ef7354c94e2c27fcbe Mon Sep 17 00:00:00 2001 From: uttam282005 Date: Thu, 15 Jan 2026 15:18:02 +0530 Subject: [PATCH 1/6] Add support for license-text, license-text-diagnostics, summary, and license-clarity-score in HTML output The HTML output now supports --license-text, --license-text-diagnostics, --summary,--license-clarity-score Signed-off-by: uttam282005 --- src/formattedcode/output_html.py | 33 ++++-- .../templates/html/template.html | 110 ++++++++++++++++++ 2 files changed, 136 insertions(+), 7 deletions(-) diff --git a/src/formattedcode/output_html.py b/src/formattedcode/output_html.py index a2c236b2adb..2c436ea8949 100644 --- a/src/formattedcode/output_html.py +++ b/src/formattedcode/output_html.py @@ -43,7 +43,7 @@ TEMPLATES_DIR = join(dirname(__file__), 'templates') -TRACE = False +TRACE = True def logger_debug(*args): @@ -83,12 +83,16 @@ def process_codebase(self, codebase, html, **kwargs): license_references = [] if hasattr(codebase.attributes, 'license_references'): license_references = codebase.attributes.license_references + summary = None + if hasattr(codebase.attributes, 'summary'): + summary = codebase.attributes.summary template_loc = join(TEMPLATES_DIR, 'html', 'template.html') output_file = html write_templated( output_file=output_file, results=results, license_references=license_references, + summary=summary, version=version, template_loc=template_loc, ) @@ -133,18 +137,22 @@ def process_codebase(self, codebase, custom_output, custom_template, **kwargs): license_references = [] if hasattr(codebase.attributes, 'license_references'): license_references = codebase.attributes.license_references + summary = None + if hasattr(codebase.attributes, 'summary'): + summary = codebase.attributes.summary template_loc = custom_template output_file = custom_output write_templated( output_file=output_file, results=results, license_references=license_references, + summary=summary, version=version, template_loc=template_loc ) -def write_templated(output_file, results, license_references, version, template_loc): +def write_templated(output_file, results, license_references, summary, version, template_loc): """ Write scan output `results` to the `output_file` opened file using a template file at `template_loc`. @@ -155,6 +163,7 @@ def write_templated(output_file, results, license_references, version, template_ for template_chunk in generate_output( results=results, license_references=license_references, + summary=summary, version=version, template=template, ): @@ -184,7 +193,7 @@ def get_template(location): return env.get_template(template_name) -def generate_output(results, license_references, version, template): +def generate_output(results, license_references, summary, version, template): """ Yield unicode strings from incrementally rendering `results` and `version` with the Jinja `template` object. @@ -197,11 +206,12 @@ def generate_output(results, license_references, version, template): converted = {} converted_infos = {} converted_packages = {} - licenses = {} + licenses = {} LICENSES = 'license_detections' COPYRIGHTS = 'copyrights' PACKAGES = 'package_data' + logger_debug(f"summary: {summary}") # Create a flattened data dict keyed by path for scanned_file in results: @@ -223,12 +233,21 @@ def generate_output(results, license_references, version, template): if TRACE: logger_debug(f"match: {match}") license_expression = match['license_expression'] - results.append({ + match_data = { 'start': match['start_line'], 'end': match['end_line'], 'what': 'license', 'value': license_expression, - }) + } + + if 'matched_text' in match: + match_data['matched_text'] = match['matched_text'] + + if 'matched_text_diagnostics' in match: + logger_debug(match['matched_text_diagnostics']) + match_data['matched_text_diagnostics'] = match['matched_text_diagnostics'] + + results.append(match_data) if not license_references and license_expression not in licenses: license_object = get_licenses_db().get(license_expression) @@ -261,7 +280,7 @@ def generate_output(results, license_references, version, template): 'package_data': converted_packages } - return template.generate(files=files, license_references=license_references, version=version) + return template.generate(files=files, license_references=license_references, summary=summary, version=version) @output_impl diff --git a/src/formattedcode/templates/html/template.html b/src/formattedcode/templates/html/template.html index 894408f6503..2aae5923b98 100644 --- a/src/formattedcode/templates/html/template.html +++ b/src/formattedcode/templates/html/template.html @@ -46,9 +46,101 @@ font-weight: normal; font-size: 12px; } + details { + margin: 5px 0; + } + summary { + cursor: pointer; + color: #5E81B7; + font-weight: bold; + } + summary:hover { + text-decoration: underline; + } + pre { + background: #f5f5f5; + padding: 10px; + border: 1px solid #ddd; + overflow-x: auto; + font-family: monospace; + font-size: 11px; + white-space: pre-wrap; + word-wrap: break-word; + } + .summary-section { + background: #f0f8ff; + padding: 15px; + margin-bottom: 20px; + border-left: 4px solid #5E81B7; + } + .summary-section h3 { + margin-top: 0; + color: #5E81B7; + font-size: 16px; + } + .summary-section p { + margin: 5px 0; + } + {% if summary %} +
+

Scan Summary

+ {% if summary.declared_license_expression %} +

Declared License: {{ summary.declared_license_expression }}

+ {% endif %} + {% if summary.declared_holder %} +

Declared Holder: {{ summary.declared_holder }}

+ {% endif %} + {% if summary.primary_language %} +

Primary Language: {{ summary.primary_language }}

+ {% endif %} + {% if summary.other_license_expressions %} +

Other License Expressions:

+
    + {% for expr in summary.other_license_expressions %} +
  • {{ expr.value }} (count: {{ expr.count }})
  • + {% endfor %} +
+ {% endif %} + {% if summary.other_holders %} +

Other Holders:

+
    + {% for holder in summary.other_holders %} +
  • {{ holder }}
  • + {% endfor %} +
+ {% endif %} +
+ {% endif %} + {% if summary and summary.license_clarity_score %} + + + + + + + + + + + + + + + + + + + + + + + + +
License Clarity Score
Overall ScoreDeclared LicenseIdentification PrecisionHas License TextDeclared CopyrightsAmbiguous CompoundConflicting Categories
{{ summary.license_clarity_score.score }}{{ summary.license_clarity_score.declared_license }}{{ summary.license_clarity_score.identification_precision }}{{ summary.license_clarity_score.has_license_text }}{{ summary.license_clarity_score.declared_copyrights }}{{ summary.license_clarity_score.ambiguous_compound_licensing }}{{ summary.license_clarity_score.conflicting_license_categories }}
+ {% endif %} {% if files.license_copyright %} @@ -59,6 +151,7 @@ + @@ -71,6 +164,23 @@ {% if row.what == 'license' %} + {% else %} {% endif %} From 2a3b647cac1a0adddb427920e93ccf3c6cb31617 Mon Sep 17 00:00:00 2001 From: uttam282005 Date: Thu, 15 Jan 2026 15:26:53 +0530 Subject: [PATCH 2/6] add tests Signed-off-by: uttam282005 --- tests/formattedcode/test_output_templated.py | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/formattedcode/test_output_templated.py b/tests/formattedcode/test_output_templated.py index 303bb97bb83..6d743184191 100644 --- a/tests/formattedcode/test_output_templated.py +++ b/tests/formattedcode/test_output_templated.py @@ -72,6 +72,46 @@ def test_paths_are_posix_in_html_format_output(): assert '/copyright_acme_c-c.c' in results assert __version__ in results +@pytest.mark.scanslow +def test_html_output_includes_license_text(): + test_file = test_env.get_test_loc('templated/simple-license.txt') + result_file = test_env.get_temp_file('html') + run_scan_click(['--license', '--license-text', test_file, '--html', result_file]) + results = open(result_file).read() + assert 'matched text' in results.lower() + assert '
' in results  # Check for formatted text display
+    assert __version__ in results
+
+@pytest.mark.scanslow
+def test_html_output_includes_license_text_diagnostics():
+    test_file = test_env.get_test_loc('templated/simple-license.txt')
+    result_file = test_env.get_temp_file('html')
+    run_scan_click(['--license', '--license-text', '--license-text-diagnostics', 
+                    test_file, '--html', result_file])
+    results = open(result_file).read()
+    assert 'diagnostics' in results.lower()
+    assert __version__ in results
+
+@pytest.mark.scanslow
+def test_html_output_includes_license_clarity_score():
+    test_dir = test_env.get_test_loc('templated/simple')
+    result_file = test_env.get_temp_file('html')
+    run_scan_click(['--license', '--classify', '--license-clarity-score', 
+                    test_dir, '--html', result_file])
+    results = open(result_file).read()
+    assert 'License Clarity Score' in results
+    assert 'score' in results.lower()
+    assert __version__ in results
+
+@pytest.mark.scanslow
+def test_html_output_includes_summary():
+    test_dir = test_env.get_test_loc('templated/simple')
+    result_file = test_env.get_temp_file('html')
+    run_scan_click(['--license', '--classify', '--summary', 
+                    test_dir, '--html', result_file])
+    results = open(result_file).read()
+    assert 'Scan Summary' in results
+    assert __version__ in results
 
 @pytest.mark.scanslow
 def test_scanned_path_is_present_in_html_app_output():

From 32520d48067f918c8f6a70587a4a55b84301243b Mon Sep 17 00:00:00 2001
From: uttam282005 
Date: Thu, 15 Jan 2026 15:57:49 +0530
Subject: [PATCH 3/6] turn off trace and remove debug logs

Signed-off-by: uttam282005 
---
 src/formattedcode/output_html.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/formattedcode/output_html.py b/src/formattedcode/output_html.py
index 2c436ea8949..352edab8653 100644
--- a/src/formattedcode/output_html.py
+++ b/src/formattedcode/output_html.py
@@ -43,7 +43,7 @@
 TEMPLATES_DIR = join(dirname(__file__), 'templates')
 
 
-TRACE = True
+TRACE = False
 
 
 def logger_debug(*args):
@@ -211,7 +211,6 @@ def generate_output(results, license_references, summary, version, template):
     LICENSES = 'license_detections'
     COPYRIGHTS = 'copyrights'
     PACKAGES = 'package_data'
-    logger_debug(f"summary: {summary}")
 
     # Create a flattened data dict keyed by path
     for scanned_file in results:
@@ -244,7 +243,6 @@ def generate_output(results, license_references, summary, version, template):
                     match_data['matched_text'] = match['matched_text']
 
                 if 'matched_text_diagnostics' in match:
-                    logger_debug(match['matched_text_diagnostics'])
                     match_data['matched_text_diagnostics'] = match['matched_text_diagnostics']
 
                 results.append(match_data)

From 884db4dbdfcc4b0699c441a5b035637ad034eb3f Mon Sep 17 00:00:00 2001
From: uttam282005 
Date: Thu, 15 Jan 2026 16:41:57 +0530
Subject: [PATCH 4/6] apply suggestions that makes sense

Signed-off-by: uttam282005 
---
 src/formattedcode/output_html.py              |  4 +-
 .../templates/html/template.html              | 72 +++++++++----------
 2 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/src/formattedcode/output_html.py b/src/formattedcode/output_html.py
index 352edab8653..1b2db9026ad 100644
--- a/src/formattedcode/output_html.py
+++ b/src/formattedcode/output_html.py
@@ -137,7 +137,7 @@ def process_codebase(self, codebase, custom_output, custom_template, **kwargs):
         license_references = []
         if hasattr(codebase.attributes, 'license_references'):
             license_references = codebase.attributes.license_references
-        summary = None 
+        summary = None
         if hasattr(codebase.attributes, 'summary'):
             summary = codebase.attributes.summary
         template_loc = custom_template
@@ -206,7 +206,7 @@ def generate_output(results, license_references, summary, version, template):
     converted = {}
     converted_infos = {}
     converted_packages = {}
-    licenses = {} 
+    licenses = {}
 
     LICENSES = 'license_detections'
     COPYRIGHTS = 'copyrights'
diff --git a/src/formattedcode/templates/html/template.html b/src/formattedcode/templates/html/template.html
index 2aae5923b98..8a3bc561b1f 100644
--- a/src/formattedcode/templates/html/template.html
+++ b/src/formattedcode/templates/html/template.html
@@ -46,41 +46,41 @@
         font-weight: normal;
         font-size: 12px;
       }
-        details {
-            margin: 5px 0;
-        }
-        summary {
-            cursor: pointer;
-            color: #5E81B7;
-            font-weight: bold;
-        }
-        summary:hover {
-            text-decoration: underline;
-        }
-        pre {
-            background: #f5f5f5;
-            padding: 10px;
-            border: 1px solid #ddd;
-            overflow-x: auto;
-            font-family: monospace;
-            font-size: 11px;
-            white-space: pre-wrap;
-            word-wrap: break-word;
-        }
-        .summary-section {
-            background: #f0f8ff;
-            padding: 15px;
-            margin-bottom: 20px;
-            border-left: 4px solid #5E81B7;
-        }
-        .summary-section h3 {
-            margin-top: 0;
-            color: #5E81B7;
-            font-size: 16px;
-        }
-        .summary-section p {
-            margin: 5px 0;
-        }
+      details {
+          margin: 5px 0;
+      }
+      summary {
+          cursor: pointer;
+          color: #5E81B7;
+          font-weight: bold;
+      }
+      summary:hover {
+          text-decoration: underline;
+      }
+      pre {
+          background: #f5f5f5;
+          padding: 10px;
+          border: 1px solid #ddd;
+          overflow-x: auto;
+          font-family: monospace;
+          font-size: 11px;
+          white-space: pre-wrap;
+          word-wrap: break-word;
+      }
+      .summary-section {
+          background: #f0f8ff;
+          padding: 15px;
+          margin-bottom: 20px;
+          border-left: 4px solid #5E81B7;
+      }
+      .summary-section h3 {
+          margin-top: 0;
+          color: #5E81B7;
+          font-size: 16px;
+      }
+      .summary-section p {
+          margin: 5px 0;
+      }
     
   
   
@@ -108,7 +108,7 @@ 

Scan Summary

Other Holders:

    {% for holder in summary.other_holders %} -
  • {{ holder }}
  • +
  • {{ holder.value }}{% if holder.count is not none %} (count: {{ holder.count }}){% endif %}
  • {% endfor %}
{% endif %} From 297bca6fd1d1c57b52288d56ec3ee20091da465b Mon Sep 17 00:00:00 2001 From: uttam282005 Date: Thu, 15 Jan 2026 16:48:19 +0530 Subject: [PATCH 5/6] fix table misallignement Signed-off-by: uttam282005 --- src/formattedcode/templates/html/template.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/formattedcode/templates/html/template.html b/src/formattedcode/templates/html/template.html index 8a3bc561b1f..e29b0b7d9ac 100644 --- a/src/formattedcode/templates/html/template.html +++ b/src/formattedcode/templates/html/template.html @@ -183,6 +183,7 @@

Scan Summary

{% else %}
+ {% endif %} {% endfor %} From c70a26faddda787b90812c6a3d0dab15da12993d Mon Sep 17 00:00:00 2001 From: uttam282005 Date: Fri, 16 Jan 2026 12:10:02 +0530 Subject: [PATCH 6/6] update expected template in the test Signed-off-by: uttam282005 --- tests/formattedcode/test_output_templated.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/formattedcode/test_output_templated.py b/tests/formattedcode/test_output_templated.py index 6d743184191..458dd373db9 100644 --- a/tests/formattedcode/test_output_templated.py +++ b/tests/formattedcode/test_output_templated.py @@ -144,6 +144,7 @@ def test_scan_html_output_does_not_truncate_copyright_html(): + '''
Copyrights and Licenses Information
end what valuematched text/ diagnostics
{{ row.what }}{{ row.value }} + {% if row.matched_text %} +
+ View Matched Text +
{{ row.matched_text|escape }}
+
+ {% endif %} + {% if row.matched_text_diagnostics %} +
+ View Diagnostics +
{{ row.matched_text_diagnostics|escape }}
+
+ {% endif %} + {% if not row.matched_text and not row.matched_text_diagnostics %} + - + {% endif %} +
{{ row.value|escape }}{{ row.value|escape }}
1 copyright Copyright \(c\) 2000 ACME, Inc\.