html-to-markdown 3.4.0 → 3.6.0.pre.rc.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +347 -0
- data/Steepfile +10 -2
- data/ext/html_to_markdown_rb/Cargo.toml +3 -2
- data/ext/html_to_markdown_rb/extconf.rb +5 -5
- data/ext/html_to_markdown_rb/native/Cargo.lock +962 -0
- data/ext/html_to_markdown_rb/native/Cargo.toml +6 -11
- data/ext/html_to_markdown_rb/native/extconf.rb +14 -0
- data/ext/html_to_markdown_rb/src/lib.rs +1715 -646
- data/lib/html_to_markdown/native.rb +913 -37
- data/lib/html_to_markdown/version.rb +3 -3
- data/lib/html_to_markdown.rb +9 -4
- data/lib/html_to_markdown_rb.so +0 -0
- data/sig/types.rbs +59 -292
- metadata +32 -179
- data/ext/html_to_markdown_rb/Makefile +0 -592
- data/lib/bin/html-to-markdown +0 -0
- data/vendor/Cargo.toml +0 -33
- data/vendor/html-to-markdown-rs/Cargo.toml +0 -54
- data/vendor/html-to-markdown-rs/README.md +0 -278
- data/vendor/html-to-markdown-rs/examples/basic.rs +0 -24
- data/vendor/html-to-markdown-rs/examples/table.rs +0 -25
- data/vendor/html-to-markdown-rs/examples/test_deser.rs +0 -12
- data/vendor/html-to-markdown-rs/examples/test_escape.rs +0 -58
- data/vendor/html-to-markdown-rs/examples/test_inline_formatting.rs +0 -113
- data/vendor/html-to-markdown-rs/examples/test_lists.rs +0 -39
- data/vendor/html-to-markdown-rs/examples/test_semantic_tags.rs +0 -89
- data/vendor/html-to-markdown-rs/examples/test_tables.rs +0 -100
- data/vendor/html-to-markdown-rs/examples/test_task_lists.rs +0 -61
- data/vendor/html-to-markdown-rs/examples/test_whitespace.rs +0 -34
- data/vendor/html-to-markdown-rs/src/convert_api.rs +0 -349
- data/vendor/html-to-markdown-rs/src/converter/block/blockquote.rs +0 -178
- data/vendor/html-to-markdown-rs/src/converter/block/container.rs +0 -114
- data/vendor/html-to-markdown-rs/src/converter/block/div.rs +0 -149
- data/vendor/html-to-markdown-rs/src/converter/block/heading.rs +0 -428
- data/vendor/html-to-markdown-rs/src/converter/block/horizontal_rule.rs +0 -103
- data/vendor/html-to-markdown-rs/src/converter/block/line_break.rs +0 -89
- data/vendor/html-to-markdown-rs/src/converter/block/mod.rs +0 -10
- data/vendor/html-to-markdown-rs/src/converter/block/paragraph.rs +0 -140
- data/vendor/html-to-markdown-rs/src/converter/block/preformatted.rs +0 -298
- data/vendor/html-to-markdown-rs/src/converter/block/table/builder.rs +0 -453
- data/vendor/html-to-markdown-rs/src/converter/block/table/caption.rs +0 -44
- data/vendor/html-to-markdown-rs/src/converter/block/table/cell.rs +0 -276
- data/vendor/html-to-markdown-rs/src/converter/block/table/cells.rs +0 -336
- data/vendor/html-to-markdown-rs/src/converter/block/table/layout.rs +0 -58
- data/vendor/html-to-markdown-rs/src/converter/block/table/mod.rs +0 -266
- data/vendor/html-to-markdown-rs/src/converter/block/table/scanner.rs +0 -146
- data/vendor/html-to-markdown-rs/src/converter/block/table/utils.rs +0 -34
- data/vendor/html-to-markdown-rs/src/converter/block/unknown.rs +0 -138
- data/vendor/html-to-markdown-rs/src/converter/context.rs +0 -208
- data/vendor/html-to-markdown-rs/src/converter/dom_context.rs +0 -337
- data/vendor/html-to-markdown-rs/src/converter/form/elements.rs +0 -770
- data/vendor/html-to-markdown-rs/src/converter/form/mod.rs +0 -82
- data/vendor/html-to-markdown-rs/src/converter/format/djot.rs +0 -64
- data/vendor/html-to-markdown-rs/src/converter/format/markdown.rs +0 -59
- data/vendor/html-to-markdown-rs/src/converter/format/mod.rs +0 -43
- data/vendor/html-to-markdown-rs/src/converter/handlers/blockquote.rs +0 -173
- data/vendor/html-to-markdown-rs/src/converter/handlers/code_block.rs +0 -434
- data/vendor/html-to-markdown-rs/src/converter/handlers/graphic.rs +0 -234
- data/vendor/html-to-markdown-rs/src/converter/handlers/image.rs +0 -282
- data/vendor/html-to-markdown-rs/src/converter/handlers/link.rs +0 -316
- data/vendor/html-to-markdown-rs/src/converter/handlers/mod.rs +0 -26
- data/vendor/html-to-markdown-rs/src/converter/inline/code.rs +0 -306
- data/vendor/html-to-markdown-rs/src/converter/inline/emphasis.rs +0 -345
- data/vendor/html-to-markdown-rs/src/converter/inline/link.rs +0 -428
- data/vendor/html-to-markdown-rs/src/converter/inline/mod.rs +0 -237
- data/vendor/html-to-markdown-rs/src/converter/inline/ruby.rs +0 -337
- data/vendor/html-to-markdown-rs/src/converter/inline/semantic/marks.rs +0 -566
- data/vendor/html-to-markdown-rs/src/converter/inline/semantic/mod.rs +0 -86
- data/vendor/html-to-markdown-rs/src/converter/inline/semantic/typography.rs +0 -558
- data/vendor/html-to-markdown-rs/src/converter/list/definition.rs +0 -232
- data/vendor/html-to-markdown-rs/src/converter/list/item.rs +0 -332
- data/vendor/html-to-markdown-rs/src/converter/list/mod.rs +0 -70
- data/vendor/html-to-markdown-rs/src/converter/list/ordered.rs +0 -201
- data/vendor/html-to-markdown-rs/src/converter/list/unordered.rs +0 -195
- data/vendor/html-to-markdown-rs/src/converter/list/utils.rs +0 -314
- data/vendor/html-to-markdown-rs/src/converter/main.rs +0 -710
- data/vendor/html-to-markdown-rs/src/converter/main_helpers.rs +0 -452
- data/vendor/html-to-markdown-rs/src/converter/media/embedded.rs +0 -393
- data/vendor/html-to-markdown-rs/src/converter/media/graphic.rs +0 -4
- data/vendor/html-to-markdown-rs/src/converter/media/image.rs +0 -183
- data/vendor/html-to-markdown-rs/src/converter/media/mod.rs +0 -87
- data/vendor/html-to-markdown-rs/src/converter/media/svg.rs +0 -280
- data/vendor/html-to-markdown-rs/src/converter/metadata.rs +0 -220
- data/vendor/html-to-markdown-rs/src/converter/mod.rs +0 -156
- data/vendor/html-to-markdown-rs/src/converter/plain_text.rs +0 -516
- data/vendor/html-to-markdown-rs/src/converter/preprocessing_helpers.rs +0 -201
- data/vendor/html-to-markdown-rs/src/converter/reference_collector.rs +0 -69
- data/vendor/html-to-markdown-rs/src/converter/semantic/attributes.rs +0 -269
- data/vendor/html-to-markdown-rs/src/converter/semantic/definition_list.rs +0 -266
- data/vendor/html-to-markdown-rs/src/converter/semantic/figure.rs +0 -391
- data/vendor/html-to-markdown-rs/src/converter/semantic/mod.rs +0 -112
- data/vendor/html-to-markdown-rs/src/converter/semantic/sectioning.rs +0 -85
- data/vendor/html-to-markdown-rs/src/converter/semantic/summary.rs +0 -324
- data/vendor/html-to-markdown-rs/src/converter/text/mod.rs +0 -8
- data/vendor/html-to-markdown-rs/src/converter/text/processing.rs +0 -56
- data/vendor/html-to-markdown-rs/src/converter/text_node.rs +0 -269
- data/vendor/html-to-markdown-rs/src/converter/utility/attributes.rs +0 -151
- data/vendor/html-to-markdown-rs/src/converter/utility/caching.rs +0 -74
- data/vendor/html-to-markdown-rs/src/converter/utility/content.rs +0 -271
- data/vendor/html-to-markdown-rs/src/converter/utility/mod.rs +0 -17
- data/vendor/html-to-markdown-rs/src/converter/utility/preprocessing.rs +0 -1002
- data/vendor/html-to-markdown-rs/src/converter/utility/serialization.rs +0 -126
- data/vendor/html-to-markdown-rs/src/converter/utility/siblings.rs +0 -97
- data/vendor/html-to-markdown-rs/src/converter/visitor_hooks.rs +0 -189
- data/vendor/html-to-markdown-rs/src/error.rs +0 -43
- data/vendor/html-to-markdown-rs/src/exports.rs +0 -24
- data/vendor/html-to-markdown-rs/src/inline_images.rs +0 -336
- data/vendor/html-to-markdown-rs/src/lib.rs +0 -139
- data/vendor/html-to-markdown-rs/src/metadata/collector.rs +0 -457
- data/vendor/html-to-markdown-rs/src/metadata/config.rs +0 -394
- data/vendor/html-to-markdown-rs/src/metadata/extraction.rs +0 -398
- data/vendor/html-to-markdown-rs/src/metadata/mod.rs +0 -288
- data/vendor/html-to-markdown-rs/src/metadata/types.rs +0 -477
- data/vendor/html-to-markdown-rs/src/options/conversion.rs +0 -559
- data/vendor/html-to-markdown-rs/src/options/inline_image.rs +0 -111
- data/vendor/html-to-markdown-rs/src/options/mod.rs +0 -20
- data/vendor/html-to-markdown-rs/src/options/preprocessing.rs +0 -201
- data/vendor/html-to-markdown-rs/src/options/validation.rs +0 -416
- data/vendor/html-to-markdown-rs/src/prelude.rs +0 -1
- data/vendor/html-to-markdown-rs/src/rcdom.rs +0 -487
- data/vendor/html-to-markdown-rs/src/text.rs +0 -358
- data/vendor/html-to-markdown-rs/src/types/document.rs +0 -191
- data/vendor/html-to-markdown-rs/src/types/mod.rs +0 -17
- data/vendor/html-to-markdown-rs/src/types/result.rs +0 -54
- data/vendor/html-to-markdown-rs/src/types/structure_builder.rs +0 -791
- data/vendor/html-to-markdown-rs/src/types/structure_collector.rs +0 -483
- data/vendor/html-to-markdown-rs/src/types/tables.rs +0 -52
- data/vendor/html-to-markdown-rs/src/types/warnings.rs +0 -33
- data/vendor/html-to-markdown-rs/src/validation.rs +0 -158
- data/vendor/html-to-markdown-rs/src/visitor/default_impl.rs +0 -63
- data/vendor/html-to-markdown-rs/src/visitor/mod.rs +0 -41
- data/vendor/html-to-markdown-rs/src/visitor/traits.rs +0 -370
- data/vendor/html-to-markdown-rs/src/visitor/types.rs +0 -319
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/callbacks/mod.rs +0 -1
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/content.rs +0 -126
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/mod.rs +0 -27
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/state.rs +0 -110
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/traversal.rs +0 -250
- data/vendor/html-to-markdown-rs/src/visitor_helpers.rs +0 -597
- data/vendor/html-to-markdown-rs/src/wrapper/sync.rs +0 -413
- data/vendor/html-to-markdown-rs/src/wrapper/utils.rs +0 -290
- data/vendor/html-to-markdown-rs/src/wrapper.rs +0 -9
- data/vendor/html-to-markdown-rs/tests/br_in_inline_test.rs +0 -87
- data/vendor/html-to-markdown-rs/tests/commonmark_compliance_test.rs +0 -297
- data/vendor/html-to-markdown-rs/tests/djot_output_test.rs +0 -153
- data/vendor/html-to-markdown-rs/tests/exclude_selectors_test.rs +0 -132
- data/vendor/html-to-markdown-rs/tests/integration_test.rs +0 -631
- data/vendor/html-to-markdown-rs/tests/issue_121_regressions.rs +0 -49
- data/vendor/html-to-markdown-rs/tests/issue_127_regressions.rs +0 -58
- data/vendor/html-to-markdown-rs/tests/issue_128_regressions.rs +0 -17
- data/vendor/html-to-markdown-rs/tests/issue_131_regressions.rs +0 -41
- data/vendor/html-to-markdown-rs/tests/issue_134_regressions.rs +0 -40
- data/vendor/html-to-markdown-rs/tests/issue_139_regressions.rs +0 -26
- data/vendor/html-to-markdown-rs/tests/issue_140_regressions.rs +0 -185
- data/vendor/html-to-markdown-rs/tests/issue_143_regressions.rs +0 -100
- data/vendor/html-to-markdown-rs/tests/issue_145_regressions.rs +0 -133
- data/vendor/html-to-markdown-rs/tests/issue_146_regressions.rs +0 -144
- data/vendor/html-to-markdown-rs/tests/issue_176_regressions.rs +0 -62
- data/vendor/html-to-markdown-rs/tests/issue_190_regressions.rs +0 -128
- data/vendor/html-to-markdown-rs/tests/issue_199_regressions.rs +0 -20
- data/vendor/html-to-markdown-rs/tests/issue_200_regressions.rs +0 -62
- data/vendor/html-to-markdown-rs/tests/issue_212_regressions.rs +0 -68
- data/vendor/html-to-markdown-rs/tests/issue_216_217_regressions.rs +0 -87
- data/vendor/html-to-markdown-rs/tests/issue_336_regressions.rs +0 -74
- data/vendor/html-to-markdown-rs/tests/issue_339_regressions.rs +0 -92
- data/vendor/html-to-markdown-rs/tests/issue_347_regressions.rs +0 -154
- data/vendor/html-to-markdown-rs/tests/issue_348_visitor_plain.rs +0 -93
- data/vendor/html-to-markdown-rs/tests/json_ld_script_extraction.rs +0 -44
- data/vendor/html-to-markdown-rs/tests/lists_test.rs +0 -199
- data/vendor/html-to-markdown-rs/tests/plain_output_test.rs +0 -273
- data/vendor/html-to-markdown-rs/tests/preprocessing_tests.rs +0 -61
- data/vendor/html-to-markdown-rs/tests/reference_links_test.rs +0 -169
- data/vendor/html-to-markdown-rs/tests/sectioning_elements_test.rs +0 -137
- data/vendor/html-to-markdown-rs/tests/skip_images_test.rs +0 -522
- data/vendor/html-to-markdown-rs/tests/tables_test.rs +0 -743
- data/vendor/html-to-markdown-rs/tests/test_custom_elements.rs +0 -41
- data/vendor/html-to-markdown-rs/tests/test_issue_187.rs +0 -204
- data/vendor/html-to-markdown-rs/tests/test_issue_218.rs +0 -68
- data/vendor/html-to-markdown-rs/tests/test_issue_277.rs +0 -77
- data/vendor/html-to-markdown-rs/tests/test_max_depth.rs +0 -82
- data/vendor/html-to-markdown-rs/tests/test_nested_simple.rs +0 -45
- data/vendor/html-to-markdown-rs/tests/test_script_style_stripping.rs +0 -396
- data/vendor/html-to-markdown-rs/tests/test_spa_bisect.rs +0 -34
- data/vendor/html-to-markdown-rs/tests/visitor_code_integration_test.rs +0 -121
- data/vendor/html-to-markdown-rs/tests/visitor_integration_test.rs +0 -1190
- data/vendor/html-to-markdown-rs/tests/xml_tables_test.rs +0 -372
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
//! Synchronous text wrapping for Markdown output.
|
|
2
|
-
|
|
3
|
-
use super::utils::{
|
|
4
|
-
is_heading, is_list_like, is_numbered_list, parse_blockquote_line, parse_list_item, wrap_blockquote_paragraph,
|
|
5
|
-
wrap_line, wrap_list_item,
|
|
6
|
-
};
|
|
7
|
-
use crate::options::ConversionOptions;
|
|
8
|
-
|
|
9
|
-
/// Wrap text at specified width while preserving Markdown formatting.
|
|
10
|
-
///
|
|
11
|
-
/// This function wraps paragraphs of text at the specified width, but:
|
|
12
|
-
/// - Does not break long words
|
|
13
|
-
/// - Does not break on hyphens
|
|
14
|
-
/// - Preserves Markdown formatting (links, bold, etc.)
|
|
15
|
-
/// - Only wraps paragraph content, not headers, lists, code blocks, etc.
|
|
16
|
-
#[must_use]
|
|
17
|
-
#[allow(clippy::too_many_lines)]
|
|
18
|
-
pub fn wrap_markdown(markdown: &str, options: &ConversionOptions) -> String {
|
|
19
|
-
if !options.wrap {
|
|
20
|
-
return markdown.to_string();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let mut result = String::with_capacity(markdown.len());
|
|
24
|
-
let mut in_code_block = false;
|
|
25
|
-
let mut in_paragraph = false;
|
|
26
|
-
let mut paragraph_buffer = String::new();
|
|
27
|
-
let mut in_blockquote_paragraph = false;
|
|
28
|
-
let mut blockquote_prefix = String::new();
|
|
29
|
-
let mut blockquote_buffer = String::new();
|
|
30
|
-
|
|
31
|
-
for line in markdown.lines() {
|
|
32
|
-
let trimmed = line.trim_start();
|
|
33
|
-
let is_code_fence = trimmed.starts_with("```");
|
|
34
|
-
let is_indented_code = line.starts_with(" ")
|
|
35
|
-
&& !is_list_like(trimmed)
|
|
36
|
-
&& !is_numbered_list(trimmed)
|
|
37
|
-
&& !is_heading(trimmed)
|
|
38
|
-
&& !trimmed.starts_with('>')
|
|
39
|
-
&& !trimmed.starts_with('|');
|
|
40
|
-
|
|
41
|
-
if is_code_fence || is_indented_code {
|
|
42
|
-
if in_paragraph && !paragraph_buffer.is_empty() {
|
|
43
|
-
result.push_str(&wrap_line(¶graph_buffer, options.wrap_width));
|
|
44
|
-
result.push_str("\n\n");
|
|
45
|
-
paragraph_buffer.clear();
|
|
46
|
-
in_paragraph = false;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if is_code_fence {
|
|
50
|
-
in_code_block = !in_code_block;
|
|
51
|
-
}
|
|
52
|
-
result.push_str(line);
|
|
53
|
-
result.push('\n');
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if in_code_block {
|
|
58
|
-
result.push_str(line);
|
|
59
|
-
result.push('\n');
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if let Some((prefix, content)) = parse_blockquote_line(line) {
|
|
64
|
-
if in_paragraph && !paragraph_buffer.is_empty() {
|
|
65
|
-
result.push_str(&wrap_line(¶graph_buffer, options.wrap_width));
|
|
66
|
-
result.push_str("\n\n");
|
|
67
|
-
paragraph_buffer.clear();
|
|
68
|
-
in_paragraph = false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let mut normalized_prefix = prefix;
|
|
72
|
-
if !normalized_prefix.ends_with(' ') {
|
|
73
|
-
normalized_prefix.push(' ');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if content.is_empty() {
|
|
77
|
-
if in_blockquote_paragraph && !blockquote_buffer.is_empty() {
|
|
78
|
-
result.push_str(&wrap_blockquote_paragraph(
|
|
79
|
-
&blockquote_prefix,
|
|
80
|
-
&blockquote_buffer,
|
|
81
|
-
options.wrap_width,
|
|
82
|
-
));
|
|
83
|
-
result.push('\n');
|
|
84
|
-
blockquote_buffer.clear();
|
|
85
|
-
in_blockquote_paragraph = false;
|
|
86
|
-
}
|
|
87
|
-
result.push_str(normalized_prefix.trim_end());
|
|
88
|
-
result.push('\n');
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if in_blockquote_paragraph && normalized_prefix != blockquote_prefix {
|
|
93
|
-
result.push_str(&wrap_blockquote_paragraph(
|
|
94
|
-
&blockquote_prefix,
|
|
95
|
-
&blockquote_buffer,
|
|
96
|
-
options.wrap_width,
|
|
97
|
-
));
|
|
98
|
-
result.push('\n');
|
|
99
|
-
blockquote_buffer.clear();
|
|
100
|
-
in_blockquote_paragraph = false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if in_blockquote_paragraph {
|
|
104
|
-
blockquote_buffer.push(' ');
|
|
105
|
-
blockquote_buffer.push_str(&content);
|
|
106
|
-
} else {
|
|
107
|
-
blockquote_prefix = normalized_prefix;
|
|
108
|
-
blockquote_buffer.push_str(&content);
|
|
109
|
-
in_blockquote_paragraph = true;
|
|
110
|
-
}
|
|
111
|
-
continue;
|
|
112
|
-
} else if in_blockquote_paragraph && !blockquote_buffer.is_empty() {
|
|
113
|
-
result.push_str(&wrap_blockquote_paragraph(
|
|
114
|
-
&blockquote_prefix,
|
|
115
|
-
&blockquote_buffer,
|
|
116
|
-
options.wrap_width,
|
|
117
|
-
));
|
|
118
|
-
result.push('\n');
|
|
119
|
-
blockquote_buffer.clear();
|
|
120
|
-
in_blockquote_paragraph = false;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if let Some((indent, marker, content)) = parse_list_item(line) {
|
|
124
|
-
if in_paragraph && !paragraph_buffer.is_empty() {
|
|
125
|
-
result.push_str(&wrap_line(¶graph_buffer, options.wrap_width));
|
|
126
|
-
result.push_str("\n\n");
|
|
127
|
-
paragraph_buffer.clear();
|
|
128
|
-
in_paragraph = false;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
result.push_str(&wrap_list_item(&indent, &marker, &content, options.wrap_width));
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let is_structural =
|
|
136
|
-
is_heading(trimmed) || trimmed.starts_with('>') || trimmed.starts_with('|') || trimmed.starts_with('=');
|
|
137
|
-
|
|
138
|
-
if is_structural {
|
|
139
|
-
if in_paragraph && !paragraph_buffer.is_empty() {
|
|
140
|
-
result.push_str(&wrap_line(¶graph_buffer, options.wrap_width));
|
|
141
|
-
result.push_str("\n\n");
|
|
142
|
-
paragraph_buffer.clear();
|
|
143
|
-
in_paragraph = false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
result.push_str(line);
|
|
147
|
-
result.push('\n');
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if line.trim().is_empty() {
|
|
152
|
-
if in_paragraph && !paragraph_buffer.is_empty() {
|
|
153
|
-
result.push_str(&wrap_line(¶graph_buffer, options.wrap_width));
|
|
154
|
-
result.push_str("\n\n");
|
|
155
|
-
paragraph_buffer.clear();
|
|
156
|
-
in_paragraph = false;
|
|
157
|
-
} else if !in_paragraph {
|
|
158
|
-
result.push('\n');
|
|
159
|
-
}
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if in_paragraph {
|
|
164
|
-
paragraph_buffer.push(' ');
|
|
165
|
-
}
|
|
166
|
-
paragraph_buffer.push_str(line.trim());
|
|
167
|
-
in_paragraph = true;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if in_blockquote_paragraph && !blockquote_buffer.is_empty() {
|
|
171
|
-
result.push_str(&wrap_blockquote_paragraph(
|
|
172
|
-
&blockquote_prefix,
|
|
173
|
-
&blockquote_buffer,
|
|
174
|
-
options.wrap_width,
|
|
175
|
-
));
|
|
176
|
-
result.push('\n');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if in_paragraph && !paragraph_buffer.is_empty() {
|
|
180
|
-
result.push_str(&wrap_line(¶graph_buffer, options.wrap_width));
|
|
181
|
-
result.push_str("\n\n");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
result
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
#[cfg(test)]
|
|
188
|
-
mod tests {
|
|
189
|
-
use super::*;
|
|
190
|
-
|
|
191
|
-
#[test]
|
|
192
|
-
fn test_wrap_markdown_disabled() {
|
|
193
|
-
let markdown = "This is a very long line that would normally be wrapped at 40 characters";
|
|
194
|
-
let options = ConversionOptions {
|
|
195
|
-
wrap: false,
|
|
196
|
-
..Default::default()
|
|
197
|
-
};
|
|
198
|
-
let result = wrap_markdown(markdown, &options);
|
|
199
|
-
assert_eq!(result, markdown);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
#[test]
|
|
203
|
-
fn test_wrap_markdown_paragraph() {
|
|
204
|
-
let markdown = "This is a very long line that would normally be wrapped at 40 characters\n\n";
|
|
205
|
-
let options = ConversionOptions {
|
|
206
|
-
wrap: true,
|
|
207
|
-
wrap_width: 40,
|
|
208
|
-
..Default::default()
|
|
209
|
-
};
|
|
210
|
-
let result = wrap_markdown(markdown, &options);
|
|
211
|
-
assert!(result.lines().all(|line| line.len() <= 40 || line.trim().is_empty()));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
#[test]
|
|
215
|
-
fn test_wrap_markdown_blockquote_paragraph() {
|
|
216
|
-
let markdown = "> This is a very long blockquote line that should wrap at 30 characters\n";
|
|
217
|
-
let options = ConversionOptions {
|
|
218
|
-
wrap: true,
|
|
219
|
-
wrap_width: 30,
|
|
220
|
-
..Default::default()
|
|
221
|
-
};
|
|
222
|
-
let result = wrap_markdown(markdown, &options);
|
|
223
|
-
assert!(
|
|
224
|
-
result.lines().all(|line| line.len() <= 30 || line.trim().is_empty()),
|
|
225
|
-
"Some lines exceed wrap width. Got: {}",
|
|
226
|
-
result
|
|
227
|
-
);
|
|
228
|
-
assert!(
|
|
229
|
-
result.contains("> This is a very"),
|
|
230
|
-
"Missing expected wrapped content. Got: {}",
|
|
231
|
-
result
|
|
232
|
-
);
|
|
233
|
-
assert!(
|
|
234
|
-
result.lines().filter(|l| l.starts_with("> ")).count() >= 2,
|
|
235
|
-
"Expected multiple wrapped blockquote lines. Got: {}",
|
|
236
|
-
result
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
#[test]
|
|
241
|
-
fn test_wrap_markdown_preserves_code() {
|
|
242
|
-
let markdown = "```\nThis is a very long line in a code block that should not be wrapped\n```\n";
|
|
243
|
-
let options = ConversionOptions {
|
|
244
|
-
wrap: true,
|
|
245
|
-
wrap_width: 40,
|
|
246
|
-
..Default::default()
|
|
247
|
-
};
|
|
248
|
-
let result = wrap_markdown(markdown, &options);
|
|
249
|
-
assert!(result.contains("This is a very long line in a code block that should not be wrapped"));
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
#[test]
|
|
253
|
-
fn test_wrap_markdown_preserves_headings() {
|
|
254
|
-
let markdown = "# This is a very long heading that should not be wrapped even if it exceeds the width\n\n";
|
|
255
|
-
let options = ConversionOptions {
|
|
256
|
-
wrap: true,
|
|
257
|
-
wrap_width: 40,
|
|
258
|
-
..Default::default()
|
|
259
|
-
};
|
|
260
|
-
let result = wrap_markdown(markdown, &options);
|
|
261
|
-
assert!(
|
|
262
|
-
result.contains("# This is a very long heading that should not be wrapped even if it exceeds the width")
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
#[test]
|
|
267
|
-
fn wrap_markdown_wraps_long_list_items() {
|
|
268
|
-
let markdown = "- This is a very long list item that should definitely be wrapped when it exceeds the specified wrap width\n- Short item\n";
|
|
269
|
-
let options = ConversionOptions {
|
|
270
|
-
wrap: true,
|
|
271
|
-
wrap_width: 60,
|
|
272
|
-
..Default::default()
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
let result = wrap_markdown(markdown, &options);
|
|
276
|
-
|
|
277
|
-
assert!(
|
|
278
|
-
result.contains("- This is a very long list item that should definitely be\n wrapped"),
|
|
279
|
-
"First list item not properly wrapped. Got: {}",
|
|
280
|
-
result
|
|
281
|
-
);
|
|
282
|
-
assert!(
|
|
283
|
-
result.contains("- Short item"),
|
|
284
|
-
"Short list item incorrectly modified. Got: {}",
|
|
285
|
-
result
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
#[test]
|
|
290
|
-
fn wrap_markdown_wraps_ordered_lists() {
|
|
291
|
-
let markdown = "1. This is a numbered list item with a very long text that should be wrapped at the specified width\n2. Short\n";
|
|
292
|
-
let options = ConversionOptions {
|
|
293
|
-
wrap: true,
|
|
294
|
-
wrap_width: 60,
|
|
295
|
-
..Default::default()
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
let result = wrap_markdown(markdown, &options);
|
|
299
|
-
|
|
300
|
-
assert!(
|
|
301
|
-
result.lines().all(|line| line.len() <= 60 || line.trim().is_empty()),
|
|
302
|
-
"Some lines exceed wrap width. Got: {}",
|
|
303
|
-
result
|
|
304
|
-
);
|
|
305
|
-
assert!(result.contains("1."), "Lost ordered list marker. Got: {}", result);
|
|
306
|
-
assert!(
|
|
307
|
-
result.contains("2."),
|
|
308
|
-
"Lost second ordered list marker. Got: {}",
|
|
309
|
-
result
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
#[test]
|
|
314
|
-
fn wrap_markdown_preserves_nested_list_structure() {
|
|
315
|
-
let markdown = "- Item one with some additional text that will need to be wrapped across multiple lines\n - Nested item with long text that also needs wrapping at the specified width\n - Short nested\n";
|
|
316
|
-
let options = ConversionOptions {
|
|
317
|
-
wrap: true,
|
|
318
|
-
wrap_width: 50,
|
|
319
|
-
..Default::default()
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
let result = wrap_markdown(markdown, &options);
|
|
323
|
-
|
|
324
|
-
assert!(result.contains("- Item"), "Lost top-level list marker. Got: {}", result);
|
|
325
|
-
assert!(
|
|
326
|
-
result.contains(" - Nested"),
|
|
327
|
-
"Lost nested list structure. Got: {}",
|
|
328
|
-
result
|
|
329
|
-
);
|
|
330
|
-
assert!(
|
|
331
|
-
result.lines().all(|line| line.len() <= 50 || line.trim().is_empty()),
|
|
332
|
-
"Some lines exceed wrap width. Got: {}",
|
|
333
|
-
result
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
#[test]
|
|
338
|
-
fn wrap_markdown_handles_list_with_links() {
|
|
339
|
-
let markdown = "- [A](#a) with additional text that is long enough to require wrapping at the configured width\n - [B](#b) also has more content that needs wrapping\n - [C](#c)\n";
|
|
340
|
-
let options = ConversionOptions {
|
|
341
|
-
wrap: true,
|
|
342
|
-
wrap_width: 50,
|
|
343
|
-
..Default::default()
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
let result = wrap_markdown(markdown, &options);
|
|
347
|
-
|
|
348
|
-
assert!(result.contains("[A](#a)"), "Lost link in list. Got: {}", result);
|
|
349
|
-
assert!(result.contains("[B](#b)"), "Lost nested link. Got: {}", result);
|
|
350
|
-
assert!(result.contains("[C](#c)"), "Lost short nested link. Got: {}", result);
|
|
351
|
-
assert!(
|
|
352
|
-
result.contains("- [A](#a)"),
|
|
353
|
-
"Lost list structure with link. Got: {}",
|
|
354
|
-
result
|
|
355
|
-
);
|
|
356
|
-
assert!(
|
|
357
|
-
result.contains(" - [B](#b)"),
|
|
358
|
-
"Lost nested list structure. Got: {}",
|
|
359
|
-
result
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
#[test]
|
|
364
|
-
fn wrap_markdown_handles_empty_list_items() {
|
|
365
|
-
let markdown = "- \n- Item with text\n- \n";
|
|
366
|
-
let options = ConversionOptions {
|
|
367
|
-
wrap: true,
|
|
368
|
-
wrap_width: 40,
|
|
369
|
-
..Default::default()
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
let result = wrap_markdown(markdown, &options);
|
|
373
|
-
|
|
374
|
-
assert!(result.contains("- "), "Lost list markers. Got: {}", result);
|
|
375
|
-
assert!(result.contains("Item with text"), "Lost item text. Got: {}", result);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
#[test]
|
|
379
|
-
fn wrap_markdown_preserves_indented_lists_with_wrapping() {
|
|
380
|
-
let markdown = "- [A](#a) with some additional text that makes this line very long and should be wrapped\n - [B](#b)\n - [C](#c) with more text that is also quite long and needs wrapping\n";
|
|
381
|
-
let options = ConversionOptions {
|
|
382
|
-
wrap: true,
|
|
383
|
-
wrap_width: 50,
|
|
384
|
-
..Default::default()
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
let result = wrap_markdown(markdown, &options);
|
|
388
|
-
|
|
389
|
-
assert!(result.contains("- [A](#a)"), "Lost top-level link. Got: {}", result);
|
|
390
|
-
assert!(result.contains(" - [B](#b)"), "Lost nested link B. Got: {}", result);
|
|
391
|
-
assert!(result.contains(" - [C](#c)"), "Lost nested link C. Got: {}", result);
|
|
392
|
-
assert!(
|
|
393
|
-
result.lines().all(|line| line.len() <= 50),
|
|
394
|
-
"Some lines exceed wrap width:\n{}",
|
|
395
|
-
result
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
#[test]
|
|
400
|
-
fn wrap_markdown_does_not_wrap_link_only_items() {
|
|
401
|
-
let markdown = "- [A very long link label that would exceed wrap width](#a-very-long-link-label)\n - [Nested very long link label that would also exceed](#nested)\n";
|
|
402
|
-
let options = ConversionOptions {
|
|
403
|
-
wrap: true,
|
|
404
|
-
wrap_width: 30,
|
|
405
|
-
..Default::default()
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
let result = wrap_markdown(markdown, &options);
|
|
409
|
-
|
|
410
|
-
assert!(result.contains("- [A very long link label that would exceed wrap width](#a-very-long-link-label)"));
|
|
411
|
-
assert!(result.contains(" - [Nested very long link label that would also exceed](#nested)"));
|
|
412
|
-
}
|
|
413
|
-
}
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
//! Utility functions for text wrapping.
|
|
2
|
-
//!
|
|
3
|
-
//! This module contains helper functions for parsing and wrapping Markdown elements.
|
|
4
|
-
|
|
5
|
-
/// Parse a blockquote line into its prefix and content.
|
|
6
|
-
///
|
|
7
|
-
/// Returns Some((prefix, content)) if the line is a blockquote, None otherwise.
|
|
8
|
-
pub fn parse_blockquote_line(line: &str) -> Option<(String, String)> {
|
|
9
|
-
let trimmed = line.trim_start();
|
|
10
|
-
if !trimmed.starts_with('>') {
|
|
11
|
-
return None;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let indent_len = line.len() - trimmed.len();
|
|
15
|
-
let bytes = line.as_bytes();
|
|
16
|
-
let mut i = indent_len;
|
|
17
|
-
|
|
18
|
-
while i < bytes.len() {
|
|
19
|
-
if bytes[i] != b'>' {
|
|
20
|
-
break;
|
|
21
|
-
}
|
|
22
|
-
i += 1;
|
|
23
|
-
if i < bytes.len() && bytes[i] == b' ' {
|
|
24
|
-
i += 1;
|
|
25
|
-
}
|
|
26
|
-
while i + 1 < bytes.len() && bytes[i] == b' ' && bytes[i + 1] == b'>' {
|
|
27
|
-
i += 1;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let prefix = line[..i].to_string();
|
|
32
|
-
let content = line[i..].trim().to_string();
|
|
33
|
-
Some((prefix, content))
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/// Wrap a blockquote paragraph while preserving its prefix.
|
|
37
|
-
///
|
|
38
|
-
/// # Arguments
|
|
39
|
-
/// - `prefix`: The blockquote prefix (e.g., "> " or "> > ")
|
|
40
|
-
/// - `content`: The text content to wrap
|
|
41
|
-
/// - `width`: The maximum line width
|
|
42
|
-
pub fn wrap_blockquote_paragraph(prefix: &str, content: &str, width: usize) -> String {
|
|
43
|
-
let prefix_len = prefix.len();
|
|
44
|
-
let inner_width = if width > prefix_len { width - prefix_len } else { 1 };
|
|
45
|
-
|
|
46
|
-
let wrapped = wrap_line(content, inner_width);
|
|
47
|
-
let mut out = String::new();
|
|
48
|
-
for (idx, part) in wrapped.split('\n').enumerate() {
|
|
49
|
-
if idx > 0 {
|
|
50
|
-
out.push('\n');
|
|
51
|
-
}
|
|
52
|
-
out.push_str(prefix);
|
|
53
|
-
out.push_str(part);
|
|
54
|
-
}
|
|
55
|
-
out
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/// Check if a line looks like an unordered list item (-, *, or +).
|
|
59
|
-
pub fn is_list_like(trimmed: &str) -> bool {
|
|
60
|
-
matches!(trimmed.chars().next(), Some('-' | '*' | '+'))
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/// Check if a line is a numbered list item.
|
|
64
|
-
pub fn is_numbered_list(trimmed: &str) -> bool {
|
|
65
|
-
let token = trimmed.split_whitespace().next().unwrap_or("");
|
|
66
|
-
if token.is_empty() || !(token.ends_with('.') || token.ends_with(')')) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let digits = token.trim_end_matches(['.', ')']);
|
|
71
|
-
!digits.is_empty() && digits.chars().all(|c| c.is_ascii_digit())
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/// Check if a line is a Markdown heading.
|
|
75
|
-
pub fn is_heading(trimmed: &str) -> bool {
|
|
76
|
-
trimmed.starts_with('#')
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/// Parse a list item into its components: (indent, marker, content)
|
|
80
|
-
///
|
|
81
|
-
/// Returns Some((indent, marker, content)) if the line is a valid list item,
|
|
82
|
-
/// None otherwise.
|
|
83
|
-
///
|
|
84
|
-
/// Examples:
|
|
85
|
-
/// - "- text" -> ("", "- ", "text")
|
|
86
|
-
/// - " - text" -> (" ", "- ", "text")
|
|
87
|
-
/// - "1. text" -> ("", "1. ", "text")
|
|
88
|
-
/// - " 42) text" -> (" ", "42) ", "text")
|
|
89
|
-
pub fn parse_list_item(line: &str) -> Option<(String, String, String)> {
|
|
90
|
-
let trimmed = line.trim_start();
|
|
91
|
-
let indent = &line[..line.len() - trimmed.len()];
|
|
92
|
-
|
|
93
|
-
if let Some(rest) = trimmed.strip_prefix('-') {
|
|
94
|
-
if rest.starts_with(' ') || rest.is_empty() {
|
|
95
|
-
return Some((indent.to_string(), "- ".to_string(), rest.trim_start().to_string()));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if let Some(rest) = trimmed.strip_prefix('*') {
|
|
99
|
-
if rest.starts_with(' ') || rest.is_empty() {
|
|
100
|
-
return Some((indent.to_string(), "* ".to_string(), rest.trim_start().to_string()));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if let Some(rest) = trimmed.strip_prefix('+') {
|
|
104
|
-
if rest.starts_with(' ') || rest.is_empty() {
|
|
105
|
-
return Some((indent.to_string(), "+ ".to_string(), rest.trim_start().to_string()));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let first_token = trimmed.split_whitespace().next()?;
|
|
110
|
-
if first_token.ends_with('.') || first_token.ends_with(')') {
|
|
111
|
-
let digits = first_token.trim_end_matches(['.', ')']);
|
|
112
|
-
if !digits.is_empty() && digits.chars().all(|c| c.is_ascii_digit()) {
|
|
113
|
-
let marker_len = first_token.len();
|
|
114
|
-
let rest = trimmed[marker_len..].trim_start();
|
|
115
|
-
return Some((
|
|
116
|
-
indent.to_string(),
|
|
117
|
-
trimmed[..marker_len].to_string() + " ",
|
|
118
|
-
rest.to_string(),
|
|
119
|
-
));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
None
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/// Check if content is a single inline link (e.g., "[text](#anchor)").
|
|
127
|
-
pub fn is_single_inline_link(content: &str) -> bool {
|
|
128
|
-
let trimmed = content.trim();
|
|
129
|
-
if !(trimmed.starts_with('[') && trimmed.ends_with(')')) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let Some(mid) = trimmed.find("](") else {
|
|
134
|
-
return false;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
let url_part = &trimmed[mid + 2..trimmed.len() - 1];
|
|
138
|
-
if url_part.chars().any(char::is_whitespace) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
!trimmed[mid + 2..].contains("](")
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/// Wrap a single line of text at the specified width.
|
|
146
|
-
///
|
|
147
|
-
/// This function wraps text without breaking long words or on hyphens,
|
|
148
|
-
/// similar to Python's `textwrap.fill()` with `break_long_words=False` and `break_on_hyphens=False`.
|
|
149
|
-
pub fn wrap_line(text: &str, width: usize) -> String {
|
|
150
|
-
if text.len() <= width {
|
|
151
|
-
return text.to_string();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
let mut result = String::new();
|
|
155
|
-
let mut current_line = String::new();
|
|
156
|
-
let words: Vec<&str> = text.split_whitespace().collect();
|
|
157
|
-
|
|
158
|
-
for word in words {
|
|
159
|
-
if current_line.is_empty() {
|
|
160
|
-
current_line.push_str(word);
|
|
161
|
-
} else if current_line.len() + 1 + word.len() <= width {
|
|
162
|
-
current_line.push(' ');
|
|
163
|
-
current_line.push_str(word);
|
|
164
|
-
} else {
|
|
165
|
-
if !result.is_empty() {
|
|
166
|
-
result.push('\n');
|
|
167
|
-
}
|
|
168
|
-
result.push_str(¤t_line);
|
|
169
|
-
current_line.clear();
|
|
170
|
-
current_line.push_str(word);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if !current_line.is_empty() {
|
|
175
|
-
if !result.is_empty() {
|
|
176
|
-
result.push('\n');
|
|
177
|
-
}
|
|
178
|
-
result.push_str(¤t_line);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
result
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/// Wrap a list item while preserving its structure.
|
|
185
|
-
///
|
|
186
|
-
/// The first line of output will be: `<indent><marker><content_start>`
|
|
187
|
-
/// Continuation lines will be: `<indent><spaces_matching_marker><content_continued>`
|
|
188
|
-
///
|
|
189
|
-
/// # Arguments
|
|
190
|
-
/// - `indent`: The leading whitespace (for nested lists)
|
|
191
|
-
/// - `marker`: The list marker (e.g., "- ", "1. ")
|
|
192
|
-
/// - `content`: The text content after the marker
|
|
193
|
-
/// - `width`: The maximum line width
|
|
194
|
-
pub fn wrap_list_item(indent: &str, marker: &str, content: &str, width: usize) -> String {
|
|
195
|
-
if content.is_empty() {
|
|
196
|
-
return format!("{}{}\n", indent, marker.trim_end());
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if is_single_inline_link(content) {
|
|
200
|
-
return format!("{}{}{}\n", indent, marker, content.trim());
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
let full_marker = format!("{indent}{marker}");
|
|
204
|
-
let continuation_indent = format!("{}{}", indent, " ".repeat(marker.len()));
|
|
205
|
-
|
|
206
|
-
let first_line_prefix_len = full_marker.len();
|
|
207
|
-
let first_line_width = if width > first_line_prefix_len {
|
|
208
|
-
width - first_line_prefix_len
|
|
209
|
-
} else {
|
|
210
|
-
width
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
let cont_line_prefix_len = continuation_indent.len();
|
|
214
|
-
let cont_line_width = if width > cont_line_prefix_len {
|
|
215
|
-
width - cont_line_prefix_len
|
|
216
|
-
} else {
|
|
217
|
-
width
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
let words: Vec<&str> = content.split_whitespace().collect();
|
|
221
|
-
if words.is_empty() {
|
|
222
|
-
return format!("{}\n", full_marker.trim_end());
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
let mut result = String::new();
|
|
226
|
-
let mut current_line = String::new();
|
|
227
|
-
let mut current_width = first_line_width;
|
|
228
|
-
let mut is_first_line = true;
|
|
229
|
-
|
|
230
|
-
for word in words {
|
|
231
|
-
let word_len = word.len();
|
|
232
|
-
let space_needed = usize::from(!current_line.is_empty());
|
|
233
|
-
|
|
234
|
-
if !current_line.is_empty() && current_line.len() + space_needed + word_len > current_width {
|
|
235
|
-
if is_first_line {
|
|
236
|
-
result.push_str(&full_marker);
|
|
237
|
-
is_first_line = false;
|
|
238
|
-
} else {
|
|
239
|
-
result.push_str(&continuation_indent);
|
|
240
|
-
}
|
|
241
|
-
result.push_str(¤t_line);
|
|
242
|
-
result.push('\n');
|
|
243
|
-
current_line.clear();
|
|
244
|
-
current_width = cont_line_width;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if !current_line.is_empty() {
|
|
248
|
-
current_line.push(' ');
|
|
249
|
-
}
|
|
250
|
-
current_line.push_str(word);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if !current_line.is_empty() {
|
|
254
|
-
if is_first_line {
|
|
255
|
-
result.push_str(&full_marker);
|
|
256
|
-
} else {
|
|
257
|
-
result.push_str(&continuation_indent);
|
|
258
|
-
}
|
|
259
|
-
result.push_str(¤t_line);
|
|
260
|
-
result.push('\n');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
result
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
#[cfg(test)]
|
|
267
|
-
mod tests {
|
|
268
|
-
use super::*;
|
|
269
|
-
|
|
270
|
-
#[test]
|
|
271
|
-
fn test_wrap_line_short() {
|
|
272
|
-
let text = "Short text";
|
|
273
|
-
let wrapped = wrap_line(text, 80);
|
|
274
|
-
assert_eq!(wrapped, "Short text");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
#[test]
|
|
278
|
-
fn test_wrap_line_long() {
|
|
279
|
-
let text = "123456789 123456789";
|
|
280
|
-
let wrapped = wrap_line(text, 10);
|
|
281
|
-
assert_eq!(wrapped, "123456789\n123456789");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
#[test]
|
|
285
|
-
fn test_wrap_line_no_break_long_words() {
|
|
286
|
-
let text = "12345678901 12345";
|
|
287
|
-
let wrapped = wrap_line(text, 10);
|
|
288
|
-
assert_eq!(wrapped, "12345678901\n12345");
|
|
289
|
-
}
|
|
290
|
-
}
|