use crate::{
Anchor, AttributeValue, BlockMetadata, Title, grammar::ParserState, model::SectionLevel,
};
#[derive(Debug)]
pub(crate) struct PositionWithOffset {
pub(crate) offset: usize,
pub(crate) position: crate::Position,
}
#[derive(Debug)]
pub(crate) enum BlockMetadataLine<'input> {
Anchor(Anchor),
Attributes((bool, Box<BlockMetadata>)),
Title(Title),
DocumentAttribute(&'input str, AttributeValue),
}
#[derive(Debug)]
pub(crate) enum HeaderMetadataLine {
Anchor(Anchor),
Attributes((bool, Box<BlockMetadata>)),
}
#[derive(Debug)]
pub(crate) struct BlockParsingMetadata {
pub(crate) metadata: BlockMetadata,
pub(crate) title: Title,
pub(crate) parent_section_level: Option<SectionLevel>,
pub(crate) macros_enabled: bool,
pub(crate) attributes_enabled: bool,
}
impl Default for BlockParsingMetadata {
fn default() -> Self {
Self {
metadata: BlockMetadata::default(),
title: Title::default(),
parent_section_level: None,
macros_enabled: true,
attributes_enabled: true,
}
}
}
#[derive(Debug)]
pub(crate) enum Shorthand {
Id(String),
Role(String),
Option(String),
}
pub(crate) const RESERVED_NAMED_ATTRIBUTE_ID: &str = "id";
pub(crate) const RESERVED_NAMED_ATTRIBUTE_ROLE: &str = "role";
pub(crate) const RESERVED_NAMED_ATTRIBUTE_OPTIONS: &str = "opts";
pub(crate) const RESERVED_NAMED_ATTRIBUTE_SUBS: &str = "subs";
pub(crate) fn strip_url_backslash_escapes(text: &str) -> String {
text.replace("\\...", "...")
.replace("\\->", "->")
.replace("\\<-", "<-")
.replace("\\=>", "=>")
.replace("\\<=", "<=")
.replace("\\--", "--")
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct AttributeProcessingMode {
pub(crate) first_positional_is_style: bool,
#[allow(dead_code)]
pub(crate) process_subs: bool,
}
impl AttributeProcessingMode {
pub(crate) const BLOCK: Self = Self {
first_positional_is_style: false,
process_subs: true,
};
pub(crate) const MACRO: Self = Self {
first_positional_is_style: true,
process_subs: false,
};
}
pub(crate) fn parse_comma_separated_values(value: &str) -> Vec<String> {
value
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
}
pub(crate) fn process_attribute_list(
attrs: impl IntoIterator<Item = Option<(String, AttributeValue, Option<(usize, usize)>)>>,
metadata: &mut BlockMetadata,
state: &ParserState,
fallback_start: usize,
fallback_end: usize,
mode: AttributeProcessingMode,
) -> Option<(usize, usize)> {
let mut title_position = None;
let mut first_positional = true;
for (key, value, pos) in attrs.into_iter().flatten() {
match key.as_str() {
k if k == RESERVED_NAMED_ATTRIBUTE_ID && metadata.id.is_none() => {
let (id_start, id_end) = pos.unwrap_or((fallback_start, fallback_end));
metadata.id = Some(Anchor {
id: value.to_string(),
xreflabel: None,
location: state.create_location(id_start, id_end),
});
}
k if k == RESERVED_NAMED_ATTRIBUTE_ROLE => {
if let AttributeValue::String(ref s) = value {
for role in s.split_whitespace() {
if !role.is_empty() {
metadata.roles.push(role.to_string());
}
}
}
}
k if k == RESERVED_NAMED_ATTRIBUTE_OPTIONS => {
if let AttributeValue::String(ref s) = value {
metadata.options.extend(parse_comma_separated_values(s));
}
}
k if k == RESERVED_NAMED_ATTRIBUTE_SUBS => {}
"title" => {
if let AttributeValue::String(ref s) = value {
if pos.is_some() {
title_position = pos;
}
metadata
.attributes
.insert(key, AttributeValue::String(s.clone()));
}
}
_ => {
if let AttributeValue::String(ref s) = value {
metadata
.attributes
.insert(key, AttributeValue::String(s.clone()));
} else if value == AttributeValue::None {
if mode.first_positional_is_style && first_positional {
metadata.style = Some(key);
first_positional = false;
} else {
metadata.positional_attributes.push(key);
}
}
}
}
}
title_position
}
pub(crate) fn title_looks_like_description_list(title: &str) -> bool {
let trimmed = title.trim();
for marker in &["::::", ":::", "::", ";;"] {
if let Some(pos) = trimmed.find(marker) &&
pos > 0 &&
let Some(after) = trimmed.get(pos + marker.len()..)
&& (after.is_empty() || after.starts_with(' ') || after.starts_with('\t'))
{
return true;
}
}
false
}