# Prism
A Python code formatter that enforces configurable blank line rules.
## Overview
Prism is a code formatting tool that intelligently manages blank lines in Python code, similar to how `black` handles code formatting. It applies sophisticated rules to ensure consistent spacing between different types of code blocks while preserving your code's logical structure and documentation.
## Features
- **Configurable blank line rules** - Customize spacing between different code block types
- **Smart block detection** - Identifies assignments, function calls, imports, control structures, definitions, and more
- **Multiline statement support** - Properly handles statements spanning multiple lines
- **Docstring preservation** - Never modifies content within docstrings
- **Nested scope handling** - Applies rules independently at each indentation level
- **Comment-aware processing** - Preserves existing spacing around comment blocks
- **Atomic file operations** - Safe file writing with automatic rollback on errors
- **Change detection** - Only modifies files that need formatting
- **Dry-run mode** - Preview changes without modifying files
- **Check mode** - Verify formatting without making changes
## Installation
### From Source
```bash
git clone https://github.com/yourusername/prism.git
cd prism
pip install -e .
```
### Requirements
- Python 3.11 or higher
- No external dependencies for core functionality
## Quick Start
```bash
# Format a single file
prism myfile.py
# Format all Python files in a directory
prism src/
# Check if files need formatting (exit 1 if changes needed)
prism --check myfile.py
# Preview changes without applying them
prism --dry-run myfile.py
# Show detailed processing information
prism --verbose myfile.py
# Show version
prism --version
```
## Configuration
### Default Behavior
By default, prism uses these rules (aligned with PEP 8):
- **1 blank line** between different block types
- **1 blank line** between consecutive control structures (`if`, `for`, `while`, `try`, etc.)
- **2 blank lines** between consecutive top-level (module level) function/class definitions
- **1 blank line** between consecutive method definitions inside classes
- **0 blank lines** between statements of the same type
- Exception: **1 blank line** between consecutive control blocks at the same scope
### Configuration File
Create a `prism.toml` file in your project root to customize blank line rules:
```toml
[blank_lines]
# Default spacing between different block types (0-3 blank lines)
default_between_different = 1
# Spacing between consecutive control blocks (if, for, while, try, with)
consecutive_control = 1
# Spacing between consecutive definitions (def, class)
consecutive_definition = 1
# Indent width for indentation detection (default: 2 spaces)
indent_width = 2
# Fine-grained transition overrides
# Format: <from_block>_to_<to_block> = <count>
assignment_to_call = 2
call_to_assignment = 2
import_to_assignment = 0
control_to_definition = 2
```
### Block Types
Prism recognizes these code block types (in precedence order):
1. **`assignment`** - Variable assignments, list/dict comprehensions, lambda expressions
```python
x = 42
items = [i for i in range(10)]
func = lambda x: x * 2
```
2. **`call`** - Function/method calls, `del`, `assert`, `pass`, `raise`, `yield`, `return`
```python
print('hello')
someFunction()
return result
```
3. **`import`** - Import statements
```python
import os
from pathlib import Path
```
4. **`control`** - Control structures (`if`/`elif`/`else`, `for`/`else`, `while`/`else`, `try`/`except`/`finally`, `with`)
```python
if condition:
x = 1
y = 0
for item in items:
prologue(item)
process(item)
epilogue(item)
```
5. **`definition`** - Function and class definitions
```python
def myFunction():
pass
class MyClass:
pass
```
6. **`declaration`** - `global` and `nonlocal` statements
```python
global myVar
nonlocal count
```
7. **`comment`** - Comment lines
```python
# This is a comment
```
### Configuration Examples
#### Minimal spacing (compact style)
```toml
[blank_lines]
default_between_different = 0
consecutive_control = 1
consecutive_definition = 1
```
#### Extra spacing (airy style)
```toml
[blank_lines]
default_between_different = 2
consecutive_control = 2
consecutive_definition = 2
```
#### Custom transitions
```toml
[blank_lines]
# Default: 1 blank line between different types
default_between_different = 1
# But no blank lines between imports and assignments
import_to_assignment = 0
# And 2 blank lines between import blocks and definitions such as a `class`
import_to_definition = 2
```
### Using Custom Configuration
```bash
# Use a specific config file
prism --config custom.toml myfile.py
# Use default configuration (ignore prism.toml if it exists)
prism --no-config myfile.py
```
## Block Classification Rules
### Precedence
When a statement could match multiple block types, prism uses precedence:
```python
x = someFunction() # Assignment (precedence over Call)
result = [i for i in range(10)] # Assignment (comprehension)
```
### Multiline Statements
Multiline statements are classified as a single unit:
```python
result = complexFunction(
arg1,
arg2,
arg3
) # Entire statement is classified as Assignment
```
### Docstrings
Docstring content is never modified - all internal formatting, blank lines, and special characters are preserved exactly:
```python
def example():
"""
This docstring content is preserved exactly.
# This is NOT treated as a comment
All blank lines inside are preserved.
"""
pass
```
## Comment Handling
Prism has special rules for comments:
1. **Consecutive comments** - No blank lines inserted between comment lines
```python
# Copyright header line 1
# Copyright header line 2
# Copyright header line 3
```
2. **Comment breaks** - Blank line added before a comment (unless previous line was also a comment)
```python
x = 1
# This comment gets a blank line before it
y = 2
```
3. **After comments** - Existing spacing preserved (leave-as-is policy)
```python
# Comment
import os # Existing blank line preserved
# Comment
x = 1 # No blank line (preserved)
```
## Scope and Blank Lines
Prism applies rules independent of scope:
```python
def outer():
x = 1
y = 0
z = 0
print('Level 1')
def inner():
y += 1
print('Level 2')
if condition:
z += 1
```
Rules are applied separately for:
- Module level (indent 0)
- Inside `outer()` function (indent 2)
- Inside `inner()` function (indent 4)
- Inside `if` block (indent 6)
## Exit Codes
- **0** - Success: No changes needed or changes applied successfully
- **1** - Failure: Changes needed (in `--check` mode) or processing error occurred
## Integration
### Pre-commit Hook
Add to `.pre-commit-config.yaml`:
```yaml
repos:
- repo: local
hooks:
- id: prism
name: prism
entry: prism
language: system
types: [python]
```
### CI/CD
```bash
# Check formatting in CI
prism --check src/
if [ $? -ne 0 ]; then
echo "Code needs formatting. Run: prism src/"
exit 1
fi
```
### Editor Integration
Most editors can be configured to run prism on save or as a format command.
## Examples
### Before and After
**Before:**
```python
import os
import sys
def main():
x = 1
y = 2
if x > 0:
print(x)
else:
print(y)
for i in range(10):
process(i)
class Helper:
pass
```
**After (with default config):**
```python
import os
import sys
x = 1
y = 2
if x > 0:
print(x)
else:
print(y)
for i in range(10):
process(i)
class Helper:
pass
```
## Comparison with Other Tools
| Feature | Prism | Black | Ruff |
|-------------------------|-----------------|-----------------------|-----------------------|
| Blank line rules | ✅ Configurable | ✅ Fixed | ✅ Fixed |
| Scope-aware spacing | ✅ Yes | ⚠️ Limited | ⚠️ Limited |
| Indentation handling | ✅ Configurable | ⚠️ Enforces/reformats | ⚠️ Enforces/reformats |
**Prism's Focus**: Prism solves **one problem exceptionally well** - scope-aware, configurable blank line enforcement. This is a unique capability that Black and Ruff don't provide comprehensively.
**Key Differentiators**:
- **Configurable blank line rules** - Control spacing between any block type transition
- **Independent scope-level processing** - Rules applied within each scope equally
- **Works with your indentation** - Detects existing style, never reformats it
**Philosophy**: Prism is designed to work **alongside** Black or Ruff, not replace them. Use Black/Ruff for general formatting (line length, quotes, imports) and Prism for blank line intelligence.
## Troubleshooting
### Files Not Being Modified
1. Check if files already match the rules: `prism --check file.py`
2. Use verbose mode to see what's happening: `prism --verbose file.py`
3. Verify your configuration: check `prism.toml` syntax
### Unexpected Blank Lines
1. Review your configuration file (`prism.toml`)
2. Use `--dry-run` to preview changes: `prism --dry-run file.py`
3. Check for comment blocks that may trigger special rules
4. Verify indentation consistency (tabs vs spaces)
### Configuration Not Being Applied
1. Ensure `prism.toml` is in the current directory or specify with `--config`
2. Check TOML syntax is valid
3. Verify values are in valid range (0-3)
4. Check block type names match documentation
## Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for any new functionality
4. Ensure all tests pass: `pytest`
5. Run code quality checks: `ruff check` and `ruff format`
6. Submit a pull request
## License
See the LICENSE file for details.
## Acknowledgments
Prism was inspired by the philosophy of tools like Black and Ruff - that automated formatting allows developers to focus on logic rather than style.
Raw data
{
"_id": null,
"home_page": null,
"name": "prism-blanklines",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "blank-lines, code-quality, formatter, python",
"author": "Greg Smethells",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/c3/e5/45b2617d8903ac24651f1c1f49e40c8b244642aab8dae82dc9c1f89e7541/prism_blanklines-0.4.2.tar.gz",
"platform": null,
"description": "# Prism\n\nA Python code formatter that enforces configurable blank line rules.\n\n## Overview\n\nPrism is a code formatting tool that intelligently manages blank lines in Python code, similar to how `black` handles code formatting. It applies sophisticated rules to ensure consistent spacing between different types of code blocks while preserving your code's logical structure and documentation.\n\n## Features\n\n- **Configurable blank line rules** - Customize spacing between different code block types\n- **Smart block detection** - Identifies assignments, function calls, imports, control structures, definitions, and more\n- **Multiline statement support** - Properly handles statements spanning multiple lines\n- **Docstring preservation** - Never modifies content within docstrings\n- **Nested scope handling** - Applies rules independently at each indentation level\n- **Comment-aware processing** - Preserves existing spacing around comment blocks\n- **Atomic file operations** - Safe file writing with automatic rollback on errors\n- **Change detection** - Only modifies files that need formatting\n- **Dry-run mode** - Preview changes without modifying files\n- **Check mode** - Verify formatting without making changes\n\n## Installation\n\n### From Source\n\n```bash\ngit clone https://github.com/yourusername/prism.git\ncd prism\npip install -e .\n```\n\n### Requirements\n\n- Python 3.11 or higher\n- No external dependencies for core functionality\n\n## Quick Start\n\n```bash\n# Format a single file\nprism myfile.py\n\n# Format all Python files in a directory\nprism src/\n\n# Check if files need formatting (exit 1 if changes needed)\nprism --check myfile.py\n\n# Preview changes without applying them\nprism --dry-run myfile.py\n\n# Show detailed processing information\nprism --verbose myfile.py\n\n# Show version\nprism --version\n```\n\n## Configuration\n\n### Default Behavior\n\nBy default, prism uses these rules (aligned with PEP 8):\n- **1 blank line** between different block types\n- **1 blank line** between consecutive control structures (`if`, `for`, `while`, `try`, etc.)\n- **2 blank lines** between consecutive top-level (module level) function/class definitions\n- **1 blank line** between consecutive method definitions inside classes\n- **0 blank lines** between statements of the same type\n - Exception: **1 blank line** between consecutive control blocks at the same scope \n\n### Configuration File\n\nCreate a `prism.toml` file in your project root to customize blank line rules:\n\n```toml\n[blank_lines]\n# Default spacing between different block types (0-3 blank lines)\ndefault_between_different = 1\n\n# Spacing between consecutive control blocks (if, for, while, try, with)\nconsecutive_control = 1\n\n# Spacing between consecutive definitions (def, class)\nconsecutive_definition = 1\n\n# Indent width for indentation detection (default: 2 spaces)\nindent_width = 2\n\n# Fine-grained transition overrides\n# Format: <from_block>_to_<to_block> = <count>\nassignment_to_call = 2\ncall_to_assignment = 2\nimport_to_assignment = 0\ncontrol_to_definition = 2\n```\n\n### Block Types\n\nPrism recognizes these code block types (in precedence order):\n\n1. **`assignment`** - Variable assignments, list/dict comprehensions, lambda expressions\n ```python\n x = 42\n items = [i for i in range(10)]\n func = lambda x: x * 2\n ```\n\n2. **`call`** - Function/method calls, `del`, `assert`, `pass`, `raise`, `yield`, `return`\n ```python\n print('hello')\n someFunction()\n return result\n ```\n\n3. **`import`** - Import statements\n ```python\n import os\n from pathlib import Path\n ```\n\n4. **`control`** - Control structures (`if`/`elif`/`else`, `for`/`else`, `while`/`else`, `try`/`except`/`finally`, `with`)\n ```python\n if condition:\n x = 1\n y = 0\n\n for item in items:\n prologue(item)\n process(item)\n epilogue(item)\n\n ```\n\n5. **`definition`** - Function and class definitions\n ```python\n def myFunction():\n pass\n\n class MyClass:\n pass\n ```\n\n6. **`declaration`** - `global` and `nonlocal` statements\n ```python\n global myVar\n nonlocal count\n ```\n\n7. **`comment`** - Comment lines\n ```python\n # This is a comment\n ```\n\n### Configuration Examples\n\n#### Minimal spacing (compact style)\n```toml\n[blank_lines]\ndefault_between_different = 0\nconsecutive_control = 1\nconsecutive_definition = 1\n```\n\n#### Extra spacing (airy style)\n```toml\n[blank_lines]\ndefault_between_different = 2\nconsecutive_control = 2\nconsecutive_definition = 2\n```\n\n#### Custom transitions\n```toml\n[blank_lines]\n# Default: 1 blank line between different types\ndefault_between_different = 1\n\n# But no blank lines between imports and assignments\nimport_to_assignment = 0\n\n# And 2 blank lines between import blocks and definitions such as a `class`\nimport_to_definition = 2\n```\n\n### Using Custom Configuration\n\n```bash\n# Use a specific config file\nprism --config custom.toml myfile.py\n\n# Use default configuration (ignore prism.toml if it exists)\nprism --no-config myfile.py\n```\n\n## Block Classification Rules\n\n### Precedence\n\nWhen a statement could match multiple block types, prism uses precedence:\n\n```python\nx = someFunction() # Assignment (precedence over Call)\nresult = [i for i in range(10)] # Assignment (comprehension)\n```\n\n### Multiline Statements\n\nMultiline statements are classified as a single unit:\n\n```python\nresult = complexFunction(\n arg1,\n arg2,\n arg3\n) # Entire statement is classified as Assignment\n```\n\n### Docstrings\n\nDocstring content is never modified - all internal formatting, blank lines, and special characters are preserved exactly:\n\n```python\ndef example():\n \"\"\"\n This docstring content is preserved exactly.\n\n # This is NOT treated as a comment\n\n All blank lines inside are preserved.\n \"\"\"\n pass\n```\n\n## Comment Handling\n\nPrism has special rules for comments:\n\n1. **Consecutive comments** - No blank lines inserted between comment lines\n ```python\n # Copyright header line 1\n # Copyright header line 2\n # Copyright header line 3\n ```\n\n2. **Comment breaks** - Blank line added before a comment (unless previous line was also a comment)\n ```python\n x = 1\n\n # This comment gets a blank line before it\n y = 2\n ```\n\n3. **After comments** - Existing spacing preserved (leave-as-is policy)\n ```python\n # Comment\n\n import os # Existing blank line preserved\n\n # Comment\n x = 1 # No blank line (preserved)\n ```\n\n## Scope and Blank Lines\n\nPrism applies rules independent of scope:\n\n```python\ndef outer():\n x = 1\n y = 0\n z = 0\n\n print('Level 1')\n\n def inner():\n y += 1\n\n print('Level 2')\n\n if condition:\n z += 1\n```\n\nRules are applied separately for:\n- Module level (indent 0)\n- Inside `outer()` function (indent 2)\n- Inside `inner()` function (indent 4)\n- Inside `if` block (indent 6)\n\n## Exit Codes\n\n- **0** - Success: No changes needed or changes applied successfully\n- **1** - Failure: Changes needed (in `--check` mode) or processing error occurred\n\n## Integration\n\n### Pre-commit Hook\n\nAdd to `.pre-commit-config.yaml`:\n\n```yaml\nrepos:\n - repo: local\n hooks:\n - id: prism\n name: prism\n entry: prism\n language: system\n types: [python]\n```\n\n### CI/CD\n\n```bash\n# Check formatting in CI\nprism --check src/\nif [ $? -ne 0 ]; then\n echo \"Code needs formatting. Run: prism src/\"\n exit 1\nfi\n```\n\n### Editor Integration\n\nMost editors can be configured to run prism on save or as a format command.\n\n## Examples\n\n### Before and After\n\n**Before:**\n```python\nimport os\nimport sys\ndef main():\n x = 1\n y = 2\n if x > 0:\n print(x)\n else:\n print(y)\n for i in range(10):\n process(i)\n class Helper:\n pass\n```\n\n**After (with default config):**\n```python\nimport os\nimport sys\n\nx = 1\ny = 2\n\nif x > 0:\n print(x)\nelse:\n print(y)\n\nfor i in range(10):\n process(i)\n\nclass Helper:\n pass\n```\n\n## Comparison with Other Tools\n\n| Feature | Prism | Black | Ruff |\n|-------------------------|-----------------|-----------------------|-----------------------|\n| Blank line rules | \u2705 Configurable | \u2705 Fixed | \u2705 Fixed |\n| Scope-aware spacing | \u2705 Yes | \u26a0\ufe0f Limited | \u26a0\ufe0f Limited |\n| Indentation handling | \u2705 Configurable | \u26a0\ufe0f Enforces/reformats | \u26a0\ufe0f Enforces/reformats |\n\n**Prism's Focus**: Prism solves **one problem exceptionally well** - scope-aware, configurable blank line enforcement. This is a unique capability that Black and Ruff don't provide comprehensively.\n\n**Key Differentiators**:\n- **Configurable blank line rules** - Control spacing between any block type transition\n- **Independent scope-level processing** - Rules applied within each scope equally\n- **Works with your indentation** - Detects existing style, never reformats it\n\n**Philosophy**: Prism is designed to work **alongside** Black or Ruff, not replace them. Use Black/Ruff for general formatting (line length, quotes, imports) and Prism for blank line intelligence.\n\n## Troubleshooting\n\n### Files Not Being Modified\n\n1. Check if files already match the rules: `prism --check file.py`\n2. Use verbose mode to see what's happening: `prism --verbose file.py`\n3. Verify your configuration: check `prism.toml` syntax\n\n### Unexpected Blank Lines\n\n1. Review your configuration file (`prism.toml`)\n2. Use `--dry-run` to preview changes: `prism --dry-run file.py`\n3. Check for comment blocks that may trigger special rules\n4. Verify indentation consistency (tabs vs spaces)\n\n### Configuration Not Being Applied\n\n1. Ensure `prism.toml` is in the current directory or specify with `--config`\n2. Check TOML syntax is valid\n3. Verify values are in valid range (0-3)\n4. Check block type names match documentation\n\n## Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for any new functionality\n4. Ensure all tests pass: `pytest`\n5. Run code quality checks: `ruff check` and `ruff format`\n6. Submit a pull request\n\n## License\n\nSee the LICENSE file for details.\n\n## Acknowledgments\n\nPrism was inspired by the philosophy of tools like Black and Ruff - that automated formatting allows developers to focus on logic rather than style.\n",
"bugtrack_url": null,
"license": "GPL-3.0-or-later",
"summary": "Blank line enforcement for Python code based on CLAUDE.md rules",
"version": "0.4.2",
"project_urls": {
"Issues": "https://github.com/gsmethells/prism/issues",
"Repository": "https://github.com/gsmethells/prism"
},
"split_keywords": [
"blank-lines",
" code-quality",
" formatter",
" python"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "93998440909277029e3a1bd87fdf94f4c816038468939b41919483cce4754ea4",
"md5": "6facb448f2771b2ca8e3b112dd1b0dff",
"sha256": "ef3c45295cdbd5010eb80e0fd1909f7d413089d62653a6978a089b8587cb9006"
},
"downloads": -1,
"filename": "prism_blanklines-0.4.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6facb448f2771b2ca8e3b112dd1b0dff",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 34129,
"upload_time": "2025-10-17T01:58:18",
"upload_time_iso_8601": "2025-10-17T01:58:18.846500Z",
"url": "https://files.pythonhosted.org/packages/93/99/8440909277029e3a1bd87fdf94f4c816038468939b41919483cce4754ea4/prism_blanklines-0.4.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c3e545b2617d8903ac24651f1c1f49e40c8b244642aab8dae82dc9c1f89e7541",
"md5": "034a7b2b7df6bee52ffe21007ba1b832",
"sha256": "b9ef506672816e291362ea6182b48d1f67f09ba1f9e18dd2dac190c9b9af9832"
},
"downloads": -1,
"filename": "prism_blanklines-0.4.2.tar.gz",
"has_sig": false,
"md5_digest": "034a7b2b7df6bee52ffe21007ba1b832",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 58555,
"upload_time": "2025-10-17T01:58:19",
"upload_time_iso_8601": "2025-10-17T01:58:19.724523Z",
"url": "https://files.pythonhosted.org/packages/c3/e5/45b2617d8903ac24651f1c1f49e40c8b244642aab8dae82dc9c1f89e7541/prism_blanklines-0.4.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-17 01:58:19",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "gsmethells",
"github_project": "prism",
"travis_ci": false,
"coveralls": true,
"github_actions": false,
"test_requirements": [
{
"name": "coverage",
"specs": [
[
">=",
"5.3"
]
]
},
{
"name": "pre-commit",
"specs": []
},
{
"name": "pytest",
"specs": [
[
">=",
"6.1.1"
]
]
},
{
"name": "pytest-xdist",
"specs": [
[
"<",
"3.0.2"
],
[
">=",
"2.2.1"
]
]
},
{
"name": "pytest-cov",
"specs": [
[
">=",
"2.11.1"
]
]
},
{
"name": "tox",
"specs": []
}
],
"lcname": "prism-blanklines"
}