diff --git a/docs/cli.md b/docs/cli.md index 5d5717962a4..7ae1b7d7968 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -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 diff --git a/src/poetry/console/application.py b/src/poetry/console/application.py index c038db7f02b..e2d79c449ae 100644 --- a/src/poetry/console/application.py +++ b/src/poetry/console/application.py @@ -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: @@ -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 @@ -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, @@ -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: @@ -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() diff --git a/src/poetry/console/commands/init.py b/src/poetry/console/commands/init.py index 61a44178590..9aef36f590a 100644 --- a/src/poetry/console/commands/init.py +++ b/src/poetry/console/commands/init.py @@ -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( - "The --directory path is not a directory." - ) + self.line_error("The --project path is not a directory.") return 1 return self._init_pyproject(project_path=project_path) diff --git a/src/poetry/console/commands/new.py b/src/poetry/console/commands/new.py index bb0fefad961..d1083216329 100644 --- a/src/poetry/console/commands/new.py +++ b/src/poetry/console/commands/new.py @@ -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( - "--directory only makes sense with existing projects, and will" + "--project only makes sense with existing projects, and will" " be ignored. You should consider the option --path instead." ) diff --git a/tests/console/commands/test_build.py b/tests/console/commands/test_build.py index 3d17d030b3a..9fc985a0545 100644 --- a/tests/console/commands/test_build.py +++ b/tests/console/commands/test_build.py @@ -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" diff --git a/tests/console/commands/test_version.py b/tests/console/commands/test_version.py index 5741cf3d9f5..4df35505c99 100644 --- a/tests/console/commands/test_version.py +++ b/tests/console/commands/test_version.py @@ -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 @@ -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