Skip to main content

heddle_cli_args/cli/cli_args/
command_suggestions.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Near-miss table for Git-shaped verbs that are not Heddle commands.
3//!
4//! Clap already suggests Levenshtein-near names (`statuz` → `status`).
5//! Users who follow the old text vocabulary (`save`) or Git muscle memory
6//! (`add`, `stash`) land too far from the real verb for that heuristic.
7
8/// Suggested replacement for an unrecognized top-level subcommand.
9pub fn suggested_command(unknown: &str) -> Option<&'static str> {
10    match unknown.trim().to_ascii_lowercase().as_str() {
11        "save" | "add" | "commit" => Some("capture"),
12        "stash" | "branch" => Some("start"),
13        "checkout" | "switch" => Some("thread switch"),
14        "merge" => Some("land"),
15        "fetch" => Some("pull"),
16        "reset" => Some("undo"),
17        "presence" => Some("agent presence"),
18        "timeline" => Some("agent timeline"),
19        "collapse" => Some("thread collapse"),
20        "expand" => Some("thread expand"),
21        "oplog" => Some("maintenance oplog recover"),
22        _ => None,
23    }
24}
25
26/// Clap-shaped usage error that names the near-miss.
27pub fn format_unrecognized_suggestion(unknown: &str, suggested: &str) -> String {
28    format!(
29        "error: unrecognized subcommand '{unknown}'\n\n  tip: a similar subcommand exists: '{suggested}'\n\nUsage: heddle [OPTIONS] <COMMAND>\n\nFor more information, try '--help'.\n"
30    )
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    #[test]
38    fn save_and_add_suggest_capture() {
39        assert_eq!(suggested_command("save"), Some("capture"));
40        assert_eq!(suggested_command("SAVE"), Some("capture"));
41        assert_eq!(suggested_command("add"), Some("capture"));
42    }
43
44    #[test]
45    fn git_commit_suggests_capture_before_clap_fuzzy_match() {
46        assert_eq!(suggested_command("commit"), Some("capture"));
47        assert_eq!(suggested_command("COMMIT"), Some("capture"));
48    }
49
50    #[test]
51    fn stash_suggests_start() {
52        assert_eq!(suggested_command("stash"), Some("start"));
53    }
54
55    #[test]
56    fn git_concept_verbs_suggest_their_heddle_actions() {
57        for (git, heddle) in [
58            ("branch", "start"),
59            ("checkout", "thread switch"),
60            ("switch", "thread switch"),
61            ("merge", "land"),
62            ("fetch", "pull"),
63            ("reset", "undo"),
64        ] {
65            assert_eq!(suggested_command(git), Some(heddle));
66        }
67    }
68
69    #[test]
70    fn moved_commands_suggest_their_nested_paths() {
71        for (old, new) in [
72            ("presence", "agent presence"),
73            ("timeline", "agent timeline"),
74            ("collapse", "thread collapse"),
75            ("expand", "thread expand"),
76            ("oplog", "maintenance oplog recover"),
77        ] {
78            assert_eq!(suggested_command(old), Some(new));
79        }
80    }
81
82    #[test]
83    fn unknown_verbs_have_no_table_hit() {
84        assert_eq!(suggested_command("statuz"), None);
85        assert_eq!(suggested_command("capture"), None);
86    }
87
88    #[test]
89    fn suggestion_text_names_both_verbs() {
90        let text = format_unrecognized_suggestion("save", "capture");
91        assert!(text.contains("unrecognized subcommand 'save'"));
92        assert!(text.contains("capture"));
93    }
94}