porringer 0.1.0__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.
- porringer-0.1.0/LICENSE.md +21 -0
- porringer-0.1.0/PKG-INFO +16 -0
- porringer-0.1.0/README.md +1 -0
- porringer-0.1.0/porringer/__init__.py +5 -0
- porringer-0.1.0/porringer/api.py +21 -0
- porringer-0.1.0/porringer/backend/__init__.py +5 -0
- porringer-0.1.0/porringer/backend/builder.py +92 -0
- porringer-0.1.0/porringer/backend/command/__init__.py +5 -0
- porringer-0.1.0/porringer/backend/command/plugin.py +62 -0
- porringer-0.1.0/porringer/backend/command/self.py +48 -0
- porringer-0.1.0/porringer/backend/command/version.py +1 -0
- porringer-0.1.0/porringer/backend/resolver.py +50 -0
- porringer-0.1.0/porringer/backend/schema.py +28 -0
- porringer-0.1.0/porringer/console/__init__.py +5 -0
- porringer-0.1.0/porringer/console/command/__init__.py +1 -0
- porringer-0.1.0/porringer/console/command/plugin.py +66 -0
- porringer-0.1.0/porringer/console/command/self.py +29 -0
- porringer-0.1.0/porringer/console/entry.py +68 -0
- porringer-0.1.0/porringer/console/schema.py +38 -0
- porringer-0.1.0/porringer/core/__init__.py +5 -0
- porringer-0.1.0/porringer/core/plugin_schema/__init__.py +5 -0
- porringer-0.1.0/porringer/core/plugin_schema/environment.py +124 -0
- porringer-0.1.0/porringer/core/schema.py +85 -0
- porringer-0.1.0/porringer/plugin/__init__.py +4 -0
- porringer-0.1.0/porringer/plugin/pip/__init__.py +5 -0
- porringer-0.1.0/porringer/plugin/pip/plugin.py +103 -0
- porringer-0.1.0/porringer/plugin/pipx/__init__.py +5 -0
- porringer-0.1.0/porringer/plugin/pipx/plugin.py +108 -0
- porringer-0.1.0/porringer/plugin/winget/__init__.py +5 -0
- porringer-0.1.0/porringer/plugin/winget/plugin.py +129 -0
- porringer-0.1.0/porringer/py.typed +0 -0
- porringer-0.1.0/porringer/schema.py +65 -0
- porringer-0.1.0/porringer/test/mock/__init__.py +4 -0
- porringer-0.1.0/porringer/test/mock/environment.py +70 -0
- porringer-0.1.0/porringer/test/pytest/__init__.py +4 -0
- porringer-0.1.0/porringer/test/pytest/plugin.py +36 -0
- porringer-0.1.0/porringer/test/pytest/shared.py +90 -0
- porringer-0.1.0/porringer/test/pytest/tests.py +31 -0
- porringer-0.1.0/porringer/test/pytest/variants.py +42 -0
- porringer-0.1.0/porringer/utility/__init__.py +5 -0
- porringer-0.1.0/porringer/utility/exception.py +70 -0
- porringer-0.1.0/porringer/utility/plugin.py +36 -0
- porringer-0.1.0/porringer/utility/py.typed +0 -0
- porringer-0.1.0/porringer/utility/utility.py +44 -0
- porringer-0.1.0/pyproject.toml +131 -0
- porringer-0.1.0/tests/__init__.py +5 -0
- porringer-0.1.0/tests/integration/__init__.py +5 -0
- porringer-0.1.0/tests/integration/plugins/__init__.py +5 -0
- porringer-0.1.0/tests/integration/plugins/pip/__init__.py +5 -0
- porringer-0.1.0/tests/integration/plugins/pip/test_environment.py +20 -0
- porringer-0.1.0/tests/integration/plugins/pipx/__init__.py +5 -0
- porringer-0.1.0/tests/integration/plugins/pipx/test_environment.py +20 -0
- porringer-0.1.0/tests/integration/plugins/winget/__init__.py +5 -0
- porringer-0.1.0/tests/integration/plugins/winget/test_environment.py +20 -0
- porringer-0.1.0/tests/unit/__init__.py +5 -0
- porringer-0.1.0/tests/unit/plugins/__init__.py +5 -0
- porringer-0.1.0/tests/unit/plugins/pip/__init__.py +5 -0
- porringer-0.1.0/tests/unit/plugins/pip/test_environment.py +20 -0
- porringer-0.1.0/tests/unit/plugins/pipx/__init__.py +5 -0
- porringer-0.1.0/tests/unit/plugins/pipx/test_environment.py +20 -0
- porringer-0.1.0/tests/unit/plugins/winget/__init__.py +5 -0
- porringer-0.1.0/tests/unit/plugins/winget/test_environment.py +20 -0
- porringer-0.1.0/tests/unit/test_cli.py +19 -0
- porringer-0.1.0/tests/unit/test_command_plugin.py +34 -0
- porringer-0.1.0/tests/unit/test_command_self.py +41 -0
- porringer-0.1.0/tests/unit/test_plugin_schema.py +5 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Synodic Software
|
|
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.
|
porringer-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: porringer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Author-Email: Synodic Software <contact@synodic.software>
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: homepage, https://github.com/Synodic-Software/Porringer
|
|
7
|
+
Project-URL: repository, https://github.com/Synodic-Software/Porringer
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Requires-Dist: typer[all]>=0.15.2
|
|
10
|
+
Requires-Dist: pydantic>=2.11.3
|
|
11
|
+
Requires-Dist: platformdirs>=4.3.7
|
|
12
|
+
Requires-Dist: userpath>=1.9.2
|
|
13
|
+
Requires-Dist: packaging>=24.2
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Porringer
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Porringer
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""API for Porringer"""
|
|
2
|
+
|
|
3
|
+
from porringer.backend.command.plugin import PluginCommands
|
|
4
|
+
from porringer.backend.command.self import SelfCommands
|
|
5
|
+
from porringer.backend.resolver import resolve_configuration
|
|
6
|
+
from porringer.backend.schema import Configuration, GlobalConfiguration
|
|
7
|
+
from porringer.schema import (
|
|
8
|
+
APIParameters,
|
|
9
|
+
LocalConfiguration,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class API:
|
|
14
|
+
"""_summary_"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, local_configuration: LocalConfiguration, parameters: APIParameters) -> None:
|
|
17
|
+
"""Initializes the API"""
|
|
18
|
+
self.configuration: Configuration = resolve_configuration(local_configuration, GlobalConfiguration())
|
|
19
|
+
self.parameters = parameters
|
|
20
|
+
self.plugin = PluginCommands(self.parameters.logger)
|
|
21
|
+
self.porringer = SelfCommands(self.parameters.logger)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Builder"""
|
|
2
|
+
|
|
3
|
+
from importlib import metadata
|
|
4
|
+
from inspect import getmodule
|
|
5
|
+
from logging import Logger
|
|
6
|
+
|
|
7
|
+
from packaging.version import Version
|
|
8
|
+
|
|
9
|
+
from porringer.core.plugin_schema.environment import Environment
|
|
10
|
+
from porringer.core.schema import Distribution, PluginParameters
|
|
11
|
+
from porringer.schema import PluginInformation
|
|
12
|
+
from porringer.utility.exception import PluginError
|
|
13
|
+
from porringer.utility.utility import canonicalize_type
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Builder:
|
|
17
|
+
"""Helper class for building Porringer projects"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, logger: Logger) -> None:
|
|
20
|
+
"""Initializes the builder"""
|
|
21
|
+
self.logger = logger
|
|
22
|
+
|
|
23
|
+
def find_environments(self) -> list[PluginInformation[Environment]]:
|
|
24
|
+
"""Searches for registered environment plugins
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
PluginError: Raised if there is no plugin found
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
A list of loaded plugins
|
|
31
|
+
"""
|
|
32
|
+
group_name = 'environment'
|
|
33
|
+
plugin_types: list[PluginInformation[Environment]] = []
|
|
34
|
+
|
|
35
|
+
# Filter entries by type
|
|
36
|
+
for entry_point in list(metadata.entry_points(group=f'porringer.{group_name}')):
|
|
37
|
+
loaded_type = entry_point.load()
|
|
38
|
+
|
|
39
|
+
canonicalized = canonicalize_type(loaded_type)
|
|
40
|
+
|
|
41
|
+
if entry_point.dist is None:
|
|
42
|
+
self.logger.error(f"Plugin '{canonicalized.name}' is not installed. Skipping")
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
# TODO: Add metadata to plugin information, percolate to pytest_synodic API
|
|
46
|
+
|
|
47
|
+
if not issubclass(loaded_type, Environment):
|
|
48
|
+
self.logger.warning(
|
|
49
|
+
f"Found incompatible plugin. The '{canonicalized.name}' plugin must be an instance"
|
|
50
|
+
f" of '{group_name}'"
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
self.logger.warning(f'{group_name} plugin found: {canonicalized.name} from {getmodule(loaded_type)}')
|
|
54
|
+
plugin_types.append(PluginInformation(loaded_type, entry_point.dist))
|
|
55
|
+
|
|
56
|
+
if not plugin_types:
|
|
57
|
+
raise PluginError(f'No {group_name} plugin was found')
|
|
58
|
+
|
|
59
|
+
return plugin_types
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def build_environment(environment_type: PluginInformation[Environment]) -> Environment:
|
|
63
|
+
"""Constructs a single environment from input type
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
environment_type: The type to construct
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
The instantiated environment
|
|
70
|
+
"""
|
|
71
|
+
pluginVersion = Version(environment_type.distribution.version)
|
|
72
|
+
pluginDistribution = Distribution(version=pluginVersion)
|
|
73
|
+
parameters = PluginParameters(distribution=pluginDistribution)
|
|
74
|
+
|
|
75
|
+
return environment_type.type(parameters)
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def build_environments(environment_types: list[PluginInformation[Environment]]) -> list[Environment]:
|
|
79
|
+
"""Constructs environments from input types
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
environment_types: The types to construct
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The instantiated environments
|
|
86
|
+
"""
|
|
87
|
+
environments: list[Environment] = []
|
|
88
|
+
|
|
89
|
+
for environment_type in environment_types:
|
|
90
|
+
environments.append(Builder.build_environment(environment_type))
|
|
91
|
+
|
|
92
|
+
return environments
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""The plugin command module."""
|
|
2
|
+
|
|
3
|
+
from logging import Logger
|
|
4
|
+
|
|
5
|
+
from porringer.backend.builder import Builder
|
|
6
|
+
from porringer.backend.resolver import resolve_list_plugins_parameters
|
|
7
|
+
from porringer.schema import ListPluginResults, ListPluginsParameters
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PluginCommands:
|
|
11
|
+
"""Plugin commands"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, logger: Logger) -> None:
|
|
14
|
+
"""Initialize the SelfCommands class.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
logger (Logger): Logger instance for logging actions.
|
|
18
|
+
"""
|
|
19
|
+
self.logger = logger
|
|
20
|
+
|
|
21
|
+
def list(self, parameters: ListPluginsParameters) -> list[ListPluginResults]:
|
|
22
|
+
"""Lists the plugins.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
parameters: The list command parameters.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
A list of registered plugins.
|
|
29
|
+
"""
|
|
30
|
+
self.logger.info('Listing plugins')
|
|
31
|
+
|
|
32
|
+
builder = Builder(self.logger)
|
|
33
|
+
|
|
34
|
+
environment_types = builder.find_environments()
|
|
35
|
+
|
|
36
|
+
environments = builder.build_environments(environment_types)
|
|
37
|
+
|
|
38
|
+
return resolve_list_plugins_parameters(environments)
|
|
39
|
+
|
|
40
|
+
def install(self, logger: Logger) -> None:
|
|
41
|
+
"""Install a plugin"""
|
|
42
|
+
logger.info('Installing plugin')
|
|
43
|
+
|
|
44
|
+
builder = Builder(logger)
|
|
45
|
+
|
|
46
|
+
environment_types = builder.find_environments()
|
|
47
|
+
|
|
48
|
+
def uninstall(self, logger: Logger) -> None:
|
|
49
|
+
"""Remove an installed plugin"""
|
|
50
|
+
logger.info('Uninstalling plugin')
|
|
51
|
+
|
|
52
|
+
builder = Builder(logger)
|
|
53
|
+
|
|
54
|
+
environment_types = builder.find_environments()
|
|
55
|
+
|
|
56
|
+
def update(self, logger: Logger) -> None:
|
|
57
|
+
"""Updates the plugins.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
logger: The logger.
|
|
61
|
+
"""
|
|
62
|
+
logger.info('Updating plugins')
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Utilities for managing and checking the Porringer installation version."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from logging import Logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SelfCommands:
|
|
10
|
+
"""Commands related to the Porringer installation."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, logger: Logger) -> None:
|
|
13
|
+
"""Initialize the SelfCommands class.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
logger: Logger instance for logging actions.
|
|
17
|
+
"""
|
|
18
|
+
self.logger = logger
|
|
19
|
+
|
|
20
|
+
def is_pipx_installation(self) -> bool:
|
|
21
|
+
"""Check if Porringer is installed via pipx.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
bool: True if the current Python environment is a pipx-managed venv, False otherwise.
|
|
25
|
+
"""
|
|
26
|
+
return sys.prefix.split(os.sep)[-3:-1] == ['pipx', 'venvs']
|
|
27
|
+
|
|
28
|
+
def update(self) -> None:
|
|
29
|
+
"""Upgrade the Porringer package using pipx if installed via pipx.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
NotImplementedError: If Porringer is not installed via pipx.
|
|
33
|
+
"""
|
|
34
|
+
if self.is_pipx_installation():
|
|
35
|
+
subprocess.run(['pipx', 'upgrade', 'porringer'], check=True)
|
|
36
|
+
else:
|
|
37
|
+
raise NotImplementedError()
|
|
38
|
+
|
|
39
|
+
def check(self) -> None:
|
|
40
|
+
"""Check for updates to the Porringer package using pipx if installed via pipx.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
NotImplementedError: If Porringer is not installed via pipx.
|
|
44
|
+
"""
|
|
45
|
+
if self.is_pipx_installation():
|
|
46
|
+
subprocess.run(['pipx', 'upgrade', 'porringer'], check=True)
|
|
47
|
+
else:
|
|
48
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Version utilities"""
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Resolves"""
|
|
2
|
+
|
|
3
|
+
from porringer.backend.schema import Configuration, GlobalConfiguration
|
|
4
|
+
from porringer.core.plugin_schema.environment import Environment
|
|
5
|
+
from porringer.schema import ListPluginResults, LocalConfiguration
|
|
6
|
+
from porringer.utility.utility import canonicalize_type
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_configuration(
|
|
10
|
+
local_configuration: LocalConfiguration, global_configuration: GlobalConfiguration
|
|
11
|
+
) -> Configuration:
|
|
12
|
+
"""Resolves the configuration.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
local_configuration: The local configuration.
|
|
16
|
+
global_configuration: The global configuration.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
The resolved configuration.
|
|
20
|
+
"""
|
|
21
|
+
local_configuration.cache_directory.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
global_configuration.config_directory.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
global_configuration.data_directory.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
|
|
26
|
+
return Configuration(
|
|
27
|
+
cache_directory=local_configuration.cache_directory,
|
|
28
|
+
config_directory=global_configuration.config_directory,
|
|
29
|
+
data_directory=global_configuration.data_directory,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def resolve_list_plugins_parameters(environment: list[Environment]) -> list[ListPluginResults]:
|
|
34
|
+
"""Resolves the list plugins parameters.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
environment: The environment.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A list of plugin metadata.
|
|
41
|
+
"""
|
|
42
|
+
plugin_metadata: list[ListPluginResults] = []
|
|
43
|
+
|
|
44
|
+
for plugin in environment:
|
|
45
|
+
canonicalized = canonicalize_type(type(plugin))
|
|
46
|
+
|
|
47
|
+
resolved_metadata = ListPluginResults(canonicalized.name, plugin.distribution.version, False)
|
|
48
|
+
plugin_metadata.append(resolved_metadata)
|
|
49
|
+
|
|
50
|
+
return plugin_metadata
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Backend schema"""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from platformdirs import user_config_dir, user_data_dir
|
|
6
|
+
from pydantic import BaseModel, DirectoryPath, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GlobalConfiguration(BaseModel):
|
|
10
|
+
"""Global configuration that Porringer manages"""
|
|
11
|
+
|
|
12
|
+
config_directory: Path = Field(
|
|
13
|
+
default=Path(user_config_dir('porringer', 'synodic')),
|
|
14
|
+
description='The configuration directory',
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
data_directory: Path = Field(
|
|
18
|
+
default=Path(user_data_dir('porringer', 'synodic')),
|
|
19
|
+
description='The data directory',
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Configuration(BaseModel):
|
|
24
|
+
"""Resolved configuration"""
|
|
25
|
+
|
|
26
|
+
cache_directory: DirectoryPath
|
|
27
|
+
config_directory: DirectoryPath
|
|
28
|
+
data_directory: DirectoryPath
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Commands for the Porringer console application."""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Porringer CLI plugin command module"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from porringer.api import API
|
|
9
|
+
from porringer.console.schema import Configuration
|
|
10
|
+
from porringer.schema import APIParameters, ListPluginsParameters
|
|
11
|
+
|
|
12
|
+
app = typer.Typer()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command('list')
|
|
16
|
+
def plugin_list(
|
|
17
|
+
context: typer.Context,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Lists available plugins
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
context: The click context
|
|
23
|
+
"""
|
|
24
|
+
configuration = context.ensure_object(Configuration)
|
|
25
|
+
|
|
26
|
+
api_parameters = APIParameters(logging.getLogger('porringer'))
|
|
27
|
+
api = API(configuration.local_configuration, api_parameters)
|
|
28
|
+
|
|
29
|
+
list_parameters = ListPluginsParameters()
|
|
30
|
+
results = api.plugin.list(list_parameters)
|
|
31
|
+
|
|
32
|
+
for result in results:
|
|
33
|
+
configuration.console.print(result)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.command('install')
|
|
37
|
+
def plugin_install(
|
|
38
|
+
context: typer.Context, plugins: Annotated[list[str], typer.Argument(help='Plugins to install')]
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Install plugins"""
|
|
41
|
+
for plugin in plugins:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command('update')
|
|
46
|
+
def plugin_update(
|
|
47
|
+
context: typer.Context, plugins: Annotated[list[str], typer.Argument(help='Plugins to update')]
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Update plugins"""
|
|
50
|
+
for plugin in plugins:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command('uninstall')
|
|
55
|
+
def plugin_uninstall(
|
|
56
|
+
context: typer.Context, plugins: Annotated[list[str], typer.Argument(help='Plugins to remove')]
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Remove installed plugins"""
|
|
59
|
+
for plugin in plugins:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.callback(invoke_without_command=True, no_args_is_help=True)
|
|
64
|
+
def application() -> None:
|
|
65
|
+
"""Plugin management and operations"""
|
|
66
|
+
pass
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Porringer CLI self command module."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
app = typer.Typer()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@app.command('update')
|
|
9
|
+
def self_update() -> None:
|
|
10
|
+
"""Updates the Porringer application.
|
|
11
|
+
|
|
12
|
+
Raises:
|
|
13
|
+
NotImplementedError: This functionality is not yet implemented.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command('check')
|
|
18
|
+
def self_check() -> None:
|
|
19
|
+
"""Checks for updates to the Porringer application.
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
NotImplementedError: This functionality is not yet implemented.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.callback(invoke_without_command=True, no_args_is_help=True)
|
|
27
|
+
def application() -> None:
|
|
28
|
+
"""Management of the Porringer instance running the CLI application."""
|
|
29
|
+
pass
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Typer CLI Application"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from porringer.console.command.plugin import app as plugin_app
|
|
10
|
+
from porringer.console.command.self import app as self_app
|
|
11
|
+
from porringer.console.schema import LOG_LEVELS, MAX_VERBOSITY_LEVEL, Configuration
|
|
12
|
+
|
|
13
|
+
# TODO: Hook up version to the version in pyproject.toml
|
|
14
|
+
__version__ = '0.1.0'
|
|
15
|
+
|
|
16
|
+
app = typer.Typer()
|
|
17
|
+
app.add_typer(plugin_app, name='plugin')
|
|
18
|
+
app.add_typer(self_app, name='self')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TyperHandler(logging.Handler):
|
|
22
|
+
"""A logging handler that outputs to typer"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, console: Console) -> None:
|
|
25
|
+
"""Initializes the handler"""
|
|
26
|
+
logging.Handler.__init__(self)
|
|
27
|
+
|
|
28
|
+
self.console = console
|
|
29
|
+
|
|
30
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
31
|
+
"""Emits the log record to typer"""
|
|
32
|
+
level = next(level for level in LOG_LEVELS if level.name == record.levelname)
|
|
33
|
+
self.console.print(record, style=level.colour)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def version_callback(value: bool) -> None:
|
|
37
|
+
"""Callback for the version option"""
|
|
38
|
+
if value:
|
|
39
|
+
print(f'Awesome CLI Version: {__version__}')
|
|
40
|
+
raise typer.Exit()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.callback(invoke_without_command=True, no_args_is_help=True)
|
|
44
|
+
def application(
|
|
45
|
+
context: typer.Context,
|
|
46
|
+
verbose: Annotated[int, typer.Option('--verbose', '-v', count=True, help='', min=0, max=MAX_VERBOSITY_LEVEL)] = 0,
|
|
47
|
+
debug: Annotated[bool, typer.Option('--debug', help='')] = False,
|
|
48
|
+
version: Annotated[
|
|
49
|
+
bool | None,
|
|
50
|
+
typer.Option('--version', callback=version_callback, is_eager=True),
|
|
51
|
+
] = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""A tool for automatic facilitation of program updates and package managers.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
context: The click context object
|
|
57
|
+
verbose: The input verbosity level
|
|
58
|
+
debug: The debug flag
|
|
59
|
+
version: The version request
|
|
60
|
+
"""
|
|
61
|
+
configuration = context.ensure_object(Configuration)
|
|
62
|
+
|
|
63
|
+
configuration.debug = debug
|
|
64
|
+
configuration.verbosity = verbose
|
|
65
|
+
|
|
66
|
+
logger = logging.getLogger('porringer')
|
|
67
|
+
handler = TyperHandler(configuration.console)
|
|
68
|
+
logger.addHandler(handler)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Data schemas for console commands"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import LiteralString
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from porringer.schema import LocalConfiguration
|
|
10
|
+
|
|
11
|
+
MAX_VERBOSITY_LEVEL = 3
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class LogLevel:
|
|
16
|
+
"""Log level metadata"""
|
|
17
|
+
|
|
18
|
+
name: LiteralString
|
|
19
|
+
colour: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
LOG_LEVELS: list[LogLevel] = [
|
|
23
|
+
LogLevel(name='ERROR', colour='red'),
|
|
24
|
+
LogLevel(name='WARNING', colour='yellow'),
|
|
25
|
+
LogLevel(name='INFO', colour='white'),
|
|
26
|
+
LogLevel(name='DEBUG', colour='bright_white'),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Configuration(BaseModel):
|
|
31
|
+
"""Configuration object for the CLI"""
|
|
32
|
+
|
|
33
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
34
|
+
|
|
35
|
+
console: Console = Console()
|
|
36
|
+
local_configuration: LocalConfiguration = LocalConfiguration()
|
|
37
|
+
debug: bool = False
|
|
38
|
+
verbosity: int = 0
|