pub mod anchor_styles;
pub mod blockquote;
pub mod code_block_utils;
pub mod early_returns;
pub mod emphasis_utils;
pub mod fix_utils;
pub mod header_id_utils;
pub mod jinja_utils;
pub mod kramdown_utils;
pub mod line_ending;
pub mod markdown_elements;
pub mod mkdocs_abbreviations;
pub mod mkdocs_admonitions;
pub mod mkdocs_attr_list;
pub mod mkdocs_common;
pub mod mkdocs_config;
pub mod mkdocs_critic;
pub mod mkdocs_definition_lists;
pub mod mkdocs_extensions;
pub mod mkdocs_footnotes;
pub mod mkdocs_html_markdown;
pub mod mkdocs_icons;
pub mod mkdocs_patterns;
pub mod mkdocs_snippets;
pub mod mkdocs_tabs;
pub mod mkdocs_test_utils;
pub mod mkdocstrings_refs;
pub mod obsidian_config;
pub mod parser_options;
pub mod pymdown_blocks;
pub mod quarto_divs;
pub mod range_utils;
pub mod regex_cache;
pub mod sentence_utils;
pub mod skip_context;
pub mod string_interner;
pub mod table_utils;
pub mod text_reflow;
pub mod thematic_break;
pub mod utf8_offsets;
pub use code_block_utils::CodeBlockUtils;
pub use line_ending::{
LineEnding, detect_line_ending, detect_line_ending_enum, ensure_consistent_line_endings, get_line_ending_str,
normalize_line_ending,
};
pub use markdown_elements::{ElementQuality, ElementType, MarkdownElement, MarkdownElements};
pub use parser_options::rumdl_parser_options;
pub use range_utils::LineIndex;
pub fn calculate_indentation_width(indent_str: &str, tab_width: usize) -> usize {
let mut width = 0;
for ch in indent_str.chars() {
if ch == '\t' {
width = ((width / tab_width) + 1) * tab_width;
} else if ch == ' ' {
width += 1;
} else {
break;
}
}
width
}
pub fn calculate_indentation_width_default(indent_str: &str) -> usize {
calculate_indentation_width(indent_str, 4)
}
pub fn is_definition_list_item(line: &str) -> bool {
let trimmed = line.trim_start();
trimmed.starts_with(": ")
|| (trimmed.starts_with(':') && trimmed.len() > 1 && trimmed.chars().nth(1).is_some_and(|c| c.is_whitespace()))
}
pub fn is_template_directive_only(line: &str) -> bool {
let trimmed = line.trim();
if trimmed.is_empty() {
return false;
}
(trimmed.starts_with("{{") && trimmed.ends_with("}}")) || (trimmed.starts_with("{%") && trimmed.ends_with("%}"))
}
pub trait StrExt {
fn replace_trailing_spaces(&self, replacement: &str) -> String;
fn has_trailing_spaces(&self) -> bool;
fn trailing_spaces(&self) -> usize;
}
impl StrExt for str {
fn replace_trailing_spaces(&self, replacement: &str) -> String {
let (content, ends_with_newline) = if let Some(stripped) = self.strip_suffix('\n') {
(stripped, true)
} else {
(self, false)
};
let mut non_space_len = content.len();
for c in content.chars().rev() {
if c == ' ' {
non_space_len -= 1;
} else {
break;
}
}
let mut result =
String::with_capacity(non_space_len + replacement.len() + if ends_with_newline { 1 } else { 0 });
result.push_str(&content[..non_space_len]);
result.push_str(replacement);
if ends_with_newline {
result.push('\n');
}
result
}
fn has_trailing_spaces(&self) -> bool {
self.trailing_spaces() > 0
}
fn trailing_spaces(&self) -> usize {
let content = self.strip_suffix('\n').unwrap_or(self);
let mut space_count = 0;
for c in content.chars().rev() {
if c == ' ' {
space_count += 1;
} else {
break;
}
}
space_count
}
}
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
pub fn fast_hash(content: &str) -> u64 {
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_line_ending_pure_lf() {
let content = "First line\nSecond line\nThird line\n";
assert_eq!(detect_line_ending(content), "\n");
}
#[test]
fn test_detect_line_ending_pure_crlf() {
let content = "First line\r\nSecond line\r\nThird line\r\n";
assert_eq!(detect_line_ending(content), "\r\n");
}
#[test]
fn test_detect_line_ending_mixed_more_lf() {
let content = "First line\nSecond line\r\nThird line\nFourth line\n";
assert_eq!(detect_line_ending(content), "\n");
}
#[test]
fn test_detect_line_ending_mixed_more_crlf() {
let content = "First line\r\nSecond line\r\nThird line\nFourth line\r\n";
assert_eq!(detect_line_ending(content), "\r\n");
}
#[test]
fn test_detect_line_ending_empty_string() {
let content = "";
assert_eq!(detect_line_ending(content), "\n");
}
#[test]
fn test_detect_line_ending_single_line_no_ending() {
let content = "This is a single line with no line ending";
assert_eq!(detect_line_ending(content), "\n");
}
#[test]
fn test_detect_line_ending_equal_lf_and_crlf() {
let content = "Line 1\r\nLine 2\nLine 3\r\nLine 4\n";
assert_eq!(detect_line_ending(content), "\n");
}
#[test]
fn test_detect_line_ending_single_lf() {
let content = "Line 1\n";
assert_eq!(detect_line_ending(content), "\n");
}
#[test]
fn test_detect_line_ending_single_crlf() {
let content = "Line 1\r\n";
assert_eq!(detect_line_ending(content), "\r\n");
}
#[test]
fn test_detect_line_ending_embedded_cr() {
let content = "Line 1\rLine 2\nLine 3\r\nLine 4\n";
assert_eq!(detect_line_ending(content), "\n");
}
}