resolvescript 0.1.2__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.
- resolve_script/__init__.py +3 -0
- resolve_script/analyze.py +277 -0
- resolve_script/cli.py +748 -0
- resolve_script/config.py +62 -0
- resolve_script/consolidate.py +604 -0
- resolve_script/fetch.py +90 -0
- resolve_script/install/__init__.py +49 -0
- resolve_script/install/discovery.py +57 -0
- resolve_script/install/installer.py +397 -0
- resolve_script/install/registry.py +106 -0
- resolve_script/manifest/__init__.py +1 -0
- resolve_script/manifest/json_reader.py +40 -0
- resolve_script/manifest/model.py +316 -0
- resolve_script/manifest/validation.py +81 -0
- resolve_script/manifest/xml_reader.py +162 -0
- resolve_script/package.py +103 -0
- resolve_script/resolver.py +204 -0
- resolve_script/sandbox/__init__.py +38 -0
- resolve_script/sandbox/api.py +393 -0
- resolve_script/sandbox/env.py +82 -0
- resolve_script/sandbox/loader.py +72 -0
- resolve_script/sandbox/repl.py +57 -0
- resolve_script/sandbox/smoke.py +104 -0
- resolve_script/scaffold.py +126 -0
- resolve_script/semver.py +236 -0
- resolve_script/sources/__init__.py +15 -0
- resolve_script/sources/archive.py +82 -0
- resolve_script/sources/git.py +107 -0
- resolve_script/sources/known.py +47 -0
- resolve_script/sources/release.py +55 -0
- resolve_script/spec.py +137 -0
- resolve_script/templates/extension/@NAME@/__init__.py +7 -0
- resolve_script/templates/extension/@NAME@/menu.py +12 -0
- resolve_script/templates/extension/@NAME@.py +13 -0
- resolve_script/templates/extension/README.md +20 -0
- resolve_script/templates/extension/conftest.py +13 -0
- resolve_script/templates/extension/manifest.json.j2 +23 -0
- resolve_script/templates/extension/manifest.xml.j2 +24 -0
- resolve_script/templates/extension/tests/test_smoke.py +26 -0
- resolve_script/templates/inapp/register.py +28 -0
- resolve_script/testing/__init__.py +6 -0
- resolve_script/testing/fixtures.py +47 -0
- resolve_script/workspace.py +66 -0
- resolvescript-0.1.2.dist-info/METADATA +146 -0
- resolvescript-0.1.2.dist-info/RECORD +49 -0
- resolvescript-0.1.2.dist-info/WHEEL +5 -0
- resolvescript-0.1.2.dist-info/entry_points.txt +2 -0
- resolvescript-0.1.2.dist-info/licenses/LICENSE +21 -0
- resolvescript-0.1.2.dist-info/top_level.txt +1 -0
resolve_script/spec.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Specifier parsing: ``add``/``install`` arguments -> :class:`Spec`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .sources.archive import is_archive_path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SpecError(ValueError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Spec:
|
|
19
|
+
raw: str
|
|
20
|
+
kind: str # path | file-archive | archive | manifest | github | name
|
|
21
|
+
source: str # canonical source string recorded in the registry / deps
|
|
22
|
+
location: Path | None = None
|
|
23
|
+
url: str | None = None
|
|
24
|
+
owner: str | None = None
|
|
25
|
+
repo: str | None = None
|
|
26
|
+
ref: str | None = None
|
|
27
|
+
range_text: str | None = None
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def name_hint(self) -> str:
|
|
31
|
+
if self.owner and self.repo:
|
|
32
|
+
return self.repo
|
|
33
|
+
if self.location:
|
|
34
|
+
return self.location.name
|
|
35
|
+
return self.raw
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _split_fragment(text: str) -> tuple[str, str | None]:
|
|
39
|
+
index = text.find("#")
|
|
40
|
+
if index == -1:
|
|
41
|
+
return text, None
|
|
42
|
+
return text[:index], text[index + 1 :]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _looks_like_windows_path(text: str) -> bool:
|
|
46
|
+
return bool(
|
|
47
|
+
re.match(r"^[a-zA-Z]:[\\/]", text)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_specifier(raw: str, *, cwd: Path | None = None) -> Spec:
|
|
52
|
+
"""Classify a raw specifier string into a :class:`Spec`."""
|
|
53
|
+
cwd = cwd or Path.cwd()
|
|
54
|
+
text = raw.strip()
|
|
55
|
+
if not text:
|
|
56
|
+
raise SpecError("empty specifier")
|
|
57
|
+
|
|
58
|
+
# file: scheme (archive or directory)
|
|
59
|
+
if text.startswith("file:"):
|
|
60
|
+
location = Path(text[len("file:") :])
|
|
61
|
+
if not location.is_absolute():
|
|
62
|
+
location = (cwd / location).resolve()
|
|
63
|
+
if is_archive_path(str(location)):
|
|
64
|
+
return Spec(raw=raw, kind="file-archive", source=f"file:{location}", location=location)
|
|
65
|
+
return Spec(raw=raw, kind="path", source=f"file:{location}", location=location)
|
|
66
|
+
|
|
67
|
+
# https?:// URLs
|
|
68
|
+
lower = text.lower()
|
|
69
|
+
if lower.startswith("http://") or lower.startswith("https://"):
|
|
70
|
+
if is_archive_path(text):
|
|
71
|
+
return Spec(raw=raw, kind="archive", source=text, url=text)
|
|
72
|
+
return Spec(raw=raw, kind="manifest", source=text, url=text)
|
|
73
|
+
|
|
74
|
+
# github:owner/repo[...]
|
|
75
|
+
if text.startswith("github:") or text.startswith("gh:"):
|
|
76
|
+
body, fragment = _split_fragment(text.split(":", 1)[1])
|
|
77
|
+
owner, repo = _split_owner_repo(body)
|
|
78
|
+
return _github_spec(raw, owner, repo, fragment)
|
|
79
|
+
|
|
80
|
+
# paths: relative markers, absolute, existing, or windows paths
|
|
81
|
+
if (
|
|
82
|
+
text in (".", "..")
|
|
83
|
+
or text.startswith("./")
|
|
84
|
+
or text.startswith("../")
|
|
85
|
+
or text.startswith("\\")
|
|
86
|
+
or _looks_like_windows_path(text)
|
|
87
|
+
or Path(text).is_absolute()
|
|
88
|
+
or Path(cwd / text).exists()
|
|
89
|
+
):
|
|
90
|
+
location = (cwd / text).resolve()
|
|
91
|
+
if is_archive_path(str(location)):
|
|
92
|
+
return Spec(raw=raw, kind="file-archive", source=f"file:{location}", location=location)
|
|
93
|
+
return Spec(raw=raw, kind="path", source=str(location), location=location)
|
|
94
|
+
|
|
95
|
+
# owner/repo
|
|
96
|
+
if "/" in text:
|
|
97
|
+
body, fragment = _split_fragment(text)
|
|
98
|
+
owner, repo = _split_owner_repo(body)
|
|
99
|
+
return _github_spec(raw, owner, repo, fragment)
|
|
100
|
+
|
|
101
|
+
# bare name
|
|
102
|
+
return Spec(raw=raw, kind="name", source=text)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _split_owner_repo(body: str) -> tuple[str, str]:
|
|
106
|
+
parts = body.split("/")
|
|
107
|
+
if len(parts) == 2 and parts[0] and parts[1]:
|
|
108
|
+
return parts[0], parts[1]
|
|
109
|
+
if len(parts) > 2:
|
|
110
|
+
raise SpecError(f"invalid github specifier {body!r} (expected owner/repo)")
|
|
111
|
+
owner = os.environ.get("RESOLVESCRIPT_USER", "")
|
|
112
|
+
if not owner:
|
|
113
|
+
raise SpecError(
|
|
114
|
+
f"cannot infer github owner for {body!r} (set RESOLVESCRIPT_USER)"
|
|
115
|
+
)
|
|
116
|
+
return owner, body
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _github_spec(raw: str, owner: str, repo: str, fragment: str | None) -> Spec:
|
|
120
|
+
source = f"github:{owner}/{repo}"
|
|
121
|
+
ref: str | None = None
|
|
122
|
+
range_text: str | None = None
|
|
123
|
+
if fragment:
|
|
124
|
+
if fragment.startswith("semver:"):
|
|
125
|
+
range_text = fragment[len("semver:") :] or "*"
|
|
126
|
+
else:
|
|
127
|
+
ref = fragment
|
|
128
|
+
source = f"{source}#{'semver:' + range_text if range_text else ref}"
|
|
129
|
+
return Spec(
|
|
130
|
+
raw=raw,
|
|
131
|
+
kind="github",
|
|
132
|
+
source=source,
|
|
133
|
+
owner=owner,
|
|
134
|
+
repo=repo,
|
|
135
|
+
ref=ref,
|
|
136
|
+
range_text=range_text,
|
|
137
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Sample menu UI for @NAME@.
|
|
2
|
+
|
|
3
|
+
``run`` is the entry the sandbox and smoke tests exercise; it works against
|
|
4
|
+
either the mock API (``resolvescript dev``/``test``) or the real Resolve API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run(resolve) -> str:
|
|
9
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
10
|
+
if project is None:
|
|
11
|
+
return "@NAME@: no project is open"
|
|
12
|
+
return f"@NAME@ ready — project: {project.GetName()}"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""In-app entry point for @NAME@.
|
|
2
|
+
|
|
3
|
+
Run inside DaVinci Resolve (Workspace → Scripts) after installing.
|
|
4
|
+
The mock API mirrors ``DaVinciResolveScript`` so this also runs in the
|
|
5
|
+
sandbox via ``resolvescript dev --editor``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import DaVinciResolveScript as dvr_script
|
|
9
|
+
|
|
10
|
+
from @NAME@.menu import run
|
|
11
|
+
|
|
12
|
+
resolve = dvr_script.scriptapp("Resolve")
|
|
13
|
+
print(run(resolve))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @NAME@
|
|
2
|
+
|
|
3
|
+
@DESCRIPTION@
|
|
4
|
+
|
|
5
|
+
Built with [ResolveScript](https://pypi.org/project/resolvescript/).
|
|
6
|
+
|
|
7
|
+
## Development
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
resolvescript dev # sandboxed REPL against the mock Resolve API
|
|
11
|
+
resolvescript test # run tests against the mock API
|
|
12
|
+
resolvescript analyze # static checks on the project
|
|
13
|
+
resolvescript build # consolidate the package into dist/@NAME@.py
|
|
14
|
+
resolvescript install # install into DaVinci Resolve (author mode)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage in Resolve
|
|
18
|
+
|
|
19
|
+
After `resolvescript install`, restart DaVinci Resolve and run the script from
|
|
20
|
+
**Workspace → Scripts → Comp → @NAME@**.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Shared pytest setup for the @NAME@ project.
|
|
2
|
+
|
|
3
|
+
Installs the mock DaVinciResolveScript sandbox (via the ResolveScript CLI's own
|
|
4
|
+
fixtures) and puts the project root on ``sys.path`` so ``import @NAME@`` works
|
|
5
|
+
from ``tests/``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
12
|
+
|
|
13
|
+
pytest_plugins = ["resolve_script.testing.fixtures"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@NAME@",
|
|
3
|
+
"version": "@VERSION@",
|
|
4
|
+
"author": "@AUTHOR@",
|
|
5
|
+
"description": "@DESCRIPTION@",
|
|
6
|
+
"python": "@NAME@",
|
|
7
|
+
"targets": [
|
|
8
|
+
"Comp",
|
|
9
|
+
"Utility"
|
|
10
|
+
],
|
|
11
|
+
"consolidate": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"output": "@NAME@.py",
|
|
14
|
+
"entry": "@NAME@/__init__.py",
|
|
15
|
+
"exclude": ["tests"],
|
|
16
|
+
"no_comment": ["sys", "os"]
|
|
17
|
+
},
|
|
18
|
+
"install": {
|
|
19
|
+
"as_directory": true,
|
|
20
|
+
"include": ["@NAME@.py", "@NAME@/**", "manifest.json"],
|
|
21
|
+
"exclude": ["**/__pycache__/**"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<manifest>
|
|
3
|
+
<name>@NAME@</name>
|
|
4
|
+
<version>@VERSION@</version>
|
|
5
|
+
<author>@AUTHOR@</author>
|
|
6
|
+
<description>@DESCRIPTION@</description>
|
|
7
|
+
<python>@NAME@</python>
|
|
8
|
+
<targets>
|
|
9
|
+
<target>Comp</target>
|
|
10
|
+
<target>Utility</target>
|
|
11
|
+
</targets>
|
|
12
|
+
<consolidate enabled="true" output="@NAME@.py">
|
|
13
|
+
<entry>@NAME@/__init__.py</entry>
|
|
14
|
+
<exclude>tests</exclude>
|
|
15
|
+
<no_comment>sys</no_comment>
|
|
16
|
+
<no_comment>os</no_comment>
|
|
17
|
+
</consolidate>
|
|
18
|
+
<install as_directory="true">
|
|
19
|
+
<include>@NAME@.py</include>
|
|
20
|
+
<include>@NAME@/**</include>
|
|
21
|
+
<include>manifest.json</include>
|
|
22
|
+
<exclude>**/__pycache__/**</exclude>
|
|
23
|
+
</install>
|
|
24
|
+
</manifest>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Smoke tests that pass in the mock sandbox with zero setup."""
|
|
2
|
+
|
|
3
|
+
import @NAME@
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_version_is_a_string() -> None:
|
|
7
|
+
assert isinstance(@NAME@.__version__, str)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_hello() -> None:
|
|
11
|
+
assert @NAME@.hello() == "Hello from @NAME@!"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_menu_against_mock_resolve() -> None:
|
|
15
|
+
"""menu.run works against the injected mock DaVinciResolveScript.
|
|
16
|
+
|
|
17
|
+
The sandbox fixture (M4) installs a FakeResolve with a project named
|
|
18
|
+
'Demo Project'.
|
|
19
|
+
"""
|
|
20
|
+
import DaVinciResolveScript as dvr_script
|
|
21
|
+
|
|
22
|
+
from @NAME@.menu import run
|
|
23
|
+
|
|
24
|
+
resolve = dvr_script.scriptapp("Resolve")
|
|
25
|
+
message = run(resolve)
|
|
26
|
+
assert "@NAME@" in message
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""In-app helper: register the @NAME@ extension into the open Resolve project.
|
|
2
|
+
|
|
3
|
+
Paste this into Resolve's Console (Workspace > Console) or run it as a Utility
|
|
4
|
+
script to wire the installed @NAME@ script into the current project's Tools or
|
|
5
|
+
Edit menus for one session. The package must already be installed via
|
|
6
|
+
``resolvescript install`` (or on ``sys.path``).
|
|
7
|
+
|
|
8
|
+
Using ``addMenuItem`` is the standard non-persistent way to surface a script in
|
|
9
|
+
the Resolve UI menus.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import DaVinciResolveScript as dvr_script
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _register() -> None:
|
|
16
|
+
resolve = dvr_script.scriptapp("Resolve")
|
|
17
|
+
project = resolve.GetProjectManager().GetCurrentProject()
|
|
18
|
+
if project is None:
|
|
19
|
+
print("@NAME@: no project is open")
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
import @NAME@ # installed under the Scripts root
|
|
23
|
+
|
|
24
|
+
project.AddMenuItem("Tools", "@NAME@", lambda: @NAME@.menu.run(resolve))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
_register()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Pytest fixtures for developing Resolve scripts against the mock API.
|
|
2
|
+
|
|
3
|
+
The ``sandbox`` fixture installs the fake ``DaVinciResolveScript`` module and
|
|
4
|
+
yields the mock Resolve object. ``load_source_module`` and ``load_built_module``
|
|
5
|
+
import the extension under test either as a source package or as a consolidated
|
|
6
|
+
single-file build.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from types import ModuleType
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from ..sandbox.env import install_fake_resolve
|
|
18
|
+
from ..sandbox.loader import load_built_module as _load_built_module
|
|
19
|
+
from ..sandbox.loader import load_source_module as _load_source_module
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
23
|
+
def _sandbox_installed() -> None:
|
|
24
|
+
"""Install the mock Resolve API once per test session."""
|
|
25
|
+
install_fake_resolve()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def sandbox() -> ModuleType:
|
|
30
|
+
"""Install (idempotently) and return the fake DaVinciResolveScript module."""
|
|
31
|
+
return install_fake_resolve()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def load_source_module() -> Callable[[str, Path | str], ModuleType]:
|
|
36
|
+
def _load(module_name: str, package_dir: Path | str = ".") -> ModuleType:
|
|
37
|
+
return _load_source_module(module_name, Path(package_dir))
|
|
38
|
+
|
|
39
|
+
return _load
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def load_built_module() -> Callable[[str, Path | str], ModuleType]:
|
|
44
|
+
def _load(module_name: str, built_file: Path | str) -> ModuleType:
|
|
45
|
+
return _load_built_module(module_name, Path(built_file))
|
|
46
|
+
|
|
47
|
+
return _load
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Workspace file management: ``resolvescript.json`` dependency list."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
WORKSPACE_FILE = "resolvescript.json"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WorkspaceError(RuntimeError):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def workspace_path(cwd: Path | None = None) -> Path:
|
|
16
|
+
return Path(cwd or Path.cwd()) / WORKSPACE_FILE
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def has_workspace(cwd: Path | None = None) -> bool:
|
|
20
|
+
return workspace_path(cwd).is_file()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_workspace(cwd: Path | None = None) -> dict:
|
|
24
|
+
path = workspace_path(cwd)
|
|
25
|
+
if not path.is_file():
|
|
26
|
+
return {"dependencies": {}}
|
|
27
|
+
try:
|
|
28
|
+
data = json.loads(path.read_text("utf-8"))
|
|
29
|
+
except json.JSONDecodeError as exc:
|
|
30
|
+
raise WorkspaceError(f"{path}: invalid JSON: {exc}") from exc
|
|
31
|
+
if not isinstance(data, dict):
|
|
32
|
+
raise WorkspaceError(f"{path}: expected a JSON object")
|
|
33
|
+
if "dependencies" not in data:
|
|
34
|
+
data["dependencies"] = {}
|
|
35
|
+
if not isinstance(data["dependencies"], dict):
|
|
36
|
+
raise WorkspaceError(f"{path}: 'dependencies' must be an object")
|
|
37
|
+
return {k: v for k, v in data.items() if v is not None}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def write_workspace(data: dict, cwd: Path | None = None) -> Path:
|
|
41
|
+
path = workspace_path(cwd)
|
|
42
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
tmp = path.with_name(f"{path.name}.tmp")
|
|
44
|
+
tmp.write_text(json.dumps(data, indent=2) + "\n", "utf-8")
|
|
45
|
+
_atomic_replace(tmp, path)
|
|
46
|
+
return path
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def add_dependency(name: str, spec_text: str, cwd: Path | None = None) -> dict:
|
|
50
|
+
data = read_workspace(cwd)
|
|
51
|
+
data.setdefault("dependencies", {})[name] = spec_text
|
|
52
|
+
return data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def remove_dependency(name: str, cwd: Path | None = None) -> dict:
|
|
56
|
+
data = read_workspace(cwd)
|
|
57
|
+
data.setdefault("dependencies", {}).pop(name, None)
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def save(data: dict, cwd: Path | None = None) -> Path:
|
|
62
|
+
return write_workspace(data, cwd)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _atomic_replace(src: Path, dest: Path) -> None:
|
|
66
|
+
src.replace(dest)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: resolvescript
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: CLI framework for building, coding, testing, packaging and installing DaVinci Resolve scripts.
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Multimedia :: Video
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# resolvescript
|
|
25
|
+
|
|
26
|
+
Build, test, package and install [DaVinci Resolve](https://www.blackmagicdesign.com/products/davinciresolve) Python scripts and plugins: a small CLI with an npm-style workflow for the Resolve `Scripts/` tree.
|
|
27
|
+
|
|
28
|
+
- `create` scaffolds a project with a `manifest.json` and smoke tests
|
|
29
|
+
- `dev` / `test` run against a mock `DaVinciResolveScript` API - no Resolve needed
|
|
30
|
+
- `build` consolidates the multi-file package into a single `.py` for distribution
|
|
31
|
+
- `add` / `install` / `update` resolve dependencies from GitHub, URLs, archives or local folders and record them in `resolvescript.json`
|
|
32
|
+
- `package` emits `dist/<name>-<version>.tar.gz` plus `SHA256SUMS.txt`
|
|
33
|
+
|
|
34
|
+
Install with pip: `pip install resolvescript` (Python 3.12+).
|
|
35
|
+
|
|
36
|
+
## Quickstart
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
$ resolvescript create my-cool-tool
|
|
40
|
+
$ cd my-cool-tool
|
|
41
|
+
$ resolvescript dev # sandboxed dev loop against the mock API
|
|
42
|
+
$ resolvescript test # run the smoke tests
|
|
43
|
+
$ resolvescript build # single-file build -> dist/my_cool_tool.py
|
|
44
|
+
$ resolvescript analyze # static checks (imports, manifest, API usage)
|
|
45
|
+
$ resolvescript package # dist/my-cool-tool-0.1.0.tar.gz + SHA256SUMS.txt
|
|
46
|
+
|
|
47
|
+
# elsewhere, consume it:
|
|
48
|
+
$ resolvescript add github:example/my-cool-tool
|
|
49
|
+
$ resolvescript install # materialize all recorded deps into Resolve
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Authoring
|
|
53
|
+
|
|
54
|
+
### Directory
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
my-cool-tool/
|
|
58
|
+
manifest.json # project manifest (or manifest.xml)
|
|
59
|
+
my_cool_tool/ # your Python package (module name from manifest.name/python)
|
|
60
|
+
__init__.py
|
|
61
|
+
export.py
|
|
62
|
+
tests/
|
|
63
|
+
test_smoke.py
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Commands
|
|
67
|
+
|
|
68
|
+
| Command | Purpose |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `create <name>` | Scaffold a new project (`--json`/`--xml`, `--template minimal\|toolkit`, `--dir`) |
|
|
71
|
+
| `dev` | Sandboxed dev loop / REPL against the mock Resolve API (`--built` for the consolidated file, `--repl`, `--editor`) |
|
|
72
|
+
| `test` | Run the project's pytest suite against the mock API (`--built`, `-k pattern`, `--api-coverage`) |
|
|
73
|
+
| `analyze` | Static checks: missing entrypoint/module, syntax, unused imports, unmocked API methods, attribute typing (`--json`) |
|
|
74
|
+
| `build` | Consolidate into a single file (`--output`, or the manifest `consolidate.output`) |
|
|
75
|
+
| `package` | Assemble `dist/<name>-<version>.tar.gz` + `SHA256SUMS.txt` (`--dist DIR`) |
|
|
76
|
+
| `add <spec>` | Resolve, install a dependency and record it (`--no-save`, `--target`, `--scripts-root`) |
|
|
77
|
+
| `install [<spec>]` | One-off install, local project, **or** materialize recorded deps (`--locked`, `--dry-run`) |
|
|
78
|
+
| `update [<name>]` | Re-resolve recorded deps within their ranges (`--precise X.Y.Z`, `--fix`) |
|
|
79
|
+
| `remove <name>` | Uninstall and unrecord (`--no-save`) |
|
|
80
|
+
| `search <query>` | Search the known sources table |
|
|
81
|
+
| `manage list\|remove` | Low-level registry operations (`--json`, `--all`) |
|
|
82
|
+
|
|
83
|
+
Exit codes: `0` ok, `1` error, `2` usage.
|
|
84
|
+
|
|
85
|
+
### Specifiers
|
|
86
|
+
|
|
87
|
+
`add` / `install <spec>` accept:
|
|
88
|
+
|
|
89
|
+
- `name` - a name from the built-in known-sources table (`resolvescript search`)
|
|
90
|
+
- `owner/repo` or `github:owner/repo[#ref]` - GitHub repo (ref = branch/tag/commit, or `#semver:^1.2` for a range)
|
|
91
|
+
- `https://...tar.gz` / `.zip` - direct archive URL
|
|
92
|
+
- `https://...` - a URL whose root carries a `manifest.json` (GitHub Pages-style hosting)
|
|
93
|
+
- `file:path`, `./dir`, an absolute path - local directory or archive
|
|
94
|
+
- `.tar.gz` / `.zip` files and local directories
|
|
95
|
+
|
|
96
|
+
Recorded specs live in `resolvescript.json`; use `resolvescript install --locked` for a CI/npm-ci style check that recorded artifacts still satisfy the recorded ranges.
|
|
97
|
+
|
|
98
|
+
### Manifest reference
|
|
99
|
+
|
|
100
|
+
`manifest.json` (or `manifest.xml`, same shape):
|
|
101
|
+
|
|
102
|
+
```jsonc
|
|
103
|
+
{
|
|
104
|
+
"name": "my-cool-tool", // required
|
|
105
|
+
"version": "0.1.0", // required, semver
|
|
106
|
+
"author": "You",
|
|
107
|
+
"description": "...",
|
|
108
|
+
"python": "my_cool_tool", // package/module name (defaults to "name")
|
|
109
|
+
"entrypoint": "export.py", // recommended CLI/bootstrap module
|
|
110
|
+
"kind": "script", // "script" (default) | "extension" (plugin)
|
|
111
|
+
"targets": ["Comp"], // Scripts subfolders: Comp, Utility, Tool, Render,
|
|
112
|
+
// Deliver, Edit, WorkflowIntegrations, Fusion, root
|
|
113
|
+
"compat": { "resolve": "18.6.4", "python": "3.12" },
|
|
114
|
+
"release": { "owner": "you", "repo": "my-cool-tool" }, // for `add` discovery
|
|
115
|
+
"scripts_root": "", // per-OS override for the Resolve Scripts root
|
|
116
|
+
"consolidate": { "enabled": true, "output": "dist/my_tool.py", "exclude": [], "no_comment": [] },
|
|
117
|
+
"install": { "as_directory": true, "include": [], "exclude": [], "to": "resolve" },
|
|
118
|
+
"dependencies": ["github:example/dep"] // resolved on install
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Install model
|
|
123
|
+
|
|
124
|
+
Installs drop into each OS's Resolve Scripts root under the manifest `targets`. Directories install as a single folder; `.py`-only packages can opt into `install.as_directory: false` multi-file installs. Every install is recorded in `<ScriptsRoot>/.resolvescript/install.json` (schema v1):
|
|
125
|
+
|
|
126
|
+
- keys are `<name>:<target>` (directory) or `<name>:<target>:<relpath>` (file-based)
|
|
127
|
+
- each entry carries `version`, `targets`, `as_directory`, `files`, `source`, `resolved` and `integrity`
|
|
128
|
+
- `install --locked` verifies registry entries against recorded specs before reusing them
|
|
129
|
+
|
|
130
|
+
`--scripts-root` (or `RESOLVESCRIPT_SCRIPTS_ROOT`) overrides OS detection.
|
|
131
|
+
|
|
132
|
+
## Frameworks
|
|
133
|
+
|
|
134
|
+
`resolvescript` ships a mock `DaVinciResolveScript` module with a faithful-enough Resolve color/editing/page object model for testing, plus `resolvescript test --api-coverage` to report which mock methods your tests actually exercise.
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
```console
|
|
139
|
+
python -m venv .venv
|
|
140
|
+
.\.venv\Scripts\activate # Windows; source .venv/bin/activate elsewhere
|
|
141
|
+
pip install -e ".[dev]"
|
|
142
|
+
pytest
|
|
143
|
+
ruff check .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Milestones and the full plan live in [`TODO.md`](TODO.md). CI runs the shared `OseMine/workflows` action; cutting a `vX.Y.Z` tag runs the release pipeline (sdist+wheel, checksums, GitHub release, optional PyPI publishing).
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
resolve_script/__init__.py,sha256=4yBp0AZtzy-XdhHBCiy-Wc_oEjzK3GqVcnAp47Rhz4Y,90
|
|
2
|
+
resolve_script/analyze.py,sha256=bE8HyosA5FbV3L1diiX1Yi_koI1nLSBhgGInlpU1mSs,8425
|
|
3
|
+
resolve_script/cli.py,sha256=q2EYBWbgErMzvtmdeF70UzhOib53tAMFfWk0zurq48Q,29402
|
|
4
|
+
resolve_script/config.py,sha256=DFG_ro9QZYPEtAfGaZ95L3dbWyhUZUT1w5mLqxQ2rfo,2002
|
|
5
|
+
resolve_script/consolidate.py,sha256=NNjEU5f3xhQq814G_xvbzREBVSJ91t1v8d7RBzCOcbc,21749
|
|
6
|
+
resolve_script/fetch.py,sha256=0NkbdbOdQolSUTQUIwbh6LfRu1q5ikORvHBiVa-eouM,2526
|
|
7
|
+
resolve_script/package.py,sha256=pL4uahXK3tEB4ta07ThJNchu9PXzKzgQpnolaMY36uA,3424
|
|
8
|
+
resolve_script/resolver.py,sha256=lv1GGn1OAOdCBAlTGGOrcwCCTFWd5msfHht6w2KxIck,6711
|
|
9
|
+
resolve_script/scaffold.py,sha256=6pX3jh2CHSFfTgjV-QXEXoia4c2JJfVV1wY9bMcjvn4,3967
|
|
10
|
+
resolve_script/semver.py,sha256=a8Y7DBoS6qIzLc-ZGWKwqideDFc9EY9cDHDCf0XbBQA,7554
|
|
11
|
+
resolve_script/spec.py,sha256=vO2-n5NzGOp6idEBGErVTj3u90Rfbd9tCn4J8prwusk,4293
|
|
12
|
+
resolve_script/workspace.py,sha256=WqsXDm8P1tAMYZLbwnykGd7_QErXDm-RMh0EHcZtM1U,1942
|
|
13
|
+
resolve_script/install/__init__.py,sha256=nCqt4aU_7AQ85G9U7VoYiVODLp7joTHFqmziYt4kUt4,1059
|
|
14
|
+
resolve_script/install/discovery.py,sha256=cqEjV6ubE3bYIpAhMJa4HknnxQjGt8kifoWKrAW4nnU,2024
|
|
15
|
+
resolve_script/install/installer.py,sha256=mnVK2ejOBNZuDQaBJ2Hk6480pble-bSNSRtyEArZKXo,14689
|
|
16
|
+
resolve_script/install/registry.py,sha256=s7KCZaTmnP0GnUyhYbJd9wi8suul23afsY8sQXIWHuM,3652
|
|
17
|
+
resolve_script/manifest/__init__.py,sha256=rfEekP2vJmnH0w8wYqdv1grsPh99Rt6Rmr43kdt-EQ8,57
|
|
18
|
+
resolve_script/manifest/json_reader.py,sha256=cgmQj2AmXSMBajamgmMrY9giR3Jq3PTcOg672Q4axEY,1383
|
|
19
|
+
resolve_script/manifest/model.py,sha256=G1XuQWm6ryZYbnOYuEZdzJvs0Nj3CorteKy1xPS0gbs,10944
|
|
20
|
+
resolve_script/manifest/validation.py,sha256=MJjL09XHgV5AVTSypltPBIc-UtrUXXWxODGrg5sFkEc,3048
|
|
21
|
+
resolve_script/manifest/xml_reader.py,sha256=quA460IyBkOqg1N-ZomCdb4jkn8SG8NXmLZShhCjPXM,5048
|
|
22
|
+
resolve_script/sandbox/__init__.py,sha256=vxhUZ36WzYFXmq7n9Aka6DNJyIppYenc9RqFrnSe7EI,969
|
|
23
|
+
resolve_script/sandbox/api.py,sha256=fAihpJqnm7QgO8zI2w8l9DCljtGxxt5UAiC9Qi9odek,11165
|
|
24
|
+
resolve_script/sandbox/env.py,sha256=0NVI4Jh02AnLj3xo1cY7XALR7vIAmwFpD4cYGoqg_cI,2479
|
|
25
|
+
resolve_script/sandbox/loader.py,sha256=30sN5Wz4y2Jo9juP4ksHL1Qj1pnnnOX_c1E83WL-pd4,2729
|
|
26
|
+
resolve_script/sandbox/repl.py,sha256=njYE4RfouXQOYj7KpN9DFt6f9P_fkjVV18wCgxQzLIw,2130
|
|
27
|
+
resolve_script/sandbox/smoke.py,sha256=3fwjZp15bzF5TjEAQoLGla2I1uB3OgmyEmuUN-KdpDI,3406
|
|
28
|
+
resolve_script/sources/__init__.py,sha256=HMtm-0m6HwL7UNpCInRbbqMmTVKZRBVDt92QNYCHkAs,687
|
|
29
|
+
resolve_script/sources/archive.py,sha256=9Tvxu4TfM7SbYOSPCCLMfuuI-J7qBbclnrjWpfTF1Tk,2690
|
|
30
|
+
resolve_script/sources/git.py,sha256=ZO3NRYKI_NY1ryKeyxKQU_hdcFOI6nBccniQk2KKtM0,3474
|
|
31
|
+
resolve_script/sources/known.py,sha256=QLjhDfI9Eh7IRh5N-neVWDuoI7EkW2pIe118zT8Vcj0,1452
|
|
32
|
+
resolve_script/sources/release.py,sha256=I8UgY-YAa0SJYKHvBIgPeoXidotUMNSIh7nutc-T3L0,1644
|
|
33
|
+
resolve_script/templates/extension/@NAME@.py,sha256=Qs1l8LT80x_l_XH0TCD-Sxh1yT59NJtEeV979CYXTXQ,357
|
|
34
|
+
resolve_script/templates/extension/README.md,sha256=uaLe4Kl1dkSxtavnyzC5enNrBpU_4Ei6QTjRVSt-I7g,587
|
|
35
|
+
resolve_script/templates/extension/conftest.py,sha256=vZCPJO38FhciZsPFmndvjkUWPP-PU3jYFwFxJa5sl8c,376
|
|
36
|
+
resolve_script/templates/extension/manifest.json.j2,sha256=czshE3YukuYCG7ql8GNut7X4pUOnKm_GzhNI_wwAMXk,482
|
|
37
|
+
resolve_script/templates/extension/manifest.xml.j2,sha256=iOAoX8r_uMPFS8uzmdYgIzRTNjSZrFZ4672NUhiVqWU,679
|
|
38
|
+
resolve_script/templates/extension/@NAME@/__init__.py,sha256=f-HFxk2izvb7Ag_AFUTb8rkv5XkcSD23nzhWnN8TT3M,101
|
|
39
|
+
resolve_script/templates/extension/@NAME@/menu.py,sha256=2TsZZb7hxHQBeFerFvuwf3-ibP3sns0-ooLkGFIsIFc,405
|
|
40
|
+
resolve_script/templates/extension/tests/test_smoke.py,sha256=30PLOZBfo_p73xL8N7tZnHd17JR16Jk2PwqixvYSkv0,650
|
|
41
|
+
resolve_script/templates/inapp/register.py,sha256=BeA2h2njP4u95oJvdhR2Z7DPzw32bIWreJgj0TjRZyo,901
|
|
42
|
+
resolve_script/testing/__init__.py,sha256=D9b0xmoOdHe-41Xcq-KNdRYcgMMv_-oyYzGOTNkVCnY,189
|
|
43
|
+
resolve_script/testing/fixtures.py,sha256=P_GXPtByPYLjV_Eo8hNOpRbKO25FCv_JanZSZ1OJ5aw,1491
|
|
44
|
+
resolvescript-0.1.2.dist-info/licenses/LICENSE,sha256=JuDRkpJ1tG2YXCjsW0iF9Ob8IH0K2Sd3c9WMTBPfO7o,1055
|
|
45
|
+
resolvescript-0.1.2.dist-info/METADATA,sha256=hHN2CQ22PoGdLBxsC14MDvx1ddwxeUJYdWoZRYb99yc,6792
|
|
46
|
+
resolvescript-0.1.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
47
|
+
resolvescript-0.1.2.dist-info/entry_points.txt,sha256=HVgNxbCSeCttwc2HqLE2B4-oaOodDwDbLAfl-YDu4kg,58
|
|
48
|
+
resolvescript-0.1.2.dist-info/top_level.txt,sha256=RwDBHbSlI6h3pRARRAXkbmPGvHaRA0mMI-cq2Tgmv4Q,15
|
|
49
|
+
resolvescript-0.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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 @@
|
|
|
1
|
+
resolve_script
|