ria-toolkit-oss/ria_toolkit_oss_cli/ria_toolkit_oss/init.py

319 lines
10 KiB
Python

"""Init command - Initialize user configuration."""
import click
from .config import (
format_config_display,
get_config_path,
load_user_config,
save_user_config,
validate_config,
)
def prompt_with_default(text: str, default: str = "") -> str:
"""Prompt user with optional default value.
Args:
text: Prompt text
default: Default value
Returns:
User input or default
"""
if default:
result = click.prompt(text, default=default, show_default=True)
else:
result = click.prompt(text, default="", show_default=False)
if result == "":
return None
return result if result else None
def init_show(config_file_path, config_path):
if not config_file_path.exists():
click.echo(f"No configuration file found at: {config_file_path}")
click.echo("\nRun 'utils init' to create a configuration.")
return
try:
config = load_user_config(config_path)
click.echo(f"Current Configuration ({config_file_path}):")
click.echo("=" * 60)
click.echo()
click.echo(format_config_display(config))
click.echo()
click.echo("To update: utils init")
click.echo("To reset: utils init --reset")
except Exception as e:
click.echo(f"Error reading configuration: {e}", err=True)
click.echo("\nRun 'utils init --reset' to recreate the configuration.")
def init_reset(config_file_path, config_path, yes):
if not config_file_path.exists():
click.echo(f"No configuration file found at: {config_file_path}")
return
# Show current config
try:
config = load_user_config(config_path)
click.echo(f"This will delete your configuration file at: {config_file_path}")
click.echo()
click.echo("Current configuration:")
for line in format_config_display(config).split("\n"):
click.echo(f" {line}")
click.echo()
except Exception:
click.echo(f"Configuration file exists but may be corrupted: {config_file_path}")
click.echo()
# Confirm deletion
if not yes:
if not click.confirm("Are you sure you want to reset?", default=False):
click.echo("Reset cancelled.")
return
# Delete config file
try:
config_file_path.unlink()
click.echo("\n✓ Configuration deleted.")
click.echo("\nRun 'utils init' to create a new configuration.")
except Exception as e:
click.echo(f"Error deleting configuration: {e}", err=True)
def build_config(author, organization, project, location, testbed):
# Build configuration
config = {}
if author:
config["author"] = author
if organization:
config["organization"] = organization
if project:
config["project"] = project
if location:
config["location"] = location
if testbed:
config["testbed"] = testbed
return config
def build_sigmf(license_id, hardware, dataset):
# Build SigMF section
sigmf = {}
if license_id:
sigmf["license"] = license_id
if hardware:
sigmf["hw"] = hardware
if dataset:
sigmf["dataset"] = dataset
return sigmf
def save_config(config, config_path, use_interactive, warnings):
# Save configuration
try:
saved_path = save_user_config(config, config_path)
click.echo(f"\n✓ Configuration saved to: {saved_path}")
if use_interactive:
click.echo()
click.echo("You can view your config anytime with: utils init --show")
click.echo("You can update values by running: utils init")
# Show warnings in non-interactive mode
elif warnings:
click.echo()
click.echo("Warnings:")
for warning in warnings:
click.echo(f" ⚠️ {warning}")
# TODO message for core team
click.echo()
click.echo("NOTE: Automatic config integration is not yet implemented.")
click.echo("Config values must currently be applied manually with --metadata flags.")
click.echo("(Core team TODO: wire config into capture/convert/transmit commands)")
return 0
except Exception as e:
click.echo(f"\nError saving configuration: {e}", err=True)
return 1
@click.command()
@click.option("--author", help="Author name (your name)")
@click.option("--organization", help="Organization/institution name")
@click.option("--project", help="Project name or identifier")
@click.option("--location", help="Physical location (lab name, site, etc.)")
@click.option("--testbed", help="Testbed identifier")
@click.option("--license", "license_id", help="Data license (SPDX identifier, default: Proprietary)")
@click.option("--hw", "hardware", help="Hardware description (e.g., PlutoSDR, USRP B210)")
@click.option("--dataset", help="Dataset identifier")
@click.option("--show", is_flag=True, help="Display current configuration and exit")
@click.option("--reset", is_flag=True, help="Delete existing config")
@click.option("--config-path", type=click.Path(), help="Use alternate config file location")
@click.option("--interactive/--no-interactive", default=None, help="Force interactive mode on/off")
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompts")
def init(
author,
organization,
project,
location,
testbed,
license_id,
hardware,
dataset,
show,
reset,
config_path,
interactive,
yes,
):
"""Initialize user configuration.
Creates a configuration file at ~/.utils/config.yaml with default metadata
values that will be used across CLI commands.
Examples:
\b
# Interactive setup
utils init
\b
# Non-interactive setup
utils init --author "Jane Doe" --project "RF_Analysis" --location "Lab_A"
\b
# Show current configuration
utils init --show
\b
# Reset configuration
utils init --reset
"""
config_file_path = get_config_path(config_path)
# Handle --show flag
if show:
init_show(config_file_path, config_path)
return
# Handle --reset flag
if reset:
init_reset(config_file_path, config_path, yes)
return
# Determine if we should use interactive mode
# Interactive if: no CLI args provided OR --interactive flag OR config file doesn't exist
has_cli_args = any([author, organization, project, location, testbed, hardware, dataset])
if interactive is None:
# Auto-detect: interactive if no args provided
use_interactive = not has_cli_args
else:
use_interactive = interactive
# Load existing config if it exists
existing_config = None
if config_file_path.exists():
try:
existing_config = load_user_config(config_path)
except Exception as e:
click.echo(f"Warning: Could not load existing config: {e}", err=True)
click.echo("Creating new configuration...\n")
# Interactive mode
if use_interactive:
click.echo()
click.echo("Welcome to Utils SDR CLI Configuration!")
click.echo("=" * 60)
click.echo()
click.echo(f"This will create a configuration file at: {config_file_path}")
click.echo()
click.echo("These values will be automatically added to recordings and conversions.")
click.echo("You can always change these later by running 'utils init' again.")
click.echo()
click.echo("Press Enter to skip optional fields.")
click.echo()
# Required information
click.echo("Required Information:")
click.echo("-" * 20)
# Use existing values as defaults
author_default = existing_config.get("author", "") if existing_config else ""
org_default = existing_config.get("organization", "") if existing_config else ""
proj_default = existing_config.get("project", "") if existing_config else ""
loc_default = existing_config.get("location", "") if existing_config else ""
test_default = existing_config.get("testbed", "") if existing_config else ""
author = click.prompt(
"Author name (your name)", default=author_default or "", show_default=bool(author_default)
)
organization = prompt_with_default("Organization (optional)", org_default)
project = prompt_with_default("Project name (optional)", proj_default)
location = prompt_with_default("Location (optional)", loc_default)
testbed = prompt_with_default("Testbed name (optional)", test_default)
# SigMF metadata
click.echo()
click.echo("SigMF Metadata (optional):")
click.echo("-" * 27)
sigmf_defaults = existing_config.get("sigmf", {}) if existing_config else {}
license_default = sigmf_defaults.get("license", "Proprietary")
hw_default = sigmf_defaults.get("hw", "")
dataset_default = sigmf_defaults.get("dataset", "")
license_id = click.prompt(
"License (e.g., Proprietary, CC-BY-4.0, MIT)", default=license_default, show_default=True
)
hardware = prompt_with_default("Hardware description (e.g., PlutoSDR)", hw_default)
dataset = prompt_with_default("Dataset name (optional)", dataset_default)
# Build configuration
config = build_config(author, organization, project, location, testbed)
# SigMF section
sigmf = build_sigmf(license_id, hardware, dataset)
if sigmf:
config["sigmf"] = sigmf
# Validate configuration
warnings = validate_config(config)
# Show configuration summary
if use_interactive:
click.echo()
click.echo("Configuration Summary:")
click.echo("-" * 22)
click.echo(format_config_display(config))
click.echo()
# Show warnings
if warnings:
click.echo("Warnings:")
for warning in warnings:
click.echo(f" ⚠️ {warning}")
click.echo()
# Confirm save
if not yes:
if not click.confirm("Save this configuration?", default=True):
click.echo("Configuration not saved.")
return
# Save configuration
return save_config(config, config_path, use_interactive, warnings)
if __name__ == "__main__":
init()