fn resolve_type(path: Path, tpbs: Option<~[TyParamBound]>,
id: ast::NodeId) -> Type {
let cx = local_data::get(super::ctxtkey, |x| *x.unwrap());
+ let tycx = match cx.tycx {
+ Some(tycx) => tycx,
+ // If we're extracting tests, this return value doesn't matter.
+ None => return Bool
+ };
debug!("searching for {:?} in defmap", id);
- let d = match cx.tycx.def_map.find(&id) {
+ let d = match tycx.def_map.find(&id) {
Some(k) => k,
None => {
- let ctxt = local_data::get(super::ctxtkey, |x| *x.unwrap());
debug!("could not find {:?} in defmap (`{}`)", id,
- syntax::ast_map::node_id_to_str(ctxt.tycx.items, id, ctxt.sess.intr()));
+ syntax::ast_map::node_id_to_str(tycx.items, id, cx.sess.intr()));
fail!("Unexpected failure: unresolved id not in defmap (this is a bug!)")
}
};
if ast_util::is_local(def_id) {
ResolvedPath{ path: path, typarams: tpbs, id: def_id.node }
} else {
- let fqn = csearch::get_item_path(cx.tycx, def_id);
+ let fqn = csearch::get_item_path(tycx, def_id);
let fqn = fqn.move_iter().map(|i| {
match i {
ast_map::path_mod(id) |
}
fn resolve_def(id: ast::NodeId) -> Option<ast::DefId> {
- let dm = local_data::get(super::ctxtkey, |x| *x.unwrap()).tycx.def_map;
- dm.find(&id).map(|&d| ast_util::def_id_of_def(d))
+ let cx = local_data::get(super::ctxtkey, |x| *x.unwrap());
+ match cx.tycx {
+ Some(tcx) => {
+ tcx.def_map.find(&id).map(|&d| ast_util::def_id_of_def(d))
+ }
+ None => None
+ }
}
pub struct DocContext {
crate: ast::Crate,
- tycx: middle::ty::ctxt,
+ tycx: Option<middle::ty::ctxt>,
sess: driver::session::Session
}
} = phase_3_run_analysis_passes(sess, &crate);
debug!("crate: {:?}", crate);
- return (DocContext { crate: crate, tycx: ty_cx, sess: sess },
+ return (DocContext { crate: crate, tycx: Some(ty_cx), sess: sess },
CrateAnalysis { exported_items: exported_items });
}
pub fn run_core (libs: HashSet<Path>, cfgs: ~[~str], path: &Path) -> (clean::Crate, CrateAnalysis) {
let (ctxt, analysis) = get_ast_and_resolve(path, libs, cfgs);
let ctxt = @ctxt;
- debug!("defmap:");
- for (k, v) in ctxt.tycx.def_map.iter() {
- debug!("{:?}: {:?}", k, v);
- }
local_data::set(super::ctxtkey, ctxt);
let v = @mut RustdocVisitor::new();
//! // ... something using html
//! ```
+use std::cast;
use std::fmt;
-use std::libc;
use std::io;
+use std::libc;
+use std::str;
+use std::unstable::intrinsics;
use std::vec;
/// A unit struct which has the `fmt::Default` trait implemented. When
type sd_markdown = libc::c_void; // this is opaque to us
-// this is a large struct of callbacks we don't use
-type sd_callbacks = [libc::size_t, ..26];
+struct sd_callbacks {
+ blockcode: extern "C" fn(*buf, *buf, *buf, *libc::c_void),
+ other: [libc::size_t, ..25],
+}
struct html_toc_data {
header_count: libc::c_int,
link_attributes: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
}
+struct my_opaque {
+ opt: html_renderopt,
+ dfltblk: extern "C" fn(*buf, *buf, *buf, *libc::c_void),
+}
+
struct buf {
data: *u8,
size: libc::size_t,
}
-fn render(w: &mut io::Writer, s: &str) {
+pub fn render(w: &mut io::Writer, s: &str) {
+ extern fn block(ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) {
+ unsafe {
+ let my_opaque: &my_opaque = cast::transmute(opaque);
+ vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
+ let text = str::from_utf8(text);
+ let mut lines = text.lines().filter(|l| {
+ !l.trim().starts_with("#")
+ });
+ let text = lines.to_owned_vec().connect("\n");
+
+ let buf = buf {
+ data: text.as_bytes().as_ptr(),
+ size: text.len() as libc::size_t,
+ asize: text.len() as libc::size_t,
+ unit: 0,
+ };
+ (my_opaque.dfltblk)(ob, &buf, lang, opaque);
+ })
+ }
+ }
+
// This code is all lifted from examples/sundown.c in the sundown repo
unsafe {
let ob = bufnew(OUTPUT_UNIT);
flags: 0,
link_attributes: None,
};
- let callbacks: sd_callbacks = [0, ..26];
+ let mut callbacks: sd_callbacks = intrinsics::init();
sdhtml_renderer(&callbacks, &options, 0);
+ let opaque = my_opaque {
+ opt: options,
+ dfltblk: callbacks.blockcode,
+ };
+ callbacks.blockcode = block;
let markdown = sd_markdown_new(extensions, 16, &callbacks,
- &options as *html_renderopt as *libc::c_void);
+ &opaque as *my_opaque as *libc::c_void);
sd_markdown_render(ob, s.as_ptr(), s.len() as libc::size_t, markdown);
}
}
+pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
+ extern fn block(_ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) {
+ unsafe {
+ if text.is_null() || lang.is_null() { return }
+ let (test, shouldfail, ignore) =
+ vec::raw::buf_as_slice((*lang).data,
+ (*lang).size as uint, |lang| {
+ let s = str::from_utf8(lang);
+ (s.contains("rust"), s.contains("should_fail"),
+ s.contains("ignore"))
+ });
+ if !test { return }
+ vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
+ let tests: &mut ::test::Collector = intrinsics::transmute(opaque);
+ let text = str::from_utf8(text);
+ let mut lines = text.lines().map(|l| l.trim_chars(&'#'));
+ let text = lines.to_owned_vec().connect("\n");
+ tests.add_test(text, ignore, shouldfail);
+ })
+ }
+ }
+
+ unsafe {
+ let ob = bufnew(OUTPUT_UNIT);
+ let extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_TABLES |
+ MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK |
+ MKDEXT_STRIKETHROUGH;
+ let callbacks = sd_callbacks {
+ blockcode: block,
+ other: intrinsics::init()
+ };
+
+ let tests = tests as *mut ::test::Collector as *libc::c_void;
+ let markdown = sd_markdown_new(extensions, 16, &callbacks, tests);
+
+ sd_markdown_render(ob, doc.as_ptr(), doc.len() as libc::size_t,
+ markdown);
+ sd_markdown_free(markdown);
+ bufrelease(ob);
+ }
+}
+
impl<'a> fmt::Default for Markdown<'a> {
fn fmt(md: &Markdown<'a>, fmt: &mut fmt::Formatter) {
// This is actually common enough to special-case
pub mod passes;
pub mod plugins;
pub mod visit_ast;
+pub mod test;
pub static SCHEMA_VERSION: &'static str = "0.8.1";
optmulti("", "plugins", "space separated list of plugins to also load",
"PLUGINS"),
optflag("", "no-defaults", "don't run the default passes"),
+ optflag("", "test", "run code examples as tests"),
+ optmulti("", "test-args", "arguments to pass to the test runner",
+ "ARGS"),
]
}
return 0;
}
+ if matches.free.len() == 0 {
+ println("expected an input file to act on");
+ return 1;
+ } if matches.free.len() > 1 {
+ println("only one input file may be specified");
+ return 1;
+ }
+ let input = matches.free[0].as_slice();
+
+ if matches.opt_present("test") {
+ return test::run(input, &matches);
+ }
+
if matches.opt_strs("passes") == ~[~"list"] {
println("Available passes for running rustdoc:");
for &(name, _, description) in PASSES.iter() {
return 0;
}
- let (crate, res) = match acquire_input(&matches) {
+ let (crate, res) = match acquire_input(input, &matches) {
Ok(pair) => pair,
Err(s) => {
println!("input error: {}", s);
/// Looks inside the command line arguments to extract the relevant input format
/// and files and then generates the necessary rustdoc output for formatting.
-fn acquire_input(matches: &getopts::Matches) -> Result<Output, ~str> {
- if matches.free.len() == 0 {
- return Err(~"expected an input file to act on");
- } if matches.free.len() > 1 {
- return Err(~"only one input file may be specified");
- }
-
- let input = matches.free[0].as_slice();
+fn acquire_input(input: &str,
+ matches: &getopts::Matches) -> Result<Output, ~str> {
match matches.opt_str("r") {
Some(~"rust") => Ok(rust_input(input, matches)),
Some(~"json") => json_input(input),
--- /dev/null
+// Copyright 2013 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.
+
+use std::hashmap::HashSet;
+use std::local_data;
+use std::os;
+use std::run;
+use std::str;
+
+use extra::tempfile::TempDir;
+use extra::getopts;
+use extra::test;
+use rustc::driver::driver;
+use rustc::driver::session;
+use syntax::diagnostic;
+use syntax::parse;
+
+use core;
+use clean;
+use clean::Clean;
+use fold::DocFolder;
+use html::markdown;
+use passes;
+use visit_ast::RustdocVisitor;
+
+pub fn run(input: &str, matches: &getopts::Matches) -> int {
+ let parsesess = parse::new_parse_sess(None);
+ let input = driver::file_input(Path::new(input));
+ let libs = matches.opt_strs("L").map(|s| Path::new(s.as_slice()));
+ let libs = @mut libs.move_iter().collect();
+
+ let sessopts = @session::options {
+ binary: @"rustdoc",
+ maybe_sysroot: Some(@os::self_exe_path().unwrap().dir_path()),
+ addl_lib_search_paths: libs,
+ outputs: ~[session::OutputDylib],
+ .. (*session::basic_options()).clone()
+ };
+
+
+ let diagnostic_handler = diagnostic::mk_handler(None);
+ let span_diagnostic_handler =
+ diagnostic::mk_span_handler(diagnostic_handler, parsesess.cm);
+
+ let sess = driver::build_session_(sessopts,
+ parsesess.cm,
+ @diagnostic::DefaultEmitter as
+ @diagnostic::Emitter,
+ span_diagnostic_handler);
+
+ let cfg = driver::build_configuration(sess);
+ let mut crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
+ crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
+
+ let ctx = @core::DocContext {
+ crate: crate,
+ tycx: None,
+ sess: sess,
+ };
+ local_data::set(super::ctxtkey, ctx);
+
+ let v = @mut RustdocVisitor::new();
+ v.visit(&ctx.crate);
+ let crate = v.clean();
+ let (crate, _) = passes::unindent_comments(crate);
+ let (crate, _) = passes::collapse_docs(crate);
+
+ let mut collector = Collector {
+ tests: ~[],
+ names: ~[],
+ cnt: 0,
+ libs: libs,
+ cratename: crate.name.to_owned(),
+ };
+ collector.fold_crate(crate);
+
+ let args = matches.opt_strs("test-args");
+ let mut args = args.iter().flat_map(|s| s.words()).map(|s| s.to_owned());
+ let mut args = args.to_owned_vec();
+ args.unshift(~"rustdoctest");
+
+ test::test_main(args, collector.tests);
+
+ 0
+}
+
+fn runtest(test: &str, cratename: &str, libs: HashSet<Path>) {
+ let test = maketest(test, cratename);
+ let parsesess = parse::new_parse_sess(None);
+ let input = driver::str_input(test);
+
+ let sessopts = @session::options {
+ binary: @"rustdoctest",
+ maybe_sysroot: Some(@os::self_exe_path().unwrap().dir_path()),
+ addl_lib_search_paths: @mut libs,
+ outputs: ~[session::OutputExecutable],
+ debugging_opts: session::prefer_dynamic,
+ .. (*session::basic_options()).clone()
+ };
+
+ let diagnostic_handler = diagnostic::mk_handler(None);
+ let span_diagnostic_handler =
+ diagnostic::mk_span_handler(diagnostic_handler, parsesess.cm);
+
+ let sess = driver::build_session_(sessopts,
+ parsesess.cm,
+ @diagnostic::DefaultEmitter as
+ @diagnostic::Emitter,
+ span_diagnostic_handler);
+
+ let outdir = TempDir::new("rustdoctest").expect("rustdoc needs a tempdir");
+ let out = Some(outdir.path().clone());
+ let cfg = driver::build_configuration(sess);
+ driver::compile_input(sess, cfg, &input, &out, &None);
+
+ let exe = outdir.path().join("rust_out");
+ let out = run::process_output(exe.as_str().unwrap(), []);
+ match out {
+ None => fail!("couldn't run the test"),
+ Some(out) => {
+ if !out.status.success() {
+ fail!("test executable failed:\n{}",
+ str::from_utf8(out.error));
+ }
+ }
+ }
+}
+
+fn maketest(s: &str, cratename: &str) -> @str {
+ let mut prog = ~r"
+#[deny(warnings)];
+#[allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)];
+#[feature(macro_rules, globs, struct_variant, managed_boxes)];
+";
+ if s.contains("extra") {
+ prog.push_str("extern mod extra;\n");
+ }
+ if s.contains(cratename) {
+ prog.push_str(format!("extern mod {};\n", cratename));
+ }
+ if s.contains("fn main") {
+ prog.push_str(s);
+ } else {
+ prog.push_str("fn main() {\n");
+ prog.push_str(s);
+ prog.push_str("\n}");
+ }
+
+ return prog.to_managed();
+}
+
+pub struct Collector {
+ priv tests: ~[test::TestDescAndFn],
+ priv names: ~[~str],
+ priv libs: @mut HashSet<Path>,
+ priv cnt: uint,
+ priv cratename: ~str,
+}
+
+impl Collector {
+ pub fn add_test(&mut self, test: &str, ignore: bool, should_fail: bool) {
+ let test = test.to_owned();
+ let name = format!("{}_{}", self.names.connect("::"), self.cnt);
+ self.cnt += 1;
+ let libs = (*self.libs).clone();
+ let cratename = self.cratename.to_owned();
+ self.tests.push(test::TestDescAndFn {
+ desc: test::TestDesc {
+ name: test::DynTestName(name),
+ ignore: ignore,
+ should_fail: should_fail,
+ },
+ testfn: test::DynTestFn(proc() {
+ runtest(test, cratename, libs);
+ }),
+ });
+ }
+}
+
+impl DocFolder for Collector {
+ fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
+ let pushed = match item.name {
+ Some(ref name) if name.len() == 0 => false,
+ Some(ref name) => { self.names.push(name.to_owned()); true }
+ None => false
+ };
+ match item.doc_value() {
+ Some(doc) => {
+ self.cnt = 0;
+ markdown::find_testable_code(doc, self);
+ }
+ None => {}
+ }
+ let ret = self.fold_item_recur(item);
+ if pushed {
+ self.names.pop();
+ }
+ return ret;
+ }
+}
//! usable for clean
use syntax::abi::AbiSet;
-use syntax::{ast, ast_map};
+use syntax::ast;
use syntax::codemap::Span;
use doctree::*;
-use std::local_data;
pub struct RustdocVisitor {
module: Module,
}
fn visit_mod_contents(span: Span, attrs: ~[ast::Attribute], vis:
- ast::visibility, id: ast::NodeId, m: &ast::_mod) -> Module {
- let am = local_data::get(super::ctxtkey, |x| *x.unwrap()).tycx.items;
- let name = match am.find(&id) {
- Some(m) => match m {
- &ast_map::node_item(ref it, _) => Some(it.ident),
- _ => fail!("mod id mapped to non-item in the ast map")
- },
- None => None
- };
+ ast::visibility, id: ast::NodeId, m: &ast::_mod,
+ name: Option<ast::Ident>) -> Module {
let mut om = Module::new(name);
om.view_items = m.view_items.clone();
om.where = span;
match item.node {
ast::item_mod(ref m) => {
om.mods.push(visit_mod_contents(item.span, item.attrs.clone(),
- item.vis, item.id, m));
+ item.vis, item.id, m,
+ Some(item.ident)));
},
ast::item_enum(ref ed, ref gen) => om.enums.push(visit_enum_def(item, ed, gen)),
ast::item_struct(sd, ref gen) => om.structs.push(visit_struct_def(item, sd, gen)),
}
self.module = visit_mod_contents(crate.span, crate.attrs.clone(),
- ast::public, ast::CRATE_NODE_ID, &crate.module);
+ ast::public, ast::CRATE_NODE_ID,
+ &crate.module, None);
}
}