Skip to content

Commit

Permalink
Add 'annotation' class as a part of the 2.1 spec.
Browse files Browse the repository at this point in the history
Add 'annotator','annotation_date', and 'comment'
to the 'annotation' class.

Signed-off-by: Yash Nisar <yash.nisar@somaiya.edu>
  • Loading branch information
yash-nisar committed Jun 12, 2018
1 parent f141f1f commit 8cb3582
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 4 deletions.
7 changes: 7 additions & 0 deletions data/SPDXRdfExample.rdf
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@
<reviewer>Person: Suzanne Reviewer</reviewer>
</Review>
</reviewed>
<annotation>
<Annotation>
<rdfs:comment>This is just an example. Some of the non-standard licenses look like they are actually BSD 3 clause licenses</rdfs:comment>
<annotationDate>2012-06-13T00:00:00Z</annotationDate>
<annotator>Person: Jim Reviewer</annotator>
</Annotation>
</annotation>
<referencesFile>
<File rdf:nodeID="A2">
<copyrightText>Copyright 2010, 2011 Source Auditor Inc.</copyrightText>
Expand Down
5 changes: 5 additions & 0 deletions data/SPDXTagExample.tag
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ Reviewer: Person: Suzanne Reviewer
ReviewDate: 2011-03-13T00:00:00Z
ReviewComment: <text>Another example reviewer.</text>

## Annotation Information
Annotator: Person: Jim Annotator
AnnotationDate: 2012-03-11T00:00:00Z
AnnotationComment: <text>An example annotation comment.</text>

## Package Information
PackageName: SPDX Translator
PackageVersion: Version 0.9.2
Expand Down
95 changes: 95 additions & 0 deletions spdx/annotation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

# Copyright (c) 2018 Yash M. Nisar
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

from datetime import datetime
from functools import total_ordering

from spdx.utils import datetime_iso_format


@total_ordering
class Annotation(object):

"""
Document annotation information.
Fields:
- annotator: Person, Organization or tool that has commented on a file,
package, or the entire document. Conditional (Mandatory, one), if there
is an Annotation.
- annotation_date: To identify when the comment was made. Conditional
(Mandatory, one), if there is an Annotation. Type: datetime.
- comment: Annotation comment. Conditional (Mandatory, one), if there is
an Annotation. Type: str.
"""

def __init__(self, annotator=None, annotation_date=None, comment=None):
self.annotator = annotator
self.annotation_date = annotation_date
self.comment = comment

def __eq__(self, other):
return (
isinstance(other, Annotation) and self.annotator == other.annotator
and self.annotation_date == other.annotation_date
and self.comment == other.comment
)

def __lt__(self, other):
return (
(self.annotator, self.annotation_date, self.comment) <
(other.annotator, other.annotation_date, other.comment,)
)

def set_annotation_date_now(self):
self.annotation_date = datetime.utcnow()

@property
def annotation_date_iso_format(self):
return datetime_iso_format(self.annotation_date)

@property
def has_comment(self):
return self.comment is not None

def validate(self, messages=None):
"""Returns True if all the fields are valid.
Appends any error messages to messages parameter.
"""
# FIXME: we should return messages instead
messages = messages if messages is not None else []

return (self.validate_annotator(messages)
and self.validate_annotation_date(messages))

def validate_annotator(self, messages=None):
# FIXME: we should return messages instead
messages = messages if messages is not None else []

if self.annotator is not None:
return True
else:
messages.append('Annotation missing annotator.')
return False

def validate_annotation_date(self, messages=None):
# FIXME: we should return messages instead
messages = messages if messages is not None else []

if self.annotation_date is not None:
return True
else:
messages.append('Annotation missing annotation date.')
return False
6 changes: 6 additions & 0 deletions spdx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ class Document(object):
SPDX license list. Optional, many. Type: ExtractedLicense.
- reviews: SPDX document review information, Optional zero or more.
Type: Review.
- annotations: SPDX document annotation information, Optional zero or more.
Type: Annotation.
"""

def __init__(self, version=None, data_license=None, comment=None, package=None):
Expand All @@ -208,10 +210,14 @@ def __init__(self, version=None, data_license=None, comment=None, package=None):
self.package = package
self.extracted_licenses = []
self.reviews = []
self.annotations = []

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

def add_annotation(self, annotation):
self.annotations.append(annotation)

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

Expand Down
4 changes: 4 additions & 0 deletions spdx/parsers/lexers/tagvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class Lexer(object):
'Reviewer': 'REVIEWER',
'ReviewDate': 'REVIEW_DATE',
'ReviewComment': 'REVIEW_COMMENT',
# Annotation info
'Annotator': 'ANNOTATOR',
'AnnotationDate': 'ANNOTATION_DATE',
'AnnotationComment': 'ANNOTATION_COMMENT',
# Package Fields
'PackageName': 'PKG_NAME',
'PackageVersion': 'PKG_VERSION',
Expand Down
71 changes: 70 additions & 1 deletion spdx/parsers/rdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
'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.',
'ANNOTATOR_VALUE': 'Invalid annotator value \'{0}\' must be Organization, Tool or Person.',
'ANNOTATION_DATE': 'Invalid annotation date value \'{0}\' must be date in ISO 8601 format.'
}


Expand Down Expand Up @@ -722,7 +724,71 @@ def get_reviewer(self, r_term):
self.value_error('REVIEWER_VALUE', reviewer_list[0][2])


class Parser(PackageParser, FileParser, ReviewParser):
class AnnotationParser(BaseParser):
"""
Helper class for parsing annotation information.
"""

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

def parse_annotation(self, r_term):
annotator = self.get_annotator(r_term)
annotation_date = self.get_annotation_date(r_term)
if annotator is not None:
self.builder.add_annotator(self.doc, annotator)
if annotation_date is not None:
try:
self.builder.add_annotation_date(self.doc, annotation_date)
except SPDXValueError:
self.value_error('ANNOTATION_DATE', annotation_date)
comment = self.get_annotation_comment(r_term)
if comment is not None:
self.builder.add_annotation_comment(self.doc, comment)

def get_annotation_comment(self, r_term):
"""Returns annotation comment or None if found none or more than one.
Reports errors.
"""
comment_list = list(self.graph.triples((r_term, RDFS.comment, None)))
if len(comment_list) > 1:
self.error = True
msg = 'Annotation can have at most one comment.'
self.logger.log(msg)
return
else:
return six.text_type(comment_list[0][2])

def get_annotation_date(self, r_term):
"""Returns annotation date or None if not found.
Reports error on failure.
Note does not check value format.
"""
annotation_date_list = list(self.graph.triples((r_term, self.spdx_namespace['annotationDate'], None)))
if len(annotation_date_list) != 1:
self.error = True
msg = 'Annotation must have exactly one annotation date.'
self.logger.log(msg)
return
return six.text_type(annotation_date_list[0][2])

def get_annotator(self, r_term):
"""Returns annotator as creator object or None if failed.
Reports errors on failure.
"""
annotator_list = list(self.graph.triples((r_term, self.spdx_namespace['annotator'], None)))
if len(annotator_list) != 1:
self.error = True
msg = 'Annotation must have exactly one annotator'
self.logger.log(msg)
return
try:
return self.builder.create_entity(self.doc, six.text_type(annotator_list[0][2]))
except SPDXValueError:
self.value_error('ANNOTATOR_VALUE', annotator_list[0][2])


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

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

validation_messages = []
# Report extra errors if self.error is False otherwise there will be
# redundent messages
Expand Down
24 changes: 23 additions & 1 deletion spdx/parsers/rdfbuilders.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,28 @@ def add_review_comment(self, doc, comment):
raise OrderError('ReviewComment')


class Builder(DocBuilder, EntityBuilder, CreationInfoBuilder, PackageBuilder, FileBuilder, ReviewBuilder):
class AnnotationBuilder(tagvaluebuilders.AnnotationBuilder):

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

def add_annotation_comment(self, doc, comment):
"""Sets the annotation comment. Raises CardinalityError if
already set. OrderError if no annotator defined before.
"""
if len(doc.annotations) != 0:
if not self.annotation_comment_set:
self.annotation_comment_set = True
doc.annotations[-1].comment = comment
return True
else:
raise CardinalityError('AnnotationComment')
else:
raise OrderError('AnnotationComment')


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

def __init__(self):
super(Builder, self).__init__()
Expand All @@ -342,3 +363,4 @@ def reset(self):
self.reset_package()
self.reset_file_stat()
self.reset_reviews()
self.reset_annotations()
54 changes: 54 additions & 0 deletions spdx/parsers/tagvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
'CREATOR_VALUE_TYPE': 'Invalid Reviewer value must be a Person, Organization or Tool. Line: {0}',
'REVIEW_DATE_VALUE_TYPE': 'ReviewDate value must be date in ISO 8601 format, line: {0}',
'REVIEW_COMMENT_VALUE_TYPE': 'ReviewComment value must be free form text between <text></text> tags, line:{0}',
'ANNOTATOR_VALUE_TYPE': 'Invalid Annotator value must be a Person, Organization or Tool. Line: {0}',
'ANNOTATION_DATE_VALUE_TYPE': 'AnnotationDate value must be date in ISO 8601 format, line: {0}',
'ANNOTATION_COMMENT_VALUE_TYPE': 'AnnotationComment value must be free form text between <text></text> tags, line:{0}',
'A_BEFORE_B': '{0} Can not appear before {1}, line: {2}',
'PACKAGE_NAME_VALUE': 'PackageName must be single line of text, line: {0}',
'PKG_VERSION_VALUE': 'PackageVersion must be single line of text, line: {0}',
Expand Down Expand Up @@ -114,6 +117,9 @@ def p_attrib(self, p):
| reviewer
| review_date
| review_comment
| annotator
| annotation_date
| annotation_comment
| package_name
| package_version
| pkg_down_location
Expand Down Expand Up @@ -1038,6 +1044,54 @@ def p_review_comment_2(self, p):
msg = ERROR_MESSAGES['REVIEW_COMMENT_VALUE_TYPE'].format(p.lineno(1))
self.logger.log(msg)

def p_annotator_1(self, p):
"""annotator : ANNOTATOR entity"""
self.builder.add_annotator(self.document, p[2])

def p_annotator_2(self, p):
"""annotator : ANNOTATOR error"""
self.error = True
msg = ERROR_MESSAGES['ANNOTATOR_VALUE_TYPE'].format(p.lineno(1))
self.logger.log(msg)

def p_annotation_date_1(self, p):
"""annotation_date : ANNOTATION_DATE DATE"""
try:
if six.PY2:
value = p[2].decode(encoding='utf-8')
else:
value = p[2]
self.builder.add_annotation_date(self.document, value)
except CardinalityError:
self.more_than_one_error('AnnotationDate', p.lineno(1))
except OrderError:
self.order_error('AnnotationDate', 'Annotator', p.lineno(1))

def p_annotation_date_2(self, p):
"""annotation_date : ANNOTATION_DATE error"""
self.error = True
msg = ERROR_MESSAGES['ANNOTATION_DATE_VALUE_TYPE'].format(p.lineno(1))
self.logger.log(msg)

def p_annotation_comment_1(self, p):
"""annotation_comment : ANNOTATION_COMMENT TEXT"""
try:
if six.PY2:
value = p[2].decode(encoding='utf-8')
else:
value = p[2]
self.builder.add_annotation_comment(self.document, value)
except CardinalityError:
self.more_than_one_error('AnnotationComment', p.lineno(1))
except OrderError:
self.order_error('AnnotationComment', 'Annotator', p.lineno(1))

def p_annotation_comment_2(self, p):
"""annotation_comment : ANNOTATION_COMMENT error"""
self.error = True
msg = ERROR_MESSAGES['ANNOTATION_COMMENT_VALUE_TYPE'].format(p.lineno(1))
self.logger.log(msg)

def p_lics_list_ver_1(self, p):
"""locs_list_ver : LIC_LIST_VER LINE"""
try:
Expand Down
Loading

0 comments on commit 8cb3582

Please sign in to comment.