Skip to content

Commit 8ab8f9b

Browse files
committed
feat: Auto-detect file type in ast command by extension
1 parent 44a531c commit 8ab8f9b

3 files changed

Lines changed: 93 additions & 3 deletions

File tree

‎crates/plotnik-cli/src/cli/commands.rs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,20 @@ pub fn build_cli() -> Command {
6060
///
6161
/// Accepts all runtime flags for unified CLI experience.
6262
/// Shows query AST when query is provided, source AST when source is provided.
63+
/// Single positional file is auto-detected: .ptk → query, otherwise → source.
6364
pub fn ast_command() -> Command {
6465
let cmd = Command::new("ast")
6566
.about("Show AST of query and/or source file")
6667
.override_usage(
6768
"\
68-
plotnik ast <QUERY> [SOURCE]
69+
plotnik ast <FILE> # auto-detect by extension
70+
plotnik ast <QUERY> <SOURCE> # both ASTs
6971
plotnik ast -q <TEXT> [SOURCE]
70-
plotnik ast <SOURCE>
7172
plotnik ast -s <TEXT> -l <LANG>",
7273
)
7374
.after_help(
7475
r#"EXAMPLES:
75-
plotnik ast query.ptk # query AST
76+
plotnik ast query.ptk # query AST (.ptk extension)
7677
plotnik ast app.ts # source AST (tree-sitter)
7778
plotnik ast query.ptk app.ts # both ASTs
7879
plotnik ast query.ptk app.ts --raw # CST / include anonymous nodes

‎crates/plotnik-cli/src/cli/dispatch.rs‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ impl AstParams {
3939
let (query_path, source_path) =
4040
shift_positional_to_source(query_text.is_some(), query_path, source_path);
4141

42+
// Extension-based detection: when a single file is provided without -q,
43+
// .ptk → query, otherwise → source (detect language from extension).
44+
let (query_path, source_path) =
45+
detect_file_type_by_extension(query_path, source_path, query_text.is_some());
46+
4247
Self {
4348
query_path,
4449
query_text,
@@ -352,3 +357,28 @@ fn shift_positional_to_source(
352357
(query_path, source_path)
353358
}
354359
}
360+
361+
/// Detect file type by extension for ast command.
362+
/// When a single file is provided without -q: .ptk → query, otherwise → source.
363+
fn detect_file_type_by_extension(
364+
query_path: Option<PathBuf>,
365+
source_path: Option<PathBuf>,
366+
has_query_text: bool,
367+
) -> (Option<PathBuf>, Option<PathBuf>) {
368+
// Only apply when: single positional file, no -q flag, no explicit source
369+
if has_query_text || source_path.is_some() {
370+
return (query_path, source_path);
371+
}
372+
373+
let Some(path) = query_path else {
374+
return (None, None);
375+
};
376+
377+
// .ptk extension → treat as query
378+
if path.extension().is_some_and(|ext| ext == "ptk") {
379+
return (Some(path), None);
380+
}
381+
382+
// Any other extension → treat as source file
383+
(None, Some(path))
384+
}

‎crates/plotnik-cli/src/cli/dispatch_tests.rs‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,3 +712,62 @@ fn check_accepts_raw_flag() {
712712
result.err()
713713
);
714714
}
715+
716+
// Extension-based detection tests for ast command
717+
718+
#[test]
719+
fn ast_detects_ptk_as_query() {
720+
let cmd = ast_command();
721+
let result = cmd.try_get_matches_from(["ast", "query.ptk"]);
722+
assert!(result.is_ok());
723+
724+
let m = result.unwrap();
725+
let params = AstParams::from_matches(&m);
726+
727+
// .ptk extension → treat as query
728+
assert_eq!(params.query_path, Some(PathBuf::from("query.ptk")));
729+
assert_eq!(params.source_path, None);
730+
}
731+
732+
#[test]
733+
fn ast_detects_non_ptk_as_source() {
734+
let cmd = ast_command();
735+
let result = cmd.try_get_matches_from(["ast", "app.js"]);
736+
assert!(result.is_ok());
737+
738+
let m = result.unwrap();
739+
let params = AstParams::from_matches(&m);
740+
741+
// Non-.ptk extension → treat as source
742+
assert_eq!(params.query_path, None);
743+
assert_eq!(params.source_path, Some(PathBuf::from("app.js")));
744+
}
745+
746+
#[test]
747+
fn ast_no_extension_detection_with_two_positionals() {
748+
let cmd = ast_command();
749+
let result = cmd.try_get_matches_from(["ast", "query.ptk", "app.js"]);
750+
assert!(result.is_ok());
751+
752+
let m = result.unwrap();
753+
let params = AstParams::from_matches(&m);
754+
755+
// Two positionals → first is query, second is source (no detection)
756+
assert_eq!(params.query_path, Some(PathBuf::from("query.ptk")));
757+
assert_eq!(params.source_path, Some(PathBuf::from("app.js")));
758+
}
759+
760+
#[test]
761+
fn ast_no_extension_detection_with_inline_query() {
762+
let cmd = ast_command();
763+
let result = cmd.try_get_matches_from(["ast", "-q", "(id) @x", "app.js"]);
764+
assert!(result.is_ok());
765+
766+
let m = result.unwrap();
767+
let params = AstParams::from_matches(&m);
768+
769+
// -q provided → positional shift takes precedence, no extension detection
770+
assert_eq!(params.query_path, None);
771+
assert_eq!(params.query_text, Some("(id) @x".to_string()));
772+
assert_eq!(params.source_path, Some(PathBuf::from("app.js")));
773+
}

0 commit comments

Comments
 (0)