Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 53 additions & 14 deletions src/deltacode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def __init__(self, new_path, old_path, options):
self.determine_delta()
self.determine_moved()
self.license_diff()
self.copyright_diff()
# Sort deltas by score, descending, i.e., high > low.
self.deltas.sort(key=lambda Delta: Delta.score, reverse=True)

Expand Down Expand Up @@ -191,28 +192,57 @@ def license_diff(self):
attribute -- if there has been a license change and depending on the
nature of that change.
"""
for i in self.deltas:
if 20 <= i.score < 100:
for delta in self.deltas:
if 20 <= delta.score < 100:

new_licenses = i.new_file.licenses or []
old_licenses = i.old_file.licenses or []
new_licenses = delta.new_file.licenses or []
old_licenses = delta.old_file.licenses or []

if len(i.new_file.licenses) > 0 and i.old_file.licenses == []:
i.factors.append('license info added')
i.score += 20
if len(delta.new_file.licenses) > 0 and delta.old_file.licenses == []:
delta.update(20, 'license info added')
return

if i.new_file.licenses == [] and len(i.old_file.licenses) > 0:
i.factors.append('license info removed')
i.score += 15
if delta.new_file.licenses == [] and len(delta.old_file.licenses) > 0:
delta.update(15, 'license info removed')
return

new_keys = set(l.key for l in new_licenses)
old_keys = set(l.key for l in old_licenses)
new_keys = set(license.key for license in new_licenses)
old_keys = set(license.key for license in old_licenses)

if new_keys != old_keys:
i.factors.append('license change')
i.score += 10
delta.update(10, 'license change')

def copyright_diff(self):
"""
Compare the copyright details for a pair of 'new' and 'old' File objects
in a Delta object and change the Delta object's 'score' attribute --
and add an appropriate category (e.g., 'copyright info removed', 'copyright
info added' or 'copyright change') to the Delta object's 'factors'
attribute -- if there has been a copyright change and depending on the
nature of that change.
"""
for delta in self.deltas:
if 20 <= delta.score < 100:

new_copyrights = delta.new_file.copyrights or []
old_copyrights = delta.old_file.copyrights or []

if len(delta.new_file.copyrights) > 0 and delta.old_file.copyrights == []:
delta.update(10, 'copyright info added')
return
elif delta.new_file.copyrights == [] and len(delta.old_file.copyrights) > 0:
delta.update(10, 'copyright info removed')
return

new_statements = set(statement for copyright in new_copyrights for statement in copyright.statements)
old_statements = set(statement for copyright in old_copyrights for statement in copyright.statements)

new_holders = set(holder for copyright in new_copyrights for holder in copyright.holders)
old_holders = set(holder for copyright in old_copyrights for holder in copyright.holders)

if ((new_statements != old_statements) or
(new_holders != old_holders)):
delta.update(5, 'copyright change')

def index_deltas(self, index_key='path', delta_list=[]):
"""
Expand Down Expand Up @@ -250,6 +280,15 @@ def __init__(self, score=0, new_file=None, old_file=None):
self.factors = []
self.score = score

def update(self, score=0, factor=''):
"""
Add the score to the Delta object's 'score' attribute and add a string,
summarizing the factor associated with the score, to the object's
'factors' attribute (a list).
"""
self.factors.append(factor)
self.score += score

def to_dict(self):
"""
Return an OrderedDict comprising the 'factors', 'score' and new and old
Expand Down
49 changes: 47 additions & 2 deletions src/deltacode/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,20 @@ def __init__(self, dictionary={}):
self.sha1 = dictionary.get('sha1', '')
self.original_path = ''
self.licenses = self.get_licenses(dictionary) if dictionary.get('licenses') else []
self.copyrights = self.get_copyrights(dictionary) if dictionary.get('copyrights') else []

def get_licenses(self, dictionary):
if dictionary.get('licenses') == []:
return []
else:
return [License(l) for l in dictionary.get('licenses')]

def get_copyrights(self, dictionary):
if dictionary.get('copyrights') == []:
return []
else:
return [Copyright(l) for l in dictionary.get('copyrights')]

def to_dict(self):
d = OrderedDict([
('path', self.path),
Expand All @@ -180,6 +187,13 @@ def to_dict(self):

if self.licenses:
d['licenses'] = [l.to_dict() for l in self.licenses]
else:
d['licenses'] = []

if self.copyrights:
d['copyrights'] = [l.to_dict() for l in self.copyrights]
else:
d['copyrights'] = []

return d

Expand All @@ -199,7 +213,8 @@ def __repr__(self):

class License(object):
"""
License object created from the 'license' field in an ABCD formatted 'file' dictionary.
License object created from the 'license' field in an ABCD formatted 'file'
dictionary.
"""
def __init__(self, dictionary={}):
self.key = dictionary.get('key')
Expand All @@ -225,7 +240,37 @@ def to_dict(self):

def __repr__(self):
"""
Return string containing a printable representation of the License object.
Return string containing a printable representation of the License
object.
"""
return "%s" % self.__dict__


class Copyright(object):
"""
Copyright object created from the 'copyrights' field in an ABCD formatted
'file' dictionary.
"""
def __init__(self, dictionary={}):
self.statements = dictionary.get('statements')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you haven’t already, you should add tests cases where a files have large numbers of copyrights holders and statements to see if we choke somewhere, espically in the output

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also tests where there are strange characters or accent marks in the copyright statements/holders.

All of these additional test cases I mentioned should probably come from scancode generated output directly as opposed to crafting the test object by hand.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG Since we want to use ScanCode-generated output, do you have any codebases in mind that satisfy the characteristics you describe?

I've started to work my way through the codebases we've worked with (openssl, zlib et al.) but I've not yet seen large numbers of copyright holders/statements or unusual characters.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnmhoran You will probably just have to hand-create a file or files that contain a bunch of copyright statements

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Thanks, @MaJuRG .

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG I'm finding that an error is thrown when I include a French accent character in the copyright statements or holders value, e.g., "é". The error is thrown even if I comment out the character (presumably because even comments are parsed). Unicode and UTF-8 do not throw an error.

...
"statements": [
    "U+00E9",
    "\xc3\xa9"
    # "é"
...

SyntaxError: Non-ASCII character '\xc3' in file C:\code\nexb\dev\deltacode\tests\test_models.py on line 1073, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

While PEP 263 gives some suggestions, it's not clear to me how we can apply these to handle our input. I've done some searching in the ScanCode repo -- surely ScanCode must be able to handle such characters -- but have not yet found how ScanCode addresses this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this issue might be addressed in scancode-toolkit/src/commoncode/text.py?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnmhoran where is this "statements" located?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG Per your suggestion above, I'm hand-crafting old and new files in a new test, test_Copyright_unusual_characters(), in test_models.py.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG Would it be easier for you if I commit and push? Except for this one failing test, it's ready for your review.

self.holders = dictionary.get('holders')

def to_dict(self):
"""
Given a Copyright object, return an OrderedDict with the full
set of fields from the ScanCode 'copyrights' value.
"""
d = OrderedDict([
('statements', self.statements),
('holders', self.holders)
])

return d

def __repr__(self):
"""
Return string containing a printable representation of the Copyright
object.
"""
return "%s" % self.__dict__

Expand Down
2 changes: 2 additions & 0 deletions tests/data/cli/copyright_and_license_info_added.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Factors,Score,Path,Name,Type,Size,Old Path
modified license info added copyright info added,50,path.txt,path.txt,file,300,
63 changes: 63 additions & 0 deletions tests/data/cli/copyright_and_license_info_added_new.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"scancode_notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
"scancode_version": "2.1.0",
"scancode_options": {
"--license": true,
"--info": true
},
"files_count": 2,
"files": [
{
"path": "new/default.txt",
"type": "file",
"name": "default.txt",
"size": 100,
"sha1": "a",
"licenses": [],
"copyrights": []
},
{
"path": "new/path.txt",
"type": "file",
"name": "path.txt",
"size": 300,
"sha1": "b_modified",
"licenses": [
{
"key": "gpl-1.0-plus",
"score": 20.0,
"short_name": "GPL 1.0 or later",
"category": "Copyleft",
"owner": "Free Software Foundation (FSF)",
"homepage_url": "http://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html",
"text_url": "http://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html",
"reference_url": "https://enterprise.dejacode.com/urn/urn:dje:license:gpl-1.0-plus",
"spdx_license_key": "GPL-1.0+",
"spdx_url": "https://spdx.org/licenses/GPL-1.0",
"start_line": 353,
"end_line": 353,
"matched_rule": {
"identifier": "gpl_63.RULE",
"license_choice": false,
"licenses": [
"gpl-1.0-plus"
]
}
}
],
"copyrights": [
{
"statements": [
"Copyright (c) 2016 Mark Adler"
],
"holders": [
"Mark Adler"
],
"authors": [],
"start_line": 1,
"end_line": 3
}
]
}
]
}
29 changes: 29 additions & 0 deletions tests/data/cli/copyright_and_license_info_added_old.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"scancode_notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
"scancode_version": "2.1.0",
"scancode_options": {
"--license": true,
"--info": true
},
"files_count": 2,
"files": [
{
"path": "old/default.txt",
"type": "file",
"name": "default.txt",
"size": 100,
"sha1": "a",
"licenses": [],
"copyrights": []
},
{
"path": "old/path.txt",
"type": "file",
"name": "path.txt",
"size": 300,
"sha1": "b",
"licenses": [],
"copyrights": []
}
]
}
2 changes: 2 additions & 0 deletions tests/data/cli/copyright_and_license_info_removed.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Factors,Score,Path,Name,Type,Size,Old Path
modified license info removed copyright info removed,45,path.txt,path.txt,file,300,
29 changes: 29 additions & 0 deletions tests/data/cli/copyright_and_license_info_removed_new.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"scancode_notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
"scancode_version": "2.1.0",
"scancode_options": {
"--license": true,
"--info": true
},
"files_count": 2,
"files": [
{
"path": "new/default.txt",
"type": "file",
"name": "default.txt",
"size": 100,
"sha1": "a",
"licenses": [],
"copyrights": []
},
{
"path": "new/path.txt",
"type": "file",
"name": "path.txt",
"size": 300,
"sha1": "b_modified",
"licenses": [],
"copyrights": []
}
]
}
63 changes: 63 additions & 0 deletions tests/data/cli/copyright_and_license_info_removed_old.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"scancode_notice": "Generated with ScanCode and provided on an \"AS IS\" BASIS, WITHOUT WARRANTIES\nOR CONDITIONS OF ANY KIND, either express or implied. No content created from\nScanCode should be considered or used as legal advice. Consult an Attorney\nfor any legal advice.\nScanCode is a free software code scanning tool from nexB Inc. and others.\nVisit https://github.com/nexB/scancode-toolkit/ for support and download.",
"scancode_version": "2.1.0",
"scancode_options": {
"--license": true,
"--info": true
},
"files_count": 2,
"files": [
{
"path": "old/default.txt",
"type": "file",
"name": "default.txt",
"size": 100,
"sha1": "a",
"licenses": [],
"copyrights": []
},
{
"path": "old/path.txt",
"type": "file",
"name": "path.txt",
"size": 300,
"sha1": "b",
"licenses": [
{
"key": "gpl-1.0-plus",
"score": 20.0,
"short_name": "GPL 1.0 or later",
"category": "Copyleft",
"owner": "Free Software Foundation (FSF)",
"homepage_url": "http://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html",
"text_url": "http://www.gnu.org/licenses/old-licenses/gpl-1.0-standalone.html",
"reference_url": "https://enterprise.dejacode.com/urn/urn:dje:license:gpl-1.0-plus",
"spdx_license_key": "GPL-1.0+",
"spdx_url": "https://spdx.org/licenses/GPL-1.0",
"start_line": 353,
"end_line": 353,
"matched_rule": {
"identifier": "gpl_63.RULE",
"license_choice": false,
"licenses": [
"gpl-1.0-plus"
]
}
}
],
"copyrights": [
{
"statements": [
"Copyright (c) 2016 Mark Adler"
],
"holders": [
"Mark Adler"
],
"authors": [],
"start_line": 1,
"end_line": 3
}
]
}
]
}
2 changes: 2 additions & 0 deletions tests/data/cli/copyright_change_no_license_change.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Factors,Score,Path,Name,Type,Size,Old Path
modified copyright change,25,path.txt,path.txt,file,300,
Loading