Skip to content

Commit 8014ae8

Browse files
Use global state to determine the project directory
1 parent 5a5d10e commit 8014ae8

28 files changed

Lines changed: 132 additions & 75 deletions

File tree

src/usethis/_config.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from contextlib import contextmanager
4+
from pathlib import Path
45
from typing import TYPE_CHECKING
56

67
import typer
@@ -19,30 +20,35 @@ class UsethisConfig(BaseModel):
1920
frozen: Do not install dependencies, nor update lockfiles.
2021
alert_only: Suppress all output except for warnings and errors.
2122
subprocess_verbose: Verbose output for subprocesses.
23+
force_project_dir: Directory for the project. If None, defaults to the current
24+
working directory dynamically determined at runtime.
2225
"""
2326

24-
offline: bool
25-
quiet: bool
27+
offline: bool = False
28+
quiet: bool = False
2629
frozen: bool = False
2730
alert_only: bool = False
2831
subprocess_verbose: bool = False
32+
project_dir: Path | None = None
2933

3034
@contextmanager
31-
def set(
35+
def set( # noqa: PLR0913
3236
self,
3337
*,
3438
offline: bool | None = None,
3539
quiet: bool | None = None,
3640
frozen: bool | None = None,
3741
alert_only: bool | None = None,
3842
subprocess_verbose: bool | None = None,
43+
project_dir: Path | None = None,
3944
) -> Generator[None, None, None]:
4045
"""Temporarily change command options."""
4146
old_offline = self.offline
4247
old_quiet = self.quiet
4348
old_frozen = self.frozen
4449
old_alert_only = self.alert_only
4550
old_subprocess_verbose = self.subprocess_verbose
51+
old_roject_dir = self.project_dir
4652

4753
if offline is None:
4854
offline = old_offline
@@ -54,18 +60,28 @@ def set(
5460
alert_only = self.alert_only
5561
if subprocess_verbose is None:
5662
subprocess_verbose = old_subprocess_verbose
63+
if project_dir is None:
64+
project_dir = old_roject_dir
5765

5866
self.offline = offline
5967
self.quiet = quiet
6068
self.frozen = frozen
6169
self.alert_only = alert_only
6270
self.subprocess_verbose = subprocess_verbose
71+
self.project_dir = project_dir
6372
yield
6473
self.offline = old_offline
6574
self.quiet = old_quiet
6675
self.frozen = old_frozen
6776
self.alert_only = old_alert_only
6877
self.subprocess_verbose = old_subprocess_verbose
78+
self.project_dir = old_roject_dir
79+
80+
def cpd(self) -> Path:
81+
"""Return the current project directory."""
82+
if self.project_dir is None:
83+
return Path.cwd()
84+
return self.project_dir
6985

7086

7187
_OFFLINE_DEFAULT = False

src/usethis/_core/badge.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22

33
import re
44
from dataclasses import dataclass
5-
from pathlib import Path
65
from typing import TYPE_CHECKING
76

87
from pydantic import BaseModel
98

9+
from usethis._config import usethis_config
1010
from usethis._console import plain_print, tick_print, warn_print
1111
from usethis._core.readme import add_readme, get_readme_path
1212
from usethis._integrations.project.name import get_project_name
1313

1414
if TYPE_CHECKING:
15+
from pathlib import Path
16+
1517
from typing_extensions import Self
1618

1719

@@ -231,7 +233,7 @@ def is_badge(line: str) -> bool:
231233

232234

233235
def remove_badge(badge: Badge) -> None:
234-
path = Path.cwd() / "README.md"
236+
path = usethis_config.cpd() / "README.md"
235237

236238
try:
237239
path = _get_markdown_readme_path()

src/usethis/_core/readme.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

3-
from pathlib import Path
4-
3+
from usethis._config import usethis_config
54
from usethis._console import box_print, tick_print
65
from usethis._integrations.file.pyproject_toml.errors import PyprojectTOMLError
76
from usethis._integrations.file.pyproject_toml.name import get_description
@@ -47,20 +46,20 @@ def add_readme() -> None:
4746
content = ""
4847

4948
tick_print("Writing 'README.md'.")
50-
(Path.cwd() / "README.md").write_text(content, encoding="utf-8")
49+
(usethis_config.cpd() / "README.md").write_text(content, encoding="utf-8")
5150
box_print("Populate 'README.md' to help users understand the project.")
5251

5352

5453
def get_readme_path():
55-
path_readme_md = Path.cwd() / "README.md"
56-
path_readme = Path.cwd() / "README"
54+
path_readme_md = usethis_config.cpd() / "README.md"
55+
path_readme = usethis_config.cpd() / "README"
5756

5857
if path_readme_md.exists() and path_readme_md.is_file():
5958
return path_readme_md
6059
elif path_readme.exists() and path_readme.is_file():
6160
return path_readme
6261

63-
for path in Path.cwd().glob("README*"):
62+
for path in usethis_config.cpd().glob("README*"):
6463
if path.is_file() and path.stem == "README":
6564
return path
6665

src/usethis/_core/tool.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
from pathlib import Path
65
from typing import TYPE_CHECKING, Protocol
76

87
from usethis._config import usethis_config
@@ -318,7 +317,7 @@ def use_requirements_txt(*, remove: bool = False, how: bool = False) -> None:
318317
tool.print_how_to_use()
319318
return
320319

321-
path = Path.cwd() / "requirements.txt"
320+
path = usethis_config.cpd() / "requirements.txt"
322321

323322
if not remove:
324323
ensure_pyproject_toml()
@@ -330,7 +329,10 @@ def use_requirements_txt(*, remove: bool = False, how: bool = False) -> None:
330329

331330
if not path.exists():
332331
# N.B. this is where a task runner would come in handy, to reduce duplication.
333-
if not (Path.cwd() / "uv.lock").exists() and not usethis_config.frozen:
332+
if (
333+
not (usethis_config.cpd() / "uv.lock").exists()
334+
and not usethis_config.frozen
335+
):
334336
tick_print("Writing 'uv.lock'.")
335337
call_uv_subprocess(["lock"], change_toml=False)
336338

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from pathlib import Path
2-
1+
from usethis._config import usethis_config
32
from usethis._console import tick_print
43
from usethis._integrations.ci.bitbucket.steps import add_placeholder_step_in_default
54

@@ -9,17 +8,17 @@ def add_bitbucket_pipeline_config(report_placeholder: bool = True) -> None:
98
109
Note that the pipeline is empty and will need steps added to it to run successfully.
1110
"""
12-
if (Path.cwd() / "bitbucket-pipelines.yml").exists():
11+
if (usethis_config.cpd() / "bitbucket-pipelines.yml").exists():
1312
# Early exit; the file already exists
1413
return
1514

1615
add_placeholder_step_in_default(report_placeholder=report_placeholder)
1716

1817

1918
def remove_bitbucket_pipeline_config() -> None:
20-
if not (Path.cwd() / "bitbucket-pipelines.yml").exists():
19+
if not (usethis_config.cpd() / "bitbucket-pipelines.yml").exists():
2120
# Early exit; the file already doesn't exist
2221
return
2322

2423
tick_print("Removing 'bitbucket-pipelines.yml'.")
25-
(Path.cwd() / "bitbucket-pipelines.yml").unlink()
24+
(usethis_config.cpd() / "bitbucket-pipelines.yml").unlink()

src/usethis/_integrations/ci/bitbucket/io_.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
from contextlib import contextmanager
44
from dataclasses import dataclass
5-
from pathlib import Path
65
from typing import TYPE_CHECKING
76

87
from pydantic import ValidationError
98

9+
from usethis._config import usethis_config
1010
from usethis._console import tick_print
1111
from usethis._integrations.ci.bitbucket.schema import PipelinesConfiguration
1212
from usethis._integrations.file.yaml.io_ import edit_yaml
1313

1414
if TYPE_CHECKING:
1515
from collections.abc import Generator
16+
from pathlib import Path
1617

1718
from ruamel.yaml.comments import CommentedMap
1819

@@ -42,7 +43,7 @@ def edit_bitbucket_pipelines_yaml() -> Generator[
4243
]:
4344
"""A context manager to modify 'bitbucket-pipelines.yml' in-place."""
4445
name = "bitbucket-pipelines.yml"
45-
path = Path.cwd() / name
46+
path = usethis_config.cpd() / name
4647

4748
if not path.exists():
4849
tick_print(f"Writing '{name}'.")

src/usethis/_integrations/ci/bitbucket/steps.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from __future__ import annotations
22

33
from functools import singledispatch
4-
from pathlib import Path
54
from typing import TYPE_CHECKING
65

76
from ruamel.yaml.comments import CommentedSeq
87
from ruamel.yaml.scalarstring import LiteralScalarString
98
from typing_extensions import assert_never
109

1110
import usethis._pipeweld.func
11+
from usethis._config import usethis_config
1212
from usethis._console import box_print, tick_print
1313
from usethis._integrations.ci.bitbucket.anchor import ScriptItemAnchor
1414
from usethis._integrations.ci.bitbucket.cache import _add_caches_via_doc, remove_cache
@@ -188,7 +188,7 @@ def remove_bitbucket_step_from_default(step: Step) -> None:
188188
189189
If the default pipeline does not exist, or the step is not found, nothing happens.
190190
"""
191-
if not (Path.cwd() / "bitbucket-pipelines.yml").exists():
191+
if not (usethis_config.cpd() / "bitbucket-pipelines.yml").exists():
192192
return
193193

194194
if step.name == _PLACEHOLDER_NAME:
@@ -360,7 +360,7 @@ def get_steps_in_default() -> list[Step]:
360360
Raises:
361361
UnexpectedImportPipelineError: If the pipeline is an import pipeline.
362362
"""
363-
if not (Path.cwd() / "bitbucket-pipelines.yml").exists():
363+
if not (usethis_config.cpd() / "bitbucket-pipelines.yml").exists():
364364
return []
365365

366366
with edit_bitbucket_pipelines_yaml() as doc:
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

3-
from pathlib import Path
3+
from usethis._config import usethis_config
44

55

66
def is_bitbucket_used() -> bool:
7-
return (Path.cwd() / "bitbucket-pipelines.yml").exists()
7+
return (usethis_config.cpd() / "bitbucket-pipelines.yml").exists()

src/usethis/_integrations/file/dir.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pathlib import Path
1+
from usethis._config import usethis_config
22

33

44
def get_project_name_from_dir() -> str:
@@ -8,7 +8,7 @@ def get_project_name_from_dir() -> str:
88
# valid characters, the name will be "hello_world".
99
# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#name
1010
# https://packaging.python.org/en/latest/specifications/name-normalization/#name-format
11-
dir_name = Path.cwd().name
11+
dir_name = usethis_config.cpd().name
1212
name = "".join(c for c in dir_name if c.isalnum() or c in {"-", "_", "."})
1313
if not name:
1414
name = "hello_world"

src/usethis/_integrations/file/pyproject_toml/remove.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from __future__ import annotations
22

3-
from pathlib import Path
4-
3+
from usethis._config import usethis_config
54
from usethis._console import tick_print
65
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager
76

87

98
def remove_pyproject_toml() -> None:
10-
path = Path.cwd() / "pyproject.toml"
9+
path = usethis_config.cpd() / "pyproject.toml"
1110
if path.exists() and path.is_file():
1211
tick_print("Removing 'pyproject.toml' file")
1312
PyprojectTOMLManager().write_file()

0 commit comments

Comments
 (0)