mirror of
https://github.com/rust-lang/mdBook.git
synced 2025-12-28 11:24:57 -05:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
441a10bdd7 | ||
|
|
efdb83266a | ||
|
|
ac9c12334a | ||
|
|
2a3088422a | ||
|
|
1f505c2b2e | ||
|
|
a7b3aa0444 | ||
|
|
a9160acd64 | ||
|
|
4c1bca1684 | ||
|
|
8fffb2a704 | ||
|
|
ba37cc8462 | ||
|
|
3ea0f9b745 | ||
|
|
29d8747e01 | ||
|
|
f5549f2267 | ||
|
|
e2a8600712 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## mdBook 0.3.5
|
||||
[6e0d0fa...efdb832](https://github.com/rust-lang/mdBook/compare/6e0d0fa...efdb832)
|
||||
|
||||
### Changed
|
||||
- The `default-theme` config setting is now case-insensitive.
|
||||
[#1079](https://github.com/rust-lang/mdBook/pull/1079)
|
||||
|
||||
### Fixed
|
||||
- Fixed `#` hidden Rust code lines not rendering properly.
|
||||
[#1088](https://github.com/rust-lang/mdBook/pull/1088)
|
||||
- Updated pulldown-cmark to 0.6.1, fixing several issues.
|
||||
[#1021](https://github.com/rust-lang/mdBook/pull/1021)
|
||||
|
||||
## mdBook 0.3.4
|
||||
[e5f77aa...6e0d0fa](https://github.com/rust-lang/mdBook/compare/e5f77aa...6e0d0fa)
|
||||
|
||||
|
||||
381
Cargo.lock
generated
381
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mdbook"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
authors = [
|
||||
"Mathieu David <mathieudavid@mathieudavid.org>",
|
||||
"Michael-F-Bryan <michaelfbryan@gmail.com>",
|
||||
@@ -26,7 +26,7 @@ lazy_static = "1.0"
|
||||
log = "0.4"
|
||||
memchr = "2.0"
|
||||
open = "1.1"
|
||||
pulldown-cmark = "0.5"
|
||||
pulldown-cmark = "0.6.1"
|
||||
regex = "1.0.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
||||
@@ -22,7 +22,7 @@ overridden with your own.
|
||||
|
||||
If you want to use another theme for `highlight.js` download it from their
|
||||
website, or make it yourself, rename it to `highlight.css` and put it in
|
||||
`src/theme` (or the equivalent if you changed your source folder)
|
||||
the `theme` folder of your book.
|
||||
|
||||
Now your theme will be used instead of the default theme.
|
||||
|
||||
|
||||
@@ -153,7 +153,8 @@ impl From<Link> for SummaryItem {
|
||||
/// > match the following regex: "[^<>\n[]]+".
|
||||
struct SummaryParser<'a> {
|
||||
src: &'a str,
|
||||
stream: pulldown_cmark::Parser<'a>,
|
||||
stream: pulldown_cmark::OffsetIter<'a>,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
/// Reads `Events` from the provided stream until the corresponding
|
||||
@@ -174,7 +175,7 @@ macro_rules! collect_events {
|
||||
let mut events = Vec::new();
|
||||
|
||||
loop {
|
||||
let event = $stream.next();
|
||||
let event = $stream.next().map(|(ev, _range)| ev);
|
||||
trace!("Next event: {:?}", event);
|
||||
|
||||
match event {
|
||||
@@ -196,23 +197,22 @@ macro_rules! collect_events {
|
||||
|
||||
impl<'a> SummaryParser<'a> {
|
||||
fn new(text: &str) -> SummaryParser<'_> {
|
||||
let pulldown_parser = pulldown_cmark::Parser::new(text);
|
||||
let pulldown_parser = pulldown_cmark::Parser::new(text).into_offset_iter();
|
||||
|
||||
SummaryParser {
|
||||
src: text,
|
||||
stream: pulldown_parser,
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current line and column to give the user more useful error
|
||||
/// messages.
|
||||
fn current_location(&self) -> (usize, usize) {
|
||||
let byte_offset = self.stream.get_offset();
|
||||
|
||||
let previous_text = self.src[..byte_offset].as_bytes();
|
||||
let previous_text = self.src[..self.offset].as_bytes();
|
||||
let line = Memchr::new(b'\n', previous_text).count() + 1;
|
||||
let start_of_line = memchr::memrchr(b'\n', previous_text).unwrap_or(0);
|
||||
let col = self.src[start_of_line..byte_offset].chars().count();
|
||||
let col = self.src[start_of_line..self.offset].chars().count();
|
||||
|
||||
(line, col)
|
||||
}
|
||||
@@ -263,7 +263,7 @@ impl<'a> SummaryParser<'a> {
|
||||
let link = self.parse_link(href.to_string())?;
|
||||
items.push(SummaryItem::Link(link));
|
||||
}
|
||||
Some(Event::Start(Tag::Rule)) => items.push(SummaryItem::Separator),
|
||||
Some(Event::Rule) => items.push(SummaryItem::Separator),
|
||||
Some(_) => {}
|
||||
None => break,
|
||||
}
|
||||
@@ -319,9 +319,6 @@ impl<'a> SummaryParser<'a> {
|
||||
break;
|
||||
}
|
||||
Some(Event::Start(other_tag)) => {
|
||||
if other_tag == Tag::Rule {
|
||||
items.push(SummaryItem::Separator);
|
||||
}
|
||||
trace!("Skipping contents of {:?}", other_tag);
|
||||
|
||||
// Skip over the contents of this tag
|
||||
@@ -337,6 +334,14 @@ impl<'a> SummaryParser<'a> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(Event::Rule) => {
|
||||
items.push(SummaryItem::Separator);
|
||||
if let Some(Event::Start(Tag::List(..))) = self.next_event() {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
// something else... ignore
|
||||
continue;
|
||||
@@ -352,7 +357,10 @@ impl<'a> SummaryParser<'a> {
|
||||
}
|
||||
|
||||
fn next_event(&mut self) -> Option<Event<'a>> {
|
||||
let next = self.stream.next();
|
||||
let next = self.stream.next().map(|(ev, range)| {
|
||||
self.offset = range.start;
|
||||
ev
|
||||
});
|
||||
trace!("Next event: {:?}", next);
|
||||
|
||||
next
|
||||
@@ -431,10 +439,10 @@ impl<'a> SummaryParser<'a> {
|
||||
|
||||
/// Try to parse the title line.
|
||||
fn parse_title(&mut self) -> Option<String> {
|
||||
if let Some(Event::Start(Tag::Header(1))) = self.next_event() {
|
||||
if let Some(Event::Start(Tag::Heading(1))) = self.next_event() {
|
||||
debug!("Found a h1 in the SUMMARY");
|
||||
|
||||
let tags = collect_events!(self.stream, end Tag::Header(1));
|
||||
let tags = collect_events!(self.stream, end Tag::Heading(1));
|
||||
Some(stringify_events(tags))
|
||||
} else {
|
||||
None
|
||||
@@ -629,7 +637,7 @@ mod tests {
|
||||
let _ = parser.stream.next(); // skip past start of paragraph
|
||||
|
||||
let href = match parser.stream.next() {
|
||||
Some(Event::Start(Tag::Link(_type, href, _title))) => href.to_string(),
|
||||
Some((Event::Start(Tag::Link(_type, href, _title)), _range)) => href.to_string(),
|
||||
other => panic!("Unreachable, {:?}", other),
|
||||
};
|
||||
|
||||
|
||||
@@ -407,13 +407,13 @@ fn make_data(
|
||||
}
|
||||
|
||||
let default_theme = match html_config.default_theme {
|
||||
Some(ref theme) => theme,
|
||||
None => "light",
|
||||
Some(ref theme) => theme.to_lowercase(),
|
||||
None => "light".to_string(),
|
||||
};
|
||||
data.insert("default_theme".to_owned(), json!(default_theme));
|
||||
|
||||
let preferred_dark_theme = match html_config.preferred_dark_theme {
|
||||
Some(ref theme) => theme,
|
||||
Some(ref theme) => theme.to_lowercase(),
|
||||
None => default_theme,
|
||||
};
|
||||
data.insert(
|
||||
@@ -601,7 +601,6 @@ fn fix_code_blocks(html: &str) -> String {
|
||||
}
|
||||
|
||||
fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
|
||||
let boring_line_regex = Regex::new(r"^(\s*)#(#|.)(.*)$").unwrap();
|
||||
let regex = Regex::new(r##"((?s)<code[^>]?class="([^"]+)".*?>(.*?)</code>)"##).unwrap();
|
||||
regex
|
||||
.replace_all(html, |caps: &Captures<'_>| {
|
||||
@@ -609,57 +608,37 @@ fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
|
||||
let classes = &caps[2];
|
||||
let code = &caps[3];
|
||||
|
||||
if (classes.contains("language-rust")
|
||||
&& !classes.contains("ignore")
|
||||
&& !classes.contains("noplaypen"))
|
||||
|| classes.contains("mdbook-runnable")
|
||||
{
|
||||
// wrap the contents in an external pre block
|
||||
format!(
|
||||
"<pre class=\"playpen\"><code class=\"{}\">{}</code></pre>",
|
||||
classes,
|
||||
{
|
||||
let content: Cow<'_, str> = if playpen_config.editable
|
||||
&& classes.contains("editable")
|
||||
|| text.contains("fn main")
|
||||
|| text.contains("quick_main!")
|
||||
if classes.contains("language-rust") {
|
||||
if (!classes.contains("ignore") && !classes.contains("noplaypen"))
|
||||
|| classes.contains("mdbook-runnable")
|
||||
{
|
||||
// wrap the contents in an external pre block
|
||||
format!(
|
||||
"<pre class=\"playpen\"><code class=\"{}\">{}</code></pre>",
|
||||
classes,
|
||||
{
|
||||
code.into()
|
||||
} else {
|
||||
// we need to inject our own main
|
||||
let (attrs, code) = partition_source(code);
|
||||
|
||||
format!(
|
||||
"\n# #![allow(unused_variables)]\n{}#fn main() {{\n{}#}}",
|
||||
attrs, code
|
||||
)
|
||||
.into()
|
||||
};
|
||||
let mut prev_line_hidden = false;
|
||||
let mut result = String::with_capacity(content.len());
|
||||
for line in content.lines() {
|
||||
if let Some(caps) = boring_line_regex.captures(line) {
|
||||
if !prev_line_hidden && &caps[2] != "#" {
|
||||
result += "<span class=\"boring\">";
|
||||
prev_line_hidden = true;
|
||||
}
|
||||
result += &caps[1];
|
||||
if &caps[2] != " " {
|
||||
result += &caps[2];
|
||||
}
|
||||
result += &caps[3];
|
||||
let content: Cow<'_, str> = if playpen_config.editable
|
||||
&& classes.contains("editable")
|
||||
|| text.contains("fn main")
|
||||
|| text.contains("quick_main!")
|
||||
{
|
||||
code.into()
|
||||
} else {
|
||||
if prev_line_hidden {
|
||||
result += "</span>";
|
||||
prev_line_hidden = false;
|
||||
}
|
||||
result += line;
|
||||
}
|
||||
result += "\n";
|
||||
// we need to inject our own main
|
||||
let (attrs, code) = partition_source(code);
|
||||
|
||||
format!(
|
||||
"\n# #![allow(unused_variables)]\n{}#fn main() {{\n{}#}}",
|
||||
attrs, code
|
||||
)
|
||||
.into()
|
||||
};
|
||||
hide_lines(&content)
|
||||
}
|
||||
result
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
format!("<code class=\"{}\">{}</code>", classes, hide_lines(code))
|
||||
}
|
||||
} else {
|
||||
// not language-rust, so no-op
|
||||
text.to_owned()
|
||||
@@ -668,6 +647,38 @@ fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String {
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref BORING_LINES_REGEX: Regex = Regex::new(r"^(\s*)#(.?)(.*)$").unwrap();
|
||||
}
|
||||
|
||||
fn hide_lines(content: &str) -> String {
|
||||
let mut result = String::with_capacity(content.len());
|
||||
for line in content.lines() {
|
||||
if let Some(caps) = BORING_LINES_REGEX.captures(line) {
|
||||
if &caps[2] == "#" {
|
||||
result += &caps[1];
|
||||
result += &caps[2];
|
||||
result += &caps[3];
|
||||
result += "\n";
|
||||
continue;
|
||||
} else if &caps[2] != "!" && &caps[2] != "[" {
|
||||
result += "<span class=\"boring\">";
|
||||
result += &caps[1];
|
||||
if &caps[2] != " " {
|
||||
result += &caps[2];
|
||||
}
|
||||
result += &caps[3];
|
||||
result += "\n";
|
||||
result += "</span>";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result += line;
|
||||
result += "\n";
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn partition_source(s: &str) -> (String, String) {
|
||||
let mut after_header = false;
|
||||
let mut before = String::new();
|
||||
@@ -740,13 +751,19 @@ mod tests {
|
||||
fn add_playpen() {
|
||||
let inputs = [
|
||||
("<code class=\"language-rust\">x()</code>",
|
||||
"<pre class=\"playpen\"><code class=\"language-rust\">\n<span class=\"boring\">#![allow(unused_variables)]\nfn main() {\n</span>x()\n<span class=\"boring\">}\n</code></pre>"),
|
||||
"<pre class=\"playpen\"><code class=\"language-rust\">\n<span class=\"boring\">#![allow(unused_variables)]\n</span><span class=\"boring\">fn main() {\n</span>x()\n<span class=\"boring\">}\n</span></code></pre>"),
|
||||
("<code class=\"language-rust\">fn main() {}</code>",
|
||||
"<pre class=\"playpen\"><code class=\"language-rust\">fn main() {}\n</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n\";</code>",
|
||||
"<pre class=\"playpen\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n ## bar\n\";</code>",
|
||||
"<pre class=\"playpen\"><code class=\"language-rust editable\">let s = \"foo\n # bar\n\";\n</code></pre>"),
|
||||
("<code class=\"language-rust editable\">let s = \"foo\n # bar\n#\n\";</code>",
|
||||
"<pre class=\"playpen\"><code class=\"language-rust editable\">let s = \"foo\n<span class=\"boring\"> bar\n</span><span class=\"boring\">\n</span>\";\n</code></pre>"),
|
||||
("<code class=\"language-rust ignore\">let s = \"foo\n # bar\n\";</code>",
|
||||
"<code class=\"language-rust ignore\">let s = \"foo\n<span class=\"boring\"> bar\n</span>\";\n</code>"),
|
||||
("<code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]</code>",
|
||||
"<pre class=\"playpen\"><code class=\"language-rust editable\">#![no_std]\nlet s = \"foo\";\n #[some_attr]\n</code></pre>"),
|
||||
];
|
||||
for (src, should_be) in &inputs {
|
||||
let got = add_playpen_pre(
|
||||
|
||||
@@ -150,7 +150,7 @@ impl HelperDef for RenderToc {
|
||||
|
||||
// filter all events that are not inline code blocks
|
||||
let parser = Parser::new(name).filter(|event| match *event {
|
||||
Event::Code(_) | Event::InlineHtml(_) | Event::Text(_) => true,
|
||||
Event::Code(_) | Event::Html(_) | Event::Text(_) => true,
|
||||
_ => false,
|
||||
});
|
||||
|
||||
|
||||
@@ -81,22 +81,21 @@ fn render_item(
|
||||
.chain_err(|| "Could not convert HTML path to str")?;
|
||||
let anchor_base = utils::fs::normalize_path(filepath);
|
||||
|
||||
let p = utils::new_cmark_parser(&chapter.content);
|
||||
let mut p = utils::new_cmark_parser(&chapter.content).peekable();
|
||||
|
||||
let mut in_header = false;
|
||||
let max_section_depth = i32::from(search_config.heading_split_level);
|
||||
let mut in_heading = false;
|
||||
let max_section_depth = u32::from(search_config.heading_split_level);
|
||||
let mut section_id = None;
|
||||
let mut heading = String::new();
|
||||
let mut body = String::new();
|
||||
let mut html_block = String::new();
|
||||
let mut breadcrumbs = chapter.parent_names.clone();
|
||||
let mut footnote_numbers = HashMap::new();
|
||||
|
||||
for event in p {
|
||||
while let Some(event) = p.next() {
|
||||
match event {
|
||||
Event::Start(Tag::Header(i)) if i <= max_section_depth => {
|
||||
Event::Start(Tag::Heading(i)) if i <= max_section_depth => {
|
||||
if !heading.is_empty() {
|
||||
// Section finished, the next header is following now
|
||||
// Section finished, the next heading is following now
|
||||
// Write the data to the index, and clear it for the next section
|
||||
add_doc(
|
||||
index,
|
||||
@@ -111,10 +110,10 @@ fn render_item(
|
||||
breadcrumbs.pop();
|
||||
}
|
||||
|
||||
in_header = true;
|
||||
in_heading = true;
|
||||
}
|
||||
Event::End(Tag::Header(i)) if i <= max_section_depth => {
|
||||
in_header = false;
|
||||
Event::End(Tag::Heading(i)) if i <= max_section_depth => {
|
||||
in_heading = false;
|
||||
section_id = Some(utils::id_from_content(&heading));
|
||||
breadcrumbs.push(heading.clone());
|
||||
}
|
||||
@@ -123,31 +122,34 @@ fn render_item(
|
||||
footnote_numbers.entry(name).or_insert(number);
|
||||
}
|
||||
Event::Html(html) => {
|
||||
html_block.push_str(&html);
|
||||
}
|
||||
Event::End(Tag::HtmlBlock) => {
|
||||
let mut html_block = html.into_string();
|
||||
|
||||
// As of pulldown_cmark 0.6, html events are no longer contained
|
||||
// in an HtmlBlock tag. We must collect consecutive Html events
|
||||
// into a block ourselves.
|
||||
while let Some(Event::Html(html)) = p.peek() {
|
||||
html_block.push_str(&html);
|
||||
p.next();
|
||||
}
|
||||
|
||||
body.push_str(&clean_html(&html_block));
|
||||
html_block.clear();
|
||||
}
|
||||
Event::Start(_) | Event::End(_) | Event::SoftBreak | Event::HardBreak => {
|
||||
Event::Start(_) | Event::End(_) | Event::Rule | Event::SoftBreak | Event::HardBreak => {
|
||||
// Insert spaces where HTML output would usually seperate text
|
||||
// to ensure words don't get merged together
|
||||
if in_header {
|
||||
if in_heading {
|
||||
heading.push(' ');
|
||||
} else {
|
||||
body.push(' ');
|
||||
}
|
||||
}
|
||||
Event::Text(text) | Event::Code(text) => {
|
||||
if in_header {
|
||||
if in_heading {
|
||||
heading.push_str(&text);
|
||||
} else {
|
||||
body.push_str(&text);
|
||||
}
|
||||
}
|
||||
Event::InlineHtml(html) => {
|
||||
body.push_str(&clean_html(&html));
|
||||
}
|
||||
Event::FootnoteReference(name) => {
|
||||
let len = footnote_numbers.len() + 1;
|
||||
let number = footnote_numbers.entry(name).or_insert(len);
|
||||
|
||||
@@ -16,9 +16,6 @@ function playpen_text(playpen) {
|
||||
}
|
||||
|
||||
(function codeSnippets() {
|
||||
// Hide Rust code lines prepended with a specific character
|
||||
var hiding_character = "#";
|
||||
|
||||
function fetch_with_timeout(url, options, timeout = 6000) {
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
|
||||
@@ -158,7 +158,6 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
|
||||
Event::Start(Tag::Image(link_type, fix(dest, path), title))
|
||||
}
|
||||
Event::Html(html) => Event::Html(fix_html(html, path)),
|
||||
Event::InlineHtml(html) => Event::InlineHtml(fix_html(html, path)),
|
||||
_ => event,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user