Repository: nathanjmcdougall/usethis-python
Pull request #605: Add a --show flag for usethis badge
Author: login: Z-Fran
Created: 2025-05-06T17:11:33Z | Merged: 2025-05-08T19:26:56Z

Implementation of https://github.com/nathanjmcdougall/usethis-python/issues/424.

Use cases:
1. only use `--show` flag:
```
usethis badge pypi --show 
```
![image](https://github.com/user-attachments/assets/7f645ee8-4c04-4688-81c3-41ae7898873d)

It will print the badge to the console and add the badge.

2. use `--show` and `--quiet` at the same time:
It will still print the badge to the console.

3. use `--show` and `--remove` at the same time:
It will print the badge to the console and remove the badge.

If anything doesn’t meet expectations, please let me know and I’ll make the changes.



Diff:
diff --git a/src/usethis/_interface/badge.py b/src/usethis/_interface/badge.py
index 9264d694..b377ea53 100644
--- a/src/usethis/_interface/badge.py
+++ b/src/usethis/_interface/badge.py
@@ -23,15 +23,18 @@
     False, "--remove", help="Remove the badge instead of adding it."
 )
 
+show_opt = typer.Option(False, "--show", help="Print the badge to the console.")
+
 
 @app.command(help="Add a badge with the version of your package on PyPI.")
 def pypi(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_pypi_badge(), remove=remove)
+        _badge_effect(get_pypi_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the Ruff linter.")
@@ -39,9 +42,10 @@ def ruff(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_ruff_badge(), remove=remove)
+        _badge_effect(get_ruff_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the pre-commit framework.")
@@ -49,9 +53,10 @@ def pre_commit(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_pre_commit_badge(), remove=remove)
+        _badge_effect(get_pre_commit_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for usethis.")
@@ -59,9 +64,10 @@ def usethis(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_usethis_badge(), remove=remove)
+        _badge_effect(get_usethis_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the uv package manager.")
@@ -69,17 +75,21 @@ def uv(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_uv_badge(), remove=remove)
+        _badge_effect(get_uv_badge(), remove=remove, show=show)
 
 
-def _modify_badge(
+def _badge_effect(
     badge: Badge,
     remove: bool = False,
+    show: bool = False,
 ):
     try:
-        if not remove:
+        if show:
+            print(badge.markdown)
+        elif not remove:
             add_badge(badge)
         else:
             remove_badge(badge)
diff --git a/tests/usethis/_interface/test_interface_badge.py b/tests/usethis/_interface/test_interface_badge.py
index 8b3992d6..1f22d13c 100644
--- a/tests/usethis/_interface/test_interface_badge.py
+++ b/tests/usethis/_interface/test_interface_badge.py
@@ -28,6 +28,18 @@ def test_remove(self, tmp_path: Path):
         # Assert
         assert result.exit_code == 0, result.output
 
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["pypi", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output
+
 
 class TestRuff:
     def test_add(self, tmp_path: Path):
@@ -63,6 +75,18 @@ def test_wrong_encoding(self, tmp_path: Path):
         # Assert
         assert result.exit_code == 0, result.output
 
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["ruff", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output
+
 
 class TestPreCommit:
     def test_add(self, tmp_path: Path):
@@ -86,6 +110,18 @@ def test_remove(self, tmp_path: Path):
         # Assert
         assert result.exit_code == 0, result.output
 
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["pre-commit", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output
+
 
 class TestUsethis:
     def test_add(self, tmp_path: Path):
@@ -109,6 +145,22 @@ def test_remove(self, tmp_path: Path):
         # Assert
         assert result.exit_code == 0, result.output
 
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["usethis", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output
+        assert (
+            result.output
+            == "[![usethis](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/nathanjmcdougall/usethis-python/main/assets/badge/v1.json)](https://github.com/nathanjmcdougall/usethis-python)\n"
+        )
+
 
 class TestUV:
     def test_add(self, tmp_path: Path):
@@ -131,3 +183,15 @@ def test_remove(self, tmp_path: Path):
 
         # Assert
         assert result.exit_code == 0, result.output
+
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["uv", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output


Reviews:
user: login: nathanjmcdougall
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T21:19:06Z
user: login: nathanjmcdougall
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-06T22:35:11Z
user: login: nathanjmcdougall
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T20:45:08Z
user: login: nathanjmcdougall
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T21:25:27Z
user: login: nathanjmcdougall
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T21:26:04Z
user: login: Z-Fran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T11:17:18Z
user: login: nathanjmcdougall
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-08T19:10:02Z

Review comments:
diff_hunk: @@ -23,66 +23,77 @@
     False, "--remove", help="Remove the badge instead of adding it."
 )
 
+show_opt = typer.Option(
+    False, "--show", help="Print the badge to the console."
+)
 
 @app.command(help="Add a badge with the version of your package on PyPI.")
 def pypi(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_pypi_badge(), remove=remove)
+        _modify_badge(get_pypi_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the Ruff linter.")
 def ruff(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_ruff_badge(), remove=remove)
+        _modify_badge(get_ruff_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the pre-commit framework.")
 def pre_commit(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_pre_commit_badge(), remove=remove)
+        _modify_badge(get_pre_commit_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for usethis.")
 def usethis(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_usethis_badge(), remove=remove)
+        _modify_badge(get_usethis_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the uv package manager.")
 def uv(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_uv_badge(), remove=remove)
+        _modify_badge(get_uv_badge(), remove=remove, show=show)
 
 
 def _modify_badge(
path: src/usethis/_interface/badge.py
user: login: nathanjmcdougall
body: I think this name `_modify_badge` is a bit misleading now, since the function also does things like showing. Can we please rename the function to `_badge_effect`?
created_at: 2025-05-06T21:17:55Z
author_association: COLLABORATOR
diff_hunk: @@ -23,66 +23,77 @@
     False, "--remove", help="Remove the badge instead of adding it."
 )
 
+show_opt = typer.Option(
+    False, "--show", help="Print the badge to the console."
+)
 
 @app.command(help="Add a badge with the version of your package on PyPI.")
 def pypi(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_pypi_badge(), remove=remove)
+        _modify_badge(get_pypi_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the Ruff linter.")
 def ruff(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_ruff_badge(), remove=remove)
+        _modify_badge(get_ruff_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the pre-commit framework.")
 def pre_commit(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_pre_commit_badge(), remove=remove)
+        _modify_badge(get_pre_commit_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for usethis.")
 def usethis(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_usethis_badge(), remove=remove)
+        _modify_badge(get_usethis_badge(), remove=remove, show=show)
 
 
 @app.command(help="Add a badge for the uv package manager.")
 def uv(
     remove: bool = remove_opt,
     offline: bool = offline_opt,
     quiet: bool = quiet_opt,
+    show: bool = show_opt,
 ) -> None:
     with usethis_config.set(offline=offline, quiet=quiet), files_manager():
-        _modify_badge(get_uv_badge(), remove=remove)
+        _modify_badge(get_uv_badge(), remove=remove, show=show)
 
 
 def _modify_badge(
     badge: Badge,
     remove: bool = False,
+    show: bool = False,
 ):
     try:
         if not remove:
path: src/usethis/_interface/badge.py
user: login: nathanjmcdougall
body: I suggest:

```Python
        if show:
            print(badge.markdown)
        elif not remove:
            add_badge(badge)
        else:
            remove_badge(badge)
```
created_at: 2025-05-06T22:35:07Z
author_association: COLLABORATOR
diff_hunk: @@ -63,6 +75,18 @@ def test_wrong_encoding(self, tmp_path: Path):
         # Assert
         assert result.exit_code == 0, result.output
 
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["ruff", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output
path: tests/usethis/_interface/test_interface_badge.py
user: login: nathanjmcdougall
body: Let's also check that the badge that's shown is correct, and that there's not other output, perhaps something like this:

```suggestion
        assert result.exit_code == 0, result.output
        assert result.output == "[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)"
```
created_at: 2025-05-07T20:44:43Z
author_association: COLLABORATOR
diff_hunk: @@ -63,6 +75,18 @@ def test_wrong_encoding(self, tmp_path: Path):
         # Assert
         assert result.exit_code == 0, result.output
 
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["ruff", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output
path: tests/usethis/_interface/test_interface_badge.py
user: login: nathanjmcdougall
body: I'm not sure why you were getting an error when importing `get_pypi_badge` but I prefer hard-coding the string anyway, to make the test more transparent to debug etc.
created_at: 2025-05-07T21:25:27Z
author_association: COLLABORATOR
diff_hunk: @@ -63,6 +75,18 @@ def test_wrong_encoding(self, tmp_path: Path):
         # Assert
         assert result.exit_code == 0, result.output
 
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["ruff", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output
path: tests/usethis/_interface/test_interface_badge.py
user: login: nathanjmcdougall
body: Note that we don't need to do this for all the tests - just one is fine.
created_at: 2025-05-07T21:26:04Z
author_association: COLLABORATOR
diff_hunk: @@ -63,6 +75,18 @@ def test_wrong_encoding(self, tmp_path: Path):
         # Assert
         assert result.exit_code == 0, result.output
 
+    def test_show(self, tmp_path: Path):
+        # Arrange
+        (tmp_path / "README.md").write_text("")
+
+        # Act
+        runner = CliRunner()
+        with change_cwd(tmp_path):
+            result = runner.invoke(app, ["ruff", "--show"])
+
+        # Assert
+        assert result.exit_code == 0, result.output
path: tests/usethis/_interface/test_interface_badge.py
user: login: Z-Fran
body: Hi, @nathanjmcdougall . Have added a test there https://github.com/nathanjmcdougall/usethis-python/blob/c311175289b20b79bb1edd383978b9cb774304a2/tests/usethis/_interface/test_interface_badge.py#L159-L162
created_at: 2025-05-08T11:17:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: nathanjmcdougall
created_at: 2025-05-06T22:32:46Z
body: Hi @Z-Fran!

Nice to meet you the other day, and thank you so much for your contribution.

First step, please configure `pre-commit` for local development. I realized this been documented anywhere so apologies (opened #606 for this). Just do this with `usethis tool pre-commit` and it will install the pre-commit hooks in your git repo. This is the reason the GitHub Actions pipeline is failing at the moment.

Also, I'm going to want to add some tests for this code. If you don't want to add them, I'd be happy to add them myself - just let me know.

One naming change I've flagged above and then regarding your points:

1. At the moment, the `--show` flag will not prevent the badge from be added. However, my preference is that the `--show` parameter, when used, would prevent both badge removal and badge addition.

2. That's fine for now. In the long run, I'd like the `--quiet` flag to always take effect, even when the `--show` command is used, i.e. no output should be displayed. There are a few reasons for this, but basically it boils down to the fact that you really want to be able to trust the `--quiet` flag. Arguably it also makes the architecture simpler. What I'd say is **don't worry about this**, we'll resolve it via #610.

3. Same as point 1 - I would like to change this behaviour so that if the `--show` parameter is used, then the `--remove` option is completely ignored.

Thanks for taking the time to work on this issue.
author_association: COLLABORATOR
user: login: Z-Fran
created_at: 2025-05-07T12:02:37Z
body: > Hi @Z-Fran!
> 
> Nice to meet you the other day, and thank you so much for your contribution.
> 
> First step, please configure `pre-commit` for local development. I realized this been documented anywhere so apologies (opened #606 for this). Just do this with `usethis tool pre-commit` and it will install the pre-commit hooks in your git repo. This is the reason the GitHub Actions pipeline is failing at the moment.
> 
> Also, I'm going to want to add some tests for this code. If you don't want to add them, I'd be happy to add them myself - just let me know.
> 
> One naming change I've flagged above and then regarding your points:
> 
> 1. At the moment, the `--show` flag will not prevent the badge from be added. However, my preference is that the `--show` parameter, when used, would prevent both badge removal and badge addition.
> 2. That's fine for now. In the long run, I'd like the `--quiet` flag to always take effect, even when the `--show` command is used, i.e. no output should be displayed. There are a few reasons for this, but basically it boils down to the fact that you really want to be able to trust the `--quiet` flag. Arguably it also makes the architecture simpler. What I'd say is **don't worry about this**, we'll resolve it via [Never use simple `print` #610](https://github.com/nathanjmcdougall/usethis-python/issues/610).
> 3. Same as point 1 - I would like to change this behaviour so that if the `--show` parameter is used, then the `--remove` option is completely ignored.
> 
> Thanks for taking the time to work on this issue.

Hi @nathanjmcdougall , I have made some changes:

1. rename `_modify_badge` to `_badge_effect`

2. ignore `--remove` and don't add badges when setting `--show`

3. add some simple tests
I want to use `assert result.output == get_pypi_badge().markdown` to test, but an error occurred when I tried to import `get_pypi_badge()`.
**If you have a better test, please modify it!**

4. code has passed pre-commit hooks.

5. I don't resolve the `--quiet` flag.

author_association: CONTRIBUTOR
user: login: codspeed-hq[bot]
created_at: 2025-05-07T20:51:30Z
body: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/nathanjmcdougall/usethis-python/branches/Z-Fran%3A424-add-show-for-badge)

### Merging #605 will **not alter performance**

<sub>Comparing <code>Z-Fran:424-add-show-for-badge</code> (c311175) with <code>main</code> (6a77f3f)</sub>



### Summary

`✅ 1` untouched benchmarks  




author_association: NONE
user: login: codecov[bot]
created_at: 2025-05-07T20:51:32Z
body: ## [Codecov](https://app.codecov.io/gh/nathanjmcdougall/usethis-python/pull/605?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nathan+McDougall) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 97.54%. Comparing base [(`fd41170`)](https://app.codecov.io/gh/nathanjmcdougall/usethis-python/commit/fd41170f32e17e592dc79ba8d6893fbe456d3b71?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nathan+McDougall) to head [(`c311175`)](https://app.codecov.io/gh/nathanjmcdougall/usethis-python/commit/c311175289b20b79bb1edd383978b9cb774304a2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nathan+McDougall).
> Report is 5 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #605   +/-   ##
=======================================
  Coverage   97.54%   97.54%           
=======================================
  Files         102      102           
  Lines        4439     4444    +5     
=======================================
+ Hits         4330     4335    +5     
  Misses        109      109           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/nathanjmcdougall/usethis-python/pull/605?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nathan+McDougall).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nathan+McDougall).

author_association: NONE
user: login: nathanjmcdougall
created_at: 2025-05-08T19:27:11Z
body: Thanks @Z-Fran !
author_association: COLLABORATOR
user: login: nathanjmcdougall
created_at: 2025-05-13T07:31:38Z
body: @Z-Fran I've just released this new feature in v0.11.0. Thank you again.
author_association: COLLABORATOR