Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,17 @@ Options: `--verbose-nodes`, `--no-node-type`, `--no-export`, `-o <FILE>`

Execute a query against source code and output JSON.

**Usage variants:**
```
exec <QUERY> <SOURCE> # two positional files
exec -q <TEXT> <SOURCE> # inline query + source file
exec -q <TEXT> -s <TEXT> -l <LANG> # all inline
```

```sh
cargo run -p plotnik-cli -- exec query.ptk app.ts
cargo run -p plotnik-cli -- exec -q '(identifier) @id' app.ts # -q shifts positional to source
cargo run -p plotnik-cli -- exec -q '(identifier) @id' -s 'let x' -l javascript
cargo run -p plotnik-cli -- exec -q 'Q = (identifier) @id' app.ts
cargo run -p plotnik-cli -- exec -q 'Q = (identifier) @id' -s 'let x' -l javascript
```

Options: `--compact`, `--verbose-nodes`, `--check`, `--entry <NAME>`
Expand All @@ -238,9 +245,16 @@ Options: `--compact`, `--verbose-nodes`, `--check`, `--entry <NAME>`

Trace query execution for debugging.

**Usage variants:**
```
trace <QUERY> <SOURCE> # two positional files
trace -q <TEXT> <SOURCE> # inline query + source file
trace -q <TEXT> -s <TEXT> -l <LANG> # all inline
```

```sh
cargo run -p plotnik-cli -- trace query.ptk app.ts
cargo run -p plotnik-cli -- trace -q '(identifier) @id' app.ts # -q shifts positional to source
cargo run -p plotnik-cli -- trace -q 'Q = (identifier) @id' app.ts
cargo run -p plotnik-cli -- trace query.ptk app.ts --no-result -vv
```

Expand Down
89 changes: 61 additions & 28 deletions crates/plotnik-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ pub struct Cli {
#[derive(Subcommand)]
pub enum Command {
/// Explore a source file's tree-sitter AST
#[command(after_help = r#"EXAMPLES:
plotnik tree app.ts
plotnik tree app.ts --raw
plotnik tree -s 'let x = 1' -l javascript"#)]
#[command(
override_usage = "\
plotnik tree <SOURCE>
plotnik tree -s <TEXT> -l <LANG>",
after_help = r#"EXAMPLES:
plotnik tree app.ts # source file
plotnik tree app.ts --raw # include anonymous nodes
plotnik tree -s 'let x = 1' -l js # inline source"#
)]
Tree {
/// Source file to parse (use "-" for stdin)
#[arg(value_name = "SOURCE")]
Expand All @@ -58,11 +63,17 @@ pub enum Command {
},

/// Validate a query
#[command(after_help = r#"EXAMPLES:
plotnik check query.ptk
plotnik check query.ptk -l typescript
plotnik check queries.ts/
plotnik check -q '(identifier) @id' -l javascript"#)]
#[command(
override_usage = "\
plotnik check <QUERY>
plotnik check <QUERY> -l <LANG>
plotnik check -q <TEXT> [-l <LANG>]",
after_help = r#"EXAMPLES:
plotnik check query.ptk # validate syntax only
plotnik check query.ptk -l ts # also check against grammar
plotnik check queries.ts/ # workspace directory
plotnik check -q 'Q = ...' -l js # inline query"#
)]
Check {
/// Query file or workspace directory
#[arg(value_name = "QUERY")]
Expand All @@ -85,10 +96,16 @@ pub enum Command {
},

/// Show compiled bytecode
#[command(after_help = r#"EXAMPLES:
plotnik dump query.ptk
plotnik dump query.ptk -l typescript
plotnik dump -q '(identifier) @id'"#)]
#[command(
override_usage = "\
plotnik dump <QUERY>
plotnik dump <QUERY> -l <LANG>
plotnik dump -q <TEXT> [-l <LANG>]",
after_help = r#"EXAMPLES:
plotnik dump query.ptk # unlinked bytecode
plotnik dump query.ptk -l ts # linked (resolved node types)
plotnik dump -q 'Q = ...' # inline query"#
)]
Dump {
/// Query file or workspace directory
#[arg(value_name = "QUERY")]
Expand All @@ -107,13 +124,17 @@ pub enum Command {
},

/// Generate type definitions from a query
#[command(after_help = r#"EXAMPLES:
plotnik infer query.ptk -l javascript
plotnik infer queries.ts/ -o types.d.ts
plotnik infer -q '(function_declaration) @fn' -l typescript
plotnik infer query.ptk -l js --verbose-nodes

NOTE: Use --verbose-nodes to match `exec --verbose-nodes` output shape."#)]
#[command(
override_usage = "\
plotnik infer <QUERY> -l <LANG>
plotnik infer -q <TEXT> -l <LANG>",
after_help = r#"EXAMPLES:
plotnik infer query.ptk -l js # from file
plotnik infer -q 'Q = ...' -l ts # inline query
plotnik infer query.ptk -l js -o types.d.ts # write to file

NOTE: Use --verbose-nodes to match `exec --verbose-nodes` output shape."#
)]
Infer {
/// Query file or workspace directory
#[arg(value_name = "QUERY")]
Expand All @@ -135,10 +156,16 @@ NOTE: Use --verbose-nodes to match `exec --verbose-nodes` output shape."#)]
},

/// Execute a query against source code and output JSON
#[command(after_help = r#"EXAMPLES:
plotnik exec query.ptk app.js
plotnik exec -q '(identifier) @id' -s 'let x = 1' -l javascript
plotnik exec query.ptk app.ts --compact"#)]
#[command(
override_usage = "\
plotnik exec <QUERY> <SOURCE>
plotnik exec -q <TEXT> <SOURCE>
plotnik exec -q <TEXT> -s <TEXT> -l <LANG>",
after_help = r#"EXAMPLES:
plotnik exec query.ptk app.js # two positional files
plotnik exec -q 'Q = ...' app.js # inline query + source file
plotnik exec -q 'Q = ...' -s 'let x' -l js # all inline"#
)]
Exec {
/// Query file or workspace directory
#[arg(value_name = "QUERY")]
Expand Down Expand Up @@ -168,10 +195,16 @@ NOTE: Use --verbose-nodes to match `exec --verbose-nodes` output shape."#)]
},

/// Trace query execution for debugging
#[command(after_help = r#"EXAMPLES:
plotnik trace -q '(identifier) @id' -s 'let x = 1' -l javascript
plotnik trace query.ptk app.ts
plotnik trace query.ptk app.ts --no-result"#)]
#[command(
override_usage = "\
plotnik trace <QUERY> <SOURCE>
plotnik trace -q <TEXT> <SOURCE>
plotnik trace -q <TEXT> -s <TEXT> -l <LANG>",
after_help = r#"EXAMPLES:
plotnik trace query.ptk app.js # two positional files
plotnik trace -q 'Q = ...' app.js # inline query + source file
plotnik trace -q 'Q = ...' -s 'let x' -l js # all inline"#
)]
Trace {
/// Query file or workspace directory
#[arg(value_name = "QUERY")]
Expand Down
30 changes: 30 additions & 0 deletions crates/plotnik-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod cli;
mod commands;

use std::path::PathBuf;

use cli::{Cli, Command};
use commands::check::CheckArgs;
use commands::dump::DumpArgs;
Expand All @@ -9,6 +11,20 @@ use commands::infer::InferArgs;
use commands::trace::TraceArgs;
use commands::tree::TreeArgs;

/// When -q is used with a single positional arg, shift it from query to source.
/// This enables: `plotnik exec -q 'query' source.js`
fn shift_positional_to_source(
has_query_text: bool,
query_path: Option<PathBuf>,
source_path: Option<PathBuf>,
) -> (Option<PathBuf>, Option<PathBuf>) {
if has_query_text && query_path.is_some() && source_path.is_none() {
(None, query_path)
} else {
(query_path, source_path)
}
}

fn main() {
let cli = <Cli as clap::Parser>::parse();

Expand Down Expand Up @@ -85,6 +101,13 @@ fn main() {
exec_output,
output,
} => {
// When -q is used with a single positional, shift it to source
let (query_path, source_path) = shift_positional_to_source(
query_text.is_some(),
query_path,
source_path,
);

// Pretty by default when stdout is a TTY, unless --compact is passed
let pretty =
!exec_output.compact && std::io::IsTerminal::is_terminal(&std::io::stdout());
Expand All @@ -111,6 +134,13 @@ fn main() {
fuel,
output,
} => {
// When -q is used with a single positional, shift it to source
let (query_path, source_path) = shift_positional_to_source(
query_text.is_some(),
query_path,
source_path,
);

use plotnik_lib::engine::Verbosity;

let verbosity = match verbose {
Expand Down
106 changes: 79 additions & 27 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,61 +152,113 @@ Supported languages (107):
Execute a query against source code and output JSON matches.

```sh
# Basic execution
plotnik exec -q 'Q = (identifier) @id' -s app.js
# Two positional arguments: QUERY SOURCE
plotnik exec query.ptk app.js

# Pretty-print JSON
plotnik exec -q 'Q = (identifier) @id' -s app.js --pretty
# Inline query + positional source (most common)
plotnik exec -q 'Q = (identifier) @id' app.js

# All inline (requires -l)
plotnik exec -q 'Q = (identifier) @id' -s 'let x = 1' -l javascript

# Include source positions in output
plotnik exec -q 'Q = (function_declaration) @fn' -s app.ts -l typescript --verbose-nodes
plotnik exec -q 'Q = (identifier) @id' app.ts --verbose-nodes

# Start from a specific definition
plotnik exec --query-file query.ptk -s app.js --entry FunctionDef
plotnik exec query.ptk app.js --entry FunctionDef
```

**Flags:**

| Flag | Purpose |
| ----------------- | ------------------------------------- |
| `-q, --query` | Inline query text |
| `-s, --source` | Inline source text |
| `-l, --lang` | Language (inferred from file ext) |
| `--compact` | Output compact JSON |
| `--verbose-nodes` | Include line/column in nodes |
| `--check` | Validate output against inferred types|
| `--entry NAME` | Start from specific definition |

---

## Input Sources
### trace

Trace query execution for debugging.

### Query Input
```sh
# Inline query + positional source
plotnik trace -q 'Q = (identifier) @id' app.js

Mutually exclusive—pick one:
# Two positional arguments
plotnik trace query.ptk app.js

| Option | Usage |
| ------------------- | ---------------------------------- |
| `-q, --query TEXT` | Inline query text |
| `--query-file FILE` | Read from file (use `-` for stdin) |
# All inline
plotnik trace -q 'Q = (identifier) @id' -s 'let x = 1' -l js

# Skip result, show only effects
plotnik trace query.ptk app.js --no-result

# Increase verbosity
plotnik trace query.ptk app.js -v # verbose
plotnik trace query.ptk app.js -vv # very verbose
```

**Flags:**

| Flag | Purpose |
| ------------- | ------------------------------ |
| `-v` | Verbose output |
| `-vv` | Very verbose output |
| `--no-result` | Skip materialization |
| `--fuel N` | Execution fuel limit |
| `--entry` | Start from specific definition |

---

## Input Modes

### Query-Only Commands (tree, check, dump, infer)

These commands take a single input. Use either:
- **Positional**: `plotnik tree app.ts` or `plotnik dump query.ptk`
- **Flag**: `plotnik tree -s 'let x' -l js` or `plotnik dump -q 'Q = ...'`

### Query+Source Commands (exec, trace)

### Source Input
These commands take two inputs. Use any combination:

Mutually exclusive—pick one:
| Pattern | Query from | Source from |
| ------------------------------- | ------------- | -------------- |
| `exec QUERY SOURCE` | 1st positional| 2nd positional |
| `exec -q '...' SOURCE` | `-q` flag | positional |
| `exec -s '...' QUERY -l lang` | positional | `-s` flag |
| `exec -q '...' -s '...' -l lang`| `-q` flag | `-s` flag |

| Option | Usage |
| ------------------------ | ---------------------------------- |
| `--source TEXT` | Inline source code |
| `-s, --source-file FILE` | Read from file (use `-` for stdin) |
**Key rule**: When `-q` is provided with one positional, it becomes SOURCE.

**Language detection:**
### Language Detection

- File input: language inferred from extension (`.ts` → typescript)
- Inline text: requires `-l/--lang` flag
| Input type | Language |
| ------------ | -------------------- |
| File | Inferred from `.ext` |
| Inline (`-s`)| Requires `-l` |

---

## Reading from Stdin

Use `-` as the filename:
Use `-` as the file argument:

```sh
# Query from stdin
echo 'Q = (identifier) @id' | plotnik debug --query-file -
echo 'Q = (identifier) @id' | plotnik dump -

# Source from stdin
cat app.ts | plotnik debug --source-file - -l typescript
cat app.ts | plotnik tree -

# Pipe query file, source from argument
plotnik infer --query-file - -l javascript < query.ptk
# Exec: query from stdin, source from file
echo 'Q = (identifier) @id' | plotnik exec - app.js
```

---
Expand Down