powercli 0.3.4__tar.gz → 0.3.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {powercli-0.3.4/src/powercli.egg-info → powercli-0.3.6}/PKG-INFO +8 -6
- {powercli-0.3.4 → powercli-0.3.6}/README.md +1 -1
- {powercli-0.3.4 → powercli-0.3.6}/pyproject.toml +6 -5
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/__init__.py +0 -1
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/commands/__init__.py +0 -1
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/commands/commands.py +10 -9
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/commands/open_.py +1 -1
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/commands/runner.py +6 -5
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/output/message.py +1 -1
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/output/progress.py +2 -2
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/output/rich.py +1 -1
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/output/status.py +2 -3
- {powercli-0.3.4 → powercli-0.3.6/src/powercli.egg-info}/PKG-INFO +8 -6
- {powercli-0.3.4 → powercli-0.3.6}/src/powercli.egg-info/SOURCES.txt +3 -1
- {powercli-0.3.4 → powercli-0.3.6}/src/powercli.egg-info/requires.txt +3 -2
- powercli-0.3.6/tests/test_cli_entry_point.py +16 -0
- powercli-0.3.6/tests/test_input.py +25 -0
- powercli-0.3.6/tests/test_output.py +30 -0
- {powercli-0.3.4 → powercli-0.3.6}/tests/test_progress.py +1 -1
- powercli-0.3.6/tests/test_run.py +93 -0
- {powercli-0.3.4 → powercli-0.3.6}/tests/test_runner.py +20 -23
- powercli-0.3.4/src/cli/commands/install.py +0 -31
- powercli-0.3.4/tests/test_cli_entry_point.py +0 -11
- {powercli-0.3.4 → powercli-0.3.6}/LICENSE +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/setup.cfg +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/cli/__init__.py +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/cli/entry_point.py +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/commands/run.py +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/input.py +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/models/__init__.py +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/models/models.py +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/output/__init__.py +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/output/console.py +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/cli/py.typed +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/powercli.egg-info/dependency_links.txt +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/powercli.egg-info/entry_points.txt +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/src/powercli.egg-info/top_level.txt +0 -0
- {powercli-0.3.4 → powercli-0.3.6}/tests/test_message.py +0 -0
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: powercli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: High-level CLI interaction
|
|
5
5
|
Author-email: Quinten Roets <qdr2104@columbia.edu>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Source Code, https://github.com/quintenroets/cli
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist:
|
|
11
|
+
Requires-Dist: pexpect<5,>=4.9.0
|
|
12
|
+
Requires-Dist: rich<15,>=14.0.1
|
|
12
13
|
Provides-Extra: dev
|
|
13
14
|
Requires-Dist: hypothesis<7,>=6.97.1; extra == "dev"
|
|
14
|
-
Requires-Dist: package-dev-tools<1,>=0.
|
|
15
|
+
Requires-Dist: package-dev-tools<1,>=0.7.1; extra == "dev"
|
|
15
16
|
Requires-Dist: package-dev-utils<1,>=0.1.6; extra == "dev"
|
|
16
17
|
Requires-Dist: superpathlib<3,>=2.0.2; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
17
19
|
|
|
18
20
|
# PowerCLI
|
|
19
21
|
[](https://badge.fury.io/py/powercli)
|
|
20
22
|

|
|
21
23
|

|
|
22
24
|

|
|
23
|
-

|
|
24
26
|
|
|
25
27
|
High-level CLI:
|
|
26
28
|
* Run commands
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|

|
|
6
|
-

|
|
7
7
|
|
|
8
8
|
High-level CLI:
|
|
9
9
|
* Run commands
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "powercli"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.6"
|
|
4
4
|
description = "High-level CLI interaction"
|
|
5
5
|
authors = [{name = "Quinten Roets", email = "qdr2104@columbia.edu"}]
|
|
6
|
-
license =
|
|
6
|
+
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
dependencies = [
|
|
10
|
-
"
|
|
10
|
+
"pexpect >=4.9.0, <5",
|
|
11
|
+
"rich >=14.0.1, <15",
|
|
11
12
|
]
|
|
12
13
|
|
|
13
14
|
[project.optional-dependencies]
|
|
14
15
|
dev = [
|
|
15
16
|
"hypothesis >=6.97.1, <7",
|
|
16
|
-
"package-dev-tools >=0.
|
|
17
|
+
"package-dev-tools >=0.7.1, <1",
|
|
17
18
|
"package-dev-utils >=0.1.6, <1",
|
|
18
19
|
"superpathlib >=2.0.2, <3",
|
|
19
20
|
]
|
|
@@ -33,7 +34,7 @@ command_line = "-m pytest tests"
|
|
|
33
34
|
|
|
34
35
|
[tool.coverage.report]
|
|
35
36
|
precision = 4
|
|
36
|
-
fail_under =
|
|
37
|
+
fail_under = 100
|
|
37
38
|
|
|
38
39
|
[tool.mypy]
|
|
39
40
|
strict = true
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shlex
|
|
3
|
-
import types
|
|
4
3
|
import typing
|
|
5
|
-
from collections.abc import Iterable, Iterator
|
|
4
|
+
from collections.abc import Iterable, Iterator, Sequence
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
from functools import cached_property
|
|
8
7
|
from pathlib import Path
|
|
9
|
-
from typing import Protocol
|
|
8
|
+
from typing import Protocol, cast
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
class StringLike(Protocol):
|
|
13
12
|
def __str__(self) -> str: ...
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
CommandItem =
|
|
15
|
+
CommandItem = (
|
|
16
|
+
StringLike | dict[str, StringLike] | Sequence[StringLike] | Iterator[StringLike]
|
|
17
|
+
)
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
@dataclass
|
|
@@ -42,7 +43,7 @@ class CommandPreparer:
|
|
|
42
43
|
commands: tuple[str, ...]
|
|
43
44
|
if self.should_use_root(command):
|
|
44
45
|
if not command.startswith(self.root_keyword):
|
|
45
|
-
command = f"{self.root_keyword
|
|
46
|
+
command = f"{self.root_keyword} {command}"
|
|
46
47
|
if self.askpass_is_available:
|
|
47
48
|
command = command.replace(self.root_keyword, f"{self.root_keyword} -A")
|
|
48
49
|
if self.use_console:
|
|
@@ -75,7 +76,7 @@ class CommandPreparer:
|
|
|
75
76
|
if os.name == "posix":
|
|
76
77
|
should_use_root = self.use_root or self.root_keyword in first_command_part
|
|
77
78
|
else:
|
|
78
|
-
should_use_root = False
|
|
79
|
+
should_use_root = False # pragma: nocover
|
|
79
80
|
return should_use_root
|
|
80
81
|
|
|
81
82
|
def generate_command_parts(self) -> Iterator[str]:
|
|
@@ -111,12 +112,12 @@ class CommandPreparer:
|
|
|
111
112
|
|
|
112
113
|
@classmethod
|
|
113
114
|
def extract_items(cls, item: CommandItem) -> Iterator[StringLike]:
|
|
114
|
-
collection_types = list, tuple,
|
|
115
|
+
collection_types = list, tuple, Iterator
|
|
115
116
|
is_collection = any(
|
|
116
117
|
isinstance(item, collection) for collection in collection_types
|
|
117
118
|
)
|
|
118
119
|
if is_collection:
|
|
119
|
-
yield from typing.cast(Iterable[StringLike], item)
|
|
120
|
+
yield from typing.cast("Iterable[StringLike]", item)
|
|
120
121
|
elif isinstance(item, dict):
|
|
121
122
|
for key, value in item.items():
|
|
122
123
|
yield f"--{key}"
|
|
@@ -126,4 +127,4 @@ class CommandPreparer:
|
|
|
126
127
|
for part in item:
|
|
127
128
|
yield f"--{part}"
|
|
128
129
|
elif hasattr(item, "__str__"):
|
|
129
|
-
yield item
|
|
130
|
+
yield cast("str", item)
|
|
@@ -7,6 +7,6 @@ from .run import launch
|
|
|
7
7
|
def open_urls(*urls: StringLike) -> None:
|
|
8
8
|
for url in urls:
|
|
9
9
|
if os.name == "nt":
|
|
10
|
-
os.startfile(url) # type: ignore[attr-defined] # noqa: S606
|
|
10
|
+
os.startfile(url) # type: ignore[attr-defined] # noqa: S606 # pragma: nocover
|
|
11
11
|
else:
|
|
12
12
|
launch("xdg-open", url)
|
|
@@ -59,7 +59,7 @@ class Runner(Generic[T1]):
|
|
|
59
59
|
import tempfile
|
|
60
60
|
|
|
61
61
|
with tempfile.TemporaryFile() as untyped_log_file:
|
|
62
|
-
log_file = typing.cast(io.TextIOWrapper, untyped_log_file)
|
|
62
|
+
log_file = typing.cast("io.TextIOWrapper", untyped_log_file)
|
|
63
63
|
self.run_in_tty(log_file)
|
|
64
64
|
log_file.seek(0)
|
|
65
65
|
return log_file.read()
|
|
@@ -72,7 +72,7 @@ class Runner(Generic[T1]):
|
|
|
72
72
|
if self.capture_output is not None:
|
|
73
73
|
child.expect(pexpect.EOF)
|
|
74
74
|
else:
|
|
75
|
-
child.interact()
|
|
75
|
+
child.interact() # pragma: nocover
|
|
76
76
|
|
|
77
77
|
def capture_output(self) -> str:
|
|
78
78
|
return self.run(capture_output=True).stdout.strip()
|
|
@@ -135,9 +135,10 @@ class Runner(Generic[T1]):
|
|
|
135
135
|
try:
|
|
136
136
|
return runner(*args, **kwargs)
|
|
137
137
|
except subprocess.CalledProcessError as exception:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
if self.verbose_errors:
|
|
139
|
+
verbose_exception = CalledProcessError(exception.stderr or exception)
|
|
140
|
+
raise verbose_exception from exception
|
|
141
|
+
raise
|
|
141
142
|
|
|
142
143
|
def prepare_console_command(self) -> None:
|
|
143
144
|
self.console = True
|
|
@@ -5,9 +5,9 @@ from functools import cached_property
|
|
|
5
5
|
from typing import TYPE_CHECKING, TypeVar
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
-
from collections.abc import Iterable, Iterator
|
|
8
|
+
from collections.abc import Iterable, Iterator # pragma: nocover
|
|
9
9
|
|
|
10
|
-
from rich.progress import Progress
|
|
10
|
+
from rich.progress import Progress # pragma: nocover
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
T = TypeVar("T")
|
|
@@ -6,11 +6,10 @@ from typing import TYPE_CHECKING, Any
|
|
|
6
6
|
from .rich import console
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from rich.console import Status
|
|
9
|
+
from rich.console import Status # pragma: nocover
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def status(*args: Any, **kwargs: Any) -> Status | contextlib.nullcontext[None]:
|
|
13
|
-
console.clear_live()
|
|
14
13
|
use_status = not is_running_in_notebook()
|
|
15
14
|
return console.status(*args, **kwargs) if use_status else contextlib.nullcontext()
|
|
16
15
|
|
|
@@ -21,5 +20,5 @@ def is_running_in_notebook() -> bool:
|
|
|
21
20
|
except NameError:
|
|
22
21
|
in_notebook = False
|
|
23
22
|
else:
|
|
24
|
-
in_notebook = True
|
|
23
|
+
in_notebook = True # pragma: nocover
|
|
25
24
|
return in_notebook
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: powercli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: High-level CLI interaction
|
|
5
5
|
Author-email: Quinten Roets <qdr2104@columbia.edu>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Source Code, https://github.com/quintenroets/cli
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist:
|
|
11
|
+
Requires-Dist: pexpect<5,>=4.9.0
|
|
12
|
+
Requires-Dist: rich<15,>=14.0.1
|
|
12
13
|
Provides-Extra: dev
|
|
13
14
|
Requires-Dist: hypothesis<7,>=6.97.1; extra == "dev"
|
|
14
|
-
Requires-Dist: package-dev-tools<1,>=0.
|
|
15
|
+
Requires-Dist: package-dev-tools<1,>=0.7.1; extra == "dev"
|
|
15
16
|
Requires-Dist: package-dev-utils<1,>=0.1.6; extra == "dev"
|
|
16
17
|
Requires-Dist: superpathlib<3,>=2.0.2; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
17
19
|
|
|
18
20
|
# PowerCLI
|
|
19
21
|
[](https://badge.fury.io/py/powercli)
|
|
20
22
|

|
|
21
23
|

|
|
22
24
|

|
|
23
|
-

|
|
24
26
|
|
|
25
27
|
High-level CLI:
|
|
26
28
|
* Run commands
|
|
@@ -8,7 +8,6 @@ src/cli/cli/__init__.py
|
|
|
8
8
|
src/cli/cli/entry_point.py
|
|
9
9
|
src/cli/commands/__init__.py
|
|
10
10
|
src/cli/commands/commands.py
|
|
11
|
-
src/cli/commands/install.py
|
|
12
11
|
src/cli/commands/open_.py
|
|
13
12
|
src/cli/commands/run.py
|
|
14
13
|
src/cli/commands/runner.py
|
|
@@ -27,6 +26,9 @@ src/powercli.egg-info/entry_points.txt
|
|
|
27
26
|
src/powercli.egg-info/requires.txt
|
|
28
27
|
src/powercli.egg-info/top_level.txt
|
|
29
28
|
tests/test_cli_entry_point.py
|
|
29
|
+
tests/test_input.py
|
|
30
30
|
tests/test_message.py
|
|
31
|
+
tests/test_output.py
|
|
31
32
|
tests/test_progress.py
|
|
33
|
+
tests/test_run.py
|
|
32
34
|
tests/test_runner.py
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
from package_dev_utils.tests.args import cli_args
|
|
5
|
+
|
|
6
|
+
from cli import cli
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@patch("subprocess.run")
|
|
10
|
+
@patch("subprocess.Popen")
|
|
11
|
+
@cli_args("ls")
|
|
12
|
+
def test_entry_point(mocked_popen: MagicMock, mocked_run: MagicMock) -> None:
|
|
13
|
+
with contextlib.suppress(FileNotFoundError):
|
|
14
|
+
cli.entry_point()
|
|
15
|
+
mocked_popen.assert_called_once()
|
|
16
|
+
mocked_run.assert_called_once()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
3
|
+
from rich.prompt import Confirm, Prompt
|
|
4
|
+
|
|
5
|
+
import cli
|
|
6
|
+
|
|
7
|
+
word = "hello"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@patch("builtins.input")
|
|
11
|
+
def test_ask(mocked_input: MagicMock) -> None:
|
|
12
|
+
cli.ask(word)
|
|
13
|
+
mocked_input.assert_called_once()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@patch.object(Prompt, "ask")
|
|
17
|
+
def test_prompt(mocked_ask: MagicMock) -> None:
|
|
18
|
+
cli.prompt(word)
|
|
19
|
+
mocked_ask.assert_called_once()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@patch.object(Confirm, "ask")
|
|
23
|
+
def test_confirm(mocked_confirm: MagicMock) -> None:
|
|
24
|
+
cli.confirm(word)
|
|
25
|
+
mocked_confirm.assert_called_once()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from cli.output.message import Message
|
|
7
|
+
|
|
8
|
+
from .test_progress import ITERATIONS
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def message() -> Iterator[Message]:
|
|
13
|
+
message = Message("hello")
|
|
14
|
+
with message:
|
|
15
|
+
yield message
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_message(message: Message) -> None:
|
|
19
|
+
for i in range(ITERATIONS):
|
|
20
|
+
message.message = str(i)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@patch("os.get_terminal_size")
|
|
24
|
+
def test_create_header(mocked_terminal_size: MagicMock, message: Message) -> None:
|
|
25
|
+
mocked_terminal_size.columns = 100
|
|
26
|
+
message.create_header()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_extract_message(message: Message) -> None:
|
|
30
|
+
assert message.message
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from hypothesis import given
|
|
6
|
+
from superpathlib import Path
|
|
7
|
+
|
|
8
|
+
import cli
|
|
9
|
+
from cli.commands.commands import CommandPreparer
|
|
10
|
+
from cli.output.console import set_title
|
|
11
|
+
|
|
12
|
+
from .test_runner import linux_only_test, text_strategy
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@linux_only_test
|
|
16
|
+
def test_exception_handling() -> None:
|
|
17
|
+
with pytest.raises(cli.CalledProcessError):
|
|
18
|
+
cli.run("exit 1", shell=True) # noqa: S604
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@linux_only_test
|
|
22
|
+
def test_non_verbose_exception_handling() -> None:
|
|
23
|
+
with pytest.raises(subprocess.CalledProcessError):
|
|
24
|
+
cli.run("exit 1", shell=True, verbose_errors=False) # noqa: S604
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_command_not_found_exception_handling() -> None:
|
|
28
|
+
with pytest.raises(FileNotFoundError):
|
|
29
|
+
cli.run("non_existing_command")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_command_and_argument_combination() -> None:
|
|
33
|
+
cli.run("ls -l", "-a")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_cwd() -> None:
|
|
37
|
+
with Path.tempdir() as folder:
|
|
38
|
+
extracted_folder_name = cli.capture_output("pwd", cwd=folder).split("/")[-1]
|
|
39
|
+
assert extracted_folder_name == folder.name
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@given(value=text_strategy())
|
|
43
|
+
@linux_only_test
|
|
44
|
+
def test_extra_subprocess_kwarg(value: str) -> None:
|
|
45
|
+
env = {"name": value}
|
|
46
|
+
assert cli.capture_output("echo", "$name", shell=True, env=env) == value # noqa: S604
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_set_parsing() -> None:
|
|
50
|
+
commands = "python", {"version"}
|
|
51
|
+
cli.run(*commands)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_iterator_parsing() -> None:
|
|
55
|
+
commands = ("python", iter(["--version"]))
|
|
56
|
+
cli.run(*commands)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_dict_parsing() -> None:
|
|
60
|
+
commands = "git", {"work-tree": "."}, "status"
|
|
61
|
+
cli.run(*commands)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@patch("subprocess.run")
|
|
65
|
+
def test_title(mocked_popen: MagicMock) -> None:
|
|
66
|
+
cli.run("ls", title="ls", console=True)
|
|
67
|
+
mocked_popen.assert_called_once()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_set_title() -> None:
|
|
71
|
+
set_title(title="ls")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_sudo() -> None:
|
|
75
|
+
cli.run("sudo ls")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_root() -> None:
|
|
79
|
+
cli.run("ls", root=True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@patch.object(CommandPreparer, "askpass_is_available", new=True)
|
|
83
|
+
def test_root_with_askpass_enabled() -> None:
|
|
84
|
+
cli.run("ls", root=True)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_root_in_shell() -> None:
|
|
88
|
+
cli.run("ls", root=True, shell=True) # noqa: S604
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@patch.object(CommandPreparer, "askpass_is_available", new=True)
|
|
92
|
+
def test_root_in_shell_with_askpass_enabled() -> None:
|
|
93
|
+
cli.run("ls", root=True, shell=True) # noqa: S604
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import string
|
|
3
|
+
from unittest.mock import patch
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
5
6
|
from hypothesis import given, settings, strategies
|
|
6
7
|
from hypothesis.strategies import SearchStrategy
|
|
7
|
-
from superpathlib import Path
|
|
8
8
|
|
|
9
9
|
import cli
|
|
10
|
+
from cli.commands.runner import Runner
|
|
10
11
|
|
|
11
12
|
linux_only_test = pytest.mark.skipif(
|
|
12
13
|
os.name != "posix",
|
|
@@ -28,7 +29,7 @@ def test_capture_output_lines(message: str) -> None:
|
|
|
28
29
|
assert cli.capture_output_lines("printf", "%s", message) == message.splitlines()
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
@settings(deadline=
|
|
32
|
+
@settings(deadline=3000)
|
|
32
33
|
@given(message=text_strategy())
|
|
33
34
|
def test_pipe_output_and_capture(message: str) -> None:
|
|
34
35
|
commands = (
|
|
@@ -51,34 +52,30 @@ def test_completes_successfully(return_code: int) -> None:
|
|
|
51
52
|
assert cli.completes_successfully("exit", return_code, shell=True) == success # noqa: S604
|
|
52
53
|
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
cli.run("exit 1", shell=True) # noqa: S604
|
|
55
|
+
def test_run_commands() -> None:
|
|
56
|
+
commands = ("ls", "pwd")
|
|
57
|
+
cli.run_commands(*commands)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def
|
|
61
|
-
|
|
62
|
-
cli.run("non_existing_command")
|
|
60
|
+
def test_launch() -> None:
|
|
61
|
+
cli.launch("ls")
|
|
63
62
|
|
|
64
63
|
|
|
65
|
-
def
|
|
66
|
-
cli.
|
|
64
|
+
def test_launch_commands() -> None:
|
|
65
|
+
cli.launch_commands("ls")
|
|
67
66
|
|
|
68
67
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
cli.run_commands(*commands)
|
|
68
|
+
def test_run_commands_in_shell() -> None:
|
|
69
|
+
cli.run_commands_in_shell("ls")
|
|
72
70
|
|
|
73
71
|
|
|
74
|
-
def
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
def test_open() -> None:
|
|
73
|
+
open_function = "os.startfile" if os.name == "nt" else "subprocess.Popen"
|
|
74
|
+
with patch(open_function) as mocked_open:
|
|
75
|
+
cli.open_urls("pwd")
|
|
76
|
+
mocked_open.assert_called_once()
|
|
78
77
|
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
env = {"name": value}
|
|
84
|
-
assert cli.capture_output("echo", "$name", shell=True, env=env) == value # noqa: S604
|
|
79
|
+
def test_tty() -> None:
|
|
80
|
+
if os.name != "nt": # not supported on Windows
|
|
81
|
+
Runner(items=["ls"]).capture_tty_output()
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import platform
|
|
2
|
-
import shlex
|
|
3
|
-
import warnings
|
|
4
|
-
from collections.abc import Iterator
|
|
5
|
-
|
|
6
|
-
from .run import completes_successfully, run
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def install(*packages: str, install_command: str | None = None) -> None:
|
|
10
|
-
is_linux = platform.system() == "Linux"
|
|
11
|
-
if is_linux:
|
|
12
|
-
_install(*packages, install_command=install_command)
|
|
13
|
-
else:
|
|
14
|
-
message = "Required packages can only be installed on Linux"
|
|
15
|
-
warnings.warn(message, stacklevel=2)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _install(*packages: str, install_command: str | None = None) -> None:
|
|
19
|
-
if install_command is None:
|
|
20
|
-
install_command = next(extract_package_manager_command(), None)
|
|
21
|
-
assert install_command is not None
|
|
22
|
-
for package in packages:
|
|
23
|
-
args = shlex.split(package)
|
|
24
|
-
run(install_command, *args, root=True, check=False)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def extract_package_manager_command() -> Iterator[str]:
|
|
28
|
-
commands = {"apt": "apt install -y", "pacman": "pacman -S --noconfirm"}
|
|
29
|
-
for package_manager, command in commands.items():
|
|
30
|
-
if completes_successfully("which", package_manager):
|
|
31
|
-
yield command
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|