1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use crate::html::markdown::{ErrorCodes, HeadingOffset, IdMap, Markdown, Playground};
use rustc_span::edition::Edition;
use std::fs;
use std::path::Path;
use std::str;

use serde::Serialize;

#[derive(Clone, Debug, Serialize)]
pub(crate) struct ExternalHtml {
    /// Content that will be included inline in the `<head>` section of a
    /// rendered Markdown file or generated documentation
    pub(crate) in_header: String,
    /// Content that will be included inline between `<body>` and the content of
    /// a rendered Markdown file or generated documentation
    pub(crate) before_content: String,
    /// Content that will be included inline between the content and `</body>` of
    /// a rendered Markdown file or generated documentation
    pub(crate) after_content: String,
}

impl ExternalHtml {
    pub(crate) fn load(
        in_header: &[String],
        before_content: &[String],
        after_content: &[String],
        md_before_content: &[String],
        md_after_content: &[String],
        nightly_build: bool,
        dcx: &rustc_errors::DiagCtxt,
        id_map: &mut IdMap,
        edition: Edition,
        playground: &Option<Playground>,
    ) -> Option<ExternalHtml> {
        let codes = ErrorCodes::from(nightly_build);
        let ih = load_external_files(in_header, dcx)?;
        let bc = load_external_files(before_content, dcx)?;
        let m_bc = load_external_files(md_before_content, dcx)?;
        let bc = format!(
            "{bc}{}",
            Markdown {
                content: &m_bc,
                links: &[],
                ids: id_map,
                error_codes: codes,
                edition,
                playground,
                heading_offset: HeadingOffset::H2,
                // For external files, it'll be disabled until the feature is enabled by default.
                custom_code_classes_in_docs: false,
            }
            .into_string()
        );
        let ac = load_external_files(after_content, dcx)?;
        let m_ac = load_external_files(md_after_content, dcx)?;
        let ac = format!(
            "{ac}{}",
            Markdown {
                content: &m_ac,
                links: &[],
                ids: id_map,
                error_codes: codes,
                edition,
                playground,
                heading_offset: HeadingOffset::H2,
                // For external files, it'll be disabled until the feature is enabled by default.
                custom_code_classes_in_docs: false,
            }
            .into_string()
        );
        Some(ExternalHtml { in_header: ih, before_content: bc, after_content: ac })
    }
}

pub(crate) enum LoadStringError {
    ReadFail,
    BadUtf8,
}

pub(crate) fn load_string<P: AsRef<Path>>(
    file_path: P,
    dcx: &rustc_errors::DiagCtxt,
) -> Result<String, LoadStringError> {
    let file_path = file_path.as_ref();
    let contents = match fs::read(file_path) {
        Ok(bytes) => bytes,
        Err(e) => {
            dcx.struct_err(format!(
                "error reading `{file_path}`: {e}",
                file_path = file_path.display()
            ))
            .emit();
            return Err(LoadStringError::ReadFail);
        }
    };
    match str::from_utf8(&contents) {
        Ok(s) => Ok(s.to_string()),
        Err(_) => {
            dcx.err(format!("error reading `{}`: not UTF-8", file_path.display()));
            Err(LoadStringError::BadUtf8)
        }
    }
}

fn load_external_files(names: &[String], dcx: &rustc_errors::DiagCtxt) -> Option<String> {
    let mut out = String::new();
    for name in names {
        let Ok(s) = load_string(name, dcx) else { return None };
        out.push_str(&s);
        out.push('\n');
    }
    Some(out)
}