Skip to content

Commit 5ece0e0

Browse files
author
Artur Shiriev
committed
add README.md, LICENSE and write tests
1 parent 64ba1b0 commit 5ece0e0

File tree

14 files changed

+468
-15
lines changed

14 files changed

+468
-15
lines changed

AGENTS.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Project Context for Agents
2+
3+
## Project Overview
4+
This is a Python project that implements an "End Of File Fixer" - a tool that ensures files end with exactly one newline character. The tool scans files in a directory and automatically adds or removes newlines at the end of files to maintain consistent formatting.
5+
6+
Despite the project name, the description in `pyproject.toml` mentions "Implementations of the Circuit Breaker" which appears to be incorrect or outdated.
7+
8+
### Key Technologies
9+
- **Language**: Python 3.10+
10+
- **Dependencies**:
11+
- `pathspec` for gitignore pattern matching
12+
- **Development Tools**:
13+
- `uv` for package management and building
14+
- `ruff` for linting and formatting
15+
- `mypy` for type checking
16+
- `pytest` for testing
17+
- `just` as a command runner
18+
19+
### Architecture
20+
The project follows a simple CLI tool structure:
21+
- Main entry point: `end_of_file_fixer/main.py`
22+
- CLI interface using `argparse`
23+
- File processing logic in `_fix_file()` function
24+
- Integration with `.gitignore` patterns via `pathspec`
25+
26+
## Building and Running
27+
28+
### Setup
29+
```bash
30+
# Install dependencies
31+
just install
32+
```
33+
34+
### Development Commands
35+
```bash
36+
# Run linting and type checking
37+
just lint
38+
39+
# Run tests
40+
just test
41+
42+
# Run tests with arguments
43+
just test -v
44+
45+
# Format code
46+
just lint # Includes auto-formatting
47+
```
48+
49+
### Using the Tool
50+
```bash
51+
# Fix files in a directory (modifies files)
52+
python -m end_of_file_fixer.main /path/to/directory
53+
54+
# Check files in a directory (dry run)
55+
python -m end_of_file_fixer.main /path/to/directory --check
56+
```
57+
58+
Or using the installed script:
59+
```bash
60+
# Fix files
61+
end-of-file-fixer /path/to/directory
62+
63+
# Check files
64+
end-of-file-fixer /path/to/directory --check
65+
```
66+
67+
## Development Conventions
68+
69+
### Code Style
70+
- Line length: 120 characters
71+
- Strict type checking with mypy
72+
- Ruff linting with specific rule exceptions (see pyproject.toml)
73+
- No mandatory docstrings (D1 ignored)
74+
75+
### Testing
76+
- Uses pytest framework
77+
- Tests should be placed in the `tests/` directory
78+
- Follow standard pytest naming conventions (`test_*.py` files, `test_*` functions)
79+
80+
### Project Structure
81+
```
82+
end-of-file-fixer/
83+
├── end_of_file_fixer/ # Main package
84+
│ ├── __init__.py # Package initializer
85+
│ └── main.py # Main CLI implementation
86+
├── tests/ # Test files
87+
│ └── test_dummy.py # Sample test file
88+
├── pyproject.toml # Project configuration
89+
├── Justfile # Command definitions
90+
├── README.md # Project description
91+
└── .gitignore # Git ignore patterns
92+
```
93+
94+
### Dependency Management
95+
- Uses `uv` for fast dependency resolution and installation
96+
- Dependencies defined in `pyproject.toml`
97+
- Development dependencies in `[dependency-groups].dev`
98+
99+
### CI/CD
100+
- Linting and type checking enforced in CI
101+
- Publishing handled via `just publish` command

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 modern-python
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.

README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,130 @@
11
# End Of File Fixer
2+
3+
A command-line tool that ensures all your text files end with exactly one newline character.
4+
This tool helps maintain consistent file formatting across your codebase by automatically adding or removing trailing newlines as needed.
5+
6+
## Why This Matters
7+
8+
Many POSIX systems expect text files to end with a newline character. Having consistent line endings:
9+
- Prevents spurious diffs in version control
10+
- Ensures proper concatenation of files
11+
- Satisfies POSIX compliance
12+
- Improves readability in terminal environments
13+
14+
## Features
15+
16+
- Automatically adds a newline to files that don't end with one
17+
- Removes excess trailing newlines from files that have too many
18+
- Respects `.gitignore` patterns to avoid processing unwanted files
19+
- Works with all text file types
20+
- Cross-platform compatibility (Windows, macOS, Linux)
21+
- Dry-run mode to preview changes before applying them
22+
23+
## Installation
24+
25+
### Using uv
26+
27+
```bash
28+
uv add end-of-file-fixer
29+
```
30+
31+
### Using pip
32+
33+
```bash
34+
pip install end-of-file-fixer
35+
```
36+
37+
## Usage
38+
39+
### Basic Usage
40+
41+
To fix all files in the current directory and subdirectories:
42+
43+
```bash
44+
end-of-file-fixer .
45+
```
46+
47+
To check which files would be modified without making changes:
48+
49+
```bash
50+
end-of-file-fixer . --check
51+
```
52+
53+
## How It Works
54+
55+
The end-of-file-fixer processes files in the following way:
56+
57+
1. **Files with no trailing newline**: Adds exactly one newline at the end
58+
2. **Files with exactly one trailing newline**: Leaves unchanged
59+
3. **Files with multiple trailing newlines**: Truncates to exactly one newline
60+
4. **Empty files**: Left unchanged
61+
62+
### Examples
63+
64+
| Original File Content | After Processing |
65+
|----------------------|------------------|
66+
| `hello world` | `hello world\n` |
67+
| `hello world\n` | `hello world\n` |
68+
| `hello world\n\n\n` | `hello world\n` |
69+
| `` (empty file) | `` (unchanged) |
70+
71+
## Configuration
72+
73+
The tool automatically respects patterns in your `.gitignore` file, so it won't process files that are ignored by Git. Additionally, it always ignores:
74+
- `.git` directories
75+
- `.cache` directories (used by uv)
76+
77+
## Exit Codes
78+
79+
- `0`: No files needed fixing or all files were successfully fixed
80+
- `1`: Some files needed fixing (when using `--check` mode)
81+
82+
## Development
83+
84+
### Prerequisites
85+
86+
- [uv](https://docs.astral.sh/uv/) for dependency management
87+
88+
### Setup
89+
90+
```bash
91+
# Clone the repository
92+
git clone https://github.com/community-of-python/end-of-file-fixer.git
93+
cd end-of-file-fixer
94+
95+
# Install dependencies
96+
just install
97+
```
98+
99+
### Running Tests
100+
101+
```bash
102+
# Run tests
103+
just test
104+
```
105+
106+
### Linting
107+
108+
```bash
109+
# Run linting and formatting
110+
just lint
111+
```
112+
113+
## Contributing
114+
115+
Contributions are welcome! Please feel free to submit a Pull Request.
116+
117+
1. Fork the repository
118+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
119+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
120+
4. Push to the branch (`git push origin feature/AmazingFeature`)
121+
5. Open a Pull Request
122+
123+
## License
124+
125+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
126+
127+
## Related Projects
128+
129+
- [pre-commit](https://pre-commit.com/) - A framework for managing and maintaining multi-language pre-commit hooks
130+
- [editorconfig](https://editorconfig.org/) - Helps maintain consistent coding styles across different editors and IDEs

end_of_file_fixer/main.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pathspec
88

99

10-
def _fix_file(file_obj: IO[bytes]) -> int:
10+
def _fix_file(file_obj: IO[bytes], check: bool) -> int: # noqa: C901
1111
# Test for newline at end of file
1212
# Empty files will throw IOError here
1313
try:
@@ -18,18 +18,20 @@ def _fix_file(file_obj: IO[bytes]) -> int:
1818
last_character = file_obj.read(1)
1919
# last_character will be '' for an empty file
2020
if last_character not in {b"\n", b"\r"} and last_character != b"":
21-
# Needs this seek for windows, otherwise IOError
22-
file_obj.seek(0, os.SEEK_END)
23-
file_obj.write(b"\n")
21+
if not check:
22+
# Needs this seek for windows, otherwise IOError
23+
file_obj.seek(0, os.SEEK_END)
24+
file_obj.write(b"\n")
2425
return 1
2526

2627
while last_character in {b"\n", b"\r"}:
2728
# Deal with the beginning of the file
2829
if file_obj.tell() == 1:
29-
# If we've reached the beginning of the file and it is all
30-
# linebreaks then we can make this file empty
31-
file_obj.seek(0)
32-
file_obj.truncate()
30+
if not check:
31+
# If we've reached the beginning of the file and it is all
32+
# linebreaks then we can make this file empty
33+
file_obj.seek(0)
34+
file_obj.truncate()
3335
return 1
3436

3537
# Go back two bytes and read a character
@@ -43,20 +45,24 @@ def _fix_file(file_obj: IO[bytes]) -> int:
4345
for sequence in (b"\n", b"\r\n", b"\r"):
4446
if remaining == sequence:
4547
return 0
48+
4649
if remaining.startswith(sequence):
47-
file_obj.seek(position + len(sequence))
48-
file_obj.truncate()
50+
if not check:
51+
file_obj.seek(position + len(sequence))
52+
file_obj.truncate()
4953
return 1
5054

51-
return 0
55+
return 0 # pragma: no cover
5256

5357

5458
def main() -> int:
5559
parser = argparse.ArgumentParser()
5660
parser.add_argument("path", help="path to directory", type=pathlib.Path)
61+
parser.add_argument("--check", action="store_true")
5762
args = parser.parse_args()
5863

5964
path: pathlib.Path = args.path
65+
check: bool = args.check
6066
gitignore_path = path / ".gitignore"
6167

6268
ignore_patterns = [
@@ -72,7 +78,7 @@ def main() -> int:
7278
retv = 0
7379
for filename in gitignore_spec.match_tree(path, negate=True):
7480
with pathlib.Path(filename).open("rb+") as f:
75-
ret_for_file = _fix_file(f)
81+
ret_for_file = _fix_file(f, check=check)
7682
if ret_for_file:
7783
sys.stdout.write(f"Fixing {filename}")
7884
retv |= ret_for_file

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "end-of-file-fixer"
3-
description = "Implementations of the Circuit Breaker"
3+
description = "A command-line tool that ensures all your text files end with exactly one newline character"
44
readme = "README.md"
55
requires-python = ">=3.10,<4"
66
dependencies = [

tests/fixtures/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.tmp
2+
temp/

tests/fixtures/empty.txt

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
This file has multiple newlines at the end
2+

tests/fixtures/newlines_only.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
3+

tests/fixtures/no_newline.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This file has no newline at the end

0 commit comments

Comments
 (0)