Skip to content

Commit b375834

Browse files
authored
Merge pull request #86 from nexB/71-account-for-license-category
Add license category-change detection and scoring
2 parents 5459956 + 3daeb19 commit b375834

36 files changed

Lines changed: 432143 additions & 95 deletions

src/deltacode/__init__.py

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ def __init__(self, new_path, old_path, options):
5858
self.determine_moved()
5959
self.license_diff()
6060
self.copyright_diff()
61-
# Sort deltas by score, descending, i.e., high > low.
61+
# Sort deltas by score, descending, i.e., high > low, and then by
62+
# factors, alphabetically.
6263
self.deltas.sort(key=lambda Delta: Delta.score, reverse=True)
64+
self.deltas.sort(key=lambda Delta: Delta.factors, reverse=False)
6365

6466
def align_scans(self):
6567
"""
@@ -134,7 +136,7 @@ def determine_delta(self):
134136
# This file already classified so do nothing
135137
new_index[path]
136138
except KeyError:
137-
delta = Delta(10, None, old_file)
139+
delta = Delta(0, None, old_file)
138140
delta.factors.append('removed')
139141
self.deltas.append(delta)
140142
continue
@@ -158,10 +160,10 @@ def determine_moved(self):
158160
both indices with the same 'sha1' and File 'name' attributes, and
159161
converting each such pair of 'added' and 'removed' Delta objects to a
160162
'moved' Delta object. The 'added' and 'removed' indices are defined by
161-
the 'score' attribute of the Delta objects.
163+
the presence/absence of the object's 'old_file' and 'new_file'.
162164
"""
163-
added = self.index_deltas('sha1', [i for i in self.deltas if i.score == 100])
164-
removed = self.index_deltas('sha1', [i for i in self.deltas if i.score == 10])
165+
added = self.index_deltas('sha1', [i for i in self.deltas if i.old_file is None and i.new_file])
166+
removed = self.index_deltas('sha1', [i for i in self.deltas if i.old_file and i.new_file is None])
165167

166168
# TODO: should it be iteritems() or items()
167169
for added_sha1, added_deltas in added.iteritems():
@@ -177,7 +179,7 @@ def update_deltas(self, added, removed):
177179
'moved' Delta object -- passing the appropriate 'score' during object
178180
creation -- and delete the 'added' and 'removed' objects.
179181
"""
180-
delta = Delta(5, added.new_file, removed.old_file)
182+
delta = Delta(0, added.new_file, removed.old_file)
181183
delta.factors.append('moved')
182184
self.deltas.append(delta)
183185
self.deltas.remove(added)
@@ -187,30 +189,48 @@ def license_diff(self):
187189
"""
188190
Compare the license details for a pair of 'new' and 'old' File objects
189191
in a Delta object and change the Delta object's 'score' attribute --
190-
and add an appropriate category (e.g., 'license info removed', 'license
191-
info added' or 'license change') to the Delta object's 'factors'
192-
attribute -- if there has been a license change and depending on the
193-
nature of that change.
192+
and add one or more appropriate categories (e.g., 'license change',
193+
'copyleft added') to the Delta object's 'factors' attribute -- if there
194+
has been a license change and depending on the nature of that change.
194195
"""
196+
unique_categories = set([
197+
'Commercial',
198+
'Copyleft',
199+
'Copyleft Limited',
200+
'Free Restricted',
201+
'Patent License',
202+
'Proprietary Free'
203+
])
204+
195205
for delta in self.deltas:
196-
if 20 <= delta.score < 100:
206+
if delta.is_modified():
207+
if not delta.new_file.has_licenses() and delta.old_file.has_licenses():
208+
delta.update(15, 'license info removed')
209+
return
197210

198211
new_licenses = delta.new_file.licenses or []
199212
old_licenses = delta.old_file.licenses or []
200213

201-
if len(delta.new_file.licenses) > 0 and delta.old_file.licenses == []:
202-
delta.update(20, 'license info added')
203-
return
214+
new_categories = set(license.category for license in new_licenses)
215+
old_categories = set(license.category for license in old_licenses)
204216

205-
if delta.new_file.licenses == [] and len(delta.old_file.licenses) > 0:
206-
delta.update(15, 'license info removed')
217+
if delta.new_file.has_licenses() and not delta.old_file.has_licenses():
218+
delta.update(20, 'license info added')
219+
# no license ==> 'Copyleft Limited'or higher
220+
for category in new_categories:
221+
if category in unique_categories:
222+
delta.update(20, category.lower() + ' added')
207223
return
208224

209225
new_keys = set(license.key for license in new_licenses)
210226
old_keys = set(license.key for license in old_licenses)
211227

212228
if new_keys != old_keys:
213229
delta.update(10, 'license change')
230+
for category in new_categories - old_categories:
231+
# 'Permissive' or 'Public Domain' ==> 'Copyleft Limited' or higher
232+
if len(old_categories & unique_categories) == 0 and category in unique_categories:
233+
delta.update(20, category.lower() + ' added')
214234

215235
def copyright_diff(self):
216236
"""
@@ -222,15 +242,14 @@ def copyright_diff(self):
222242
nature of that change.
223243
"""
224244
for delta in self.deltas:
225-
if 20 <= delta.score < 100:
226-
245+
if delta.is_modified():
227246
new_copyrights = delta.new_file.copyrights or []
228247
old_copyrights = delta.old_file.copyrights or []
229248

230-
if len(delta.new_file.copyrights) > 0 and delta.old_file.copyrights == []:
249+
if delta.new_file.has_copyrights() and not delta.old_file.has_copyrights():
231250
delta.update(10, 'copyright info added')
232251
return
233-
elif delta.new_file.copyrights == [] and len(delta.old_file.copyrights) > 0:
252+
if not delta.new_file.has_copyrights() and delta.old_file.has_copyrights():
234253
delta.update(10, 'copyright info removed')
235254
return
236255

@@ -289,6 +308,26 @@ def update(self, score=0, factor=''):
289308
self.factors.append(factor)
290309
self.score += score
291310

311+
def is_modified(self):
312+
"""
313+
Identify a Delta object meriting attention to possible changes in its
314+
license or copyright content because the File object has been modified.
315+
"""
316+
if self.score > 0 and self.old_file:
317+
return True
318+
319+
def is_unmodified(self):
320+
"""
321+
Since 'unmodified' is no longer the only category/factor with a
322+
score = 0, test the Delta object's attributes for categories/factors
323+
other than 'unmodified' and return True if all but 'unmodified' are
324+
ruled out.
325+
"""
326+
if (self.old_file and self.new_file and
327+
self.old_file.sha1 == self.new_file.sha1 and
328+
self.old_file.path == self.new_file.path):
329+
return True
330+
292331
def to_dict(self):
293332
"""
294333
Return an OrderedDict comprising the 'factors', 'score' and new and old

src/deltacode/models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def index_files(self, index_key='path'):
140140
for f in self.files:
141141
key = getattr(f, index_key)
142142

143-
if index.get(key) == None:
143+
if index.get(key) is None:
144144
index[key] = []
145145
index[key].append(f)
146146
else:
@@ -169,12 +169,20 @@ def get_licenses(self, dictionary):
169169
else:
170170
return [License(l) for l in dictionary.get('licenses')]
171171

172+
def has_licenses(self):
173+
if len(self.licenses) > 0:
174+
return True
175+
172176
def get_copyrights(self, dictionary):
173177
if dictionary.get('copyrights') == []:
174178
return []
175179
else:
176180
return [Copyright(l) for l in dictionary.get('copyrights')]
177181

182+
def has_copyrights(self):
183+
if len(self.copyrights) > 0:
184+
return True
185+
178186
def to_dict(self):
179187
d = OrderedDict([
180188
('path', self.path),

src/deltacode/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ def collect_errors(deltacode):
4545
def deltas(deltacode, all_delta_types=False):
4646
"""
4747
Return a generator of Delta dictionaries for JSON serialized ouput. Omit
48-
all unmodified Delta objects -- identified by a 'score' of 0 -- unless the
49-
user selects the '-a'/'--all' option.
48+
all unmodified Delta objects unless the user selects the '-a'/'--all'
49+
option.
5050
"""
5151
for delta in deltacode.deltas:
5252
if all_delta_types is True:
5353
yield delta.to_dict()
54-
elif delta.score != 0:
54+
elif not delta.is_unmodified():
5555
yield delta.to_dict()
5656

5757

tests/data/cli/1_file_moved.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Factors,Score,Path,Name,Type,Size,Old Path
2-
moved,5,b/a4.py,a4.py,file,200,a/a4.py
2+
moved,0,b/a4.py,a4.py,file,200,a/a4.py
33
unmodified,0,a/a3.py,a3.py,file,200,
44
unmodified,0,b/b4.py,b4.py,file,200,
55
unmodified,0,a/a2.py,a2.py,file,200,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
Factors,Score,Path,Name,Type,Size,Old Path
2-
moved,5,b/a4.py,a4.py,file,200,a/a4.py
2+
moved,0,b/a4.py,a4.py,file,200,a/a4.py
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
Factors,Score,Path,Name,Type,Size,Old Path
22
added,100,b/a4.py,a4.py,file,200,
33
added,100,b/a4_copy.py,a4_copy.py,file,200,
4-
removed,10,a/a4.py,a4.py,file,200,
4+
removed,0,a/a4.py,a4.py,file,200,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
Factors,Score,Path,Name,Type,Size,Old Path
22
added,100,b/a4.py,a4.py,file,200,
33
added,100,c/a4.py,a4.py,file,200,
4-
removed,10,a/a4.py,a4.py,file,200,
4+
removed,0,a/a4.py,a4.py,file,200,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
Factors,Score,Path,Name,Type,Size,Old Path
2-
modified license info added copyright info added,50,path.txt,path.txt,file,300,
2+
modified license info added copyleft added copyright info added,70,path.txt,path.txt,file,300,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
Factors,Score,Path,Name,Type,Size,Old Path
2-
modified license info added copyright info removed,50,path.txt,path.txt,file,300,
2+
modified license info added copyleft added copyright info removed,70,path.txt,path.txt,file,300,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
Factors,Score,Path,Name,Type,Size,Old Path
22
modified,20,some/path/c/c1.py,c1.py,file,300,
3-
modified license change,30,some/path/a/a1.py,a1.py,file,300,
3+
modified license change copyleft added,50,some/path/a/a1.py,a1.py,file,300,
44
modified license change,30,some/path/b/b1.py,b1.py,file,300,

0 commit comments

Comments
 (0)