tldr_cli/commands/extract.rs
1//! Extract command - Extract complete module info from a file
2//!
3//! Returns functions, classes, imports, and call graph for a single file.
4//! Auto-routes through daemon when available for ~35x speedup.
5
6use std::path::PathBuf;
7
8use anyhow::Result;
9use clap::Args;
10
11use tldr_core::types::ModuleInfo;
12use tldr_core::{extract_file_with_lang, Language};
13
14use crate::commands::daemon_router::{params_with_file_lang, try_daemon_route};
15use crate::output::{format_module_info_text, OutputFormat, OutputWriter};
16
17/// Extract complete module info from a file
18#[derive(Debug, Args)]
19pub struct ExtractArgs {
20 /// File to extract
21 pub file: PathBuf,
22
23 /// Programming language (auto-detected from file extension if not specified)
24 #[arg(long, short = 'l')]
25 pub lang: Option<Language>,
26}
27
28impl ExtractArgs {
29 /// Run the extract command
30 pub fn run(&self, format: OutputFormat, quiet: bool) -> Result<()> {
31 let writer = OutputWriter::new(format, quiet);
32
33 // cross-command-consistency-v3 (P5.BUG-N1): resolve the language hint
34 // BEFORE choosing a route. The user's explicit `--lang` wins over any
35 // detection. When the user did not pass `--lang`, apply the
36 // sibling-aware widening so `.h` files in C++ projects parse as C++
37 // (otherwise the C grammar mis-classifies `class Foo` as a function
38 // with `return_type: "class"` and emits zero classes).
39 let resolved_lang: Option<Language> = match self.lang {
40 Some(l) => Some(l),
41 None => Language::from_path_with_siblings(&self.file),
42 };
43
44 // Try daemon first for cached result (use file's parent as project root)
45 let project = self.file.parent().unwrap_or(&self.file);
46 if let Some(result) = try_daemon_route::<ModuleInfo>(
47 project,
48 "extract",
49 params_with_file_lang(&self.file, resolved_lang.as_ref().map(|l| l.as_str())),
50 ) {
51 if writer.is_text() {
52 writer.write_text(&format_module_info_text(&result))?;
53 } else {
54 writer.write(&result)?;
55 }
56 return Ok(());
57 }
58
59 // Fallback to direct compute
60 writer.progress(&format!(
61 "Extracting module info from {}...",
62 self.file.display()
63 ));
64
65 // Extract module info, propagating the resolved language hint so the
66 // parser pool honors it instead of falling back to extension-based
67 // detection (which breaks `.h` for C++ and any extensionless file
68 // with `--lang`).
69 let result = extract_file_with_lang(&self.file, None, resolved_lang)?;
70
71 // Output based on format
72 if writer.is_text() {
73 writer.write_text(&format_module_info_text(&result))?;
74 } else {
75 writer.write(&result)?;
76 }
77
78 Ok(())
79 }
80}