aphelion-plugin-sdk 0.1.0__py3-none-any.whl
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.
- aphelion_plugin_sdk-0.1.0.dist-info/METADATA +114 -0
- aphelion_plugin_sdk-0.1.0.dist-info/RECORD +39 -0
- aphelion_plugin_sdk-0.1.0.dist-info/WHEEL +5 -0
- aphelion_plugin_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- aphelion_plugin_sdk-0.1.0.dist-info/top_level.txt +1 -0
- aphelion_sdk/__init__.py +151 -0
- aphelion_sdk/__main__.py +10 -0
- aphelion_sdk/audio/__init__.py +9 -0
- aphelion_sdk/cli/__init__.py +7 -0
- aphelion_sdk/cli/main.py +203 -0
- aphelion_sdk/host/__init__.py +16 -0
- aphelion_sdk/host/constants.py +17 -0
- aphelion_sdk/host/errors.py +20 -0
- aphelion_sdk/host/install.py +55 -0
- aphelion_sdk/host/locate.py +81 -0
- aphelion_sdk/host/models.py +33 -0
- aphelion_sdk/host/validate.py +75 -0
- aphelion_sdk/host/windows.py +80 -0
- aphelion_sdk/packaging/__init__.py +19 -0
- aphelion_sdk/packaging/builder.py +222 -0
- aphelion_sdk/packaging/discovery.py +250 -0
- aphelion_sdk/packaging/errors.py +20 -0
- aphelion_sdk/packaging/names.py +174 -0
- aphelion_sdk/packaging/project.py +241 -0
- aphelion_sdk/plugin.py +51 -0
- aphelion_sdk/properties.py +179 -0
- aphelion_sdk/py.typed +0 -0
- aphelion_sdk/registration.py +85 -0
- aphelion_sdk/types.py +19 -0
- aphelion_sdk/version.py +6 -0
- aphelion_sdk/video/__init__.py +7 -0
- aphelion_sdk/video/effect.py +93 -0
- aphelion_sdk/widgets/__init__.py +23 -0
- aphelion_sdk/widgets/base.py +76 -0
- aphelion_sdk/widgets/dialog.py +45 -0
- aphelion_sdk/widgets/host.py +105 -0
- aphelion_sdk/widgets/kinds.py +14 -0
- aphelion_sdk/widgets/panel.py +35 -0
- aphelion_sdk/widgets/qt.py +35 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aphelion-plugin-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Public SDK for Aphelion plugins (video now; audio later).
|
|
5
|
+
Author: youthx
|
|
6
|
+
License-Expression: LicenseRef-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/aphelion-engine/aphelion-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/aphelion-engine/aphelion-sdk
|
|
9
|
+
Keywords: aphelion,plugin,sdk,video,audio
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: numpy>=1.26.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: build>=1.2.0; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
17
|
+
|
|
18
|
+
# Aphelion Plugin SDK
|
|
19
|
+
|
|
20
|
+
Public API for plugins that run **inside Aphelion Editor**. This package sits beside `aphelion-editor`, not inside it.
|
|
21
|
+
|
|
22
|
+
Import **`aphelion_sdk` only**. Never import `core`, `effects`, `render`, or `ui`.
|
|
23
|
+
|
|
24
|
+
Video effects are available now (`VideoEffectPlugin`). Audio bases will land under `aphelion_sdk.audio` later.
|
|
25
|
+
|
|
26
|
+
Version **0.1.0**. Python **3.11+**. Install with **`pip install aphelion-plugin-sdk`**. Import **`aphelion_sdk`**.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install aphelion-plugin-sdk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
From the `aphelion-engine` root, with a venv active:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install -e ./aphelion-editor
|
|
38
|
+
pip install -e ./aphelion-sdk
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Installing the editor already depends on this package (`aphelion-plugin-sdk @ file:../aphelion-sdk`).
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
aphelion-sdk --version
|
|
45
|
+
python -m aphelion_sdk --help
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import aphelion_sdk
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@aphelion_sdk.register_plugin
|
|
55
|
+
class GrayscaleEffect(aphelion_sdk.VideoEffectPlugin):
|
|
56
|
+
plugin_name = "Grayscale"
|
|
57
|
+
plugin_category = "Plugins"
|
|
58
|
+
plugin_description = "Blend a frame toward grayscale."
|
|
59
|
+
plugin_color = (140, 140, 140)
|
|
60
|
+
|
|
61
|
+
def setup_effect_properties(self) -> None:
|
|
62
|
+
self.set_property(
|
|
63
|
+
"amount",
|
|
64
|
+
aphelion_sdk.slider_property(
|
|
65
|
+
100, 0, 100,
|
|
66
|
+
label="Amount",
|
|
67
|
+
suffix="%",
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def process_frame(
|
|
72
|
+
self,
|
|
73
|
+
frame: aphelion_sdk.Frame,
|
|
74
|
+
_frame_num: int,
|
|
75
|
+
) -> aphelion_sdk.Frame:
|
|
76
|
+
amount = self.float_value("amount", 100.0) / 100.0
|
|
77
|
+
luma = (
|
|
78
|
+
frame[..., 0] * 0.2126
|
|
79
|
+
+ frame[..., 1] * 0.7152
|
|
80
|
+
+ frame[..., 2] * 0.0722
|
|
81
|
+
)
|
|
82
|
+
gray = luma[..., None].repeat(3, axis=2)
|
|
83
|
+
return frame * (1.0 - amount) + gray * amount
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Drop the file in the editor's `plugins/` or `userdata/plugins/`, or pack a wheel (below). Time-independent effects should name the unused argument `_frame_num`.
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
|
|
90
|
+
- [`examples/grayscale_effect.py`](examples/grayscale_effect.py)
|
|
91
|
+
- [`examples/effect_with_widget.py`](examples/effect_with_widget.py) — dialog + panel via `widgets = (...)`
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
| Guide | Contents |
|
|
96
|
+
|---|---|
|
|
97
|
+
| [Authoring](docs/authoring.md) | Effect class, properties, discovery rules |
|
|
98
|
+
| [Widgets](docs/widgets.md) | Panels, dialogs, primitives, PyQt6 |
|
|
99
|
+
| [API reference](docs/api.md) | Public symbols |
|
|
100
|
+
| [Packaging](docs/packaging.md) | `aphelion-sdk build`, entry points, drop-in install |
|
|
101
|
+
| [Editor plugins](../aphelion-editor/docs/plugins.md) | How the host loads and reloads plugins |
|
|
102
|
+
|
|
103
|
+
## Package a plugin
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
aphelion-sdk build examples/grayscale_effect.py -o dist
|
|
107
|
+
pip install dist/aphelion_plugin_grayscale-*.whl
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Entry point group: `aphelion.plugins`. Widgets are declared on the plugin (`widgets = (MyDialog, MyPanel)`); they are not registered on their own.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
Proprietary (`LicenseRef-Proprietary` in `pyproject.toml`).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
aphelion_sdk/__init__.py,sha256=Tq_JL8Zd0VdUWfVByLowWvHbkciAqP_wwOoUMkR1-us,5180
|
|
2
|
+
aphelion_sdk/__main__.py,sha256=W5PU-J3zrWyMLkpkWOo9q8RG0ah2Da2Ua-kMsUZkVKY,215
|
|
3
|
+
aphelion_sdk/plugin.py,sha256=IiVSVH7V_20dr9knFdF_jsKwytGduKNBnUa-iB3FAGk,2053
|
|
4
|
+
aphelion_sdk/properties.py,sha256=Wq_ChTMZaKltNup3OpLtFIeuFEehTyVntGEY3ma1GSQ,4764
|
|
5
|
+
aphelion_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
aphelion_sdk/registration.py,sha256=ZnKewc1Vd2eZEwSJeG-MIYJiPt3NgDObxPaEZ9wmVQE,2805
|
|
7
|
+
aphelion_sdk/types.py,sha256=fcQrZyIKw51Mh8nNWbKeDZKlGaz7NgO8kqhaRff0j78,602
|
|
8
|
+
aphelion_sdk/version.py,sha256=DRDKPdzt6Ku28r5lgFuYvtmulhod8TmfX2b8UVmtql8,169
|
|
9
|
+
aphelion_sdk/audio/__init__.py,sha256=lQAb4POqnfot3fbg_UZiezJD1cH9CBBZXwFBAJziJxg,185
|
|
10
|
+
aphelion_sdk/cli/__init__.py,sha256=qAkOQZWWPoll4T5pGsVNsfWAsqwMF0hVXn2WQ3bM6mU,161
|
|
11
|
+
aphelion_sdk/cli/main.py,sha256=oxxmG-phVV9tk6TwMoWUIYHSJsB3tqnVNSVnXnjpr9g,6609
|
|
12
|
+
aphelion_sdk/host/__init__.py,sha256=7SYdewYdfYLAYxXqu97JE_KQlAXmWSnyPKbPVT99rPI,511
|
|
13
|
+
aphelion_sdk/host/constants.py,sha256=_M3EsBRX5IlhjrwQKwFAvtROrvIjP-XNv8iAJVfM2e8,704
|
|
14
|
+
aphelion_sdk/host/errors.py,sha256=uRRcUQjWjuwfS-F0sZ-QKj8WD39V7POt0MhdrhNJRQc,419
|
|
15
|
+
aphelion_sdk/host/install.py,sha256=yl-2DKiMx5-4rKCXET5qyZA1cL2RCKpWMwuJuAlG2xY,1973
|
|
16
|
+
aphelion_sdk/host/locate.py,sha256=lTFxnBD6_BKfn8LimDXx8IpqyNJtvnPKfx8IDTBcQRQ,2809
|
|
17
|
+
aphelion_sdk/host/models.py,sha256=XiCHewoaCDpNmOpyuwvkjIW1butlR4AyVODSEem4jmk,1063
|
|
18
|
+
aphelion_sdk/host/validate.py,sha256=gzHHbuUC7LJx0ywO55seWVOghZWrvUpcyaA2RZca0qc,2315
|
|
19
|
+
aphelion_sdk/host/windows.py,sha256=bCJf0-_5pNwDBegNJtvGaeFPgrXrhXP_961QL_Y_DHg,2607
|
|
20
|
+
aphelion_sdk/packaging/__init__.py,sha256=4WH_jAHxnxngsopPXcX3o2Y5bsXSa8oRGWO9XPhwfNg,666
|
|
21
|
+
aphelion_sdk/packaging/builder.py,sha256=qTtDsS9mXMaLyJDZr94jcl0oBvvQXh07_lOnEW_6O2c,7548
|
|
22
|
+
aphelion_sdk/packaging/discovery.py,sha256=mLV19IH2xVIQZcPAeL6YCfqLu6T2RU8s61GQF4x5Y_U,8979
|
|
23
|
+
aphelion_sdk/packaging/errors.py,sha256=ihIpSNHrBu_opkI8Mf-KGCbyiQh-T5TnPH_irV5TqqA,408
|
|
24
|
+
aphelion_sdk/packaging/names.py,sha256=MD7uWXWQSj8QZBU8GkbEPE-f4WAuAnvMLDUxoOIfd2k,4958
|
|
25
|
+
aphelion_sdk/packaging/project.py,sha256=Y0eTQfJhZyWmaXU4kSG-coXfUuyi3SJsgwagsrMN8I8,7822
|
|
26
|
+
aphelion_sdk/video/__init__.py,sha256=XHct9Eu4DixPmYkm6q3Gn4tLiuw1flnQ6rUvebnFeKc,211
|
|
27
|
+
aphelion_sdk/video/effect.py,sha256=b_YkK7zetpoi78RxcVVve9c7N6w2xV5X5_i5UIb88O0,3125
|
|
28
|
+
aphelion_sdk/widgets/__init__.py,sha256=9RDyHzzIgMm9RgNTyVqasuVHa2IblJDP2WiuT4z683c,737
|
|
29
|
+
aphelion_sdk/widgets/base.py,sha256=TtOpx6zfSNeafMH6Hsn9v_R3AvNHA_BD2pY8ztVcUAs,2841
|
|
30
|
+
aphelion_sdk/widgets/dialog.py,sha256=8s1_sw82UobBotm7EWTAjts2NYjPmNNuRxOFvTIWrUI,1669
|
|
31
|
+
aphelion_sdk/widgets/host.py,sha256=xjH6Ksuq97nl-I89IAielZZOQDIH-LdyO7KDOB2rApw,3512
|
|
32
|
+
aphelion_sdk/widgets/kinds.py,sha256=QU1u83-ReEShDk9Je7Di6yPJ4JWJPRu2nbzYueaBKzY,438
|
|
33
|
+
aphelion_sdk/widgets/panel.py,sha256=wPk8tUtfzgsRSY1HNMuP26N2tggX9wZKwSR2GhBGk6I,1237
|
|
34
|
+
aphelion_sdk/widgets/qt.py,sha256=hnXj3OchPzF4lMw15tnT9E0NJK_-0CDI4nPuzte1jIs,950
|
|
35
|
+
aphelion_plugin_sdk-0.1.0.dist-info/METADATA,sha256=IZgE7lNQ0bbGvJUg-i5rDDPcF4HeEfO_JCr5erelnkA,3660
|
|
36
|
+
aphelion_plugin_sdk-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
37
|
+
aphelion_plugin_sdk-0.1.0.dist-info/entry_points.txt,sha256=st-HkX1WMH1MzN4mqFMZ4lQaAXgMhI-Nw-kh52d_sfI,60
|
|
38
|
+
aphelion_plugin_sdk-0.1.0.dist-info/top_level.txt,sha256=tx-w30xPZbHoZHSQdaIC6GatkPMGdnC1mh6hEixMgRw,13
|
|
39
|
+
aphelion_plugin_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aphelion_sdk
|
aphelion_sdk/__init__.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Aphelion Plugin SDK.
|
|
2
|
+
|
|
3
|
+
Public, stable surface for custom Aphelion plugins. Authors should
|
|
4
|
+
``import aphelion_sdk`` and must never import editor internals
|
|
5
|
+
(``core``, ``effects``, ``render``, ``ui``).
|
|
6
|
+
|
|
7
|
+
Video effects subclass ``VideoEffectPlugin``. Attach UI with
|
|
8
|
+
``widgets = (MyDialog, MyPanel)``. Simple widgets use host primitives;
|
|
9
|
+
advanced widgets may import PyQt6 and return a ``QWidget`` from
|
|
10
|
+
``build_qt_widget`` or ``embed_native``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from importlib import import_module
|
|
16
|
+
from typing import TYPE_CHECKING, Final
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from aphelion_sdk.plugin import Plugin
|
|
20
|
+
from aphelion_sdk.properties import (
|
|
21
|
+
PluginProperty,
|
|
22
|
+
choice_property,
|
|
23
|
+
color_property,
|
|
24
|
+
custom_property,
|
|
25
|
+
number_property,
|
|
26
|
+
slider_property,
|
|
27
|
+
text_property,
|
|
28
|
+
toggle_property,
|
|
29
|
+
)
|
|
30
|
+
from aphelion_sdk.registration import (
|
|
31
|
+
clear_registered_plugins,
|
|
32
|
+
discover_installed_plugins,
|
|
33
|
+
get_registered_plugins,
|
|
34
|
+
register_plugin,
|
|
35
|
+
)
|
|
36
|
+
from aphelion_sdk.types import ColorRgb, Frame
|
|
37
|
+
from aphelion_sdk.version import __version__
|
|
38
|
+
from aphelion_sdk.video import VideoEffectPlugin
|
|
39
|
+
from aphelion_sdk.widgets import (
|
|
40
|
+
DialogWidget,
|
|
41
|
+
PanelWidget,
|
|
42
|
+
PluginWidget,
|
|
43
|
+
WidgetContext,
|
|
44
|
+
WidgetHost,
|
|
45
|
+
WidgetView,
|
|
46
|
+
coerce_qt_parent,
|
|
47
|
+
is_qt_widget,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"ColorRgb",
|
|
52
|
+
"DialogWidget",
|
|
53
|
+
"Frame",
|
|
54
|
+
"PanelWidget",
|
|
55
|
+
"Plugin",
|
|
56
|
+
"PluginProperty",
|
|
57
|
+
"PluginWidget",
|
|
58
|
+
"VideoEffectPlugin",
|
|
59
|
+
"WidgetContext",
|
|
60
|
+
"WidgetHost",
|
|
61
|
+
"WidgetView",
|
|
62
|
+
"__version__",
|
|
63
|
+
"choice_property",
|
|
64
|
+
"clear_registered_plugins",
|
|
65
|
+
"coerce_qt_parent",
|
|
66
|
+
"color_property",
|
|
67
|
+
"custom_property",
|
|
68
|
+
"discover_editors",
|
|
69
|
+
"discover_installed_plugins",
|
|
70
|
+
"get_registered_plugins",
|
|
71
|
+
"install_plugins_into_editor",
|
|
72
|
+
"locate_editor",
|
|
73
|
+
"is_qt_widget",
|
|
74
|
+
"number_property",
|
|
75
|
+
"register_plugin",
|
|
76
|
+
"slider_property",
|
|
77
|
+
"text_property",
|
|
78
|
+
"toggle_property",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
_EXPORT_MAP: Final[dict[str, tuple[str, str]]] = {
|
|
82
|
+
"ColorRgb": ("aphelion_sdk.types", "ColorRgb"),
|
|
83
|
+
"DialogWidget": ("aphelion_sdk.widgets.dialog", "DialogWidget"),
|
|
84
|
+
"Frame": ("aphelion_sdk.types", "Frame"),
|
|
85
|
+
"PanelWidget": ("aphelion_sdk.widgets.panel", "PanelWidget"),
|
|
86
|
+
"Plugin": ("aphelion_sdk.plugin", "Plugin"),
|
|
87
|
+
"PluginProperty": ("aphelion_sdk.properties", "PluginProperty"),
|
|
88
|
+
"PluginWidget": ("aphelion_sdk.widgets.base", "PluginWidget"),
|
|
89
|
+
"VideoEffectPlugin": ("aphelion_sdk.video", "VideoEffectPlugin"),
|
|
90
|
+
"WidgetContext": ("aphelion_sdk.widgets.host", "WidgetContext"),
|
|
91
|
+
"WidgetHost": ("aphelion_sdk.widgets.host", "WidgetHost"),
|
|
92
|
+
"WidgetView": ("aphelion_sdk.widgets.host", "WidgetView"),
|
|
93
|
+
"__version__": ("aphelion_sdk.version", "__version__"),
|
|
94
|
+
"choice_property": ("aphelion_sdk.properties", "choice_property"),
|
|
95
|
+
"clear_registered_plugins": (
|
|
96
|
+
"aphelion_sdk.registration",
|
|
97
|
+
"clear_registered_plugins",
|
|
98
|
+
),
|
|
99
|
+
"coerce_qt_parent": ("aphelion_sdk.widgets.qt", "coerce_qt_parent"),
|
|
100
|
+
"color_property": ("aphelion_sdk.properties", "color_property"),
|
|
101
|
+
"custom_property": ("aphelion_sdk.properties", "custom_property"),
|
|
102
|
+
"discover_editors": ("aphelion_sdk.host.locate", "discover_editors"),
|
|
103
|
+
"discover_installed_plugins": (
|
|
104
|
+
"aphelion_sdk.registration",
|
|
105
|
+
"discover_installed_plugins",
|
|
106
|
+
),
|
|
107
|
+
"install_plugins_into_editor": (
|
|
108
|
+
"aphelion_sdk.host.install",
|
|
109
|
+
"install_plugins_into_editor",
|
|
110
|
+
),
|
|
111
|
+
"locate_editor": ("aphelion_sdk.host.locate", "locate_editor"),
|
|
112
|
+
"get_registered_plugins": (
|
|
113
|
+
"aphelion_sdk.registration",
|
|
114
|
+
"get_registered_plugins",
|
|
115
|
+
),
|
|
116
|
+
"is_qt_widget": ("aphelion_sdk.widgets.qt", "is_qt_widget"),
|
|
117
|
+
"number_property": ("aphelion_sdk.properties", "number_property"),
|
|
118
|
+
"register_plugin": ("aphelion_sdk.registration", "register_plugin"),
|
|
119
|
+
"slider_property": ("aphelion_sdk.properties", "slider_property"),
|
|
120
|
+
"text_property": ("aphelion_sdk.properties", "text_property"),
|
|
121
|
+
"toggle_property": ("aphelion_sdk.properties", "toggle_property"),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def __getattr__(name: str) -> object:
|
|
126
|
+
"""Resolve a public SDK symbol on first access.
|
|
127
|
+
|
|
128
|
+
Parameters:
|
|
129
|
+
name: Attribute requested on ``aphelion_sdk``.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The exported object bound to ``name``.
|
|
133
|
+
|
|
134
|
+
Exceptions:
|
|
135
|
+
AttributeError: If ``name`` is not part of the public surface.
|
|
136
|
+
|
|
137
|
+
Side effects:
|
|
138
|
+
Imports the implementing submodule and caches the attribute.
|
|
139
|
+
"""
|
|
140
|
+
location: tuple[str, str] | None = _EXPORT_MAP.get(name)
|
|
141
|
+
if location is None:
|
|
142
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
143
|
+
module_name, attr_name = location
|
|
144
|
+
value: object = getattr(import_module(module_name), attr_name)
|
|
145
|
+
globals()[name] = value
|
|
146
|
+
return value
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def __dir__() -> list[str]:
|
|
150
|
+
"""Return module attributes including lazy public exports."""
|
|
151
|
+
return sorted(set(globals()) | set(_EXPORT_MAP))
|
aphelion_sdk/__main__.py
ADDED
aphelion_sdk/cli/main.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Argparse entry for ``aphelion-sdk`` and ``python -m aphelion_sdk``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from aphelion_sdk.host import EditorHostError, EditorInstall, locate_editor
|
|
11
|
+
from aphelion_sdk.host.install import install_plugins_into_editor
|
|
12
|
+
from aphelion_sdk.packaging import (
|
|
13
|
+
PackageBuildResult,
|
|
14
|
+
PluginPackageError,
|
|
15
|
+
build_plugin_package,
|
|
16
|
+
)
|
|
17
|
+
from aphelion_sdk.version import DISTRIBUTION_NAME, __version__
|
|
18
|
+
|
|
19
|
+
_DESCRIPTION: str = (
|
|
20
|
+
"Build Aphelion plugins, locate the installed editor, and copy "
|
|
21
|
+
"drop-in plugins into that editor. Pass a .py file or plugin "
|
|
22
|
+
"directory to wrap it, or a project root with pyproject.toml "
|
|
23
|
+
"to run a standard PEP 517 build."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
28
|
+
"""Parse CLI arguments and dispatch the requested command.
|
|
29
|
+
|
|
30
|
+
Parameters:
|
|
31
|
+
argv: Argument vector without the program name. Defaults to
|
|
32
|
+
``sys.argv[1:]``.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Process exit code. ``0`` on success, ``1`` on packaging errors,
|
|
36
|
+
``2`` on usage errors (argparse).
|
|
37
|
+
|
|
38
|
+
Exceptions:
|
|
39
|
+
None. Packaging failures are printed to stderr.
|
|
40
|
+
|
|
41
|
+
Side effects:
|
|
42
|
+
May write wheel files and print status to stdout/stderr.
|
|
43
|
+
"""
|
|
44
|
+
parser: argparse.ArgumentParser = _build_parser()
|
|
45
|
+
args: argparse.Namespace = parser.parse_args(list(argv) if argv is not None else None)
|
|
46
|
+
command: str | None = args.command
|
|
47
|
+
if command == "build":
|
|
48
|
+
return _handle_build(args)
|
|
49
|
+
if command == "locate":
|
|
50
|
+
return _handle_locate()
|
|
51
|
+
if command == "install":
|
|
52
|
+
return _handle_install(args)
|
|
53
|
+
parser.print_help()
|
|
54
|
+
return 2
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
58
|
+
"""Return the root parser with the ``build`` subcommand."""
|
|
59
|
+
parser = argparse.ArgumentParser(prog="aphelion-sdk", description=_DESCRIPTION)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--version",
|
|
62
|
+
action="version",
|
|
63
|
+
version=f"{DISTRIBUTION_NAME} {__version__}",
|
|
64
|
+
)
|
|
65
|
+
subparsers = parser.add_subparsers(dest="command", metavar="COMMAND")
|
|
66
|
+
_add_build_parser(subparsers)
|
|
67
|
+
_add_locate_parser(subparsers)
|
|
68
|
+
_add_install_parser(subparsers)
|
|
69
|
+
return parser
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _add_build_parser(
|
|
73
|
+
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Register ``aphelion-sdk build``."""
|
|
76
|
+
parser = subparsers.add_parser(
|
|
77
|
+
"build",
|
|
78
|
+
help="Build a pip wheel from plugin source or an existing project.",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"source",
|
|
82
|
+
nargs="?",
|
|
83
|
+
type=Path,
|
|
84
|
+
default=Path.cwd(),
|
|
85
|
+
help="Plugin .py file, plugin directory, or project root (default: cwd).",
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"-o",
|
|
89
|
+
"--output",
|
|
90
|
+
type=Path,
|
|
91
|
+
default=Path.cwd() / "dist",
|
|
92
|
+
help="Directory for built artifacts (default: ./dist).",
|
|
93
|
+
)
|
|
94
|
+
_add_build_metadata_flags(parser)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _add_locate_parser(
|
|
98
|
+
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Register ``aphelion-sdk locate``."""
|
|
101
|
+
subparsers.add_parser(
|
|
102
|
+
"locate",
|
|
103
|
+
help="Print the Aphelion Editor install this SDK will target.",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _add_install_parser(
|
|
108
|
+
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Register ``aphelion-sdk install``."""
|
|
111
|
+
parser = subparsers.add_parser(
|
|
112
|
+
"install",
|
|
113
|
+
help="Copy plugin sources into the located editor's userdata/plugins.",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"source",
|
|
117
|
+
type=Path,
|
|
118
|
+
help="Plugin .py file or directory of plugin modules.",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _handle_locate() -> int:
|
|
123
|
+
"""Print the discovered editor install."""
|
|
124
|
+
try:
|
|
125
|
+
install: EditorInstall = locate_editor()
|
|
126
|
+
except EditorHostError as exc:
|
|
127
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
128
|
+
return 1
|
|
129
|
+
kind: str = "frozen" if install.is_frozen else "source"
|
|
130
|
+
print(f"root: {install.root}")
|
|
131
|
+
print(f"executable: {install.executable}")
|
|
132
|
+
print(f"plugins: {install.user_plugin_dir}")
|
|
133
|
+
print(f"version: {install.version or '(unknown)'}")
|
|
134
|
+
print(f"source: {install.source} ({kind})")
|
|
135
|
+
return 0
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _handle_install(args: argparse.Namespace) -> int:
|
|
139
|
+
"""Copy plugin files into the located editor."""
|
|
140
|
+
try:
|
|
141
|
+
written: tuple[Path, ...] = install_plugins_into_editor(args.source)
|
|
142
|
+
except (EditorHostError, PluginPackageError) as exc:
|
|
143
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
144
|
+
return 1
|
|
145
|
+
print(f"Installed {len(written)} plugin file(s) into {written[0].parent}")
|
|
146
|
+
for path in written:
|
|
147
|
+
print(f" {path}")
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _add_build_metadata_flags(parser: argparse.ArgumentParser) -> None:
|
|
152
|
+
"""Attach optional package-identity flags to the build parser."""
|
|
153
|
+
parser.add_argument(
|
|
154
|
+
"-n",
|
|
155
|
+
"--name",
|
|
156
|
+
default=None,
|
|
157
|
+
help="Distribution name (generated packages only).",
|
|
158
|
+
)
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
"--package-version",
|
|
161
|
+
default="0.1.0",
|
|
162
|
+
help="Package version for generated packages (default: 0.1.0).",
|
|
163
|
+
)
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"--author",
|
|
166
|
+
default=None,
|
|
167
|
+
help="Package author (generated packages only).",
|
|
168
|
+
)
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
"--description",
|
|
171
|
+
default=None,
|
|
172
|
+
help="Package summary (generated packages only).",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _handle_build(args: argparse.Namespace) -> int:
|
|
177
|
+
"""Run the packaging builder and print the resulting artifacts."""
|
|
178
|
+
source: Path = args.source
|
|
179
|
+
output: Path = args.output
|
|
180
|
+
try:
|
|
181
|
+
result: PackageBuildResult = build_plugin_package(
|
|
182
|
+
source,
|
|
183
|
+
output,
|
|
184
|
+
distribution_name=args.name,
|
|
185
|
+
version=args.package_version,
|
|
186
|
+
author=args.author,
|
|
187
|
+
description=args.description,
|
|
188
|
+
)
|
|
189
|
+
except PluginPackageError as exc:
|
|
190
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
191
|
+
return 1
|
|
192
|
+
_print_success(result)
|
|
193
|
+
return 0
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _print_success(result: PackageBuildResult) -> None:
|
|
197
|
+
"""Write a short build summary to stdout."""
|
|
198
|
+
kind: str = "project" if result.mode == "project" else "plugin package"
|
|
199
|
+
print(f"Built {kind} {result.distribution_name} {result.version}")
|
|
200
|
+
for plugin in result.plugins:
|
|
201
|
+
print(f" plugin: {plugin.plugin_name} ({plugin.class_name})")
|
|
202
|
+
for artifact in result.artifacts:
|
|
203
|
+
print(f" {artifact}")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Locate the installed Aphelion Editor and install drop-in plugins into it."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from aphelion_sdk.host.errors import EditorHostError
|
|
6
|
+
from aphelion_sdk.host.install import install_plugins_into_editor
|
|
7
|
+
from aphelion_sdk.host.locate import discover_editors, locate_editor
|
|
8
|
+
from aphelion_sdk.host.models import EditorInstall
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"EditorHostError",
|
|
12
|
+
"EditorInstall",
|
|
13
|
+
"discover_editors",
|
|
14
|
+
"install_plugins_into_editor",
|
|
15
|
+
"locate_editor",
|
|
16
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Shared names used to locate an Aphelion Editor install."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
ENV_EDITOR_HOME: Final[str] = "APHELION_EDITOR_HOME"
|
|
8
|
+
HOST_MANIFEST_NAME: Final[str] = "aphelion-host.json"
|
|
9
|
+
EDITOR_EXE_NAME: Final[str] = "AphelionEditor.exe"
|
|
10
|
+
SOURCE_ENTRY_NAME: Final[str] = "main.py"
|
|
11
|
+
REGISTRY_KEY: Final[str] = r"Software\Aphelion\Editor"
|
|
12
|
+
REGISTRY_VALUE_PATH: Final[str] = "InstallPath"
|
|
13
|
+
REGISTRY_VALUE_EXE: Final[str] = "Executable"
|
|
14
|
+
USERDATA_DIR_NAME: Final[str] = "userdata"
|
|
15
|
+
PLUGINS_DIR_NAME: Final[str] = "plugins"
|
|
16
|
+
DEFAULT_USER_INSTALL: Final[str] = r"Aphelion\Aphelion Editor"
|
|
17
|
+
DEFAULT_MACHINE_INSTALL: Final[str] = r"Aphelion\Aphelion Editor"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Errors raised while locating the editor or installing drop-in plugins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EditorHostError(RuntimeError):
|
|
7
|
+
"""Raised when the SDK cannot find or write into Aphelion Editor.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
message: Human-readable explanation of what failed.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
None.
|
|
14
|
+
|
|
15
|
+
Exceptions:
|
|
16
|
+
None.
|
|
17
|
+
|
|
18
|
+
Side effects:
|
|
19
|
+
None.
|
|
20
|
+
"""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Copy plugin sources into the located editor's user plugin folder."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from aphelion_sdk.host.errors import EditorHostError
|
|
9
|
+
from aphelion_sdk.host.locate import locate_editor
|
|
10
|
+
from aphelion_sdk.host.models import EditorInstall
|
|
11
|
+
from aphelion_sdk.packaging.discovery import plugin_source_files
|
|
12
|
+
from aphelion_sdk.packaging.errors import PluginPackageError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def install_plugins_into_editor(
|
|
16
|
+
source: Path,
|
|
17
|
+
*,
|
|
18
|
+
editor: EditorInstall | None = None,
|
|
19
|
+
) -> tuple[Path, ...]:
|
|
20
|
+
"""Copy plugin modules into the editor's ``userdata/plugins`` folder.
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
source: A ``.py`` file or a directory of plugin modules.
|
|
24
|
+
editor: Install to target. Defaults to ``locate_editor()``.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Destination paths written on disk.
|
|
28
|
+
|
|
29
|
+
Exceptions:
|
|
30
|
+
EditorHostError: If the editor cannot be found or copy fails.
|
|
31
|
+
PluginPackageError: If ``source`` is not a valid plugin path.
|
|
32
|
+
|
|
33
|
+
Side effects:
|
|
34
|
+
Creates the user plugin directory and overwrites same-named files.
|
|
35
|
+
"""
|
|
36
|
+
target: EditorInstall = editor if editor is not None else locate_editor()
|
|
37
|
+
files: tuple[Path, ...] = plugin_source_files(source)
|
|
38
|
+
destination_dir: Path = target.user_plugin_dir
|
|
39
|
+
destination_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
return _copy_plugin_files(files, destination_dir)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _copy_plugin_files(files: tuple[Path, ...], destination_dir: Path) -> tuple[Path, ...]:
|
|
44
|
+
"""Copy each plugin file into ``destination_dir``."""
|
|
45
|
+
written: list[Path] = []
|
|
46
|
+
for file_path in files:
|
|
47
|
+
dest: Path = destination_dir / file_path.name
|
|
48
|
+
try:
|
|
49
|
+
shutil.copy2(file_path, dest)
|
|
50
|
+
except OSError as exc:
|
|
51
|
+
raise EditorHostError(
|
|
52
|
+
f"Failed to install {file_path} -> {dest}: {exc}"
|
|
53
|
+
) from exc
|
|
54
|
+
written.append(dest)
|
|
55
|
+
return tuple(written)
|