Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 4 additions & 105 deletions cli/python/base_release/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

import subprocess # pylint: disable=unused-import
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import TextIO

import base_cli
from base_setup import process # pylint: disable=unused-import
from base_setup.manifest import ManifestError
from base_setup.manifest import read_manifest

from .release_model import ReleaseContext, ReleaseError, ReleaseFinding
from .release_parser import ReleaseArguments
from .release_parser import ReleaseUsageError
from .release_parser import parse_release_args
from .release_parser import print_usage
# pylint: disable=unused-import
from .release_publish import RELEASE_STEP_TIMEOUT_SECONDS
from .release_publish import release_publish_recovery_guidance, require_interactive_publish_confirmation
Expand All @@ -31,27 +32,6 @@
)


class ReleaseUsageError(RuntimeError):
pass


@dataclass(frozen=True)
class ReleaseArguments:
command: str
version: str
manifest_path: Path | None
dry_run: bool = False
yes: bool = False


@dataclass
class ReleaseOptionState:
version: str | None = None
manifest_path: Path | None = None
dry_run: bool = False
yes: bool = False


def main(argv: list[str] | None = None) -> int:
return base_cli.run_app(app, argv)

Expand Down Expand Up @@ -95,87 +75,6 @@ def print_error(exc: ManifestError | ReleaseError) -> None:
print(guidance, file=sys.stderr)


def parse_release_args(arguments: tuple[str, ...]) -> ReleaseArguments:
if not arguments or arguments[0] in ("-h", "--help", "help"):
print_usage()
raise SystemExit(0)

command = arguments[0]
if command not in ("check", "plan", "notes", "publish"):
raise ReleaseUsageError(f"Unknown release command '{command}'.")

state = ReleaseOptionState()
remaining = list(arguments[1:])
index = 0
while index < len(remaining):
index = parse_release_option(command, remaining, index, state)

if state.version is None:
raise ReleaseUsageError(f"The 'release {command}' command requires --version.")
return ReleaseArguments(
command=command,
version=state.version,
manifest_path=state.manifest_path,
dry_run=state.dry_run,
yes=state.yes,
)


def parse_release_option(
command: str,
arguments: list[str],
index: int,
state: ReleaseOptionState,
) -> int:
arg = arguments[index]
if arg in ("-h", "--help"):
print_usage()
raise SystemExit(0)
if arg == "--version":
state.version = read_release_option_value(arguments, index, "--version")
return index + 2
if arg == "--manifest":
state.manifest_path = Path(read_release_option_value(arguments, index, "--manifest")).expanduser()
return index + 2
if arg == "--dry-run":
require_publish_option(command, "--dry-run")
state.dry_run = True
return index + 1
if arg == "--yes":
require_publish_option(command, "--yes")
state.yes = True
return index + 1
raise ReleaseUsageError(f"Unknown release {command} option '{arg}'.")


def read_release_option_value(arguments: list[str], index: int, option_name: str) -> str:
value_index = index + 1
if value_index >= len(arguments) or not arguments[value_index]:
raise ReleaseUsageError(f"Option '{option_name}' requires an argument.")
return arguments[value_index]


def require_publish_option(command: str, option_name: str) -> None:
if command != "publish":
raise ReleaseUsageError(f"Option '{option_name}' is only supported by release publish.")


def print_usage(file: TextIO = sys.stdout) -> None:
command = base_cli.delegated_display_command("base_release")
print(
f"""Usage:
{command} check --version <version> [--manifest <path>]
{command} plan --version <version> [--manifest <path>]
{command} notes --version <version> [--manifest <path>]
{command} publish --version <version> [--manifest <path>] [--dry-run] [--yes]

Purpose:
Inspect release readiness and guarded GitHub publishing for a Base-managed
project. Homebrew tap changes remain a manual handoff.""",
file=file,
)


def build_release_context(ctx: base_cli.Context, args: ReleaseArguments) -> ReleaseContext:
manifest_path = args.manifest_path or ctx.manifest_path
if manifest_path is None:
Expand Down
110 changes: 110 additions & 0 deletions cli/python/base_release/release_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from __future__ import annotations

import sys
from dataclasses import dataclass
from pathlib import Path
from typing import TextIO

import base_cli


class ReleaseUsageError(RuntimeError):
pass


@dataclass(frozen=True)
class ReleaseArguments:
command: str
version: str
manifest_path: Path | None
dry_run: bool = False
yes: bool = False


@dataclass
class ReleaseOptionState:
version: str | None = None
manifest_path: Path | None = None
dry_run: bool = False
yes: bool = False


def parse_release_args(arguments: tuple[str, ...]) -> ReleaseArguments:
if not arguments or arguments[0] in ("-h", "--help", "help"):
print_usage()
raise SystemExit(0)

command = arguments[0]
if command not in ("check", "plan", "notes", "publish"):
raise ReleaseUsageError(f"Unknown release command '{command}'.")

state = ReleaseOptionState()
remaining = list(arguments[1:])
index = 0
while index < len(remaining):
index = parse_release_option(command, remaining, index, state)

if state.version is None:
raise ReleaseUsageError(f"The 'release {command}' command requires --version.")
return ReleaseArguments(
command=command,
version=state.version,
manifest_path=state.manifest_path,
dry_run=state.dry_run,
yes=state.yes,
)


def parse_release_option(
command: str,
arguments: list[str],
index: int,
state: ReleaseOptionState,
) -> int:
arg = arguments[index]
if arg in ("-h", "--help"):
print_usage()
raise SystemExit(0)
if arg == "--version":
state.version = read_release_option_value(arguments, index, "--version")
return index + 2
if arg == "--manifest":
state.manifest_path = Path(read_release_option_value(arguments, index, "--manifest")).expanduser()
return index + 2
if arg == "--dry-run":
require_publish_option(command, "--dry-run")
state.dry_run = True
return index + 1
if arg == "--yes":
require_publish_option(command, "--yes")
state.yes = True
return index + 1
raise ReleaseUsageError(f"Unknown release {command} option '{arg}'.")


def read_release_option_value(arguments: list[str], index: int, option_name: str) -> str:
value_index = index + 1
if value_index >= len(arguments) or not arguments[value_index]:
raise ReleaseUsageError(f"Option '{option_name}' requires an argument.")
return arguments[value_index]


def require_publish_option(command: str, option_name: str) -> None:
if command != "publish":
raise ReleaseUsageError(f"Option '{option_name}' is only supported by release publish.")


def print_usage(file: TextIO = sys.stdout) -> None:
command = base_cli.delegated_display_command("base_release")
print(
f"""Usage:
{command} check --version <version> [--manifest <path>]
{command} plan --version <version> [--manifest <path>]
{command} notes --version <version> [--manifest <path>]
{command} publish --version <version> [--manifest <path>] [--dry-run] [--yes]

Purpose:
Inspect release readiness and guarded GitHub publishing for a Base-managed
project. Homebrew tap changes remain a manual handoff.""",
file=file,
)
9 changes: 9 additions & 0 deletions cli/python/base_release/tests/test_engine_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ def test_release_publish_helpers_are_split_from_engine() -> None:
assert publish_path.exists()
assert "def run_release_step" in publish_path.read_text(encoding="utf-8")
assert "def run_release_step" not in engine_path.read_text(encoding="utf-8")


def test_release_parser_helpers_are_split_from_engine() -> None:
engine_path = Path(engine.__file__)
parser_path = engine_path.with_name("release_parser.py")

assert parser_path.exists()
assert "def parse_release_args" in parser_path.read_text(encoding="utf-8")
assert "def parse_release_args" not in engine_path.read_text(encoding="utf-8")
Loading