use std::collections::HashSet;
use std::error::Error;
use std::io;
use ignore::WalkBuilder;
use llmcc_core::*;
pub struct LlmccOptions {
pub files: Vec<String>,
pub dirs: Vec<String>,
pub print_ir: bool,
pub print_block: bool,
pub design_graph: bool,
pub pagerank: bool,
pub top_k: Option<usize>,
pub query: Option<String>,
pub depends: bool,
pub dependents: bool,
pub recursive: bool,
pub summary: bool,
}
pub fn run_main<L: LanguageTrait>(opts: &LlmccOptions) -> Result<Option<String>, Box<dyn Error>> {
if !opts.files.is_empty() && !opts.dirs.is_empty() {
return Err("Specify either --file or --dir, not both".into());
}
if opts.pagerank && !opts.design_graph {
return Err("--pagerank requires --design-graph".into());
}
if opts.depends && opts.dependents {
return Err("--depends and --dependents are mutually exclusive".into());
}
let mut seen = HashSet::new();
let mut requested_files = Vec::new();
let mut add_path = |path: String| {
if seen.insert(path.clone()) {
requested_files.push(path);
}
};
for file in &opts.files {
add_path(file.clone());
}
if !opts.dirs.is_empty() {
let supported_exts = L::supported_extensions();
for dir in &opts.dirs {
let walker = WalkBuilder::new(dir).standard_filters(true).build();
for entry in walker {
let entry = entry.map_err(|e| {
io::Error::other(format!("Failed to walk directory {dir}: {e}"))
})?;
if !entry
.file_type()
.map(|file_type| file_type.is_file())
.unwrap_or(false)
{
continue;
}
let path = entry.path();
let Some(ext) = path.extension().and_then(|ext| ext.to_str()) else {
continue;
};
if supported_exts.contains(&ext) {
add_path(path.to_string_lossy().into_owned());
}
}
}
}
if requested_files.is_empty() {
return Err("No input files provided. --lang not set correct maybe".into());
}
let cc = CompileCtxt::from_files::<L>(&requested_files)?;
let files = cc.get_files();
let use_compact_builder = opts.design_graph && opts.query.is_none();
build_llmcc_ir::<L>(&cc)?;
let globals = cc.create_globals();
if opts.print_ir {
for (index, _) in files.iter().enumerate() {
let unit = cc.compile_unit(index);
print_llmcc_ir(unit);
}
}
for (index, _) in files.iter().enumerate() {
let unit = cc.compile_unit(index);
L::collect_symbols(unit, globals);
}
let mut pg = ProjectGraph::new(&cc);
let graph_config = if use_compact_builder {
GraphBuildConfig::compact()
} else {
GraphBuildConfig::default()
};
for (index, _) in files.iter().enumerate() {
let unit = cc.compile_unit(index);
L::bind_symbols(unit, globals);
let unit_graph = build_llmcc_graph_with_config::<L>(unit, index, graph_config)?;
if opts.print_block {
print_llmcc_graph(unit_graph.root(), unit);
}
pg.add_child(unit_graph);
}
pg.link_units();
let mut outputs = Vec::new();
if opts.design_graph {
if opts.pagerank {
let limit = Some(opts.top_k.unwrap_or(80));
pg.set_compact_rank_limit(limit);
}
outputs.push(pg.render_compact_graph());
} else if let Some(name) = opts.query.as_ref() {
let query = ProjectQuery::new(&pg);
let query_result = if opts.dependents {
if opts.recursive {
query.find_depended_recursive(name)
} else {
query.find_depended(name)
}
} else if opts.recursive {
query.find_depends_recursive(name)
} else {
query.find_depends(name)
};
let formatted = if opts.summary {
query_result.format_summary()
} else {
query_result.format_for_llm()
};
outputs.push(formatted);
}
if outputs.is_empty() {
Ok(None)
} else {
Ok(Some(outputs.join("\n")))
}
}