Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CI Pipeline

on:
push:
branches:
- main
pull_request:
branches:
- main
release:
types:
- published

jobs:
ci:
uses: community-of-python/community-workflow/.github/workflows/preset.yml@main
with:
python-version: '["3.10","3.11","3.12","3.13","3.14"]'
os: '["ubuntu-latest"]'
secrets: inherit
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generic things
*.pyc
*~
__pycache__/*
*.swp
*.sqlite3
*.map
.vscode
.idea
.DS_Store
.env
.mypy_cache
.pytest_cache
.ruff_cache
.coverage
htmlcov/
coverage.xml
pytest.xml
dist/
.python-version
.venv
uv.lock
/*.egg-info/
.zed
24 changes: 24 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
default: install lint test

install:
uv lock --upgrade
uv sync --all-extras --frozen

lint:
uv run ruff format
uv run ruff check --fix
uv run mypy .

lint-ci:
uv run ruff format --check
uv run ruff check --no-fix
uv run mypy .

test *args:
uv run --no-sync pytest {{ args }}

publish:
rm -rf dist
uv version $GITHUB_REF_NAME
uv build
uv publish --token $PYPI_TOKEN
Empty file added end_of_file_fixer/__init__.py
Empty file.
77 changes: 77 additions & 0 deletions end_of_file_fixer/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import argparse
import os
import pathlib
import sys
from typing import IO

import pathspec


def _fix_file(file_obj: IO[bytes]) -> int:
# Test for newline at end of file
# Empty files will throw IOError here
try:
file_obj.seek(-1, os.SEEK_END)
except OSError:
return 0

last_character = file_obj.read(1)
# last_character will be '' for an empty file
if last_character not in {b"\n", b"\r"} and last_character != b"":
# Needs this seek for windows, otherwise IOError
file_obj.seek(0, os.SEEK_END)
file_obj.write(b"\n")
return 1

while last_character in {b"\n", b"\r"}:
# Deal with the beginning of the file
if file_obj.tell() == 1:
# If we've reached the beginning of the file and it is all
# linebreaks then we can make this file empty
file_obj.seek(0)
file_obj.truncate()
return 1

# Go back two bytes and read a character
file_obj.seek(-2, os.SEEK_CUR)
last_character = file_obj.read(1)

# Our current position is at the end of the file just before any amount of
# newlines. If we find extraneous newlines, then backtrack and trim them.
position = file_obj.tell()
remaining = file_obj.read()
for sequence in (b"\n", b"\r\n", b"\r"):
if remaining == sequence:
return 0
if remaining.startswith(sequence):
file_obj.seek(position + len(sequence))
file_obj.truncate()
return 1

return 0


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("path", help="path to directory", type=pathlib.Path)
args = parser.parse_args()

path: pathlib.Path = args.path
gitignore_path = path / ".gitignore"

ignore_patterns = [".git"]
if gitignore_path.exists():
with gitignore_path.open("r") as f:
ignore_patterns.extend(f.readlines())

gitignore_spec = pathspec.GitIgnoreSpec.from_lines(ignore_patterns)

retv = 0
for filename in gitignore_spec.match_tree(path, negate=True):
with pathlib.Path(filename).open("rb+") as f:
ret_for_file = _fix_file(f)
if ret_for_file:
sys.stdout.write(f"Fixing {filename}")
retv |= ret_for_file

return retv
Empty file added end_of_file_fixer/py.typed
Empty file.
77 changes: 77 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
[project]
name = "end-of-file-fixer"
description = "Implementations of the Circuit Breaker"
readme = "README.md"
requires-python = ">=3.10,<4"
dependencies = [
"pathspec",
]
version = "0"
authors = [{ name = "community-of-python" }]
keywords = [
"python",
]
classifiers = [
"Typing :: Typed",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]

[project.urls]
Repository = "https://github.com/community-of-python/end-of-file-fixer"
Issues = "https://github.com/community-of-python/end-of-file-fixer/issues"
Changelogs = "https://github.com/community-of-python/end-of-file-fixer/releases"

[project.scripts]
end-of-file-fixer = "end_of_file_fixer.main:main"

[dependency-groups]
dev = [
"ruff",
"mypy",
"pytest",
"pytest-cov",
]

[build-system]
requires = ["uv_build"]
build-backend = "uv_build"

[tool.uv.build-backend]
module-name = "end_of_file_fixer"
module-root = ""

[tool.mypy]
python_version = "3.10"
strict = true

[tool.ruff]
fix = true
unsafe-fixes = true
line-length = 120
target-version = "py310"

[tool.ruff.lint]
select = ["ALL"]
ignore = [
"D1", # allow missing docstrings
"S101", # allow asserts
"TCH", # ignore flake8-type-checking
"FBT", # allow boolean args
"D203", # "one-blank-line-before-class" conflicting with D211
"D213", # "multi-line-summary-second-line" conflicting with D212
"COM812", # flake8-commas "Trailing comma missing"
"ISC001", # flake8-implicit-str-concat
]
isort.lines-after-imports = 2
isort.no-lines-before = ["standard-library", "local-folder"]

[tool.pytest.ini_options]
addopts = "--cov=. --cov-report term-missing"

[tool.coverage]
run.concurrency = ["thread"]
report.exclude_also = ["if typing.TYPE_CHECKING:"]
Empty file added tests/__init__.py
Empty file.
2 changes: 2 additions & 0 deletions tests/test_dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_dummy() -> None:
assert True