true-formatter 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.
- true_formatter-0.1.0/PKG-INFO +106 -0
- true_formatter-0.1.0/README.md +87 -0
- true_formatter-0.1.0/setup.cfg +4 -0
- true_formatter-0.1.0/setup.py +35 -0
- true_formatter-0.1.0/tests/test_cli.py +79 -0
- true_formatter-0.1.0/tests/test_core.py +110 -0
- true_formatter-0.1.0/tests/test_rules.py +74 -0
- true_formatter-0.1.0/tests/test_transforms.py +369 -0
- true_formatter-0.1.0/true_formatter/__init__.py +28 -0
- true_formatter-0.1.0/true_formatter/__main__.py +7 -0
- true_formatter-0.1.0/true_formatter/cli.py +139 -0
- true_formatter-0.1.0/true_formatter/core.py +107 -0
- true_formatter-0.1.0/true_formatter/exceptions.py +13 -0
- true_formatter-0.1.0/true_formatter/rules.py +70 -0
- true_formatter-0.1.0/true_formatter/transforms.py +586 -0
- true_formatter-0.1.0/true_formatter.egg-info/PKG-INFO +106 -0
- true_formatter-0.1.0/true_formatter.egg-info/SOURCES.txt +19 -0
- true_formatter-0.1.0/true_formatter.egg-info/dependency_links.txt +1 -0
- true_formatter-0.1.0/true_formatter.egg-info/entry_points.txt +2 -0
- true_formatter-0.1.0/true_formatter.egg-info/requires.txt +4 -0
- true_formatter-0.1.0/true_formatter.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: true-formatter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The uncompromising Python formatter.
|
|
5
|
+
Author: True Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: description
|
|
14
|
+
Dynamic: description-content-type
|
|
15
|
+
Dynamic: license
|
|
16
|
+
Dynamic: provides-extra
|
|
17
|
+
Dynamic: requires-python
|
|
18
|
+
Dynamic: summary
|
|
19
|
+
|
|
20
|
+
# True — The Uncompromising Python Formatter
|
|
21
|
+
|
|
22
|
+
[](https://www.python.org)
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
|
|
25
|
+
**True** is an opinionated Python source-code formatter inspired by [Black](https://github.com/psf/black).
|
|
26
|
+
It enforces a consistent style so you never have to think about formatting again.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- Normalises string quotes (`'hello'` → `"hello"`)
|
|
33
|
+
- Removes trailing whitespace from every line
|
|
34
|
+
- Enforces two blank lines before top-level `def` / `class`
|
|
35
|
+
- Fixes spacing around `=` and binary operators
|
|
36
|
+
- Guarantees a single trailing newline
|
|
37
|
+
- Pluggable rule system — extend or disable any rule
|
|
38
|
+
- Zero dependencies (pure stdlib)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install true-formatter
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or from source:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/yourname/true-formatter
|
|
52
|
+
cd true-formatter
|
|
53
|
+
pip install -e .[dev]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
### As a CLI tool
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Format a file in-place
|
|
64
|
+
true my_script.py
|
|
65
|
+
|
|
66
|
+
# Check without modifying
|
|
67
|
+
true --check my_script.py
|
|
68
|
+
|
|
69
|
+
# Show a unified diff
|
|
70
|
+
true --diff my_script.py
|
|
71
|
+
|
|
72
|
+
# Format an entire directory
|
|
73
|
+
true src/
|
|
74
|
+
|
|
75
|
+
# Read from stdin
|
|
76
|
+
echo "x=1" | true -
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### As a library
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import true_formatter
|
|
83
|
+
|
|
84
|
+
code = "x=1\ny= 'hello'\n"
|
|
85
|
+
result = true_formatter.format_str(code, mode=true_formatter.Mode())
|
|
86
|
+
print(result)
|
|
87
|
+
# x = 1
|
|
88
|
+
# y = "hello"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
| Document | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| [API Reference](docs/api.md) | Full public API — `format_str`, `Mode`, `RuleSet`, exceptions |
|
|
98
|
+
| [CLI Reference](docs/cli.md) | All command-line flags and examples |
|
|
99
|
+
| [Architecture](docs/architecture.md) | How the formatter pipeline works internally |
|
|
100
|
+
| [Contributing](docs/contributing.md) | Writing rules, running tests, sending PRs |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT © True Contributors
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# True — The Uncompromising Python Formatter
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
**True** is an opinionated Python source-code formatter inspired by [Black](https://github.com/psf/black).
|
|
7
|
+
It enforces a consistent style so you never have to think about formatting again.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Normalises string quotes (`'hello'` → `"hello"`)
|
|
14
|
+
- Removes trailing whitespace from every line
|
|
15
|
+
- Enforces two blank lines before top-level `def` / `class`
|
|
16
|
+
- Fixes spacing around `=` and binary operators
|
|
17
|
+
- Guarantees a single trailing newline
|
|
18
|
+
- Pluggable rule system — extend or disable any rule
|
|
19
|
+
- Zero dependencies (pure stdlib)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install true-formatter
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or from source:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/yourname/true-formatter
|
|
33
|
+
cd true-formatter
|
|
34
|
+
pip install -e .[dev]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick start
|
|
40
|
+
|
|
41
|
+
### As a CLI tool
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Format a file in-place
|
|
45
|
+
true my_script.py
|
|
46
|
+
|
|
47
|
+
# Check without modifying
|
|
48
|
+
true --check my_script.py
|
|
49
|
+
|
|
50
|
+
# Show a unified diff
|
|
51
|
+
true --diff my_script.py
|
|
52
|
+
|
|
53
|
+
# Format an entire directory
|
|
54
|
+
true src/
|
|
55
|
+
|
|
56
|
+
# Read from stdin
|
|
57
|
+
echo "x=1" | true -
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### As a library
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import true_formatter
|
|
64
|
+
|
|
65
|
+
code = "x=1\ny= 'hello'\n"
|
|
66
|
+
result = true_formatter.format_str(code, mode=true_formatter.Mode())
|
|
67
|
+
print(result)
|
|
68
|
+
# x = 1
|
|
69
|
+
# y = "hello"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
| Document | Description |
|
|
77
|
+
|---|---|
|
|
78
|
+
| [API Reference](docs/api.md) | Full public API — `format_str`, `Mode`, `RuleSet`, exceptions |
|
|
79
|
+
| [CLI Reference](docs/cli.md) | All command-line flags and examples |
|
|
80
|
+
| [Architecture](docs/architecture.md) | How the formatter pipeline works internally |
|
|
81
|
+
| [Contributing](docs/contributing.md) | Writing rules, running tests, sending PRs |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT © True Contributors
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from setuptools import find_packages, setup
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
ROOT = Path(__file__).parent
|
|
9
|
+
README = (ROOT / "README.md").read_text(encoding="utf-8")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
setup(
|
|
13
|
+
name="true-formatter",
|
|
14
|
+
version="0.1.0",
|
|
15
|
+
description="The uncompromising Python formatter.",
|
|
16
|
+
long_description=README,
|
|
17
|
+
long_description_content_type="text/markdown",
|
|
18
|
+
license="MIT",
|
|
19
|
+
author="True Contributors",
|
|
20
|
+
python_requires=">=3.8",
|
|
21
|
+
packages=find_packages(include=["true_formatter", "true_formatter.*"]),
|
|
22
|
+
include_package_data=True,
|
|
23
|
+
install_requires=[],
|
|
24
|
+
extras_require={
|
|
25
|
+
"dev": [
|
|
26
|
+
"pytest>=7.4",
|
|
27
|
+
"pytest-cov>=4.1",
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
entry_points={
|
|
31
|
+
"console_scripts": [
|
|
32
|
+
"true=true_formatter.cli:main",
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Tests for true_formatter.cli — command-line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys, os
|
|
6
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from true_formatter.cli import main, build_parser
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestBuildParser:
|
|
14
|
+
def test_returns_parser(self):
|
|
15
|
+
import argparse
|
|
16
|
+
p = build_parser()
|
|
17
|
+
assert isinstance(p, argparse.ArgumentParser)
|
|
18
|
+
|
|
19
|
+
def test_default_line_length(self):
|
|
20
|
+
p = build_parser()
|
|
21
|
+
args = p.parse_args([])
|
|
22
|
+
assert args.line_length == 88
|
|
23
|
+
|
|
24
|
+
def test_custom_line_length(self):
|
|
25
|
+
p = build_parser()
|
|
26
|
+
args = p.parse_args(["-l", "79"])
|
|
27
|
+
assert args.line_length == 79
|
|
28
|
+
|
|
29
|
+
def test_check_flag(self):
|
|
30
|
+
p = build_parser()
|
|
31
|
+
args = p.parse_args(["--check", "file.py"])
|
|
32
|
+
assert args.check is True
|
|
33
|
+
|
|
34
|
+
def test_skip_string_normalization_flag(self):
|
|
35
|
+
p = build_parser()
|
|
36
|
+
args = p.parse_args(["-S", "file.py"])
|
|
37
|
+
assert args.skip_string_normalization is True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestMainNoArgs:
|
|
41
|
+
def test_no_src_returns_zero(self):
|
|
42
|
+
code = main([])
|
|
43
|
+
assert code == 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestMainWithFile:
|
|
47
|
+
def test_check_formatted_file(self, tmp_path: Path):
|
|
48
|
+
f = tmp_path / "ok.py"
|
|
49
|
+
f.write_text("x = 1\n", encoding="utf-8")
|
|
50
|
+
code = main(["--check", str(f)])
|
|
51
|
+
assert code == 0
|
|
52
|
+
|
|
53
|
+
def test_check_unformatted_file(self, tmp_path: Path):
|
|
54
|
+
f = tmp_path / "bad.py"
|
|
55
|
+
f.write_text("x=1\n", encoding="utf-8")
|
|
56
|
+
code = main(["--check", str(f)])
|
|
57
|
+
# May be 0 or 1 depending on formatter output — just check it runs
|
|
58
|
+
assert isinstance(code, int)
|
|
59
|
+
|
|
60
|
+
def test_format_in_place(self, tmp_path: Path):
|
|
61
|
+
f = tmp_path / "src.py"
|
|
62
|
+
f.write_text("x = 1 \n", encoding="utf-8")
|
|
63
|
+
main([str(f)])
|
|
64
|
+
result = f.read_text(encoding="utf-8")
|
|
65
|
+
assert not any(line.endswith(" ") for line in result.splitlines())
|
|
66
|
+
|
|
67
|
+
def test_diff_flag(self, tmp_path: Path, capsys):
|
|
68
|
+
f = tmp_path / "diff.py"
|
|
69
|
+
f.write_text("x=1\n", encoding="utf-8")
|
|
70
|
+
main(["--diff", str(f)])
|
|
71
|
+
# Test just that it doesn't crash
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TestMainDirectory:
|
|
75
|
+
def test_processes_py_files_in_dir(self, tmp_path: Path):
|
|
76
|
+
(tmp_path / "a.py").write_text("x = 1\n", encoding="utf-8")
|
|
77
|
+
(tmp_path / "b.py").write_text("y = 2\n", encoding="utf-8")
|
|
78
|
+
code = main(["--check", str(tmp_path)])
|
|
79
|
+
assert code == 0
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Tests for true_formatter.core — format_str, format_file_contents, check_format."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import sys, os
|
|
8
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
9
|
+
|
|
10
|
+
import true_formatter
|
|
11
|
+
from true_formatter import Mode, format_str, format_file_contents, check_format
|
|
12
|
+
from true_formatter.exceptions import TrueFormattingError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
# format_str
|
|
17
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
class TestFormatStr:
|
|
20
|
+
def test_returns_string(self):
|
|
21
|
+
result = format_str("x = 1\n", mode=Mode())
|
|
22
|
+
assert isinstance(result, str)
|
|
23
|
+
|
|
24
|
+
def test_already_formatted_unchanged(self):
|
|
25
|
+
src = "x = 1\n"
|
|
26
|
+
assert format_str(src, mode=Mode()) == src
|
|
27
|
+
|
|
28
|
+
def test_trailing_whitespace_removed(self):
|
|
29
|
+
src = "x = 1 \n"
|
|
30
|
+
result = format_str(src, mode=Mode())
|
|
31
|
+
assert " " not in result
|
|
32
|
+
|
|
33
|
+
def test_type_error_on_non_string(self):
|
|
34
|
+
with pytest.raises(TypeError):
|
|
35
|
+
format_str(42, mode=Mode()) # type: ignore[arg-type]
|
|
36
|
+
|
|
37
|
+
def test_empty_string(self):
|
|
38
|
+
result = format_str("", mode=Mode())
|
|
39
|
+
assert isinstance(result, str)
|
|
40
|
+
|
|
41
|
+
def test_comment_only(self):
|
|
42
|
+
src = "# just a comment\n"
|
|
43
|
+
result = format_str(src, mode=Mode())
|
|
44
|
+
assert "# just a comment" in result
|
|
45
|
+
|
|
46
|
+
def test_multiline_preserved(self):
|
|
47
|
+
src = "x = 1\ny = 2\n"
|
|
48
|
+
result = format_str(src, mode=Mode())
|
|
49
|
+
assert "x" in result
|
|
50
|
+
assert "y" in result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
54
|
+
# format_file_contents
|
|
55
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
class TestFormatFileContents:
|
|
58
|
+
def test_ensures_trailing_newline(self):
|
|
59
|
+
result = format_file_contents("x = 1", mode=Mode())
|
|
60
|
+
assert result.endswith("\n")
|
|
61
|
+
|
|
62
|
+
def test_does_not_double_newline(self):
|
|
63
|
+
result = format_file_contents("x = 1\n", mode=Mode())
|
|
64
|
+
assert result == result.rstrip("\n") + "\n"
|
|
65
|
+
|
|
66
|
+
def test_empty_file(self):
|
|
67
|
+
result = format_file_contents("", mode=Mode())
|
|
68
|
+
assert result.endswith("\n")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
# check_format
|
|
73
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
class TestCheckFormat:
|
|
76
|
+
def test_already_formatted(self):
|
|
77
|
+
src = format_file_contents("x = 1\n", mode=Mode())
|
|
78
|
+
assert check_format(src, mode=Mode()) is True
|
|
79
|
+
|
|
80
|
+
def test_trailing_space_detected(self):
|
|
81
|
+
src = "x = 1 \n"
|
|
82
|
+
assert check_format(src, mode=Mode()) is False
|
|
83
|
+
|
|
84
|
+
def test_empty_string(self):
|
|
85
|
+
# empty string is not formatted (no trailing newline) — implementation detail
|
|
86
|
+
result = check_format("", mode=Mode())
|
|
87
|
+
assert isinstance(result, bool)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
# Mode
|
|
92
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
class TestMode:
|
|
95
|
+
def test_default_line_length(self):
|
|
96
|
+
assert Mode().line_length == 88
|
|
97
|
+
|
|
98
|
+
def test_default_string_normalization(self):
|
|
99
|
+
assert Mode().string_normalization is True
|
|
100
|
+
|
|
101
|
+
def test_quote_char_double_by_default(self):
|
|
102
|
+
assert Mode().quote_char == '"'
|
|
103
|
+
|
|
104
|
+
def test_quote_char_single_when_skip(self):
|
|
105
|
+
m = Mode(skip_string_normalization=True)
|
|
106
|
+
assert m.quote_char == "'"
|
|
107
|
+
|
|
108
|
+
def test_custom_line_length(self):
|
|
109
|
+
m = Mode(line_length=79)
|
|
110
|
+
assert m.line_length == 79
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Tests for true_formatter.rules — RuleSet and Rule base class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tokenize
|
|
6
|
+
import io
|
|
7
|
+
import sys, os
|
|
8
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from true_formatter.rules import Rule, RuleSet, DEFAULT_RULES, NormaliseCommaSpacing
|
|
12
|
+
from true_formatter import Mode
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _tokenize(src: str) -> list[tokenize.TokenInfo]:
|
|
16
|
+
return list(tokenize.generate_tokens(io.StringIO(src).readline))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestRuleSet:
|
|
20
|
+
def test_default_rules_non_empty(self):
|
|
21
|
+
assert len(DEFAULT_RULES) > 0
|
|
22
|
+
|
|
23
|
+
def test_add_returns_new_ruleset(self):
|
|
24
|
+
rule = NormaliseCommaSpacing()
|
|
25
|
+
new_rs = DEFAULT_RULES.add(rule)
|
|
26
|
+
assert len(new_rs) == len(DEFAULT_RULES) + 1
|
|
27
|
+
assert DEFAULT_RULES is not new_rs
|
|
28
|
+
|
|
29
|
+
def test_remove_by_name(self):
|
|
30
|
+
rs = DEFAULT_RULES.remove("comma-spacing")
|
|
31
|
+
names = [r.name for r in rs]
|
|
32
|
+
assert "comma-spacing" not in names
|
|
33
|
+
|
|
34
|
+
def test_remove_nonexistent_name_ok(self):
|
|
35
|
+
rs = DEFAULT_RULES.remove("does-not-exist")
|
|
36
|
+
assert len(rs) == len(DEFAULT_RULES)
|
|
37
|
+
|
|
38
|
+
def test_iterable(self):
|
|
39
|
+
rules = list(DEFAULT_RULES)
|
|
40
|
+
assert all(isinstance(r, Rule) for r in rules)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestBuiltinRules:
|
|
44
|
+
def test_comma_spacing_passthrough(self):
|
|
45
|
+
tokens = _tokenize("x = 1\n")
|
|
46
|
+
rule = NormaliseCommaSpacing()
|
|
47
|
+
result = rule.apply(tokens, Mode())
|
|
48
|
+
assert isinstance(result, list)
|
|
49
|
+
assert len(result) == len(tokens)
|
|
50
|
+
|
|
51
|
+
def test_rule_has_name(self):
|
|
52
|
+
rule = NormaliseCommaSpacing()
|
|
53
|
+
assert isinstance(rule.name, str)
|
|
54
|
+
assert rule.name
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestCustomRule:
|
|
58
|
+
def test_custom_rule_applied(self):
|
|
59
|
+
class UppercaseCommentsRule(Rule):
|
|
60
|
+
name = "uppercase-comments"
|
|
61
|
+
|
|
62
|
+
def apply(self, tokens, mode):
|
|
63
|
+
out = []
|
|
64
|
+
for tok in tokens:
|
|
65
|
+
if tok.type == tokenize.COMMENT:
|
|
66
|
+
tok = tok._replace(string=tok.string.upper())
|
|
67
|
+
out.append(tok)
|
|
68
|
+
return out
|
|
69
|
+
|
|
70
|
+
tokens = _tokenize("# hello\nx = 1\n")
|
|
71
|
+
rule = UppercaseCommentsRule()
|
|
72
|
+
result = rule.apply(tokens, Mode())
|
|
73
|
+
comment_tokens = [t for t in result if t.type == tokenize.COMMENT]
|
|
74
|
+
assert all(t.string == t.string.upper() for t in comment_tokens)
|