205 lines · 5.8 KB · Rust
RawDownload
use crate::html_element::HtmlElement;use crate::markdown_writer::{HandleTag, HandlerOutcome, MarkdownWriter, StartTagOutcome}; pub struct RustdocHeadingHandler; impl HandleTag for RustdocHeadingHandler {    fn should_handle(&self, _tag: &str) -> bool {        // We're only handling text, so we don't need to visit any tags.        false    }     fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome {        if writer.is_inside("h1")            || writer.is_inside("h2")            || writer.is_inside("h3")            || writer.is_inside("h4")            || writer.is_inside("h5")            || writer.is_inside("h6")        {            let text = text                .trim_matches(|char| char == '\n' || char == '\r' || char == '§')                .replace('\n', " ");            writer.push_str(&text);             return HandlerOutcome::Handled;        }         HandlerOutcome::NoOp    }} pub struct RustdocCodeHandler; impl HandleTag for RustdocCodeHandler {    fn should_handle(&self, tag: &str) -> bool {        match tag {            "pre" | "code" => true,            _ => false,        }    }     fn handle_tag_start(        &mut self,        tag: &HtmlElement,        writer: &mut MarkdownWriter,    ) -> StartTagOutcome {        match tag.tag.as_str() {            "code" => {                if !writer.is_inside("pre") {                    writer.push_str("`");                }            }            "pre" => {                let classes = tag.classes();                let is_rust = classes.iter().any(|class| class == "rust");                let language = is_rust                    .then(|| "rs")                    .or_else(|| {                        classes.iter().find_map(|class| {                            if let Some((_, language)) = class.split_once("language-") {                                Some(language.trim())                            } else {                                None                            }                        })                    })                    .unwrap_or("");                 writer.push_str(&format!("\n\n```{language}\n"));            }            _ => {}        }         StartTagOutcome::Continue    }     fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {        match tag.tag.as_str() {            "code" => {                if !writer.is_inside("pre") {                    writer.push_str("`");                }            }            "pre" => writer.push_str("\n```\n"),            _ => {}        }    }     fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome {        if writer.is_inside("pre") {            writer.push_str(&text);            return HandlerOutcome::Handled;        }         HandlerOutcome::NoOp    }} const RUSTDOC_ITEM_NAME_CLASS: &str = "item-name"; pub struct RustdocItemHandler; impl RustdocItemHandler {    /// Returns whether we're currently inside of an `.item-name` element, which    /// rustdoc uses to display Rust items in a list.    fn is_inside_item_name(writer: &MarkdownWriter) -> bool {        writer            .current_element_stack()            .iter()            .any(|element| element.has_class(RUSTDOC_ITEM_NAME_CLASS))    }} impl HandleTag for RustdocItemHandler {    fn should_handle(&self, tag: &str) -> bool {        match tag {            "div" | "span" => true,            _ => false,        }    }     fn handle_tag_start(        &mut self,        tag: &HtmlElement,        writer: &mut MarkdownWriter,    ) -> StartTagOutcome {        match tag.tag.as_str() {            "div" | "span" => {                if Self::is_inside_item_name(writer) && tag.has_class("stab") {                    writer.push_str(" [");                }            }            _ => {}        }         StartTagOutcome::Continue    }     fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {        match tag.tag.as_str() {            "div" | "span" => {                if tag.has_class(RUSTDOC_ITEM_NAME_CLASS) {                    writer.push_str(": ");                }                 if Self::is_inside_item_name(writer) && tag.has_class("stab") {                    writer.push_str("]");                }            }            _ => {}        }    }     fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome {        if Self::is_inside_item_name(writer)            && !writer.is_inside("span")            && !writer.is_inside("code")        {            writer.push_str(&format!("`{text}`"));            return HandlerOutcome::Handled;        }         HandlerOutcome::NoOp    }} pub struct RustdocChromeRemover; impl HandleTag for RustdocChromeRemover {    fn should_handle(&self, tag: &str) -> bool {        match tag {            "head" | "script" | "nav" | "summary" | "button" | "div" | "span" => true,            _ => false,        }    }     fn handle_tag_start(        &mut self,        tag: &HtmlElement,        _writer: &mut MarkdownWriter,    ) -> StartTagOutcome {        match tag.tag.as_str() {            "head" | "script" | "nav" => return StartTagOutcome::Skip,            "summary" => {                if tag.has_class("hideme") {                    return StartTagOutcome::Skip;                }            }            "button" => {                if tag.attr("id").as_deref() == Some("copy-path") {                    return StartTagOutcome::Skip;                }            }            "div" | "span" => {                let classes_to_skip = ["nav-container", "sidebar-elems", "out-of-band"];                if tag.has_any_classes(&classes_to_skip) {                    return StartTagOutcome::Skip;                }            }            _ => {}        }         StartTagOutcome::Continue    }}