diff options
author | Martin Vilcans <martin@librador.com> | 2011-11-10 23:33:02 +0100 |
---|---|---|
committer | Martin Vilcans <martin@librador.com> | 2011-11-10 23:53:34 +0100 |
commit | 083800d933041f7a35cb19011d68d1e51c7f8b9f (patch) | |
tree | d2c81877c8c31c1b985ddf9e1ff5ff16a31ee998 | |
parent | a7f8938b10877ff207688b3ff02f62c55e6c1266 (diff) | |
download | screenplain-083800d933041f7a35cb19011d68d1e51c7f8b9f.tar.gz |
Made RichString non-hierarchical (simpler).
Also: FDX output should have correct line breaks now.
-rw-r--r-- | screenplain/export/fdx.py | 61 | ||||
-rw-r--r-- | screenplain/richstring.py | 281 | ||||
-rw-r--r-- | tests/fdx_test.py | 43 | ||||
-rw-r--r-- | tests/richstring_test.py | 96 | ||||
-rw-r--r-- | tests/spmd_test.py | 63 |
5 files changed, 338 insertions, 206 deletions
diff --git a/screenplain/export/fdx.py b/screenplain/export/fdx.py index 618d91c..937963d 100644 --- a/screenplain/export/fdx.py +++ b/screenplain/export/fdx.py @@ -1,7 +1,8 @@ from xml.sax.saxutils import escape from screenplain.types import * -from screenplain.richstring import RichString, Bold, Italic, Underline +from screenplain.richstring import RichString +from screenplain.richstring import Bold, Italic, Underline paragraph_types = { Slug: 'Scene Heading', @@ -13,38 +14,34 @@ style_names = { Bold: 'Bold', Italic: 'Italic', Underline: 'Underline', - RichString: None, } def _write_text_element(out, styles, text): style_value = '+'.join(str(s) for s in styles) if style_value == '': - out.write('<Text>%s</Text>' % (escape(text))) + out.write(' <Text>%s</Text>\n' % (escape(text))) else: - out.write('<Text style="%s">%s</Text>' % (style_value, escape(text))) + out.write(' <Text style="%s">%s</Text>\n' % + (style_value, escape(text)) + ) -def write_text(out, rich, parent_styles=None): - parent_styles = parent_styles or set() - style_name = style_names[type(rich)] - if style_name: - styles = parent_styles | set((style_name,)) - else: - styles = parent_styles - - for segment in rich.segments: - if isinstance(segment, basestring): - _write_text_element(out, styles, segment) +def write_text(out, rich, trailing_linebreak): + """Writes <Text style="..."> elements.""" + for seg_no, segment in enumerate(rich.segments): + fdx_styles = set(style_names[n] for n in segment.styles) + if trailing_linebreak and seg_no == len(rich.segments) - 1: + _write_text_element(out, fdx_styles, segment.text + '\n') else: - write_text(out, segment, styles) - + _write_text_element(out, fdx_styles, segment.text) def write_paragraph(out, para_type, lines): - out.write('<Paragraph Type="%s">' % para_type) - for line in lines: - write_text(out, line) - out.write('</Paragraph>') + out.write(' <Paragraph Type="%s">\n' % para_type) + last_line_no = len(lines) - 1 + for line_no, line in enumerate(lines): + write_text(out, line, line_no != last_line_no) + out.write(' </Paragraph>\n') def write_dialog(out, dialog): @@ -57,18 +54,25 @@ def write_dialog(out, dialog): def write_dual_dialog(out, dual): - out.write('<Paragraph><DualDialogue>') + out.write( + ' <Paragraph>\n' + ' <DualDialogue>\n' + ) write_dialog(out, dual.left) write_dialog(out, dual.right) - out.write('</DualDialogue></Paragraph>') + out.write( + ' </DualDialogue>\n' + ' </Paragraph>\n' + ) def to_fdx(screenplay, out): out.write( - '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' - '<FinalDraft DocumentType="Script" Template="No" Version="1">' - '<Content>' + '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n' + '<FinalDraft DocumentType="Script" Template="No" Version="1">\n' + '\n' + ' <Content>\n' ) for para in screenplay: @@ -79,4 +83,7 @@ def to_fdx(screenplay, out): else: para_type = paragraph_types[type(para)] write_paragraph(out, para_type, para.lines) - out.write('</Content></FinalDraft>\n') + out.write( + ' </Content>\n' + '</FinalDraft>\n' + ) diff --git a/screenplain/richstring.py b/screenplain/richstring.py index 54b5794..ee8332a 100644 --- a/screenplain/richstring.py +++ b/screenplain/richstring.py @@ -1,122 +1,174 @@ import re import cgi -_emphasis = re.compile( - r'(?:' - r'(\*\*)' # two stars - r'(?=\S)' # must not be followed by space - r'(.+?[*_]*)' # inside text - r'(?<=\S)\*\*' # finishing with two stars - r'|' - r'(\*)' # one star - r'(?=\S)' # must not be followed by space - r'(.+)' # inside text - r'(?<=\S)\*' # finishing with one star - r'(?!\*)' # must not be followed by star - r'|' - r'(_)' # underline - r'(?=\S)' # must not be followed by space - r'([^_]+)' # inside text - r'(?<=\S)_' # finishing with underline - r')' -) +_magic_re = re.compile(u'[\ue700-\ue705]') class RichString(object): - def __init__(self, *segments): - self.segments = segments + """A sequence of segments where each segment can have its own style.""" - def to_html(self): - result = '' - for segment in self.segments: - if isinstance(segment, basestring): - result += cgi.escape(segment) - else: - result += segment.to_html() - return result - - def to_plain(self): - result = '' - for segment in self.segments: - if isinstance(segment, basestring): - result += segment - else: - result += segment.to_plain() - return result - - def startswith(self, prefix): - return self.to_plain().startswith(prefix) - - def endswith(self, prefix): - return self.to_plain().endswith(prefix) - - def __unicode__(self): - return self.to_html() + def __init__(self, *segments): + self.segments = tuple(segments) def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - ', '.join(repr(s) for s in self.segments) - ) + return ' + '.join(repr(s) for s in self.segments) + + def startswith(self, string): + """Checks if the first segment in this string starts with a + specific string. + + """ + if '' == string: + return True + if not self.segments: + return False + return self.segments[0].text.startswith(string) + + def endswith(self, string): + """Checks if the last segment in this string starts with a + specific string. + + """ + if '' == string: + return True + if not self.segments: + return False + return self.segments[-1].text.endswith(string) + + def to_html(self): + return ''.join(seg.to_html() for seg in self.segments) def __eq__(self, other): - if isinstance(other, basestring): - return self.to_plain() == other return ( - self.__class__ == other.__class__ and + type(self) == type(other) and self.segments == other.segments ) def __ne__(self, other): return ( - self.__class__ != other.__class__ or + type(self) != type(other) or self.segments != other.segments ) + def __add__(self, other): + if hasattr(other, 'segments'): + return RichString(*(self.segments + other.segments)) + else: + raise ValueError('Concatenating requires RichString') -class Italic(RichString): - def to_html(self): - return '<em>' + super(Italic, self).to_html() + '</em>' +class Segment(object): + """A piece of a rich string. Has a set of styles.""" + def __init__(self, text, styles): + """ + Creates a segment with a set of styles. + text is the raw text string, and + styles is a set of Style subclasses""" + self.styles = set(styles) + self.text = text -class Bold(RichString): - def to_html(self): - return '<strong>' + super(Bold, self).to_html() + '</strong>' + def __repr__(self): + return '(%s)(%r)' % ( + '+'.join( + style.name() for style in self.styles + ) or 'plain', + self.text + ) + def __eq__(self, other): + return self.text == other.text and self.styles == other.styles + + def __ne__(self, other): + return self.text != other.text or self.styles != other.styles -class Underline(RichString): def to_html(self): - return '<u>' + super(Underline, self).to_html() + '</u>' + ordered_styles = list(self.styles) + return ( + ''.join(style.start_html for style in ordered_styles) + + cgi.escape(self.text) + + ''.join(style.end_html for style in reversed(ordered_styles)) + ) -def _parse(source): - segments = [] +class Style(object): + """Abstract base class for styles""" - scanner = _emphasis.scanner(source) - pos = 0 - while pos != len(source): - match = scanner.search() - if not match: - segments.append(source[pos:]) - break - if match.start() != pos: - segments.append(source[pos:match.start()]) - - ( - two_stars, two_stars_text, - one_star, one_star_text, - underline, underline_text - ) = match.groups() - - if two_stars: - segments.append(Bold(*_parse(two_stars_text))) - elif one_star: - segments.append(Italic(*_parse(one_star_text))) - else: - segments.append(Underline(*_parse(underline_text))) - pos = match.end() + start_magic = '' + end_magic = '' + start_html = '' + end_html = '' + + @classmethod + def name(cls): + return cls.__name__.lower() + + +class Italic(Style): - return segments + parse_re = re.compile( + r'\*' # one star + r'(?=\S)' # must not be followed by space + r'(.+)' # inside text + r'(?<=\S)\*' # finishing with one star + r'(?!\*)' # must not be followed by star + ) + + start_magic = u'\ue700' + end_magic = u'\ue701' + + start_html = '<em>' + end_html = '</em>' + + +class Bold(Style): + + parse_re = re.compile( + r'\*\*' # two stars + r'(?=\S)' # must not be followed by space + r'(.+?[*_]*)' # inside text + r'(?<=\S)\*\*' # finishing with two stars + ) + + start_magic = u'\ue702' + end_magic = u'\ue703' + + start_html = '<strong>' + end_html = '</strong>' + + +class Underline(Style): + + parse_re = re.compile( + r'_' # underline + r'(?=\S)' # must not be followed by space + r'([^_]+)' # inside text + r'(?<=\S)_' # finishing with underline + ) + + start_magic = u'\ue704' + end_magic = u'\ue705' + + start_html = '<u>' # TODO: use an actual html5 tag + end_html = '</u>' + + +class _CreateStyledString(object): + """Function object that creates a RichString object + with a single segment with a specified style. + """ + def __init__(self, styles): + self.styles = styles + + def __call__(self, text): + return RichString(Segment(text, self.styles)) + + def __add__(self, other): + return _CreateStyledString(self.styles.union(other.styles)) + +plain = _CreateStyledString(set()) +bold = _CreateStyledString(set((Bold,))) +italic = _CreateStyledString(set((Italic,))) +underline = _CreateStyledString((Underline,)) def parse_emphasis(source): @@ -124,12 +176,49 @@ def parse_emphasis(source): and returns a RichString object. >>> parse_emphasis(u'**hello**') - Bold(u'hello') + (bold)(u'hello') >>> parse_emphasis(u'plain') - RichString(u'plain') + (plain)(u'plain') + >>> parse_emphasis(u'**hello** there') + (bold)(u'hello') + (plain)(u' there') """ - segments = _parse(source) - if len(segments) == 1 and isinstance(segments[0], RichString): - return segments[0] - else: - return RichString(*segments) + source = Bold.parse_re.sub( + Bold.start_magic + r'\1' + Bold.end_magic, source + ) + source = Italic.parse_re.sub( + Italic.start_magic + r'\1' + Italic.end_magic, source + ) + source = Underline.parse_re.sub( + Underline.start_magic + r'\1' + Underline.end_magic, source + ) + + styles = set() + segments = [] + pos = 0 + + def append(pos, end): + if(pos == end): + return + text = source[pos:end] + segments.append(Segment(text, styles)) + + for match in _magic_re.finditer(source): + end = match.start() + append(pos, end) + pos = end + 1 + magic = match.group(0) + if magic == Bold.start_magic: + styles.add(Bold) + elif magic == Bold.end_magic: + styles.remove(Bold) + elif magic == Italic.start_magic: + styles.add(Italic) + elif magic == Italic.end_magic: + styles.remove(Italic) + elif magic == Underline.start_magic: + styles.add(Underline) + elif magic == Underline.end_magic: + styles.remove(Underline) + append(pos, len(source)) + + return RichString(*segments) diff --git a/tests/fdx_test.py b/tests/fdx_test.py index 153a0cf..d97782f 100644 --- a/tests/fdx_test.py +++ b/tests/fdx_test.py @@ -2,8 +2,7 @@ import unittest2 from StringIO import StringIO from screenplain.export.fdx import write_text - -from screenplain.richstring import RichString, Bold, Italic +from screenplain.richstring import plain, bold, italic class OutputTests(unittest2.TestCase): @@ -12,35 +11,45 @@ class OutputTests(unittest2.TestCase): self.out = StringIO() def test_plain_text_should_have_no_style(self): - write_text(self.out, RichString('hello')) + write_text(self.out, plain('hello'), False) self.assertEqual( self.out.getvalue(), - '<Text>hello</Text>' + ' <Text>hello</Text>\n' ) def test_bold_text_should_have_bold_style(self): - write_text(self.out, Bold('hello')) + write_text(self.out, bold('hello'), False) self.assertEqual( self.out.getvalue(), - '<Text style="Bold">hello</Text>' + ' <Text style="Bold">hello</Text>\n' ) def test_sequential_styles(self): - rich = RichString('plain', Bold('b'), Italic('i')) - write_text(self.out, rich) + rich = plain('plain') + bold('b') + italic('i') + write_text(self.out, rich, False) + self.assertEqual( + self.out.getvalue(), + ' <Text>plain</Text>\n' + ' <Text style="Bold">b</Text>\n' + ' <Text style="Italic">i</Text>\n' + ) + + def test_several_styles(self): + rich = bold('outer') + (bold + italic)('inner') + bold('outer') + write_text(self.out, rich, False) self.assertEqual( self.out.getvalue(), - '<Text>plain</Text>' - '<Text style="Bold">b</Text>' - '<Text style="Italic">i</Text>' + ' <Text style="Bold">outer</Text>\n' + ' <Text style="Bold+Italic">inner</Text>\n' + ' <Text style="Bold">outer</Text>\n' ) - def test_nested_styles(self): - rich = Bold('outer', Italic('inner'), 'outer') - write_text(self.out, rich) + def test_write_text_adds_line_break_if_requested(self): + rich = bold('outer') + (bold + italic)('inner') + bold('outer') + write_text(self.out, rich, True) self.assertEqual( self.out.getvalue(), - '<Text style="Bold">outer</Text>' - '<Text style="Bold+Italic">inner</Text>' - '<Text style="Bold">outer</Text>' + ' <Text style="Bold">outer</Text>\n' + ' <Text style="Bold+Italic">inner</Text>\n' + ' <Text style="Bold">outer\n</Text>\n' # note newline ) diff --git a/tests/richstring_test.py b/tests/richstring_test.py index b442f90..c79981d 100644 --- a/tests/richstring_test.py +++ b/tests/richstring_test.py @@ -1,128 +1,154 @@ import unittest2 -from screenplain.richstring import RichString, Bold, Italic, Underline +from screenplain.richstring import ( + RichString, Segment, + Bold, Italic, + plain, bold, italic, underline +) from screenplain.richstring import parse_emphasis from screenplain.types import Slug, Action, Dialog, DualDialog, Transition +class LowLevelRichStringTests(unittest2.TestCase): + + def test_plain_string_has_one_single_segment(self): + s = RichString(plain('hello')) + self.assertEqual((plain('hello'),), s.segments) + + class RichStringOperatorTests(unittest2.TestCase): def test_repr(self): - s = RichString(Bold('Hello'), ' there ', Bold('folks')) + s = bold('Hello') + plain(' there ') + bold('folks') self.assertEquals( - "RichString(Bold('Hello'), ' there ', Bold('folks'))", + "(bold)('Hello') + (plain)(' there ') + (bold)('folks')", repr(s) ) def test_eq(self): - self.assertEquals(Bold('Hello'), Bold('Hello')) - self.assertNotEquals(Bold('Hello'), Bold('Foo')) - self.assertNotEquals('Hello', Bold('Hello')) + self.assertEquals(bold('Hello'), bold('Hello')) + self.assertNotEquals(bold('Hello'), bold('Foo')) + self.assertNotEquals(plain('Hello'), bold('Hello')) + + def test_ne(self): + self.assertFalse(bold('Hello') != bold('Hello')) + + def test_concatenating_two_richstrings(self): + expected = RichString(Segment('hello', ()), Segment(' there', (Bold,))) + s1 = plain('hello') + s2 = bold(' there') + result = s1 + s2 + self.assertEquals(expected, result) + + +class StyleGeneratorTests(unittest2.TestCase): + + def test_bold_function_creates_bold_richstring(self): self.assertEquals( - Bold('a', Italic('b'), 'c'), - Bold('a', Italic('b'), 'c') - ) - self.assertNotEquals( - Bold('a', Italic('b'), 'c'), - Bold('a', Italic('b'), 'd') + RichString(Segment('a', (Bold,))), + bold('a') ) - def test_ne(self): - self.assertFalse(Bold('Hello') != Bold('Hello')) + def test_adding_functions(self): + self.assertEquals( + RichString(Segment('a', (Bold, Italic))), + (bold + italic)('a') + ) class RichStringTests(unittest2.TestCase): def test_plain_to_html(self): - self.assertEquals('hello', RichString('hello').to_html()) + self.assertEquals('hello', RichString(plain('hello')).to_html()) def test_to_html(self): s = RichString( - Bold('bold'), - ' normal ', - Italic('italic'), - Underline('wonderline') + bold('bold'), + plain(' normal '), + italic('italic'), + underline('wonderline') ) self.assertEquals( '<strong>bold</strong> normal <em>italic</em><u>wonderline</u>', s.to_html() ) - class ParseEmphasisTests(unittest2.TestCase): def test_parse_without_emphasis(self): - self.assertEquals(RichString('Hello'), parse_emphasis('Hello'), - 'Expected parse_emphasis to return a string') + self.assertEquals(plain('Hello'), parse_emphasis('Hello'), + 'Expected parse_emphasis to return a plain string') def test_parse_bold(self): self.assertEquals( parse_emphasis('**Hello**'), - Bold('Hello') + bold('Hello') ) def test_parse_pre_and_postfix_and_bold(self): self.assertEquals( parse_emphasis('pre**Hello**post'), - RichString('pre', Bold('Hello'), 'post'), + plain('pre') + bold('Hello') + plain('post') ) def test_parse_multiple_bold(self): self.assertEquals( parse_emphasis('x**Hello** **there**'), - RichString('x', Bold('Hello'), ' ', Bold('there')) + plain('x') + bold('Hello') + plain(' ') + bold('there') ) def test_parse_adjacent_bold(self): self.assertEquals( - parse_emphasis('**123****456**'), - RichString(Bold('123**'), '456**') + parse_emphasis('**123**456**'), + bold('123') + plain('456**') ) def test_italic(self): self.assertEquals( parse_emphasis('*Italian style*'), - Italic('Italian style') + italic('Italian style') ) def test_bold_inside_italic(self): self.assertEquals( parse_emphasis('*Swedish **style** rules*'), - Italic('Swedish ', Bold('style'), ' rules') + italic('Swedish ') + (bold + italic)('style') + italic(' rules') ) def test_italic_inside_bold(self): self.assertEquals( parse_emphasis('**Swedish *style* rules**'), - Bold('Swedish ', Italic('style'), ' rules') + bold('Swedish ') + (bold + italic)('style') + bold(' rules') ) def test_italic_and_bold(self): self.assertEquals( parse_emphasis('***really strong***'), - Bold(Italic('really strong')) + (bold + italic)('really strong') ) @unittest2.expectedFailure def test_additional_star(self): self.assertEquals( parse_emphasis('*foo* bar* baz'), - RichString(Italic('foo'), ' bar* baz') + italic('foo') + plain(' bar* baz') ) def test_underline(self): self.assertEquals( parse_emphasis('_hello_'), - Underline('hello') + underline('hello') ) def test_bold_inside_underline(self): self.assertEquals( parse_emphasis('_**hello**_'), - Underline(Bold('hello')) + (bold + underline)('hello') ) def test_overlapping_underscore_and_italic(self): + # It's unclear what result to expect in this case. + # This is one way of interpreting it self.assertEquals( parse_emphasis('_*he_llo*'), - RichString(Underline('*he'), 'llo*') + (italic + underline)('he') + italic('llo') ) diff --git a/tests/spmd_test.py b/tests/spmd_test.py index 1f5c0f5..fbc7867 100644 --- a/tests/spmd_test.py +++ b/tests/spmd_test.py @@ -1,7 +1,7 @@ import unittest2 from screenplain.parsers.spmd import parse from screenplain.types import Slug, Action, Dialog, DualDialog, Transition - +from screenplain.richstring import plain class ParseTests(unittest2.TestCase): @@ -28,8 +28,8 @@ class ParseTests(unittest2.TestCase): self.assertEquals([Dialog, Action], [type(p) for p in paras]) # What looks like a scene headingis parsed as a character name. # Unexpected perhaps, but that's how I interpreted the spec. - self.assertEquals('INT. SOMEWHERE - DAY', paras[0].character) - self.assertEquals(['Some action'], paras[1].lines) + self.assertEquals(plain('INT. SOMEWHERE - DAY'), paras[0].character) + self.assertEquals([plain('Some action')], paras[1].lines) def test_action_is_not_a_slug(self): paras = list(parse([ @@ -57,7 +57,7 @@ class ParseTests(unittest2.TestCase): self.assertEquals(1, len(paras)) dialog = paras[0] self.assertEquals(Dialog, type(dialog)) - self.assertEquals('SOME GUY', dialog.character) + self.assertEquals(plain('SOME GUY'), dialog.character) # SPMD would not be able to support a character named "23". We # might need a syntax to force a character element. @@ -86,8 +86,14 @@ class ParseTests(unittest2.TestCase): self.assertEquals(1, len(paras)) dialog = paras[0] self.assertEqual(2, len(dialog.blocks)) - self.assertEqual((True, '(starting the engine)'), dialog.blocks[0]) - self.assertEqual((False, 'So much for retirement!'), dialog.blocks[1]) + self.assertEqual( + (True, plain('(starting the engine)')), + dialog.blocks[0] + ) + self.assertEqual( + (False, plain('So much for retirement!')), + dialog.blocks[1] + ) def test_dual_dialog(self): paras = list(parse([ @@ -99,10 +105,16 @@ class ParseTests(unittest2.TestCase): ])) self.assertEquals([DualDialog], [type(p) for p in paras]) dual = paras[0] - self.assertEquals('BRICK', dual.left.character) - self.assertEquals([(False, 'Fuck retirement.')], dual.left.blocks) - self.assertEquals('STEEL', dual.right.character) - self.assertEquals([(False, 'Fuck retirement!')], dual.right.blocks) + self.assertEquals(plain('BRICK'), dual.left.character) + self.assertEquals( + [(False, plain('Fuck retirement.'))], + dual.left.blocks + ) + self.assertEquals(plain('STEEL'), dual.right.character) + self.assertEquals( + [(False, plain('Fuck retirement!'))], + dual.right.blocks + ) def test_dual_dialog_with_empty_right_dialog_is_ordinary_dialog(self): paras = list(parse([ @@ -112,10 +124,10 @@ class ParseTests(unittest2.TestCase): ])) self.assertEquals([Dialog], [type(p) for p in paras]) dialog = paras[0] - self.assertEqual('BRICK', dialog.character) + self.assertEqual(plain('BRICK'), dialog.character) self.assertEqual([ - (False, 'Nice retirement.'), - (False, '||') + (False, plain('Nice retirement.')), + (False, plain('||')) ], dialog.blocks) def test_standard_transition(self): @@ -129,17 +141,6 @@ class ParseTests(unittest2.TestCase): ])) self.assertEquals([Action, Transition, Slug], [type(p) for p in paras]) - def test_standard_transition(self): - - paras = list(parse([ - 'Jack begins to argue vociferously in Vietnamese (?)', - '', - 'CUT TO:', - '', - "EXT. BRICK'S POOL - DAY", - ])) - self.assertEquals([Action, Transition, Slug], [type(p) for p in paras]) - def test_transition_needs_to_be_upper_case(self): paras = list(parse([ 'Jack begins to argue vociferously in Vietnamese (?)', @@ -185,9 +186,9 @@ class ParseTests(unittest2.TestCase): self.assertEquals([Action, Action, Action], [type(p) for p in paras]) self.assertEquals( [ - "And then there's a long beat.", - "Longer than is funny.", - "Long enough to be depressing.", + plain("And then there's a long beat."), + plain("Longer than is funny."), + plain("Long enough to be depressing."), ], paras[1].lines ) @@ -201,10 +202,10 @@ class ParseTests(unittest2.TestCase): ])) self.assertEquals([Dialog], [type(p) for p in paras]) self.assertEquals([ - (False, 'O Romeo, Romeo! wherefore art thou Romeo?'), - (False, 'Deny thy father and refuse thy name;'), - (False, 'Or, if thou wilt not, be but sworn my love,'), - (False, "And I'll no longer be a Capulet."), + (False, plain('O Romeo, Romeo! wherefore art thou Romeo?')), + (False, plain('Deny thy father and refuse thy name;')), + (False, plain('Or, if thou wilt not, be but sworn my love,')), + (False, plain("And I'll no longer be a Capulet.")), ], paras[0].blocks) if __name__ == '__main__': |