use crate::cli::Command;
use anyhow::Result;
use magellan::OutputFormat;
use std::path::PathBuf;
use crate::cli::parsers::*;
use crate::db_resolver::resolve_db_path;
pub fn parse_args_impl<F>(print_version: F) -> Result<Command>
where
F: FnOnce(),
{
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
return Err(anyhow::anyhow!("Missing command"));
}
let command = &args[1];
if command == "--version" || command == "-V" {
print_version();
std::process::exit(0);
}
if command == "--help" || command == "-h" {
crate::cli::print_short_usage();
std::process::exit(0);
}
if command == "--help-full" || command == "-H" {
crate::cli::print_full_usage();
std::process::exit(0);
}
match command.as_str() {
"watch" => parse_watch_args(&args[2..]),
"backfill" => parse_backfill_args(&args[2..]),
"cross-file-refs" => parse_cross_file_refs_args(&args[2..]),
"delete" => parse_delete_args(&args[2..]),
"export" => parse_export_args(&args[2..]),
"index" => parse_index_args(&args[2..]),
"import-lsif" => parse_import_lsif_args(&args[2..]),
"ingest-coverage" => parse_ingest_coverage_args(&args[2..]),
"enrich" => parse_enrich_args(&args[2..]),
"status" => parse_status_args(&args[2..]),
"project-metadata" => parse_project_metadata_args(&args[2..]),
"context" => parse_context_args(&args[2..]),
"doctor" => parse_doctor_args(&args[2..]),
"query" => parse_query_args(&args[2..]),
"search" => parse_search_args(&args[2..]),
"find" => parse_find_args(&args[2..]),
"refs" => parse_refs_args(&args[2..]),
"get" => parse_get_args(&args[2..]),
"get-file" => parse_get_file_args(&args[2..]),
"files" => parse_files_args(&args[2..]),
"verify" => parse_verify_args(&args[2..]),
"refresh" => parse_refresh_args(&args[2..]),
"label" => parse_label_args(&args[2..]),
"collisions" => parse_collisions_args(&args[2..]),
"migrate" => parse_migrate_args(&args[2..]),
"migrate-backend" => parse_migrate_backend_args(&args[2..]),
"temporal-sweep" => parse_temporal_sweep_args(&args[2..]),
"temporal-status" => parse_temporal_status_args(&args[2..]),
"temporal-barcode" => parse_temporal_barcode_args(&args[2..]),
"as-of" => parse_as_of_args(&args[2..]),
"orient" => parse_orient_args(&args[2..]),
"chunks" => parse_chunks_args(&args[2..]),
"chunk-by-span" => parse_chunk_by_span_args(&args[2..]),
"chunk-by-symbol" => parse_chunk_by_symbol_args(&args[2..]),
"ast" => parse_ast_args(&args[2..]),
"find-ast" => parse_find_ast_args(&args[2..]),
"reachable" => parse_reachable_args(&args[2..]),
"dead-code" => parse_dead_code_args(&args[2..]),
"cycles" => parse_cycles_args(&args[2..]),
"catalog" => {
if args.len() >= 3 && args[2] == "describe" {
parse_catalog_describe_args(&args[3..])
} else {
parse_catalog_args(&args[2..])
}
}
"config" => {
if args.len() < 3 {
return Err(anyhow::anyhow!("config subcommand required: show, init"));
}
match args[2].as_str() {
"show" => parse_config_show_args(&args[3..]),
"init" => parse_config_init_args(&args[3..]),
_ => Err(anyhow::anyhow!("Unknown config subcommand: {}", args[2])),
}
}
"condense" => parse_condense_args(&args[2..]),
"init" => parse_project_init_args(&args[2..]),
"install-hook" => parse_install_hook_args(&args[2..]),
"paths" => parse_paths_args(&args[2..]),
"score" => parse_score_args(&args[2..]),
"slice" => parse_slice_args(&args[2..]),
"source-inventory" => parse_source_inventory_args(&args[2..]),
"service" => {
if args.len() < 3 {
return Err(anyhow::anyhow!("service subcommand required: start, stop, list, register, unregister, pause, resume, status"));
}
let mut output_format = OutputFormat::Human;
let mut name: Option<String> = None;
let mut root: Option<PathBuf> = None;
let mut event_type: Option<String> = None;
let mut event_project: Option<String> = None;
let mut since_hours: Option<u64> = None;
let mut event_limit: usize = 50;
let mut json_output = false;
let mut include: Vec<String> = Vec::new();
let mut exclude: Vec<String> = Vec::new();
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--output" | "-o" => {
let value = parse_required_arg(&args[..], &mut i, "--output")?;
output_format = parse_output_format(&value)?;
}
"--name" | "-n" => {
name = Some(parse_required_arg(&args[..], &mut i, "--name")?);
}
"--root" | "-r" => {
root = Some(parse_path_arg(&args[..], &mut i, "--root")?);
}
"--include" | "-I" => {
include.push(parse_required_arg(&args[..], &mut i, "--include")?);
}
"--exclude" | "-E" => {
exclude.push(parse_required_arg(&args[..], &mut i, "--exclude")?);
}
"--type" | "-t" => {
event_type = Some(parse_required_arg(&args[..], &mut i, "--type")?);
}
"--project" | "-p" => {
event_project = Some(parse_required_arg(&args[..], &mut i, "--project")?);
}
"--since" => {
since_hours = Some(
parse_required_arg(&args[..], &mut i, "--since")?
.parse()
.map_err(|_| anyhow::anyhow!("--since must be a number"))?,
);
}
"--limit" | "-l" => {
event_limit = parse_required_arg(&args[..], &mut i, "--limit")?
.parse()
.map_err(|_| anyhow::anyhow!("--limit must be a number"))?;
}
"--json" | "-j" => {
json_output = true;
i += 1;
}
_ => i += 1,
}
}
let positional = if args.len() > 3 {
Some(args[3].clone())
} else {
None
};
let action = match args[2].as_str() {
"start" => crate::service_cmd::ServiceAction::Start,
"stop" => crate::service_cmd::ServiceAction::Stop,
"list" => crate::service_cmd::ServiceAction::List,
"register" => crate::service_cmd::ServiceAction::Register {
root: root.unwrap_or_else(|| PathBuf::from(".")),
name: name.or(positional),
include,
exclude,
},
"unregister" => crate::service_cmd::ServiceAction::Unregister {
name: name.or(positional).unwrap_or_default(),
},
"pause" => crate::service_cmd::ServiceAction::Pause {
name: name.or(positional).unwrap_or_default(),
},
"resume" => crate::service_cmd::ServiceAction::Resume {
name: name.or(positional).unwrap_or_default(),
},
"status" => crate::service_cmd::ServiceAction::Status,
"stats" => crate::service_cmd::ServiceAction::Stats,
"events" => crate::service_cmd::ServiceAction::Events {
project: event_project,
event_type,
since_hours,
limit: event_limit,
json_output,
},
_ => return Err(anyhow::anyhow!("Unknown service subcommand: {}", args[2])),
};
Ok(Command::Service {
action,
output_format,
})
}
"candidate-fact" => parse_candidate_fact_args(&args[2..]),
"cypher" => parse_cypher_args(&args[2..]),
"hnsw-create" => parse_hnsw_create_args(&args[2..]),
"hnsw-query" => parse_hnsw_query_args(&args[2..]),
"ask" => parse_ask_args(&args[2..]),
"blast-score" => parse_blast_score_args(&args[2..]),
"navigate" => parse_navigate_args(&args[2..]),
"explore" => parse_explore_args(&args[2..]),
"telemetry" => parse_telemetry_args(&args[2..]),
"hopgraph" => parse_hopgraph_args(&args[2..]),
"embed" => parse_embed_args(&args[2..]),
"features" => parse_features_args(&args[2..]),
"service-daemon" => Ok(Command::ServiceDaemon),
_ => Err(anyhow::anyhow!("Unknown command: {}", command)),
}
}
pub fn parse_temporal_sweep_args(args: &[String]) -> Result<Command> {
let mut db_path: Option<PathBuf> = None;
let mut repo_path: Option<PathBuf> = None;
let mut every_n: Option<usize> = None;
let mut tags_only = false;
let mut merge_commits_only = false;
let mut since_commit_time: Option<i64> = None;
let mut until_commit_time: Option<i64> = None;
let mut output_format = OutputFormat::Human;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--db" => db_path = Some(parse_path_arg(args, &mut i, "--db")?),
"--repo" => repo_path = Some(parse_path_arg(args, &mut i, "--repo")?),
"--every" => {
every_n = Some(parse_required_arg(args, &mut i, "--every")?.parse()?);
}
"--tags-only" => {
tags_only = true;
i += 1;
}
"--merge-commits-only" => {
merge_commits_only = true;
i += 1;
}
"--since" => {
since_commit_time = Some(parse_required_arg(args, &mut i, "--since")?.parse()?);
}
"--until" => {
until_commit_time = Some(parse_required_arg(args, &mut i, "--until")?.parse()?);
}
"--output" => {
let value = parse_required_arg(args, &mut i, "--output")?;
output_format = parse_output_format(&value)?;
}
_ => return Err(anyhow::anyhow!("Unknown argument: {}", args[i])),
}
}
let db_path = resolve_db_path(db_path)?;
let repo_path = repo_path.ok_or_else(|| anyhow::anyhow!("--repo is required"))?;
Ok(Command::TemporalSweep {
db_path,
repo_path,
every_n,
tags_only,
merge_commits_only,
since_commit_time,
until_commit_time,
output_format,
})
}
pub fn parse_temporal_status_args(args: &[String]) -> Result<Command> {
let mut db_path: Option<PathBuf> = None;
let mut output_format = OutputFormat::Human;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--db" => db_path = Some(parse_path_arg(args, &mut i, "--db")?),
"--output" => {
let value = parse_required_arg(args, &mut i, "--output")?;
output_format = parse_output_format(&value)?;
}
_ => return Err(anyhow::anyhow!("Unknown argument: {}", args[i])),
}
}
let db_path = resolve_db_path(db_path)?;
Ok(Command::TemporalStatus {
db_path,
output_format,
})
}
pub fn parse_temporal_barcode_args(args: &[String]) -> Result<Command> {
let mut db_path: Option<PathBuf> = None;
let mut stable_id: Option<String> = None;
let mut edge_source: Option<String> = None;
let mut edge_target: Option<String> = None;
let mut edge_kind: Option<String> = None;
let mut scc = false;
let mut output_format = OutputFormat::Human;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--db" => db_path = Some(parse_path_arg(args, &mut i, "--db")?),
"--symbol" => stable_id = Some(parse_required_arg(args, &mut i, "--symbol")?),
"--edge-source" => {
edge_source = Some(parse_required_arg(args, &mut i, "--edge-source")?)
}
"--edge-target" => {
edge_target = Some(parse_required_arg(args, &mut i, "--edge-target")?)
}
"--kind" => edge_kind = Some(parse_required_arg(args, &mut i, "--kind")?),
"--scc" => {
scc = true;
i += 1;
}
"--output" => {
let value = parse_required_arg(args, &mut i, "--output")?;
output_format = parse_output_format(&value)?;
}
_ => return Err(anyhow::anyhow!("Unknown argument: {}", args[i])),
}
}
let db_path = resolve_db_path(db_path)?;
let edge_mode = edge_source.is_some() || edge_target.is_some() || edge_kind.is_some();
if scc && (stable_id.is_some() || edge_mode) {
return Err(anyhow::anyhow!(
"Use exactly one selector: --symbol, --scc, or --edge-source/--edge-target"
));
}
if edge_mode && (edge_source.is_none() || edge_target.is_none()) {
return Err(anyhow::anyhow!(
"--edge-source and --edge-target are both required for edge barcode queries"
));
}
if !scc && stable_id.is_none() && !edge_mode {
return Err(anyhow::anyhow!(
"Provide --symbol, --scc, or --edge-source/--edge-target"
));
}
if stable_id.is_some() && edge_mode {
return Err(anyhow::anyhow!(
"Use either --symbol or --edge-source/--edge-target, not both"
));
}
Ok(Command::TemporalBarcode {
db_path,
stable_id,
edge_source,
edge_target,
edge_kind,
scc,
output_format,
})
}
pub fn parse_as_of_args(args: &[String]) -> Result<Command> {
let mut db_path: Option<PathBuf> = None;
let mut commit_oid: Option<String> = None;
let mut symbol_name: Option<String> = None;
let mut output_format = OutputFormat::Human;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--db" => db_path = Some(parse_path_arg(args, &mut i, "--db")?),
"--commit" => commit_oid = Some(parse_required_arg(args, &mut i, "--commit")?),
"--symbol" => symbol_name = Some(parse_required_arg(args, &mut i, "--symbol")?),
"--output" => {
let value = parse_required_arg(args, &mut i, "--output")?;
output_format = parse_output_format(&value)?;
}
_ => return Err(anyhow::anyhow!("Unknown argument: {}", args[i])),
}
}
let db_path = resolve_db_path(db_path)?;
let commit_oid = commit_oid.ok_or_else(|| anyhow::anyhow!("--commit is required"))?;
let symbol_name = symbol_name.ok_or_else(|| anyhow::anyhow!("--symbol is required"))?;
Ok(Command::AsOf {
db_path,
commit_oid,
symbol_name,
output_format,
})
}
pub fn parse_orient_args(args: &[String]) -> Result<Command> {
let mut db_path: Option<PathBuf> = None;
let mut repo_path: Option<PathBuf> = None;
let mut top_n: usize = 10;
let mut output_format = OutputFormat::Human;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--db" => db_path = Some(parse_path_arg(args, &mut i, "--db")?),
"--repo" => repo_path = Some(parse_path_arg(args, &mut i, "--repo")?),
"--top" => {
top_n = parse_required_arg(args, &mut i, "--top")?.parse()?;
}
"--output" => {
let value = parse_required_arg(args, &mut i, "--output")?;
output_format = parse_output_format(&value)?;
}
_ => return Err(anyhow::anyhow!("Unknown argument: {}", args[i])),
}
}
let db_path = resolve_db_path(db_path)?;
Ok(Command::Orient {
db_path,
repo_path,
top_n,
output_format,
})
}
pub fn parse_install_hook_args(args: &[String]) -> Result<Command> {
let mut threshold: f64 = 10.0;
let mut strict: bool = false;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--threshold" => {
let value = parse_required_arg(args, &mut i, "--threshold")?;
threshold = value
.parse()
.map_err(|_| anyhow::anyhow!("--threshold must be a number"))?;
}
"--strict" => {
strict = true;
i += 1;
}
_ => return Err(anyhow::anyhow!("Unknown argument: {}", args[i])),
}
}
Ok(Command::InstallHook { threshold, strict })
}
pub fn parse_args() -> Result<Command> {
parse_args_impl(|| {
println!("{}", crate::version::version());
})
}
pub fn parse_files_args(args: &[String]) -> Result<Command> {
let mut db_path: Option<PathBuf> = None;
let mut output_format = OutputFormat::Human;
let mut with_symbols = false;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--db" => db_path = Some(parse_path_arg(args, &mut i, "--db")?),
"--output" => {
let value = parse_required_arg(args, &mut i, "--output")?;
output_format = parse_output_format(&value)?;
}
"--symbols" => {
with_symbols = true;
i += 1;
}
_ => return Err(anyhow::anyhow!("Unknown argument: {}", args[i])),
}
}
let db_path = resolve_db_path(db_path)?;
Ok(Command::Files {
db_path,
output_format,
with_symbols,
})
}