1pub mod anchor_styles;
6pub mod ast_utils;
7pub mod code_block_utils;
8pub mod document_structure;
9pub mod early_returns;
10pub mod element_cache;
11pub mod emphasis_utils;
12pub mod fix_utils;
13pub mod header_id_utils;
14pub mod kramdown_utils;
15pub mod markdown_elements;
16pub mod mkdocs_patterns;
17pub mod range_utils;
18pub mod regex_cache;
19pub mod skip_context;
20pub mod string_interner;
21pub mod table_utils;
22pub mod text_reflow;
23
24pub use ast_utils::AstCache;
25pub use code_block_utils::CodeBlockUtils;
26pub use document_structure::DocumentStructure;
27pub use markdown_elements::{ElementQuality, ElementType, MarkdownElement, MarkdownElements};
28
29pub fn detect_line_ending(content: &str) -> &'static str {
31 let crlf_count = content.matches("\r\n").count();
32 let lf_count = content.matches('\n').count() - crlf_count;
33
34 if crlf_count > lf_count { "\r\n" } else { "\n" }
35}
36pub use range_utils::LineIndex;
37
38pub trait StrExt {
40 fn replace_trailing_spaces(&self, replacement: &str) -> String;
42
43 fn has_trailing_spaces(&self) -> bool;
45
46 fn trailing_spaces(&self) -> usize;
48}
49
50impl StrExt for str {
51 fn replace_trailing_spaces(&self, replacement: &str) -> String {
52 let (content, ends_with_newline) = if let Some(stripped) = self.strip_suffix('\n') {
56 (stripped, true)
57 } else {
58 (self, false)
59 };
60
61 let mut non_space_len = content.len();
63 for c in content.chars().rev() {
64 if c == ' ' {
65 non_space_len -= 1;
66 } else {
67 break;
68 }
69 }
70
71 let mut result =
73 String::with_capacity(non_space_len + replacement.len() + if ends_with_newline { 1 } else { 0 });
74 result.push_str(&content[..non_space_len]);
75 result.push_str(replacement);
76 if ends_with_newline {
77 result.push('\n');
78 }
79
80 result
81 }
82
83 fn has_trailing_spaces(&self) -> bool {
84 self.trailing_spaces() > 0
85 }
86
87 fn trailing_spaces(&self) -> usize {
88 let content = self.strip_suffix('\n').unwrap_or(self);
92
93 let mut space_count = 0;
95 for c in content.chars().rev() {
96 if c == ' ' {
97 space_count += 1;
98 } else {
99 break;
100 }
101 }
102
103 space_count
104 }
105}
106
107use std::collections::hash_map::DefaultHasher;
108use std::hash::{Hash, Hasher};
109
110pub fn fast_hash(content: &str) -> u64 {
123 let mut hasher = DefaultHasher::new();
124 content.hash(&mut hasher);
125 hasher.finish()
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_detect_line_ending_pure_lf() {
134 let content = "First line\nSecond line\nThird line\n";
136 assert_eq!(detect_line_ending(content), "\n");
137 }
138
139 #[test]
140 fn test_detect_line_ending_pure_crlf() {
141 let content = "First line\r\nSecond line\r\nThird line\r\n";
143 assert_eq!(detect_line_ending(content), "\r\n");
144 }
145
146 #[test]
147 fn test_detect_line_ending_mixed_more_lf() {
148 let content = "First line\nSecond line\r\nThird line\nFourth line\n";
150 assert_eq!(detect_line_ending(content), "\n");
151 }
152
153 #[test]
154 fn test_detect_line_ending_mixed_more_crlf() {
155 let content = "First line\r\nSecond line\r\nThird line\nFourth line\r\n";
157 assert_eq!(detect_line_ending(content), "\r\n");
158 }
159
160 #[test]
161 fn test_detect_line_ending_empty_string() {
162 let content = "";
164 assert_eq!(detect_line_ending(content), "\n");
165 }
166
167 #[test]
168 fn test_detect_line_ending_single_line_no_ending() {
169 let content = "This is a single line with no line ending";
171 assert_eq!(detect_line_ending(content), "\n");
172 }
173
174 #[test]
175 fn test_detect_line_ending_equal_lf_and_crlf() {
176 let content = "Line 1\r\nLine 2\nLine 3\r\nLine 4\n";
180 assert_eq!(detect_line_ending(content), "\n");
181 }
182
183 #[test]
184 fn test_detect_line_ending_single_lf() {
185 let content = "Line 1\n";
187 assert_eq!(detect_line_ending(content), "\n");
188 }
189
190 #[test]
191 fn test_detect_line_ending_single_crlf() {
192 let content = "Line 1\r\n";
194 assert_eq!(detect_line_ending(content), "\r\n");
195 }
196
197 #[test]
198 fn test_detect_line_ending_embedded_cr() {
199 let content = "Line 1\rLine 2\nLine 3\r\nLine 4\n";
202 assert_eq!(detect_line_ending(content), "\n");
204 }
205}