]> git.proxmox.com Git - rustc.git/blame - src/doc/book/tools/src/bin/link2print.rs
New upstream version 1.34.2+dfsg1
[rustc.git] / src / doc / book / tools / src / bin / link2print.rs
CommitLineData
9fa01778 1// FIXME: we have some long lines that could be refactored, but it's not a big deal.
13cf67c4
XL
2// ignore-tidy-linelength
3
9fa01778 4
13cf67c4
XL
5
6use std::collections::HashMap;
7use std::io;
8use std::io::{Read, Write};
9use regex::{Regex, Captures};
10
11fn main() {
13cf67c4
XL
12 write_md(parse_links(parse_references(read_md())));
13}
14
15fn read_md() -> String {
16 let mut buffer = String::new();
17 match io::stdin().read_to_string(&mut buffer) {
18 Ok(_) => buffer,
19 Err(error) => panic!(error),
20 }
21}
22
23fn write_md(output: String) {
24 write!(io::stdout(), "{}", output).unwrap();
25}
26
27fn parse_references(buffer: String) -> (String, HashMap<String, String>) {
28 let mut ref_map = HashMap::new();
9fa01778 29 // FIXME: currently doesn't handle "title" in following line.
13cf67c4 30 let re = Regex::new(r###"(?m)\n?^ {0,3}\[([^]]+)\]:[[:blank:]]*(.*)$"###).unwrap();
9fa01778 31 let output = re.replace_all(&buffer, |caps: &Captures<'_>| {
13cf67c4
XL
32 let key = caps.at(1).unwrap().to_owned().to_uppercase();
33 let val = caps.at(2).unwrap().to_owned();
34 if ref_map.insert(key, val).is_some() {
35 panic!("Did not expect markdown page to have duplicate reference");
36 }
37 "".to_string()
38 });
39 (output, ref_map)
40}
41
42fn parse_links((buffer, ref_map): (String, HashMap<String, String>)) -> String {
9fa01778 43 // FIXME: check which punctuation is allowed by spec.
13cf67c4
XL
44 let re = Regex::new(r###"(?:(?P<pre>(?:```(?:[^`]|`[^`])*`?\n```\n)|(?:[^[]`[^`\n]+[\n]?[^`\n]*`))|(?:\[(?P<name>[^]]+)\](?:(?:\([[:blank:]]*(?P<val>[^")]*[^ ])(?:[[:blank:]]*"[^"]*")?\))|(?:\[(?P<key>[^]]*)\]))?))"###).expect("could not create regex");
45 let error_code = Regex::new(r###"^E\d{4}$"###).expect("could not create regex");
9fa01778 46 let output = re.replace_all(&buffer, |caps: &Captures<'_>| {
13cf67c4
XL
47 match caps.name("pre") {
48 Some(pre_section) => format!("{}", pre_section.to_owned()),
49 None => {
50 let name = caps.name("name").expect("could not get name").to_owned();
51 // Really we should ignore text inside code blocks,
52 // this is a hack to not try to treat `#[derive()]`,
9fa01778 53 // `[profile]`, `[test]`, or `[E\d\d\d\d]` like a link.
13cf67c4
XL
54 if name.starts_with("derive(") ||
55 name.starts_with("profile") ||
56 name.starts_with("test") ||
57 error_code.is_match(&name) {
58 return name
59 }
60
61 let val = match caps.name("val") {
9fa01778 62 // `[name](link)`
13cf67c4
XL
63 Some(value) => value.to_owned(),
64 None => {
65 match caps.name("key") {
66 Some(key) => {
67 match key {
9fa01778 68 // `[name][]`
13cf67c4 69 "" => format!("{}", ref_map.get(&name.to_uppercase()).expect(&format!("could not find url for the link text `{}`", name))),
9fa01778 70 // `[name][reference]`
13cf67c4
XL
71 _ => format!("{}", ref_map.get(&key.to_uppercase()).expect(&format!("could not find url for the link text `{}`", key))),
72 }
73 }
9fa01778 74 // `[name]` as reference
13cf67c4
XL
75 None => format!("{}", ref_map.get(&name.to_uppercase()).expect(&format!("could not find url for the link text `{}`", name))),
76 }
77 }
78 };
79 format!("{} at *{}*", name, val)
80 }
81 }
82 });
83 output
84}
85
86#[cfg(test)]
87mod tests {
88 fn parse(source: String) -> String {
89 super::parse_links(super::parse_references(source))
90 }
91
92 #[test]
93 fn parses_inline_link() {
94 let source = r"This is a [link](http://google.com) that should be expanded".to_string();
95 let target = r"This is a link at *http://google.com* that should be expanded".to_string();
96 assert_eq!(parse(source), target);
97 }
98
99 #[test]
100 fn parses_multiline_links() {
101 let source = r"This is a [link](http://google.com) that
102should appear expanded. Another [location](/here/) and [another](http://gogogo)"
103 .to_string();
104 let target = r"This is a link at *http://google.com* that
105should appear expanded. Another location at */here/* and another at *http://gogogo*"
106 .to_string();
107 assert_eq!(parse(source), target);
108 }
109
110 #[test]
111 fn parses_reference() {
112 let source = r"This is a [link][theref].
113[theref]: http://example.com/foo
114more text"
115 .to_string();
116 let target = r"This is a link at *http://example.com/foo*.
117more text"
118 .to_string();
119 assert_eq!(parse(source), target);
120 }
121
122 #[test]
123 fn parses_implicit_link() {
124 let source = r"This is an [implicit][] link.
125[implicit]: /The Link/"
126 .to_string();
127 let target = r"This is an implicit at */The Link/* link.".to_string();
128 assert_eq!(parse(source), target);
129 }
130 #[test]
131 fn parses_refs_with_one_space_indentation() {
132 let source = r"This is a [link][ref]
133 [ref]: The link"
134 .to_string();
135 let target = r"This is a link at *The link*".to_string();
136 assert_eq!(parse(source), target);
137 }
138
139 #[test]
140 fn parses_refs_with_two_space_indentation() {
141 let source = r"This is a [link][ref]
142 [ref]: The link"
143 .to_string();
144 let target = r"This is a link at *The link*".to_string();
145 assert_eq!(parse(source), target);
146 }
147
148 #[test]
149 fn parses_refs_with_three_space_indentation() {
150 let source = r"This is a [link][ref]
151 [ref]: The link"
152 .to_string();
153 let target = r"This is a link at *The link*".to_string();
154 assert_eq!(parse(source), target);
155 }
156
157 #[test]
158 #[should_panic]
159 fn rejects_refs_with_four_space_indentation() {
160 let source = r"This is a [link][ref]
161 [ref]: The link"
162 .to_string();
163 let target = r"This is a link at *The link*".to_string();
164 assert_eq!(parse(source), target);
165 }
166
167 #[test]
168 fn ignores_optional_inline_title() {
169 let source = r###"This is a titled [link](http://example.com "My title")."###.to_string();
170 let target = r"This is a titled link at *http://example.com*.".to_string();
171 assert_eq!(parse(source), target);
172 }
173
174 #[test]
175 fn parses_title_with_puctuation() {
176 let source = r###"[link](http://example.com "It's Title")"###.to_string();
177 let target = r"link at *http://example.com*".to_string();
178 assert_eq!(parse(source), target);
179 }
180
181 #[test]
182 fn parses_name_with_punctuation() {
183 let source = r###"[I'm here](there)"###.to_string();
184 let target = r###"I'm here at *there*"###.to_string();
185 assert_eq!(parse(source), target);
186 }
187 #[test]
188 fn parses_name_with_utf8() {
189 let source = r###"[user’s forum](the user’s forum)"###.to_string();
190 let target = r###"user’s forum at *the user’s forum*"###.to_string();
191 assert_eq!(parse(source), target);
192 }
193
194
195 #[test]
196 fn parses_reference_with_punctuation() {
197 let source = r###"[link][the ref-ref]
198[the ref-ref]:http://example.com/ref-ref"###
199 .to_string();
200 let target = r###"link at *http://example.com/ref-ref*"###.to_string();
201 assert_eq!(parse(source), target);
202 }
203
204 #[test]
205 fn parses_reference_case_insensitively() {
206 let source = r"[link][Ref]
207[ref]: The reference"
208 .to_string();
209 let target = r"link at *The reference*".to_string();
210 assert_eq!(parse(source), target);
211 }
212 #[test]
213 fn parses_link_as_reference_when_reference_is_empty() {
214 let source = r"[link as reference][]
215[link as reference]: the actual reference"
216 .to_string();
217 let target = r"link as reference at *the actual reference*".to_string();
218 assert_eq!(parse(source), target);
219 }
220
221 #[test]
222 fn parses_link_without_reference_as_reference() {
223 let source = r"[link] is alone
224[link]: The contents"
225 .to_string();
226 let target = r"link at *The contents* is alone".to_string();
227 assert_eq!(parse(source), target);
228 }
229
230 #[test]
231 #[ignore]
232 fn parses_link_without_reference_as_reference_with_asterisks() {
233 let source = r"*[link]* is alone
234[link]: The contents"
235 .to_string();
236 let target = r"*link* at *The contents* is alone".to_string();
237 assert_eq!(parse(source), target);
238 }
239 #[test]
240 fn ignores_links_in_pre_sections() {
241 let source = r###"```toml
242[package]
243name = "hello_cargo"
244version = "0.1.0"
245authors = ["Your Name <you@example.com>"]
246
247[dependencies]
248```
249"###
250 .to_string();
251 let target = source.clone();
252 assert_eq!(parse(source), target);
253 }
254
255 #[test]
256 fn ignores_links_in_quoted_sections() {
257 let source = r###"do not change `[package]`."###.to_string();
258 let target = source.clone();
259 assert_eq!(parse(source), target);
260 }
261 #[test]
262 fn ignores_links_in_quoted_sections_containing_newlines() {
263 let source = r"do not change `this [package]
264is still here` [link](ref)"
265 .to_string();
266 let target = r"do not change `this [package]
267is still here` link at *ref*"
268 .to_string();
269 assert_eq!(parse(source), target);
270 }
271
272 #[test]
273 fn ignores_links_in_pre_sections_while_still_handling_links() {
274 let source = r###"```toml
275[package]
276name = "hello_cargo"
277version = "0.1.0"
278authors = ["Your Name <you@example.com>"]
279
280[dependencies]
281```
282Another [link]
283more text
284[link]: http://gohere
285"###
286 .to_string();
287 let target = r###"```toml
288[package]
289name = "hello_cargo"
290version = "0.1.0"
291authors = ["Your Name <you@example.com>"]
292
293[dependencies]
294```
295Another link at *http://gohere*
296more text
297"###
298 .to_string();
299 assert_eq!(parse(source), target);
300 }
301 #[test]
302 fn ignores_quotes_in_pre_sections() {
303 let source = r###"```bash
304$ cargo build
305 Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
306src/main.rs:23:21: 23:35 error: mismatched types [E0308]
307src/main.rs:23 match guess.cmp(&secret_number) {
308 ^~~~~~~~~~~~~~
309src/main.rs:23:21: 23:35 help: run `rustc --explain E0308` to see a detailed explanation
310src/main.rs:23:21: 23:35 note: expected type `&std::string::String`
311src/main.rs:23:21: 23:35 note: found type `&_`
312error: aborting due to previous error
313Could not compile `guessing_game`.
314```
315"###
316 .to_string();
317 let target = source.clone();
318 assert_eq!(parse(source), target);
319 }
320 #[test]
321 fn ignores_short_quotes() {
322 let source = r"to `1` at index `[0]` i".to_string();
323 let target = source.clone();
324 assert_eq!(parse(source), target);
325 }
326 #[test]
327 fn ignores_pre_sections_with_final_quote() {
328 let source = r###"```bash
329$ cargo run
330 Compiling points v0.1.0 (file:///projects/points)
331error: the trait bound `Point: std::fmt::Display` is not satisfied [--explain E0277]
332 --> src/main.rs:8:29
3338 |> println!("Point 1: {}", p1);
334 |> ^^
335<std macros>:2:27: 2:58: note: in this expansion of format_args!
336<std macros>:3:1: 3:54: note: in this expansion of print! (defined in <std macros>)
337src/main.rs:8:5: 8:33: note: in this expansion of println! (defined in <std macros>)
338note: `Point` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
339note: required by `std::fmt::Display::fmt`
340```
341`here` is another [link](the ref)
342"###.to_string();
343 let target = r###"```bash
344$ cargo run
345 Compiling points v0.1.0 (file:///projects/points)
346error: the trait bound `Point: std::fmt::Display` is not satisfied [--explain E0277]
347 --> src/main.rs:8:29
3488 |> println!("Point 1: {}", p1);
349 |> ^^
350<std macros>:2:27: 2:58: note: in this expansion of format_args!
351<std macros>:3:1: 3:54: note: in this expansion of print! (defined in <std macros>)
352src/main.rs:8:5: 8:33: note: in this expansion of println! (defined in <std macros>)
353note: `Point` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
354note: required by `std::fmt::Display::fmt`
355```
356`here` is another link at *the ref*
357"###.to_string();
358 assert_eq!(parse(source), target);
359 }
360 #[test]
361 fn parses_adam_p_cheatsheet() {
362 let source = r###"[I'm an inline-style link](https://www.google.com)
363
364[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
365
366[I'm a reference-style link][Arbitrary case-insensitive reference text]
367
368[I'm a relative reference to a repository file](../blob/master/LICENSE)
369
370[You can use numbers for reference-style link definitions][1]
371
372Or leave it empty and use the [link text itself][].
373
374URLs and URLs in angle brackets will automatically get turned into links.
375http://www.example.com or <http://www.example.com> and sometimes
376example.com (but not on Github, for example).
377
378Some text to show that the reference links can follow later.
379
380[arbitrary case-insensitive reference text]: https://www.mozilla.org
381[1]: http://slashdot.org
382[link text itself]: http://www.reddit.com"###
383 .to_string();
384
385 let target = r###"I'm an inline-style link at *https://www.google.com*
386
387I'm an inline-style link with title at *https://www.google.com*
388
389I'm a reference-style link at *https://www.mozilla.org*
390
391I'm a relative reference to a repository file at *../blob/master/LICENSE*
392
393You can use numbers for reference-style link definitions at *http://slashdot.org*
394
395Or leave it empty and use the link text itself at *http://www.reddit.com*.
396
397URLs and URLs in angle brackets will automatically get turned into links.
398http://www.example.com or <http://www.example.com> and sometimes
399example.com (but not on Github, for example).
400
401Some text to show that the reference links can follow later.
402"###
403 .to_string();
404 assert_eq!(parse(source), target);
405 }
13cf67c4 406}