Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 135 additions & 4 deletions src/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub struct HtmlItem {
pub result: CovResult,
}

#[derive(Clone, Debug, Default, serde::Serialize)]
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct HtmlStats {
pub total_lines: usize,
pub covered_lines: usize,
Expand All @@ -102,26 +102,80 @@ pub struct HtmlStats {
pub covered_branches: usize,
}

#[derive(Clone, Debug, serde::Serialize)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct HtmlFileStats {
pub stats: HtmlStats,
pub abs_prefix: Option<PathBuf>,
}

#[derive(Clone, Debug, serde::Serialize)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct HtmlDirStats {
pub files: BTreeMap<String, HtmlFileStats>,
pub stats: HtmlStats,
pub abs_prefix: Option<PathBuf>,
}

#[derive(Debug, Default, serde::Serialize)]
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct HtmlGlobalStats {
pub dirs: BTreeMap<String, HtmlDirStats>,
pub stats: HtmlStats,
pub abs_prefix: Option<PathBuf>,
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum HtmlItemStats {
Directory(HtmlDirStats),
File(HtmlFileStats),
}

impl HtmlGlobalStats {
pub fn list(&self, dir: &str) -> BTreeMap<String, HtmlItemStats> {
let mut result = BTreeMap::new();

// Add files from the specified directory
if let Some(dir_stats) = self.dirs.get(dir) {
for (file_name, file_stats) in &dir_stats.files {
result.insert(file_name.clone(), HtmlItemStats::File(file_stats.clone()));
}
}

// Add subdirectories as entries
if dir.is_empty() {
// For root directory, add top-level directories
for (dir_path, dir_stats) in &self.dirs {
if !dir_path.is_empty() && !dir_path.contains('/') {
result.insert(
dir_path.clone(),
HtmlItemStats::Directory(dir_stats.clone()),
);
}
}
} else {
// For specific directory, add immediate subdirectories
let prefix = if dir.ends_with('/') {
dir.to_string()
} else {
format!("{}/", dir)
};

for (dir_path, dir_stats) in &self.dirs {
if dir_path.starts_with(&prefix) {
let suffix = &dir_path[prefix.len()..];
if !suffix.is_empty() && !suffix.contains('/') {
result.insert(
suffix.to_string(),
HtmlItemStats::Directory(dir_stats.clone()),
);
}
}
}
}

result
}
}

pub type HtmlJobReceiver = Receiver<Option<HtmlItem>>;
pub type HtmlJobSender = Sender<Option<HtmlItem>>;

Expand Down Expand Up @@ -155,3 +209,80 @@ pub struct JacocoReport {
pub lines: BTreeMap<u32, u64>,
pub branches: BTreeMap<u32, Vec<bool>>,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_html_global_stats_list() {
let global_json = serde_json::json!({
"dirs": {
"": {
"files": {
"build.rs": {
"stats": {"total_lines": 30, "covered_lines": 25, "total_funs": 3, "covered_funs": 2, "total_branches": 6, "covered_branches": 5},
"abs_prefix": null
}
},
"stats": {"total_lines": 30, "covered_lines": 25, "total_funs": 3, "covered_funs": 2, "total_branches": 6, "covered_branches": 5},
"abs_prefix": null
},
"src": {
"files": {
"lib.rs": {
"stats": {"total_lines": 50, "covered_lines": 40, "total_funs": 5, "covered_funs": 4, "total_branches": 10, "covered_branches": 8},
"abs_prefix": null
}
},
"stats": {"total_lines": 100, "covered_lines": 80, "total_funs": 10, "covered_funs": 8, "total_branches": 20, "covered_branches": 16},
"abs_prefix": null
},
"src/utils": {
"files": {
"mod.rs": {
"stats": {"total_lines": 50, "covered_lines": 40, "total_funs": 5, "covered_funs": 4, "total_branches": 10, "covered_branches": 8},
"abs_prefix": null
}
},
"stats": {"total_lines": 50, "covered_lines": 40, "total_funs": 5, "covered_funs": 4, "total_branches": 10, "covered_branches": 8},
"abs_prefix": null
}
},
"stats": {"total_lines": 130, "covered_lines": 105, "total_funs": 13, "covered_funs": 10, "total_branches": 26, "covered_branches": 21},
"abs_prefix": null
});

let global: HtmlGlobalStats = serde_json::from_value(global_json).unwrap();

let root_items = global.list("");
assert_eq!(root_items.len(), 2);
assert!(root_items.contains_key("build.rs"));
assert!(root_items.contains_key("src"));

// Check that build.rs is a file and src is a directory
match root_items.get("build.rs").unwrap() {
HtmlItemStats::File(_) => {}
HtmlItemStats::Directory(_) => panic!("build.rs should be a file"),
}
match root_items.get("src").unwrap() {
HtmlItemStats::Directory(_) => {}
HtmlItemStats::File(_) => panic!("src should be a directory"),
}

let src_items = global.list("src");
assert_eq!(src_items.len(), 2);
assert!(src_items.contains_key("lib.rs"));
assert!(src_items.contains_key("utils"));

// Check that utils is a directory
match src_items.get("utils").unwrap() {
HtmlItemStats::Directory(_) => {}
HtmlItemStats::File(_) => panic!("utils should be a directory"),
}

let utils_items = global.list("src/utils");
assert_eq!(utils_items.len(), 1);
assert!(utils_items.contains_key("mod.rs"));
}
}
41 changes: 33 additions & 8 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,11 @@ pub fn gen_index(tera: &Tera, global: &HtmlGlobalStats, conf: &Config, output: &

let mut ctx = make_context(conf, ".");
let empty: &[&str] = &[];
let items = global.list("");
ctx.insert("current", "top_level");
ctx.insert("parents", empty);
ctx.insert("stats", &global.stats);
ctx.insert("items", &global.dirs);
ctx.insert("kind", "Directory");
ctx.insert("items", &items);

let out = tera.render("index.html", &ctx).unwrap();

Expand All @@ -347,14 +347,22 @@ pub fn gen_index(tera: &Tera, global: &HtmlGlobalStats, conf: &Config, output: &
return;
}

for (dir_name, dir_stats) in global.dirs.iter() {
gen_dir_index(tera, dir_name, dir_stats, conf, output);
for (dir_name, item_type) in items.iter() {
match item_type {
HtmlItemStats::Directory(dir_stats) => {
gen_dir_index(tera, dir_name, global, dir_stats, conf, output);
}
HtmlItemStats::File(_) => {
// Files don't generate directory indexes, skip
}
}
}
}

pub fn gen_dir_index(
tera: &Tera,
dir_name: &str,
global: &HtmlGlobalStats,
dir_stats: &HtmlDirStats,
conf: &Config,
output: &Path,
Expand All @@ -364,7 +372,7 @@ pub fn gen_dir_index(
let prefix = "../".repeat(layers) + "index.html";
let output_file = output.join(index);
create_parent(&output_file);
let mut output = match File::create(&output_file) {
let mut output_file = match File::create(&output_file) {
Err(_) => {
eprintln!("Cannot create file {output_file:?}");
return;
Expand All @@ -380,14 +388,31 @@ pub fn gen_dir_index(
};
ctx.insert("parents", &[(parent_link, "top_level")]);
ctx.insert("stats", &dir_stats.stats);
ctx.insert("items", &dir_stats.files);
ctx.insert("kind", "File");
let items = global.list(dir_name);
ctx.insert("items", &items);

let out = tera.render("index.html", &ctx).unwrap();

if output.write_all(out.as_bytes()).is_err() {
if output_file.write_all(out.as_bytes()).is_err() {
eprintln!("Cannot write the file {output_file:?}");
}

let parent = if dir_name.is_empty() {
dir_name.to_string()
} else {
dir_name.to_string() + "/"
};
for (dir_name, item_type) in items.iter() {
match item_type {
HtmlItemStats::Directory(dir_stats) => {
let full_dir_name = parent.clone() + dir_name;
gen_dir_index(tera, &full_dir_name, global, dir_stats, conf, output);
}
HtmlItemStats::File(_) => {
// Files don't generate directory indexes, skip
}
}
}
}

fn gen_html(
Expand Down
36 changes: 18 additions & 18 deletions src/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<table class="table is-fullwidth">
<thead>
<tr>
<th>{{ kind }}</th>
<th>Name</th>
<th class="has-text-centered" colspan="3">Line Coverage</th>
<th class="has-text-centered" colspan="2">Functions</th>
{% if branch_enabled %}
Expand All @@ -17,23 +17,23 @@
</tr>
</thead>
<tbody>
{%- if kind == "Directory" -%}
{%- for item, info in items -%}
{% if info.abs_prefix and info.abs_prefix != "" %}
{{ macros::stats_line(name=item, url=info.abs_prefix~item~"/index.html", stats=info.stats, precision=precision) }}
{% else %}
{{ macros::stats_line(name=item, url=item~"/index.html", stats=info.stats, precision=precision) }}
{% endif %}
{%- endfor -%}
{%- else -%}
{%- for item, info in items -%}
{% if info.abs_prefix and info.abs_prefix != "" %}
{{ macros::stats_line(name=item, url=info.abs_prefix~"/"~item~".html", stats=info.stats, precision=precision) }}
{% else %}
{{ macros::stats_line(name=item, url=item~".html", stats=info.stats, precision=precision) }}
{% endif %}
{%- endfor -%}
{%- endif -%}
{%- for item, info in items -%}
{%- if info.files is defined -%}
{# This is a directory #}
{% if info.abs_prefix and info.abs_prefix != "" %}
{{ macros::stats_line(name=item, url=info.abs_prefix~item~"/index.html", stats=info.stats, precision=precision) }}
{% else %}
{{ macros::stats_line(name=item, url=item~"/index.html", stats=info.stats, precision=precision) }}
{% endif %}
{%- else -%}
{# This is a file #}
{% if info.abs_prefix and info.abs_prefix != "" %}
{{ macros::stats_line(name=item, url=info.abs_prefix~"/"~item~".html", stats=info.stats, precision=precision) }}
{% else %}
{{ macros::stats_line(name=item, url=item~".html", stats=info.stats, precision=precision) }}
{% endif %}
{%- endif -%}
{%- endfor -%}
</tbody>
</table>
{%- endblock content -%}
Expand Down
Loading