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