fountain-py 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fountain_py-0.1.0/.gitignore +210 -0
- fountain_py-0.1.0/.vale.ini +69 -0
- fountain_py-0.1.0/CHANGELOG.md +61 -0
- fountain_py-0.1.0/CONTRIBUTING.md +84 -0
- fountain_py-0.1.0/LICENSE +21 -0
- fountain_py-0.1.0/PKG-INFO +186 -0
- fountain_py-0.1.0/README.md +161 -0
- fountain_py-0.1.0/docs/source/_templates/sidebar/brand.html +14 -0
- fountain_py-0.1.0/docs/source/api/document.rst +9 -0
- fountain_py-0.1.0/docs/source/api/elements.rst +10 -0
- fountain_py-0.1.0/docs/source/api/index.rst +16 -0
- fountain_py-0.1.0/docs/source/api/parser.rst +8 -0
- fountain_py-0.1.0/docs/source/api/renderer.rst +9 -0
- fountain_py-0.1.0/docs/source/changelog.rst +73 -0
- fountain_py-0.1.0/docs/source/conf.py +125 -0
- fountain_py-0.1.0/docs/source/contributing/development.rst +86 -0
- fountain_py-0.1.0/docs/source/contributing/documentation.rst +76 -0
- fountain_py-0.1.0/docs/source/contributing/index.rst +18 -0
- fountain_py-0.1.0/docs/source/contributing/testing.rst +74 -0
- fountain_py-0.1.0/docs/source/examples/coffee_shop.fountain +45 -0
- fountain_py-0.1.0/docs/source/explanation/pipeline.rst +75 -0
- fountain_py-0.1.0/docs/source/explanation/roundtrip-and-notes.rst +78 -0
- fountain_py-0.1.0/docs/source/explanation/what-is-fountain.rst +44 -0
- fountain_py-0.1.0/docs/source/how-to/embed-fragment.rst +40 -0
- fountain_py-0.1.0/docs/source/how-to/export-to-json.rst +31 -0
- fountain_py-0.1.0/docs/source/how-to/extract-character-dialogue.rst +50 -0
- fountain_py-0.1.0/docs/source/how-to/render-to-html-file.rst +37 -0
- fountain_py-0.1.0/docs/source/how-to/roundtrip-to-fountain.rst +28 -0
- fountain_py-0.1.0/docs/source/how-to/style-the-html.rst +50 -0
- fountain_py-0.1.0/docs/source/how-to/validate-a-file.rst +122 -0
- fountain_py-0.1.0/docs/source/index.rst +124 -0
- fountain_py-0.1.0/docs/source/installation.rst +66 -0
- fountain_py-0.1.0/docs/source/quickstart.rst +111 -0
- fountain_py-0.1.0/docs/source/reference/elements.rst +472 -0
- fountain_py-0.1.0/docs/source/reference/parsing-behavior.rst +651 -0
- fountain_py-0.1.0/docs/source/reference/rendering.rst +409 -0
- fountain_py-0.1.0/docs-plan.md +280 -0
- fountain_py-0.1.0/examples/README.md +57 -0
- fountain_py-0.1.0/pyproject.toml +121 -0
- fountain_py-0.1.0/src/fountain/__init__.py +25 -0
- fountain_py-0.1.0/src/fountain/document.py +359 -0
- fountain_py-0.1.0/src/fountain/elements.py +291 -0
- fountain_py-0.1.0/src/fountain/parser.py +2042 -0
- fountain_py-0.1.0/src/fountain/py.typed +0 -0
- fountain_py-0.1.0/src/fountain/renderer.py +1056 -0
- fountain_py-0.1.0/test-plan.md +977 -0
- fountain_py-0.1.0/tests/__init__.py +1 -0
- fountain_py-0.1.0/tests/conftest.py +38 -0
- fountain_py-0.1.0/tests/fixtures/simple_script.fountain +41 -0
- fountain_py-0.1.0/tests/test_document.py +175 -0
- fountain_py-0.1.0/tests/test_edge_cases.py +2581 -0
- fountain_py-0.1.0/tests/test_parser.py +1219 -0
- fountain_py-0.1.0/tests/test_quickstart_examples.py +376 -0
- fountain_py-0.1.0/tests/test_renderer.py +1138 -0
- fountain_py-0.1.0/tests/test_validation.py +105 -0
- fountain_py-0.1.0/uv.lock +1130 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Testing stuff for now
|
|
2
|
+
*macbeth*
|
|
3
|
+
scratchpad.md
|
|
4
|
+
.claude/*
|
|
5
|
+
commit-msg.md
|
|
6
|
+
goal.md
|
|
7
|
+
.ai-sessions/handoffs/
|
|
8
|
+
.token.txt
|
|
9
|
+
|
|
10
|
+
# Byte-compiled / optimized / DLL files
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.py[codz]
|
|
13
|
+
*$py.class
|
|
14
|
+
|
|
15
|
+
# C extensions
|
|
16
|
+
*.so
|
|
17
|
+
|
|
18
|
+
# Distribution / packaging
|
|
19
|
+
.Python
|
|
20
|
+
build/
|
|
21
|
+
develop-eggs/
|
|
22
|
+
dist/
|
|
23
|
+
downloads/
|
|
24
|
+
eggs/
|
|
25
|
+
.eggs/
|
|
26
|
+
lib/
|
|
27
|
+
lib64/
|
|
28
|
+
parts/
|
|
29
|
+
sdist/
|
|
30
|
+
var/
|
|
31
|
+
wheels/
|
|
32
|
+
share/python-wheels/
|
|
33
|
+
*.egg-info/
|
|
34
|
+
.installed.cfg
|
|
35
|
+
*.egg
|
|
36
|
+
MANIFEST
|
|
37
|
+
|
|
38
|
+
# PyInstaller
|
|
39
|
+
# Usually these files are written by a python script from a template
|
|
40
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
41
|
+
*.manifest
|
|
42
|
+
*.spec
|
|
43
|
+
|
|
44
|
+
# Installer logs
|
|
45
|
+
pip-log.txt
|
|
46
|
+
pip-delete-this-directory.txt
|
|
47
|
+
|
|
48
|
+
# Unit test / coverage reports
|
|
49
|
+
htmlcov/
|
|
50
|
+
.tox/
|
|
51
|
+
.nox/
|
|
52
|
+
.coverage
|
|
53
|
+
.coverage.*
|
|
54
|
+
.cache
|
|
55
|
+
nosetests.xml
|
|
56
|
+
coverage.xml
|
|
57
|
+
*.cover
|
|
58
|
+
*.py,cover
|
|
59
|
+
.hypothesis/
|
|
60
|
+
.pytest_cache/
|
|
61
|
+
cover/
|
|
62
|
+
|
|
63
|
+
# Translations
|
|
64
|
+
*.mo
|
|
65
|
+
*.pot
|
|
66
|
+
|
|
67
|
+
# Django stuff:
|
|
68
|
+
*.log
|
|
69
|
+
local_settings.py
|
|
70
|
+
db.sqlite3
|
|
71
|
+
db.sqlite3-journal
|
|
72
|
+
|
|
73
|
+
# Flask stuff:
|
|
74
|
+
instance/
|
|
75
|
+
.webassets-cache
|
|
76
|
+
|
|
77
|
+
# Scrapy stuff:
|
|
78
|
+
.scrapy
|
|
79
|
+
|
|
80
|
+
# Sphinx documentation
|
|
81
|
+
docs/_build/
|
|
82
|
+
|
|
83
|
+
# PyBuilder
|
|
84
|
+
.pybuilder/
|
|
85
|
+
target/
|
|
86
|
+
|
|
87
|
+
# Jupyter Notebook
|
|
88
|
+
.ipynb_checkpoints
|
|
89
|
+
|
|
90
|
+
# IPython
|
|
91
|
+
profile_default/
|
|
92
|
+
ipython_config.py
|
|
93
|
+
|
|
94
|
+
# pyenv
|
|
95
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
96
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
97
|
+
# .python-version
|
|
98
|
+
|
|
99
|
+
# pipenv
|
|
100
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
101
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
102
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
103
|
+
# install all needed dependencies.
|
|
104
|
+
#Pipfile.lock
|
|
105
|
+
|
|
106
|
+
# UV
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
108
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
109
|
+
# commonly ignored for libraries.
|
|
110
|
+
#uv.lock
|
|
111
|
+
|
|
112
|
+
# poetry
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
114
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
115
|
+
# commonly ignored for libraries.
|
|
116
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
117
|
+
#poetry.lock
|
|
118
|
+
#poetry.toml
|
|
119
|
+
|
|
120
|
+
# pdm
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
122
|
+
#pdm.lock
|
|
123
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
124
|
+
# in version control.
|
|
125
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
126
|
+
.pdm.toml
|
|
127
|
+
.pdm-python
|
|
128
|
+
.pdm-build/
|
|
129
|
+
|
|
130
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
131
|
+
__pypackages__/
|
|
132
|
+
|
|
133
|
+
# Celery stuff
|
|
134
|
+
celerybeat-schedule
|
|
135
|
+
celerybeat.pid
|
|
136
|
+
|
|
137
|
+
# SageMath parsed files
|
|
138
|
+
*.sage.py
|
|
139
|
+
|
|
140
|
+
# Environments
|
|
141
|
+
.env
|
|
142
|
+
.envrc
|
|
143
|
+
.venv
|
|
144
|
+
env/
|
|
145
|
+
venv/
|
|
146
|
+
ENV/
|
|
147
|
+
env.bak/
|
|
148
|
+
venv.bak/
|
|
149
|
+
|
|
150
|
+
# Spyder project settings
|
|
151
|
+
.spyderproject
|
|
152
|
+
.spyproject
|
|
153
|
+
|
|
154
|
+
# Rope project settings
|
|
155
|
+
.ropeproject
|
|
156
|
+
|
|
157
|
+
# mkdocs documentation
|
|
158
|
+
/site
|
|
159
|
+
|
|
160
|
+
# mypy
|
|
161
|
+
.mypy_cache/
|
|
162
|
+
.dmypy.json
|
|
163
|
+
dmypy.json
|
|
164
|
+
|
|
165
|
+
# Pyre type checker
|
|
166
|
+
.pyre/
|
|
167
|
+
|
|
168
|
+
# pytype static type analyzer
|
|
169
|
+
.pytype/
|
|
170
|
+
|
|
171
|
+
# Cython debug symbols
|
|
172
|
+
cython_debug/
|
|
173
|
+
|
|
174
|
+
# PyCharm
|
|
175
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
176
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
177
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
178
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
179
|
+
#.idea/
|
|
180
|
+
|
|
181
|
+
# Abstra
|
|
182
|
+
# Abstra is an AI-powered process automation framework.
|
|
183
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
184
|
+
# Learn more at https://abstra.io/docs
|
|
185
|
+
.abstra/
|
|
186
|
+
|
|
187
|
+
# Visual Studio Code
|
|
188
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
189
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
190
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
191
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
192
|
+
# .vscode/
|
|
193
|
+
|
|
194
|
+
# Ruff stuff:
|
|
195
|
+
.ruff_cache/
|
|
196
|
+
|
|
197
|
+
# PyPI configuration file
|
|
198
|
+
.pypirc
|
|
199
|
+
|
|
200
|
+
# Cursor
|
|
201
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
202
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
203
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
204
|
+
.cursorignore
|
|
205
|
+
.cursorindexingignore
|
|
206
|
+
|
|
207
|
+
# Marimo
|
|
208
|
+
marimo/_static/
|
|
209
|
+
marimo/_lsp/
|
|
210
|
+
__marimo__/
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Vale configuration for the fountain-py docs (reStructuredText under docs/source).
|
|
2
|
+
#
|
|
3
|
+
# StylesPath points at Mason's shared vale-styles checkout, following the
|
|
4
|
+
# convention in that repo's README (one styles directory per machine that every
|
|
5
|
+
# project points at). Contributors linting locally should set StylesPath to
|
|
6
|
+
# their own checkout of https://github.com/MasonEgger/vale-styles.
|
|
7
|
+
#
|
|
8
|
+
# Vale parses .rst natively. The vale-styles OneSentencePerLine rule skips
|
|
9
|
+
# .. code-block:: / .. doctest:: bodies; note that SentenceLength can still flag
|
|
10
|
+
# the occasional line inside a Sphinx doctest, which is a known limitation.
|
|
11
|
+
#
|
|
12
|
+
# Three base rules are tuned down for this project, in every section below:
|
|
13
|
+
# FirstUse off - the acronyms here (HTML, CSS, JSON, API, UTF) and the
|
|
14
|
+
# screenplay terms (INT, EXT, V.O., NOTE) are known to
|
|
15
|
+
# the audience or defined by the linked Fountain spec.
|
|
16
|
+
# BoldBulletHeaders off - the reference catalogs and landing pages use bold
|
|
17
|
+
# labels for reference and navigation, which the writing
|
|
18
|
+
# style rules permit for reference material.
|
|
19
|
+
# FleschReadingEase sug - a document-level readability nudge, not a per-line fix;
|
|
20
|
+
# kept visible as a suggestion rather than a warning.
|
|
21
|
+
|
|
22
|
+
StylesPath = /home/mmegger/Code/vale-styles/styles
|
|
23
|
+
MinAlertLevel = suggestion
|
|
24
|
+
|
|
25
|
+
# The tutorial: full technical register. The quickstart closes with a
|
|
26
|
+
# "Next Steps" section rather than a literal "Conclusion" heading.
|
|
27
|
+
[docs/source/quickstart.rst]
|
|
28
|
+
BasedOnStyles = MasonBase, MasonTechnical
|
|
29
|
+
MasonBase.We = suggestion
|
|
30
|
+
MasonBase.Conclusion = NO
|
|
31
|
+
MasonBase.FirstUse = NO
|
|
32
|
+
MasonBase.BoldBulletHeaders = NO
|
|
33
|
+
MasonBase.FleschReadingEase = suggestion
|
|
34
|
+
|
|
35
|
+
# How-to guides and reference: technical register for the STE clarity rules,
|
|
36
|
+
# but these are not tutorials, so drop the Prerequisites and Conclusion
|
|
37
|
+
# expectations.
|
|
38
|
+
[docs/source/{how-to,reference}/**/*.rst]
|
|
39
|
+
BasedOnStyles = MasonBase, MasonTechnical
|
|
40
|
+
MasonBase.We = suggestion
|
|
41
|
+
MasonBase.Conclusion = NO
|
|
42
|
+
MasonTechnical.Prerequisites = NO
|
|
43
|
+
MasonBase.FirstUse = NO
|
|
44
|
+
MasonBase.BoldBulletHeaders = NO
|
|
45
|
+
MasonBase.FleschReadingEase = suggestion
|
|
46
|
+
|
|
47
|
+
# Explanation: discursive prose. Base rules only, so the procedural sentence-
|
|
48
|
+
# length cap does not flatten conceptual writing.
|
|
49
|
+
[docs/source/explanation/**/*.rst]
|
|
50
|
+
BasedOnStyles = MasonBase
|
|
51
|
+
MasonBase.Conclusion = NO
|
|
52
|
+
MasonBase.FirstUse = NO
|
|
53
|
+
MasonBase.BoldBulletHeaders = NO
|
|
54
|
+
MasonBase.FleschReadingEase = suggestion
|
|
55
|
+
|
|
56
|
+
# Landing, install, changelog, contributing, and autodoc stubs: base prose only.
|
|
57
|
+
[docs/source/{index,installation,changelog}.rst]
|
|
58
|
+
BasedOnStyles = MasonBase
|
|
59
|
+
MasonBase.Conclusion = NO
|
|
60
|
+
MasonBase.FirstUse = NO
|
|
61
|
+
MasonBase.BoldBulletHeaders = NO
|
|
62
|
+
MasonBase.FleschReadingEase = suggestion
|
|
63
|
+
|
|
64
|
+
[docs/source/{contributing,api}/**/*.rst]
|
|
65
|
+
BasedOnStyles = MasonBase
|
|
66
|
+
MasonBase.Conclusion = NO
|
|
67
|
+
MasonBase.FirstUse = NO
|
|
68
|
+
MasonBase.BoldBulletHeaders = NO
|
|
69
|
+
MasonBase.FleschReadingEase = suggestion
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-04-09
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
#### Parser — Full Fountain Spec Compliance
|
|
15
|
+
- Two-pass parser: title page metadata extraction, then line-by-line element classification
|
|
16
|
+
- All 14 body element types emitted by the parser: scene headings, action, character, dialogue, parenthetical, transitions, notes, boneyard, sections, synopses, dual dialogue, page breaks, centered text, lyrics (the `ElementType` enum has 15 members; the 15th, `TITLE_PAGE`, is not emitted as an element: title page data is parsed into `FountainDocument.metadata`)
|
|
17
|
+
- Forced element prefixes (`.`, `!`, `@`, `>`) override natural detection rules
|
|
18
|
+
- Scene number extraction (`#1#`, `#2A#`)
|
|
19
|
+
- Character extensions (`V.O.`, `O.S.`, `CONT'D`) and automatic continuation detection
|
|
20
|
+
- Section level metadata (`# Act` = level 1, `## Scene` = level 2, etc.)
|
|
21
|
+
- Ellipsis protection on forced scene headings (`.` + alphanumeric only)
|
|
22
|
+
- Arbitrary title page keys (any `Key: Value` pair accepted, not just known fields)
|
|
23
|
+
- Blank-line-before requirement for natural scene headings, character names, and transitions
|
|
24
|
+
- Blank-line-after requirement for transitions
|
|
25
|
+
- Inline note stripping (`[[notes]]` removed from element text in non-note elements)
|
|
26
|
+
- Multi-line note support (`[[note\nspanning\nlines]]`)
|
|
27
|
+
- Dialogue continuation with whitespace-only lines (two spaces preserves blank line in dialogue)
|
|
28
|
+
- Backslash escaping for emphasis markers (`\*` → literal `*`, `\_` → literal `_`)
|
|
29
|
+
- Tabs in action elements are converted to four spaces in the element text at parse time; indentation is preserved in HTML via `white-space: pre-wrap` on `.fountain-action`
|
|
30
|
+
- Inline formatting: bold (`**`), italic (`*`), underline (`_`), bold-italic (`***`)
|
|
31
|
+
|
|
32
|
+
#### Renderers
|
|
33
|
+
- `HTMLRenderer` with three output modes:
|
|
34
|
+
- `render(doc)` — pure HTML fragment for embedding (no `<style>` tags)
|
|
35
|
+
- `render_page(doc)` — standalone HTML with embedded CSS
|
|
36
|
+
- `get_css()` — raw CSS string for external stylesheet use
|
|
37
|
+
- `FountainRenderer` for round-trip conversion back to Fountain markup
|
|
38
|
+
- All CSS classes namespaced with `fountain-` prefix to prevent framework collisions
|
|
39
|
+
- Screenplay-formatted CSS: Courier font, proper margins, centered dialogue, hidden boneyard
|
|
40
|
+
- Dual dialogue side-by-side layout via flexbox
|
|
41
|
+
- Title page rendering with all standard and custom metadata fields
|
|
42
|
+
|
|
43
|
+
#### Document Analysis
|
|
44
|
+
- `FountainDocument` container with element access and metadata
|
|
45
|
+
- `get_characters()` — extract unique character names
|
|
46
|
+
- `get_scenes()` — list scene heading elements
|
|
47
|
+
- `get_statistics()` — element counts by type, character count, scene count
|
|
48
|
+
- `to_html()` — convenience method for standalone HTML output
|
|
49
|
+
|
|
50
|
+
#### Type System
|
|
51
|
+
- Full type hints throughout, strict mypy compliance
|
|
52
|
+
- `FormatType` literal type (`"bold"`, `"italic"`, `"underline"`, `"bold_italic"`)
|
|
53
|
+
- `MetadataValue` union type for element metadata documentation
|
|
54
|
+
- PEP 561 `py.typed` marker for downstream type checking
|
|
55
|
+
|
|
56
|
+
#### Quality
|
|
57
|
+
- 314 tests with 99% code coverage
|
|
58
|
+
- 38 module-level doctests + 447 Sphinx doctests
|
|
59
|
+
- Supports Python 3.10, 3.11, 3.12, 3.13, 3.14
|
|
60
|
+
- Zero runtime dependencies
|
|
61
|
+
- CI with GitHub Actions across all supported Python versions
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Contributing to Fountain-Py
|
|
2
|
+
|
|
3
|
+
We love your input! We want to make contributing to this project as easy and transparent as possible.
|
|
4
|
+
|
|
5
|
+
## Development Process
|
|
6
|
+
|
|
7
|
+
1. Fork the repo and create your branch from `main`
|
|
8
|
+
2. If you've added code that should be tested, add tests
|
|
9
|
+
3. Ensure the test suite passes
|
|
10
|
+
4. Make sure your code follows the style guidelines
|
|
11
|
+
5. Issue that pull request!
|
|
12
|
+
|
|
13
|
+
## Development Setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Clone your fork
|
|
17
|
+
git clone https://github.com/your-username/fountain-py.git
|
|
18
|
+
cd fountain-py
|
|
19
|
+
|
|
20
|
+
# Install development dependencies
|
|
21
|
+
uv sync --dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Running Tests
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Run all tests
|
|
28
|
+
uv run pytest
|
|
29
|
+
|
|
30
|
+
# Run tests with coverage
|
|
31
|
+
uv run pytest --cov=fountain --cov-report=html
|
|
32
|
+
|
|
33
|
+
# Run specific test file
|
|
34
|
+
uv run pytest tests/test_parser.py
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Code Style
|
|
38
|
+
|
|
39
|
+
We use:
|
|
40
|
+
- `ruff` for linting and formatting
|
|
41
|
+
- `mypy` for type checking
|
|
42
|
+
- All code must have type hints
|
|
43
|
+
- Follow PEP8 conventions
|
|
44
|
+
|
|
45
|
+
Run these checks locally:
|
|
46
|
+
```bash
|
|
47
|
+
# Format code
|
|
48
|
+
uv run ruff format src/ tests/
|
|
49
|
+
|
|
50
|
+
# Check linting
|
|
51
|
+
uv run ruff check src/ tests/
|
|
52
|
+
|
|
53
|
+
# Type checking
|
|
54
|
+
uv run mypy src/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Pull Request Process
|
|
58
|
+
|
|
59
|
+
1. Update the README.md with details of changes if needed
|
|
60
|
+
2. Update the CHANGELOG.md with your changes
|
|
61
|
+
3. The PR will be merged once you have the sign-off of at least one maintainer
|
|
62
|
+
|
|
63
|
+
## Reporting Bugs
|
|
64
|
+
|
|
65
|
+
1. Use the GitHub Issues tracker
|
|
66
|
+
2. Include a clear title and description
|
|
67
|
+
3. Provide example code if possible
|
|
68
|
+
4. Include error messages and stack traces
|
|
69
|
+
5. Mention your Python version and OS
|
|
70
|
+
|
|
71
|
+
## Suggesting Features
|
|
72
|
+
|
|
73
|
+
1. Open an issue with the "enhancement" label
|
|
74
|
+
2. Clearly describe the feature and its use case
|
|
75
|
+
3. Provide examples of how it would work
|
|
76
|
+
4. Be open to discussion and feedback
|
|
77
|
+
|
|
78
|
+
## Code of Conduct
|
|
79
|
+
|
|
80
|
+
Please note we have a code of conduct. Follow it in all your interactions with the project.
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
By contributing, you agree that your contributions will be licensed under the MIT License.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mason Egger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fountain-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for parsing Fountain markup, the screenwriting format
|
|
5
|
+
Project-URL: Homepage, https://github.com/MasonEgger/fountain-py
|
|
6
|
+
Project-URL: Repository, https://github.com/MasonEgger/fountain-py
|
|
7
|
+
Project-URL: Documentation, https://masonegger.github.io/fountain-py/
|
|
8
|
+
Project-URL: Issues, https://github.com/MasonEgger/fountain-py/issues
|
|
9
|
+
Author-email: Mason Egger <mason@masonegger.com>
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: fountain,parser,screenplay,screenwriting,script,theater
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Fountain-Py
|
|
27
|
+
|
|
28
|
+
[](https://github.com/MasonEgger/fountain-py/actions?query=workflow%3ACI)
|
|
29
|
+
[](https://www.python.org/downloads/)
|
|
30
|
+
[](https://opensource.org/licenses/MIT)
|
|
31
|
+
[](https://github.com/astral-sh/ruff)
|
|
32
|
+
|
|
33
|
+
A Python library for parsing [Fountain markup](https://fountain.io/), the screenwriting format. Fountain-Py converts Fountain scripts into structured Python objects and can render them as HTML.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **Full Fountain Support**: Parses all Fountain elements including scenes, dialogue, action, transitions, notes, dual dialogue, lyrics, and inline emphasis
|
|
38
|
+
- **Validation**: Reports structural problems (unclosed comments and notes, orphaned character cues) through a validation API
|
|
39
|
+
- **Multiple Render Modes**: HTML fragments for embedding, full pages with CSS, or raw CSS for custom styling
|
|
40
|
+
- **Round-Trip and Export**: Write a parsed script back to clean Fountain, or export it to JSON
|
|
41
|
+
- **Type-Safe**: Built with full type hints and strict mypy compliance
|
|
42
|
+
- **Zero Dependencies**: Pure Python with no runtime dependencies
|
|
43
|
+
- **Well-Tested**: Unit tests and doctests across every module, with high coverage enforced in CI
|
|
44
|
+
- **Modern Python**: Supports Python 3.10 through 3.14
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Installation
|
|
49
|
+
|
|
50
|
+
Fountain-Py is not on PyPI yet (the 0.1.0 release is pending), so install it from source:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install git+https://github.com/MasonEgger/fountain-py.git
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Once it is published, `pip install fountain-py` will work.
|
|
57
|
+
|
|
58
|
+
### Basic Usage
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from fountain import FountainParser
|
|
62
|
+
from fountain.renderer import HTMLRenderer
|
|
63
|
+
|
|
64
|
+
# Parse a Fountain script
|
|
65
|
+
parser = FountainParser()
|
|
66
|
+
document = parser.parse("""Title: My Screenplay
|
|
67
|
+
Author: Jane Writer
|
|
68
|
+
|
|
69
|
+
INT. COFFEE SHOP - DAY
|
|
70
|
+
|
|
71
|
+
SARAH enters, looking tired.
|
|
72
|
+
|
|
73
|
+
SARAH
|
|
74
|
+
One large cappuccino, please!
|
|
75
|
+
""")
|
|
76
|
+
|
|
77
|
+
# Access parsed data
|
|
78
|
+
print(document.metadata["title"]) # "My Screenplay"
|
|
79
|
+
print(document.get_characters()) # ["SARAH"]
|
|
80
|
+
print(len(document.elements)) # 3
|
|
81
|
+
|
|
82
|
+
# Render as HTML fragment (for embedding in web pages)
|
|
83
|
+
renderer = HTMLRenderer()
|
|
84
|
+
html_fragment = renderer.render(document)
|
|
85
|
+
|
|
86
|
+
# Render as standalone HTML file with embedded CSS
|
|
87
|
+
html_page = renderer.render_page(document)
|
|
88
|
+
|
|
89
|
+
# Get raw CSS for external stylesheets
|
|
90
|
+
css = renderer.get_css()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Rendering Modes
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
renderer = HTMLRenderer()
|
|
97
|
+
|
|
98
|
+
# Fragment — no <style> tags, just the screenplay markup
|
|
99
|
+
# Use this for embedding in web pages, docs, or CMS systems
|
|
100
|
+
fragment = renderer.render(document)
|
|
101
|
+
|
|
102
|
+
# Full page — self-contained HTML with embedded CSS
|
|
103
|
+
# Use this for saving as .html files or previewing
|
|
104
|
+
page = renderer.render_page(document)
|
|
105
|
+
|
|
106
|
+
# Raw CSS — for custom stylesheet integration
|
|
107
|
+
# Use this with mkdocs, static site generators, or your own build pipeline
|
|
108
|
+
css = renderer.get_css()
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Round-Trip Conversion
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from fountain.renderer import FountainRenderer
|
|
115
|
+
|
|
116
|
+
# Convert back to Fountain markup
|
|
117
|
+
fountain_renderer = FountainRenderer()
|
|
118
|
+
fountain_text = fountain_renderer.render(document)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The round trip preserves element structure and inline emphasis.
|
|
122
|
+
Scene headings, action, character cues, parentheticals, dialogue, transitions, dual dialogue, lyrics, sections, synopses, and notes all keep their element types through `parse(render(parse(text)))`, and the blank lines that separate structural blocks survive so blocks are not merged on re-parse.
|
|
123
|
+
|
|
124
|
+
Inline emphasis is re-emitted too.
|
|
125
|
+
The parser records the bold (`**`), italic (`*`), and underline (`_`) delimiters as formatting spans, and `FountainRenderer` restores them, so a `**bold**` word round-trips as `**bold**`, including nested emphasis and backslash-escaped literals.
|
|
126
|
+
|
|
127
|
+
## Documentation
|
|
128
|
+
|
|
129
|
+
Full documentation is available at [masonegger.github.io/fountain-py](https://masonegger.github.io/fountain-py/).
|
|
130
|
+
|
|
131
|
+
- [Installation Guide](https://masonegger.github.io/fountain-py/installation.html)
|
|
132
|
+
- [Quick Start Tutorial](https://masonegger.github.io/fountain-py/quickstart.html)
|
|
133
|
+
- [API Reference](https://masonegger.github.io/fountain-py/api/index.html)
|
|
134
|
+
|
|
135
|
+
## Development
|
|
136
|
+
|
|
137
|
+
### Setup
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
git clone https://github.com/MasonEgger/fountain-py.git
|
|
141
|
+
cd fountain-py
|
|
142
|
+
|
|
143
|
+
# Install with development and docs dependencies
|
|
144
|
+
just dev && uv sync --group docs
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Running Tests
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Run comprehensive quality checks (tests, coverage, doctests, lint, type check)
|
|
151
|
+
just test
|
|
152
|
+
|
|
153
|
+
# Run only unit tests
|
|
154
|
+
just unit-test
|
|
155
|
+
|
|
156
|
+
# Run tests with coverage
|
|
157
|
+
just unit-test-cov
|
|
158
|
+
|
|
159
|
+
# Run specific tests
|
|
160
|
+
uv run pytest tests/test_parser.py
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Code Quality
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
just lint # Lint check (ruff)
|
|
167
|
+
just format # Format code (ruff)
|
|
168
|
+
just type-check # Type checking (mypy strict)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Contributing
|
|
172
|
+
|
|
173
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
174
|
+
|
|
175
|
+
## Fountain Format
|
|
176
|
+
|
|
177
|
+
Fountain is a simple markup syntax for writing, editing and sharing screenplays in plain text. Learn more at [fountain.io](https://fountain.io/).
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
182
|
+
|
|
183
|
+
## Acknowledgments
|
|
184
|
+
|
|
185
|
+
- The [Fountain](https://fountain.io/) format creators
|
|
186
|
+
- The screenwriting community for feedback and testing
|