mod handlers;
use std::path::PathBuf;
use crate::args::{ParsedArgs, SearchQueryMode};
use crate::types::{DEFAULT_LOC_THRESHOLD, Mode, OutputMode};
use super::command::*;
pub fn command_to_parsed_args(cmd: &Command, global: &GlobalOptions) -> ParsedArgs {
let mut parsed = ParsedArgs {
output: if global.json {
OutputMode::Json
} else {
OutputMode::Human
},
verbose: global.verbose,
color: global.color,
..Default::default()
};
parsed.library_mode = global.library_mode;
parsed.python_library = global.python_library;
parsed.py_roots = global.py_roots.clone();
match cmd {
Command::Auto(opts) => {
if opts.for_agent_feed {
parsed.mode = Mode::ForAi;
parsed.output = if opts.agent_json {
OutputMode::Json
} else {
OutputMode::Jsonl
};
parsed.for_agent_feed = true;
parsed.agent_json = opts.agent_json;
parsed.force_full_scan = true; } else {
parsed.mode = Mode::Init;
parsed.auto_outputs = true;
}
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.suppress_duplicates = opts.suppress_duplicates;
parsed.suppress_dynamic = opts.suppress_dynamic;
parsed.full_scan = opts.full_scan;
parsed.scan_all = opts.scan_all;
parsed.use_gitignore = true; }
Command::Scan(opts) => {
parsed.mode = Mode::Init;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.full_scan = opts.full_scan;
parsed.scan_all = opts.scan_all;
parsed.use_gitignore = true;
}
Command::Tree(opts) => {
parsed.mode = Mode::Tree;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.max_depth = opts.depth;
if let Some(limit) = opts.summary {
parsed.summary = true;
parsed.summary_limit = limit;
}
parsed.summary_only = opts.summary_only;
parsed.loc_threshold = opts.loc_threshold.unwrap_or(DEFAULT_LOC_THRESHOLD);
parsed.show_hidden = opts.show_hidden;
parsed.find_artifacts = opts.find_artifacts;
parsed.show_ignored = opts.show_ignored;
if opts.show_ignored {
parsed.use_gitignore = true;
}
}
Command::Slice(opts) => {
parsed.mode = Mode::Slice;
parsed.slice_target = Some(opts.target.clone());
parsed.slice_consumers = opts.consumers;
parsed.slice_rescan = opts.rescan;
parsed.root_list = if let Some(ref root) = opts.root {
vec![root.clone()]
} else {
vec![PathBuf::from(".")]
};
}
Command::Find(opts) => {
parsed.mode = Mode::Search;
parsed.search_query_mode = SearchQueryMode::Single;
parsed.search_queries.clear();
parsed.search_query = opts
.query
.clone()
.or_else(|| opts.symbol.clone())
.or_else(|| opts.similar.clone())
.or_else(|| opts.impact.clone())
.or_else(|| {
if opts.queries.is_empty() {
return None;
}
if opts.queries.len() >= 2 {
if opts.or_mode {
Some(opts.queries.join("|"))
} else {
parsed.search_query_mode = SearchQueryMode::Split;
parsed.search_queries = opts.queries.clone();
None
}
} else {
let raw = opts.queries[0].trim().to_string();
if raw.chars().any(|c| c.is_whitespace()) && !raw.contains('|') {
let terms: Vec<String> =
raw.split_whitespace().map(|t| t.to_string()).collect();
if terms.len() >= 2 {
parsed.search_query_mode = SearchQueryMode::And;
parsed.search_queries = terms;
None
} else {
Some(raw)
}
} else {
Some(raw)
}
}
});
parsed.symbol = opts.symbol.clone();
parsed.impact = opts.impact.clone();
parsed.check_sim = opts.similar.clone();
parsed.search_dead_only = opts.dead_only;
parsed.search_exported_only = opts.exported_only;
parsed.search_lang = opts.lang.clone();
parsed.search_limit = opts.limit;
parsed.root_list = vec![PathBuf::from(".")];
}
Command::Findings(opts) => {
parsed.mode = if opts.summary {
Mode::Summary
} else {
Mode::Findings
};
parsed.output = OutputMode::Json;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.use_gitignore = true;
}
Command::Dead(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.dead_exports = true;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.dead_confidence = opts.confidence.clone();
parsed.top_dead_symbols = if opts.full {
usize::MAX
} else if let Some(top) = opts.top {
top
} else {
parsed.top_dead_symbols
};
parsed.use_gitignore = true;
parsed.with_tests = opts.with_tests;
parsed.with_helpers = opts.with_helpers;
}
Command::Cycles(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.circular = true;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.use_gitignore = true;
}
Command::Trace(_) => {
}
Command::Commands(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.tauri_preset = true;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.use_gitignore = true;
parsed.commands_name_filter = opts.name_filter.clone();
parsed.commands_missing_only = opts.missing_only;
parsed.commands_unused_only = opts.unused_only;
parsed.suppress_duplicates = opts.suppress_duplicates;
parsed.suppress_dynamic = opts.suppress_dynamic;
}
Command::Events(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.py_races = opts.races;
parsed.use_gitignore = true;
parsed.suppress_duplicates = opts.suppress_duplicates;
parsed.suppress_dynamic = opts.suppress_dynamic;
}
Command::Pipelines(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.use_gitignore = true;
}
Command::Insights(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.use_gitignore = true;
}
Command::Manifests(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
parsed.use_gitignore = true;
}
Command::Info(_opts) => {
parsed.mode = Mode::Init;
parsed.root_list = vec![PathBuf::from(".")];
}
Command::Lint(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.entrypoints = opts.entrypoints;
parsed.sarif = opts.sarif;
parsed.tauri_preset = opts.tauri;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
if opts.fail {
parsed.fail_on_missing_handlers = true;
parsed.fail_on_ghost_events = true;
}
parsed.use_gitignore = true;
parsed.suppress_duplicates = opts.suppress_duplicates;
parsed.suppress_dynamic = opts.suppress_dynamic;
}
Command::Report(opts) => {
parsed.mode = Mode::AnalyzeImports;
parsed.auto_outputs = true;
parsed.root_list = if opts.roots.is_empty() {
vec![PathBuf::from(".")]
} else {
opts.roots.clone()
};
if let Some(ref output) = opts.output {
parsed.report_path = Some(output.clone());
}
parsed.serve = opts.serve;
parsed.serve_port = opts.port;
if let Some(ref editor) = opts.editor {
parsed.editor_kind = Some(editor.clone());
}
parsed.use_gitignore = true;
}
Command::Help(opts) => {
if opts.legacy {
parsed.show_help_full = true; } else {
parsed.show_help = true;
}
}
Command::Version => {
parsed.show_version = true;
}
Command::Query(_) => {
}
Command::Impact(_) => {
}
Command::Diff(_) => {
}
Command::Memex(_) => {
}
Command::Crowd(_) => {
}
Command::Tagmap(_) => {
}
Command::Twins(_) => {
}
Command::Sniff(_) => {
}
Command::Suppress(_) => {
}
Command::Routes(_) => {
}
Command::Dist(_) => {
}
Command::Coverage(_) => {
}
Command::JqQuery(_) => {
}
Command::Focus(_) => {
}
Command::Hotspots(_) => {
}
Command::Layoutmap(_) => {
}
Command::Zombie(_) => {
}
Command::Health(_) => {
}
Command::Audit(_) => {
}
Command::Doctor(_) => {
}
Command::Plan(_) => {
}
Command::Cache(_) => {
}
}
parsed
}
pub enum DispatchResult {
Exit(i32),
ShowHelp,
ShowLegacyHelp,
ShowVersion,
Continue(Box<ParsedArgs>),
}
pub fn dispatch_command(parsed_cmd: &ParsedCommand) -> DispatchResult {
parsed_cmd.emit_deprecation_warning();
match &parsed_cmd.command {
Command::Help(opts) if opts.legacy => {
return DispatchResult::ShowLegacyHelp;
}
Command::Help(opts) if opts.full => {
return DispatchResult::ShowLegacyHelp; }
Command::Help(opts) if opts.command.is_some() => {
let cmd_name = opts.command.clone().unwrap();
if let Some(text) = Command::format_command_help(&cmd_name) {
println!("{}", text);
return DispatchResult::Exit(0);
} else {
eprintln!(
"Unknown command '{}'. Run 'loct --help' for available commands.",
cmd_name
);
return DispatchResult::Exit(1);
}
}
Command::Help(_) => {
return DispatchResult::ShowHelp;
}
Command::Version => {
return DispatchResult::ShowVersion;
}
Command::Query(opts) => {
return handlers::query::handle_query_command(opts, &parsed_cmd.global);
}
Command::Impact(opts) => {
return handlers::diff::handle_impact_command(opts, &parsed_cmd.global);
}
Command::Diff(opts) => {
return handlers::diff::handle_diff_command(opts, &parsed_cmd.global);
}
Command::Memex(opts) => {
return handlers::ai::handle_memex_command(opts, &parsed_cmd.global);
}
Command::Crowd(opts) => {
return handlers::ai::handle_crowd_command(opts, &parsed_cmd.global);
}
Command::Tagmap(opts) => {
return handlers::ai::handle_tagmap_command(opts, &parsed_cmd.global);
}
Command::Twins(opts) => {
return handlers::ai::handle_twins_command(opts, &parsed_cmd.global);
}
Command::Sniff(opts) => {
return handlers::ai::handle_sniff_command(opts, &parsed_cmd.global);
}
Command::Suppress(opts) => {
return handlers::ai::handle_suppress_command(opts, &parsed_cmd.global);
}
Command::Dead(opts) => {
return handlers::analysis::handle_dead_command(opts, &parsed_cmd.global);
}
Command::Cycles(opts) => {
return handlers::analysis::handle_cycles_command(opts, &parsed_cmd.global);
}
Command::Trace(opts) => {
return handlers::analysis::handle_trace_command(opts, &parsed_cmd.global);
}
Command::Commands(opts) => {
return handlers::analysis::handle_commands_command(opts, &parsed_cmd.global);
}
Command::Routes(opts) => {
return handlers::analysis::handle_routes_command(opts, &parsed_cmd.global);
}
Command::Events(opts) => {
return handlers::analysis::handle_events_command(opts, &parsed_cmd.global);
}
Command::Pipelines(opts) => {
return handlers::analysis::handle_pipelines_command(opts, &parsed_cmd.global);
}
Command::Insights(opts) => {
return handlers::analysis::handle_insights_command(opts, &parsed_cmd.global);
}
Command::Manifests(opts) => {
return handlers::analysis::handle_manifests_command(opts, &parsed_cmd.global);
}
Command::Lint(opts) => {
return handlers::output::handle_lint_command(opts, &parsed_cmd.global);
}
Command::Dist(opts) => {
return handlers::output::handle_dist_command(opts, &parsed_cmd.global);
}
Command::Coverage(opts) => {
return handlers::watch::handle_coverage_command(opts, &parsed_cmd.global);
}
Command::JqQuery(opts) => {
return handlers::query::handle_jq_query_command(opts, &parsed_cmd.global);
}
Command::Focus(opts) => {
return handlers::analysis::handle_focus_command(opts, &parsed_cmd.global);
}
Command::Hotspots(opts) => {
return handlers::analysis::handle_hotspots_command(opts, &parsed_cmd.global);
}
Command::Layoutmap(opts) => {
return handlers::analysis::handle_layoutmap_command(opts, &parsed_cmd.global);
}
Command::Zombie(opts) => {
return handlers::analysis::handle_zombie_command(opts, &parsed_cmd.global);
}
Command::Health(opts) => {
return handlers::analysis::handle_health_command(opts, &parsed_cmd.global);
}
Command::Audit(opts) => {
return handlers::analysis::handle_audit_command(opts, &parsed_cmd.global);
}
Command::Doctor(opts) => {
return handlers::analysis::handle_doctor_command(opts, &parsed_cmd.global);
}
Command::Plan(opts) => {
return handlers::analysis::handle_plan_command(opts, &parsed_cmd.global);
}
Command::Cache(opts) => {
return handlers::cache::handle_cache_command(opts);
}
Command::Scan(opts) if opts.watch => {
return handlers::watch::handle_scan_watch_command(opts, &parsed_cmd.global);
}
_ => {}
}
let parsed_args = command_to_parsed_args(&parsed_cmd.command, &parsed_cmd.global);
DispatchResult::Continue(Box::new(parsed_args))
}
pub(crate) fn load_or_create_snapshot_for_roots(
roots: &[std::path::PathBuf],
global: &GlobalOptions,
) -> std::io::Result<crate::snapshot::Snapshot> {
use crate::args::ParsedArgs;
use crate::snapshot::Snapshot;
use crate::snapshot::resolve_snapshot_root;
let snapshot_root = resolve_snapshot_root(roots);
let requested_roots =
normalize_roots_for_scope_compare(roots.iter().map(|p| p.as_path()), &snapshot_root);
if !global.fresh {
match Snapshot::load(&snapshot_root) {
Ok(s) => {
let snapshot_roots = normalize_roots_for_scope_compare(
s.metadata.roots.iter().map(std::path::Path::new),
&snapshot_root,
);
if requested_roots != snapshot_roots {
if global.no_scan {
return Err(std::io::Error::other(format!(
"Snapshot scope mismatch and --no-scan is set.\nrequested roots: [{}]\nsnapshot roots: [{}]\nRun `loct scan` (or the command without --no-scan) to refresh scope.",
requested_roots.join(", "),
snapshot_roots.join(", ")
)));
}
if !global.quiet {
eprintln!(
"[loct] Snapshot roots differ from requested roots; refreshing snapshot scope.\n requested: [{}]\n snapshot: [{}]",
requested_roots.join(", "),
snapshot_roots.join(", ")
);
}
} else if s.is_stale(&snapshot_root) {
if global.fail_stale || global.no_scan {
let snap_commit = s.metadata.git_commit.as_deref().unwrap_or("unknown");
let current = get_current_git_head(&snapshot_root)
.unwrap_or_else(|| "unknown".into());
return Err(std::io::Error::other(format!(
"Snapshot is stale: snapshot commit={} but current HEAD={}. Run 'loct scan' to refresh.",
&snap_commit[..7.min(snap_commit.len())],
¤t[..7.min(current.len())]
)));
}
if !global.quiet {
eprintln!("[loct] Snapshot stale, rescanning...");
}
} else {
return Ok(s);
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
if global.no_scan {
return Err(std::io::Error::other(
"No snapshot found and --no-scan is set. Run 'loct' first to create a snapshot.",
));
}
if !global.quiet {
eprintln!("[loct] No snapshot found, running initial scan...");
}
}
Err(e) => return Err(e), }
} else {
if !global.quiet {
eprintln!("[loct] --fresh: forcing rescan...");
}
}
let parsed = ParsedArgs {
verbose: global.verbose,
use_gitignore: true,
output: if global.json {
OutputMode::Json
} else {
OutputMode::Human
},
..Default::default()
};
crate::snapshot::run_init_with_options(roots, &parsed, global.json || global.quiet)?;
Snapshot::load(&snapshot_root)
}
fn normalize_roots_for_scope_compare<'a, I>(
roots: I,
snapshot_root: &std::path::Path,
) -> Vec<String>
where
I: Iterator<Item = &'a std::path::Path>,
{
let mut normalized = Vec::new();
for root in roots {
let candidate = if root.is_absolute() {
root.to_path_buf()
} else {
snapshot_root.join(root)
};
let canon = candidate.canonicalize().unwrap_or(candidate);
normalized.push(canon.to_string_lossy().replace('\\', "/"));
}
normalized.sort();
normalized.dedup();
normalized
}
pub(crate) fn load_or_create_snapshot(
root: &std::path::Path,
global: &GlobalOptions,
) -> std::io::Result<crate::snapshot::Snapshot> {
let root_list = vec![root.to_path_buf()];
load_or_create_snapshot_for_roots(&root_list, global)
}
fn get_current_git_head(root: &std::path::Path) -> Option<String> {
std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(root)
.output()
.ok()
.and_then(|output| {
if output.status.success() {
String::from_utf8(output.stdout)
.ok()
.map(|s| s.trim().to_string())
} else {
None
}
})
}
pub(crate) fn is_test_file(path: &str) -> bool {
let path_lower = path.to_lowercase();
let filename = std::path::Path::new(path)
.file_name()
.and_then(|f| f.to_str())
.unwrap_or("")
.to_lowercase();
if path_lower.contains("/tests/")
|| path_lower.contains("/__tests__/")
|| path_lower.contains("/test/")
|| path_lower.contains("/spec/")
|| path_lower.contains("/fixtures/")
|| path_lower.contains("/mocks/")
{
return true;
}
if filename.contains("_test.")
|| filename.contains(".test.")
|| filename.contains("_spec.")
|| filename.contains(".spec.")
|| filename.contains("_tests.") || filename.starts_with("test_")
|| filename.starts_with("spec_")
|| filename.starts_with("tests.") || filename == "conftest.py"
{
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_auto_command_to_parsed_args() {
let cmd = Command::Auto(AutoOptions {
roots: vec![PathBuf::from(".")],
full_scan: true,
scan_all: false,
for_agent_feed: false,
agent_json: false,
suppress_duplicates: false,
suppress_dynamic: false,
});
let global = GlobalOptions::default();
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::Init));
assert!(parsed.full_scan);
assert!(!parsed.scan_all);
}
#[test]
fn test_dead_command_to_parsed_args() {
let cmd = Command::Dead(DeadOptions {
roots: vec![],
confidence: Some("high".into()),
top: Some(10),
full: false,
path_filter: None,
with_tests: false,
with_helpers: false,
with_shadows: false,
with_ambient: false,
with_dynamic: false,
});
let global = GlobalOptions {
json: true,
..Default::default()
};
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::AnalyzeImports));
assert!(parsed.dead_exports);
assert_eq!(parsed.dead_confidence, Some("high".into()));
assert_eq!(parsed.top_dead_symbols, 10);
assert!(!parsed.with_tests);
assert!(!parsed.with_helpers);
assert!(matches!(parsed.output, OutputMode::Json));
}
#[test]
fn test_tree_command_to_parsed_args() {
let cmd = Command::Tree(TreeOptions {
roots: vec![PathBuf::from("src")],
depth: Some(3),
summary: Some(5),
summary_only: false,
loc_threshold: Some(500),
show_hidden: true,
find_artifacts: false,
show_ignored: false,
});
let global = GlobalOptions::default();
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::Tree));
assert_eq!(parsed.max_depth, Some(3));
assert!(parsed.summary);
assert_eq!(parsed.summary_limit, 5);
assert_eq!(parsed.loc_threshold, 500);
assert!(parsed.show_hidden);
}
#[test]
fn test_parse_findings_command_to_parsed_args() {
let cmd = Command::Findings(FindingsOptions {
roots: vec![PathBuf::from("src")],
summary: true,
});
let global = GlobalOptions::default();
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::Summary));
assert!(matches!(parsed.output, OutputMode::Json));
assert_eq!(parsed.root_list, vec![PathBuf::from("src")]);
}
#[test]
fn test_slice_command_to_parsed_args() {
let cmd = Command::Slice(SliceOptions {
target: "src/main.rs".into(),
root: None,
consumers: true,
depth: None,
rescan: false,
});
let global = GlobalOptions {
json: true,
..Default::default()
};
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::Slice));
assert_eq!(parsed.slice_target, Some("src/main.rs".into()));
assert!(parsed.slice_consumers);
assert!(matches!(parsed.output, OutputMode::Json));
}
#[test]
fn test_normalize_roots_for_scope_compare_relative_and_absolute_match() {
let tmp = TempDir::new().expect("temp dir");
std::fs::create_dir_all(tmp.path().join("src")).expect("create src");
let relative = normalize_roots_for_scope_compare(
[std::path::Path::new("src")].into_iter(),
tmp.path(),
);
let absolute = normalize_roots_for_scope_compare(
[tmp.path().join("src")].iter().map(|p| p.as_path()),
tmp.path(),
);
assert_eq!(relative, absolute);
assert_eq!(relative.len(), 1);
}
#[test]
fn test_normalize_roots_for_scope_compare_sorted_and_deduped() {
let tmp = TempDir::new().expect("temp dir");
std::fs::create_dir_all(tmp.path().join("a")).expect("create a");
std::fs::create_dir_all(tmp.path().join("b")).expect("create b");
let normalized = normalize_roots_for_scope_compare(
[
std::path::Path::new("b"),
std::path::Path::new("./a"),
std::path::Path::new("b"),
]
.into_iter(),
tmp.path(),
);
assert_eq!(normalized.len(), 2);
assert!(normalized[0] <= normalized[1]);
}
#[test]
fn test_find_multi_arg_defaults_to_split_mode() {
let cmd = Command::Find(FindOptions {
queries: vec!["Props".into(), "Options".into(), "ViewModel".into()],
..Default::default()
});
let global = GlobalOptions::default();
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::Search));
assert!(matches!(parsed.search_query_mode, SearchQueryMode::Split));
assert_eq!(parsed.search_queries, vec!["Props", "Options", "ViewModel"]);
assert!(parsed.search_query.is_none());
}
#[test]
fn test_find_single_arg_with_spaces_defaults_to_and_mode() {
let cmd = Command::Find(FindOptions {
queries: vec!["Props Options ViewModel".into()],
..Default::default()
});
let global = GlobalOptions::default();
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::Search));
assert!(matches!(parsed.search_query_mode, SearchQueryMode::And));
assert_eq!(parsed.search_queries, vec!["Props", "Options", "ViewModel"]);
assert!(parsed.search_query.is_none());
}
#[test]
fn test_find_multi_arg_can_force_legacy_or_mode() {
let cmd = Command::Find(FindOptions {
queries: vec!["Props".into(), "Options".into()],
or_mode: true,
..Default::default()
});
let global = GlobalOptions::default();
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::Search));
assert!(matches!(parsed.search_query_mode, SearchQueryMode::Single));
assert_eq!(parsed.search_query.as_deref(), Some("Props|Options"));
assert!(parsed.search_queries.is_empty());
}
#[test]
fn test_find_single_arg_with_pipe_stays_single_mode() {
let cmd = Command::Find(FindOptions {
queries: vec!["Props|Options|ViewModel".into()],
..Default::default()
});
let global = GlobalOptions::default();
let parsed = command_to_parsed_args(&cmd, &global);
assert!(matches!(parsed.mode, Mode::Search));
assert!(matches!(parsed.search_query_mode, SearchQueryMode::Single));
assert_eq!(
parsed.search_query.as_deref(),
Some("Props|Options|ViewModel")
);
assert!(parsed.search_queries.is_empty());
}
#[test]
fn test_dispatch_help_command() {
let parsed_cmd = ParsedCommand::new(
Command::Help(HelpOptions::default()),
GlobalOptions::default(),
);
let result = dispatch_command(&parsed_cmd);
assert!(matches!(result, DispatchResult::ShowHelp));
}
#[test]
fn test_dispatch_legacy_help_command() {
let parsed_cmd = ParsedCommand::new(
Command::Help(HelpOptions {
legacy: true,
..Default::default()
}),
GlobalOptions::default(),
);
let result = dispatch_command(&parsed_cmd);
assert!(matches!(result, DispatchResult::ShowLegacyHelp));
}
#[test]
fn test_dispatch_version_command() {
let parsed_cmd = ParsedCommand::new(Command::Version, GlobalOptions::default());
let result = dispatch_command(&parsed_cmd);
assert!(matches!(result, DispatchResult::ShowVersion));
}
#[test]
fn test_crowd_command_to_dispatch() {
let tmp = TempDir::new().expect("temp dir");
let parsed_cmd = ParsedCommand::new(
Command::Crowd(CrowdOptions {
pattern: Some("message".into()),
roots: vec![tmp.path().to_path_buf()],
..Default::default()
}),
GlobalOptions::default(),
);
let result = dispatch_command(&parsed_cmd);
assert!(matches!(result, DispatchResult::Exit(_)));
}
}