poetry-plugin-hook 0.0.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- poetry_plugin_hook-0.0.0/LICENSE +21 -0
- poetry_plugin_hook-0.0.0/PKG-INFO +55 -0
- poetry_plugin_hook-0.0.0/README.md +38 -0
- poetry_plugin_hook-0.0.0/poetry_plugin_hook/__init__.py +17 -0
- poetry_plugin_hook-0.0.0/poetry_plugin_hook/latest.py +72 -0
- poetry_plugin_hook-0.0.0/poetry_plugin_hook/redirect.py +82 -0
- poetry_plugin_hook-0.0.0/poetry_plugin_hook/sync.py +90 -0
- poetry_plugin_hook-0.0.0/pyproject.toml +18 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Christoph Dörrer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,55 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: poetry-plugin-hook
|
3
|
+
Version: 0.0.0
|
4
|
+
Summary: poetry plugin to register new command to check if all dependencies are up-to-date
|
5
|
+
Author: Christoph Dörrer
|
6
|
+
Author-email: d-chris@web.de
|
7
|
+
Requires-Python: >=3.9,<4.0
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
13
|
+
Requires-Dist: cleo (>=2.1.0,<3.0.0)
|
14
|
+
Requires-Dist: poetry (>=1.8.0,<2.0.0)
|
15
|
+
Description-Content-Type: text/markdown
|
16
|
+
|
17
|
+
# poetry-plugin-latest
|
18
|
+
|
19
|
+
poetry plugin to add a command to check if all dependencies are up-to-date
|
20
|
+
|
21
|
+
```cmd
|
22
|
+
$ poetry latest --help
|
23
|
+
|
24
|
+
Description:
|
25
|
+
Check if all top-level dependencies are up-to-date
|
26
|
+
|
27
|
+
Usage:
|
28
|
+
latest [options] [--] [<package>]
|
29
|
+
|
30
|
+
Arguments:
|
31
|
+
package The package to inspect
|
32
|
+
|
33
|
+
Options:
|
34
|
+
--without=WITHOUT The dependency groups to ignore. (multiple values allowed)
|
35
|
+
--with=WITH The optional dependency groups to include. (multiple values allowed)
|
36
|
+
--only=ONLY The only dependency groups to include. (multiple values allowed)
|
37
|
+
-l, --latest Show the latest version. (option is always True)
|
38
|
+
-o, --outdated Show the latest version but only for packages that are outdated. (option is always True)
|
39
|
+
-T, --top-level Show only top-level dependencies. (option is always True)
|
40
|
+
-h, --help Display help for the given command. When no command is given display help for the list command.
|
41
|
+
-q, --quiet Do not output any message.
|
42
|
+
-V, --version Display this application version.
|
43
|
+
--ansi Force ANSI output.
|
44
|
+
--no-ansi Disable ANSI output.
|
45
|
+
-n, --no-interaction Do not ask any interactive question.
|
46
|
+
--no-plugins Disables plugins.
|
47
|
+
--no-cache Disables Poetry source caches.
|
48
|
+
-C, --directory=DIRECTORY The working directory for the Poetry command (defaults to the current working directory).
|
49
|
+
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
|
50
|
+
|
51
|
+
Help:
|
52
|
+
The show command displays detailed information about a package, or
|
53
|
+
lists all packages available.
|
54
|
+
```
|
55
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# poetry-plugin-latest
|
2
|
+
|
3
|
+
poetry plugin to add a command to check if all dependencies are up-to-date
|
4
|
+
|
5
|
+
```cmd
|
6
|
+
$ poetry latest --help
|
7
|
+
|
8
|
+
Description:
|
9
|
+
Check if all top-level dependencies are up-to-date
|
10
|
+
|
11
|
+
Usage:
|
12
|
+
latest [options] [--] [<package>]
|
13
|
+
|
14
|
+
Arguments:
|
15
|
+
package The package to inspect
|
16
|
+
|
17
|
+
Options:
|
18
|
+
--without=WITHOUT The dependency groups to ignore. (multiple values allowed)
|
19
|
+
--with=WITH The optional dependency groups to include. (multiple values allowed)
|
20
|
+
--only=ONLY The only dependency groups to include. (multiple values allowed)
|
21
|
+
-l, --latest Show the latest version. (option is always True)
|
22
|
+
-o, --outdated Show the latest version but only for packages that are outdated. (option is always True)
|
23
|
+
-T, --top-level Show only top-level dependencies. (option is always True)
|
24
|
+
-h, --help Display help for the given command. When no command is given display help for the list command.
|
25
|
+
-q, --quiet Do not output any message.
|
26
|
+
-V, --version Display this application version.
|
27
|
+
--ansi Force ANSI output.
|
28
|
+
--no-ansi Disable ANSI output.
|
29
|
+
-n, --no-interaction Do not ask any interactive question.
|
30
|
+
--no-plugins Disables plugins.
|
31
|
+
--no-cache Disables Poetry source caches.
|
32
|
+
-C, --directory=DIRECTORY The working directory for the Poetry command (defaults to the current working directory).
|
33
|
+
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
|
34
|
+
|
35
|
+
Help:
|
36
|
+
The show command displays detailed information about a package, or
|
37
|
+
lists all packages available.
|
38
|
+
```
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from poetry.plugins.application_plugin import ApplicationPlugin
|
2
|
+
|
3
|
+
from .latest import LatestCommand
|
4
|
+
from .sync import SyncCommand
|
5
|
+
|
6
|
+
|
7
|
+
class HookPlugin(ApplicationPlugin):
|
8
|
+
def activate(self, application):
|
9
|
+
|
10
|
+
application.command_loader.register_factory(
|
11
|
+
LatestCommand.name,
|
12
|
+
lambda: LatestCommand(),
|
13
|
+
)
|
14
|
+
application.command_loader.register_factory(
|
15
|
+
SyncCommand.name,
|
16
|
+
lambda: SyncCommand(),
|
17
|
+
)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
from poetry.console.commands.show import ShowCommand
|
4
|
+
|
5
|
+
from .redirect import buffered_io, strip_ansi
|
6
|
+
|
7
|
+
|
8
|
+
class LatestCommand(ShowCommand):
|
9
|
+
name = "hook latest"
|
10
|
+
description = "Check if all top-level dependencies are up-to-date."
|
11
|
+
help = ""
|
12
|
+
|
13
|
+
_dependencies = re.compile(
|
14
|
+
r"^(?P<package>\w\S+)\s+"
|
15
|
+
r"(?P<current>\d\S+)\s+"
|
16
|
+
r"(?P<latest>\d\S+)\s+"
|
17
|
+
r"(?P<description>\w.*?)$",
|
18
|
+
re.MULTILINE,
|
19
|
+
)
|
20
|
+
|
21
|
+
_true_options = ["latest", "outdated", "top-level"]
|
22
|
+
_del_options = ["no-dev", "tree", "all", "why"]
|
23
|
+
|
24
|
+
def configure(self) -> None:
|
25
|
+
"""
|
26
|
+
Modifiy all options from `poetry show` to fit the `poetry latest` command.
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
None
|
30
|
+
"""
|
31
|
+
|
32
|
+
self.options = [
|
33
|
+
option for option in self.options if option.name not in self._del_options
|
34
|
+
]
|
35
|
+
|
36
|
+
for opt in filter(lambda o: o.name in self._true_options, self.options):
|
37
|
+
opt._description += " <warning>(option is always True)</warning>"
|
38
|
+
|
39
|
+
super().configure()
|
40
|
+
|
41
|
+
def handle(self) -> int:
|
42
|
+
"""
|
43
|
+
Executes `poetry show -o -T` to check for outdated dependencies.
|
44
|
+
|
45
|
+
Catches stdout to check for dependencies and returns non-zero.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
int: Non-zero if there are outdated dependencies, zero otherwise.
|
49
|
+
"""
|
50
|
+
|
51
|
+
# force options to True, `poetry show -o -T`
|
52
|
+
for option in self._true_options:
|
53
|
+
self.io.input.set_option(option, True)
|
54
|
+
|
55
|
+
# redirect output to check for outdated dependencies
|
56
|
+
with buffered_io(self) as io:
|
57
|
+
super().handle()
|
58
|
+
text = io.fetch_output()
|
59
|
+
|
60
|
+
# count outdated dependencies
|
61
|
+
outdated = len(
|
62
|
+
self._dependencies.findall(
|
63
|
+
strip_ansi(text),
|
64
|
+
)
|
65
|
+
)
|
66
|
+
|
67
|
+
if outdated == 0:
|
68
|
+
self.line("All top-level dependencies are up-to-date.")
|
69
|
+
else:
|
70
|
+
self.line(text)
|
71
|
+
|
72
|
+
return outdated
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import contextlib
|
2
|
+
import re
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from typing import Generator, List
|
5
|
+
|
6
|
+
from cleo.io.buffered_io import BufferedIO
|
7
|
+
from cleo.io.io import IO
|
8
|
+
|
9
|
+
|
10
|
+
class CommandCleo(ABC):
|
11
|
+
@property
|
12
|
+
@abstractmethod
|
13
|
+
def _io(self) -> IO:
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
def strip_ansi(text: str) -> str:
|
18
|
+
"""
|
19
|
+
Remove ANSI escape sequences from a string.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
text (str): The string to remove ANSI escape sequences from.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
str: The string without ANSI escape sequences.
|
26
|
+
"""
|
27
|
+
return re.sub(r"\x1B[@-_][0-?]*[ -/]*[@-~]", "", text)
|
28
|
+
|
29
|
+
|
30
|
+
@contextlib.contextmanager
|
31
|
+
def buffered_io(
|
32
|
+
*args: CommandCleo,
|
33
|
+
**kwargs,
|
34
|
+
) -> Generator[BufferedIO, None, None]:
|
35
|
+
"""
|
36
|
+
Context manager that temporarily replaces the I/O of multiple Poetry commands with
|
37
|
+
the same buffered I/O to capture their output.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
*cmds (List[CommandCleo]): The Poetry commands whose I/O will be captured.
|
41
|
+
**kwargs: Additional keyword arguments to pass to the BufferedIO constructor.
|
42
|
+
|
43
|
+
Yields:
|
44
|
+
BufferedIO: The buffered I/O object.
|
45
|
+
|
46
|
+
Raises:
|
47
|
+
ValueError: If any of the commands does not have an I/O attribute.
|
48
|
+
|
49
|
+
Example:
|
50
|
+
```python
|
51
|
+
with buffered_ios(cmd1, cmd2, decorated=False) as io:
|
52
|
+
# Perform operations with the buffered I/O
|
53
|
+
output = io.fetch_output()
|
54
|
+
```
|
55
|
+
"""
|
56
|
+
|
57
|
+
# perform check if all commands have a `_io` attribute
|
58
|
+
original: List[IO] = []
|
59
|
+
|
60
|
+
for cmd in args:
|
61
|
+
if not hasattr(cmd, "_io"):
|
62
|
+
raise ValueError(f"Command {cmd} does not have an I/O attribute.")
|
63
|
+
|
64
|
+
original.append(cmd._io)
|
65
|
+
|
66
|
+
# create a new buffered I/O object
|
67
|
+
io = BufferedIO(
|
68
|
+
input=kwargs.pop("input", original[0].input),
|
69
|
+
decorated=kwargs.pop("decorated", original[0].output.is_decorated()),
|
70
|
+
**kwargs,
|
71
|
+
)
|
72
|
+
|
73
|
+
try:
|
74
|
+
# assign the buffered I/O object to all commands
|
75
|
+
for cmd in args:
|
76
|
+
cmd._io = io
|
77
|
+
|
78
|
+
yield io
|
79
|
+
finally:
|
80
|
+
# restore the original I/O objects
|
81
|
+
for cmd, original_io in zip(args, original):
|
82
|
+
cmd._io = original_io
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
from cleo.exceptions import CleoError
|
4
|
+
from cleo.helpers import option
|
5
|
+
from poetry.console.commands.install import InstallCommand
|
6
|
+
|
7
|
+
from .redirect import buffered_io, strip_ansi
|
8
|
+
|
9
|
+
|
10
|
+
class SyncCommand(InstallCommand):
|
11
|
+
name = "hook sync"
|
12
|
+
description = "Install the current project and synchronize the environment."
|
13
|
+
help = ""
|
14
|
+
|
15
|
+
_true_options = ["sync"]
|
16
|
+
_del_options = ["no-dev", "remove-untracked", "compile"]
|
17
|
+
_exit_codes = ["any", "installs", "updates", "removals"]
|
18
|
+
|
19
|
+
_operations = re.compile(
|
20
|
+
r"^Package operations: "
|
21
|
+
r"(?P<installs>\d+)\D+"
|
22
|
+
r"(?P<updates>\d+)\D+"
|
23
|
+
r"(?P<removals>\d+)\D+"
|
24
|
+
r"(?P<skipped>\d+)\D+$",
|
25
|
+
re.MULTILINE,
|
26
|
+
)
|
27
|
+
|
28
|
+
def configure(self) -> None:
|
29
|
+
|
30
|
+
self.options = [
|
31
|
+
option(
|
32
|
+
"exit",
|
33
|
+
description=(
|
34
|
+
"Specify the value to return as exitcode. "
|
35
|
+
f"<info>choices={str(self._exit_codes)}</info>"
|
36
|
+
),
|
37
|
+
flag=False,
|
38
|
+
default="any",
|
39
|
+
)
|
40
|
+
] + [option for option in self.options if option.name not in self._del_options]
|
41
|
+
|
42
|
+
for opt in filter(lambda o: o.name in self._true_options, self.options):
|
43
|
+
opt._description += " <warning>(option is always True)</warning>"
|
44
|
+
|
45
|
+
super().configure()
|
46
|
+
|
47
|
+
def handle(self) -> int:
|
48
|
+
|
49
|
+
# check if the exit option is valid
|
50
|
+
exit = self.io.input.option("exit")
|
51
|
+
if exit not in self._exit_codes:
|
52
|
+
raise CleoError(f"Invalid option: {exit=}")
|
53
|
+
|
54
|
+
# force options to `poetry install --sync`
|
55
|
+
for opt in self._true_options:
|
56
|
+
self.io.input.set_option(opt, True)
|
57
|
+
|
58
|
+
with buffered_io(
|
59
|
+
self.installer.executor,
|
60
|
+
self.installer,
|
61
|
+
self,
|
62
|
+
) as io:
|
63
|
+
super().handle()
|
64
|
+
stdout = io.fetch_output()
|
65
|
+
|
66
|
+
# parse the output for matching lines and take the last one
|
67
|
+
match: re.Match = list(
|
68
|
+
self._operations.finditer(
|
69
|
+
strip_ansi(stdout),
|
70
|
+
)
|
71
|
+
)[-1]
|
72
|
+
|
73
|
+
# retrive the exit code
|
74
|
+
try:
|
75
|
+
result = int(match.group(exit))
|
76
|
+
except IndexError:
|
77
|
+
result = 0
|
78
|
+
|
79
|
+
for code in self._exit_codes:
|
80
|
+
try:
|
81
|
+
result += int(match.group(code))
|
82
|
+
except IndexError:
|
83
|
+
pass
|
84
|
+
|
85
|
+
if result == 0:
|
86
|
+
self.line(match.group(0))
|
87
|
+
else:
|
88
|
+
self.line(stdout)
|
89
|
+
|
90
|
+
return result
|
@@ -0,0 +1,18 @@
|
|
1
|
+
[tool.poetry]
|
2
|
+
name = "poetry-plugin-hook"
|
3
|
+
version = "0.0.0"
|
4
|
+
description = "poetry plugin to register new command to check if all dependencies are up-to-date"
|
5
|
+
authors = ["Christoph Dörrer <d-chris@web.de>"]
|
6
|
+
readme = "README.md"
|
7
|
+
|
8
|
+
[tool.poetry.dependencies]
|
9
|
+
python = "^3.9"
|
10
|
+
poetry = "^1.8.0"
|
11
|
+
cleo = "^2.1.0"
|
12
|
+
|
13
|
+
[tool.poetry.plugins."poetry.application.plugin"]
|
14
|
+
hook = "poetry_plugin_hook:HookPlugin"
|
15
|
+
|
16
|
+
[build-system]
|
17
|
+
requires = ["poetry-core"]
|
18
|
+
build-backend = "poetry.core.masonry.api"
|