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