use anyhow::{Context, Result};
use clap::Parser;
use std::path::PathBuf;
use termimad::MadSkin;
mod languages;
mod parser;
use parser::CodeStructure;
#[derive(Parser, Debug)]
#[command(name = "peeker")]
#[command(about = "Peek into source code structure using tree-sitter")]
#[command(version)]
struct Args {
file: PathBuf,
#[arg(long)]
exports_only: bool,
#[arg(long, short, default_value = "pretty")]
format: OutputFormat,
}
#[derive(Debug, Clone, Copy, Default, clap::ValueEnum)]
enum OutputFormat {
#[default]
Pretty,
Json,
Markdown,
}
fn main() -> Result<()> {
let args = Args::parse();
let source = std::fs::read_to_string(&args.file)
.with_context(|| format!("Failed to read file: {}", args.file.display()))?;
let extension = args.file.extension().and_then(|e| e.to_str()).unwrap_or("");
let structure = parser::parse_file(&source, extension)?;
let filtered = if args.exports_only {
structure.exports_only()
} else {
structure
};
match args.format {
OutputFormat::Pretty => print_pretty(&filtered, &args.file),
OutputFormat::Json => print_json(&filtered)?,
OutputFormat::Markdown => print_markdown(&filtered, &args.file),
}
Ok(())
}
fn print_pretty(structure: &CodeStructure, file: &PathBuf) {
let skin = MadSkin::default();
println!();
skin.print_text(&format!("# {}\n", file.display()));
if !structure.imports.is_empty() {
skin.print_text("## Imports\n");
for import in &structure.imports {
println!(" {} (line {})", import.name, import.line);
}
println!();
}
if !structure.structs.is_empty() {
skin.print_text("## Structs/Classes\n");
for s in &structure.structs {
let visibility = if s.is_public { "pub " } else { "" };
println!(
" {}{} (lines {}-{})",
visibility, s.name, s.start_line, s.end_line
);
for field in &s.fields {
let field_vis = if field.is_public { "pub " } else { "" };
println!(" {}{}: {}", field_vis, field.name, field.type_name);
}
}
println!();
}
if !structure.functions.is_empty() {
skin.print_text("## Functions\n");
for f in &structure.functions {
let visibility = if f.is_public { "pub " } else { "" };
let async_kw = if f.is_async { "async " } else { "" };
println!(
" {}{}fn {}({}) -> {} (lines {}-{})",
visibility,
async_kw,
f.name,
f.params.join(", "),
f.return_type.as_deref().unwrap_or("()"),
f.start_line,
f.end_line
);
}
println!();
}
if !structure.traits.is_empty() {
skin.print_text("## Traits/Interfaces\n");
for t in &structure.traits {
let visibility = if t.is_public { "pub " } else { "" };
println!(
" {}trait {} (lines {}-{})",
visibility, t.name, t.start_line, t.end_line
);
}
println!();
}
if !structure.enums.is_empty() {
skin.print_text("## Enums\n");
for e in &structure.enums {
let visibility = if e.is_public { "pub " } else { "" };
println!(
" {}enum {} (lines {}-{})",
visibility, e.name, e.start_line, e.end_line
);
for variant in &e.variants {
println!(" {}", variant);
}
}
println!();
}
}
fn print_json(structure: &CodeStructure) -> Result<()> {
let json = serde_json::to_string_pretty(structure)?;
println!("{}", json);
Ok(())
}
fn print_markdown(structure: &CodeStructure, file: &PathBuf) {
println!("# {}\n", file.display());
if !structure.imports.is_empty() {
println!("## Imports\n");
for import in &structure.imports {
println!("- `{}` (line {})", import.name, import.line);
}
println!();
}
if !structure.structs.is_empty() {
println!("## Structs/Classes\n");
for s in &structure.structs {
let visibility = if s.is_public { "pub " } else { "" };
println!(
"### {}`{}`\n",
if s.is_public { "🔓 " } else { "🔒 " },
format!("{}{}", visibility, s.name)
);
println!("Lines {}-{}\n", s.start_line, s.end_line);
if !s.fields.is_empty() {
println!("| Field | Type | Visibility |");
println!("|-------|------|------------|");
for field in &s.fields {
let vis = if field.is_public { "pub" } else { "private" };
println!("| `{}` | `{}` | {} |", field.name, field.type_name, vis);
}
println!();
}
}
}
if !structure.functions.is_empty() {
println!("## Functions\n");
for f in &structure.functions {
let visibility = if f.is_public { "pub " } else { "" };
let async_kw = if f.is_async { "async " } else { "" };
println!(
"### {}`{}{}fn {}`\n",
if f.is_public { "🔓 " } else { "🔒 " },
visibility,
async_kw,
f.name
);
println!("Lines {}-{}\n", f.start_line, f.end_line);
println!("```");
println!(
"fn {}({}) -> {}",
f.name,
f.params.join(", "),
f.return_type.as_deref().unwrap_or("()")
);
println!("```\n");
}
}
if !structure.traits.is_empty() {
println!("## Traits/Interfaces\n");
for t in &structure.traits {
let visibility = if t.is_public { "pub " } else { "" };
println!(
"- `{}trait {}` (lines {}-{})",
visibility, t.name, t.start_line, t.end_line
);
}
println!();
}
if !structure.enums.is_empty() {
println!("## Enums\n");
for e in &structure.enums {
let visibility = if e.is_public { "pub " } else { "" };
println!(
"### {}`{}enum {}`\n",
if e.is_public { "🔓 " } else { "🔒 " },
visibility,
e.name
);
println!("Lines {}-{}\n", e.start_line, e.end_line);
println!("Variants: {}\n", e.variants.join(", "));
}
}
}