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
78 changes: 55 additions & 23 deletions src/usethis/_core/badge.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -81,6 +82,33 @@ def get_badge_order() -> list[Badge]:
]


@dataclass
class MarkdownH1Status:
"""A way of keeping track of whether we're in a block of H1 tags.

We don't want to add badges inside a block of H1 tags.
"""

h1_count: int = 0
in_block: bool = False

def update_from_line(self, line: str) -> None:
self.h1_count += self._count_h1_open_tags(line)
self.in_block = self.h1_count > 0
self.h1_count -= self._count_h1_close_tags(line)

@staticmethod
def _count_h1_open_tags(line: str) -> int:
h1_start_match = re.match(r"(<h1\s.*>)", line)
if h1_start_match is not None:
return len(h1_start_match.groups())
return 0

@staticmethod
def _count_h1_close_tags(line: str) -> int:
return line.count("</h1>")


def add_badge(badge: Badge) -> None:
add_readme()

Expand All @@ -91,27 +119,28 @@ def add_badge(badge: Badge) -> None:
print(badge.markdown)
return

prerequisites: list[Badge] = []
for _b in get_badge_order():
if badge.equivalent_to(_b):
break
prerequisites.append(_b)

content = path.read_text(encoding="utf-8")
try:
content = path.read_text(encoding="utf-8")
except UnicodeDecodeError:
warn_print(
"README file uses an unsupported encoding, printing badge markdown instead..."
)
print(badge.markdown)
return

original_lines = content.splitlines()

prerequisites = _get_prerequisites(badge)

have_added = False
have_encountered_badge = False
html_h1_count = 0
h1_status = MarkdownH1Status()
lines: list[str] = []
for original_line in original_lines:
if is_badge(original_line):
have_encountered_badge = True

html_h1_count += _count_h1_open_tags(original_line)
in_block = html_h1_count > 0
html_h1_count -= _count_h1_close_tags(original_line)
h1_status.update_from_line(original_line)

original_badge = Badge(markdown=original_line)

Expand All @@ -126,7 +155,7 @@ def add_badge(badge: Badge) -> None:
not original_line_is_prerequisite
and (not is_blank(original_line) or have_encountered_badge)
and not is_header(original_line)
and not in_block
and not h1_status.in_block
):
lines.append(badge.markdown)
have_added = True
Expand Down Expand Up @@ -160,6 +189,20 @@ def add_badge(badge: Badge) -> None:
path.write_text(output, encoding="utf-8")


def _get_prerequisites(badge: Badge) -> list[Badge]:
"""Get the prerequisites for a badge.

We want to place the badges in a specific order, so we need to check if we've got
past those prerequisites.
"""
prerequisites: list[Badge] = []
for _b in get_badge_order():
if badge.equivalent_to(_b):
break
prerequisites.append(_b)
return prerequisites


def _get_markdown_readme_path() -> Path:
path = get_readme_path()

Expand Down Expand Up @@ -196,17 +239,6 @@ def is_badge(line: str) -> bool:
)


def _count_h1_open_tags(line: str) -> int:
h1_start_match = re.match(r"(<h1\s.*>)", line)
if h1_start_match is not None:
return len(h1_start_match.groups())
return 0


def _count_h1_close_tags(line: str) -> int:
return line.count("</h1>")


def remove_badge(badge: Badge) -> None:
path = Path.cwd() / "README.md"

Expand Down
10 changes: 1 addition & 9 deletions src/usethis/_core/show.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
from __future__ import annotations

import typer

from usethis._config import usethis_config
from usethis._console import err_print
from usethis._integrations.file.pyproject_toml.name import get_name
from usethis._integrations.sonarqube.config import get_sonar_project_properties
from usethis._integrations.uv.init import ensure_pyproject_toml
from usethis.errors import UsethisError


def show_name() -> None:
Expand All @@ -19,8 +15,4 @@ def show_name() -> None:
def show_sonarqube_config() -> None:
with usethis_config.set(quiet=True):
ensure_pyproject_toml()
try:
print(get_sonar_project_properties())
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
get_sonar_project_properties()
12 changes: 7 additions & 5 deletions src/usethis/_interface/author.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

from usethis._config import quiet_opt, usethis_config
from usethis._config_file import files_manager
from usethis._console import err_print
from usethis._core.author import add_author
from usethis.errors import UsethisError


def author(
Expand All @@ -21,8 +23,8 @@ def author(
email_arg = email

with usethis_config.set(quiet=quiet), files_manager():
add_author(
name=name,
email=email_arg,
overwrite=overwrite,
)
try:
add_author(name=name, email=email_arg, overwrite=overwrite)
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
38 changes: 20 additions & 18 deletions src/usethis/_interface/badge.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from usethis._config import offline_opt, quiet_opt, usethis_config
from usethis._config_file import files_manager
from usethis._console import err_print
from usethis._core.badge import (
Badge,
add_badge,
get_pre_commit_badge,
get_pypi_badge,
Expand All @@ -11,6 +13,7 @@
get_uv_badge,
remove_badge,
)
from usethis.errors import UsethisError

app = typer.Typer(help="Add badges to the top of the README.md file.")

Expand All @@ -26,10 +29,7 @@
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
if not remove:
add_badge(get_pypi_badge())
else:
remove_badge(get_pypi_badge())
_modify_badge(get_pypi_badge(), remove=remove)


@app.command(help="Add a badge for the Ruff linter.")
Expand All @@ -39,10 +39,7 @@
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
if not remove:
add_badge(get_ruff_badge())
else:
remove_badge(get_ruff_badge())
_modify_badge(get_ruff_badge(), remove=remove)


@app.command(help="Add a badge for the pre-commit framework.")
Expand All @@ -52,10 +49,7 @@
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
if not remove:
add_badge(get_pre_commit_badge())
else:
remove_badge(get_pre_commit_badge())
_modify_badge(get_pre_commit_badge(), remove=remove)


@app.command(help="Add a badge for usethis.")
Expand All @@ -65,10 +59,7 @@
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
if not remove:
add_badge(get_usethis_badge())
else:
remove_badge(get_usethis_badge())
_modify_badge(get_usethis_badge(), remove=remove)


@app.command(help="Add a badge for the uv package manager.")
Expand All @@ -78,7 +69,18 @@
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
_modify_badge(get_uv_badge(), remove=remove)


def _modify_badge(
badge: Badge,
remove: bool = False,
):
try:
if not remove:
add_badge(get_uv_badge())
add_badge(badge)
else:
remove_badge(get_uv_badge())
remove_badge(badge)
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None

Check warning on line 86 in src/usethis/_interface/badge.py

View check run for this annotation

Codecov / codecov/patch

src/usethis/_interface/badge.py#L84-L86

Added lines #L84 - L86 were not covered by tests
8 changes: 7 additions & 1 deletion src/usethis/_interface/browse.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import typer

from usethis._config import offline_opt, quiet_opt, usethis_config
from usethis._console import err_print
from usethis._core.browse import browse_pypi
from usethis.errors import UsethisError

app = typer.Typer(help="Visit important project-related web pages.")

Expand All @@ -17,4 +19,8 @@
quiet: bool = quiet_opt,
) -> None:
with usethis_config.set(offline=offline, quiet=quiet):
browse_pypi(package=package, browser=browser)
try:
browse_pypi(package=package, browser=browser)
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None

Check warning on line 26 in src/usethis/_interface/browse.py

View check run for this annotation

Codecov / codecov/patch

src/usethis/_interface/browse.py#L24-L26

Added lines #L24 - L26 were not covered by tests
10 changes: 5 additions & 5 deletions src/usethis/_interface/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ def bitbucket(
offline: bool = offline_opt,
quiet: bool = quiet_opt,
) -> None:
try:
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
with usethis_config.set(offline=offline, quiet=quiet), files_manager():
try:
use_ci_bitbucket(remove=remove)
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
18 changes: 14 additions & 4 deletions src/usethis/_interface/docstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

from usethis._config import quiet_opt, usethis_config
from usethis._config_file import files_manager
from usethis._console import err_print
from usethis._core.docstyle import UnknownDocstringStyleError, use_docstyle
from usethis.errors import UsethisError


def docstyle(
Expand All @@ -14,9 +16,17 @@ def docstyle(
),
quiet: bool = quiet_opt,
) -> None:
if style not in ("numpy", "google", "pep257"):
msg = f"Invalid docstring style: {style}. Choose from 'numpy', 'google', or 'pep257'."
raise UnknownDocstringStyleError(msg)
try:
if style not in ("numpy", "google", "pep257"):
msg = f"Invalid docstring style: {style}. Choose from 'numpy', 'google', or 'pep257'."
raise UnknownDocstringStyleError(msg)
except UnknownDocstringStyleError as err:
err_print(err)
raise typer.Exit(code=1) from None

with usethis_config.set(quiet=quiet), files_manager():
use_docstyle(style)
try:
use_docstyle(style)
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
10 changes: 9 additions & 1 deletion src/usethis/_interface/list.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from __future__ import annotations

import typer

from usethis._config_file import files_manager
from usethis._console import err_print
from usethis._core.list import show_usage_table
from usethis.errors import UsethisError


def list( # noqa: A001
) -> None:
with files_manager():
show_usage_table()
try:
show_usage_table()
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
24 changes: 15 additions & 9 deletions src/usethis/_interface/readme.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from usethis._config import quiet_opt, usethis_config
from usethis._config_file import files_manager
from usethis._console import err_print
from usethis._core.badge import (
add_badge,
get_pre_commit_badge,
Expand All @@ -14,23 +15,28 @@
from usethis._core.readme import add_readme
from usethis._integrations.uv.used import is_uv_used
from usethis._tool import PreCommitTool, RuffTool
from usethis.errors import UsethisError


def readme(
quiet: bool = quiet_opt,
badges: bool = typer.Option(False, "--badges", help="Add relevant badges"),
) -> None:
with usethis_config.set(quiet=quiet), files_manager():
add_readme()
try:
add_readme()

if badges:
if RuffTool().is_used():
add_badge(get_ruff_badge())
if badges:
if RuffTool().is_used():
add_badge(get_ruff_badge())

if PreCommitTool().is_used():
add_badge(get_pre_commit_badge())
if PreCommitTool().is_used():
add_badge(get_pre_commit_badge())

if is_uv_used():
add_badge(get_uv_badge())
if is_uv_used():
add_badge(get_uv_badge())

add_badge(get_usethis_badge())
add_badge(get_usethis_badge())
except UsethisError as err:
err_print(err)
raise typer.Exit(code=1) from None
Loading