Skip to content

Commit 666e455

Browse files
authored
Merge pull request #38 from fosslight/develop
Add compare mode
2 parents b326a56 + 1980952 commit 666e455

File tree

10 files changed

+506
-54
lines changed

10 files changed

+506
-54
lines changed

.reuse/dep5

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ License: Apache-2.0
2525
Files: tests/*
2626
Copyright: 2021 LG Electronics
2727
License: Apache-2.0
28+
29+
Files: src/fosslight_scanner/resources/bom_compare.html
30+
Copyright: 2022 LG Electronics
31+
License: Apache-2.0

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: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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+
html_f = ''
68+
69+
try:
70+
base_dir = sys._MEIPASS
71+
except Exception:
72+
base_dir = os.path.dirname(__file__)
73+
74+
file_withpath = os.path.join(base_dir, html_file)
75+
try:
76+
html_f = codecs.open(file_withpath, 'r', 'utf-8')
77+
except Exception as ex:
78+
logger.error(f"Error to get sample html file : {ex}")
79+
80+
return html_f
81+
82+
83+
def write_result_html(output_file, compared_result, before_yaml, after_yaml):
84+
ret = True
85+
html_f = get_sample_html()
86+
if html_f != '':
87+
try:
88+
f = BeautifulSoup(html_f.read(), 'html.parser')
89+
f.find("li", {"class": "before_f"}).append(before_yaml)
90+
f.find("li", {"class": "after_f"}).append(after_yaml)
91+
92+
table_html = f.find("table", {"id": "comp_result"})
93+
94+
status = [ADD, DELETE, CHANGE]
95+
row = 2
96+
for st in status:
97+
for oi in compared_result[st]:
98+
compared_row = parse_result_for_table(oi, st)
99+
tr = f.new_tag('tr')
100+
for i, ci in enumerate(compared_row):
101+
td = f.new_tag('td')
102+
td.string = ci
103+
td.attrs = {"style": "padding:5px;"}
104+
tr.insert(i, td)
105+
table_html.insert(row, tr)
106+
row += 1
107+
108+
if row == 2:
109+
tr = f.new_tag('tr')
110+
td = f.new_tag('td')
111+
td.string = 'Same'
112+
td.attrs = {"style": "padding:5px;"}
113+
tr.insert(0, td)
114+
for i in range(1, 5):
115+
td = f.new_tag('td')
116+
td.string = ''
117+
td.attrs = {"style": "padding:5px;"}
118+
tr.insert(i, td)
119+
table_html.insert(row, tr)
120+
121+
with open(output_file, "wb") as f_out:
122+
f_out.write(f.prettify("utf-8"))
123+
except Exception as e:
124+
logger.error(f'Fail to write html file: {e}')
125+
ret = False
126+
else:
127+
ret = False
128+
return ret
129+
130+
131+
def write_result_xlsx(output_file, compared_result):
132+
HEADER = ['Status', 'OSS_Before', 'License_Before', 'OSS_After', 'License_After']
133+
ret = True
134+
135+
try:
136+
output_dir = os.path.dirname(output_file)
137+
Path(output_dir).mkdir(parents=True, exist_ok=True)
138+
139+
workbook = xlsxwriter.Workbook(os.path.basename(output_file))
140+
worksheet = workbook.add_worksheet('BOM_compare')
141+
bold = workbook.add_format({'bold': True})
142+
worksheet.write_row(0, 0, HEADER, bold)
143+
144+
row = 1
145+
status = [ADD, DELETE, CHANGE]
146+
for st in status:
147+
for oi in compared_result[st]:
148+
compared_row = parse_result_for_table(oi, st)
149+
worksheet.write_row(row, 0, compared_row)
150+
row += 1
151+
if row == 1:
152+
worksheet.write_row(row, 0, ['Same', '', '', '', ''])
153+
workbook.close()
154+
except Exception as e:
155+
logger.error(f'Fail to write xlsx file: {e}')
156+
ret = False
157+
158+
return ret
159+
160+
161+
def write_compared_result(output_file, compared_result, file_ext, before_yaml='', after_yaml=''):
162+
success = False
163+
if file_ext == "" or file_ext == ".xlsx":
164+
success = write_result_xlsx(output_file, compared_result)
165+
elif file_ext == ".html":
166+
success = write_result_html(output_file, compared_result, before_yaml, after_yaml)
167+
elif file_ext == ".json":
168+
success = write_result_json_yaml(output_file, compared_result, file_ext)
169+
elif file_ext == ".yaml":
170+
success = write_result_json_yaml(output_file, compared_result, file_ext)
171+
else:
172+
logger.info("Not supported file extension")
173+
174+
return success
175+
176+
177+
def run_compare(before_yaml, after_yaml, output_file, file_ext):
178+
ret = False
179+
logger.info("Start compare mode")
180+
181+
compared_result = compare_yaml(before_yaml, after_yaml)
182+
if compared_result != '':
183+
ret = write_compared_result(output_file, compared_result, file_ext, before_yaml, after_yaml)
184+
if ret:
185+
logger.info(f"Success to write compared result: {output_file}")
186+
else:
187+
logger.error("Fail to write compared result file.")
188+
189+
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)