[PATCH 09/12] copyright: Parse the rest of the paragraphs.
John Wright
jsw at debian.org
Sun Aug 31 21:26:15 UTC 2014
From: John Wright <jsw at google.com>
This change adds accessor methods to Copyright for the rest of the
paragraphs, and the ability to dump a parsed Copyright object back to
its original text format.
---
lib/debian/copyright.py | 81 +++++++++++++++++++++++++++++++++++++++++++++--
tests/test_copyright.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 162 insertions(+), 3 deletions(-)
diff --git a/lib/debian/copyright.py b/lib/debian/copyright.py
index 7e82e0b..70e1542 100644
--- a/lib/debian/copyright.py
+++ b/lib/debian/copyright.py
@@ -72,12 +72,24 @@ class Copyright(object):
"""
super(Copyright, self).__init__()
+ self.__paragraphs = []
+
if sequence is not None:
paragraphs = list(deb822.Deb822.iter_paragraphs(
sequence=sequence, encoding=encoding))
- if len(paragraphs) > 0:
- self.__header = Header(paragraphs[0])
- # TODO(jsw): Parse the rest of the paragraphs.
+ if not paragraphs:
+ raise NotMachineReadableError('no paragraphs in input')
+ self.__header = Header(paragraphs[0])
+ for i in range(1, len(paragraphs)):
+ p = paragraphs[i]
+ if 'Files' in p:
+ p = FilesParagraph(p)
+ elif 'License' in p:
+ p = LicenseParagraph(p)
+ else:
+ warnings.warn('Non-header paragraph has neither "Files"'
+ ' nor "License" fields')
+ self.__paragraphs.append(p)
else:
self.__header = Header()
@@ -92,6 +104,69 @@ class Copyright(object):
raise TypeError('value must be a Header object')
self.__header = hdr
+ def all_files_paragraphs(self):
+ """Returns an iterator over the contained FilesParagraph objects."""
+ return (p for p in self.__paragraphs if isinstance(p, FilesParagraph))
+
+ def find_files_paragraph(self, filename):
+ """Returns the FilesParagraph for the given filename.
+
+ In accordance with the spec, this method returns the last FilesParagraph
+ that matches the filename. If no paragraphs matched, returns None.
+ """
+ result = None
+ for p in self.all_files_paragraphs():
+ if p.matches(filename):
+ result = p
+ return result
+
+ def add_files_paragraph(self, paragraph):
+ """Adds a FilesParagraph to this object.
+
+ The paragraph is inserted directly after the last FilesParagraph (which
+ might be before a standalone LicenseParagraph).
+ """
+ if not isinstance(paragraph, FilesParagraph):
+ raise TypeError('paragraph must be a FilesParagraph instance')
+
+ last_i = -1
+ for i, p in enumerate(self.__paragraphs):
+ if isinstance(p, FilesParagraph):
+ last_i = i
+ self.__paragraphs.insert(last_i + 1, paragraph)
+
+ def all_license_paragraphs(self):
+ """Returns an iterator over standalone LicenseParagraph objects."""
+ return (p for p in self.__paragraphs if isinstance(p, LicenseParagraph))
+
+ def add_license_paragraph(self, paragraph):
+ """Adds a LicenceParagraph to this object.
+
+ The paragraph is inserted after any other paragraphs.
+ """
+ if not isinstance(paragraph, LicenseParagraph):
+ raise TypeError('paragraph must be a LicenseParagraph instance')
+ self.__paragraphs.append(paragraph)
+
+ def dump(self, f=None):
+ """Dumps the contents of the copyright file.
+
+ If f is None, returns a unicode object. Otherwise, writes the contents
+ to f, which must be a file-like object that is opened in text mode
+ (i.e. that accepts unicode objects directly). It is thus up to the
+ caller to arrange for the file to do any appropriate encoding.
+ """
+ return_string = False
+ if f is None:
+ return_string = True
+ f = io.StringIO()
+ self.header.dump(f, text_mode=True)
+ for p in self.__paragraphs:
+ f.write('\n')
+ p.dump(f, text_mode=True)
+ if return_string:
+ return f.getvalue()
+
def _single_line(s):
"""Returns s if it is a single line; otherwise raises ValueError."""
diff --git a/tests/test_copyright.py b/tests/test_copyright.py
index 29c9f10..ed9cdda 100755
--- a/tests/test_copyright.py
+++ b/tests/test_copyright.py
@@ -86,6 +86,35 @@ On Debian systems, the full text of the GNU General Public
License version 2 can be found in the file
`/usr/share/common-licenses/GPL-2'."""
+MULTI_LICENSE = """\
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: Project Y
+
+Files: *
+Copyright: Copyright 2000 Company A
+License: ABC
+
+Files: src/baz.*
+Copyright: Copyright 2000 Company A
+ Copyright 2001 Company B
+License: ABC
+
+License: ABC
+ [ABC TEXT]
+
+Files: debian/*
+Copyright: Copyright 2003 Debian Developer <someone at debian.org>
+License: 123
+
+Files: debian/rules
+Copyright: Copyright 2003 Debian Developer <someone at debian.org>
+ Copyright 2004 Someone Else <foo at bar.com>
+License: 123
+
+License: 123
+ [123 TEXT]
+"""
+
FORMAT = 'http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/'
@@ -231,6 +260,61 @@ class CopyrightTest(unittest.TestCase):
self.assertEqual('ftp://ftp.example.com/pub/games', c.header['Source'])
self.assertIsNone(c.header.license)
+ def test_parse_and_dump(self):
+ c = copyright.Copyright(sequence=SIMPLE.splitlines())
+ dumped = c.dump()
+ self.assertEqual(SIMPLE, dumped)
+
+ def test_all_files_paragraphs(self):
+ c = copyright.Copyright(sequence=SIMPLE.splitlines())
+ self.assertEqual(
+ [('*',), ('debian/*',)],
+ [fp.files for fp in c.all_files_paragraphs()])
+
+ c = copyright.Copyright()
+ self.assertEqual([], list(c.all_files_paragraphs()))
+
+ def test_find_files_paragraph(self):
+ c = copyright.Copyright(sequence=SIMPLE.splitlines())
+ paragraphs = list(c.all_files_paragraphs())
+
+ self.assertIs(paragraphs[0], c.find_files_paragraph('Makefile'))
+ self.assertIs(paragraphs[0], c.find_files_paragraph('src/foo.cc'))
+ self.assertIs(paragraphs[1], c.find_files_paragraph('debian/rules'))
+ self.assertIs(paragraphs[1], c.find_files_paragraph('debian/a/b.py'))
+
+ def test_find_files_paragraph_some_unmatched(self):
+ c = copyright.Copyright()
+ files1 = copyright.FilesParagraph.create(
+ ['foo/*'], 'CompanyA', copyright.License('ISC'))
+ files2 = copyright.FilesParagraph.create(
+ ['bar/*'], 'CompanyB', copyright.License('Apache'))
+ c.add_files_paragraph(files1)
+ c.add_files_paragraph(files2)
+ self.assertIs(files1, c.find_files_paragraph('foo/bar.cc'))
+ self.assertIs(files2, c.find_files_paragraph('bar/baz.cc'))
+ self.assertIsNone(c.find_files_paragraph('baz/quux.cc'))
+ self.assertIsNone(c.find_files_paragraph('Makefile'))
+
+ def test_all_license_paragraphs(self):
+ c = copyright.Copyright(sequence=SIMPLE.splitlines())
+ self.assertEqual([], list(c.all_license_paragraphs()))
+
+ c = copyright.Copyright(MULTI_LICENSE.splitlines())
+ self.assertEqual(
+ [copyright.License('ABC', '[ABC TEXT]'),
+ copyright.License('123', '[123 TEXT]')],
+ list(p.license for p in c.all_license_paragraphs()))
+
+ c.add_license_paragraph(copyright.LicenseParagraph.create(
+ copyright.License('Foo', '[FOO TEXT]')))
+ self.assertEqual(
+ [copyright.License('ABC', '[ABC TEXT]'),
+ copyright.License('123', '[123 TEXT]'),
+ copyright.License('Foo', '[FOO TEXT]')],
+ list(p.license for p in c.all_license_paragraphs()))
+
+
class MultlineTest(unittest.TestCase):
"""Test cases for format_multiline{,_lines} and parse_multline{,_as_lines}.
--
2.1.0
More information about the pkg-python-debian-maint
mailing list