release: public alpha 0.6.0 baseline
This commit is contained in:
Executable
+286
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate or verify all reviewed local extension adapter packages."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OFFICIAL_SRC = (
|
||||
ROOT
|
||||
/ "packages"
|
||||
/ "jwebot-official-extensions"
|
||||
/ "src"
|
||||
)
|
||||
SDK_SRC = ROOT / "packages" / "jwebot-sdk" / "src"
|
||||
sys.path[:0] = [str(SDK_SRC), str(OFFICIAL_SRC)]
|
||||
|
||||
from jwebot_official_extensions import LOCAL_EXTENSIONS # noqa: E402
|
||||
|
||||
|
||||
LEGACY_EXTENSION_DIRS = (
|
||||
ROOT / "extensions" / "official" / "core-help",
|
||||
ROOT / "extensions" / "official" / "group-insights",
|
||||
ROOT / "extensions" / "official" / "local-operations",
|
||||
ROOT / "extensions" / "official" / "random-games",
|
||||
ROOT / "extensions" / "official" / "session-games",
|
||||
ROOT / "extensions" / "official" / "steam-presence",
|
||||
)
|
||||
|
||||
|
||||
def module_name(extension_id: str) -> str:
|
||||
return extension_id.replace("-", "_")
|
||||
|
||||
|
||||
def manifest_text(descriptor) -> str:
|
||||
command_values = ", ".join(
|
||||
json.dumps(command, ensure_ascii=False)
|
||||
for command in descriptor.commands
|
||||
)
|
||||
help_values = ", ".join(
|
||||
json.dumps(entry, ensure_ascii=False)
|
||||
for entry in descriptor.help_entries
|
||||
)
|
||||
extension_id = descriptor.extension_id
|
||||
return f"""id = "{extension_id}"
|
||||
author = "{descriptor.author}"
|
||||
description = {json.dumps(descriptor.description, ensure_ascii=False)}
|
||||
help = [{help_values}]
|
||||
version = "1.0.0"
|
||||
api_version = "1"
|
||||
entrypoint = "{module_name(extension_id)}:plugin"
|
||||
type = "command"
|
||||
|
||||
[capabilities]
|
||||
commands = [{command_values}]
|
||||
storage = false
|
||||
network_profiles = []
|
||||
service_profiles = ["{extension_id}"]
|
||||
"""
|
||||
|
||||
|
||||
def module_text(descriptor) -> str:
|
||||
extension_id = descriptor.extension_id
|
||||
return f'''"""Generated reviewed adapter for {extension_id}."""
|
||||
|
||||
from jwebot_sdk import Event, ExtensionResult, ReplyPlan, SingleGroupContext
|
||||
|
||||
|
||||
PROFILE = {extension_id!r}
|
||||
AUTHOR = {descriptor.author!r}
|
||||
DESCRIPTION = {descriptor.description!r}
|
||||
HELP_ENTRIES = {descriptor.help_entries!r}
|
||||
COMMANDS = {descriptor.commands!r}
|
||||
|
||||
|
||||
def _handler(context: SingleGroupContext, command: str):
|
||||
def handle(event: Event, arguments: str) -> ExtensionResult:
|
||||
response = context.services.call(
|
||||
PROFILE,
|
||||
operation="execute",
|
||||
payload={{
|
||||
"command": command,
|
||||
"arguments": arguments,
|
||||
"event": event.to_mapping(),
|
||||
}},
|
||||
)
|
||||
text = response.get("text")
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("local service returned invalid text")
|
||||
return ExtensionResult(
|
||||
reply=ReplyPlan.text(text),
|
||||
metadata={{"local_extension": PROFILE, "author": AUTHOR}},
|
||||
)
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
def plugin(context: SingleGroupContext) -> None:
|
||||
for command in COMMANDS:
|
||||
context.commands.register(command, _handler(context, command))
|
||||
'''
|
||||
|
||||
|
||||
def readme_text(descriptor) -> str:
|
||||
command_list = "\n".join(
|
||||
f"- `{command}`"
|
||||
for command in descriptor.commands
|
||||
)
|
||||
return f"""# {descriptor.extension_id}
|
||||
|
||||
Author: `{descriptor.author}`
|
||||
|
||||
{descriptor.description}
|
||||
|
||||
Help:
|
||||
|
||||
{chr(10).join(f"- `{entry}`" for entry in descriptor.help_entries)}
|
||||
|
||||
Registered commands:
|
||||
|
||||
{command_list}
|
||||
|
||||
The adapter has no direct Ledger, GUI, environment, shell, or network access.
|
||||
It delegates to the reviewed `{descriptor.extension_id}` Kernel service profile
|
||||
and returns a structured `ReplyPlan`.
|
||||
"""
|
||||
|
||||
|
||||
def test_text(descriptor) -> str:
|
||||
module = module_name(descriptor.extension_id)
|
||||
first_command = descriptor.commands[0]
|
||||
return f'''import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
|
||||
SOURCE = Path(__file__).resolve().parents[1] / "src"
|
||||
sys.path.insert(0, str(SOURCE))
|
||||
|
||||
from {module} import AUTHOR, DESCRIPTION, HELP_ENTRIES, plugin
|
||||
|
||||
|
||||
class PluginTests(unittest.TestCase):
|
||||
def test_registers_declared_commands_and_author(self):
|
||||
context = mock.Mock()
|
||||
plugin(context)
|
||||
registered = [
|
||||
call.args[0]
|
||||
for call in context.commands.register.call_args_list
|
||||
]
|
||||
self.assertEqual(AUTHOR, {descriptor.author!r})
|
||||
self.assertEqual(DESCRIPTION, {descriptor.description!r})
|
||||
self.assertEqual(HELP_ENTRIES, {descriptor.help_entries!r})
|
||||
self.assertIn({first_command!r}, registered)
|
||||
self.assertEqual(len(registered), {len(descriptor.commands)})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
'''
|
||||
|
||||
|
||||
def collection_readme(author: str) -> str:
|
||||
if author == "official":
|
||||
description = (
|
||||
"Official-owned reviewed extensions. Person memory is the only "
|
||||
"official command plugin in the current inventory."
|
||||
)
|
||||
else:
|
||||
description = (
|
||||
"Reviewed local extensions maintained by the JWeBot project. Each "
|
||||
"independently "
|
||||
"understandable function is packaged separately; dice and dice "
|
||||
"challenge intentionally remain one complete feature collection."
|
||||
)
|
||||
return f"""# {author}
|
||||
|
||||
{description}
|
||||
|
||||
Every child directory is generated from the authoritative local extension
|
||||
ownership inventory.
|
||||
"""
|
||||
|
||||
|
||||
def expected_files() -> dict[Path, str]:
|
||||
values = {
|
||||
ROOT / "extensions" / "official" / "README.md": collection_readme(
|
||||
"official"
|
||||
),
|
||||
ROOT / "extensions" / "jwebot" / "README.md": collection_readme(
|
||||
"jwebot"
|
||||
),
|
||||
}
|
||||
for descriptor in LOCAL_EXTENSIONS:
|
||||
root = (
|
||||
ROOT
|
||||
/ "extensions"
|
||||
/ descriptor.directory
|
||||
/ descriptor.extension_id
|
||||
)
|
||||
module = module_name(descriptor.extension_id)
|
||||
values.update(
|
||||
{
|
||||
root / "plugin.toml": manifest_text(descriptor),
|
||||
root / "src" / f"{module}.py": module_text(descriptor),
|
||||
(
|
||||
OFFICIAL_SRC
|
||||
/ "jwebot_official_extensions"
|
||||
/ "adapters"
|
||||
/ f"{module}.py"
|
||||
): module_text(descriptor),
|
||||
root / "tests" / "test_plugin.py": test_text(descriptor),
|
||||
root / "README.md": readme_text(descriptor),
|
||||
}
|
||||
)
|
||||
return values
|
||||
|
||||
|
||||
def stale_paths(expected: set[Path]) -> list[Path]:
|
||||
stale = [
|
||||
path
|
||||
for path in LEGACY_EXTENSION_DIRS
|
||||
if path.exists() and path not in expected
|
||||
]
|
||||
adapters = (
|
||||
OFFICIAL_SRC
|
||||
/ "jwebot_official_extensions"
|
||||
/ "adapters"
|
||||
)
|
||||
stale.extend(
|
||||
path
|
||||
for path in adapters.glob("*.py")
|
||||
if path.name != "__init__.py" and path not in expected
|
||||
)
|
||||
return sorted(stale)
|
||||
|
||||
|
||||
def synchronize(*, check: bool) -> list[Path]:
|
||||
expected = expected_files()
|
||||
changed: list[Path] = []
|
||||
for path, content in expected.items():
|
||||
current = (
|
||||
path.read_text(encoding="utf-8")
|
||||
if path.is_file()
|
||||
else None
|
||||
)
|
||||
if current == content:
|
||||
continue
|
||||
changed.append(path)
|
||||
if not check:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
stale = stale_paths(set(expected))
|
||||
changed.extend(stale)
|
||||
if not check:
|
||||
for path in stale:
|
||||
if path.is_dir():
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
path.unlink()
|
||||
return changed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
changed = synchronize(check=args.check)
|
||||
if args.check and changed:
|
||||
for path in changed:
|
||||
print(path.relative_to(ROOT), file=sys.stderr)
|
||||
return 1
|
||||
for path in changed:
|
||||
print(path.relative_to(ROOT))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user