"""Logging setup and small helpers shared across commands."""
import json
import logging
import os
import re
import sys
from typing import Any
import click
_SCHEME_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9+.-]*://")
_LOG_FORMAT = "%(asctime)s.%(msecs)03d | %(levelname)-7s | %(name)s:%(funcName)s:%(lineno)d - %(message)s"
_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
print_logger = logging.getLogger("ar.print")
print_logger.setLevel(logging.INFO)
print_logger.propagate = False
_print_handler = logging.StreamHandler(sys.stdout)
_print_handler.setFormatter(logging.Formatter("%(message)s"))
print_logger.addHandler(_print_handler)
def setup_logging(verbose: bool) -> None:
"""Configure root logging to the console (stderr).
DEBUG when ``verbose`` is set (shows request details), otherwise INFO.
"""
logging.basicConfig(
level=logging.DEBUG if verbose else logging.INFO,
format=_LOG_FORMAT,
datefmt=_DATE_FORMAT,
force=True,
)
def normalize_addr(addr: str) -> str:
"""Normalize a user-supplied address to a URL base.
Users pass a bare ``host:port`` (no scheme); http is assumed and prepended.
An address that already carries a scheme is left as-is.
"""
addr = addr.strip()
if not _SCHEME_RE.match(addr):
addr = "http://" + addr
return addr
def load_spec(spec_arg: str) -> Any:
"""Load a function spec from an inline JSON string or a JSON file path.
A value that points to an existing file is read from disk; anything else is
parsed as an inline JSON string. Invalid input raises ``click.BadParameter``
so the process exits with the standard parameter-error code (2).
"""
raw = spec_arg
if os.path.isfile(spec_arg):
try:
with open(spec_arg, "r", encoding="utf-8") as f:
raw = f.read()
except OSError as e:
raise click.BadParameter(f"failed to read spec file '{spec_arg}': {e}")
try:
return json.loads(raw)
except json.JSONDecodeError as e:
hint = spec_arg if os.path.isfile(spec_arg) else _truncate(raw)
raise click.BadParameter(f"spec is not valid JSON ({hint}): {e}")
def parse_json_arg(value: str, label: str) -> Any:
"""Validate that ``value`` is a JSON document, raising ``BadParameter`` if not."""
try:
return json.loads(value)
except json.JSONDecodeError as e:
raise click.BadParameter(f"{label} is not valid JSON ({_truncate(value)}): {e}")
def _truncate(text: str, limit: int = 120) -> str:
text = text.strip().replace("\n", " ")
return text if len(text) <= limit else text[:limit] + "..."