Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli)!: actually switch directory with --directory/-C #9831

Merged
merged 3 commits into from
Nov 26, 2024
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
3 changes: 2 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ then `--help` combined with any of those can give you more information.
* `--no-interaction (-n)`: Do not ask any interactive question.
* `--no-plugins`: Disables plugins.
* `--no-cache`: Disables Poetry source caches.
* `--directory=DIRECTORY (-C)`: The working directory for the Poetry command (defaults to the current working directory).
* `--directory=DIRECTORY (-C)`: The working directory for the Poetry command (defaults to the current working directory). All command-line arguments will be resolved relative to the given directory.
* `--project=PROJECT (-P)`: Specify another path as the project root. All command-line arguments will be resolved relative to the current working directory or directory specified using `--directory` option if used.


## new
Expand Down
103 changes: 64 additions & 39 deletions src/poetry/console/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from poetry.__version__ import __version__
from poetry.console.command_loader import CommandLoader
from poetry.console.commands.command import Command
from poetry.utils.helpers import directory


if TYPE_CHECKING:
Expand Down Expand Up @@ -110,6 +111,64 @@ def __init__(self) -> None:
command_loader = CommandLoader({name: load_command(name) for name in COMMANDS})
self.set_command_loader(command_loader)

@property
def _default_definition(self) -> Definition:
from cleo.io.inputs.option import Option

definition = super()._default_definition

definition.add_option(
Option("--no-plugins", flag=True, description="Disables plugins.")
)

definition.add_option(
Option(
"--no-cache", flag=True, description="Disables Poetry source caches."
)
)

definition.add_option(
Option(
"--project",
"-P",
flag=False,
description=(
"Specify another path as the project root."
" All command-line arguments will be resolved relative to the current working directory."
),
)
)

definition.add_option(
Option(
"--directory",
"-C",
flag=False,
description=(
"The working directory for the Poetry command (defaults to the"
" current working directory). All command-line arguments will be"
" resolved relative to the given directory."
),
)
)

return definition

@cached_property
def _project_directory(self) -> Path:
if self._io and self._io.input.option("project"):
with directory(self._working_directory):
return Path(self._io.input.option("project")).absolute()

return self._working_directory

@cached_property
def _working_directory(self) -> Path:
if self._io and self._io.input.option("directory"):
return Path(self._io.input.option("directory")).absolute()

return Path.cwd()

@property
def poetry(self) -> Poetry:
from poetry.factory import Factory
Expand All @@ -118,7 +177,7 @@ def poetry(self) -> Poetry:
return self._poetry

self._poetry = Factory().create_poetry(
cwd=self._directory,
cwd=self._project_directory,
io=self._io,
disable_plugins=self._disable_plugins,
disable_cache=self._disable_cache,
Expand Down Expand Up @@ -171,7 +230,9 @@ def _run(self, io: IO) -> int:

self._load_plugins(io)

exit_code: int = super()._run(io)
with directory(self._working_directory):
exit_code: int = super()._run(io)

return exit_code

def _configure_io(self, io: IO) -> None:
Expand Down Expand Up @@ -331,49 +392,13 @@ def _load_plugins(self, io: IO) -> None:
from poetry.plugins.application_plugin import ApplicationPlugin
from poetry.plugins.plugin_manager import PluginManager

PluginManager.add_project_plugin_path(self._directory)
PluginManager.add_project_plugin_path(self._project_directory)
manager = PluginManager(ApplicationPlugin.group)
manager.load_plugins()
manager.activate(self)

self._plugins_loaded = True

@property
def _default_definition(self) -> Definition:
from cleo.io.inputs.option import Option

definition = super()._default_definition

definition.add_option(
Option("--no-plugins", flag=True, description="Disables plugins.")
)

definition.add_option(
Option(
"--no-cache", flag=True, description="Disables Poetry source caches."
)
)

definition.add_option(
Option(
"--directory",
"-C",
flag=False,
description=(
"The working directory for the Poetry command (defaults to the"
" current working directory)."
),
)
)

return definition

@cached_property
def _directory(self) -> Path:
if self._io and self._io.input.option("directory"):
return Path(self._io.input.option("directory")).absolute()
return Path.cwd()


def main() -> int:
exit_code: int = Application().run()
Expand Down
8 changes: 3 additions & 5 deletions src/poetry/console/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,10 @@ def handle(self) -> int:

project_path = Path.cwd()

if self.io.input.option("directory"):
project_path = Path(self.io.input.option("directory"))
if self.io.input.option("project"):
project_path = Path(self.io.input.option("project"))
if not project_path.exists() or not project_path.is_dir():
self.line_error(
"<error>The --directory path is not a directory.</error>"
)
self.line_error("<error>The --project path is not a directory.</error>")
return 1

return self._init_pyproject(project_path=project_path)
Expand Down
4 changes: 2 additions & 2 deletions src/poetry/console/commands/new.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ class NewCommand(InitCommand):
def handle(self) -> int:
from pathlib import Path

if self.io.input.option("directory"):
if self.io.input.option("project"):
self.line_error(
"<warning>--directory only makes sense with existing projects, and will"
"<warning>--project only makes sense with existing projects, and will"
" be ignored. You should consider the option --path instead.</warning>"
)

Expand Down
2 changes: 1 addition & 1 deletion tests/console/commands/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def test_build_relative_directory_src_layout(
# initializes Poetry before passing the directory.
app = Application()
tester = ApplicationTester(app)
tester.execute("build --directory .")
tester.execute("build --project .")

build_dir = tmp_project_path / "dist"

Expand Down
108 changes: 108 additions & 0 deletions tests/console/commands/test_version.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from __future__ import annotations

import os
import textwrap

from pathlib import Path
from typing import TYPE_CHECKING

import pytest

from cleo.testers.application_tester import ApplicationTester

from poetry.console.application import Application
from poetry.console.commands.version import VersionCommand


if TYPE_CHECKING:
from cleo.testers.command_tester import CommandTester
from pytest_mock import MockerFixture

from poetry.poetry import Poetry
from tests.types import CommandTesterFactory
Expand Down Expand Up @@ -132,3 +140,103 @@ def test_dry_run(tester: CommandTester) -> None:
new_pyproject = tester.command.poetry.file.path.read_text(encoding="utf-8")
assert tester.io.fetch_output() == "Bumping version from 1.2.3 to 2.0.0\n"
assert old_pyproject == new_pyproject


def test_version_with_project_parameter(
fixture_dir: FixtureDirGetter, mocker: MockerFixture
) -> None:
app = Application()
tester = ApplicationTester(app)

orig_version_command = VersionCommand.handle

def mock_handle(command: VersionCommand) -> int:
exit_code = orig_version_command(command)

command.io.write_line(f"ProjectPath: {command.poetry.pyproject_path.parent}")
command.io.write_line(f"WorkingDirectory: {os.getcwd()}")

return exit_code

mocker.patch("poetry.console.commands.version.VersionCommand.handle", mock_handle)

source_dir = fixture_dir("scripts")
tester.execute(f"--project {source_dir} version")

output = tester.io.fetch_output()
expected = textwrap.dedent(f"""\
scripts 0.1.0
ProjectPath: {source_dir}
WorkingDirectory: {os.getcwd()}
""")

assert source_dir != Path(os.getcwd())
assert output == expected


def test_version_with_directory_parameter(
fixture_dir: FixtureDirGetter, mocker: MockerFixture
) -> None:
app = Application()
tester = ApplicationTester(app)

orig_version_command = VersionCommand.handle

def mock_handle(command: VersionCommand) -> int:
exit_code = orig_version_command(command)

command.io.write_line(f"ProjectPath: {command.poetry.pyproject_path.parent}")
command.io.write_line(f"WorkingDirectory: {os.getcwd()}")

return exit_code

mocker.patch("poetry.console.commands.version.VersionCommand.handle", mock_handle)

source_dir = fixture_dir("scripts")
tester.execute(f"--directory {source_dir} version")

output = tester.io.fetch_output()
expected = textwrap.dedent(f"""\
scripts 0.1.0
ProjectPath: {source_dir}
WorkingDirectory: {source_dir}
""")

assert source_dir != Path(os.getcwd())
assert output == expected


def test_version_with_directory_and_project_parameter(
fixture_dir: FixtureDirGetter, mocker: MockerFixture
) -> None:
app = Application()
tester = ApplicationTester(app)

orig_version_command = VersionCommand.handle

def mock_handle(command: VersionCommand) -> int:
exit_code = orig_version_command(command)

command.io.write_line(f"ProjectPath: {command.poetry.pyproject_path.parent}")
command.io.write_line(f"WorkingDirectory: {os.getcwd()}")

return exit_code

mocker.patch("poetry.console.commands.version.VersionCommand.handle", mock_handle)

source_dir = fixture_dir("scripts")
working_directory = source_dir.parent
project_path = "./scripts"

tester.execute(f"--directory {working_directory} --project {project_path} version")

output = tester.io.fetch_output()

expected = textwrap.dedent(f"""\
scripts 0.1.0
ProjectPath: {source_dir}
WorkingDirectory: {working_directory}
""")

assert source_dir != working_directory
assert output == expected
Loading