use argparse::ReportConfig;
use error::{Result, ResultExt};
use sourcepath::{SourceType, identify_source_path};
use template::new as new_template;
use utils::clean_dir;
use fs_extra::dir;
use cov::{self, Gcov, Graph, Interner, Report, Symbol};
use serde_json::Value;
use tera::{Context, Tera};
use std::ffi::OsStr;
use std::fs::{File, create_dir_all, read_dir};
use std::io::{BufRead, BufReader, Read, Write};
use std::path::PathBuf;
pub fn generate(config: &ReportConfig) -> Result<Option<PathBuf>> {
let report_path = &config.output_path;
clean_dir(report_path).chain_err(|| "Cannot clean report directory")?;
create_dir_all(report_path)?;
let mut interner = Interner::new();
let graph = create_graph(config, &mut interner).chain_err(|| "Cannot create graph")?;
let report = graph.report();
render(config, &report, &interner).chain_err(|| "Cannot render report")
}
fn create_graph(config: &ReportConfig, interner: &mut Interner) -> cov::Result<Graph> {
let mut graph = Graph::default();
for &(extension, dir_path) in &[("gcno", &config.gcno_path), ("gcda", &config.gcda_path)] {
progress!("Parsing", "{}/*.{}", dir_path.display(), extension);
for entry in read_dir(dir_path)? {
let path = entry?.path();
if path.extension() == Some(OsStr::new(extension)) {
trace!("merging {} {:?}", extension, path);
graph.merge(Gcov::open(path, interner)?)?;
}
}
}
graph.analyze();
Ok(graph)
}
fn render(config: &ReportConfig, report: &Report, interner: &Interner) -> Result<Option<PathBuf>> {
use toml::de::from_slice;
let mut template_path = [env!("CARGO_MANIFEST_DIR"), "res", "templates"].iter().collect::<PathBuf>();
template_path.push(config.template_name);
trace!("using templates at {:?}", template_path);
template_path.push("config.toml");
let mut config_file = File::open(&template_path).chain_err(|| format!("Cannot open template at `{}`", template_path.display()))?;
let mut config_bytes = Vec::new();
config_file.read_to_end(&mut config_bytes)?;
let template_config: Config = from_slice(&config_bytes).chain_err(|| "Cannot read template configuration")?;
template_path.set_file_name("static");
if template_path.is_dir() {
let mut options = dir::CopyOptions::new();
options.copy_inside = true;
dir::copy(&template_path, config.output_path.join("static"), &options)?;
}
template_path.set_file_name("tera");
template_path.push("*");
let workspace_str = config.workspace_path.to_string_lossy();
let mut tera = new_template(template_path.to_str().expect("UTF-8 template path"))?;
let mut report_files = report
.files
.iter()
.filter_map(|(&symbol, file)| {
let path = &interner[symbol];
let source_type = identify_source_path(path, &workspace_str).0;
if config.allowed_source_types.contains(source_type) {
Some(ReportFileEntry {
symbol,
source_type,
path,
file,
})
} else {
None
}
})
.collect::<Vec<_>>();
report_files.sort_by_key(|entry| (entry.source_type, entry.path));
let summary_path = if let Some(summary) = template_config.summary {
Some(write_summary(config, &report_files, &tera, &summary).chain_err(|| "Cannot write summary")?)
} else {
None
};
if let Some(files_config) = template_config.files {
tera.add_raw_template("<filename>", files_config.output)?;
for entry in &report_files {
write_file(config, interner, entry, &tera, files_config.template).chain_err(|| format!("Cannot write file at `{}`", entry.path))?;
}
}
Ok(summary_path)
}
struct ReportFileEntry<'a> {
symbol: Symbol,
source_type: SourceType,
path: &'a str,
file: &'a ::cov::report::File,
}
#[derive(Deserialize, Debug)]
struct Config<'a> {
#[serde(borrow)]
summary: Option<FileConfig<'a>>,
#[serde(borrow)]
files: Option<FileConfig<'a>>,
}
#[derive(Deserialize, Debug)]
struct FileConfig<'a> {
#[serde(borrow)]
output: &'a str,
#[serde(borrow)]
template: &'a str,
}
fn write_summary(config: &ReportConfig, report_files: &[ReportFileEntry], tera: &Tera, file_config: &FileConfig) -> Result<PathBuf> {
let path = config.output_path.join(file_config.output);
let mut context = Context::new();
let files = report_files
.iter()
.map(|entry| {
json!({
"symbol": entry.symbol,
"path": entry.path,
"summary": entry.file.summary(),
})
})
.collect::<Vec<_>>();
context.add("crate_path", &config.workspace_path);
context.add("files", &files);
let rendered = tera.render(file_config.template, &context)?;
let mut summary_file = File::create(&path)?;
summary_file.write_all(rendered.as_bytes())?;
progress!("Created", "{}", path.display());
Ok(path)
}
fn write_file(config: &ReportConfig, interner: &Interner, entry: &ReportFileEntry, tera: &Tera, template_name: &str) -> Result<()> {
let mut context = Context::new();
let mut lines = Vec::new();
let mut source_line_number = 1;
let path = config.workspace_path.join(entry.path);
if let Ok(source_file) = File::open(path) {
let source_file = BufReader::new(source_file);
for source_line in source_file.lines() {
let (count, branches) = if let Some(line) = entry.file.lines.get(&source_line_number) {
let (count, branches) = serialize_line(line, interner);
(Some(count), branches)
} else {
(None, Vec::new())
};
lines.push(json!({
"line": source_line_number,
"source": source_line?,
"count": count,
"branches": branches,
}));
source_line_number += 1;
}
}
lines.extend(entry.file.lines.range(source_line_number..).map(|(line_number, line)| {
let (count, branches) = serialize_line(line, interner);
json!({
"line": *line_number,
"count": Some(count),
"source": Value::Null,
"branches": branches,
})
}));
let functions = entry
.file
.functions
.iter()
.map(|f| {
let name = &interner[f.name];
json!({
"symbol": f.name,
"name": name,
"line": f.line,
"column": f.column,
"summary": &f.summary,
})
})
.collect::<Vec<_>>();
context.add("crate_path", &config.workspace_path);
context.add("symbol", &entry.symbol);
context.add("path", &entry.path);
context.add("summary", &entry.file.summary());
context.add("lines", &lines);
context.add("functions", &functions);
let filename = tera.render("<filename>", &context)?;
let path = config.output_path.join(filename);
let rendered = tera.render(template_name, &context)?;
let mut file_file = File::create(path)?;
file_file.write_all(rendered.as_bytes())?;
Ok(())
}
fn serialize_line(line: &::cov::report::Line, interner: &Interner) -> (u64, Vec<Value>) {
(
line.count,
line.branches
.iter()
.map(|branch| {
json!({
"count": branch.count,
"symbol": branch.filename,
"path": &interner[branch.filename],
"line": branch.line,
"column": branch.column,
})
})
.collect(),
)
}