Skip to main content

j_cli/interactive/
parser.rs

1use crate::cli::SubCmd;
2use crate::command;
3use crate::config::YamlConfig;
4use crate::constants::cmd;
5
6/// 交互命令解析结果(三态)
7pub enum ParseResult {
8    /// 成功解析为内置命令
9    Matched(SubCmd),
10    /// 是内置命令但参数不足,已打印 usage 提示
11    Handled,
12    /// 不是内置命令
13    NotFound,
14}
15
16/// 在交互模式下执行命令
17pub fn execute_interactive_command(args: &[String], config: &mut YamlConfig) {
18    if args.is_empty() {
19        return;
20    }
21
22    let cmd_str = &args[0];
23
24    if cmd::EXIT.contains(&cmd_str.as_str()) {
25        command::system::handle_exit();
26        return;
27    }
28
29    match parse_interactive_command(args) {
30        ParseResult::Matched(subcmd) => {
31            command::dispatch(subcmd, config);
32        }
33        ParseResult::Handled => {}
34        ParseResult::NotFound => {
35            command::open::handle_open(args, config);
36        }
37    }
38}
39
40/// 从交互模式输入的参数解析出 SubCmd
41pub fn parse_interactive_command(args: &[String]) -> ParseResult {
42    if args.is_empty() {
43        return ParseResult::NotFound;
44    }
45
46    let cmd = args[0].as_str();
47    let rest = &args[1..];
48
49    let is = |names: &[&str]| names.contains(&cmd);
50
51    if is(cmd::SET) {
52        if rest.is_empty() {
53            crate::usage!("set <alias> <path>");
54            return ParseResult::Handled;
55        }
56        ParseResult::Matched(SubCmd::Set {
57            alias: rest[0].clone(),
58            path: rest[1..].to_vec(),
59        })
60    } else if is(cmd::REMOVE) {
61        match rest.first() {
62            Some(alias) => ParseResult::Matched(SubCmd::Remove {
63                alias: alias.clone(),
64            }),
65            None => {
66                crate::usage!("rm <alias>");
67                ParseResult::Handled
68            }
69        }
70    } else if is(cmd::RENAME) {
71        if rest.len() < 2 {
72            crate::usage!("rename <alias> <new_alias>");
73            return ParseResult::Handled;
74        }
75        ParseResult::Matched(SubCmd::Rename {
76            alias: rest[0].clone(),
77            new_alias: rest[1].clone(),
78        })
79    } else if is(cmd::MODIFY) {
80        if rest.is_empty() {
81            crate::usage!("mf <alias> <new_path>");
82            return ParseResult::Handled;
83        }
84        ParseResult::Matched(SubCmd::Modify {
85            alias: rest[0].clone(),
86            path: rest[1..].to_vec(),
87        })
88    } else if is(cmd::NOTE) {
89        if rest.len() < 2 {
90            crate::usage!("note <alias> <category>");
91            return ParseResult::Handled;
92        }
93        ParseResult::Matched(SubCmd::Note {
94            alias: rest[0].clone(),
95            category: rest[1].clone(),
96        })
97    } else if is(cmd::DENOTE) {
98        if rest.len() < 2 {
99            crate::usage!("denote <alias> <category>");
100            return ParseResult::Handled;
101        }
102        ParseResult::Matched(SubCmd::Denote {
103            alias: rest[0].clone(),
104            category: rest[1].clone(),
105        })
106    } else if is(cmd::LIST) {
107        ParseResult::Matched(SubCmd::List {
108            part: rest.first().cloned(),
109        })
110    } else if is(cmd::CONTAIN) {
111        if rest.is_empty() {
112            crate::usage!("contain <alias> [sections]");
113            return ParseResult::Handled;
114        }
115        ParseResult::Matched(SubCmd::Contain {
116            alias: rest[0].clone(),
117            containers: rest.get(1).cloned(),
118        })
119    } else if is(cmd::LOG) {
120        if rest.len() < 2 {
121            crate::usage!("log mode <verbose|concise>");
122            return ParseResult::Handled;
123        }
124        ParseResult::Matched(SubCmd::Log {
125            key: rest[0].clone(),
126            value: rest[1].clone(),
127        })
128    } else if is(cmd::CHANGE) {
129        if rest.len() < 3 {
130            crate::usage!("change <part> <field> <value>");
131            return ParseResult::Handled;
132        }
133        ParseResult::Matched(SubCmd::Change {
134            part: rest[0].clone(),
135            field: rest[1].clone(),
136            value: rest[2].clone(),
137        })
138    } else if is(cmd::CLEAR) {
139        ParseResult::Matched(SubCmd::Clear)
140    } else if is(cmd::REPORT) {
141        ParseResult::Matched(SubCmd::Report {
142            content: rest.to_vec(),
143        })
144    } else if is(cmd::REPORTCTL) {
145        if rest.is_empty() {
146            crate::usage!("reportctl <new|sync|push|pull|set-url> [date|message|url]");
147            return ParseResult::Handled;
148        }
149        ParseResult::Matched(SubCmd::Reportctl {
150            action: rest[0].clone(),
151            arg: rest.get(1).cloned(),
152        })
153    } else if is(cmd::CHECK) {
154        ParseResult::Matched(SubCmd::Check {
155            line_count: rest.first().cloned(),
156        })
157    } else if is(cmd::SEARCH) {
158        if rest.len() < 2 {
159            crate::usage!("search <line_count|all> <target> [-f|-fuzzy]");
160            return ParseResult::Handled;
161        }
162        ParseResult::Matched(SubCmd::Search {
163            line_count: rest[0].clone(),
164            target: rest[1].clone(),
165            fuzzy: rest.get(2).cloned(),
166        })
167    } else if is(cmd::TODO) {
168        ParseResult::Matched(SubCmd::Todo {
169            content: rest.to_vec(),
170        })
171    } else if is(cmd::CHAT) {
172        ParseResult::Matched(SubCmd::Chat {
173            content: rest.to_vec(),
174        })
175    } else if is(cmd::CONCAT) {
176        if rest.is_empty() {
177            crate::usage!("concat <script_name> [\"<script_content>\"]");
178            return ParseResult::Handled;
179        }
180        ParseResult::Matched(SubCmd::Concat {
181            name: rest[0].clone(),
182            content: if rest.len() > 1 {
183                rest[1..].to_vec()
184            } else {
185                vec![]
186            },
187        })
188    } else if is(cmd::TIME) {
189        if rest.len() < 2 {
190            crate::usage!("time countdown <duration>");
191            return ParseResult::Handled;
192        }
193        ParseResult::Matched(SubCmd::Time {
194            function: rest[0].clone(),
195            arg: rest[1].clone(),
196        })
197    } else if is(cmd::VERSION) {
198        ParseResult::Matched(SubCmd::Version)
199    } else if is(cmd::HELP) {
200        ParseResult::Matched(SubCmd::Help)
201    } else if is(cmd::COMPLETION) {
202        ParseResult::Matched(SubCmd::Completion {
203            shell: rest.first().cloned(),
204        })
205    } else if is(cmd::VOICE) {
206        ParseResult::Matched(SubCmd::Voice {
207            action: rest.first().cloned().unwrap_or_default(),
208            copy: rest.contains(&"-c".to_string()),
209            model: rest
210                .iter()
211                .position(|a| a == "-m" || a == "--model")
212                .and_then(|i| rest.get(i + 1).cloned()),
213        })
214    } else {
215        ParseResult::NotFound
216    }
217}