rustdoc: Improve playground run buttons

The main change is to stop using javascript to generate the URLs and use
rustdoc instead.

This also adds run buttons to the error index examples.
This commit is contained in:
Oliver Middleton 2016-10-11 09:56:30 +01:00
parent 8e05e7ee3c
commit 0b2746c8db
15 changed files with 131 additions and 115 deletions

View File

@ -5,4 +5,3 @@ or the <a href="https://opensource.org/licenses/MIT">MIT license</a>, at your op
</p><p>
This file may not be copied, modified, or distributed except according to those terms.
</p></footer>
<script type="text/javascript" src="playpen.js"></script>

View File

@ -336,13 +336,11 @@ table th {
/* Code snippets */
.rusttest { display: none; }
pre.rust { position: relative; }
a.test-arrow {
background-color: rgba(78, 139, 202, 0.2);
display: inline-block;
position: absolute;
background-color: #4e8bca;
color: #f5f5f5;
padding: 5px 10px 5px 10px;
border-radius: 5px;
@ -350,6 +348,10 @@ a.test-arrow {
top: 5px;
right: 5px;
}
a.test-arrow:hover{
background-color: #4e8bca;
text-decoration: none;
}
.unstable-feature {
border: 2px solid red;

View File

@ -19,7 +19,6 @@ pub struct Layout {
pub favicon: String,
pub external_html: ExternalHtml,
pub krate: String,
pub playground_url: String,
}
pub struct Page<'a> {
@ -136,11 +135,9 @@ r##"<!DOCTYPE html>
<script>
window.rootPath = "{root_path}";
window.currentCrate = "{krate}";
window.playgroundUrl = "{play_url}";
</script>
<script src="{root_path}jquery.js"></script>
<script src="{root_path}main.js"></script>
{play_js}
<script defer src="{root_path}search-index.js"></script>
</body>
</html>"##,
@ -174,12 +171,6 @@ r##"<!DOCTYPE html>
after_content = layout.external_html.after_content,
sidebar = *sidebar,
krate = layout.krate,
play_url = layout.playground_url,
play_js = if layout.playground_url.is_empty() {
format!(r#"<script src="{}extra.js"></script>"#, page.root_path)
} else {
format!(r#"<script src="{}playpen.js"></script>"#, page.root_path)
}
)
}

View File

@ -31,7 +31,7 @@ use std::ascii::AsciiExt;
use std::cell::RefCell;
use std::default::Default;
use std::ffi::CString;
use std::fmt;
use std::fmt::{self, Write};
use std::slice;
use std::str;
use syntax::feature_gate::UnstableFeatures;
@ -214,7 +214,9 @@ fn collapse_whitespace(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ")
}
thread_local!(pub static PLAYGROUND_KRATE: RefCell<Option<Option<String>>> = {
// Information about the playground if a URL has been specified, containing an
// optional crate name and the URL.
thread_local!(pub static PLAYGROUND: RefCell<Option<(Option<String>, String)>> = {
RefCell::new(None)
});
@ -248,24 +250,53 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
});
let text = lines.collect::<Vec<&str>>().join("\n");
if rendered { return }
PLAYGROUND_KRATE.with(|krate| {
PLAYGROUND.with(|play| {
// insert newline to clearly separate it from the
// previous block so we can shorten the html output
let mut s = String::from("\n");
krate.borrow().as_ref().map(|krate| {
let playground_button = play.borrow().as_ref().and_then(|&(ref krate, ref url)| {
if url.is_empty() {
return None;
}
let test = origtext.lines().map(|l| {
stripped_filtered_line(l).unwrap_or(l)
}).collect::<Vec<&str>>().join("\n");
let krate = krate.as_ref().map(|s| &**s);
let test = test::maketest(&test, krate, false,
&Default::default());
s.push_str(&format!("<span class='rusttest'>{}</span>", Escape(&test)));
let channel = if test.contains("#![feature(") {
"&amp;version=nightly"
} else {
""
};
// These characters don't need to be escaped in a URI.
// FIXME: use a library function for percent encoding.
fn dont_escape(c: u8) -> bool {
(b'a' <= c && c <= b'z') ||
(b'A' <= c && c <= b'Z') ||
(b'0' <= c && c <= b'9') ||
c == b'-' || c == b'_' || c == b'.' ||
c == b'~' || c == b'!' || c == b'\'' ||
c == b'(' || c == b')' || c == b'*'
}
let mut test_escaped = String::new();
for b in test.bytes() {
if dont_escape(b) {
test_escaped.push(char::from(b));
} else {
write!(test_escaped, "%{:02X}", b).unwrap();
}
}
Some(format!(
r#"<a class="test-arrow" target="_blank" href="{}?code={}{}">Run</a>"#,
url, test_escaped, channel
))
});
s.push_str(&highlight::render_with_highlighting(
&text,
Some("rust-example-rendered"),
None,
Some("<a class='test-arrow' target='_blank' href=''>Run</a>")));
playground_button.as_ref().map(String::as_str)));
let output = CString::new(s).unwrap();
hoedown_buffer_puts(ob, output.as_ptr());
})

View File

@ -449,7 +449,6 @@ pub fn run(mut krate: clean::Crate,
favicon: "".to_string(),
external_html: external_html.clone(),
krate: krate.name.clone(),
playground_url: "".to_string(),
},
css_file_extension: css_file_extension.clone(),
};
@ -469,11 +468,10 @@ pub fn run(mut krate: clean::Crate,
}
clean::NameValue(ref x, ref s)
if "html_playground_url" == *x => {
scx.layout.playground_url = s.to_string();
markdown::PLAYGROUND_KRATE.with(|slot| {
markdown::PLAYGROUND.with(|slot| {
if slot.borrow().is_none() {
let name = krate.name.clone();
*slot.borrow_mut() = Some(Some(name));
*slot.borrow_mut() = Some((Some(name), s.clone()));
}
});
}
@ -659,8 +657,6 @@ fn write_shared(cx: &Context,
include_bytes!("static/jquery-2.1.4.min.js"))?;
write(cx.dst.join("main.js"),
include_bytes!("static/main.js"))?;
write(cx.dst.join("playpen.js"),
include_bytes!("static/playpen.js"))?;
write(cx.dst.join("rustdoc.css"),
include_bytes!("static/rustdoc.css"))?;
write(cx.dst.join("main.css"),

View File

@ -1,25 +0,0 @@
// Copyright 2014-2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*jslint browser: true, es5: true */
/*globals $: true, rootPath: true */
document.addEventListener('DOMContentLoaded', function() {
'use strict';
if (!window.playgroundUrl) {
var runButtons = document.querySelectorAll(".test-arrow");
for (var i = 0; i < runButtons.length; i++) {
runButtons[i].classList.remove("test-arrow");
}
return;
}
});

View File

@ -1,48 +0,0 @@
// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*jslint browser: true, es5: true */
/*globals $: true, rootPath: true */
document.addEventListener('DOMContentLoaded', function() {
'use strict';
if (!window.playgroundUrl) {
var runButtons = document.querySelectorAll(".test-arrow");
for (var i = 0; i < runButtons.length; i++) {
runButtons[i].classList.remove("test-arrow");
}
return;
}
var featureRegexp = new RegExp('^\s*#!\\[feature\\(\.*?\\)\\]');
var elements = document.querySelectorAll('pre.rust-example-rendered');
Array.prototype.forEach.call(elements, function(el) {
el.onmouseover = function(e) {
if (el.contains(e.relatedTarget)) {
return;
}
var a = el.querySelectorAll('a.test-arrow')[0];
var code = el.previousElementSibling.textContent;
var channel = '';
if (featureRegexp.test(code)) {
channel = '&version=nightly';
}
a.setAttribute('href', window.playgroundUrl + '?code=' +
encodeURIComponent(code) + channel);
};
});
});

View File

@ -575,7 +575,6 @@ pre.rust .question-mark {
font-weight: bold;
}
.rusttest { display: none; }
pre.rust { position: relative; }
a.test-arrow {
background-color: rgba(78, 139, 202, 0.2);

View File

@ -63,11 +63,9 @@ pub fn render(input: &str, mut output: PathBuf, matches: &getopts::Matches,
Err(LoadStringError::ReadFail) => return 1,
Err(LoadStringError::BadUtf8) => return 2,
};
let playground = matches.opt_str("markdown-playground-url");
if playground.is_some() {
markdown::PLAYGROUND_KRATE.with(|s| { *s.borrow_mut() = Some(None); });
if let Some(playground) = matches.opt_str("markdown-playground-url") {
markdown::PLAYGROUND.with(|s| { *s.borrow_mut() = Some((None, playground)); });
}
let playground = playground.unwrap_or("".to_string());
let mut out = match File::create(&output) {
Err(e) => {
@ -119,9 +117,6 @@ pub fn render(input: &str, mut output: PathBuf, matches: &getopts::Matches,
{before_content}
<h1 class="title">{title}</h1>
{text}
<script type="text/javascript">
window.playgroundUrl = "{playground}";
</script>
{after_content}
</body>
</html>"#,
@ -131,7 +126,6 @@ pub fn render(input: &str, mut output: PathBuf, matches: &getopts::Matches,
before_content = external_html.before_content,
text = rendered,
after_content = external_html.after_content,
playground = playground,
);
match err {

View File

@ -355,7 +355,7 @@ pub fn maketest(s: &str, cratename: Option<&str>, dont_insert_main: bool,
if dont_insert_main || s.contains("fn main") {
prog.push_str(&everything_else);
} else {
prog.push_str("fn main() {\n ");
prog.push_str("fn main() {\n");
prog.push_str(&everything_else);
prog = prog.trim().into();
prog.push_str("\n}");

View File

@ -0,0 +1,21 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![crate_name = "foo"]
#![doc(html_playground_url = "")]
//! module docs
//!
//! ```
//! println!("Hello, world!");
//! ```
// @!has foo/index.html '//a[@class="test-arrow"]' "Run"

View File

@ -0,0 +1,19 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![crate_name = "foo"]
//! module docs
//!
//! ```
//! println!("Hello, world!");
//! ```
// @!has foo/index.html '//a[@class="test-arrow"]' "Run"

View File

@ -0,0 +1,39 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// ignore-tidy-linelength
#![crate_name = "foo"]
#![doc(html_playground_url = "https://www.example.com/")]
//! module docs
//!
//! ```
//! println!("Hello, world!");
//! ```
//!
//! ```
//! fn main() {
//! println!("Hello, world!");
//! }
//! ```
//!
//! ```
//! #![feature(something)]
//!
//! fn main() {
//! println!("Hello, world!");
//! }
//! ```
// @matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=fn%20main()%20%7B%0A%20%20%20%20println!(%22Hello%2C%20world!%22)%3B%0A%7D%0A"]' "Run"
// @matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=fn%20main()%20%7B%0Aprintln!(%22Hello%2C%20world!%22)%3B%0A%7D"]' "Run"
// @matches foo/index.html '//a[@class="test-arrow"][@href="https://www.example.com/?code=%23!%5Bfeature(something)%5D%0A%0Afn%20main()%20%7B%0A%20%20%20%20println!(%22Hello%2C%20world!%22)%3B%0A%7D%0A&version=nightly"]' "Run"

View File

@ -24,7 +24,7 @@ use std::path::PathBuf;
use syntax::diagnostics::metadata::{get_metadata_dir, ErrorMetadataMap, ErrorMetadata};
use rustdoc::html::markdown::Markdown;
use rustdoc::html::markdown::{Markdown, PLAYGROUND};
use rustc_serialize::json;
enum OutputFormat {
@ -201,6 +201,9 @@ fn parse_args() -> (OutputFormat, PathBuf) {
}
fn main() {
PLAYGROUND.with(|slot| {
*slot.borrow_mut() = Some((None, String::from("https://play.rust-lang.org/")));
});
let (format, dst) = parse_args();
if let Err(e) = main_with_result(format, &dst) {
panic!("{}", e.description());

View File

@ -131,7 +131,6 @@ fn render(book: &Book, tgt: &Path) -> CliResult<()> {
{
let mut buffer = BufWriter::new(File::create(&postlude)?);
writeln!(&mut buffer, "<script src='rustbook.js'></script>")?;
writeln!(&mut buffer, "<script src='playpen.js'></script>")?;
writeln!(&mut buffer, "</div></div>")?;
}
@ -143,7 +142,7 @@ fn render(book: &Book, tgt: &Path) -> CliResult<()> {
format!("-o{}", out_path.display()),
format!("--html-before-content={}", prelude.display()),
format!("--html-after-content={}", postlude.display()),
format!("--markdown-playground-url=https://play.rust-lang.org"),
format!("--markdown-playground-url=https://play.rust-lang.org/"),
format!("--markdown-css={}", item.path_to_root.join("rustbook.css").display()),
"--markdown-no-toc".to_string(),
];
@ -158,10 +157,6 @@ fn render(book: &Book, tgt: &Path) -> CliResult<()> {
// create index.html from the root README
fs::copy(&tgt.join("README.html"), &tgt.join("index.html"))?;
// Copy js for playpen
let mut playpen = File::create(tgt.join("playpen.js"))?;
let js = include_bytes!("../../librustdoc/html/static/playpen.js");
playpen.write_all(js)?;
Ok(())
}