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:
objectParser 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'>
- 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:
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:
FileNotFoundError – If the specified file does not exist
OSError – If the file cannot be read
UnicodeDecodeError – If the file is not valid UTF-8
- Return type:
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 closedunclosed-note(error): a[[note opened but never closedorphan-character-cue(warning): an uppercase cue demoted to action because no dialogue follows itempty-document(warning): input that parses to zero elements
- Parameters:
text (str) – Raw Fountain markup text to validate
- Returns:
List of
ValidationIssuediagnostics. 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.