From 5883c35785b03f1823fcbdea82a00b76aaa3f785 Mon Sep 17 00:00:00 2001 From: Martin Vilcans Date: Sun, 19 Feb 2012 22:06:21 +0100 Subject: Renamed SPMD to Fountain --- tests/files/scene-numbers.fountain | 7 + tests/files/scene-numbers.fountain.html | 4 + tests/files/scene-numbers.spmd | 7 - tests/files/scene-numbers.spmd.html | 4 - tests/files/sections.fountain | 13 + tests/files/sections.fountain.html | 7 + tests/files/sections.spmd | 13 - tests/files/sections.spmd.html | 7 - tests/files/simple.fountain | 9 + tests/files/simple.fountain.fdx | 24 ++ tests/files/simple.fountain.html | 4 + tests/files/simple.spmd | 9 - tests/files/simple.spmd.fdx | 24 -- tests/files/simple.spmd.html | 4 - tests/files_test.py | 16 +- tests/fountain_test.py | 435 +++++++++++++++++++++++++++++++ tests/spmd_test.py | 437 -------------------------------- 17 files changed, 511 insertions(+), 513 deletions(-) create mode 100644 tests/files/scene-numbers.fountain create mode 100644 tests/files/scene-numbers.fountain.html delete mode 100644 tests/files/scene-numbers.spmd delete mode 100644 tests/files/scene-numbers.spmd.html create mode 100644 tests/files/sections.fountain create mode 100644 tests/files/sections.fountain.html delete mode 100644 tests/files/sections.spmd delete mode 100644 tests/files/sections.spmd.html create mode 100644 tests/files/simple.fountain create mode 100644 tests/files/simple.fountain.fdx create mode 100644 tests/files/simple.fountain.html delete mode 100644 tests/files/simple.spmd delete mode 100644 tests/files/simple.spmd.fdx delete mode 100644 tests/files/simple.spmd.html create mode 100644 tests/fountain_test.py delete mode 100644 tests/spmd_test.py (limited to 'tests') diff --git a/tests/files/scene-numbers.fountain b/tests/files/scene-numbers.fountain new file mode 100644 index 0000000..51064d2 --- /dev/null +++ b/tests/files/scene-numbers.fountain @@ -0,0 +1,7 @@ +INT. SOMEWHERE - DAY #1# + +Bla bla + +INT. SOMEWHERE ELSE - NIGHT #2# + +Bla bla diff --git a/tests/files/scene-numbers.fountain.html b/tests/files/scene-numbers.fountain.html new file mode 100644 index 0000000..4b8d051 --- /dev/null +++ b/tests/files/scene-numbers.fountain.html @@ -0,0 +1,4 @@ +
1INT. SOMEWHERE - DAY1
+

Bla bla

+
2INT. SOMEWHERE ELSE - NIGHT2
+

Bla bla

diff --git a/tests/files/scene-numbers.spmd b/tests/files/scene-numbers.spmd deleted file mode 100644 index 51064d2..0000000 --- a/tests/files/scene-numbers.spmd +++ /dev/null @@ -1,7 +0,0 @@ -INT. SOMEWHERE - DAY #1# - -Bla bla - -INT. SOMEWHERE ELSE - NIGHT #2# - -Bla bla diff --git a/tests/files/scene-numbers.spmd.html b/tests/files/scene-numbers.spmd.html deleted file mode 100644 index 4b8d051..0000000 --- a/tests/files/scene-numbers.spmd.html +++ /dev/null @@ -1,4 +0,0 @@ -
1INT. SOMEWHERE - DAY1
-

Bla bla

-
2INT. SOMEWHERE ELSE - NIGHT2
-

Bla bla

diff --git a/tests/files/sections.fountain b/tests/files/sections.fountain new file mode 100644 index 0000000..96a2266 --- /dev/null +++ b/tests/files/sections.fountain @@ -0,0 +1,13 @@ +# First level + +## Second level + +EXT. STREET - DAWN + +DAWN walks down the street. + +## Second level again + +EXT. ALLEY - CONTINUOUS + +Dawn walks into this place. diff --git a/tests/files/sections.fountain.html b/tests/files/sections.fountain.html new file mode 100644 index 0000000..d06decb --- /dev/null +++ b/tests/files/sections.fountain.html @@ -0,0 +1,7 @@ +

First level

+

Second level

+
EXT. STREET - DAWN
+

DAWN walks down the street.

+

Second level again

+
EXT. ALLEY - CONTINUOUS
+

Dawn walks into this place.

diff --git a/tests/files/sections.spmd b/tests/files/sections.spmd deleted file mode 100644 index 96a2266..0000000 --- a/tests/files/sections.spmd +++ /dev/null @@ -1,13 +0,0 @@ -# First level - -## Second level - -EXT. STREET - DAWN - -DAWN walks down the street. - -## Second level again - -EXT. ALLEY - CONTINUOUS - -Dawn walks into this place. diff --git a/tests/files/sections.spmd.html b/tests/files/sections.spmd.html deleted file mode 100644 index d06decb..0000000 --- a/tests/files/sections.spmd.html +++ /dev/null @@ -1,7 +0,0 @@ -

First level

-

Second level

-
EXT. STREET - DAWN
-

DAWN walks down the street.

-

Second level again

-
EXT. ALLEY - CONTINUOUS
-

Dawn walks into this place.

diff --git a/tests/files/simple.fountain b/tests/files/simple.fountain new file mode 100644 index 0000000..07d488d --- /dev/null +++ b/tests/files/simple.fountain @@ -0,0 +1,9 @@ +EXT. SOMEWHERE - DAY + +GUY and GURL walks down the street. + +GUY +So what's up? + +GURL +Nothing much. diff --git a/tests/files/simple.fountain.fdx b/tests/files/simple.fountain.fdx new file mode 100644 index 0000000..2e56d82 --- /dev/null +++ b/tests/files/simple.fountain.fdx @@ -0,0 +1,24 @@ + + + + + + EXT. SOMEWHERE - DAY + + + GUY and GURL walks down the street. + + + GUY + + + So what's up? + + + GURL + + + Nothing much. + + + diff --git a/tests/files/simple.fountain.html b/tests/files/simple.fountain.html new file mode 100644 index 0000000..abcc55a --- /dev/null +++ b/tests/files/simple.fountain.html @@ -0,0 +1,4 @@ +
EXT. SOMEWHERE - DAY
+

GUY and GURL walks down the street.

+

GUY

So what's up?

+

GURL

Nothing much.

diff --git a/tests/files/simple.spmd b/tests/files/simple.spmd deleted file mode 100644 index 07d488d..0000000 --- a/tests/files/simple.spmd +++ /dev/null @@ -1,9 +0,0 @@ -EXT. SOMEWHERE - DAY - -GUY and GURL walks down the street. - -GUY -So what's up? - -GURL -Nothing much. diff --git a/tests/files/simple.spmd.fdx b/tests/files/simple.spmd.fdx deleted file mode 100644 index 2e56d82..0000000 --- a/tests/files/simple.spmd.fdx +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - EXT. SOMEWHERE - DAY - - - GUY and GURL walks down the street. - - - GUY - - - So what's up? - - - GURL - - - Nothing much. - - - diff --git a/tests/files/simple.spmd.html b/tests/files/simple.spmd.html deleted file mode 100644 index abcc55a..0000000 --- a/tests/files/simple.spmd.html +++ /dev/null @@ -1,4 +0,0 @@ -
EXT. SOMEWHERE - DAY
-

GUY and GURL walks down the street.

-

GUY

So what's up?

-

GURL

Nothing much.

diff --git a/tests/files_test.py b/tests/files_test.py index 41d17bb..0f0cba0 100644 --- a/tests/files_test.py +++ b/tests/files_test.py @@ -49,24 +49,24 @@ class ParseTests(unittest2.TestCase): expected = read_file(self.source(expected_results_file)) return actual, expected - def test_spmd_to_fdx(self): + def test_fountain_to_fdx(self): actual, expected = self.convert( - 'simple.spmd', 'simple.fdx', 'simple.spmd.fdx') + 'simple.fountain', 'simple.fdx', 'simple.fountain.fdx') self.assertMultiLineEqual(expected, actual) - def test_spmd_to_html(self): + def test_fountain_to_html(self): actual, expected = self.convert( - 'simple.spmd', 'simple.html', 'simple.spmd.html', '--bare') + 'simple.fountain', 'simple.html', 'simple.fountain.html', '--bare') self.assertMultiLineEqual(expected, actual) def test_scene_numbers(self): actual, expected = self.convert( - 'scene-numbers.spmd', 'scene-numbers.html', - 'scene-numbers.spmd.html', '--bare') + 'scene-numbers.fountain', 'scene-numbers.html', + 'scene-numbers.fountain.html', '--bare') self.assertMultiLineEqual(expected, actual) def test_sections(self): actual, expected = self.convert( - 'sections.spmd', 'sections.html', - 'sections.spmd.html', '--bare') + 'sections.fountain', 'sections.html', + 'sections.fountain.html', '--bare') self.assertMultiLineEqual(expected, actual) diff --git a/tests/fountain_test.py b/tests/fountain_test.py new file mode 100644 index 0000000..99578cf --- /dev/null +++ b/tests/fountain_test.py @@ -0,0 +1,435 @@ +# Copyright (c) 2011 Martin Vilcans +# Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php + +import unittest2 +from screenplain.parsers.fountain import parse +from screenplain.parsers import fountain +from screenplain.types import ( + Slug, Action, Dialog, DualDialog, Transition, Section +) +from screenplain.richstring import plain, italic, empty_string + + +class SlugTests(unittest2.TestCase): + def test_slug_with_prefix(self): + paras = list(parse([ + 'INT. SOMEWHERE - DAY', + '', + 'THIS IS JUST ACTION', + ])) + self.assertEquals([Slug, Action], [type(p) for p in paras]) + + def test_slug_must_be_single_line(self): + paras = list(parse([ + 'INT. SOMEWHERE - DAY', + 'ANOTHER LINE', + '', + 'Some action', + ])) + 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(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([ + '', + 'THIS IS JUST ACTION', + ])) + self.assertEquals([Action], [type(p) for p in paras]) + + def test_two_lines_creates_no_slug(self): + types = [type(p) for p in parse([ + '', + '', + 'This is a slug', + '', + ])] + # This used to be Slug. Changed in the Jan 2012 version of the spec. + self.assertEquals([Action], types) + + def test_period_creates_slug(self): + paras = parse([ + '.SNIPER SCOPE POV', + '', + ]) + self.assertEquals(1, len(paras)) + self.assertEquals(Slug, type(paras[0])) + self.assertEquals(plain('SNIPER SCOPE POV'), paras[0].line) + + def test_scene_number_is_parsed(self): + paras = parse(['EXT SOMEWHERE - DAY #42#']) + self.assertEquals(plain('EXT SOMEWHERE - DAY'), paras[0].line) + self.assertEquals(plain('42'), paras[0].scene_number) + + def test_only_last_two_hashes_in_slug_used_for_scene_number(self): + paras = parse(['INT ROOM #237 #42#']) + self.assertEquals(plain('42'), paras[0].scene_number) + self.assertEquals(plain('INT ROOM #237'), paras[0].line) + + def test_scene_number_must_be_alphanumeric(self): + paras = parse(['.SOMEWHERE #*HELLO*#']) + self.assertIsNone(paras[0].scene_number) + self.assertEquals( + (plain)(u'SOMEWHERE #') + (italic)(u'HELLO') + (plain)(u'#'), + paras[0].line + ) + + +class SectionTests(unittest2.TestCase): + def test_section_parsed_correctly(self): + paras = parse([ + '# first level', + '', + '## second level', + ]) + self.assertEquals([Section, Section], [type(p) for p in paras]) + self.assertEquals(1, paras[0].level) + self.assertEquals(plain('first level'), paras[0].text) + self.assertEquals(2, paras[1].level) + self.assertEquals(plain('second level'), paras[1].text) + + +class DialogTests(unittest2.TestCase): + # 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([ + 'SOME GUY', + 'Hello', + ])] + self.assertEquals(1, len(paras)) + dialog = paras[0] + self.assertEquals(Dialog, type(dialog)) + self.assertEquals(plain('SOME GUY'), dialog.character) + + # Fountain 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([ + '23', + 'Hello', + ])) + self.assertEquals([Action], [type(p) for p in paras]) + + def test_twospaced_line_is_not_character(self): + paras = list(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([ + '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, plain('(starting the engine)')), + dialog.blocks[0] + ) + self.assertEqual( + (False, plain('So much for retirement!')), + dialog.blocks[1] + ) + + def test_twospace_keeps_dialog_together(self): + paras = list(parse([ + 'SOMEONE', + 'One', + ' ', + 'Two', + ])) + self.assertEquals([Dialog], [type(p) for p in paras]) + self.assertEquals([ + (False, plain('One')), + (False, empty_string), + (False, plain('Two')), + ], paras[0].blocks) + + def test_dual_dialog(self): + paras = list(parse([ + 'BRICK', + 'Fuck retirement.', + '', + 'STEEL ^', + 'Fuck retirement!', + ])) + self.assertEquals([DualDialog], [type(p) for p in paras]) + dual = paras[0] + 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_without_previous_dialog_is_ignored(self): + paras = list(parse([ + 'Brick strolls down the street.', + '', + 'BRICK ^', + 'Nice retirement.', + ])) + self.assertEquals([Action, Dialog], [type(p) for p in paras]) + dialog = paras[1] + self.assertEqual(plain('BRICK ^'), dialog.character) + self.assertEqual([ + (False, plain('Nice retirement.')) + ], dialog.blocks) + + def test_leading_and_trailing_spaces_in_dialog(self): + paras = list(parse([ + 'JULIET', + 'O Romeo, Romeo! wherefore art thou Romeo?', + ' Deny thy father and refuse thy name; ', + 'Or, if thou wilt not, be but sworn my love,', + " And I'll no longer be a Capulet.", + ])) + self.assertEquals([Dialog], [type(p) for p in paras]) + self.assertEquals([ + (False, plain(u'O Romeo, Romeo! wherefore art thou Romeo?')), + (False, plain(u'Deny thy father and refuse thy name;')), + (False, plain(u'Or, if thou wilt not, be but sworn my love,')), + (False, plain(u"And I'll no longer be a Capulet.")), + ], paras[0].blocks) + + +class TransitionTests(unittest2.TestCase): + + 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 (?)', + '', + 'cut to:', + '', + "EXT. BRICK'S POOL - DAY", + ])) + self.assertEquals([Action, Action, Slug], [type(p) for p in paras]) + + def test_not_a_transition_on_trailing_whitespace(self): + paras = list(parse([ + 'Jack begins to argue vociferously in Vietnamese (?)', + '', + 'CUT TO: ', + '', + "EXT. BRICK'S POOL - DAY", + ])) + self.assertEquals([Action, Action, Slug], [type(p) for p in paras]) + + def test_transition_does_not_have_to_be_followed_by_slug(self): + # The "followed by slug" requirement is gone from the Jan 2012 spec + paras = list(parse([ + 'Bill lights a cigarette.', + '', + 'CUT TO:', + '', + 'SOME GUY mowing the lawn.', + ])) + self.assertEquals( + [Action, Transition, Action], + [type(p) for p in paras] + ) + + def test_greater_than_sign_means_transition(self): + paras = list(parse([ + 'Bill blows out the match.', + '', + '> FADE OUT.', + '', + '.DARKNESS', + ])) + self.assertEquals([Action, Transition, Slug], [type(p) for p in paras]) + self.assertEquals(plain('FADE OUT.'), paras[1].line) + + def test_centered_text_is_not_parsed_as_transition(self): + paras = list(parse([ + 'Bill blows out the match.', + '', + '> THE END. <', + '', + 'bye!' + ])) + self.assertEquals([Action, Action, Action], [type(p) for p in paras]) + + def test_transition_at_end(self): + paras = list(parse([ + 'They stroll hand in hand down the street.', + '', + '> FADE OUT.', + ])) + self.assertEquals([Action, Transition], [type(p) for p in paras]) + self.assertEquals(plain('FADE OUT.'), paras[1].line) + + +class ActionTests(unittest2.TestCase): + + def test_action_preserves_leading_whitespace(self): + paras = list(parse([ + 'hello', + '', + ' two spaces', + ' three spaces ', + ])) + self.assertEquals([Action, Action], [type(p) for p in paras]) + self.assertEquals( + [ + plain(u' two spaces'), + plain(u' three spaces'), + ], paras[1].lines + ) + + def test_single_centered_line(self): + paras = list(parse(['> center me! <'])) + self.assertEquals([Action], [type(p) for p in paras]) + self.assertTrue(paras[0].centered) + + def test_full_centered_paragraph(self): + lines = [ + '> first! <', + ' > second! <', + '> third!< ', + ] + paras = list(parse(lines)) + self.assertEquals([Action], [type(p) for p in paras]) + self.assertTrue(paras[0].centered) + self.assertEquals([ + plain('first!'), + plain('second!'), + plain('third!'), + ], paras[0].lines) + + def test_upper_case_centered_not_parsed_as_dialog(self): + paras = list(parse([ + '> FIRST! <', + ' > SECOND! <', + '> THIRD! <', + ])) + self.assertEquals([Action], [type(p) for p in paras]) + self.assertTrue(paras[0].centered) + + def test_centering_marks_in_middle_of_paragraphs_are_verbatim(self): + lines = [ + 'first!', + '> second! <', + 'third!', + ] + paras = list(parse(lines)) + self.assertEquals([Action], [type(p) for p in paras]) + self.assertFalse(paras[0].centered) + self.assertEquals([plain(line) for line in lines], paras[0].lines) + + +class SynopsisTests(unittest2.TestCase): + def test_synopsis_after_slug_adds_synopsis_to_scene(self): + paras = list(parse([ + "EXT. BRICK'S PATIO - DAY", + '', + "= Set up Brick & Steel's new life." + '', + ])) + self.assertEquals([Slug], [type(p) for p in paras]) + self.assertEquals( + "Set up Brick & Steel's new life.", + paras[0].synopsis + ) + + def test_synopsis_in_section(self): + paras = list(parse([ + '# section one', + '', + '= In which we get to know our characters' + ])) + self.assertEquals([Section], [type(p) for p in paras]) + self.assertEquals( + 'In which we get to know our characters', + paras[0].synopsis + ) + + def test_synopsis_syntax_parsed_as_literal(self): + paras = list(parse([ + 'Some action', + '', + '= A line that just happens to look like a synopsis' + ])) + self.assertEquals([Action, Action], [type(p) for p in paras]) + self.assertEquals( + [plain('= A line that just happens to look like a synopsis')], + paras[1].lines + ) + + +class TitlePageTests(unittest2.TestCase): + + def test_basic_title_page(self): + lines = [ + 'Title:', + ' _**BRICK & STEEL**_', + ' _**FULL RETIRED**_', + 'Author: Stu Maschwitz', + ] + self.assertDictEqual( + { + 'Title': ['_**BRICK & STEEL**_', '_**FULL RETIRED**_'], + 'Author': ['Stu Maschwitz'], + }, + fountain.parse_title_page(lines) + ) + + def test_multiple_values(self): + lines = [ + 'Title: Death', + 'Title: - a love story', + 'Title:', + ' (which happens to be true)', + ] + self.assertDictEqual( + { + 'Title': [ + 'Death', + '- a love story', + '(which happens to be true)' + ] + }, + fountain.parse_title_page(lines) + ) + + def test_empty_value_ignored(self): + lines = [ + 'Title:', + 'Author: John August', + ] + self.assertDictEqual( + {'Author': ['John August']}, + fountain.parse_title_page(lines) + ) + + def test_unparsable_title_page_returns_none(self): + lines = [ + 'Title: Inception', + ' additional line', + ] + self.assertIsNone(fountain.parse_title_page(lines)) + +if __name__ == '__main__': + unittest2.main() diff --git a/tests/spmd_test.py b/tests/spmd_test.py deleted file mode 100644 index bbd03b6..0000000 --- a/tests/spmd_test.py +++ /dev/null @@ -1,437 +0,0 @@ -# Copyright (c) 2011 Martin Vilcans -# Licensed under the MIT license: -# http://www.opensource.org/licenses/mit-license.php - -import unittest2 -from screenplain.parsers.spmd import parse -from screenplain.parsers import spmd -from screenplain.types import ( - Slug, Action, Dialog, DualDialog, Transition, Section -) -from screenplain.richstring import plain, italic, empty_string - - -class SlugTests(unittest2.TestCase): - def test_slug_with_prefix(self): - paras = list(parse([ - 'INT. SOMEWHERE - DAY', - '', - 'THIS IS JUST ACTION', - ])) - self.assertEquals([Slug, Action], [type(p) for p in paras]) - - def test_slug_must_be_single_line(self): - paras = list(parse([ - 'INT. SOMEWHERE - DAY', - 'ANOTHER LINE', - '', - 'Some action', - ])) - 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(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([ - '', - 'THIS IS JUST ACTION', - ])) - self.assertEquals([Action], [type(p) for p in paras]) - - def test_two_lines_creates_no_slug(self): - types = [type(p) for p in parse([ - '', - '', - 'This is a slug', - '', - ])] - # This used to be Slug. Changed in the Jan 2012 version of the spec. - self.assertEquals([Action], types) - - def test_period_creates_slug(self): - paras = parse([ - '.SNIPER SCOPE POV', - '', - ]) - self.assertEquals(1, len(paras)) - self.assertEquals(Slug, type(paras[0])) - self.assertEquals(plain('SNIPER SCOPE POV'), paras[0].line) - - def test_scene_number_is_parsed(self): - paras = parse(['EXT SOMEWHERE - DAY #42#']) - self.assertEquals(plain('EXT SOMEWHERE - DAY'), paras[0].line) - self.assertEquals(plain('42'), paras[0].scene_number) - - def test_only_last_two_hashes_in_slug_used_for_scene_number(self): - paras = parse(['INT ROOM #237 #42#']) - self.assertEquals(plain('42'), paras[0].scene_number) - self.assertEquals(plain('INT ROOM #237'), paras[0].line) - - def test_scene_number_must_be_alphanumeric(self): - paras = parse(['.SOMEWHERE #*HELLO*#']) - self.assertIsNone(paras[0].scene_number) - self.assertEquals( - (plain)(u'SOMEWHERE #') + (italic)(u'HELLO') + (plain)(u'#'), - paras[0].line - ) - - -class SectionTests(unittest2.TestCase): - def test_section_parsed_correctly(self): - paras = parse([ - '# first level', - '', - '## second level', - ]) - self.assertEquals([Section, Section], [type(p) for p in paras]) - self.assertEquals(1, paras[0].level) - self.assertEquals(plain('first level'), paras[0].text) - self.assertEquals(2, paras[1].level) - self.assertEquals(plain('second level'), paras[1].text) - - -class DialogTests(unittest2.TestCase): - # 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([ - 'SOME GUY', - 'Hello', - ])] - self.assertEquals(1, len(paras)) - dialog = paras[0] - self.assertEquals(Dialog, type(dialog)) - 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. - def test_nonalpha_character(self): - paras = list(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([ - '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([ - '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, plain('(starting the engine)')), - dialog.blocks[0] - ) - self.assertEqual( - (False, plain('So much for retirement!')), - dialog.blocks[1] - ) - - def test_twospace_keeps_dialog_together(self): - paras = list(parse([ - 'SOMEONE', - 'One', - ' ', - 'Two', - ])) - self.assertEquals([Dialog], [type(p) for p in paras]) - self.assertEquals([ - (False, plain('One')), - (False, empty_string), - (False, plain('Two')), - ], paras[0].blocks) - - def test_dual_dialog(self): - paras = list(parse([ - 'BRICK', - 'Fuck retirement.', - '', - 'STEEL ^', - 'Fuck retirement!', - ])) - self.assertEquals([DualDialog], [type(p) for p in paras]) - dual = paras[0] - 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_without_previous_dialog_is_ignored(self): - paras = list(parse([ - 'Brick strolls down the street.', - '', - 'BRICK ^', - 'Nice retirement.', - ])) - self.assertEquals([Action, Dialog], [type(p) for p in paras]) - dialog = paras[1] - self.assertEqual(plain('BRICK ^'), dialog.character) - self.assertEqual([ - (False, plain('Nice retirement.')) - ], dialog.blocks) - - def test_leading_and_trailing_spaces_in_dialog(self): - paras = list(parse([ - 'JULIET', - 'O Romeo, Romeo! wherefore art thou Romeo?', - ' Deny thy father and refuse thy name; ', - 'Or, if thou wilt not, be but sworn my love,', - " And I'll no longer be a Capulet.", - ])) - self.assertEquals([Dialog], [type(p) for p in paras]) - self.assertEquals([ - (False, plain(u'O Romeo, Romeo! wherefore art thou Romeo?')), - (False, plain(u'Deny thy father and refuse thy name;')), - (False, plain(u'Or, if thou wilt not, be but sworn my love,')), - (False, plain(u"And I'll no longer be a Capulet.")), - ], paras[0].blocks) - - -class TransitionTests(unittest2.TestCase): - - 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 (?)', - '', - 'cut to:', - '', - "EXT. BRICK'S POOL - DAY", - ])) - self.assertEquals([Action, Action, Slug], [type(p) for p in paras]) - - def test_not_a_transition_on_trailing_whitespace(self): - paras = list(parse([ - 'Jack begins to argue vociferously in Vietnamese (?)', - '', - 'CUT TO: ', - '', - "EXT. BRICK'S POOL - DAY", - ])) - self.assertEquals([Action, Action, Slug], [type(p) for p in paras]) - - def test_transition_does_not_have_to_be_followed_by_slug(self): - # The "followed by slug" requirement is gone from the Jan 2012 spec - paras = list(parse([ - 'Bill lights a cigarette.', - '', - 'CUT TO:', - '', - 'SOME GUY mowing the lawn.', - ])) - self.assertEquals( - [Action, Transition, Action], - [type(p) for p in paras] - ) - - def test_greater_than_sign_means_transition(self): - paras = list(parse([ - 'Bill blows out the match.', - '', - '> FADE OUT.', - '', - '.DARKNESS', - ])) - self.assertEquals([Action, Transition, Slug], [type(p) for p in paras]) - self.assertEquals(plain('FADE OUT.'), paras[1].line) - - def test_centered_text_is_not_parsed_as_transition(self): - paras = list(parse([ - 'Bill blows out the match.', - '', - '> THE END. <', - '', - 'bye!' - ])) - self.assertEquals([Action, Action, Action], [type(p) for p in paras]) - - def test_transition_at_end(self): - paras = list(parse([ - 'They stroll hand in hand down the street.', - '', - '> FADE OUT.', - ])) - self.assertEquals([Action, Transition], [type(p) for p in paras]) - self.assertEquals(plain('FADE OUT.'), paras[1].line) - - -class ActionTests(unittest2.TestCase): - - def test_action_preserves_leading_whitespace(self): - paras = list(parse([ - 'hello', - '', - ' two spaces', - ' three spaces ', - ])) - self.assertEquals([Action, Action], [type(p) for p in paras]) - self.assertEquals( - [ - plain(u' two spaces'), - plain(u' three spaces'), - ], paras[1].lines - ) - - def test_single_centered_line(self): - paras = list(parse(['> center me! <'])) - self.assertEquals([Action], [type(p) for p in paras]) - self.assertTrue(paras[0].centered) - - def test_full_centered_paragraph(self): - lines = [ - '> first! <', - ' > second! <', - '> third!< ', - ] - paras = list(parse(lines)) - self.assertEquals([Action], [type(p) for p in paras]) - self.assertTrue(paras[0].centered) - self.assertEquals([ - plain('first!'), - plain('second!'), - plain('third!'), - ], paras[0].lines) - - def test_upper_case_centered_not_parsed_as_dialog(self): - paras = list(parse([ - '> FIRST! <', - ' > SECOND! <', - '> THIRD! <', - ])) - self.assertEquals([Action], [type(p) for p in paras]) - self.assertTrue(paras[0].centered) - - def test_centering_marks_in_middle_of_paragraphs_are_verbatim(self): - lines = [ - 'first!', - '> second! <', - 'third!', - ] - paras = list(parse(lines)) - self.assertEquals([Action], [type(p) for p in paras]) - self.assertFalse(paras[0].centered) - self.assertEquals([plain(line) for line in lines], paras[0].lines) - - -class SynopsisTests(unittest2.TestCase): - def test_synopsis_after_slug_adds_synopsis_to_scene(self): - paras = list(parse([ - "EXT. BRICK'S PATIO - DAY", - '', - "= Set up Brick & Steel's new life." - '', - ])) - self.assertEquals([Slug], [type(p) for p in paras]) - self.assertEquals( - "Set up Brick & Steel's new life.", - paras[0].synopsis - ) - - def test_synopsis_in_section(self): - paras = list(parse([ - '# section one', - '', - '= In which we get to know our characters' - ])) - self.assertEquals([Section], [type(p) for p in paras]) - self.assertEquals( - 'In which we get to know our characters', - paras[0].synopsis - ) - - def test_synopsis_syntax_parsed_as_literal(self): - paras = list(parse([ - 'Some action', - '', - '= A line that just happens to look like a synopsis' - ])) - self.assertEquals([Action, Action], [type(p) for p in paras]) - self.assertEquals( - [plain('= A line that just happens to look like a synopsis')], - paras[1].lines - ) - - -class TitlePageTests(unittest2.TestCase): - - def test_basic_title_page(self): - lines = [ - 'Title:', - ' _**BRICK & STEEL**_', - ' _**FULL RETIRED**_', - 'Author: Stu Maschwitz', - ] - self.assertDictEqual( - { - 'Title': ['_**BRICK & STEEL**_', '_**FULL RETIRED**_'], - 'Author': ['Stu Maschwitz'], - }, - spmd.parse_title_page(lines) - ) - - def test_multiple_values(self): - lines = [ - 'Title: Death', - 'Title: - a love story', - 'Title:', - ' (which happens to be true)', - ] - self.assertDictEqual( - { - 'Title': [ - 'Death', - '- a love story', - '(which happens to be true)' - ] - }, - spmd.parse_title_page(lines) - ) - - def test_empty_value_ignored(self): - lines = [ - 'Title:', - 'Author: John August', - ] - self.assertDictEqual( - {'Author': ['John August']}, - spmd.parse_title_page(lines) - ) - - def test_unparsable_title_page_returns_none(self): - lines = [ - 'Title: Inception', - ' additional line', - ] - self.assertIsNone(spmd.parse_title_page(lines)) - -if __name__ == '__main__': - unittest2.main() -- cgit v1.2.3