Skip to content

Add 'Snippet' class as a part of the SPDX 2.1 specification #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
11 changes: 11 additions & 0 deletions data/SPDXRdfExample.rdf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
xmlns:j.0="http://usefulinc.com/ns/doap#"
xmlns="http://spdx.org/rdf/terms#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
<Snippet rdf:about="http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301#SPDXRef-Snippet">
<snippetFromFile>
<File rdf:about="http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301#SPDXRef-DoapSource"/>
</snippetFromFile>
<name>from linux kernel</name>
<copyrightText>Copyright 2008-2010 John Smith</copyrightText>
<licenseComments>The concluded license was taken from package xyz, from which the snippet was copied into the current file. The concluded license information was found in the COPYING.txt file in package xyz.</licenseComments>
<rdfs:comment>This snippet was identified as significant and highlighted in this Apache-2.0 file, when a commercial scanner identified it as being derived from file foo.c in package xyz which is licensed under GPL-2.0-or-later.</rdfs:comment>
<licenseConcluded rdf:resource="http://spdx.org/licenses/Apache-2.0"/>
<licenseInfoInSnippet rdf:resource="http://spdx.org/licenses/Apache-2.0"/>
</Snippet>
<SpdxDocument rdf:about="http://www.spdx.org/tools#SPDXANALYSIS">
<creationInfo>
<CreationInfo>
Expand Down
9 changes: 9 additions & 0 deletions data/SPDXTagExample.tag
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ ArtifactOfProjectHomePage: http://www.openjena.org/
ArtifactOfProjectURI: UNKNOWN
FileComment: <text>This file belongs to Jena</text>

## Snippet Information
SnippetSPDXID: SPDXRef-Snippet
SnippetFromFileSPDXID: SPDXRef-DoapSource
SnippetLicenseComments: <text>The concluded license was taken from package xyz, from which the snippet was copied into the current file. The concluded license information was found in the COPYING.txt file in package xyz.</text>
SnippetCopyrightText: <text> Copyright 2008-2010 John Smith </text>
SnippetComment: <text>This snippet was identified as significant and highlighted in this Apache-2.0 file, when a commercial scanner identified it as being derived from file foo.c in package xyz which is licensed under GPL-2.0-or-later.</text>
SnippetName: from linux kernel
SnippetLicenseConcluded: Apache-2.0
LicenseInfoInSnippet: Apache-2.0

## License Information
LicenseID: LicenseRef-3
Expand Down
18 changes: 17 additions & 1 deletion spdx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,11 @@ class Document(object):
SPDX license list. Optional, many. Type: ExtractedLicense.
- reviews: SPDX document review information, Optional zero or more.
Type: Review.
- snippet: Snippet information. Optional zero or more. Type: Snippet.
"""

def __init__(self, version=None, data_license=None, comment=None, package=None):
def __init__(self, version=None, data_license=None, comment=None,
package=None, snippet=None):
# avoid recursive impor
from spdx.creationinfo import CreationInfo
self.version = version
Expand All @@ -208,13 +210,17 @@ def __init__(self, version=None, data_license=None, comment=None, package=None):
self.package = package
self.extracted_licenses = []
self.reviews = []
self.snippet = []

def add_review(self, review):
self.reviews.append(review)

def add_extr_lic(self, lic):
self.extracted_licenses.append(lic)

def add_snippet(self, snip):
self.snippet.append(snip)

@property
def files(self):
return self.package.files
Expand All @@ -241,6 +247,7 @@ def validate(self, messages=None):
and self.validate_package(messages)
and self.validate_extracted_licenses(messages)
and self.validate_reviews(messages)
and self.validate_snippet(messages)
)

def validate_version(self, messages=None):
Expand Down Expand Up @@ -277,6 +284,15 @@ def validate_reviews(self, messages=None):
valid = review.validate(messages) and valid
return valid

def validate_snippet(self, messages=None):
# FIXME: messages should be returned
messages = messages if messages is not None else []

valid = True
for snippet in self.snippet:
valid = snippet.validate(messages) and valid
return valid

def validate_creation_info(self, messages=None):
# FIXME: messages should be returned
messages = messages if messages is not None else []
Expand Down
12 changes: 11 additions & 1 deletion spdx/parsers/lexers/tagvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ class Lexer(object):
'LicenseName': 'LICS_NAME',
'LicenseCrossReference': 'LICS_CRS_REF',
'LicenseComment': 'LICS_COMMENT',
# Snippet
'SnippetSPDXID': 'SNIPPET_SPDX_ID',
'SnippetName': 'SNIPPET_NAME',
'SnippetComment': 'SNIPPET_COMMENT',
'SnippetCopyrightText': 'SNIPPET_CR_TEXT',
'SnippetLicenseComments': 'SNIPPET_LICS_COMMENT',
'SnippetFromFileSPDXID': 'SNIPPET_FILE_SPDXID',
'SnippetLicenseConcluded': 'SNIPPET_LICS_CONC',
'LicenseInfoInSnippet': 'SNIPPET_LICS_INFO',
# Common
'NOASSERTION': 'NO_ASSERT',
'UNKNOWN': 'UN_KNOWN',
Expand All @@ -94,8 +103,9 @@ def t_text_end(self, t):
r'</text>\s*'
t.type = 'TEXT'
t.value = t.lexer.lexdata[
t.lexer.text_start:t.lexer.lexpos].strip()
t.lexer.text_start:t.lexer.lexpos]
t.lexer.lineno += t.value.count('\n')
t.value = t.value.strip()
t.lexer.begin('INITIAL')
return t

Expand Down
91 changes: 90 additions & 1 deletion spdx/parsers/rdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
'FILE_SINGLE_LICS': 'File concluded license must be a license url or spdx:noassertion or spdx:none.',
'REVIEWER_VALUE' : 'Invalid reviewer value \'{0}\' must be Organization, Tool or Person.',
'REVIEW_DATE' : 'Invalid review date value \'{0}\' must be date in ISO 8601 format.',
'SNIPPET_SPDX_ID_VALUE' : 'SPDXID must be "SPDXRef-[idstring]" where [idstring] is a unique string '
'containing letters, numbers, ".", "-".',
'SNIPPET_SINGLE_LICS' : 'Snippet Concluded License must be a license url or spdx:noassertion or spdx:none.',
'SNIPPET_LIC_INFO' : 'License Information in Snippet must be a license url or a reference '
'to the license, denoted by LicenseRef-[idstring] or spdx:noassertion or spdx:none.',
}


Expand Down Expand Up @@ -658,6 +663,87 @@ def p_file_lic_conc(self, f_term, predicate):
self.more_than_one_error('file {0}'.format(predicate))


class SnippetParser(LicenseParser):
"""
Helper class for parsing snippet information.
"""

def __init__(self, builder, logger):
super(SnippetParser, self).__init__(builder, logger)

def parse_snippet(self, snippet_term):
try:
self.builder.create_snippet(self.doc, snippet_term)
except SPDXValueError:
self.value_error('SNIPPET_SPDX_ID_VALUE', snippet_term)

for _s, _p, o in self.graph.triples((snippet_term, self.spdx_namespace['name'], None)):
try:
self.builder.set_snippet_name(self.doc, six.text_type(o))
except CardinalityError:
self.more_than_one_error('snippetName')
break

for _s, _p, o in self.graph.triples((snippet_term, self.spdx_namespace['licenseComments'], None)):
try:
self.builder.set_snippet_lic_comment(self.doc, six.text_type(o))
except CardinalityError:
self.more_than_one_error('licenseComments')
break

for _s, _p, o in self.graph.triples((snippet_term, RDFS.comment, None)):
try:
self.builder.set_snippet_comment(self.doc, six.text_type(o))
except CardinalityError:
self.more_than_one_error('comment')
break

for _s, _p, o in self.graph.triples((snippet_term, self.spdx_namespace['copyrightText'], None)):
try:
self.builder.set_snippet_copyright(self.doc, self.to_special_value(six.text_type(o)))
except CardinalityError:
self.more_than_one_error('copyrightText')
break

try:
for _, _, licenses in self.graph.triples(
(snippet_term, self.spdx_namespace['licenseConcluded'], None)):
if (licenses, RDF.type, self.spdx_namespace['ConjunctiveLicenseSet']) in self.graph:
lics = self.handle_conjunctive_list(licenses)
self.builder.set_snip_concluded_license(self.doc, lics)

elif (licenses, RDF.type, self.spdx_namespace['DisjunctiveLicenseSet']) in self.graph:
lics = self.handle_disjunctive_list(licenses)
self.builder.set_snip_concluded_license(self.doc, lics)

else:
try:
lics = self.handle_lics(licenses)
self.builder.set_snip_concluded_license(self.doc, lics)
except SPDXValueError:
self.value_error('SNIPPET_SINGLE_LICS', licenses)
except CardinalityError:
self.more_than_one_error('package {0}'.format(
self.spdx_namespace['licenseConcluded']))

for _, _, info in self.graph.triples(
(snippet_term, self.spdx_namespace['licenseInfoInSnippet'], None)):
lic = self.handle_lics(info)
if lic is not None:
try:
self.builder.set_snippet_lics_info(self.doc, lic)
except SPDXValueError:
self.value_error('SNIPPET_LIC_INFO', lic)

for _s, _p, o in self.graph.triples(
(snippet_term, self.spdx_namespace['snippetFromFile'], None)):
try:
self.builder.set_snip_from_file_spdxid(self.doc, six.text_type(o))
except CardinalityError:
self.more_than_one_error('snippetFromFile')
break


class ReviewParser(BaseParser):
"""
Helper class for parsing review information.
Expand Down Expand Up @@ -722,7 +808,7 @@ def get_reviewer(self, r_term):
self.value_error('REVIEWER_VALUE', reviewer_list[0][2])


class Parser(PackageParser, FileParser, ReviewParser):
class Parser(PackageParser, FileParser, SnippetParser, ReviewParser):
"""
RDF/XML file parser.
"""
Expand Down Expand Up @@ -751,6 +837,9 @@ def parse(self, fil):
for s, _p, o in self.graph.triples((None, self.spdx_namespace['referencesFile'], None)):
self.parse_file(o)

for s, _p, o in self.graph.triples((None, RDF.type, self.spdx_namespace['Snippet'])):
self.parse_snippet(s)

for s, _p, o in self.graph.triples((None, self.spdx_namespace['reviewed'], None)):
self.parse_review(o)

Expand Down
47 changes: 46 additions & 1 deletion spdx/parsers/rdfbuilders.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,50 @@ def set_file_notice(self, doc, text):
raise OrderError('File::Notice')


class SnippetBuilder(tagvaluebuilders.SnippetBuilder):

def __init__(self):
super(SnippetBuilder, self).__init__()

def set_snippet_lic_comment(self, doc, lic_comment):
"""Sets the snippet's license comment.
Raises OrderError if no snippet previously defined.
Raises CardinalityError if already set.
"""
self.assert_snippet_exists()
if not self.snippet_lic_comment_set:
self.snippet_lic_comment_set = True
doc.snippet[-1].license_comment = lic_comment
else:
CardinalityError('Snippet::licenseComments')

def set_snippet_comment(self, doc, comment):
"""
Sets general comments about the snippet.
Raises OrderError if no snippet previously defined.
Raises CardinalityError if comment already set.
"""
self.assert_snippet_exists()
if not self.snippet_comment_set:
self.snippet_comment_set = True
doc.snippet[-1].comment = comment
return True
else:
raise CardinalityError('Snippet::comment')

def set_snippet_copyright(self, doc, copyright):
"""Sets the snippet's copyright text.
Raises OrderError if no snippet previously defined.
Raises CardinalityError if already set.
"""
self.assert_snippet_exists()
if not self.snippet_copyright_set:
self.snippet_copyright_set = True
doc.snippet[-1].copyright = copyright
else:
raise CardinalityError('Snippet::copyrightText')


class ReviewBuilder(tagvaluebuilders.ReviewBuilder):

def __init__(self):
Expand All @@ -325,7 +369,8 @@ def add_review_comment(self, doc, comment):
raise OrderError('ReviewComment')


class Builder(DocBuilder, EntityBuilder, CreationInfoBuilder, PackageBuilder, FileBuilder, ReviewBuilder):
class Builder(DocBuilder, EntityBuilder, CreationInfoBuilder, PackageBuilder, FileBuilder,
SnippetBuilder, ReviewBuilder):

def __init__(self):
super(Builder, self).__init__()
Expand Down
Loading