]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/markdown.rs
New upstream version 1.44.1+dfsg1
[rustc.git] / src / librustdoc / markdown.rs
CommitLineData
ba9703b0 1use std::fs::{create_dir_all, File};
c34b1796 2use std::io::prelude::*;
a1dfa0c6 3use std::path::PathBuf;
1a4d82fc 4
60c5eb7d 5use rustc_feature::UnstableFeatures;
dfeec247
XL
6use rustc_span::edition::Edition;
7use rustc_span::source_map::DUMMY_SP;
1a4d82fc 8
9fa01778 9use crate::config::{Options, RenderOptions};
dfeec247 10use crate::externalfiles::{load_string, LoadStringError};
9fa01778
XL
11use crate::html::escape::Escape;
12use crate::html::markdown;
dfeec247
XL
13use crate::html::markdown::{find_testable_code, ErrorCodes, IdMap, Markdown, MarkdownWithToc};
14use crate::test::{Collector, TestOptions};
1a4d82fc 15
cc61c64b 16/// Separate any lines at the start of the file that begin with `# ` or `%`.
416331ca 17fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) {
1a4d82fc 18 let mut metadata = Vec::new();
c1a9b12d 19 let mut count = 0;
cc61c64b 20
1a4d82fc 21 for line in s.lines() {
74b04a01 22 if line.starts_with("# ") || line.starts_with('%') {
cc61c64b 23 // trim the whitespace after the symbol
0731742a 24 metadata.push(line[1..].trim_start());
c1a9b12d 25 count += line.len() + 1;
1a4d82fc 26 } else {
c1a9b12d 27 return (metadata, &s[count..]);
1a4d82fc
JJ
28 }
29 }
cc61c64b
XL
30
31 // if we're here, then all lines were metadata `# ` or `%` lines.
1a4d82fc
JJ
32 (metadata, "")
33}
34
0731742a
XL
35/// Render `input` (e.g., "foo.md") into an HTML file in `output`
36/// (e.g., output = "bar" => "bar/foo.html").
48663c56
XL
37pub fn render(
38 input: PathBuf,
39 options: RenderOptions,
dfeec247
XL
40 diag: &rustc_errors::Handler,
41 edition: Edition,
48663c56 42) -> i32 {
ba9703b0
XL
43 if let Err(e) = create_dir_all(&options.output) {
44 diag.struct_err(&format!("{}: {}", options.output.display(), e)).emit();
45 return 4;
46 }
47
a1dfa0c6 48 let mut output = options.output;
e1599b0c 49 output.push(input.file_name().unwrap());
1a4d82fc
JJ
50 output.set_extension("html");
51
52 let mut css = String::new();
a1dfa0c6 53 for name in &options.markdown_css {
1a4d82fc 54 let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
85aaf69f 55 css.push_str(&s)
1a4d82fc
JJ
56 }
57
a1dfa0c6 58 let input_str = match load_string(&input, diag) {
c30ab7b3
SL
59 Ok(s) => s,
60 Err(LoadStringError::ReadFail) => return 1,
61 Err(LoadStringError::BadUtf8) => return 2,
62 };
dfeec247
XL
63 let playground_url = options.markdown_playground_url.or(options.playground_url);
64 let playground = playground_url.map(|url| markdown::Playground { crate_name: None, url });
1a4d82fc 65
c34b1796 66 let mut out = match File::create(&output) {
1a4d82fc 67 Err(e) => {
94b46f34 68 diag.struct_err(&format!("{}: {}", output.display(), e)).emit();
1a4d82fc
JJ
69 return 4;
70 }
a1dfa0c6 71 Ok(f) => f,
1a4d82fc
JJ
72 };
73
85aaf69f 74 let (metadata, text) = extract_leading_metadata(&input_str);
9346a6ac 75 if metadata.is_empty() {
94b46f34 76 diag.struct_err("invalid markdown file: no initial lines starting with `# ` or `%`").emit();
1a4d82fc
JJ
77 return 5;
78 }
85aaf69f 79 let title = metadata[0];
1a4d82fc 80
b7449926
XL
81 let mut ids = IdMap::new();
82 let error_codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
a1dfa0c6 83 let text = if !options.markdown_no_toc {
416331ca 84 MarkdownWithToc(text, &mut ids, error_codes, edition, &playground).to_string()
1a4d82fc 85 } else {
416331ca 86 Markdown(text, &[], &mut ids, error_codes, edition, &playground).to_string()
1a4d82fc
JJ
87 };
88
89 let err = write!(
90 &mut out,
91 r#"<!DOCTYPE html>
92<html lang="en">
93<head>
94 <meta charset="utf-8">
85aaf69f 95 <meta name="viewport" content="width=device-width, initial-scale=1.0">
1a4d82fc
JJ
96 <meta name="generator" content="rustdoc">
97 <title>{title}</title>
98
99 {css}
100 {in_header}
101</head>
102<body class="rustdoc">
103 <!--[if lte IE 8]>
104 <div class="warning">
105 This old browser is unsupported and will most likely display funky
106 things.
107 </div>
108 <![endif]-->
109
110 {before_content}
111 <h1 class="title">{title}</h1>
112 {text}
1a4d82fc
JJ
113 {after_content}
114</body>
115</html>"#,
116 title = Escape(title),
117 css = css,
a1dfa0c6
XL
118 in_header = options.external_html.in_header,
119 before_content = options.external_html.before_content,
0531ce1d 120 text = text,
a1dfa0c6 121 after_content = options.external_html.after_content,
ff7c6d11 122 );
1a4d82fc
JJ
123
124 match err {
125 Err(e) => {
94b46f34 126 diag.struct_err(&format!("cannot write to `{}`: {}", output.display(), e)).emit();
1a4d82fc
JJ
127 6
128 }
ff7c6d11 129 Ok(_) => 0,
1a4d82fc
JJ
130 }
131}
132
9fa01778 133/// Runs any tests/code examples in the markdown file `input`.
dfeec247 134pub fn test(mut options: Options, diag: &rustc_errors::Handler) -> i32 {
a1dfa0c6 135 let input_str = match load_string(&options.input, diag) {
c30ab7b3
SL
136 Ok(s) => s,
137 Err(LoadStringError::ReadFail) => return 1,
138 Err(LoadStringError::BadUtf8) => return 2,
139 };
1a4d82fc 140
9346a6ac
AL
141 let mut opts = TestOptions::default();
142 opts.no_crate_inject = true;
a1dfa0c6 143 opts.display_warnings = options.display_warnings;
dfeec247
XL
144 let mut collector = Collector::new(
145 options.input.display().to_string(),
146 options.clone(),
147 true,
148 opts,
149 None,
150 Some(options.input),
151 options.enable_per_target_ignores,
152 );
b7449926
XL
153 collector.set_position(DUMMY_SP);
154 let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
48663c56 155
e1599b0c 156 find_testable_code(&input_str, &mut collector, codes, options.enable_per_target_ignores);
48663c56 157
a1dfa0c6 158 options.test_args.insert(0, "rustdoctest".to_string());
dfeec247
XL
159 testing::test_main(
160 &options.test_args,
161 collector.tests,
162 Some(testing::Options::new().display_output(options.display_warnings)),
163 );
1a4d82fc
JJ
164 0
165}