From 7513b2cc69a3da30d520e5c99b218562a9eababe Mon Sep 17 00:00:00 2001 From: icewater Date: Wed, 5 Jul 2017 17:04:18 -0400 Subject: [PATCH 1/6] add basic dialog numbering for full html and pdf documents --- requirements.txt | 5 +- screenplain/export/html.py | 36 ++++++++++-- screenplain/export/pdf.py | 20 ++++--- screenplain/main.py | 16 +++++- tests/dialog_numbering_tests.py | 98 +++++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 tests/dialog_numbering_tests.py diff --git a/requirements.txt b/requirements.txt index 321e747..279c030 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -reportlab -unittest2 +PDF nose pep8 +reportlab +unittest2 diff --git a/screenplain/export/html.py b/screenplain/export/html.py index 1f88e64..56ecac2 100644 --- a/screenplain/export/html.py +++ b/screenplain/export/html.py @@ -10,6 +10,28 @@ from screenplain.richstring import plain +dialog_numbers_css = """ + +/* begin dialog numbering css */ + +body { + counter-reset: dialog_counter; +} + + +div.dialog:before, div.dual:before { + content: counter(dialog_counter); + counter-increment: dialog_counter; + display: inline; + float: left; + padding-right: 1em; +} + +/* end dialog numbering css */ + +""" + + class tag(object): """Handler for automatically opening and closing a tag. @@ -168,7 +190,7 @@ def _read_file(filename): return stream.read() -def convert(screenplay, out, css_file=None, bare=False): +def convert(screenplay, out, css_file=None, bare=False, numbered=False): """Convert the screenplay into HTML, written to the file-like object `out`. The output will be a complete HTML document unless `bare` is true. @@ -179,15 +201,17 @@ def convert(screenplay, out, css_file=None, bare=False): else: convert_full( screenplay, out, - css_file or os.path.join(os.path.dirname(__file__), 'default.css') + css_file or os.path.join(os.path.dirname(__file__), 'default.css'), + numbered ) -def convert_full(screenplay, out, css_file): +def convert_full(screenplay, out, css_file, numbered=False): """Convert the screenplay into a complete HTML document, written to the file-like object `out`. """ + with open(css_file, 'r') as stream: css = stream.read() out.write( @@ -198,6 +222,8 @@ def convert_full(screenplay, out, css_file): '' '' @@ -212,11 +238,13 @@ def convert_full(screenplay, out, css_file): ) -def convert_bare(screenplay, out): +def convert_bare(screenplay, out, numbered=False): """Convert the screenplay into HTML, written to the file-like object `out`. Does not create a complete HTML document, as it doesn't include , , etc. """ + if numbered: + out.write(dialog_numbers_css) formatter = Formatter(out) formatter.convert(screenplay) diff --git a/screenplain/export/pdf.py b/screenplain/export/pdf.py index 6955616..2d29364 100644 --- a/screenplain/export/pdf.py +++ b/screenplain/export/pdf.py @@ -43,7 +43,6 @@ top_margin = 1 * inch bottom_margin = page_height - top_margin - frame_height - default_style = ParagraphStyle( 'default', fontName='Courier', @@ -156,8 +155,12 @@ def add_slug(story, para, style, is_strong): story.append(Paragraph(html, style)) -def add_dialog(story, dialog): - story.append(Paragraph(dialog.character.to_html(), character_style)) +def add_dialog(story, dialog, numbered=False): + dialog_counter = numbered and \ + """ """ \ + or '' + story.append(Paragraph(dialog.character.to_html() + dialog_counter, + character_style)) for parenthetical, line in dialog.blocks: if parenthetical: story.append(Paragraph(line.to_html(), parenthentical_style)) @@ -165,10 +168,10 @@ def add_dialog(story, dialog): story.append(Paragraph(line.to_html(), dialog_style)) -def add_dual_dialog(story, dual): +def add_dual_dialog(story, dual, numbered=False): # TODO: format dual dialog - add_dialog(story, dual.left) - add_dialog(story, dual.right) + add_dialog(story, dual.left, numbered=numbered) + add_dialog(story, dual.right, numbered=numbered) def get_title_page_story(screenplay): @@ -245,15 +248,16 @@ def to_pdf( screenplay, output_filename, template_constructor=DocTemplate, is_strong=False, + numbered=False, ): story = get_title_page_story(screenplay) has_title_page = bool(story) for para in screenplay: if isinstance(para, Dialog): - add_dialog(story, para) + add_dialog(story, para, numbered=numbered) elif isinstance(para, DualDialog): - add_dual_dialog(story, para) + add_dual_dialog(story, para, numbered=numbered) elif isinstance(para, Action): add_paragraph( story, para, diff --git a/screenplain/main.py b/screenplain/main.py index 770427c..4c8d9d6 100644 --- a/screenplain/main.py +++ b/screenplain/main.py @@ -66,6 +66,14 @@ def main(args): 'Bold and Underlined.' ) ) + parser.add_option( + '--numbered', + action='store_true', + dest='numbered', + help=( + 'For Full HTML or PDF output, number the dialog.' + ) + ) options, args = parser.parse_args(args) if len(args) >= 3: parser.error('Too many arguments') @@ -124,11 +132,15 @@ def main(args): from screenplain.export.html import convert convert( screenplay, output, - css_file=options.css, bare=options.bare + css_file=options.css, bare=options.bare, + numbered=options.numbered ) elif format == 'pdf': from screenplain.export.pdf import to_pdf - to_pdf(screenplay, output, is_strong=options.strong) + to_pdf( + screenplay, output, is_strong=options.strong, + numbered=options.numbered + ) finally: if output_file: output.close() diff --git a/tests/dialog_numbering_tests.py b/tests/dialog_numbering_tests.py new file mode 100644 index 0000000..70f4585 --- /dev/null +++ b/tests/dialog_numbering_tests.py @@ -0,0 +1,98 @@ +from __future__ import with_statement + + +import codecs + +from PDF import PdfFileReader +from io import BytesIO + +from screenplain.export import html +from screenplain.export.html import dialog_numbers_css +from screenplain.export import pdf +from screenplain.parsers import fountain + +from testcompat import TestCase + + +script = """ + +EXT. DESSERT ISLAND - DAY + +ROBERT +I've told you a thousand times, I do not care for sea urchins. + +WALTER +Given our current situation, I hardly think we can afford to picky! + +ROBERT +Listen Walt, I'm a kelp man - through and through! +(staring determinedly into the mid-foredistance) +My father was a kelp man... + +ROBERT +...and his father before him. + +WALTER ^ +(sarcastically) +"...and his father before him." ... + +WALTER +I've heard it all before. +(pause) +Hey. This island is made of pie. + +""" + + +class DialogNumberingTests(TestCase): + + def setUp(self): + script_file = BytesIO(script) + self.screenplay = fountain.parse(BytesIO(script)) + + def _extract_character_lines_from_pdf(self, pdf_file): + pdf_reader = PdfFileReader(pdf_file) + page_1 = pdf_reader.getPage(0) + text = page_1.extractText() + lines = text.split('\n') + character_lines = [line for line in lines + if line.startswith('WALTER') or + line.startswith('ROBERT')] + return character_lines + + def test_pdf_without_numbering(self): + pdf_file = BytesIO() + pdf.to_pdf(self.screenplay, output_filename=pdf_file, numbered=False) + character_lines = self._extract_character_lines_from_pdf(pdf_file) + assert character_lines == ['ROBERT', + 'WALTER', + 'ROBERT', + 'ROBERT', + 'WALTER', + 'WALTER', + ] + + def test_pdf_with_numbering(self): + pdf_file = BytesIO() + pdf.to_pdf(self.screenplay, output_filename=pdf_file, numbered=True) + character_lines = self._extract_character_lines_from_pdf(pdf_file) + assert character_lines == ['ROBERT 1', + 'WALTER 2', + 'ROBERT 3', + 'ROBERT 4', + 'WALTER 5', + 'WALTER 6', + ] + + def _test_html(self, bare, numbered, expected): + html_file = BytesIO() + html.convert(self.screenplay, out=html_file, bare=bare, + numbered=numbered) + generated_html = html_file.getvalue() + return (dialog_numbers_css in generated_html) is expected + + def test_html_numbering(self): + assert self._test_html(bare=False, numbered=False, expected=False) + assert self._test_html(bare=False, numbered=True, expected=True) + assert self._test_html(bare=True, numbered=False, expected=False) + assert self._test_html(bare=True, numbered=True, expected=False) From 7e991b42996485ed286c419e509e71a9d9822a29 Mon Sep 17 00:00:00 2001 From: icewater Date: Wed, 5 Jul 2017 17:33:23 -0400 Subject: [PATCH 2/6] remove unused param from convert_bare --- screenplain/export/html.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/screenplain/export/html.py b/screenplain/export/html.py index 56ecac2..3fed4e2 100644 --- a/screenplain/export/html.py +++ b/screenplain/export/html.py @@ -238,13 +238,11 @@ def convert_full(screenplay, out, css_file, numbered=False): ) -def convert_bare(screenplay, out, numbered=False): +def convert_bare(screenplay, out): """Convert the screenplay into HTML, written to the file-like object `out`. Does not create a complete HTML document, as it doesn't include , , etc. """ - if numbered: - out.write(dialog_numbers_css) formatter = Formatter(out) formatter.convert(screenplay) From 0d8b59e7593a53fefec69084391bba42c7eda25b Mon Sep 17 00:00:00 2001 From: icewater Date: Wed, 5 Jul 2017 17:38:47 -0400 Subject: [PATCH 3/6] remove unnecessary line from test setup --- tests/dialog_numbering_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/dialog_numbering_tests.py b/tests/dialog_numbering_tests.py index 70f4585..7167cf7 100644 --- a/tests/dialog_numbering_tests.py +++ b/tests/dialog_numbering_tests.py @@ -47,7 +47,6 @@ class DialogNumberingTests(TestCase): def setUp(self): - script_file = BytesIO(script) self.screenplay = fountain.parse(BytesIO(script)) def _extract_character_lines_from_pdf(self, pdf_file): From e8b5b57b1d194893bbcc5c9c171369b667b2ef2a Mon Sep 17 00:00:00 2001 From: icewater Date: Wed, 5 Jul 2017 17:51:09 -0400 Subject: [PATCH 4/6] remove unused test imports --- tests/dialog_numbering_tests.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/dialog_numbering_tests.py b/tests/dialog_numbering_tests.py index 7167cf7..fbd311a 100644 --- a/tests/dialog_numbering_tests.py +++ b/tests/dialog_numbering_tests.py @@ -1,8 +1,3 @@ -from __future__ import with_statement - - -import codecs - from PDF import PdfFileReader from io import BytesIO From e9e36689d0c0ddd695e2ff3346d67e4e2203ea73 Mon Sep 17 00:00:00 2001 From: icewater Date: Wed, 5 Jul 2017 17:57:57 -0400 Subject: [PATCH 5/6] correct typo in test script copy --- tests/dialog_numbering_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dialog_numbering_tests.py b/tests/dialog_numbering_tests.py index fbd311a..efe9aeb 100644 --- a/tests/dialog_numbering_tests.py +++ b/tests/dialog_numbering_tests.py @@ -17,7 +17,7 @@ I've told you a thousand times, I do not care for sea urchins. WALTER -Given our current situation, I hardly think we can afford to picky! +Given our current situation, I hardly think we can afford to be picky! ROBERT Listen Walt, I'm a kelp man - through and through! From 775511cf766371d50ff6c8c50f0695f190014c5a Mon Sep 17 00:00:00 2001 From: icewater Date: Wed, 5 Jul 2017 18:05:59 -0400 Subject: [PATCH 6/6] de-convolute test helper method --- tests/dialog_numbering_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/dialog_numbering_tests.py b/tests/dialog_numbering_tests.py index efe9aeb..f4107d1 100644 --- a/tests/dialog_numbering_tests.py +++ b/tests/dialog_numbering_tests.py @@ -78,15 +78,15 @@ def test_pdf_with_numbering(self): 'WALTER 6', ] - def _test_html(self, bare, numbered, expected): + def _html_contains_numbering(self, bare, numbered): html_file = BytesIO() html.convert(self.screenplay, out=html_file, bare=bare, numbered=numbered) generated_html = html_file.getvalue() - return (dialog_numbers_css in generated_html) is expected + return dialog_numbers_css in generated_html def test_html_numbering(self): - assert self._test_html(bare=False, numbered=False, expected=False) - assert self._test_html(bare=False, numbered=True, expected=True) - assert self._test_html(bare=True, numbered=False, expected=False) - assert self._test_html(bare=True, numbered=True, expected=False) + assert self._html_contains_numbering(bare=False, numbered=True) + assert not self._html_contains_numbering(bare=False, numbered=False) + assert not self._html_contains_numbering(bare=True, numbered=False) + assert not self._html_contains_numbering(bare=True, numbered=True)