Mercurial > jouvence
changeset 24:2ef526c301cc 0.3.0
Add support for sections and synopsis.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Thu, 05 Jan 2017 09:27:11 -0800 |
parents | 36424a1081ff |
children | cd284808d139 |
files | README.rst jouvence/console.py jouvence/document.py jouvence/html.py jouvence/parser.py jouvence/renderer.py jouvence/resources/html_header.html jouvence/resources/html_styles.css jouvence/resources/html_styles.scss tests/conftest.py tests/test_scripts.yaml tests/test_synopsis.yaml |
diffstat | 12 files changed, 177 insertions(+), 31 deletions(-) [+] |
line wrap: on
line diff
--- a/README.rst Wed Jan 04 23:39:13 2017 -0800 +++ b/README.rst Thu Jan 05 09:27:11 2017 -0800 @@ -59,7 +59,6 @@ are not implemented yet: * Dual dialogue -* Sections and synopses * Proper Unicode support (although Fountain's spec greatly assumes English screenplays, sadly).
--- a/jouvence/console.py Wed Jan 04 23:39:13 2017 -0800 +++ b/jouvence/console.py Thu Jan 05 09:27:11 2017 -0800 @@ -78,6 +78,14 @@ print("", file=out) _w(out, colorama.Style.DIM, 80 * '=') + def write_section(self, depth, text, out): + print("", file=out) + _w(out, colorama.Fore.CYAN, '#' * depth + ' ' + text, True) + + def write_synopsis(self, text, out): + print("", file=out) + _w(out, colorama.Fore.GREEN, '= ' + text, True) + class ConsoleTextRenderer(BaseTextRenderer): def _writeStyled(self, style, text):
--- a/jouvence/document.py Wed Jan 04 23:39:13 2017 -0800 +++ b/jouvence/document.py Thu Jan 05 09:27:11 2017 -0800 @@ -59,6 +59,9 @@ def addPageBreak(self): self.paragraphs.append(JouvenceSceneElement(TYPE_PAGEBREAK, None)) + def addSection(self, depth, text): + self.paragraphs.append(JouvenceSceneSection(depth, text)) + def lastParagraph(self): try: return self.paragraphs[-1] @@ -77,6 +80,12 @@ _ellipsis(self.text, 15)) +class JouvenceSceneSection(JouvenceSceneElement): + def __init__(self, depth, text): + super().__init__(TYPE_SECTION, text) + self.depth = depth + + TYPE_ACTION = 0 TYPE_CENTEREDACTION = 1 TYPE_CHARACTER = 2 @@ -85,7 +94,8 @@ TYPE_TRANSITION = 5 TYPE_LYRICS = 6 TYPE_PAGEBREAK = 7 -TYPE_EMPTYLINES = 8 +TYPE_SECTION = 8 +TYPE_SYNOPSIS = 9 def _scene_element_type_str(t): @@ -105,8 +115,10 @@ return 'LYRICS' if t == TYPE_PAGEBREAK: return 'PAGEBREAK' - if t == TYPE_EMPTYLINES: - return 'EMPTYLINES' + if t == TYPE_SECTION: + return 'SECTION' + if t == TYPE_SYNOPSIS: + return 'SYNOPSIS' raise NotImplementedError()
--- a/jouvence/html.py Wed Jan 04 23:39:13 2017 -0800 +++ b/jouvence/html.py Thu Jan 05 09:27:11 2017 -0800 @@ -103,6 +103,12 @@ def write_pagebreak(self, out): out.write('<hr/>\n') + def write_section(self, depth, text, out): + _elem(out, 'p', 'section', '%s %s' % ('#' * depth, text)) + + def write_synopsis(self, text, out): + _elem(out, 'p', 'synopsis', text) + def _render_footnotes(self, out): for i, n in enumerate(self.text_renderer.notes): note_id = i + 1
--- a/jouvence/parser.py Wed Jan 04 23:39:13 2017 -0800 +++ b/jouvence/parser.py Thu Jan 05 09:27:11 2017 -0800 @@ -163,7 +163,7 @@ ctx.document.lastScene().addAction(self.text) -RE_CENTERED_LINE = re.compile(r"^\s*>\s*.*\s*<\s*$", re.M) +RE_CENTERED_LINE = re.compile(r"^\s*>\s*.*\s*<\s*$") class _CenteredActionState(JouvenceState): @@ -213,7 +213,7 @@ ctx.document.lastScene().addCenteredAction(self.text) -RE_CHARACTER_LINE = re.compile(r"^\s*[A-Z][A-Z\-\._\s]+\s*(\(.*\))?$", re.M) +RE_CHARACTER_LINE = re.compile(r"^\s*[A-Z][A-Z\-\._\s]+\s*(\(.*\))?$") class _CharacterState(JouvenceState): @@ -232,7 +232,7 @@ return [_ParentheticalState, _DialogState] -RE_PARENTHETICAL_LINE = re.compile(r"^\s*\(.*\)\s*$", re.M) +RE_PARENTHETICAL_LINE = re.compile(r"^\s*\(.*\)\s*$") class _ParentheticalState(JouvenceState): @@ -328,7 +328,7 @@ ctx.document.lastScene().addLyrics(self.text) -RE_TRANSITION_LINE = re.compile(r"^\s*[^a-z]+TO\:$", re.M) +RE_TRANSITION_LINE = re.compile(r"^\s*[^a-z]+TO\:$") class _TransitionState(JouvenceState): @@ -347,7 +347,7 @@ return ANY_STATE -RE_PAGE_BREAK_LINE = re.compile(r"^\=\=\=+$", re.M) +RE_PAGE_BREAK_LINE = re.compile(r"^\=\=\=+$") class _PageBreakState(JouvenceState): @@ -405,8 +405,8 @@ return self._state_cls() -RE_BONEYARD_START = re.compile(r"^/\*", re.M) -RE_BONEYARD_END = re.compile(r"\*/\s*$", re.M) +RE_BONEYARD_START = re.compile(r"^/\*") +RE_BONEYARD_END = re.compile(r"\*/\s*$") class _BoneyardState(JouvenceState): @@ -421,6 +421,45 @@ return ANY_STATE +RE_SECTION_LINE = re.compile(r"^(?P<depth>#+)\s*") + + +class _SectionState(JouvenceState): + def match(self, fp, ctx): + lines = fp.peeklines(3) + return (RE_EMPTY_LINE.match(lines[0]) and + RE_SECTION_LINE.match(lines[1]) and + RE_EMPTY_LINE.match(lines[2])) + + def consume(self, fp, ctx): + fp.readline() + line = fp.readline() + m = RE_SECTION_LINE.match(line) + depth = len(m.group('depth')) + line = line[m.end():].rstrip('\r\n') + ctx.document.lastScene().addSection(depth, line) + return ANY_STATE + + +RE_SYNOPSIS_LINE = re.compile(r"^=[^=]") + + +class _SynopsisState(JouvenceState): + def match(self, fp, ctx): + lines = fp.peeklines(3) + return (RE_EMPTY_LINE.match(lines[0]) and + RE_SYNOPSIS_LINE.match(lines[1]) and + RE_EMPTY_LINE.match(lines[2])) + + def consume(self, fp, ctx): + fp.readline() + line = fp.readline() + line = line[1:].lstrip() # Remove the `#`, and the following spaces. + line = line.rstrip('\r\n') + ctx.document.lastScene().addSynopsis(line) + return ANY_STATE + + class _EmptyLineState(JouvenceState): def __init__(self): super().__init__() @@ -457,6 +496,8 @@ _TransitionState, _PageBreakState, _CenteredActionState, + _SectionState, + _SynopsisState, _BoneyardState, _EmptyLineState, # Must be second to last. _ActionState, # Must be last.
--- a/jouvence/renderer.py Wed Jan 04 23:39:13 2017 -0800 +++ b/jouvence/renderer.py Thu Jan 05 09:27:11 2017 -0800 @@ -1,7 +1,8 @@ import re from jouvence.document import ( TYPE_ACTION, TYPE_CENTEREDACTION, TYPE_CHARACTER, TYPE_DIALOG, - TYPE_PARENTHETICAL, TYPE_TRANSITION, TYPE_LYRICS, TYPE_PAGEBREAK) + TYPE_PARENTHETICAL, TYPE_TRANSITION, TYPE_LYRICS, TYPE_PAGEBREAK, + TYPE_SECTION, TYPE_SYNOPSIS) class BaseDocumentRenderer: @@ -19,6 +20,8 @@ TYPE_TRANSITION: self.write_transition, TYPE_LYRICS: self.write_lyrics, TYPE_PAGEBREAK: self.write_pagebreak, + TYPE_SECTION: self.write_section, + TYPE_SYNOPSIS: self.write_synopsis, } def _tr(self, text): @@ -45,10 +48,12 @@ self.write_scene_heading(scene.header, out) for p in scene.paragraphs: rdr_func = self._para_rdrs[p.type] - if p.type != TYPE_PAGEBREAK: + if p.type == TYPE_PAGEBREAK: + rdr_func(out) + elif p.type == TYPE_SECTION: + rdr_func(p.depth, self._tr(p.text), out) + else: rdr_func(self._tr(p.text), out) - else: - rdr_func(out) def write_header(self, doc, out): pass @@ -86,6 +91,12 @@ def write_pagebreak(self, out): raise NotImplementedError() + def write_section(self, depth, text, out): + raise NotImplementedError() + + def write_synopsis(self, text, out): + raise NotImplementedError() + RE_ITALICS = re.compile( r"(?P<before>^|\s)(?P<esc>\\)?\*(?P<text>.*[^\s\*])\*(?=[^a-zA-Z0-9\*]|$)")
--- a/jouvence/resources/html_header.html Wed Jan 04 23:39:13 2017 -0800 +++ b/jouvence/resources/html_header.html Thu Jan 05 09:27:11 2017 -0800 @@ -16,16 +16,8 @@ background-color: #666; } .jouvence-doc { - background-color: #fff; box-shadow: #111 0px 0.5em 2em; } - .jouvence-notes { - background-color: #593; - color: #fff; - } - .jouvence-main, .jouvence-notes { - padding: 2em; - } p.footer { text-align: center; text-transform: uppercase;
--- a/jouvence/resources/html_styles.css Wed Jan 04 23:39:13 2017 -0800 +++ b/jouvence/resources/html_styles.css Thu Jan 05 09:27:11 2017 -0800 @@ -1,5 +1,15 @@ .jouvence-doc { - font-family: "Courier Prime", "Courier New", Courier, sans-serif; } + font-family: "Courier Prime", "Courier New", Courier, sans-serif; + background-color: #fff; + color: #111; } + .jouvence-doc hr { + border: none; + height: 1px; + background: #aaa; } + .jouvence-doc .jouvence-main { + padding: 2em 0; } + .jouvence-doc .jouvence-main p { + padding: 0 2em; } .jouvence-doc h1, .jouvence-doc .jouvence-title-page-heading { text-align: center; } .jouvence-doc .jouvence-scene-heading { @@ -12,5 +22,17 @@ margin: 0 4rem 0 4rem; } .jouvence-doc .jouvence-action-centered { text-align: center; } + .jouvence-doc p.jouvence-section { + background-color: #ccc; + padding: 1rem; } + .jouvence-doc p.jouvence-synopsis { + background-color: #ddd; + padding: 1rem; } + .jouvence-doc .jouvence-notes { + background-color: #593; + color: #fff; + padding: 1em 0; } + .jouvence-doc .jouvence-notes p { + padding: 0 2em; } /*# sourceMappingURL=html_styles.css.map */
--- a/jouvence/resources/html_styles.scss Wed Jan 04 23:39:13 2017 -0800 +++ b/jouvence/resources/html_styles.scss Thu Jan 05 09:27:11 2017 -0800 @@ -1,5 +1,19 @@ .jouvence-doc { font-family: "Courier Prime", "Courier New", Courier, sans-serif; + background-color: #fff; + color: #111; + + hr { + border: none; + height: 1px; + background: #aaa; + } + + .jouvence-main { + padding: 2em 0; + + p { padding: 0 2em; } + } h1, .jouvence-title-page-heading { text-align: center; @@ -27,4 +41,22 @@ .jouvence-action-centered { text-align: center; } + + p.jouvence-section { + background-color: #ccc; + padding: 1rem; + } + + p.jouvence-synopsis { + background-color: #ddd; + padding: 1rem; + } + + .jouvence-notes { + background-color: #593; + color: #fff; + padding: 1em 0; + + p { padding: 0 2em; } + } }
--- a/tests/conftest.py Wed Jan 04 23:39:13 2017 -0800 +++ b/tests/conftest.py Thu Jan 05 09:27:11 2017 -0800 @@ -4,10 +4,10 @@ import yaml import pytest from jouvence.document import ( - JouvenceSceneElement, + JouvenceSceneElement, JouvenceSceneSection, TYPE_ACTION, TYPE_CENTEREDACTION, TYPE_CHARACTER, TYPE_DIALOG, TYPE_PARENTHETICAL, TYPE_TRANSITION, TYPE_LYRICS, TYPE_PAGEBREAK, - TYPE_EMPTYLINES, + TYPE_SYNOPSIS, _scene_element_type_str) from jouvence.parser import JouvenceParser, JouvenceParserError @@ -170,11 +170,6 @@ cur_paras.append(JouvenceSceneElement(TYPE_PAGEBREAK, None)) continue - if RE_BLANK_LINE.match(item): - text = len(item) * '\n' - cur_paras.append(JouvenceSceneElement(TYPE_EMPTYLINES, text)) - continue - token = item[:1] if token == '.': if cur_header or cur_paras: @@ -197,6 +192,12 @@ cur_paras.append(_t(item[1:])) elif token == '~': cur_paras.append(_l(item[1:])) + elif token == '#': + cur_paras.append( + JouvenceSceneSection(1, item[1:])) + elif token == '+': + cur_paras.append( + JouvenceSceneElement(TYPE_SYNOPSIS, item[1:])) else: raise Exception("Unknown token: %s" % token) if cur_header or cur_paras:
--- a/tests/test_scripts.yaml Wed Jan 04 23:39:13 2017 -0800 +++ b/tests/test_scripts.yaml Thu Jan 05 09:27:11 2017 -0800 @@ -61,3 +61,20 @@ out: - ".EXT. ANOTHER PLACE" - "!\nThis is an action I think." +--- +in: | + # ACT I + + = Set up the characters and the story. + + EXT. BRICK'S PATIO - DAY + + = This scene sets up Brick & Steel's new life as retirees. + + A gorgeous day. The sun is shining. +out: + - "#ACT I" + - "+Set up the characters and the story." + - ".EXT. BRICK'S PATIO - DAY" + - "+This scene sets up Brick & Steel's new life as retirees." + - "!\nA gorgeous day. The sun is shining."