Skip to main content

impactsense_parser/
extract.rs

1//! Pure extraction from parsed files into [`ProjectIr`] (no Neo4j).
2
3use std::path::Path;
4
5use thiserror::Error;
6
7use crate::compress::CompressError;
8use crate::graph::{build_project_ir, enrich_project_ir_code_bytes, GraphPersistenceOptions};
9use crate::ir::ProjectIr;
10use crate::pipeline::ScanOptions;
11use crate::scanner::{scan_and_parse, FileScanConfig, ScannerError};
12
13pub use crate::graph::ExtractOptions;
14
15impl From<&ScanOptions> for crate::graph::ExtractOptions {
16    fn from(opts: &ScanOptions) -> Self {
17        Self {
18            verbose_imports: opts.graph.verbose_imports,
19            max_parse_warnings_per_file: opts.graph.max_parse_warnings_per_file,
20            compressor: opts.graph.compressor.clone(),
21        }
22    }
23}
24
25impl From<&GraphPersistenceOptions> for crate::graph::ExtractOptions {
26    fn from(opts: &GraphPersistenceOptions) -> Self {
27        Self {
28            verbose_imports: opts.verbose_imports,
29            max_parse_warnings_per_file: opts.max_parse_warnings_per_file,
30            compressor: opts.compressor.clone(),
31        }
32    }
33}
34
35#[derive(Debug, Error)]
36pub enum ExtractError {
37    #[error("scan/parse failed: {0}")]
38    Scanner(#[from] ScannerError),
39    #[error("code compression failed: {0}")]
40    Compress(#[from] CompressError),
41}
42
43/// Build a full project IR from an already-parsed file batch.
44pub fn build_project_ir_from_files(
45    root: &Path,
46    files: &[crate::scanner::ParsedFile],
47    options: &ExtractOptions,
48) -> ProjectIr {
49    build_project_ir(root, files, options)
50}
51
52/// Scan a repository root and build project IR (includes `code_bytes` when compression is enabled).
53pub fn scan_and_build_ir(
54    root: &Path,
55    options: &ExtractOptions,
56    scan: &ScanOptions,
57) -> Result<ProjectIr, ExtractError> {
58    let mut config = FileScanConfig::new(root);
59    config.follow_symlinks = scan.follow_symlinks;
60    config.max_file_size = scan.max_file_size;
61    let files = scan_and_parse(&config)?;
62    let mut ir = build_project_ir_from_files(root, &files, options);
63    run_ir_compression(&mut ir, root, &files, &options.compressor)?;
64    Ok(ir)
65}
66
67fn run_ir_compression(
68    ir: &mut ProjectIr,
69    root: &Path,
70    files: &[crate::scanner::ParsedFile],
71    config: &crate::graph::CompressorConfig,
72) -> Result<(), CompressError> {
73    if !config.enabled {
74        return Ok(());
75    }
76    if let Ok(handle) = tokio::runtime::Handle::try_current() {
77        return handle.block_on(enrich_project_ir_code_bytes(ir, root, files, config));
78    }
79    let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime for compression");
80    rt.block_on(enrich_project_ir_code_bytes(ir, root, files, config))
81}
82
83/// Async variant of [`scan_and_build_ir`] for callers already on a Tokio runtime (e.g. MCP).
84pub async fn scan_and_build_ir_async(
85    root: &Path,
86    options: &ExtractOptions,
87    scan: &ScanOptions,
88) -> Result<ProjectIr, ExtractError> {
89    let mut config = FileScanConfig::new(root);
90    config.follow_symlinks = scan.follow_symlinks;
91    config.max_file_size = scan.max_file_size;
92    let files = scan_and_parse(&config)?;
93    let mut ir = build_project_ir_from_files(root, &files, options);
94    enrich_project_ir_code_bytes(&mut ir, root, &files, &options.compressor).await?;
95    Ok(ir)
96}
97
98/// Parse specific targets and build IR delta (incremental).
99pub fn parse_files_to_ir(
100    root: &Path,
101    parse_targets: &[String],
102    options: &ExtractOptions,
103    scan: &ScanOptions,
104) -> Result<ProjectIr, ExtractError> {
105    let mut config = FileScanConfig::new(root);
106    config.follow_symlinks = scan.follow_symlinks;
107    config.max_file_size = scan.max_file_size;
108    let paths: Vec<_> = parse_targets.iter().map(std::path::PathBuf::from).collect();
109    let files = crate::scanner_incremental::scan_and_parse_incremental_vector(&config, &paths)?;
110    let mut ir = build_project_ir_from_files(root, &files, options);
111    run_ir_compression(&mut ir, root, &files, &options.compressor)?;
112    Ok(ir)
113}