rustdoc/
markdown.rs
1use std::fmt::Write as _;
12use std::fs::{File, create_dir_all, read_to_string};
13use std::io::prelude::*;
14use std::path::Path;
15
16use rustc_span::edition::Edition;
17
18use crate::config::RenderOptions;
19use crate::html::escape::Escape;
20use crate::html::markdown;
21use crate::html::markdown::{ErrorCodes, HeadingOffset, IdMap, Markdown, MarkdownWithToc};
22
23fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) {
25 let mut metadata = Vec::new();
26 let mut count = 0;
27
28 for line in s.lines() {
29 if line.starts_with("# ") || line.starts_with('%') {
30 metadata.push(line[1..].trim_start());
32 count += line.len() + 1;
33 } else {
34 return (metadata, &s[count..]);
35 }
36 }
37
38 (metadata, "")
40}
41
42pub(crate) fn render_and_write<P: AsRef<Path>>(
47 input: P,
48 options: RenderOptions,
49 edition: Edition,
50) -> Result<(), String> {
51 if let Err(e) = create_dir_all(&options.output) {
52 return Err(format!("{output}: {e}", output = options.output.display()));
53 }
54
55 let input = input.as_ref();
56 let mut output = options.output;
57 output.push(input.file_name().unwrap());
58 output.set_extension("html");
59
60 let mut css = String::new();
61 for name in &options.markdown_css {
62 write!(css, r#"<link rel="stylesheet" href="{name}">"#)
63 .expect("Writing to a String can't fail");
64 }
65
66 let input_str =
67 read_to_string(input).map_err(|err| format!("{input}: {err}", input = input.display()))?;
68 let playground_url = options.markdown_playground_url.or(options.playground_url);
69 let playground = playground_url.map(|url| markdown::Playground { crate_name: None, url });
70
71 let mut out =
72 File::create(&output).map_err(|e| format!("{output}: {e}", output = output.display()))?;
73
74 let (metadata, text) = extract_leading_metadata(&input_str);
75 if metadata.is_empty() {
76 return Err("invalid markdown file: no initial lines starting with `# ` or `%`".to_owned());
77 }
78 let title = metadata[0];
79
80 let mut ids = IdMap::new();
81 let error_codes = ErrorCodes::from(options.unstable_features.is_nightly_build());
82 let text = if !options.markdown_no_toc {
83 MarkdownWithToc {
84 content: text,
85 links: &[],
86 ids: &mut ids,
87 error_codes,
88 edition,
89 playground: &playground,
90 }
91 .into_string()
92 } else {
93 Markdown {
94 content: text,
95 links: &[],
96 ids: &mut ids,
97 error_codes,
98 edition,
99 playground: &playground,
100 heading_offset: HeadingOffset::H1,
101 }
102 .into_string()
103 };
104
105 let err = write!(
106 &mut out,
107 r#"<!DOCTYPE html>
108<html lang="en">
109<head>
110 <meta charset="utf-8">
111 <meta name="viewport" content="width=device-width, initial-scale=1.0">
112 <meta name="generator" content="rustdoc">
113 <title>{title}</title>
114
115 {css}
116 {in_header}
117</head>
118<body class="rustdoc">
119 <!--[if lte IE 8]>
120 <div class="warning">
121 This old browser is unsupported and will most likely display funky
122 things.
123 </div>
124 <![endif]-->
125
126 {before_content}
127 <h1 class="title">{title}</h1>
128 {text}
129 {after_content}
130</body>
131</html>"#,
132 title = Escape(title),
133 css = css,
134 in_header = options.external_html.in_header,
135 before_content = options.external_html.before_content,
136 text = text,
137 after_content = options.external_html.after_content,
138 );
139
140 match err {
141 Err(e) => Err(format!("cannot write to `{output}`: {e}", output = output.display())),
142 Ok(_) => Ok(()),
143 }
144}