Skip to content

Commit 3936863

Browse files
committed
Add compare mode
- Compare two FOSSLight Report yaml Signed-off-by: Jiyeong Seok <[email protected]>
1 parent e3e5e77 commit 3936863

File tree

7 files changed

+353
-51
lines changed

7 files changed

+353
-51
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ xlrd
55
openpyxl
66
progress
77
pyyaml
8+
beautifulsoup4
89
fosslight_util>=1.3.13
910
fosslight_dependency>=3.7.4
1011
fosslight_binary>=4.0.7

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"Programming Language :: Python :: 3.8",
3333
"Programming Language :: Python :: 3.9", ],
3434
install_requires=required,
35+
package_data={'fosslight_scanner': ['resources/bom_compare.html']},
3536
entry_points={
3637
"console_scripts": [
3738
"fosslight = fosslight_scanner.cli:main",

src/fosslight_scanner/_help.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
binary\t\t Run FOSSLight Binary
1919
reuse\t\t Run FOSSLight Reuse
2020
all\t\t\t Run all scanners
21+
compare\t\t Compare two FOSSLight reports in yaml format
2122
2223
Options:
2324
-h\t\t\t Print help message
2425
-p <path>\t\t Path to analyze
2526
-w <link>\t\t Link to be analyzed can be downloaded by wget or git clone
26-
-f <format>\t\t Output file format (excel, csv, opossum)
27+
-f <format>\t\t FOSSLight Report file format (excel, yaml)
28+
\t\t(In compare mode, supports excel, json, yaml, html)
2729
-o <output>\t\t Output directory or file
2830
-c <number>\t\t Number of processes to analyze source
2931
-r\t\t\t Keep raw data
@@ -34,7 +36,10 @@
3436
-u <db_url>\t\t DB Connection(format :'postgresql://username:password@host:port/database_name')
3537
3638
Options for only 'all' or 'dependency' mode
37-
-d <dependency_argument>\t Additional arguments for running dependency analysis"""
39+
-d <dependency_argument>\t Additional arguments for running dependency analysis
40+
41+
Options for only 'compare' mode
42+
-y <before yaml> <after yaml> Two FOSSLight reports in yaml format (ex, -y 'before.yaml' 'after.yaml')"""
3843

3944

4045
def print_help_msg():
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# FOSSLight Scanner
4+
# Copyright (c) 2022 LG Electronics Inc.
5+
# SPDX-License-Identifier: Apache-2.0
6+
import os
7+
import sys
8+
import json
9+
import yaml
10+
import logging
11+
import xlsxwriter
12+
import codecs
13+
from pathlib import Path
14+
from bs4 import BeautifulSoup
15+
import fosslight_util.constant as constant
16+
from fosslight_util.compare_yaml import compare_yaml
17+
18+
logger = logging.getLogger(constant.LOGGER_NAME)
19+
ADD = "add"
20+
DELETE = "delete"
21+
CHANGE = "change"
22+
23+
24+
def write_result_json_yaml(output_file, compared_result, file_ext):
25+
ret = True
26+
try:
27+
with open(output_file, 'w') as f:
28+
if file_ext == '.json':
29+
json.dump(compared_result, f, indent=4, sort_keys=True)
30+
elif file_ext == '.yaml':
31+
yaml.dump(compared_result, f, sort_keys=True)
32+
except Exception:
33+
ret = False
34+
return ret
35+
36+
37+
def parse_result_for_table(oi, status):
38+
compared_row = []
39+
if status == ADD:
40+
oss_after = f"{oi['name']} ({oi['version']})"
41+
license_after = f"{', '.join(oi['license'])}"
42+
compared_row = [status, '', '', oss_after, license_after]
43+
elif status == DELETE:
44+
oss_before = f"{oi['name']} ({oi['version']})"
45+
license_before = f"{', '.join(oi['license'])}"
46+
compared_row = [status, oss_before, license_before, '', '']
47+
elif status == CHANGE:
48+
oss_before, oss_after, license_before, license_after = [], [], [], []
49+
for prev_i in oi['prev']:
50+
oss_before.append(f"{oi['name']} ({prev_i['version']})")
51+
license_before.append(f"{', '.join(prev_i['license'])}")
52+
for now_i in oi['now']:
53+
oss_after.append(f"{oi['name']} ({now_i['version']})")
54+
license_after.append(f"{', '.join(now_i['license'])}")
55+
compared_row = [status, ' / '.join(oss_before), ' / '.join(license_before),
56+
' / '.join(oss_after), ' / '.join(license_after)]
57+
else:
58+
logger.error(f"Not supported compared status: {status}")
59+
60+
return compared_row
61+
62+
63+
def get_sample_html():
64+
RESOURCES_DIR = 'resources'
65+
SAMPLE_HTML = 'bom_compare.html'
66+
html_file = os.path.join(RESOURCES_DIR, SAMPLE_HTML)
67+
68+
try:
69+
base_dir = sys._MEIPASS
70+
except Exception:
71+
base_dir = os.path.dirname(__file__)
72+
73+
file_withpath = os.path.join(base_dir, html_file)
74+
try:
75+
html_f = codecs.open(file_withpath, 'r', 'utf-8')
76+
except Exception as ex:
77+
logger.error(f"Error to get sample html file : {ex}")
78+
79+
return html_f
80+
81+
82+
def write_result_html(output_file, compared_result, before_yaml, after_yaml):
83+
ret = True
84+
85+
f = BeautifulSoup(get_sample_html().read(), 'html.parser')
86+
f.find("li", {"class": "before_f"}).append(before_yaml)
87+
f.find("li", {"class": "after_f"}).append(after_yaml)
88+
89+
table_html = f.find("table", {"id": "comp_result"})
90+
91+
status = [ADD, DELETE, CHANGE]
92+
row = 2
93+
for st in status:
94+
for oi in compared_result[st]:
95+
compared_row = parse_result_for_table(oi, st)
96+
tr = f.new_tag('tr')
97+
for i, ci in enumerate(compared_row):
98+
td = f.new_tag('td')
99+
td.string = ci
100+
td.attrs = {"style": "padding:5px;"}
101+
tr.insert(i, td)
102+
table_html.insert(row, tr)
103+
row += 1
104+
105+
with open(output_file, "wb") as f_out:
106+
f_out.write(f.prettify("utf-8"))
107+
return ret
108+
109+
110+
def write_result_xlsx(output_file, compared_result):
111+
HEADER = ['Status', 'OSS_Before', 'License_Before', 'OSS_After', 'License_After']
112+
ret = True
113+
114+
try:
115+
output_dir = os.path.dirname(output_file)
116+
Path(output_dir).mkdir(parents=True, exist_ok=True)
117+
118+
workbook = xlsxwriter.Workbook(os.path.basename(output_file))
119+
worksheet = workbook.add_worksheet('BOM_compare')
120+
bold = workbook.add_format({'bold': True})
121+
worksheet.write_row(0, 0, HEADER, bold)
122+
123+
row = 1
124+
status = [ADD, DELETE, CHANGE]
125+
for st in status:
126+
for oi in compared_result[st]:
127+
compared_row = parse_result_for_table(oi, st)
128+
worksheet.write_row(row, 0, compared_row)
129+
row += 1
130+
workbook.close()
131+
except Exception:
132+
ret = False
133+
134+
return ret
135+
136+
137+
def write_compared_result(output_file, compared_result, file_ext, before_yaml='', after_yaml=''):
138+
success = False
139+
if file_ext == "" or file_ext == ".xlsx":
140+
success = write_result_xlsx(output_file, compared_result)
141+
elif file_ext == ".html":
142+
success = write_result_html(output_file, compared_result, before_yaml, after_yaml)
143+
elif file_ext == ".json":
144+
success = write_result_json_yaml(output_file, compared_result, file_ext)
145+
elif file_ext == ".yaml":
146+
success = write_result_json_yaml(output_file, compared_result, file_ext)
147+
else:
148+
logger.info("Not supported file extension")
149+
150+
return success
151+
152+
153+
def run_compare(before_yaml, after_yaml, output_file, file_ext):
154+
ret = False
155+
logger.info("Start compare mode")
156+
157+
compared_result = compare_yaml(before_yaml, after_yaml)
158+
if compared_result != '':
159+
ret = write_compared_result(output_file, compared_result, file_ext, before_yaml, after_yaml)
160+
if ret:
161+
logger.info(f"Success to write compared result: {output_file}")
162+
else:
163+
logger.error("Fail to write compared result file.")
164+
165+
return ret

src/fosslight_scanner/cli.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010

1111
def main():
1212
parser = ArgumentParser(description='FOSSLight Scanner', prog='fosslight_scanner', add_help=False)
13-
parser.add_argument('mode', nargs='?', help='source| dependency| binary| reuse| all', default="all")
13+
parser.add_argument('mode', nargs='?', help='source| dependency| binary| reuse| all| compare', default="all")
1414
parser.add_argument('--path', '-p', help='Path to analyze', type=str, dest='path', default="")
1515
parser.add_argument('--wget', '-w', help='Link to be analyzed', type=str, dest='link', default="")
16-
parser.add_argument('--file', '-f', help='Output file format (excel, csv, opossum)', type=str, dest='file', default="")
16+
parser.add_argument('--file', '-f', help='Output file format (excel, csv, opossum, yaml)', type=str, dest='file', default="")
1717
parser.add_argument('--output', '-o', help='Output directory or file', type=str, dest='output', default="")
1818
parser.add_argument('--dependency', '-d', help='Dependency arguments', type=str, dest='dep_argument', default="")
1919
parser.add_argument('--url', '-u', help="DB Url", type=str, dest='db_url', default="")
@@ -22,6 +22,7 @@ def main():
2222
parser.add_argument('--timer', '-t', help='Hide the progress bar', action='store_true', dest='timer', default=False)
2323
parser.add_argument('--version', '-v', help='Print version', action='store_true', dest='version', default=False)
2424
parser.add_argument('--help', '-h', help='Print help message', action='store_true', dest='help')
25+
parser.add_argument('--yaml', '-y', help='Two FOSSLight reports in yaml format', nargs=2, default="")
2526
try:
2627
args = parser.parse_args()
2728
except SystemExit:
@@ -32,8 +33,15 @@ def main():
3233
elif args.version:
3334
print_package_version(PKG_NAME, "FOSSLight Scanner Version:")
3435
else:
36+
if args.yaml:
37+
before_yaml = args.yaml[0]
38+
after_yaml = args.yaml[1]
39+
else:
40+
before_yaml = ''
41+
after_yaml = ''
42+
3543
run_main(args.mode, args.path, args.dep_argument, args.output, args.file,
36-
args.link, args.db_url, args.timer, args.raw, args.core)
44+
args.link, args.db_url, args.timer, args.raw, args.core, before_yaml, after_yaml)
3745

3846

3947
if __name__ == "__main__":

0 commit comments

Comments
 (0)