Merge lp:~jml/pkgme/format-description into lp:pkgme

Proposed by Jonathan Lange
Status: Merged
Approved by: James Westby
Approved revision: 103
Merged at revision: 94
Proposed branch: lp:~jml/pkgme/format-description
Merge into: lp:pkgme
Diff against target: 195 lines (+164/-0)
2 files modified
pkgme/info_elements.py (+49/-0)
pkgme/tests/test_info_elements.py (+115/-0)
To merge this branch: bzr merge lp:~jml/pkgme/format-description
Reviewer Review Type Date Requested Status
James Westby Approve
Review via email: mp+86558@code.launchpad.net

Commit message

Auto-format description when it doesn't appear to be correctly formatted.

Description of the change

Gives Description powers of automatically formatting incoming descriptions so they fit into the Debian control file.

Implementation has been guided by http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Description, which I assume is the relevant standard.

In distinguishing between auto-formatted descriptions and ones that we leave alone, I felt it appropriate to add error checking for the ones we leave as-is. This means that callers of Description.clean() can rely on getting a proper description.

I'm raising user-friendly errors when the description is invalid, but I'm not 100% sure whether there would be a more appropriate way of raising them.

As an alternative strategy, when we find errors we could instead go back and treat the whole string as one that needs auto-formatting.

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote :

Hi,

I think this test shows undesirable behaviour:

162 + def test_non_indented_further_lines(self):

In the binary backend we're going to want to take the description from MyApps
and hand it over to this code.

If the developer enters a space at the start of the first line then it will
error, and there's nothing we can do except lstrip() the description.

I think that a better check for whether to act on it is whether there is
a space at the start of every line. You would still get in to trouble if
the developer put a space at the start of every line but left blank lines,
but that will be rarer.

I think this is what you meant by

  As an alternative strategy, when we find errors we could instead go back and treat the whole string as one that needs auto-formatting.

but I'm of course willing to be convinced otherwise.

Thanks,

James

review: Needs Fixing
lp:~jml/pkgme/format-description updated
102. By Jonathan Lange

Don't raise an error on unindented lines.

103. By Jonathan Lange

Just fix up wrong blank lines too.

Revision history for this message
Jonathan Lange (jml) wrote :

On Wed, Dec 21, 2011 at 4:23 PM, James Westby <email address hidden> wrote:
> Review: Needs Fixing
>
> Hi,
>
> I think this test shows undesirable behaviour:
>
> 162     + def test_non_indented_further_lines(self):
>
> In the binary backend we're going to want to take the description from MyApps
> and hand it over to this code.
>
> If the developer enters a space at the start of the first line then it will
> error, and there's nothing we can do except lstrip() the description.
>
> I think that a better check for whether to act on it is whether there is
> a space at the start of every line. You would still get in to trouble if
> the developer put a space at the start of every line but left blank lines,
> but that will be rarer.
>

I've changed this to not do a check at all, but to clean up the
description regardless. In this case, "clean" means to change any
blank lines to " ." and to indent any lines without indentation.

jml

Revision history for this message
James Westby (james-w) wrote :

Looks good to me, thanks for the changes.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'pkgme/info_elements.py'
--- pkgme/info_elements.py 2011-08-10 14:33:16 +0000
+++ pkgme/info_elements.py 2012-01-09 13:11:24 +0000
@@ -164,12 +164,61 @@
164 description = """The dependencies of the binary package."""164 description = """The dependencies of the binary package."""
165165
166166
167def chomp(text):
168 """Remove the trailing newline if present.
169
170 Named after the Perl built-in that does the same thing.
171 """
172 if text and text[-1] == '\n':
173 return text[:-1]
174 return text
175
176
167class Description(InfoElement):177class Description(InfoElement):
168178
169 name = "description"179 name = "description"
170 description = "The description of the package."180 description = "The description of the package."
171 default = "a package"181 default = "a package"
172182
183 @classmethod
184 def _format_lines(cls, lines):
185 first_line, lines = lines[0], lines[1:]
186 yield first_line
187 for i, line in enumerate(lines):
188 is_blank = bool(not line.strip())
189 if is_blank:
190 yield ' .'
191 elif line[0] == ' ':
192 yield line
193 else:
194 yield ' ' + line
195
196 @classmethod
197 def clean(cls, value):
198 """Prepare a string for the Description field in the control file.
199
200 If ``value`` looks to already be formatted according to Debian policy,
201 then we pass it through. If it does not, then we do our best to clean
202 it up.
203
204 We determine whether ``value`` is already Debian-formatted by checking
205 whether the second line is indented by a space. If so, then we assume
206 it's Debian-formatted. If later lines break Debian formatting, then
207 we raise a ValueError.
208
209 See http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Description
210 for the rules governing the Description field.
211
212 :param value: A string to go into the Description field of a control
213 file.
214 :raise ValueError: When the string initially looks to be Debian-
215 formatted but actually is not.
216 :return: A correctly-formatted version of that string.
217 """
218 if not value.strip():
219 return ''
220 return '\n'.join(cls._format_lines(value.strip().splitlines()))
221
173222
174class Version(InfoElement):223class Version(InfoElement):
175224
176225
=== modified file 'pkgme/tests/test_info_elements.py'
--- pkgme/tests/test_info_elements.py 2011-08-10 14:33:16 +0000
+++ pkgme/tests/test_info_elements.py 2012-01-09 13:11:24 +0000
@@ -4,6 +4,7 @@
44
5from pkgme.info_elements import (5from pkgme.info_elements import (
6 BuildDepends,6 BuildDepends,
7 Description,
7 Distribution,8 Distribution,
8 ExtraFiles,9 ExtraFiles,
9 InfoElement,10 InfoElement,
@@ -132,3 +133,117 @@
132 line.strip().split('=') for line in fp.readlines())133 line.strip().split('=') for line in fp.readlines())
133 self.assertEqual(134 self.assertEqual(
134 Distribution.get_default(), release_keys['DISTRIB_CODENAME'])135 Distribution.get_default(), release_keys['DISTRIB_CODENAME'])
136
137
138class DescriptionTestCase(TestCase):
139
140 # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Description
141
142 def test_single_line(self):
143 # If the description is just a single line with no line breaks, then
144 # that counts as the synopsis and no special formatting is needed.
145 description = "single line description"
146 cleaned = Description.clean(description)
147 self.assertEqual(description, cleaned)
148
149 def test_single_line_trailing_newline(self):
150 # If the single line ends with a newline then we remove that newline,
151 # as it's not needed for the Description in the template.
152 description = "single line description\n"
153 cleaned = Description.clean(description)
154 self.assertEqual(description.rstrip(), cleaned)
155
156 def test_indented_further_lines(self):
157 # If the incoming description is already in the format needed by the
158 # control file, then we do nothing to it.
159 description = """initial synopsis
160 follow-up line
161 preformatted text
162 more preformatted text
163 more information
164"""
165 cleaned = Description.clean(description)
166 self.assertEqual(description.rstrip(), cleaned)
167
168 def test_non_indented_further_lines(self):
169 # If the incoming description *looks* like it's in correct Debian
170 # format (i.e. has a leading space in the second line), then we assume
171 # that they don't know what they are doing and reformat the lines
172 # properly.
173 bad_description = """\
174initial synopsis
175 follow-up line
176more information
177"""
178 description = Description.clean(bad_description)
179 self.assertEqual("""\
180initial synopsis
181 follow-up line
182 more information""", description)
183
184 def test_bad_literal_description(self):
185 # If the incoming description *looks* like it's in correct Debian
186 # format (i.e. has a leading space in the second line), but then
187 # contains actual blank lines rather than lines with '.', then we
188 # raise an error.
189 bad_description = """initial synopsis
190 follow-up line
191
192 more information
193"""
194 description = Description.clean(bad_description)
195 self.assertEqual("""initial synopsis
196 follow-up line
197 .
198 more information""", description)
199
200 def test_multiple_lines(self):
201 # If the description appears to be out of the Debian format and spans
202 # multiple lines, then we indent those lines by a single space,
203 # treating them as paragraph text that will be word-wrapped.
204 description = """initial synopsis
205follow-up line
206more information
207"""
208 cleaned = Description.clean(description)
209 self.assertEqual("""initial synopsis
210 follow-up line
211 more information""", cleaned)
212
213 def test_blank_lines(self):
214 # If the description appears to be out of the Debian format and
215 # contains blank lines, we mark those blank lines with a single '.'.
216 description = """initial synopsis
217follow-up line
218
219more information
220
221that previous line has spaces in it, but needs to be marked
222up as a blank line all the same
223"""
224 cleaned = Description.clean(description)
225 self.assertEqual("""initial synopsis
226 follow-up line
227 .
228 more information
229 .
230 that previous line has spaces in it, but needs to be marked
231 up as a blank line all the same""", cleaned)
232
233 def test_empty_description(self):
234 # If the description is empty, then we return an empty description.
235 self.assertEqual('', Description.clean(''))
236 self.assertEqual('', Description.clean('\n'))
237 self.assertEqual('', Description.clean(' '))
238
239 def test_initial_blank_lines(self):
240 description = """
241
242initial synopsis
243follow-up line
244more information
245"""
246 cleaned = Description.clean(description)
247 self.assertEqual("""initial synopsis
248 follow-up line
249 more information""", cleaned)

Subscribers

People subscribed via source and target branches