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