use pulldown_cmark::LinkType;
use std::borrow::Cow;
#[derive(Debug, Clone)]
pub struct LineInfo {
pub byte_offset: usize,
pub byte_len: usize,
pub indent: usize,
pub visual_indent: usize,
pub is_blank: bool,
pub in_code_block: bool,
pub in_front_matter: bool,
pub in_html_block: bool,
pub in_html_comment: bool,
pub list_item: Option<Box<ListItemInfo>>,
pub heading: Option<Box<HeadingInfo>>,
pub blockquote: Option<Box<BlockquoteInfo>>,
pub in_mkdocstrings: bool,
pub in_esm_block: bool,
pub in_code_span_continuation: bool,
pub is_horizontal_rule: bool,
pub in_math_block: bool,
pub in_quarto_div: bool,
pub is_div_marker: bool,
pub in_jsx_expression: bool,
pub in_mdx_comment: bool,
pub in_admonition: bool,
pub in_content_tab: bool,
pub in_mkdocs_html_markdown: bool,
pub in_definition_list: bool,
pub in_obsidian_comment: bool,
pub in_pymdown_block: bool,
pub in_kramdown_extension_block: bool,
pub is_kramdown_block_ial: bool,
pub in_jsx_block: bool,
}
impl LineInfo {
pub fn content<'a>(&self, source: &'a str) -> &'a str {
&source[self.byte_offset..self.byte_offset + self.byte_len]
}
#[inline]
pub fn in_mkdocs_container(&self) -> bool {
self.in_admonition || self.in_content_tab || self.in_mkdocs_html_markdown
}
}
#[derive(Debug, Clone)]
pub struct ListItemInfo {
pub marker: String,
pub is_ordered: bool,
pub number: Option<usize>,
pub marker_column: usize,
pub content_column: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HeadingStyle {
ATX,
Setext1,
Setext2,
}
#[derive(Debug, Clone)]
pub struct ParsedLink<'a> {
pub line: usize,
pub start_col: usize,
pub end_col: usize,
pub byte_offset: usize,
pub byte_end: usize,
pub text: Cow<'a, str>,
pub url: Cow<'a, str>,
pub is_reference: bool,
pub reference_id: Option<Cow<'a, str>>,
pub link_type: LinkType,
}
#[derive(Debug, Clone)]
pub struct BrokenLinkInfo {
pub reference: String,
pub span: std::ops::Range<usize>,
}
#[derive(Debug, Clone)]
pub struct FootnoteRef {
pub id: String,
pub line: usize,
pub byte_offset: usize,
pub byte_end: usize,
}
#[derive(Debug, Clone)]
pub struct ParsedImage<'a> {
pub line: usize,
pub start_col: usize,
pub end_col: usize,
pub byte_offset: usize,
pub byte_end: usize,
pub alt_text: Cow<'a, str>,
pub url: Cow<'a, str>,
pub is_reference: bool,
pub reference_id: Option<Cow<'a, str>>,
pub link_type: LinkType,
}
#[derive(Debug, Clone)]
pub struct ReferenceDef {
pub line: usize,
pub id: String,
pub url: String,
pub title: Option<String>,
pub byte_offset: usize,
pub byte_end: usize,
pub title_byte_start: Option<usize>,
pub title_byte_end: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct CodeSpan {
pub line: usize,
pub end_line: usize,
pub start_col: usize,
pub end_col: usize,
pub byte_offset: usize,
pub byte_end: usize,
pub backtick_count: usize,
pub content: String,
}
#[derive(Debug, Clone)]
pub struct MathSpan {
pub line: usize,
pub end_line: usize,
pub start_col: usize,
pub end_col: usize,
pub byte_offset: usize,
pub byte_end: usize,
pub is_display: bool,
pub content: String,
}
#[derive(Debug, Clone)]
pub struct HeadingInfo {
pub level: u8,
pub style: HeadingStyle,
pub marker: String,
pub marker_column: usize,
pub content_column: usize,
pub text: String,
pub custom_id: Option<String>,
pub raw_text: String,
pub has_closing_sequence: bool,
pub closing_sequence: String,
pub is_valid: bool,
}
#[derive(Debug, Clone)]
pub struct ValidHeading<'a> {
pub line_num: usize,
pub heading: &'a HeadingInfo,
pub line_info: &'a LineInfo,
}
pub struct ValidHeadingsIter<'a> {
lines: &'a [LineInfo],
current_index: usize,
}
impl<'a> ValidHeadingsIter<'a> {
pub(super) fn new(lines: &'a [LineInfo]) -> Self {
Self {
lines,
current_index: 0,
}
}
}
impl<'a> Iterator for ValidHeadingsIter<'a> {
type Item = ValidHeading<'a>;
fn next(&mut self) -> Option<Self::Item> {
while self.current_index < self.lines.len() {
let idx = self.current_index;
self.current_index += 1;
let line_info = &self.lines[idx];
if let Some(heading) = line_info.heading.as_deref()
&& heading.is_valid
{
return Some(ValidHeading {
line_num: idx + 1, heading,
line_info,
});
}
}
None
}
}
#[derive(Debug, Clone)]
pub struct BlockquoteInfo {
pub nesting_level: usize,
pub indent: String,
pub marker_column: usize,
pub prefix: String,
pub content: String,
pub has_no_space_after_marker: bool,
pub has_multiple_spaces_after_marker: bool,
pub needs_md028_fix: bool,
}
#[derive(Debug, Clone)]
pub struct ListBlock {
pub start_line: usize,
pub end_line: usize,
pub is_ordered: bool,
pub marker: Option<String>,
pub blockquote_prefix: String,
pub item_lines: Vec<usize>,
pub nesting_level: usize,
pub max_marker_width: usize,
}
#[derive(Debug, Clone, Default)]
pub struct CharFrequency {
pub hash_count: usize,
pub asterisk_count: usize,
pub underscore_count: usize,
pub hyphen_count: usize,
pub plus_count: usize,
pub gt_count: usize,
pub pipe_count: usize,
pub bracket_count: usize,
pub backtick_count: usize,
pub lt_count: usize,
pub exclamation_count: usize,
pub newline_count: usize,
}
#[derive(Debug, Clone)]
pub struct HtmlTag {
pub line: usize,
pub start_col: usize,
pub end_col: usize,
pub byte_offset: usize,
pub byte_end: usize,
pub tag_name: String,
pub is_closing: bool,
pub is_self_closing: bool,
pub raw_content: String,
}
#[derive(Debug, Clone)]
pub struct EmphasisSpan {
pub line: usize,
pub start_col: usize,
pub end_col: usize,
pub byte_offset: usize,
pub byte_end: usize,
pub marker: char,
pub marker_count: usize,
pub content: String,
}
#[derive(Debug, Clone)]
pub struct TableRow {
pub line: usize,
pub is_separator: bool,
pub column_count: usize,
pub column_alignments: Vec<String>, }
#[derive(Debug, Clone)]
pub struct BareUrl {
pub line: usize,
pub start_col: usize,
pub end_col: usize,
pub byte_offset: usize,
pub byte_end: usize,
pub url: String,
pub url_type: String,
}
#[derive(Debug, Clone)]
pub struct LazyContLine {
pub line_num: usize,
pub expected_indent: usize,
pub current_indent: usize,
pub blockquote_level: usize,
}
pub fn is_horizontal_rule_line(line: &str) -> bool {
let leading_spaces = line.len() - line.trim_start_matches(' ').len();
if leading_spaces > 3 || line.starts_with('\t') {
return false;
}
is_horizontal_rule_content(line.trim())
}
pub fn is_horizontal_rule_content(trimmed: &str) -> bool {
if trimmed.len() < 3 {
return false;
}
let mut chars = trimmed.chars();
let first_char = match chars.next() {
Some(c @ ('-' | '*' | '_')) => c,
_ => return false,
};
let mut count = 1; for ch in chars {
if ch == first_char {
count += 1;
} else if ch != ' ' && ch != '\t' {
return false;
}
}
count >= 3
}
pub fn is_horizontal_rule(trimmed: &str) -> bool {
is_horizontal_rule_content(trimmed)
}