use std::process::ExitCode;
use anyhow::Result;
use clap::{Parser, Subcommand};
use tldr_core::Language;
use tldr_cli::commands::remaining::{ApiCheckArgs, VulnArgs};
use tldr_cli::commands::{
ApiSurfaceArgs, AvailableArgs, BugbotCheckArgs, CacheClearArgs, CacheStatsArgs, CallsArgs,
ChangeImpactArgs, ChopArgs, ChurnArgs, ClonesArgs, CognitiveArgs, ComplexityArgs, ContextArgs,
ContractsArgs, CoverageArgs, DaemonListArgs, DaemonNotifyArgs, DaemonQueryArgs,
DaemonStartArgs, DaemonStatusArgs, DaemonStopArgs, DeadArgs, DeadStoresArgs, DebtArgs,
DefinitionArgs, DepsArgs,
DiagnosticsArgs, DiceArgs, DiffArgs, DoctorArgs, ExplainArgs, ExtractArgs, FixArgs,
HalsteadArgs, HealthArgs, HotspotsArgs, HubsArgs, ImpactArgs, ImportersArgs, ImportsArgs,
InheritanceArgs, InvariantsArgs, LocArgs, PatternsArgs, ReachingDefsArgs, ReferencesArgs,
SecureArgs, SliceArgs, SmartSearchArgs, SmellsArgs, SpecsArgs, StatsArgs, StructureArgs,
TaintArgs, TodoArgs, TreeArgs, VerifyArgs, WarmArgs, WhatbreaksArgs,
};
use tldr_cli::commands::patterns::{
CohesionArgs, CouplingArgs, InterfaceArgs, ResourcesArgs, TemporalArgs,
};
#[cfg(feature = "semantic")]
use tldr_cli::commands::{EmbedArgs, SemanticArgs, SimilarArgs};
use tldr_cli::output::{validate_format_for_command, OutputFormat};
#[derive(Debug, Parser)]
#[command(
name = "tldr",
version,
about = "Token-efficient code analysis tool",
long_about = "TLDR provides code analysis commands optimized for LLM consumption.\n\n\
Commands are organized by analysis layer:\n\
- L1 (AST): tree, structure\n\
- L2 (Call Graph): calls, impact, dead\n\
- L3 (CFG): reaching-defs, available\n\
- L4 (DFG): dead-stores\n\
- L5 (PDG): slice\n\
- Search: search\n\
- Context: context\n\
- Quality: smells\n\
- Security: taint, vuln, secure"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
#[arg(
long,
short = 'f',
global = true,
default_value = "json",
hide_possible_values = true,
value_name = "FORMAT"
)]
pub format: OutputFormat,
#[arg(long, short = 'l', global = true)]
pub lang: Option<Language>,
#[arg(long, short = 'q', global = true)]
pub quiet: bool,
#[arg(long, short = 'v', global = true)]
pub verbose: bool,
}
#[derive(Debug, Subcommand)]
pub enum Command {
#[command(visible_alias = "t")]
Tree(TreeArgs),
#[command(visible_alias = "s")]
Structure(StructureArgs),
#[command(visible_alias = "c")]
Calls(CallsArgs),
#[command(visible_alias = "i")]
Impact(ImpactArgs),
#[command(visible_alias = "d")]
Dead(DeadArgs),
#[command(name = "reaching-defs", visible_alias = "rd")]
ReachingDefs(ReachingDefsArgs),
#[command(visible_alias = "ta")]
Taint(TaintArgs),
#[command(visible_alias = "av")]
Available(AvailableArgs),
Slice(SliceArgs),
#[command(name = "search")]
SmartSearch(SmartSearchArgs),
Context(ContextArgs),
Smells(SmellsArgs),
#[command(visible_alias = "e")]
Extract(ExtractArgs),
Imports(ImportsArgs),
Importers(ImportersArgs),
Complexity(ComplexityArgs),
Churn(ChurnArgs),
Debt(DebtArgs),
#[command(visible_alias = "h")]
Health(HealthArgs),
Hubs(HubsArgs),
#[command(visible_alias = "wb")]
Whatbreaks(WhatbreaksArgs),
#[command(visible_alias = "p")]
Patterns(PatternsArgs),
#[command(visible_alias = "inh")]
Inheritance(InheritanceArgs),
#[command(visible_alias = "ci", name = "change-impact")]
ChangeImpact(ChangeImpactArgs),
#[command(visible_alias = "dep")]
Deps(DepsArgs),
#[command(visible_alias = "diag")]
Diagnostics(DiagnosticsArgs),
#[command(visible_alias = "doc")]
Doctor(DoctorArgs),
#[command(visible_alias = "refs")]
References(ReferencesArgs),
#[command(visible_alias = "cl")]
Clones(ClonesArgs),
Dice(DiceArgs),
Loc(LocArgs),
#[command(visible_alias = "cog")]
Cognitive(CognitiveArgs),
#[command(visible_alias = "hal")]
Halstead(HalsteadArgs),
#[command(visible_alias = "cov")]
Coverage(CoverageArgs),
#[command(visible_alias = "hot")]
Hotspots(HotspotsArgs),
#[cfg(feature = "semantic")]
#[command(visible_alias = "emb")]
Embed(EmbedArgs),
#[cfg(feature = "semantic")]
#[command(visible_alias = "sem")]
Semantic(SemanticArgs),
#[cfg(feature = "semantic")]
#[command(visible_alias = "sim")]
Similar(SimilarArgs),
#[command(subcommand)]
Daemon(DaemonCommand),
#[command(subcommand)]
Cache(CacheCommand),
#[command(visible_alias = "w")]
Warm(WarmArgs),
Stats(StatsArgs),
#[command(visible_alias = "surf")]
Surface(ApiSurfaceArgs),
#[command(visible_alias = "con")]
Contracts(ContractsArgs),
#[command(visible_alias = "ds")]
DeadStores(DeadStoresArgs),
#[command(visible_alias = "chp")]
Chop(ChopArgs),
#[command(visible_alias = "sp")]
Specs(SpecsArgs),
#[command(visible_alias = "inv")]
Invariants(InvariantsArgs),
#[command(visible_alias = "ver")]
Verify(VerifyArgs),
#[command(visible_alias = "coh")]
Cohesion(CohesionArgs),
#[command(visible_alias = "tem")]
Temporal(TemporalArgs),
#[command(visible_alias = "res")]
Resources(ResourcesArgs),
#[command(visible_alias = "coup")]
Coupling(CouplingArgs),
#[command(visible_alias = "iface")]
Interface(InterfaceArgs),
#[command(visible_alias = "exp")]
Explain(ExplainArgs),
Todo(TodoArgs),
#[command(visible_alias = "sec")]
Secure(SecureArgs),
#[command(visible_alias = "def")]
Definition(DefinitionArgs),
#[command(visible_alias = "df")]
Diff(DiffArgs),
#[command(name = "api-check", visible_alias = "ac")]
ApiCheck(ApiCheckArgs),
Vuln(VulnArgs),
#[command(visible_alias = "fx")]
Fix(FixArgs),
#[command(subcommand)]
Bugbot(BugbotCommand),
}
#[derive(Debug, Subcommand)]
pub enum DaemonCommand {
Start(DaemonStartArgs),
Stop(DaemonStopArgs),
Status(DaemonStatusArgs),
Query(DaemonQueryArgs),
Notify(DaemonNotifyArgs),
List(DaemonListArgs),
}
#[derive(Debug, Subcommand)]
pub enum CacheCommand {
Stats(CacheStatsArgs),
Clear(CacheClearArgs),
}
#[derive(Debug, Subcommand)]
pub enum BugbotCommand {
Check(BugbotCheckArgs),
}
fn main() -> ExitCode {
let cli = Cli::parse();
if cli.verbose {
std::env::set_var("TLDR_LOG", "debug");
}
let result = run_command(&cli);
match result {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("Error: {}", e);
if cli.verbose {
let mut source = e.source();
while let Some(err) = source {
eprintln!(" Caused by: {}", err);
source = err.source();
}
}
if let Some(bugbot_err) =
e.downcast_ref::<tldr_cli::commands::bugbot::BugbotExitError>()
{
ExitCode::from(bugbot_err.exit_code())
} else if let Some(tldr_err) = e.downcast_ref::<tldr_core::TldrError>() {
ExitCode::from(tldr_err.exit_code() as u8)
} else if let Some(remaining_err) =
e.downcast_ref::<tldr_cli::commands::remaining::RemainingError>()
{
ExitCode::from(remaining_err.exit_code() as u8)
} else {
ExitCode::FAILURE
}
}
}
}
fn command_name(cmd: &Command) -> &'static str {
match cmd {
Command::Tree(_) => "tree",
Command::Structure(_) => "structure",
Command::Calls(_) => "calls",
Command::Impact(_) => "impact",
Command::Dead(_) => "dead",
Command::ReachingDefs(_) => "reaching-defs",
Command::Taint(_) => "taint",
Command::Available(_) => "available",
Command::Slice(_) => "slice",
Command::SmartSearch(_) => "search",
Command::Context(_) => "context",
Command::Smells(_) => "smells",
Command::Extract(_) => "extract",
Command::Imports(_) => "imports",
Command::Importers(_) => "importers",
Command::Complexity(_) => "complexity",
Command::Churn(_) => "churn",
Command::Debt(_) => "debt",
Command::Health(_) => "health",
Command::Hubs(_) => "hubs",
Command::Whatbreaks(_) => "whatbreaks",
Command::Patterns(_) => "patterns",
Command::Inheritance(_) => "inheritance",
Command::ChangeImpact(_) => "change-impact",
Command::Deps(_) => "deps",
Command::Diagnostics(_) => "diagnostics",
Command::Doctor(_) => "doctor",
Command::References(_) => "references",
Command::Clones(_) => "clones",
Command::Dice(_) => "dice",
Command::Loc(_) => "loc",
Command::Cognitive(_) => "cognitive",
Command::Halstead(_) => "halstead",
Command::Coverage(_) => "coverage",
Command::Hotspots(_) => "hotspots",
#[cfg(feature = "semantic")]
Command::Embed(_) => "embed",
#[cfg(feature = "semantic")]
Command::Semantic(_) => "semantic",
#[cfg(feature = "semantic")]
Command::Similar(_) => "similar",
Command::Daemon(sub) => match sub {
DaemonCommand::Start(_) => "daemon start",
DaemonCommand::Stop(_) => "daemon stop",
DaemonCommand::Status(_) => "daemon status",
DaemonCommand::Query(_) => "daemon query",
DaemonCommand::Notify(_) => "daemon notify",
DaemonCommand::List(_) => "daemon list",
},
Command::Cache(sub) => match sub {
CacheCommand::Stats(_) => "cache stats",
CacheCommand::Clear(_) => "cache clear",
},
Command::Warm(_) => "warm",
Command::Stats(_) => "stats",
Command::Surface(_) => "surface",
Command::Contracts(_) => "contracts",
Command::DeadStores(_) => "dead-stores",
Command::Chop(_) => "chop",
Command::Specs(_) => "specs",
Command::Invariants(_) => "invariants",
Command::Verify(_) => "verify",
Command::Cohesion(_) => "cohesion",
Command::Temporal(_) => "temporal",
Command::Resources(_) => "resources",
Command::Coupling(_) => "coupling",
Command::Interface(_) => "interface",
Command::Explain(_) => "explain",
Command::Todo(_) => "todo",
Command::Secure(_) => "secure",
Command::Definition(_) => "definition",
Command::Diff(_) => "diff",
Command::ApiCheck(_) => "api-check",
Command::Vuln(_) => "vuln",
Command::Fix(_) => "fix",
Command::Bugbot(sub) => match sub {
BugbotCommand::Check(_) => "bugbot check",
},
}
}
fn run_command(cli: &Cli) -> Result<()> {
if let Err(msg) = validate_format_for_command(command_name(&cli.command), cli.format) {
anyhow::bail!(msg);
}
let auto_quiet_env = matches!(
cli.format,
OutputFormat::Json | OutputFormat::Compact | OutputFormat::Sarif
);
if cli.quiet || auto_quiet_env {
std::env::set_var("TLDR_QUIET", "1");
}
let q = cli.quiet;
match &cli.command {
Command::Tree(args) => args.run(cli.format, q),
Command::Structure(args) => args.run(cli.format, q),
Command::Calls(args) => args.run(cli.format, q),
Command::Impact(args) => args.run(cli.format, q),
Command::Dead(args) => args.run(cli.format, q),
Command::ReachingDefs(args) => args.run(cli.format, q),
Command::Taint(args) => args.run(cli.format, q),
Command::Available(args) => args.run(cli.format, q),
Command::Slice(args) => args.run(cli.format, q),
Command::SmartSearch(args) => args.run(cli.format, q),
Command::Context(args) => args.run(cli.format, q),
Command::Smells(args) => args.run(cli.format, q),
Command::Extract(args) => args.run(cli.format, q),
Command::Imports(args) => args.run(cli.format, q),
Command::Importers(args) => args.run(cli.format, q),
Command::Complexity(args) => args.run(cli.format, q),
Command::Churn(args) => args.run(cli.format, q),
Command::Debt(args) => args.run(cli.format, q, cli.lang),
Command::Health(args) => args.run(cli.format, q, cli.lang),
Command::Hubs(args) => args.run(cli.format, q),
Command::Whatbreaks(args) => args.run(cli.format, q),
Command::Patterns(args) => args.run(cli.format, q),
Command::Inheritance(args) => args.run(cli.format, q),
Command::ChangeImpact(args) => args.run(cli.format, q),
Command::Deps(args) => args.run(cli.format, q),
Command::Diagnostics(args) => args.run(cli.format, q),
Command::Doctor(args) => args.run(cli.format, q),
Command::References(args) => args.run(cli.format, q, cli.lang),
Command::Clones(args) => args.run(cli.format, q, cli.lang),
Command::Dice(args) => args.run(cli.format, q),
Command::Loc(args) => args.run(cli.format, q),
Command::Cognitive(args) => args.run(cli.format, q),
Command::Halstead(args) => args.run(cli.format, q),
Command::Coverage(args) => args.run(cli.format, q),
Command::Hotspots(args) => args.run(cli.format, q),
#[cfg(feature = "semantic")]
Command::Embed(args) => args.run(cli.format, q),
#[cfg(feature = "semantic")]
Command::Semantic(args) => args.run(cli.format, q),
#[cfg(feature = "semantic")]
Command::Similar(args) => args.run(cli.format, q),
Command::Daemon(daemon_cmd) => match daemon_cmd {
DaemonCommand::Start(args) => args.run(cli.format, q),
DaemonCommand::Stop(args) => args.run(cli.format, q),
DaemonCommand::Status(args) => args.run(cli.format, q),
DaemonCommand::Query(args) => args.run(cli.format, q),
DaemonCommand::Notify(args) => args.run(cli.format, q),
DaemonCommand::List(args) => args.run(cli.format, q),
},
Command::Cache(cache_cmd) => match cache_cmd {
CacheCommand::Stats(args) => args.run(cli.format, q),
CacheCommand::Clear(args) => args.run(cli.format, q),
},
Command::Warm(args) => args.run(cli.format, q),
Command::Stats(args) => args.run(cli.format, q),
Command::Surface(args) => args.run(cli.format, q, cli.lang),
Command::Contracts(args) => args.run(cli.format, q),
Command::DeadStores(args) => args.run(cli.format, q),
Command::Chop(args) => args.run(cli.format, q),
Command::Specs(args) => args.run(cli.format, q),
Command::Invariants(args) => args.run(cli.format, q),
Command::Verify(args) => args.run(cli.format, q),
Command::Cohesion(args) => args.run(cli.format),
Command::Temporal(args) => args.run(cli.format),
Command::Resources(args) => args.run(cli.format),
Command::Coupling(args) => {
tldr_cli::commands::patterns::coupling::run(args.clone(), cli.format)
}
Command::Interface(args) => {
tldr_cli::commands::patterns::interface::run(args.clone(), cli.format)
}
Command::Explain(args) => args.run(cli.format, q),
Command::Todo(args) => args.run(cli.format, q, cli.lang),
Command::Secure(args) => args.run(cli.format),
Command::Definition(args) => args.run(cli.format, q, cli.lang),
Command::Diff(args) => args.run(cli.format),
Command::ApiCheck(args) => args.run(cli.format, q, cli.lang),
Command::Vuln(args) => args.run(cli.format),
Command::Fix(args) => args.run(cli.format, q, cli.lang),
Command::Bugbot(bugbot_cmd) => match bugbot_cmd {
BugbotCommand::Check(args) => args.run(cli.format, q, cli.lang),
},
}
}