Skip to content

Commit a8aa721

Browse files
committed
Refactor CLI to use subcommand structure with Debug
1 parent 7ed5c86 commit a8aa721

File tree

1 file changed

+66
-54
lines changed

1 file changed

+66
-54
lines changed

crates/plotnik-cli/src/main.rs

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@ use std::path::PathBuf;
77
#[derive(Parser)]
88
#[command(name = "plotnik", bin_name = "plotnik")]
99
#[command(about = "Query language for tree-sitter AST with type inference")]
10-
#[command(after_help = r#"OUTPUT DEPENDENCIES:
10+
struct Cli {
11+
#[command(subcommand)]
12+
command: Command,
13+
}
14+
15+
#[derive(Subcommand)]
16+
enum Command {
17+
/// Debug and inspect queries and source files
18+
#[command(after_help = r#"OUTPUT DEPENDENCIES:
1119
┌─────────────────┬─────────────┬──────────────┐
1220
│ Output │ Needs Query │ Needs Source │
1321
├─────────────────┼─────────────┼──────────────┤
@@ -22,40 +30,32 @@ use std::path::PathBuf;
2230
2331
EXAMPLES:
2432
# Parse and typecheck query only
25-
plotnik --query-text '(identifier) @id' --query-cst --query-types
33+
plotnik debug --query-text '(identifier) @id' --query-cst --query-types
2634
2735
# Dump tree-sitter AST of source file
28-
plotnik --source-file app.ts --source-ast
36+
plotnik debug --source-file app.ts --source-ast
2937
3038
# Full pipeline: match query against source
31-
plotnik --query-file rules.pql --source-file app.ts --result
39+
plotnik debug --query-file rules.pql --source-file app.ts --result
3240
3341
# Debug with trace
34-
plotnik --query-text '(function_declaration) @fn' \
35-
--source-text 'function foo() {}' --lang typescript --trace
42+
plotnik debug --query-text '(function_declaration) @fn' \
43+
--source-text 'function foo() {}' --lang typescript --trace"#)]
44+
Debug {
45+
#[command(flatten)]
46+
query: QueryArgs,
3647

37-
# Show documentation
38-
plotnik docs reference"#)]
39-
struct Cli {
40-
#[command(subcommand)]
41-
command: Option<Command>,
42-
43-
#[command(flatten)]
44-
query: QueryArgs,
48+
#[command(flatten)]
49+
source: SourceArgs,
4550

46-
#[command(flatten)]
47-
source: SourceArgs,
48-
49-
/// Language for source (required for --source-text, inferred from extension for --source-file)
50-
#[arg(long, short = 'l', value_name = "LANG")]
51-
lang: Option<String>,
51+
/// Language for source (required for --source-text, inferred from extension for --source-file)
52+
#[arg(long, short = 'l', value_name = "LANG")]
53+
lang: Option<String>,
5254

53-
#[command(flatten)]
54-
output: OutputArgs,
55-
}
55+
#[command(flatten)]
56+
output: OutputArgs,
57+
},
5658

57-
#[derive(Subcommand)]
58-
enum Command {
5959
/// Print documentation
6060
Docs {
6161
/// Topic to display (e.g., "reference", "examples")
@@ -121,19 +121,32 @@ struct OutputArgs {
121121
fn main() {
122122
let cli = Cli::parse();
123123

124-
if let Some(Command::Docs { topic }) = cli.command {
125-
print_help(topic.as_deref());
126-
return;
124+
match cli.command {
125+
Command::Docs { topic } => {
126+
print_help(topic.as_deref());
127+
}
128+
Command::Debug {
129+
query,
130+
source,
131+
lang,
132+
output,
133+
} => {
134+
run_debug(query, source, lang, output);
135+
}
127136
}
137+
}
128138

129-
let has_query = cli.query.query_text.is_some() || cli.query.query_file.is_some();
130-
let has_source = cli.source.source_text.is_some() || cli.source.source_file.is_some();
139+
fn run_debug(
140+
query_args: QueryArgs,
141+
source_args: SourceArgs,
142+
lang: Option<String>,
143+
output: OutputArgs,
144+
) {
145+
let has_query = query_args.query_text.is_some() || query_args.query_file.is_some();
146+
let has_source = source_args.source_text.is_some() || source_args.source_file.is_some();
131147

132148
// Validate output dependencies
133-
if (cli.output.query_cst
134-
|| cli.output.query_ast
135-
|| cli.output.query_refs
136-
|| cli.output.query_types)
149+
if (output.query_cst || output.query_ast || output.query_refs || output.query_types)
137150
&& !has_query
138151
{
139152
eprintln!(
@@ -142,81 +155,81 @@ fn main() {
142155
std::process::exit(1);
143156
}
144157

145-
if cli.output.source_ast && !has_source {
158+
if output.source_ast && !has_source {
146159
eprintln!("error: --source-ast requires --source-text or --source-file");
147160
std::process::exit(1);
148161
}
149162

150-
if cli.output.trace && !(has_query && has_source) {
163+
if output.trace && !(has_query && has_source) {
151164
eprintln!("error: --trace requires both query and source inputs");
152165
std::process::exit(1);
153166
}
154167

155-
if cli.output.result && !(has_query && has_source) {
168+
if output.result && !(has_query && has_source) {
156169
eprintln!("error: --result requires both query and source inputs");
157170
std::process::exit(1);
158171
}
159172

160173
// If both inputs provided and no outputs selected, default to --result
161-
let show_result = cli.output.result
174+
let show_result = output.result
162175
|| (has_query
163176
&& has_source
164-
&& !cli.output.query_cst
165-
&& !cli.output.query_ast
166-
&& !cli.output.query_refs
167-
&& !cli.output.query_types
168-
&& !cli.output.source_ast
169-
&& !cli.output.trace);
177+
&& !output.query_cst
178+
&& !output.query_ast
179+
&& !output.query_refs
180+
&& !output.query_types
181+
&& !output.source_ast
182+
&& !output.trace);
170183

171184
// Validate --lang requirement
172-
if cli.source.source_text.is_some() && cli.lang.is_none() {
185+
if source_args.source_text.is_some() && lang.is_none() {
173186
eprintln!("error: --lang is required when using --source-text");
174187
std::process::exit(1);
175188
}
176189

177190
// Load query if needed
178191
let query_source = if has_query {
179-
Some(load_query(&cli.query))
192+
Some(load_query(&query_args))
180193
} else {
181194
None
182195
};
183196

184197
let query = query_source.as_ref().map(|src| Query::new(src));
185198

186-
if cli.output.query_cst {
199+
if output.query_cst {
187200
println!("=== QUERY CST ===");
188201
if let Some(ref q) = query {
189202
print!("{}", q.format_cst());
190203
}
191204
}
192205

193-
if cli.output.query_ast {
206+
if output.query_ast {
194207
println!("=== QUERY AST ===");
195208
if let Some(ref q) = query {
196209
print!("{}", q.format_ast());
197210
}
198211
}
199212

200-
if cli.output.query_refs {
213+
if output.query_refs {
201214
println!("=== QUERY REFS ===");
202215
if let Some(ref q) = query {
203216
print!("{}", q.format_refs());
204217
}
205218
}
206219

207-
if cli.output.query_types {
220+
if output.query_types {
208221
println!("=== QUERY TYPES ===");
209222
println!("(not implemented)");
210223
println!();
211224
}
212225

213-
if cli.output.source_ast {
226+
if output.source_ast {
214227
println!("=== SOURCE AST ===");
215228
println!("(not implemented)");
216229
println!();
217230
}
218231

219-
if cli.output.trace {
232+
if output.trace {
220233
println!("=== TRACE ===");
221234
println!("(not implemented)");
222235
println!();
@@ -263,15 +276,14 @@ fn print_help(topic: Option<&str>) {
263276
println!("Usage: plotnik docs <topic>");
264277
}
265278
Some("reference") => {
266-
// TODO: include_str! the actual REFERENCE.md
267279
println!("{}", include_str!("../../../docs/REFERENCE.md"));
268280
}
269281
Some("examples") => {
270282
println!("(examples not yet written)");
271283
}
272284
Some(other) => {
273285
eprintln!("Unknown help topic: {}", other);
274-
eprintln!("Run 'plotnik help' to see available topics");
286+
eprintln!("Run 'plotnik docs' to see available topics");
275287
std::process::exit(1);
276288
}
277289
}

0 commit comments

Comments
 (0)