aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Vilcans <martin@librador.com>2011-08-23 00:09:12 +0200
committerMartin Vilcans <martin@librador.com>2011-08-23 00:11:51 +0200
commit760e0929449808db8fec420798e0d031b168f79d (patch)
tree8a9c602697fbee1e1e067632d5f85406b614ce15
parent96be3fc202e8094646f184cafb34b9c5b4437b16 (diff)
downloadscreenplain-760e0929449808db8fec420798e0d031b168f79d.tar.gz
Simplified dual dialog design.
Forgot to add tests earlier. Here they are too!
-rw-r--r--screenplain/parse.py49
-rw-r--r--tests/parse_test.py111
2 files changed, 133 insertions, 27 deletions
diff --git a/screenplain/parse.py b/screenplain/parse.py
index 3f19662..336b4eb 100644
--- a/screenplain/parse.py
+++ b/screenplain/parse.py
@@ -42,36 +42,17 @@ class Dialog(object):
def __init__(self, lines):
self.character = lines[0]
- self.secondary_character = None
- self.dual = False
self.blocks = [] # list of tuples of (is_parenthetical, text)
- self.secondary_blocks = [] # for the second speaker if dual
self._parse(lines[1:])
def _parse(self, lines):
- append = self.blocks.append
inside_parenthesis = False
- it = iter(lines)
- while True:
- try:
- line = it.next()
- except StopIteration:
- return
- if line == '||' and not self.dual:
- self.dual = True
- append = self.secondary_blocks.append
- try:
- self.secondary_character = it.next()
- # what if this doesn't look like a character name?
- except StopIteration:
- append(line) # keep trailing '||'
- return
- else:
- if line.startswith('('):
- inside_parenthesis = True
- append((inside_parenthesis, line))
- if line.endswith(')'):
- inside_parenthesis = False
+ for line in lines:
+ if line.startswith('('):
+ inside_parenthesis = True
+ self.blocks.append((inside_parenthesis, line))
+ if line.endswith(')'):
+ inside_parenthesis = False
def format(self):
yield self.indent_character + self.character
@@ -94,6 +75,11 @@ class Dialog(object):
for line in lines:
yield line
+class DualDialog(object):
+ def __init__(self, left_lines, right_lines):
+ self.left = Dialog(left_lines)
+ self.right = Dialog(right_lines)
+
class Action(object):
indent = ''
fill = 68
@@ -127,14 +113,23 @@ def is_slug(blanks_before, string):
upper = string.upper()
return any(upper.startswith(s) for s in slug_prefixes)
+def _create_dialog(line_list):
+ try:
+ dual_index = line_list.index('||')
+ except ValueError:
+ return Dialog(line_list)
+ else:
+ return DualDialog(line_list[:dual_index], line_list[dual_index + 1:])
+
def create_paragraph(blanks_before, line_list):
if is_slug(blanks_before, line_list[0]):
return Slug(line_list)
if (
len(line_list) > 1 and
line_list[0].isupper() and
- not line_list[0].endswith(TWOSPACE)):
- return Dialog(line_list)
+ not line_list[0].endswith(TWOSPACE)
+ ):
+ return _create_dialog(line_list)
elif len(line_list) == 1 and line_list[0].endswith(':'):
# Not according to spec, see my comment
#http://prolost.com/blog/2011/8/9/screenplay-markdown.html?lastPage=true#comment14865294
diff --git a/tests/parse_test.py b/tests/parse_test.py
new file mode 100644
index 0000000..75d762a
--- /dev/null
+++ b/tests/parse_test.py
@@ -0,0 +1,111 @@
+import unittest2
+from screenplain import parse
+from screenplain.parse import Slug, Action, Dialog, DualDialog, Transition
+
+class ParseTests(unittest2.TestCase):
+
+ # Without this, the @skip decorator gives
+ # AttributeError: 'ParseTests' object has no attribute '__name__'
+ __name__ = 'ParseTests'
+
+ # A Scene Heading, or "slugline," is any line that has a blank
+ # line following it, and either begins with INT or EXT, or has
+ # two empty lines preceding it. A Scene Heading always has at
+ # least one blank line preceding it.
+ # NOTE: Actually the list used in Appendix 1
+ def test_slug_with_prefix(self):
+ paras = list(parse.parse([
+ 'INT. SOMEWHERE - DAY',
+ '',
+ 'THIS IS JUST ACTION',
+ ]))
+ self.assertEquals([Slug, Action], [type(p) for p in paras])
+
+ def test_action_is_not_a_slug(self):
+ paras = list(parse.parse([
+ '',
+ 'THIS IS JUST ACTION',
+ ]))
+ self.assertEquals([Action], [type(p) for p in paras])
+
+ def test_two_lines_creates_a_slug(self):
+ types = [type(p) for p in parse.parse([
+ '',
+ '',
+ 'This is a slug',
+ '',
+ ])]
+ self.assertEquals([Slug], types)
+
+ # A Character element is any line entirely in caps, with one empty
+ # line before it and without an empty line after it.
+ def test_all_caps_is_character(self):
+ paras = [p for p in parse.parse([
+ 'SOME GUY',
+ 'Hello',
+ ])]
+ self.assertEquals(1, len(paras))
+ dialog = paras[0]
+ self.assertEquals(Dialog, type(dialog))
+ self.assertEquals('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.
+ def test_nonalpha_character(self):
+ paras = list(parse.parse([
+ '23',
+ 'Hello',
+ ]))
+ self.assertEquals([Action], [type(p) for p in paras])
+
+ # See
+ # http://prolost.com/storage/downloads/spmd/SPMD_proposal.html#section-br
+ def test_twospaced_line_is_not_character(self):
+ paras = list(parse.parse([
+ 'SCANNING THE AISLES... ',
+ 'Where is that pit boss?',
+ ]))
+ self.assertEquals([Action], [type(p) for p in paras])
+
+ def test_simple_parenthetical(self):
+ paras = list(parse.parse([
+ 'STEEL',
+ '(starting the engine)',
+ 'So much for retirement!',
+ ]))
+ 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])
+
+ def test_dual_dialog(self):
+ paras = list(parse.parse([
+ 'BRICK',
+ 'Fuck retirement.',
+ '||',
+ 'STEEL',
+ 'Fuck retirement!',
+ ]))
+ 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)
+
+ def test_standard_transition(self):
+ # Need clarification of the spec here, see
+ # http://prolost.com/blog/2011/8/9/screenplay-markdown.html?lastPage=true#comment14865294
+
+ paras = list(parse.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])
+
+if __name__ == '__main__':
+ unittest2.main()