Skip to content

Commit 7b56e8f

Browse files
authored
Merge pull request #77 from nexB/62-pass-only-score-during-delta-creation
Refactor scoring structure #62 #63 #64
2 parents 9794515 + d720912 commit 7b56e8f

81 files changed

Lines changed: 1696 additions & 402043 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/deltacode/__init__.py

Lines changed: 83 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def __init__(self, new_path, old_path, options):
5656
if self.new.path != '' and self.old.path != '':
5757
self.determine_delta()
5858
self.determine_moved()
59-
# TODO: how can we test the sort order?
59+
self.license_diff()
6060
# Sort deltas by score, descending, i.e., high > low.
6161
self.deltas.sort(key=lambda Delta: Delta.score, reverse=True)
6262

@@ -77,8 +77,8 @@ def align_scans(self):
7777

7878
def determine_delta(self):
7979
"""
80-
Add to a list of Delta objects that can be sorted by their attributes,
81-
e.g., by Delta.score. Return None if no File objects can be loaded
80+
Add to a list of Delta objects that can be sorted by their attributes,
81+
e.g., by Delta.score. Return None if no File objects can be loaded
8282
from either scan.
8383
"""
8484
# align scan and create our index
@@ -95,13 +95,15 @@ def determine_delta(self):
9595

9696
if new_file.type != 'file':
9797
continue
98-
98+
9999
new_visited += 1
100100

101101
try:
102102
delta_old_files = old_index[path]
103103
except KeyError:
104-
self.deltas.append(Delta(new_file, None, 'added'))
104+
delta = Delta(100, new_file, None)
105+
delta.factors.append('added')
106+
self.deltas.append(delta)
105107
continue
106108

107109
# at this point, we have a delta_old_file.
@@ -110,25 +112,30 @@ def determine_delta(self):
110112
for f in delta_old_files:
111113
# TODO: make sure sha1 is NOT empty
112114
if new_file.sha1 == f.sha1:
113-
self.deltas.append(Delta(new_file, f, 'unmodified'))
115+
delta = Delta(0, new_file, f)
116+
delta.factors.append('unmodified')
117+
self.deltas.append(delta)
114118
continue
115119
else:
116-
delta = Delta(new_file, f, 'modified')
120+
delta = Delta(20, new_file, f)
121+
delta.factors.append('modified')
117122
self.deltas.append(delta)
118123

119124
# now time to find the added.
120125
for path, old_files in old_index.items():
121126
for old_file in old_files:
122127
if old_file.type != 'file':
123128
continue
124-
129+
125130
old_visited += 1
126131

127132
try:
128133
# This file already classified so do nothing
129134
new_index[path]
130135
except KeyError:
131-
self.deltas.append(Delta(None, old_file, 'removed'))
136+
delta = Delta(10, None, old_file)
137+
delta.factors.append('removed')
138+
self.deltas.append(delta)
132139
continue
133140

134141
# make sure everything is accounted for
@@ -149,10 +156,11 @@ def determine_moved(self):
149156
by their 'sha1' attribute, identifying any unique pairs of Deltas in
150157
both indices with the same 'sha1' and File 'name' attributes, and
151158
converting each such pair of 'added' and 'removed' Delta objects to a
152-
'moved' Delta object.
159+
'moved' Delta object. The 'added' and 'removed' indices are defined by
160+
the 'score' attribute of the Delta objects.
153161
"""
154-
added = self.index_deltas('sha1', [i for i in self.deltas if i.category == 'added'])
155-
removed = self.index_deltas('sha1', [i for i in self.deltas if i.category == 'removed'])
162+
added = self.index_deltas('sha1', [i for i in self.deltas if i.score == 100])
163+
removed = self.index_deltas('sha1', [i for i in self.deltas if i.score == 10])
156164

157165
# TODO: should it be iteritems() or items()
158166
for added_sha1, added_deltas in added.iteritems():
@@ -165,28 +173,61 @@ def determine_moved(self):
165173
def update_deltas(self, added, removed):
166174
"""
167175
Convert the matched 'added' and 'removed' Delta objects to a combined
168-
'moved' Delta object and delete the 'added' and 'removed' objects.
176+
'moved' Delta object -- passing the appropriate 'score' during object
177+
creation -- and delete the 'added' and 'removed' objects.
169178
"""
170-
self.deltas.append(Delta(added.new_file, removed.old_file, 'moved'))
179+
delta = Delta(5, added.new_file, removed.old_file)
180+
delta.factors.append('moved')
181+
self.deltas.append(delta)
171182
self.deltas.remove(added)
172183
self.deltas.remove(removed)
173184

185+
def license_diff(self):
186+
"""
187+
Compare the license details for a pair of 'new' and 'old' File objects
188+
in a Delta object and change the Delta object's 'score' attribute --
189+
and add an appropriate category (e.g., 'license info removed', 'license
190+
info added' or 'license change') to the Delta object's 'factors'
191+
attribute -- if there has been a license change and depending on the
192+
nature of that change.
193+
"""
194+
for i in self.deltas:
195+
if 20 <= i.score < 100:
196+
197+
new_licenses = i.new_file.licenses or []
198+
old_licenses = i.old_file.licenses or []
199+
200+
if len(i.new_file.licenses) > 0 and i.old_file.licenses == []:
201+
i.factors.append('license info added')
202+
i.score += 20
203+
return
204+
205+
if i.new_file.licenses == [] and len(i.old_file.licenses) > 0:
206+
i.factors.append('license info removed')
207+
i.score += 15
208+
return
209+
210+
new_keys = set(l.key for l in new_licenses)
211+
old_keys = set(l.key for l in old_licenses)
212+
213+
if new_keys != old_keys:
214+
i.factors.append('license change')
215+
i.score += 10
216+
174217
def index_deltas(self, index_key='path', delta_list=[]):
175218
"""
176219
Return a dictionary of a list of Delta objects indexed by the key
177220
passed via the 'index_key' variable. If no 'index_key' variable is
178221
passed, the dict is keyed by the Delta object's 'path' variable. For a
179-
'removed' Delta object, use the variable from the 'old_file'; for all
180-
other Delta objects (e.g., 'added'), use the 'new_file'. This function
181-
does not currently catch the AttributeError exception.
222+
'removed' Delta object -- identified by its 'score' attribute -- use
223+
the variable from the 'old_file'; for all other Delta objects (e.g.,
224+
'added'), use the 'new_file'. This function does not currently catch
225+
the AttributeError exception.
182226
"""
183227
index = {}
184228

185229
for delta in delta_list:
186-
if delta.category == 'removed':
187-
key = getattr(delta.old_file, index_key)
188-
else:
189-
key = getattr(delta.new_file, index_key)
230+
key = getattr(delta.new_file if delta.new_file else delta.old_file, index_key)
190231

191232
if index.get(key) is None:
192233
index[key] = []
@@ -196,111 +237,37 @@ def index_deltas(self, index_key='path', delta_list=[]):
196237

197238
return index
198239

199-
def get_stats(self):
200-
"""
201-
Given a list of Delta objects, return a 'counts' dictionary keyed by
202-
the Delta object's 'category' attribute that contains the count as a
203-
value for each category.
204-
"""
205-
added, modified, moved, removed, unmodified = 0, 0, 0, 0, 0
206-
207-
added = len([i for i in self.deltas if i.category == 'added'])
208-
modified = len([i for i in self.deltas if i.category == 'modified'])
209-
moved = len([i for i in self.deltas if i.category == 'moved'])
210-
removed = len([i for i in self.deltas if i.category == 'removed'])
211-
unmodified = len([i for i in self.deltas if i.category == 'unmodified'])
212-
213-
return OrderedDict([('added', added), ('modified', modified), ('moved', moved), ('removed', removed), ('unmodified', unmodified)])
214-
215240

216241
class Delta(object):
217242
"""
218243
A tuple reflecting a comparison of two files -- each of which is a File
219-
object -- and the category that characterizes the comparison:
220-
'added', 'modified', 'moved', 'removed' or 'unmodified'.
244+
object -- and the 'factors' (e.g., 'added', 'modified' etc.) and related
245+
'score' that characterize that comparison.
221246
"""
222-
def __init__(self, new_file=None, old_file=None, delta_type=None, score=0):
223-
self.new_file = new_file if new_file else File()
224-
self.old_file = old_file if old_file else File()
225-
self.category = delta_type if delta_type else ''
247+
def __init__(self, score=0, new_file=None, old_file=None):
248+
self.new_file = new_file if new_file else None
249+
self.old_file = old_file if old_file else None
250+
self.factors = []
226251
self.score = score
227252

228-
# If a license change is detected, and depending on the nature of that change,
229-
# change the Delta object's 'category' attribute from 'modified' to
230-
# 'license change', 'license info removed' or 'license info added'.
231-
if self.category == 'modified':
232-
self._license_diff()
233-
234-
self.determine_score()
235-
236-
def _license_diff(self, cutoff_score=50):
237-
"""
238-
Compare the license details for a pair of 'new' and 'old' File objects
239-
in a Delta object and change the Delta object's 'category' attribute to
240-
'license info removed', 'license info added' or 'license change' if
241-
there has been a license change and depending on the nature of that change.
242-
"""
243-
new_licenses = self.new_file.licenses or []
244-
old_licenses = self.old_file.licenses or []
245-
246-
if len(self.new_file.licenses) > 0 and self.old_file.licenses == []:
247-
self.category = 'license info added'
248-
return
249-
250-
if self.new_file.licenses == [] and len(self.old_file.licenses) > 0:
251-
self.category = 'license info removed'
252-
return
253-
254-
new_keys = set(l.key for l in new_licenses if l.score >= cutoff_score)
255-
old_keys = set(l.key for l in old_licenses if l.score >= cutoff_score)
256-
257-
if new_keys != old_keys:
258-
self.category = 'license change'
259-
260-
def determine_score(self):
261-
"""
262-
Assign a score to each 'Delta' object by modifying the object's 'score'
263-
attribute based on the object's 'category' attribute.
264-
"""
265-
scores = {
266-
'added': 75,
267-
'license info added': 70,
268-
'license info removed': 65,
269-
'license change': 60,
270-
'modified': 50,
271-
'removed': 25,
272-
'moved': 0,
273-
'unmodified': 0
274-
}
275-
276-
self.score = scores.get(self.category, 0)
277-
278253
def to_dict(self):
279254
"""
280-
Check the 'category' attribute of the Delta object and return an
281-
OrderedDict comprising the 'category', 'score' and 'path' of the object.
255+
Return an OrderedDict comprising the 'factors', 'score' and new and old
256+
'path' attributes of the object.
282257
"""
283-
delta = OrderedDict([
284-
('category', self.category),
285-
('score', self.score)
286-
])
287-
288-
if self.category == 'added':
289-
delta.update(OrderedDict([
290-
('new', self.new_file.to_dict()),
291-
('old', None),
292-
]))
293-
294-
elif self.category == 'removed':
295-
delta.update(OrderedDict([
296-
('new', None),
297-
('old', self.old_file.to_dict()),
298-
]))
258+
if self.new_file:
259+
new_file = self.new_file.to_dict()
260+
else:
261+
new_file = None
299262

263+
if self.old_file:
264+
old_file = self.old_file.to_dict()
300265
else:
301-
delta.update(OrderedDict([
302-
('new', self.new_file.to_dict()),
303-
('old', self.old_file.to_dict()),
304-
]))
266+
old_file = None
305267

306-
return delta
268+
return OrderedDict([
269+
('factors', self.factors),
270+
('score', self.score),
271+
('new', new_file),
272+
('old', old_file),
273+
])

src/deltacode/cli.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,22 @@
4040
# FIXME: update the function argument delta to deltacode
4141
def write_csv(delta, result_file, all_delta_types=False):
4242
"""
43-
Using the DeltaCode object, create a .csv file
44-
containing the primary information from the Delta objects. Omit all Delta
45-
objects whose 'category' is 'unmodified' unless the user selects the
46-
'-a'/'--all' option.
43+
Using the DeltaCode object, create a .csv file containing the primary
44+
information from the Delta objects. Omit all unmodified Delta objects --
45+
identified by a 'score' of 0 -- unless the user selects the '-a'/'--all'
46+
option.
4747
"""
4848
with open(result_file, 'wb') as out:
4949
csv_out = csv.writer(out)
50-
csv_out.writerow(['Type of delta', 'Score', 'Path', 'Name', 'Type', 'Size', 'Old Path'])
50+
csv_out.writerow(['Factors', 'Score', 'Path', 'Name', 'Type', 'Size', 'Old Path'])
5151
for row in [(
52-
f.category,
52+
' '.join(f.factors),
5353
f.score,
54-
f.old_file.path if f.category == 'removed' else f.new_file.path,
55-
f.old_file.name if f.category == 'removed' else f.new_file.name,
56-
f.old_file.type if f.category == 'removed' else f.new_file.type,
57-
f.old_file.size if f.category == 'removed' else f.new_file.size,
58-
f.old_file.path if f.category == 'moved' else '')
54+
f.old_file.path if 'removed' in f.factors else f.new_file.path,
55+
f.old_file.name if 'removed' in f.factors else f.new_file.name,
56+
f.old_file.type if 'removed' in f.factors else f.new_file.type,
57+
f.old_file.size if 'removed' in f.factors else f.new_file.size,
58+
f.old_file.path if 'moved' in f.factors else '')
5959
for f in delta.deltas]:
6060
if all_delta_types is True:
6161
csv_out.writerow(row)
@@ -66,15 +66,14 @@ def write_csv(delta, result_file, all_delta_types=False):
6666
def write_json(deltacode, outfile, all_delta_types=False):
6767
"""
6868
Using the DeltaCode object, create a .json file containing the primary
69-
information from the Delta objects. Omit all Delta objects whose
70-
'category' is 'unmodified' unless the user selects the
71-
'-a'/'--all-delta-types' option.
69+
information from the Delta objects. Through a call to utils.deltas(), omit
70+
all unmodified Delta objects -- identified by a 'score' of 0 -- unless the
71+
user selects the '-a'/'--all-delta-types' option.
7272
"""
7373
results = OrderedDict([
7474
('deltacode_notice', get_notice()),
7575
('deltacode_options', deltacode.options),
7676
('deltacode_version', __version__),
77-
('deltacode_stats', deltacode.get_stats()),
7877
('deltacode_errors', collect_errors(deltacode)),
7978
('deltas', deltas(deltacode, all_delta_types))
8079
])

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 Delta objects whose 'category' is 'unmodified' unless the user selects
49-
the '-a'/'--all' option.
48+
all unmodified Delta objects -- identified by a 'score' of 0 -- unless the
49+
user selects the '-a'/'--all' option.
5050
"""
5151
for delta in deltacode.deltas:
5252
if all_delta_types is True:
5353
yield delta.to_dict()
54-
elif delta.category != 'unmodified':
54+
elif delta.score != 0:
5555
yield delta.to_dict()
5656

5757

tests/data/cli/1_file_moved.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Type of delta,Score,Path,Name,Type,Size,Old Path
2-
moved,0,b/a4.py,a4.py,file,200,a/a4.py
1+
Factors,Score,Path,Name,Type,Size,Old Path
2+
moved,5,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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Type of delta,Score,Path,Name,Type,Size,Old Path
2-
moved,0,b/a4.py,a4.py,file,200,a/a4.py
1+
Factors,Score,Path,Name,Type,Size,Old Path
2+
moved,5,b/a4.py,a4.py,file,200,a/a4.py
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Type of delta,Score,Path,Name,Type,Size,Old Path
2-
added,75,b/a4.py,a4.py,file,200,
3-
added,75,b/a4_copy.py,a4_copy.py,file,200,
4-
removed,25,a/a4.py,a4.py,file,200,
1+
Factors,Score,Path,Name,Type,Size,Old Path
2+
added,100,b/a4.py,a4.py,file,200,
3+
added,100,b/a4_copy.py,a4_copy.py,file,200,
4+
removed,10,a/a4.py,a4.py,file,200,
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Type of delta,Score,Path,Name,Type,Size,Old Path
2-
added,75,b/a4.py,a4.py,file,200,
3-
added,75,c/a4.py,a4.py,file,200,
4-
removed,25,a/a4.py,a4.py,file,200,
1+
Factors,Score,Path,Name,Type,Size,Old Path
2+
added,100,b/a4.py,a4.py,file,200,
3+
added,100,c/a4.py,a4.py,file,200,
4+
removed,10,a/a4.py,a4.py,file,200,

tests/data/cli/added1.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Type of delta,Score,Path,Name,Type,Size,Old Path
2-
added,75,a/a5.py,a5.py,file,200,
1+
Factors,Score,Path,Name,Type,Size,Old Path
2+
added,100,a/a5.py,a5.py,file,200,

0 commit comments

Comments
 (0)