diff --git a/src/db/add_package.rs b/src/db/add_package.rs index 0080b15c9..eb00f9b7b 100644 --- a/src/db/add_package.rs +++ b/src/db/add_package.rs @@ -469,7 +469,10 @@ fn read_rust_doc(file_path: &Path) -> Result> { let line = line?; if line.starts_with("//!") { // some lines may or may not have a space between the `//!` and the start of the text - let line = line.trim_start_matches("//!").trim_start(); + let mut line = line.trim_start_matches("//!"); + if line.starts_with(' ') { + line = &line[1..]; + } if !line.is_empty() { rustdoc.push_str(line); } diff --git a/src/test/fakes.rs b/src/test/fakes.rs index ac7187073..c493f6f62 100644 --- a/src/test/fakes.rs +++ b/src/test/fakes.rs @@ -231,6 +231,13 @@ impl<'a> FakeRelease<'a> { self } + pub(crate) fn target_source(mut self, path: &'a str) -> Self { + if let Some(target) = self.package.targets.first_mut() { + target.src_path = Some(path.into()); + } + self + } + pub(crate) fn no_cargo_toml(mut self) -> Self { self.no_cargo_toml = true; self @@ -503,6 +510,7 @@ impl<'a> FakeRelease<'a> { if let Some(markdown) = self.readme { fs::write(crate_dir.join("README.md"), markdown)?; } + store_files_into(&self.source_files, crate_dir)?; // Many tests rely on the default-target being linux, so it should not // be set to docsrs_metadata::HOST_TARGET, because then tests fail on all diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index e47c5e929..f8f117911 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -2187,6 +2187,61 @@ mod tests { }); } + #[test] + fn no_readme() { + async_wrapper(|env| async move { + env.fake_release() + .await + .name("dummy") + .version("0.2.0") + .source_file( + "Cargo.toml", + br#"[package] +name = "dummy" +version = "0.2.0" + +[lib] +name = "dummy" +path = "src/lib.rs" +"#, + ) + .source_file( + "src/lib.rs", + b"//! # Crate-level docs +//! +//! ``` +//! let x = 21; +//! ``` +", + ) + .target_source("src/lib.rs") + .create() + .await?; + + let web = env.web_app().await; + let response = web.get("/crate/dummy/0.2.0").await?; + assert!(response.status().is_success()); + + let dom = kuchikiki::parse_html().one(response.text().await?); + dom.select_first("#main").expect("not main crate docs"); + // First we check that the crate-level docs have been rendered as expected. + assert_eq!( + dom.select_first("#main h1") + .expect("no h1 found") + .text_contents(), + "Crate-level docs" + ); + // Then we check that by default, the language used for highlighting is rust. + assert_eq!( + dom.select_first("#main pre .syntax-source.syntax-rust") + .expect("no rust code block found") + .text_contents(), + "let x = 21;\n" + ); + Ok(()) + }); + } + #[test] fn test_crate_name_with_other_uri_chars() { async_wrapper(|env| async move { diff --git a/src/web/highlight.rs b/src/web/highlight.rs index 7b91a1234..cc0250d64 100644 --- a/src/web/highlight.rs +++ b/src/web/highlight.rs @@ -49,8 +49,17 @@ fn try_with_syntax(syntax: &SyntaxReference, code: &str) -> Result { Ok(html_generator.finalize()) } -fn select_syntax(name: Option<&str>, code: &str) -> &'static SyntaxReference { +fn select_syntax( + name: Option<&str>, + code: &str, + default: Option<&str>, +) -> &'static SyntaxReference { name.and_then(|name| { + if name.is_empty() { + if let Some(default) = default { + return SYNTAXES.find_syntax_by_token(default); + } + } SYNTAXES.find_syntax_by_token(name).or_else(|| { name.rsplit_once('.') .and_then(|(_, ext)| SYNTAXES.find_syntax_by_token(ext)) @@ -60,12 +69,12 @@ fn select_syntax(name: Option<&str>, code: &str) -> &'static SyntaxReference { .unwrap_or_else(|| SYNTAXES.find_syntax_plain_text()) } -pub fn try_with_lang(lang: Option<&str>, code: &str) -> Result { - try_with_syntax(select_syntax(lang, code), code) +pub fn try_with_lang(lang: Option<&str>, code: &str, default: Option<&str>) -> Result { + try_with_syntax(select_syntax(lang, code, default), code) } -pub fn with_lang(lang: Option<&str>, code: &str) -> String { - match try_with_lang(lang, code) { +pub fn with_lang(lang: Option<&str>, code: &str, default: Option<&str>) -> String { + match try_with_lang(lang, code, default) { Ok(highlighted) => highlighted, Err(err) => { if err.is::() { @@ -89,23 +98,29 @@ mod tests { #[test] fn custom_filetypes() { - let toml = select_syntax(Some("toml"), ""); + let toml = select_syntax(Some("toml"), "", None); - assert_eq!(select_syntax(Some("Cargo.toml.orig"), "").name, toml.name); - assert_eq!(select_syntax(Some("Cargo.lock"), "").name, toml.name); + assert_eq!( + select_syntax(Some("Cargo.toml.orig"), "", None).name, + toml.name + ); + assert_eq!(select_syntax(Some("Cargo.lock"), "", None).name, toml.name); } #[test] fn dotfile_with_extension() { - let toml = select_syntax(Some("toml"), ""); + let toml = select_syntax(Some("toml"), "", None); - assert_eq!(select_syntax(Some(".rustfmt.toml"), "").name, toml.name); + assert_eq!( + select_syntax(Some(".rustfmt.toml"), "", None).name, + toml.name + ); } #[test] fn limits() { let is_limited = |s: String| { - try_with_lang(Some("toml"), &s) + try_with_lang(Some("toml"), &s, None) .unwrap_err() .is::() }; @@ -116,7 +131,7 @@ mod tests { #[test] fn limited_escaped() { let text = "

\n".to_string() + "aa".repeat(PER_LINE_BYTE_LENGTH_LIMIT).as_str(); - let highlighted = with_lang(Some("toml"), &text); + let highlighted = with_lang(Some("toml"), &text, None); assert!(highlighted.starts_with("<p>\n")); } } diff --git a/src/web/markdown.rs b/src/web/markdown.rs index c3cdcaae3..2242bcd63 100644 --- a/src/web/markdown.rs +++ b/src/web/markdown.rs @@ -5,9 +5,9 @@ use comrak::{ use std::collections::HashMap; #[derive(Debug)] -struct CodeAdapter(F); +struct CodeAdapter(F, Option<&'static str>); -impl, &str) -> String + Send + Sync> SyntaxHighlighterAdapter +impl, &str, Option<&str>) -> String + Send + Sync> SyntaxHighlighterAdapter for CodeAdapter { fn write_highlighted( @@ -19,7 +19,7 @@ impl, &str) -> String + Send + Sync> SyntaxHighlighterAdapter // comrak does not treat `,` as an info-string delimiter, so we do that here // TODO: https://github.com/kivikakk/comrak/issues/246 let lang = lang.and_then(|lang| lang.split(',').next()); - write!(output, "{}", (self.0)(lang, code)) + write!(output, "{}", (self.0)(lang, code, self.1)) } fn write_pre_tag( @@ -67,9 +67,10 @@ fn write_opening_tag( fn render_with_highlighter( text: &str, - highlighter: impl Fn(Option<&str>, &str) -> String + Send + Sync, + default_syntax: Option<&'static str>, + highlighter: impl Fn(Option<&str>, &str, Option<&str>) -> String + Send + Sync, ) -> String { - let code_adapter = CodeAdapter(highlighter); + let code_adapter = CodeAdapter(highlighter, default_syntax); comrak::markdown_to_html_with_plugins( text, @@ -95,7 +96,11 @@ fn render_with_highlighter( /// Wrapper around the Markdown parser and renderer to render markdown pub fn render(text: &str) -> String { - render_with_highlighter(text, highlight::with_lang) + render_with_highlighter(text, None, highlight::with_lang) +} + +pub fn render_with_default(text: &str, default: &'static str) -> String { + render_with_highlighter(text, Some(default), highlight::with_lang) } #[cfg(test)] @@ -118,7 +123,8 @@ mod test { ignore::spaces(); ``` "}, - |lang, code| { + None, + |lang, code, _| { let mut highlighted = highlighted.lock().unwrap(); highlighted.push((lang.map(str::to_owned), code.to_owned())); code.to_owned() diff --git a/src/web/page/templates.rs b/src/web/page/templates.rs index a39d705da..252e945ab 100644 --- a/src/web/page/templates.rs +++ b/src/web/page/templates.rs @@ -201,7 +201,8 @@ pub mod filters { } pub fn highlight(code: impl std::fmt::Display, lang: &str) -> rinja::Result> { - let highlighted_code = crate::web::highlight::with_lang(Some(lang), &code.to_string()); + let highlighted_code = + crate::web::highlight::with_lang(Some(lang), &code.to_string(), None); Ok(Safe(format!( "

{}
", highlighted_code diff --git a/templates/crate/details.html b/templates/crate/details.html index e2ae588cb..35b4e6be7 100644 --- a/templates/crate/details.html +++ b/templates/crate/details.html @@ -198,7 +198,7 @@ {# If there's not a readme then attempt to display the long description #} {%- elif let Some(rustdoc) = rustdoc -%} - {{ rustdoc|safe }} + {{ crate::web::markdown::render_with_default(rustdoc, "rust")|safe }} {%- endif -%}