use crate::model::Symbol;
use crate::output::OutputFormat;
use crate::pipeline::GraphAnalysis;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct EmitConfig {
pub output: Option<PathBuf>,
pub format: OutputFormat,
pub html: bool,
pub open_browser: bool,
}
pub fn emit_inspect(symbols: &mut Vec<Symbol>, config: &EmitConfig) -> anyhow::Result<()> {
let content = crate::output::inspect::serialize_inspect(symbols, &config.format)?;
match &config.output {
Some(path) => {
std::fs::write(path, content)?;
}
None => {
println!("{content}");
}
}
Ok(())
}
pub fn emit_graph(analysis: &GraphAnalysis, config: &EmitConfig) -> anyhow::Result<()> {
if config.html {
let html = crate::output::dashboard::to_graph_html(
&analysis.graph,
&analysis.scc,
analysis.snapshot_id.to_raw() as u64,
)?;
let path = config
.output
.clone()
.unwrap_or_else(|| PathBuf::from("project.metast"));
let path_str = path.to_string_lossy().to_string();
std::fs::write(&path, html)?;
if config.open_browser {
let open_res = webbrowser::open(&path_str);
if let Err(e) = open_res {
tracing::warn!(error = %e, "could not open browser");
}
}
} else {
let content = crate::output::graph::serialize_graph(
&analysis.graph,
&analysis.scc,
analysis.snapshot_id.to_raw() as u64,
&config.format,
)?;
match &config.output {
Some(path) => {
std::fs::write(path, content)?;
}
None => {
println!("{content}");
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::{GraphBuilder, SccAnalysis};
use crate::language::LangId;
use crate::model::{LineColumn, SnapshotId, SourceRange, Symbol, SymbolId, SymbolKind};
use crate::output::OutputFormat;
#[test]
fn emit_inspect_writes_to_path() {
let temp_dir = std::env::temp_dir().join("emit_inspect_writes_to_path");
if temp_dir.exists() {
let _ = std::fs::remove_dir_all(&temp_dir);
}
std::fs::create_dir_all(&temp_dir).unwrap();
let file_path = temp_dir.join("output.json");
let mut symbols = vec![Symbol {
id: SymbolId::new(1).unwrap(),
name: "test".into(),
kind: SymbolKind::Function,
language: LangId::Python,
file_path: PathBuf::from("a.py"),
source_range: SourceRange {
byte_start: 0,
byte_end: 10,
start: LineColumn { line: 1, column: 0 },
end: LineColumn {
line: 1,
column: 10,
},
},
visibility: None,
signature: None,
docstring: None,
is_async: false,
}];
let config = EmitConfig {
output: Some(file_path.clone()),
format: OutputFormat::Json,
html: false,
open_browser: false,
};
emit_inspect(&mut symbols, &config).unwrap();
assert!(file_path.exists());
let content = std::fs::read_to_string(file_path).unwrap();
assert!(content.contains("test"));
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn emit_inspect_prints_to_stdout_when_no_path() {
let mut symbols = vec![Symbol {
id: SymbolId::new(1).unwrap(),
name: "test".into(),
kind: SymbolKind::Function,
language: LangId::Python,
file_path: PathBuf::from("a.py"),
source_range: SourceRange {
byte_start: 0,
byte_end: 10,
start: LineColumn { line: 1, column: 0 },
end: LineColumn {
line: 1,
column: 10,
},
},
visibility: None,
signature: None,
docstring: None,
is_async: false,
}];
let config = EmitConfig {
output: None,
format: OutputFormat::Json,
html: false,
open_browser: false,
};
emit_inspect(&mut symbols, &config).unwrap();
}
#[test]
fn emit_graph_html_writes_file() {
let temp_dir = std::env::temp_dir().join("emit_graph_html_writes_file");
if temp_dir.exists() {
let _ = std::fs::remove_dir_all(&temp_dir);
}
std::fs::create_dir_all(&temp_dir).unwrap();
let file_path = temp_dir.join("graph.html");
let builder = GraphBuilder::new(SnapshotId::new(1).unwrap());
let graph = builder.build();
let scc = SccAnalysis::analyze(&graph.graph);
let analysis = GraphAnalysis {
graph,
scc,
snapshot_id: SnapshotId::new(1).unwrap(),
extractions: vec![],
};
let config = EmitConfig {
output: Some(file_path.clone()),
format: OutputFormat::Json,
html: true,
open_browser: false,
};
emit_graph(&analysis, &config).unwrap();
assert!(file_path.exists());
let content = std::fs::read_to_string(file_path).unwrap();
assert!(content.contains("<!DOCTYPE html>"));
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn emit_graph_text_json_output() {
let temp_dir = std::env::temp_dir().join("emit_graph_text_json_output");
if temp_dir.exists() {
let _ = std::fs::remove_dir_all(&temp_dir);
}
std::fs::create_dir_all(&temp_dir).unwrap();
let file_path = temp_dir.join("graph.json");
let builder = GraphBuilder::new(SnapshotId::new(1).unwrap());
let graph = builder.build();
let scc = SccAnalysis::analyze(&graph.graph);
let analysis = GraphAnalysis {
graph,
scc,
snapshot_id: SnapshotId::new(1).unwrap(),
extractions: vec![],
};
let config = EmitConfig {
output: Some(file_path.clone()),
format: OutputFormat::Json,
html: false,
open_browser: false,
};
emit_graph(&analysis, &config).unwrap();
assert!(file_path.exists());
let content = std::fs::read_to_string(file_path).unwrap();
assert!(content.contains("nodes"));
let _ = std::fs::remove_dir_all(&temp_dir);
}
}