release: public alpha 0.6.0 baseline
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=68"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-config"
|
||||
version = "0.6.0a0+1015"
|
||||
description = "Profile-based configuration compiler for JWeBot"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"tomli>=2; python_version < '3.11'",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
jwebot-config = "jwebot_config.__main__:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Profile-based configuration generation and activation for JWeBot."""
|
||||
|
||||
from .profile import (
|
||||
CONFIG_FORMAT_VERSION,
|
||||
DEFAULT_PROFILE_FILES,
|
||||
ConfigError,
|
||||
ConfigLayout,
|
||||
activate_profile,
|
||||
archive_legacy_material,
|
||||
compile_profile,
|
||||
export_profile,
|
||||
import_profile,
|
||||
load_active_environment,
|
||||
load_private_context,
|
||||
migrate_legacy_context,
|
||||
migrate_legacy_environment,
|
||||
validate_profile,
|
||||
write_default_profile,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CONFIG_FORMAT_VERSION",
|
||||
"DEFAULT_PROFILE_FILES",
|
||||
"ConfigError",
|
||||
"ConfigLayout",
|
||||
"activate_profile",
|
||||
"archive_legacy_material",
|
||||
"compile_profile",
|
||||
"export_profile",
|
||||
"import_profile",
|
||||
"load_active_environment",
|
||||
"load_private_context",
|
||||
"migrate_legacy_context",
|
||||
"migrate_legacy_environment",
|
||||
"validate_profile",
|
||||
"write_default_profile",
|
||||
]
|
||||
|
||||
__version__ = "0.6.0a0+1015"
|
||||
@@ -0,0 +1,242 @@
|
||||
"""Command-line interface for JWeBot configuration profiles."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .profile import (
|
||||
ConfigError,
|
||||
ConfigLayout,
|
||||
activate_profile,
|
||||
archive_legacy_material,
|
||||
compile_profile,
|
||||
export_profile,
|
||||
import_profile,
|
||||
migrate_legacy_context,
|
||||
migrate_legacy_environment,
|
||||
validate_profile,
|
||||
write_default_profile,
|
||||
)
|
||||
|
||||
|
||||
def progress(step: int, total: int, label: str) -> None:
|
||||
width = 20
|
||||
filled = round(width * step / total)
|
||||
bar = "#" * filled + "-" * (width - filled)
|
||||
percent = round(100 * step / total)
|
||||
print(f"[{bar}] {percent:3d}% {label}")
|
||||
|
||||
|
||||
def layout_from_args(args: argparse.Namespace) -> ConfigLayout:
|
||||
return ConfigLayout.from_environment(
|
||||
config_root=args.config_root,
|
||||
state_root=args.state_root,
|
||||
)
|
||||
|
||||
|
||||
def add_layout_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument("--config-root", type=Path)
|
||||
parser.add_argument("--state-root", type=Path)
|
||||
|
||||
|
||||
def command_init(args: argparse.Namespace) -> int:
|
||||
layout = layout_from_args(args)
|
||||
progress(1, 3, "create profile")
|
||||
profile = write_default_profile(args.profile, layout, force=args.force)
|
||||
progress(2, 3, "compile profile")
|
||||
compile_profile(profile, layout)
|
||||
if args.activate:
|
||||
activate_profile(args.profile, layout)
|
||||
progress(3, 3, "ready")
|
||||
print(f"profile={args.profile}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_validate(args: argparse.Namespace) -> int:
|
||||
layout = layout_from_args(args)
|
||||
path = args.path or layout.profile(args.profile)
|
||||
profile_id = validate_profile(path)
|
||||
print(f"valid={profile_id}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_compile(args: argparse.Namespace) -> int:
|
||||
layout = layout_from_args(args)
|
||||
progress(1, 2, "validate profile")
|
||||
profile = layout.profile(args.profile)
|
||||
validate_profile(profile)
|
||||
progress(2, 2, "compile generated files")
|
||||
generated = compile_profile(profile, layout)
|
||||
print(f"generated={generated}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_activate(args: argparse.Namespace) -> int:
|
||||
layout = layout_from_args(args)
|
||||
progress(1, 3, "validate profile")
|
||||
validate_profile(layout.profile(args.profile))
|
||||
progress(2, 3, "compile atomically")
|
||||
activate_profile(args.profile, layout)
|
||||
progress(3, 3, "activate profile")
|
||||
print(f"active={args.profile}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_import(args: argparse.Namespace) -> int:
|
||||
layout = layout_from_args(args)
|
||||
progress(1, 3, "inspect package")
|
||||
profile_id = import_profile(args.source, layout, replace=args.replace)
|
||||
progress(2, 3, "compile package")
|
||||
compile_profile(layout.profile(profile_id), layout)
|
||||
if args.activate:
|
||||
activate_profile(profile_id, layout)
|
||||
progress(3, 3, "import ready")
|
||||
print(f"imported={profile_id}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_export(args: argparse.Namespace) -> int:
|
||||
layout = layout_from_args(args)
|
||||
progress(1, 2, "validate profile")
|
||||
validate_profile(layout.profile(args.profile))
|
||||
progress(2, 2, "build private export" if args.include_private else "build redacted export")
|
||||
output = export_profile(
|
||||
args.profile,
|
||||
args.output,
|
||||
layout,
|
||||
include_private=args.include_private,
|
||||
)
|
||||
print(f"export={output}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_migrate_legacy(args: argparse.Namespace) -> int:
|
||||
layout = layout_from_args(args)
|
||||
profile = layout.profile(args.profile)
|
||||
if not profile.exists():
|
||||
progress(1, 6, "create profile")
|
||||
write_default_profile(args.profile, layout)
|
||||
else:
|
||||
progress(1, 6, "reuse profile")
|
||||
progress(2, 6, "archive incidents and debug material")
|
||||
archive = archive_legacy_material(args.repository, args.profile, layout)
|
||||
progress(3, 6, "import private context")
|
||||
imported_context = migrate_legacy_context(
|
||||
args.repository,
|
||||
args.profile,
|
||||
layout,
|
||||
environment=args.environment,
|
||||
)
|
||||
progress(4, 6, "import legacy environment")
|
||||
imported_environment = migrate_legacy_environment(
|
||||
args.environment,
|
||||
args.profile,
|
||||
layout,
|
||||
)
|
||||
progress(5, 6, "compile profile")
|
||||
compile_profile(profile, layout)
|
||||
if args.activate:
|
||||
activate_profile(args.profile, layout)
|
||||
progress(6, 6, "migration ready")
|
||||
print(f"archive={archive}")
|
||||
print(f"context_imported={str(imported_context).lower()}")
|
||||
print(f"environment_imported={str(imported_environment).lower()}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_status(args: argparse.Namespace) -> int:
|
||||
layout = layout_from_args(args)
|
||||
active = layout.active_profile()
|
||||
profiles = sorted(
|
||||
path.name
|
||||
for path in layout.profiles_root.glob("*")
|
||||
if path.is_dir() and not path.name.startswith(".")
|
||||
)
|
||||
value = {
|
||||
"config_root": str(layout.config_root),
|
||||
"state_root": str(layout.state_root),
|
||||
"active": active.name if active else None,
|
||||
"profiles": profiles,
|
||||
}
|
||||
print(json.dumps(value, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate, import, validate, and activate JWeBot config profiles"
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
init = subparsers.add_parser("init")
|
||||
init.add_argument("profile")
|
||||
init.add_argument("--activate", action="store_true")
|
||||
init.add_argument("--force", action="store_true")
|
||||
add_layout_arguments(init)
|
||||
init.set_defaults(handler=command_init)
|
||||
|
||||
validate = subparsers.add_parser("validate")
|
||||
validate.add_argument("profile", nargs="?", default="default")
|
||||
validate.add_argument("--path", type=Path)
|
||||
add_layout_arguments(validate)
|
||||
validate.set_defaults(handler=command_validate)
|
||||
|
||||
compile_parser = subparsers.add_parser("compile")
|
||||
compile_parser.add_argument("profile")
|
||||
add_layout_arguments(compile_parser)
|
||||
compile_parser.set_defaults(handler=command_compile)
|
||||
|
||||
activate = subparsers.add_parser("activate")
|
||||
activate.add_argument("profile")
|
||||
add_layout_arguments(activate)
|
||||
activate.set_defaults(handler=command_activate)
|
||||
|
||||
import_parser = subparsers.add_parser("import")
|
||||
import_parser.add_argument("source", type=Path)
|
||||
import_parser.add_argument("--replace", action="store_true")
|
||||
import_parser.add_argument("--activate", action="store_true")
|
||||
add_layout_arguments(import_parser)
|
||||
import_parser.set_defaults(handler=command_import)
|
||||
|
||||
export = subparsers.add_parser("export")
|
||||
export.add_argument("profile")
|
||||
export.add_argument("--output", type=Path, required=True)
|
||||
export.add_argument("--include-private", action="store_true")
|
||||
add_layout_arguments(export)
|
||||
export.set_defaults(handler=command_export)
|
||||
|
||||
migrate = subparsers.add_parser("migrate-legacy")
|
||||
migrate.add_argument("profile")
|
||||
migrate.add_argument("--repository", type=Path, default=Path.cwd())
|
||||
migrate.add_argument(
|
||||
"--environment",
|
||||
type=Path,
|
||||
default=Path.home() / ".config" / "wechat-health-watch" / "environment",
|
||||
)
|
||||
migrate.add_argument("--activate", action="store_true")
|
||||
add_layout_arguments(migrate)
|
||||
migrate.set_defaults(handler=command_migrate_legacy)
|
||||
|
||||
status = subparsers.add_parser("status")
|
||||
add_layout_arguments(status)
|
||||
status.set_defaults(handler=command_status)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
return int(args.handler(args))
|
||||
except ConfigError as error:
|
||||
print(f"jwebot-config: {error}", file=sys.stderr)
|
||||
return 2
|
||||
except KeyboardInterrupt:
|
||||
return 130
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user