Parser Module

Fountain markup parser for converting Fountain screenwriting format to structured elements.

The parser architecture follows a structured two-pass approach designed to handle Fountain’s unique title page format followed by screenplay body content:

Pass 1: Title Page Metadata Extraction - Parses key-value pairs from document header (Title:, Author:, etc.) - Handles multi-line values and continuation lines - Stops when encountering the first body element (scene heading, action, etc.) - Supports all standard Fountain title page fields

Pass 2: Body Element Classification - Line-by-line parsing using regex pattern matching - Precedence-based classification (forced elements > natural patterns > context-dependent) - Context-aware dialogue detection using lookahead and element history - Dual dialogue post-processing to pair simultaneous character speech

Regex Classification Hierarchy: 1. Forced elements (!, @, >, .) - highest precedence override 2. Special markers (boneyard, page breaks, notes) 3. Natural structural patterns (scene headings, transitions) 4. Character name detection with dialogue lookahead 5. Context-dependent elements (dialogue, parentheticals) 6. Default fallback to action text

Key Design Principles: - Lenient parsing: unmatched lines fall back to action; problems become diagnostics - Regex patterns are class constants, each documented next to its definition

class fountain.parser.FountainParser[source]

Bases: object

Parser for Fountain markup format.

This parser implements a two-pass parsing strategy for Fountain screenplay format: 1. First pass: Extract title page metadata from document header 2. Second pass: Parse body elements using regex pattern matching

The parser handles all Fountain specification elements including scene headings, character names, dialogue, action lines, transitions, and special formatting.

lines

List of text lines being parsed

current_line

Current line index during parsing

elements

List of parsed FountainElement objects

in_boneyard

Flag tracking if parser is inside a multi-line comment block

Examples

>>> parser = FountainParser()
>>> doc = parser.parse("INT. COFFEE SHOP - DAY\n\nJOHN\nHello world.")
>>> len(doc.elements)
3
>>> doc.elements[0].type
<ElementType.SCENE_HEADING: 'scene_heading'>
__init__()[source]
parse(text)[source]

Parse Fountain text and return a FountainDocument.

Implements a two-pass parsing strategy: 1. First pass: Extract title page metadata from document header 2. Second pass: Parse body elements line by line using regex classification

The parser processes elements in order, maintaining context for dialogue detection and handling special cases like dual dialogue pairing.

Parameters:

text (str) – Raw Fountain markup text to parse

Returns:

FountainDocument containing parsed elements and metadata

Return type:

FountainDocument

Basic parsing with title page and dialogue:

>>> parser = FountainParser()
>>> script = "Title: My Script\n\nINT. HOUSE - DAY\n\nJOHN\nHello there."
>>> doc = parser.parse(script)
>>> doc.metadata['title']
'My Script'
>>> len(doc.elements)
3
>>> doc.elements[0].type.value
'scene_heading'
Complex parsing with forced elements and formatting:
>>> complex_script = '''Title: Complex Script
... Author: Test Author
...
... .FLASHBACK - TITLE SEQUENCE
...
... @narrator
... This is **bold** and *italic* text.
...
... >THE END<'''
>>> doc = parser.parse(complex_script)
>>> doc.metadata['author']
'Test Author'
>>> doc.elements[0].metadata['forced']
True
>>> doc.elements[1].metadata['forced']
True
>>> len(doc.elements[2].formatting)
2
>>> doc.elements[3].type.value
'centered'
Complex dual dialogue with extensions and formatting:
>>> dual_script = '''Title: Dual Dialogue Test
...
... INT. RESTAURANT - NIGHT
...
... JOHN (V.O.)
... This is my **inner voice**.
...
... MARY (PHONE)^
... I can hear you from _here_.
...
... >THE END<'''
>>> doc = parser.parse(dual_script)
>>> doc.elements[0].type.value  # Scene heading
'scene_heading'
>>> doc.elements[1].type.value  # Dual dialogue element (characters combined)
'dual_dialogue'
>>> doc.elements[1].metadata['left_character'].metadata['extension']
'V.O.'
>>> doc.elements[1].metadata['right_character'].metadata['extension']
'PHONE'
>>> len(doc.elements[1].metadata['left_dialogue'])  # Bold formatting preserved
1
>>> doc.elements[2].type.value  # Centered text
'centered'
Boneyard comments and special elements:
>>> boneyard_script = '''/* This is a comment */
... INT. HOUSE - DAY
...
... [[This is a note]]
...
... JOHN
... Hello /* inline comment */ world.
...
... ===
...
... # Act Two
...
... = Synopsis of next scene'''
>>> doc = parser.parse(boneyard_script)
>>> doc.elements[0].type.value
'boneyard'
>>> doc.elements[2].type.value
'note'
>>> doc.elements[5].type.value
'page_break'
>>> doc.elements[6].type.value
'section'
>>> doc.elements[7].type.value
'synopsis'
parse_file(filepath)[source]

Parse a Fountain file and return a FountainDocument.

Convenience method that reads a Fountain file from disk and parses it into a structured FountainDocument, decoding it as UTF-8.

Parameters:

filepath (str | PathLike[str]) – Path to the Fountain file to parse, as a string or path object. Can be absolute or relative.

Returns:

FountainDocument containing the parsed screenplay elements and metadata

Raises:
Return type:

FountainDocument

Examples

>>> parser = FountainParser()
>>> doc = parser.parse_file("screenplay.fountain")
>>> print(f"Title: {doc.metadata.get('title', 'Untitled')}")
Title: My Great Screenplay
>>> len(doc.elements)
42

Note

This method assumes UTF-8 encoding, which is standard for Fountain files. If you need to handle other encodings, read the file manually and use the parse() method instead.

validate(text)[source]

Validate Fountain text and return a list of diagnostics.

Runs the same two-pass analysis as parse(), but collects diagnostics about structural problems instead of discarding the information. parse() stays lenient and non-raising; validate() reports what a lenient parse silently tolerated. Calling validate() does not change what a later parse() of the same text returns, because parse() fully resets parser state on entry.

The initial diagnostic set covers:

  • unclosed-boneyard (error): a /* comment opened but never closed

  • unclosed-note (error): a [[ note opened but never closed

  • orphan-character-cue (warning): an uppercase cue demoted to action because no dialogue follows it

  • empty-document (warning): input that parses to zero elements

Parameters:

text (str) – Raw Fountain markup text to validate

Returns:

List of ValidationIssue diagnostics. Empty when the document is well formed.

Return type:

list[ValidationIssue]

Examples

>>> parser = FountainParser()
>>> parser.validate("INT. HOUSE - DAY\n\nJOHN\nHello.")
[]
>>> issues = parser.validate("INT. HOUSE - DAY\n\n/* open")
>>> issues[0].code
'unclosed-boneyard'
>>> issues[0].severity
'error'

The parser module converts raw Fountain text into structured elements.