rustdoc: Submit examples to play.rust-lang.org

This grows a new option inside of rustdoc to add the ability to submit examples
to an external website. If the `--markdown-playground-url` command line option
or crate doc attribute `html_playground_url` is present, then examples will have
a button on hover to submit the code to the playground specified.

This commit enables submission of example code to play.rust-lang.org. The code
submitted is that which is tested by rustdoc, not necessarily the exact code
shown in the example.

Closes #14654
This commit is contained in:
Alex Crichton
2014-06-06 09:12:18 -07:00
parent cc63d4c61b
commit e5bbbca33e
29 changed files with 186 additions and 43 deletions

View File

@@ -25,7 +25,7 @@ use html::escape::Escape;
use t = syntax::parse::token;
/// Highlights some source code, returning the HTML output.
pub fn highlight(src: &str, class: Option<&str>) -> String {
pub fn highlight(src: &str, class: Option<&str>, id: Option<&str>) -> String {
debug!("highlighting: ================\n{}\n==============", src);
let sess = parse::new_parse_sess();
let fm = parse::string_to_filemap(&sess,
@@ -36,6 +36,7 @@ pub fn highlight(src: &str, class: Option<&str>) -> String {
doit(&sess,
lexer::StringReader::new(&sess.span_diagnostic, fm),
class,
id,
&mut out).unwrap();
str::from_utf8_lossy(out.unwrap().as_slice()).to_string()
}
@@ -47,11 +48,17 @@ pub fn highlight(src: &str, class: Option<&str>) -> String {
/// it's used. All source code emission is done as slices from the source map,
/// not from the tokens themselves, in order to stay true to the original
/// source.
fn doit(sess: &parse::ParseSess, mut lexer: lexer::StringReader, class: Option<&str>,
fn doit(sess: &parse::ParseSess, mut lexer: lexer::StringReader,
class: Option<&str>, id: Option<&str>,
out: &mut Writer) -> io::IoResult<()> {
use syntax::parse::lexer::Reader;
try!(write!(out, "<pre class='rust {}'>\n", class.unwrap_or("")));
try!(write!(out, "<pre "));
match id {
Some(id) => try!(write!(out, "id='{}' ", id)),
None => {}
}
try!(write!(out, "class='rust {}'>\n", class.unwrap_or("")));
let mut last = BytePos(0);
let mut is_attribute = false;
let mut is_macro = false;

View File

@@ -16,6 +16,7 @@ pub struct Layout {
pub logo: String,
pub favicon: String,
pub krate: String,
pub playground_url: String,
}
pub struct Page<'a> {
@@ -108,11 +109,13 @@ r##"<!DOCTYPE html>
</div>
<script>
var rootPath = "{root_path}";
var currentCrate = "{krate}";
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 async src="{root_path}search-index.js"></script>
</body>
</html>"##,
@@ -124,6 +127,12 @@ r##"<!DOCTYPE html>
favicon = nonestr(layout.favicon.as_slice()),
sidebar = *sidebar,
krate = layout.krate,
play_url = layout.playground_url,
play_js = if layout.playground_url.len() == 0 {
"".to_string()
} else {
format!(r#"<script src="{}playpen.js"></script>"#, page.root_path)
},
)
}

View File

@@ -27,7 +27,7 @@
#![allow(non_camel_case_types)]
use libc;
use std::cell::RefCell;
use std::cell::{RefCell, Cell};
use std::fmt;
use std::slice;
use std::str;
@@ -35,6 +35,8 @@ use std::collections::HashMap;
use html::toc::TocBuilder;
use html::highlight;
use html::escape::Escape;
use test;
/// A unit struct which has the `fmt::Show` trait implemented. When
/// formatted, this struct will emit the HTML corresponding to the rendered
@@ -139,6 +141,9 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
}
local_data_key!(used_header_map: RefCell<HashMap<String, uint>>)
local_data_key!(test_idx: Cell<uint>)
// None == render an example, but there's no crate name
local_data_key!(pub playground_krate: Option<String>)
pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
extern fn block(ob: *mut hoedown_buffer, text: *hoedown_buffer,
@@ -149,9 +154,9 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
let opaque = opaque as *mut hoedown_html_renderer_state;
let my_opaque: &MyOpaque = &*((*opaque).opaque as *MyOpaque);
slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
let text = str::from_utf8(text).unwrap();
let origtext = str::from_utf8(text).unwrap();
debug!("docblock: ==============\n{}\n=======", text);
let mut lines = text.lines().filter(|l| {
let mut lines = origtext.lines().filter(|l| {
stripped_filtered_line(*l).is_none()
});
let text = lines.collect::<Vec<&str>>().connect("\n");
@@ -180,9 +185,26 @@ pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
};
if !rendered {
let output = highlight::highlight(text.as_slice(),
None).as_slice()
.to_c_str();
let mut s = String::new();
let id = playground_krate.get().map(|krate| {
let idx = test_idx.get().unwrap();
let i = idx.get();
idx.set(i + 1);
let test = origtext.lines().map(|l| {
stripped_filtered_line(l).unwrap_or(l)
}).collect::<Vec<&str>>().connect("\n");
let krate = krate.as_ref().map(|s| s.as_slice());
let test = test::maketest(test.as_slice(), krate, false);
s.push_str(format!("<span id='rust-example-raw-{}' \
class='rusttest'>{}</span>",
i, Escape(test.as_slice())).as_slice());
format!("rust-example-rendered-{}", i)
});
let id = id.as_ref().map(|a| a.as_slice());
s.push_str(highlight::highlight(text.as_slice(), None, id)
.as_slice());
let output = s.to_c_str();
output.with_ref(|r| {
hoedown_buffer_puts(ob, r)
})
@@ -377,6 +399,7 @@ fn parse_lang_string(string: &str) -> (bool,bool,bool,bool) {
/// previous state (if any).
pub fn reset_headers() {
used_header_map.replace(Some(RefCell::new(HashMap::new())));
test_idx.replace(Some(Cell::new(0)));
}
impl<'a> fmt::Show for Markdown<'a> {

View File

@@ -230,6 +230,7 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> {
logo: "".to_string(),
favicon: "".to_string(),
krate: krate.name.clone(),
playground_url: "".to_string(),
},
include_sources: true,
render_redirect_pages: false,
@@ -250,6 +251,14 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> {
if "html_logo_url" == x.as_slice() => {
cx.layout.logo = s.to_string();
}
clean::NameValue(ref x, ref s)
if "html_playground_url" == x.as_slice() => {
cx.layout.playground_url = s.to_string();
let name = krate.name.clone();
if markdown::playground_krate.get().is_none() {
markdown::playground_krate.replace(Some(Some(name)));
}
}
clean::Word(ref x)
if "html_no_source" == x.as_slice() => {
cx.include_sources = false;
@@ -450,6 +459,7 @@ fn write_shared(cx: &Context,
try!(write(cx.dst.join("jquery.js"),
include_bin!("static/jquery-2.1.0.min.js")));
try!(write(cx.dst.join("main.js"), include_bin!("static/main.js")));
try!(write(cx.dst.join("playpen.js"), include_bin!("static/playpen.js")));
try!(write(cx.dst.join("main.css"), include_bin!("static/main.css")));
try!(write(cx.dst.join("normalize.css"),
include_bin!("static/normalize.css")));
@@ -2055,14 +2065,15 @@ impl<'a> fmt::Show for Source<'a> {
try!(write!(fmt, "<span id='{0:u}'>{0:1$u}</span>\n", i, cols));
}
try!(write!(fmt, "</pre>"));
try!(write!(fmt, "{}", highlight::highlight(s.as_slice(), None)));
try!(write!(fmt, "{}", highlight::highlight(s.as_slice(), None, None)));
Ok(())
}
}
fn item_macro(w: &mut fmt::Formatter, it: &clean::Item,
t: &clean::Macro) -> fmt::Result {
try!(w.write(highlight::highlight(t.source.as_slice(), Some("macro")).as_bytes()));
try!(w.write(highlight::highlight(t.source.as_slice(), Some("macro"),
None).as_bytes()));
document(w, it)
}

View File

@@ -156,7 +156,7 @@ nav.sub {
padding: 0 10px;
margin-bottom: 10px;
}
.block h2 {
.block h2 {
margin-top: 0;
text-align: center;
}
@@ -396,6 +396,17 @@ pre.rust .doccomment { color: #4D4D4C; }
pre.rust .macro, pre.rust .macro-nonterminal { color: #3E999F; }
pre.rust .lifetime { color: #B76514; }
.rusttest { display: none; }
pre.rust { position: relative; }
pre.rust a { transform: scaleX(-1); }
.test-arrow {
display: inline-block;
position: absolute;
top: 0;
right: 10px;
font-size: 150%;
}
.methods .section-header {
/* Override parent class attributes. */
border-bottom: none !important;

View File

@@ -678,7 +678,7 @@
window.register_implementors(window.pending_implementors);
}
// See documentaiton in html/render.rs for what this is doing.
// See documentation in html/render.rs for what this is doing.
var query = getQueryStringParams();
if (query['gotosrc']) {
window.location = $('#src-' + query['gotosrc']).attr('href');

View File

@@ -0,0 +1,29 @@
// Copyright 2014 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 */
(function() {
if (window.playgroundUrl) {
$('pre.rust').hover(function() {
var id = '#' + $(this).attr('id').replace('rendered', 'raw');
var a = $('<a>').text('⇱').attr('class', 'test-arrow');
var code = $(id).text();
a.attr('href', window.playgroundUrl + '?code=' +
encodeURIComponent(code));
a.attr('target', '_blank');
$(this).append(a);
}, function() {
$(this).find('a').remove();
});
}
}());