Merge lp:~adiroiban/pocket-lint/986239 into lp:pocket-lint
- 986239
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Curtis Hovey | ||||
Approved revision: | 469 | ||||
Merged at revision: | 468 | ||||
Proposed branch: | lp:~adiroiban/pocket-lint/986239 | ||||
Merge into: | lp:pocket-lint | ||||
Diff against target: |
868 lines (+790/-2) 4 files modified
pocketlint/formatcheck.py (+245/-0) pocketlint/tests/__init__.py (+1/-0) pocketlint/tests/test_restructuredtext.py (+524/-0) test.py (+20/-2) |
||||
To merge this branch: | bzr merge lp:~adiroiban/pocket-lint/986239 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey | code | Approve | |
Review via email:
|
Commit message
Description of the change
Hi,
This is my initial rst checker.
It contains a few changes outside of the rst part.
1. For the test runner I added the option to select the test based on regex. In this was I was able to run only a subset of test modules.
2. For the general CheckerTestCase, i set self.reporter.
Please let me know if you want me to revert these changes.
----
Now for the RST part.
For the Language class, I wanted to sort the language type identifier objects, mimetypes.add_type and mime_type_language in alphabetic order... but I didn't want to add to much noise to the diff.
I know that it is hard to force a certain coding convention and the current rules are made base on the style used in the Python documentation.
It does not checks for rst compilation errors, but rather for coding conventions.
For not it only checks for sections styles and an empty line at the end of file.
Please let me know if you thinks this is on a right direction and what should be changed.
Thanks!
Adi
Preview Diff
1 | === modified file 'pocketlint/formatcheck.py' |
2 | --- pocketlint/formatcheck.py 2012-04-20 16:52:00 +0000 |
3 | +++ pocketlint/formatcheck.py 2012-04-21 17:17:20 +0000 |
4 | @@ -178,6 +178,7 @@ |
5 | DOCBOOK = object() |
6 | LOG = object() |
7 | SQL = object() |
8 | + RESTRUCTUREDTEXT = object() |
9 | |
10 | XML_LIKE = (XML, XSLT, HTML, ZPT, ZCML, DOCBOOK) |
11 | |
12 | @@ -186,6 +187,7 @@ |
13 | mimetypes.add_type('text/x-python-doctest', '.doctest') |
14 | mimetypes.add_type('text/x-twisted-application', '.tac') |
15 | mimetypes.add_type('text/x-log', '.log') |
16 | + mimetypes.add_type('text/x-rst', '.rst') |
17 | mime_type_language = { |
18 | 'text/x-python': PYTHON, |
19 | 'text/x-twisted-application': PYTHON, |
20 | @@ -195,6 +197,7 @@ |
21 | 'text/plain': TEXT, |
22 | 'text/x-sql': SQL, |
23 | 'text/x-log': LOG, |
24 | + 'text/x-rst': RESTRUCTUREDTEXT, |
25 | 'application/javascript': JAVASCRIPT, |
26 | 'application/xml': XML, |
27 | 'application/x-sh': SH, |
28 | @@ -648,6 +651,248 @@ |
29 | self.check_tab(line_no, line) |
30 | |
31 | |
32 | +class ReStructuredTextChecker(BaseChecker, AnyTextMixin): |
33 | + """Check reStructuredText ource code.""" |
34 | + |
35 | + # Taken from rst documentation. |
36 | + delimiter_characters = [ |
37 | + '=', '-', '`', ':', '\'', '"', '~', '^', '_', '*', '+', '#', '<', '>', |
38 | + ] |
39 | + |
40 | + def __init__(self, file_path, text, reporter=None): |
41 | + super(ReStructuredTextChecker, self).__init__( |
42 | + file_path, text, reporter=reporter) |
43 | + self.lines = self.text.splitlines() |
44 | + |
45 | + def check(self): |
46 | + """Check the syntax of the reStructuredText code.""" |
47 | + self.check_lines() |
48 | + self.check_empty_last_line() |
49 | + |
50 | + def check_lines(self): |
51 | + """Call each line checker for each line in text.""" |
52 | + for line_no, line in enumerate(self.lines): |
53 | + line_no += 1 |
54 | + self.check_length(line_no, line) |
55 | + self.check_trailing_whitespace(line_no, line) |
56 | + self.check_tab(line_no, line) |
57 | + self.check_conflicts(line_no, line) |
58 | + |
59 | + if self.isTransition(line_no - 1): |
60 | + self.check_transition(line_no - 1) |
61 | + elif self.isSectionDelimiter(line_no - 1): |
62 | + self.check_section_delimiter(line_no - 1) |
63 | + else: |
64 | + pass |
65 | + |
66 | + def isTransition(self, line_number): |
67 | + '''Return True if the current line is a line transition.''' |
68 | + line = self.lines[line_number] |
69 | + if len(line) < 4: |
70 | + return False |
71 | + |
72 | + if len(self.lines) < 3: |
73 | + return False |
74 | + |
75 | + succesive_characters = ( |
76 | + line[0] == line[1] == line[2] == line[3] and |
77 | + line[0] in self.delimiter_characters) |
78 | + |
79 | + if not succesive_characters: |
80 | + return False |
81 | + |
82 | + emply_lines_bounded = ( |
83 | + self.lines[line_number - 1] == '' and |
84 | + self.lines[line_number + 1] == '') |
85 | + |
86 | + if not emply_lines_bounded: |
87 | + return False |
88 | + |
89 | + return True |
90 | + |
91 | + def check_transition(self, line_number): |
92 | + '''Transitions should be delimited by a single emtpy line.''' |
93 | + if (self.lines[line_number - 2] == '' or |
94 | + self.lines[line_number + 2] == ''): |
95 | + self.message( |
96 | + line_number + 1, |
97 | + 'Transition markers should be bounded by single empty lines.', |
98 | + icon='info', |
99 | + ) |
100 | + |
101 | + def isSectionDelimiter(self, line_number): |
102 | + '''Return true if the line is a section delimiter.''' |
103 | + if len(self.lines) < 3: |
104 | + return False |
105 | + |
106 | + if line_number >= len(self.lines): |
107 | + return False |
108 | + |
109 | + line = self.lines[line_number] |
110 | + if len(line) < 3: |
111 | + return False |
112 | + |
113 | + if (line[0] == line[1] == line[2] and line[0] in |
114 | + self.delimiter_characters): |
115 | + if ' ' in line: |
116 | + # We have a table header. |
117 | + return False |
118 | + else: |
119 | + return True |
120 | + |
121 | + return False |
122 | + |
123 | + def check_section_delimiter(self, line_number): |
124 | + """Checks for section delimiter. |
125 | + |
126 | + These checkes are designed for sections delimited by top and bottom |
127 | + markers. |
128 | + |
129 | + ======= <- top marker |
130 | + Section <- text_line |
131 | + ======= <- bottom marker |
132 | + |
133 | + If the section is delimted only by bottom marker, the section text |
134 | + is considered the top marker. |
135 | + |
136 | + Section <- top marker, text_line |
137 | + ======= <- bottom marker |
138 | + |
139 | + If the section has a custom anchor name: |
140 | + |
141 | + .. _link <- top marker |
142 | + |
143 | + ======= |
144 | + Section <- text_line |
145 | + ======= <- bottom marker |
146 | + |
147 | + or: |
148 | + |
149 | + .. _link <- top marker |
150 | + |
151 | + Section <- text_line |
152 | + ======= <- bottom marker |
153 | + |
154 | + If we have top and bottom markers, the check will be called twice ( |
155 | + for each marker). In this case we will skip the tests for bottom |
156 | + marker. |
157 | + """ |
158 | + human_line_number = line_number + 1 |
159 | + current_line = self.lines[line_number] |
160 | + |
161 | + # Skip test if we have both top and bottom markers and we are |
162 | + # at the bottom marker. |
163 | + if (line_number > 1 and current_line == self.lines[line_number - 2]): |
164 | + return |
165 | + |
166 | + if ((line_number + 2) < len(self.lines) and |
167 | + current_line == self.lines[line_number + 2]): |
168 | + # We have both top and bottom markers and we are currently at |
169 | + # the top marker. |
170 | + top_marker = line_number |
171 | + text_line = line_number + 1 |
172 | + bottom_marker = line_number + 2 |
173 | + else: |
174 | + # We only have bottom marker, and are at the bottom marker. |
175 | + top_marker = line_number - 1 |
176 | + text_line = line_number - 1 |
177 | + bottom_marker = line_number |
178 | + |
179 | + # In case we have a custom anchor, the top_marker is replaced by |
180 | + # the custom anchor. |
181 | + if self._sectionHasCustomAnchor(top_marker): |
182 | + top_marker = top_marker - 2 |
183 | + |
184 | + # Check underline length for bottom marker, |
185 | + # since top marker can be the same as text line. |
186 | + if len(self.lines[bottom_marker]) != len(self.lines[text_line]): |
187 | + self.message( |
188 | + human_line_number, |
189 | + 'Section marker has wrong length.', |
190 | + icon='error', |
191 | + ) |
192 | + |
193 | + if not self._haveGoodSpacingBeforeSection(top_marker): |
194 | + self.message( |
195 | + human_line_number, |
196 | + 'Section should be divided by 2 empty lines.', |
197 | + icon='info', |
198 | + ) |
199 | + |
200 | + if not self._haveGoodSpacingAfterSection(bottom_marker): |
201 | + self.message( |
202 | + human_line_number, |
203 | + 'Section title should be followed by 1 empty line.', |
204 | + icon='info', |
205 | + ) |
206 | + |
207 | + def _sectionHasCustomAnchor(self, top_marker): |
208 | + if (top_marker - 2) < 0: |
209 | + return False |
210 | + |
211 | + if self.lines[top_marker - 2].startswith('.. _'): |
212 | + return True |
213 | + |
214 | + return False |
215 | + |
216 | + def _haveGoodSpacingBeforeSection(self, top_marker): |
217 | + '''Return True if we have good spacing before the section.''' |
218 | + if top_marker > 0: |
219 | + if self.lines[top_marker - 1] != '': |
220 | + return False |
221 | + |
222 | + # If we are on the second line, there is no space for 2 empty lines |
223 | + # before. |
224 | + if top_marker == 1: |
225 | + return False |
226 | + |
227 | + if top_marker > 1: |
228 | + if self.lines[top_marker - 2] != '': |
229 | + return False |
230 | + |
231 | + if top_marker > 2: |
232 | + if self.lines[top_marker - 3] == '': |
233 | + return False |
234 | + |
235 | + return True |
236 | + |
237 | + def _haveGoodSpacingAfterSection(self, bottom_marker): |
238 | + '''Return True if we have good spacing after the section.''' |
239 | + lines_count = len(self.lines) |
240 | + |
241 | + if bottom_marker < lines_count - 1: |
242 | + if self.lines[bottom_marker + 1] != '': |
243 | + return False |
244 | + |
245 | + if bottom_marker < lines_count - 2: |
246 | + if self.lines[bottom_marker + 2] == '': |
247 | + # If the section is followed by 2 empty spaces and then |
248 | + # followed by a section delimiter, the section delimiter |
249 | + # rules will take priority |
250 | + if self.isSectionDelimiter(bottom_marker + 3): |
251 | + return True |
252 | + if self.isSectionDelimiter(bottom_marker + 4): |
253 | + return True |
254 | + return False |
255 | + |
256 | + return True |
257 | + |
258 | + def check_empty_last_line(self): |
259 | + """Chech the files ends with an emtpy line and not with double empty |
260 | + line. |
261 | + |
262 | + This will avoid merge conflicts. |
263 | + """ |
264 | + if len(self.lines) < 2: |
265 | + return |
266 | + if self.text[-1] != '\n' or self.text[-2:] == '\n\n': |
267 | + self.message( |
268 | + len(self.lines), |
269 | + 'File does not ends with an empty line.', |
270 | + icon='info', |
271 | + ) |
272 | + |
273 | + |
274 | def get_option_parser(): |
275 | """Return the option parser for this program.""" |
276 | usage = "usage: %prog [options] file1 file2" |
277 | |
278 | === modified file 'pocketlint/tests/__init__.py' |
279 | --- pocketlint/tests/__init__.py 2012-01-29 21:24:20 +0000 |
280 | +++ pocketlint/tests/__init__.py 2012-04-21 17:17:20 +0000 |
281 | @@ -11,3 +11,4 @@ |
282 | |
283 | def setUp(self): |
284 | self.reporter = Reporter(Reporter.COLLECTOR) |
285 | + self.reporter.call_count = 0 |
286 | |
287 | === added file 'pocketlint/tests/test_restructuredtext.py' |
288 | --- pocketlint/tests/test_restructuredtext.py 1970-01-01 00:00:00 +0000 |
289 | +++ pocketlint/tests/test_restructuredtext.py 2012-04-21 17:17:20 +0000 |
290 | @@ -0,0 +1,524 @@ |
291 | +'''Tests for ReStructuredTextChecker.''' |
292 | +from pocketlint.formatcheck import ReStructuredTextChecker |
293 | +from pocketlint.tests import CheckerTestCase |
294 | + |
295 | +# This is a valid rst content. |
296 | +# This comment is here so that the content starts at line 11 |
297 | +# and make it easier to identify errors in tests. |
298 | +# Just add 10 to the reported line number. |
299 | +# |
300 | +valid_rst_content = '''\ |
301 | +============= |
302 | +First section |
303 | +============= |
304 | + |
305 | +Text *for* first **section**. |
306 | + |
307 | + |
308 | +-------------------- |
309 | +Second emtpy section |
310 | +-------------------- |
311 | + |
312 | + |
313 | +Third section |
314 | +^^^^^^^^^^^^^ |
315 | + |
316 | +Paragrhap for |
317 | +third section `with link<http://my.home>`_. |
318 | + |
319 | +:: |
320 | + |
321 | + Literal block1. |
322 | + |
323 | + Literal block paragraph. |
324 | + |
325 | + |
326 | +| Line blocks are useful for addresses, |
327 | +| verse, and adornment-free lists. |
328 | + |
329 | + |
330 | +.. _section-permalink: |
331 | + |
332 | +Another Section with predefined link |
333 | +------------------------------------ |
334 | + |
335 | +>>> print "This is a doctest block." |
336 | +... with a line continuation |
337 | +This is a doctest block. |
338 | + |
339 | +A grid table. |
340 | + |
341 | ++------------+------------+-----------+ |
342 | +| Header 1 | Header 2 | Header 3 | |
343 | ++============+============+===========+ |
344 | +| body row 1 | column 2 | column 3 | |
345 | ++------------+------------+-----------+ |
346 | +| body row 2 | Cells may span columns.| |
347 | ++------------+------------+-----------+ |
348 | +| body row 3 | Cells may | - Cells | |
349 | ++------------+ span rows. | - contain | |
350 | +| body row 4 | | - blocks. | |
351 | ++------------+------------+-----------+ |
352 | + |
353 | +A simple table. |
354 | + |
355 | +===== ===== ====== |
356 | + Inputs Output |
357 | +------------ ------ |
358 | + A B A or B |
359 | +===== ===== ====== |
360 | +False False False |
361 | +True False True |
362 | +False True True |
363 | +True True True |
364 | +===== ===== ====== |
365 | + |
366 | +A transition marker is a horizontal line |
367 | +of 4 or more repeated punctuation |
368 | +characters. |
369 | + |
370 | +------------ |
371 | + |
372 | +A transition should not begin or end a |
373 | +section or document, nor should two |
374 | +transitions be immediately adjacent. |
375 | + |
376 | +Footnote references, like [5]_. |
377 | +Note that footnotes may get |
378 | +rearranged, e.g., to the bottom of |
379 | +the "page". |
380 | + |
381 | +.. [5] A numerical footnote. Note |
382 | + there's no colon after the ``]``. |
383 | + |
384 | +External hyperlinks, like Python_. |
385 | +.. _Python: http://www.python.org/ |
386 | + |
387 | +For instance: |
388 | + |
389 | +.. image:: images/ball1.gif |
390 | + |
391 | +The |biohazard| symbol must be used on containers used to dispose of |
392 | +medical waste. |
393 | + |
394 | +.. |biohazard| image:: biohazard.png |
395 | + |
396 | +.. This text will not be shown |
397 | + (but, for instance, in HTML might be |
398 | + rendered as an HTML comment) |
399 | +''' |
400 | +# The last line from multi line string is a bit hard to visualize, |
401 | +# but it is there. |
402 | + |
403 | + |
404 | +class TestReStructuredTextChecker(CheckerTestCase): |
405 | + """Verify reStructuredTextChecker checking.""" |
406 | + |
407 | + def test_empty_file(self): |
408 | + self.reporter.call_count = 0 |
409 | + content = ('') |
410 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
411 | + checker.check() |
412 | + self.assertEqual([], self.reporter.messages) |
413 | + self.assertEqual(0, self.reporter.call_count) |
414 | + |
415 | + def test_valid_content(self): |
416 | + self.reporter.call_count = 0 |
417 | + checker = ReStructuredTextChecker( |
418 | + 'bogus', valid_rst_content, self.reporter) |
419 | + checker.check() |
420 | + self.assertEqual([], self.reporter.messages) |
421 | + self.assertEqual(0, self.reporter.call_count) |
422 | + |
423 | + def test_no_empty_last_line(self): |
424 | + self.reporter.call_count = 0 |
425 | + content = ( |
426 | + 'Some first line\n' |
427 | + 'the second and last line witout newline' |
428 | + ) |
429 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
430 | + checker.check_empty_last_line() |
431 | + expected = [( |
432 | + 2, 'File does not ends with an empty line.')] |
433 | + self.assertEqual(expected, self.reporter.messages) |
434 | + self.assertEqual(1, self.reporter.call_count) |
435 | + |
436 | + def test_multiple_empty_last_lines(self): |
437 | + self.reporter.call_count = 0 |
438 | + content = ( |
439 | + 'Some first line\n' |
440 | + 'the second and last\n' |
441 | + '\n' |
442 | + ) |
443 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
444 | + checker.check_empty_last_line() |
445 | + expected = [( |
446 | + 3, 'File does not ends with an empty line.')] |
447 | + self.assertEqual(expected, self.reporter.messages) |
448 | + self.assertEqual(1, self.reporter.call_count) |
449 | + |
450 | + def test_isTransition_good(self): |
451 | + content = ( |
452 | + '\n' |
453 | + '----\n' |
454 | + '\n' |
455 | + ) |
456 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
457 | + result = checker.isTransition(1) |
458 | + self.assertTrue(result) |
459 | + |
460 | + def test_isTransition_short_line(self): |
461 | + content = ( |
462 | + '\n' |
463 | + '---\n' |
464 | + '\n' |
465 | + ) |
466 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
467 | + result = checker.isTransition(1) |
468 | + self.assertFalse(result) |
469 | + |
470 | + def test_isTransition_short_file(self): |
471 | + content = ( |
472 | + '\n' |
473 | + '----\n' |
474 | + '' |
475 | + ) |
476 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
477 | + result = checker.isTransition(1) |
478 | + self.assertFalse(result) |
479 | + |
480 | + def test_isTransition_false(self): |
481 | + content = ( |
482 | + '\n' |
483 | + '----\n' |
484 | + 'some\n' |
485 | + ) |
486 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
487 | + result = checker.isTransition(1) |
488 | + self.assertFalse(result) |
489 | + |
490 | + content = ( |
491 | + 'some\n' |
492 | + '----\n' |
493 | + '\n' |
494 | + ) |
495 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
496 | + result = checker.isTransition(1) |
497 | + self.assertFalse(result) |
498 | + |
499 | + def test_check_transitions_good(self): |
500 | + content = ( |
501 | + 'some text\n' |
502 | + '\n' |
503 | + '----\n' |
504 | + '\n' |
505 | + 'some text\n' |
506 | + ) |
507 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
508 | + checker.check_transition(2) |
509 | + self.assertEqual([], self.reporter.messages) |
510 | + self.assertEqual(0, self.reporter.call_count) |
511 | + |
512 | + def test_check_transitions_bad_spacing_before(self): |
513 | + content = ( |
514 | + 'some text\n' |
515 | + '\n' |
516 | + '\n' |
517 | + '----\n' |
518 | + '\n' |
519 | + 'some text\n' |
520 | + ) |
521 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
522 | + checker.check_transition(3) |
523 | + expect = [( |
524 | + 4, 'Transition markers should be bounded by single empty lines.')] |
525 | + self.assertEqual(expect, self.reporter.messages) |
526 | + self.assertEqual(1, self.reporter.call_count) |
527 | + |
528 | + def test_check_transitions_bad_spacing_after(self): |
529 | + content = ( |
530 | + 'some text\n' |
531 | + '\n' |
532 | + '----\n' |
533 | + '\n' |
534 | + '\n' |
535 | + 'some text\n' |
536 | + ) |
537 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
538 | + checker.check_transition(2) |
539 | + expect = [( |
540 | + 3, 'Transition markers should be bounded by single empty lines.')] |
541 | + self.assertEqual(expect, self.reporter.messages) |
542 | + self.assertEqual(1, self.reporter.call_count) |
543 | + |
544 | + def test_check_transitions_bad_spacing_both(self): |
545 | + content = ( |
546 | + 'some text\n' |
547 | + '\n' |
548 | + '\n' |
549 | + '----\n' |
550 | + '\n' |
551 | + '\n' |
552 | + 'some text\n' |
553 | + ) |
554 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
555 | + checker.check_transition(3) |
556 | + expect = [( |
557 | + 4, 'Transition markers should be bounded by single empty lines.')] |
558 | + self.assertEqual(expect, self.reporter.messages) |
559 | + self.assertEqual(1, self.reporter.call_count) |
560 | + |
561 | + def test_isSectionDelimiter_short_file(self): |
562 | + content = ( |
563 | + 'Something' |
564 | + '---------\n' |
565 | + '' |
566 | + ) |
567 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
568 | + result = checker.isSectionDelimiter(1) |
569 | + self.assertFalse(result) |
570 | + |
571 | + def test_isSectionDelimiter_short_line(self): |
572 | + content = ( |
573 | + 'Som' |
574 | + '---\n' |
575 | + '' |
576 | + ) |
577 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
578 | + result = checker.isSectionDelimiter(1) |
579 | + self.assertFalse(result) |
580 | + |
581 | + def test_isSectionDelimiter_table(self): |
582 | + content = ( |
583 | + '---- ----' |
584 | + 'Row1 Row1' |
585 | + '---- ----\n' |
586 | + '' |
587 | + ) |
588 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
589 | + result = checker.isSectionDelimiter(0) |
590 | + self.assertFalse(result) |
591 | + result = checker.isSectionDelimiter(2) |
592 | + self.assertFalse(result) |
593 | + |
594 | + def test_isSectionDelimiter_good(self): |
595 | + content = ( |
596 | + 'Section\n' |
597 | + '-------\n' |
598 | + 'some text' |
599 | + ) |
600 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
601 | + result = checker.isSectionDelimiter(1) |
602 | + self.assertTrue(result) |
603 | + |
604 | + def test_isSectionDelimiter_good_bounded_start_of_file(self): |
605 | + content = ( |
606 | + '=======\n' |
607 | + 'Section\n' |
608 | + '=======\n' |
609 | + ) |
610 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
611 | + result = checker.isSectionDelimiter(0) |
612 | + self.assertTrue(result) |
613 | + result = checker.isSectionDelimiter(2) |
614 | + self.assertTrue(result) |
615 | + |
616 | + def test_check_section_delimiter_bounded(self): |
617 | + content = ( |
618 | + 'some text\n' |
619 | + '\n' |
620 | + '\n' |
621 | + '=======\n' |
622 | + 'Section\n' |
623 | + '=======\n' |
624 | + '\n' |
625 | + 'some text\n' |
626 | + ) |
627 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
628 | + checker.check_section_delimiter(3) |
629 | + checker.check_section_delimiter(5) |
630 | + self.assertEqual([], self.reporter.messages) |
631 | + self.assertEqual(0, self.reporter.call_count) |
632 | + |
633 | + def test_check_section_delimiter_bad_marker_length(self): |
634 | + content = ( |
635 | + 'Section\n' |
636 | + '------\n' |
637 | + '\n' |
638 | + 'some text\n' |
639 | + 'other text\n' |
640 | + ) |
641 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
642 | + checker.check_section_delimiter(1) |
643 | + expect = [(2, 'Section marker has wrong length.')] |
644 | + self.assertEqual(expect, self.reporter.messages) |
645 | + self.assertEqual(1, self.reporter.call_count) |
646 | + |
647 | + def test_check_section_delimiter_bad_length_both_markers(self): |
648 | + content = ( |
649 | + '---------\n' |
650 | + 'Section\n' |
651 | + '---------\n' |
652 | + '\n' |
653 | + 'some text\n' |
654 | + 'other text\n' |
655 | + ) |
656 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
657 | + checker.check_section_delimiter(0) |
658 | + checker.check_section_delimiter(2) |
659 | + expect = [(1, 'Section marker has wrong length.')] |
660 | + self.assertEqual(expect, self.reporter.messages) |
661 | + self.assertEqual(1, self.reporter.call_count) |
662 | + |
663 | + def test_check_section_before_space_good_start_both(self): |
664 | + content = ( |
665 | + '-------\n' |
666 | + 'Section\n' |
667 | + '-------\n' |
668 | + '\n' |
669 | + ) |
670 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
671 | + checker.check_section_delimiter(0) |
672 | + checker.check_section_delimiter(2) |
673 | + self.assertEqual([], self.reporter.messages) |
674 | + self.assertEqual(0, self.reporter.call_count) |
675 | + |
676 | + def test_check_section_before_space_good_start_bottom(self): |
677 | + content = ( |
678 | + 'Section\n' |
679 | + '-------\n' |
680 | + '\n' |
681 | + ) |
682 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
683 | + checker.check_section_delimiter(1) |
684 | + self.assertEqual([], self.reporter.messages) |
685 | + self.assertEqual(0, self.reporter.call_count) |
686 | + |
687 | + def test_check_section_before_space_bad_only_one_line_near_start(self): |
688 | + content = ( |
689 | + '\n' |
690 | + 'Section\n' |
691 | + '-------\n' |
692 | + '\n' |
693 | + ) |
694 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
695 | + checker.check_section_delimiter(2) |
696 | + expect = [(3, 'Section should be divided by 2 empty lines.')] |
697 | + self.assertEqual(expect, self.reporter.messages) |
698 | + self.assertEqual(1, self.reporter.call_count) |
699 | + |
700 | + def test_check_section_before_space_bad_only_one_line(self): |
701 | + content = ( |
702 | + 'end of previous section.\n' |
703 | + '\n' |
704 | + 'Section\n' |
705 | + '-------\n' |
706 | + '\n' |
707 | + ) |
708 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
709 | + checker.check_section_delimiter(3) |
710 | + expect = [(4, 'Section should be divided by 2 empty lines.')] |
711 | + self.assertEqual(expect, self.reporter.messages) |
712 | + self.assertEqual(1, self.reporter.call_count) |
713 | + |
714 | + def test_check_section_before_space_multiple_empty_lines(self): |
715 | + content = ( |
716 | + 'end of previous section.\n' |
717 | + '\n' |
718 | + '\n' |
719 | + '\n' |
720 | + 'Section\n' |
721 | + '-------\n' |
722 | + '\n' |
723 | + ) |
724 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
725 | + checker.check_section_delimiter(5) |
726 | + expect = [(6, 'Section should be divided by 2 empty lines.')] |
727 | + self.assertEqual(expect, self.reporter.messages) |
728 | + self.assertEqual(1, self.reporter.call_count) |
729 | + |
730 | + def test_check_section_after_space_last_line(self): |
731 | + content = ( |
732 | + 'Section\n' |
733 | + '-------\n' |
734 | + ) |
735 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
736 | + checker.check_section_delimiter(1) |
737 | + self.assertEqual([], self.reporter.messages) |
738 | + self.assertEqual(0, self.reporter.call_count) |
739 | + |
740 | + def test_check_section_after_space_bad(self): |
741 | + content = ( |
742 | + 'Section\n' |
743 | + '-------\n' |
744 | + 'Paragraph start.\n' |
745 | + ) |
746 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
747 | + checker.check_section_delimiter(1) |
748 | + expect = [(2, 'Section title should be followed by 1 empty line.')] |
749 | + self.assertEqual(expect, self.reporter.messages) |
750 | + self.assertEqual(1, self.reporter.call_count) |
751 | + |
752 | + def test_check_section_after_space_too_many_empty_lines(self): |
753 | + content = ( |
754 | + 'Section\n' |
755 | + '-------\n' |
756 | + '\n' |
757 | + '\n' |
758 | + 'Paragraph start.\n' |
759 | + ) |
760 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
761 | + checker.check_section_delimiter(1) |
762 | + expect = [(2, 'Section title should be followed by 1 empty line.')] |
763 | + self.assertEqual(expect, self.reporter.messages) |
764 | + self.assertEqual(1, self.reporter.call_count) |
765 | + |
766 | + def test_check_section_empty_section_next_section_only_bottom(self): |
767 | + content = ( |
768 | + 'Emtpy Section\n' |
769 | + '=============\n' |
770 | + '\n' |
771 | + '\n' |
772 | + 'Another Section\n' |
773 | + '---------------\n' |
774 | + ) |
775 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
776 | + checker.check_section_delimiter(1) |
777 | + self.assertEqual([], self.reporter.messages) |
778 | + self.assertEqual(0, self.reporter.call_count) |
779 | + |
780 | + def test_check_section_empty_section_next_section_both_markers(self): |
781 | + content = ( |
782 | + 'Emtpy Section\n' |
783 | + '=============\n' |
784 | + '\n' |
785 | + '\n' |
786 | + '---------------\n' |
787 | + 'Another Section\n' |
788 | + '---------------\n' |
789 | + ) |
790 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
791 | + checker.check_section_delimiter(1) |
792 | + self.assertEqual([], self.reporter.messages) |
793 | + self.assertEqual(0, self.reporter.call_count) |
794 | + |
795 | + def disable_check_section_delimiter_both_markers_not_sync(self): |
796 | + # When both top and bottom markers are used, and they don't have |
797 | + # the same size, they are interpreted as separate markers. |
798 | + content = ( |
799 | + '------\n' |
800 | + 'Section\n' |
801 | + '--------\n' |
802 | + '\n' |
803 | + 'some text\n' |
804 | + 'other text\n' |
805 | + ) |
806 | + checker = ReStructuredTextChecker('bogus', content, self.reporter) |
807 | + checker.check_lines() |
808 | + expect = [ |
809 | + (1, 'Section marker has wrong length.'), |
810 | + (1, 'Section title should be followed by 1 empty line.'), |
811 | + (3, 'Section marker has wrong length.'), |
812 | + ] |
813 | + self.assertEqual(expect, self.reporter.messages) |
814 | + self.assertEqual(3, self.reporter.call_count) |
815 | |
816 | === modified file 'test.py' |
817 | --- test.py 2012-01-29 21:24:20 +0000 |
818 | +++ test.py 2012-04-21 17:17:20 +0000 |
819 | @@ -6,6 +6,7 @@ |
820 | |
821 | import re |
822 | import os |
823 | +import sys |
824 | import unittest |
825 | try: |
826 | from unittest.runner import _WritelnDecorator |
827 | @@ -81,23 +82,40 @@ |
828 | self.stream.write(text) |
829 | |
830 | |
831 | -def find_tests(root_dir): |
832 | +def find_tests(root_dir, filter=None): |
833 | """Generate a list of matching test modules below a directory.""" |
834 | for path, subdirs, files in os.walk(root_dir): |
835 | subdirs[:] = [dir for dir in subdirs] |
836 | if path.endswith('tests'): |
837 | for file_ in files: |
838 | if file_.startswith('test_') and file_.endswith('.py'): |
839 | + if filter and not re.search(filter, file_): |
840 | + continue |
841 | file_path = os.path.join(path, file_) |
842 | test_module = file_path[2:-3].replace('/', '.') |
843 | yield test_module |
844 | |
845 | |
846 | +def show_help(): |
847 | + print '''\ |
848 | +python test.py [-h|--help] [test_module_regex_filter] |
849 | +''' |
850 | + |
851 | + |
852 | def main(): |
853 | + if len(sys.argv) > 1: |
854 | + if (sys.argv[1] in ['-h', '--help']): |
855 | + show_help() |
856 | + return |
857 | + else: |
858 | + filter = sys.argv[1] |
859 | + else: |
860 | + filter = None |
861 | + |
862 | unittest.runner._WritelnDecorator = XTermWritelnDecorator |
863 | test_loader = unittest.defaultTestLoader |
864 | suite = unittest.TestSuite() |
865 | - for test_module in find_tests('.'): |
866 | + for test_module in find_tests('.', filter=filter): |
867 | suite.addTest(test_loader.loadTestsFromName(test_module)) |
868 | unittest.TextTestRunner(verbosity=2).run(suite) |
869 |
Thank you very much.
I have merged this and pushed it to Launchpad. I think there are a few methods that return True or False that could be made smaller and more precise. I do not think they need changing just yet. I think it is best that this feature be merged and used so that we know exactly where we want to improve it.