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> </p><p>
This file may not be copied, modified, or distributed except according to those terms. This file may not be copied, modified, or distributed except according to those terms.
</p></footer> </p></footer>
<script type="text/javascript" src="playpen.js"></script>

View File

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

View File

@ -19,7 +19,6 @@ pub struct Layout {
pub favicon: String, pub favicon: String,
pub external_html: ExternalHtml, pub external_html: ExternalHtml,
pub krate: String, pub krate: String,
pub playground_url: String,
} }
pub struct Page<'a> { pub struct Page<'a> {
@ -136,11 +135,9 @@ r##"<!DOCTYPE html>
<script> <script>
window.rootPath = "{root_path}"; window.rootPath = "{root_path}";
window.currentCrate = "{krate}"; window.currentCrate = "{krate}";
window.playgroundUrl = "{play_url}";
</script> </script>
<script src="{root_path}jquery.js"></script> <script src="{root_path}jquery.js"></script>
<script src="{root_path}main.js"></script> <script src="{root_path}main.js"></script>
{play_js}
<script defer src="{root_path}search-index.js"></script> <script defer src="{root_path}search-index.js"></script>
</body> </body>
</html>"##, </html>"##,
@ -174,12 +171,6 @@ r##"<!DOCTYPE html>
after_content = layout.external_html.after_content, after_content = layout.external_html.after_content,
sidebar = *sidebar, sidebar = *sidebar,
krate = layout.krate, 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::cell::RefCell;
use std::default::Default; use std::default::Default;
use std::ffi::CString; use std::ffi::CString;
use std::fmt; use std::fmt::{self, Write};
use std::slice; use std::slice;
use std::str; use std::str;
use syntax::feature_gate::UnstableFeatures; use syntax::feature_gate::UnstableFeatures;
@ -214,7 +214,9 @@ fn collapse_whitespace(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ") 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) 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"); let text = lines.collect::<Vec<&str>>().join("\n");
if rendered { return } if rendered { return }
PLAYGROUND_KRATE.with(|krate| { PLAYGROUND.with(|play| {
// insert newline to clearly separate it from the // insert newline to clearly separate it from the
// previous block so we can shorten the html output // previous block so we can shorten the html output
let mut s = String::from("\n"); 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| { let test = origtext.lines().map(|l| {
stripped_filtered_line(l).unwrap_or(l) stripped_filtered_line(l).unwrap_or(l)
}).collect::<Vec<&str>>().join("\n"); }).collect::<Vec<&str>>().join("\n");
let krate = krate.as_ref().map(|s| &**s); let krate = krate.as_ref().map(|s| &**s);
let test = test::maketest(&test, krate, false, let test = test::maketest(&test, krate, false,
&Default::default()); &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( s.push_str(&highlight::render_with_highlighting(
&text, &text,
Some("rust-example-rendered"), Some("rust-example-rendered"),
None, 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(); let output = CString::new(s).unwrap();
hoedown_buffer_puts(ob, output.as_ptr()); hoedown_buffer_puts(ob, output.as_ptr());
}) })

View File

@ -449,7 +449,6 @@ pub fn run(mut krate: clean::Crate,
favicon: "".to_string(), favicon: "".to_string(),
external_html: external_html.clone(), external_html: external_html.clone(),
krate: krate.name.clone(), krate: krate.name.clone(),
playground_url: "".to_string(),
}, },
css_file_extension: css_file_extension.clone(), css_file_extension: css_file_extension.clone(),
}; };
@ -469,11 +468,10 @@ pub fn run(mut krate: clean::Crate,
} }
clean::NameValue(ref x, ref s) clean::NameValue(ref x, ref s)
if "html_playground_url" == *x => { if "html_playground_url" == *x => {
scx.layout.playground_url = s.to_string(); markdown::PLAYGROUND.with(|slot| {
markdown::PLAYGROUND_KRATE.with(|slot| {
if slot.borrow().is_none() { if slot.borrow().is_none() {
let name = krate.name.clone(); 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"))?; include_bytes!("static/jquery-2.1.4.min.js"))?;
write(cx.dst.join("main.js"), write(cx.dst.join("main.js"),
include_bytes!("static/main.js"))?; include_bytes!("static/main.js"))?;
write(cx.dst.join("playpen.js"),
include_bytes!("static/playpen.js"))?;
write(cx.dst.join("rustdoc.css"), write(cx.dst.join("rustdoc.css"),
include_bytes!("static/rustdoc.css"))?; include_bytes!("static/rustdoc.css"))?;
write(cx.dst.join("main.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; font-weight: bold;
} }
.rusttest { display: none; }
pre.rust { position: relative; } pre.rust { position: relative; }
a.test-arrow { a.test-arrow {
background-color: rgba(78, 139, 202, 0.2); 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::ReadFail) => return 1,
Err(LoadStringError::BadUtf8) => return 2, Err(LoadStringError::BadUtf8) => return 2,
}; };
let playground = matches.opt_str("markdown-playground-url"); if let Some(playground) = matches.opt_str("markdown-playground-url") {
if playground.is_some() { markdown::PLAYGROUND.with(|s| { *s.borrow_mut() = Some((None, playground)); });
markdown::PLAYGROUND_KRATE.with(|s| { *s.borrow_mut() = Some(None); });
} }
let playground = playground.unwrap_or("".to_string());
let mut out = match File::create(&output) { let mut out = match File::create(&output) {
Err(e) => { Err(e) => {
@ -119,9 +117,6 @@ pub fn render(input: &str, mut output: PathBuf, matches: &getopts::Matches,
{before_content} {before_content}
<h1 class="title">{title}</h1> <h1 class="title">{title}</h1>
{text} {text}
<script type="text/javascript">
window.playgroundUrl = "{playground}";
</script>
{after_content} {after_content}
</body> </body>
</html>"#, </html>"#,
@ -131,7 +126,6 @@ pub fn render(input: &str, mut output: PathBuf, matches: &getopts::Matches,
before_content = external_html.before_content, before_content = external_html.before_content,
text = rendered, text = rendered,
after_content = external_html.after_content, after_content = external_html.after_content,
playground = playground,
); );
match err { 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") { if dont_insert_main || s.contains("fn main") {
prog.push_str(&everything_else); prog.push_str(&everything_else);
} else { } else {
prog.push_str("fn main() {\n "); prog.push_str("fn main() {\n");
prog.push_str(&everything_else); prog.push_str(&everything_else);
prog = prog.trim().into(); prog = prog.trim().into();
prog.push_str("\n}"); 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 syntax::diagnostics::metadata::{get_metadata_dir, ErrorMetadataMap, ErrorMetadata};
use rustdoc::html::markdown::Markdown; use rustdoc::html::markdown::{Markdown, PLAYGROUND};
use rustc_serialize::json; use rustc_serialize::json;
enum OutputFormat { enum OutputFormat {
@ -201,6 +201,9 @@ fn parse_args() -> (OutputFormat, PathBuf) {
} }
fn main() { fn main() {
PLAYGROUND.with(|slot| {
*slot.borrow_mut() = Some((None, String::from("https://play.rust-lang.org/")));
});
let (format, dst) = parse_args(); let (format, dst) = parse_args();
if let Err(e) = main_with_result(format, &dst) { if let Err(e) = main_with_result(format, &dst) {
panic!("{}", e.description()); panic!("{}", e.description());

View File

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