use crate::snippets::types::Language;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct GapCoverage {
pub snippet_roots: usize,
pub snippets_discovered: usize,
pub docs_roots: usize,
pub docs_pages_scanned: usize,
pub include_references: usize,
pub mkdocs_include_references: usize,
pub configured_references: usize,
pub required_languages: usize,
pub language_groups: usize,
pub include_base_paths: usize,
}
impl GapCoverage {
#[must_use]
pub fn report_lines(&self) -> Vec<String> {
vec![
"Gap coverage (what this run compared, so a clean result is not read as more than it is):".to_owned(),
format!(
" snippet roots: {} configured, {} snippet file(s) discovered",
self.snippet_roots, self.snippets_discovered
),
format!(
" documentation roots: {} configured, {} page(s) opened and parsed{}",
self.docs_roots,
self.docs_pages_scanned,
if self.docs_pages_scanned == 0 {
" -- NO documentation page entered this result"
} else {
""
}
),
format!(
" references: {} discovered in documentation ({} include base path(s)), {} supplied by \
configuration (coverage ledgers, [crates.readme] snippets, Astro collections)",
self.include_references, self.include_base_paths, self.configured_references
),
format!(
" language parity: {} required language(s) across {} snippet group(s){}",
self.required_languages,
self.language_groups,
if self.required_languages == 0 || self.language_groups == 0 {
" -- the missing-language-variant check produced no finding because it compared nothing"
} else {
""
}
),
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnsetGapInput {
pub key: &'static str,
pub flag: &'static str,
pub consequence: &'static str,
pub vacuous: bool,
}
#[must_use]
pub fn unset_gap_inputs(
docs_dirs: &[PathBuf],
required_languages: &[Language],
include_base_paths: &[PathBuf],
mkdocs_include_references: usize,
) -> Vec<UnsetGapInput> {
let mut unset = Vec::new();
if docs_dirs.is_empty() {
unset.push(UnsetGapInput {
key: "docs_dirs",
flag: "--docs",
consequence: "no documentation page was opened, so the missing-include-target check did NOT run and \
every snippet's referenced/orphaned status came only from configured references",
vacuous: true,
});
}
if required_languages.is_empty() {
unset.push(UnsetGapInput {
key: "required_languages",
flag: "-L/--required-languages",
consequence: "the missing-language-variant check did NOT run, so no language parity was compared",
vacuous: true,
});
}
if include_base_paths.is_empty() && mkdocs_include_references > 0 {
unset.push(UnsetGapInput {
key: "include_base_paths",
flag: "--include-base-path",
consequence: "`--8<--` targets resolve against the documentation root only, so includes written \
against a pymdownx.snippets base_path resolve to the wrong candidate path",
vacuous: false,
});
}
unset
}
#[must_use]
pub fn has_vacuous_input(unset: &[UnsetGapInput]) -> bool {
unset.iter().any(|input| input.vacuous)
}
#[must_use]
pub fn unset_input_lines(unset: &[UnsetGapInput], strict: bool) -> Vec<String> {
if unset.is_empty() {
return Vec::new();
}
let mut lines = vec![format!(
"Gap check NOT fully configured: {} input(s) unset, each disabling a check class:",
unset.len()
)];
for input in unset {
lines.push(format!(" {} unset ({}): {}", input.key, input.flag, input.consequence));
}
if has_vacuous_input(unset) && !strict {
lines.push(
" A clean result below therefore proves less than it appears to. Configure the keys above, or \
pass --strict to make an unconfigured gap check fail instead of pass."
.to_owned(),
);
}
lines
}
#[cfg(test)]
mod tests;