use crate::pass_subs::{Defaults, Stage};
use crate::ruby_strip::RubyStrip;
use crate::{AttributePart, Attributes, ResourceProvider, ResourceRequest, Span};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DocinfoLocation {
Head,
Header,
Footer,
}
impl DocinfoLocation {
fn name(self) -> &'static str {
match self {
Self::Head => "head",
Self::Header => "header",
Self::Footer => "footer",
}
}
}
pub fn docinfo(
attributes: &Attributes,
location: DocinfoLocation,
suffix: &str,
provider: &dyn ResourceProvider,
referrer: Option<&str>,
base_dir: Option<&str>,
warnings: &mut Vec<String>,
) -> Vec<AttributePart> {
let selected: Vec<String> = match attributes.get("docinfo") {
Some(value) if !value.is_empty() => value
.split(',')
.map(|name| name.ruby_strip().to_owned())
.collect(),
value => {
let names: &[&str] = if attributes.get("docinfo2").is_some() {
&["private", "shared"]
} else if attributes.get("docinfo1").is_some() {
&["shared"]
} else if value.is_some() {
&["private"]
} else {
&[]
};
names.iter().map(|name| (*name).to_owned()).collect()
}
};
if selected.is_empty() {
return Vec::new();
}
let has = |kind: &str| {
selected
.iter()
.any(|name| name == kind || *name == format!("{kind}-{}", location.name()))
};
let qualifier = match location {
DocinfoLocation::Head => String::new(),
_ => format!("-{}", location.name()),
};
let file = format!("docinfo{qualifier}{suffix}");
let referrer_dir = referrer
.and_then(|name| provider.document_path(name))
.and_then(|path| path.rsplit_once('/').map(|(dir, _)| dir.to_owned()))
.map(|dir| if dir.is_empty() { "/".to_owned() } else { dir });
let safe = crate::SafeMode::of(attributes);
let docinfodir = attributes.get("docinfodir").filter(|dir| !dir.is_empty());
let mut path = |name: &str| {
let resolved = crate::system_path::asset_path(
name,
docinfodir,
base_dir,
referrer_dir.as_deref(),
safe,
"path",
);
warnings.extend(resolved.warnings);
resolved.request
};
let mut targets = Vec::new();
if has("shared") {
targets.push(path(&file));
}
if let Some(docname) = attributes.get("docname").filter(|name| !name.is_empty()) {
if has("private") {
targets.push(path(&format!("{docname}-{file}")));
}
}
let stages = match attributes.get("docinfosubs") {
Some(subs) => crate::pass_subs::resolve_block(
subs,
Defaults::None,
"docinfo",
Span::new(0, 0),
&mut Vec::new(),
),
None => vec![Stage::Attributes],
};
let mut content = Vec::new();
for target in targets {
let Ok(resource) = crate::resource::load_source_text(
provider,
ResourceRequest {
target: &target,
referrer,
},
) else {
continue;
};
if !content.is_empty() {
content.push(AttributePart::Raw("\n".to_owned()));
}
content.extend(substitute_docinfo(
&normalize(&resource.text),
&stages,
attributes,
));
}
content
}
fn normalize(text: &str) -> String {
let text = text.strip_prefix('\u{feff}').unwrap_or(text);
text.split_terminator('\n')
.map(RubyStrip::ruby_rstrip)
.collect::<Vec<_>>()
.join("\n")
}
fn substitute_docinfo(text: &str, stages: &[Stage], attributes: &Attributes) -> Vec<AttributePart> {
let mut evaluation = crate::substitution_context::Evaluation::read_only(attributes);
let compat = attributes.get("compat-mode").is_some();
let (_, mut passes) = crate::inline_pass::Passes::extract("", 0, &mut evaluation, compat);
let mut parts = vec![AttributePart::Raw(text.to_owned())];
for &stage in stages {
parts = crate::pass_subs::apply(
parts,
stage,
&mut evaluation,
compat,
Span::new(0, 0),
0,
&mut passes,
);
}
parts
}
pub(crate) fn substitute(
text: &str,
stages: &[Stage],
attributes: &Attributes,
diagnostics: &mut Vec<crate::Diagnostic>,
) -> Vec<AttributePart> {
let mut evaluation = crate::substitution_context::Evaluation::read_only(attributes);
let compat = attributes.get("compat-mode").is_some();
crate::inline_pass::Passes::selected_stages(
text,
stages,
Default::default(),
&mut evaluation,
compat,
Span::new(0, 0),
diagnostics,
)
}