rustdoc: Out with the old, in with the new

Removes old rustdoc, moves rustdoc_ng into its place instead (plus drops the _ng
suffix). Also shreds all reference to rustdoc_ng from the Makefile rules.
This commit is contained in:
Alex Crichton 2013-09-21 23:25:48 -07:00
parent 5f6a8ae966
commit 7b24efd6f3
54 changed files with 280 additions and 6977 deletions

View File

@ -214,7 +214,6 @@ CFG_LIBRUSTC_$(1) :=$(call CFG_LIB_NAME_$(1),rustc)
CFG_LIBSYNTAX_$(1) :=$(call CFG_LIB_NAME_$(1),syntax)
CFG_LIBRUSTPKG_$(1) :=$(call CFG_LIB_NAME_$(1),rustpkg)
CFG_LIBRUSTDOC_$(1) :=$(call CFG_LIB_NAME_$(1),rustdoc)
CFG_LIBRUSTDOCNG_$(1) :=$(call CFG_LIB_NAME_$(1),rustdoc_ng)
CFG_LIBRUSTI_$(1) :=$(call CFG_LIB_NAME_$(1),rusti)
CFG_LIBRUST_$(1) :=$(call CFG_LIB_NAME_$(1),rust)
@ -224,7 +223,6 @@ LIBRUSTC_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustc)
LIBSYNTAX_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),syntax)
LIBRUSTPKG_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustpkg)
LIBRUSTDOC_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustdoc)
LIBRUSTDOCNG_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rustdoc_ng)
LIBRUSTI_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rusti)
LIBRUST_GLOB_$(1) :=$(call CFG_LIB_GLOB_$(1),rust)
EXTRALIB_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),extra)
@ -233,7 +231,6 @@ LIBRUSTC_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustc)
LIBSYNTAX_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),syntax)
LIBRUSTPKG_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustpkg)
LIBRUSTDOC_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustdoc)
LIBRUSTDOCNG_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rustdoc_ng)
LIBRUSTI_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rusti)
LIBRUST_DSYM_GLOB_$(1) :=$(call CFG_LIB_DSYM_GLOB_$(1),rust)
@ -442,12 +439,10 @@ CSREQ$(1)_T_$(2)_H_$(3) = \
$$(TSREQ$(1)_T_$(2)_H_$(3)) \
$$(HBIN$(1)_H_$(3))/rustpkg$$(X_$(3)) \
$$(HBIN$(1)_H_$(3))/rustdoc$$(X_$(3)) \
$$(HBIN$(1)_H_$(3))/rustdoc_ng$$(X_$(3)) \
$$(HBIN$(1)_H_$(3))/rusti$$(X_$(3)) \
$$(HBIN$(1)_H_$(3))/rust$$(X_$(3)) \
$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTPKG_$(3)) \
$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTDOC_$(3)) \
$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(3)) \
$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUSTI_$(3)) \
$$(HLIB$(1)_H_$(3))/$(CFG_LIBRUST_$(3)) \
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2)) \
@ -456,7 +451,6 @@ CSREQ$(1)_T_$(2)_H_$(3) = \
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTC_$(2)) \
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTPKG_$(2)) \
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOC_$(2)) \
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(2)) \
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTI_$(2)) \
$$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUST_$(2))

View File

@ -143,7 +143,8 @@ Version 0.8 (October 2013)
* The runtime uses jemalloc for allocations.
* Segmented stacks are temporarily disabled as part of the transition to
the new runtime. Stack overflows are possible!
* A new documentation backend, rustdoc_ng, is available for use
* A new documentation backend, rustdoc_ng, is available for use. It is
still invoked through the normal `rustdoc` command.
Version 0.7 (July 2013)
-----------------------

View File

@ -68,12 +68,10 @@ clean$(1)_H_$(2):
$(Q)rm -f $$(HBIN$(1)_H_$(2))/rustpkg$(X_$(2))
$(Q)rm -f $$(HBIN$(1)_H_$(2))/serializer$(X_$(2))
$(Q)rm -f $$(HBIN$(1)_H_$(2))/rustdoc$(X_$(2))
$(Q)rm -f $$(HBIN$(1)_H_$(2))/rustdoc_ng$(X_$(2))
$(Q)rm -f $$(HBIN$(1)_H_$(2))/rusti$(X_$(2))
$(Q)rm -f $$(HBIN$(1)_H_$(2))/rust$(X_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTPKG_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTDOC_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_LIBRUSTDOCNG_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_RUNTIME_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_STDLIB_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_EXTRALIB_$(2))
@ -87,7 +85,6 @@ clean$(1)_H_$(2):
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBSYNTAX_GLOB_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTPKG_GLOB_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTDOC_GLOB_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTDOCNG_GLOB_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUSTI_GLOB_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(LIBRUST_GLOB_$(2))
$(Q)rm -f $$(HLIB$(1)_H_$(2))/$(CFG_RUSTLLVM_$(2))
@ -106,12 +103,10 @@ clean$(1)_T_$(2)_H_$(3):
$(Q)rm -f $$(TBIN$(1)_T_$(2)_H_$(3))/rustpkg$(X_$(2))
$(Q)rm -f $$(TBIN$(1)_T_$(2)_H_$(3))/serializer$(X_$(2))
$(Q)rm -f $$(TBIN$(1)_T_$(2)_H_$(3))/rustdoc$(X_$(2))
$(Q)rm -f $$(TBIN$(1)_T_$(2)_H_$(3))/rustdoc_ng$(X_$(2))
$(Q)rm -f $$(TBIN$(1)_T_$(2)_H_$(3))/rusti$(X_$(2))
$(Q)rm -f $$(TBIN$(1)_T_$(2)_H_$(3))/rust$(X_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTPKG_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOC_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_RUNTIME_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_STDLIB_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_EXTRALIB_$(2))
@ -125,7 +120,6 @@ clean$(1)_T_$(2)_H_$(3):
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBSYNTAX_GLOB_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTPKG_GLOB_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTDOC_GLOB_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTDOCNG_GLOB_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUSTI_GLOB_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(LIBRUST_GLOB_$(2))
$(Q)rm -f $$(TLIB$(1)_T_$(2)_H_$(3))/$(CFG_RUSTLLVM_$(2))

View File

@ -39,7 +39,6 @@ PKG_FILES := \
libsyntax \
rt \
librustdoc \
rustdoc_ng \
rustllvm \
snapshots.txt \
test) \

View File

@ -213,40 +213,21 @@ else
# The rustdoc executable
RUSTDOC = $(HBIN2_H_$(CFG_BUILD_TRIPLE))/rustdoc$(X_$(CFG_BUILD_TRIPLE))
RUSTDOC_NG = $(HBIN2_H_$(CFG_BUILD_TRIPLE))/rustdoc_ng$(X_$(CFG_BUILD_TRIPLE))
# The library documenting macro
# $(1) - The output directory
# $(1) - The crate name (std/extra)
# $(2) - The crate file
# $(3) - The crate soruce files
# $(3) - The relevant host build triple (to depend on libstd)
define libdoc
doc/$(1)/index.html: $(2) $(3) $$(RUSTDOC) doc/$(1)/rust.css
doc/$(1)/index.html: $$(RUSTDOC) $$(TLIB2_T_$(3)_H_$(3))/$(CFG_STDLIB_$(3))
@$$(call E, rustdoc: $$@)
$(Q)$(RUSTDOC) $(2) --output-dir=doc/$(1)
doc/$(1)/rust.css: rust.css
@$$(call E, cp: $$@)
$(Q)cp $$< $$@
$(Q)$(RUSTDOC) html $(2)
DOCS += doc/$(1)/index.html
endef
# The "next generation" library documenting macro
# $(1) - The crate name (std/extra)
# $(2) - The crate file
# $(3) - The relevant host build triple (to depend on libstd)
define libdocng
doc/ng/$(1)/index.html: $$(RUSTDOC_NG) $$(TLIB2_T_$(3)_H_$(3))/$(CFG_STDLIB_$(3))
@$$(call E, rustdoc_ng: $$@)
$(Q)$(RUSTDOC_NG) html $(2) -o doc/ng
DOCS += doc/ng/$(1)/index.html
endef
$(eval $(call libdoc,std,$(STDLIB_CRATE),$(STDLIB_INPUTS)))
$(eval $(call libdoc,extra,$(EXTRALIB_CRATE),$(EXTRALIB_INPUTS)))
$(eval $(call libdocng,std,$(STDLIB_CRATE),$(CFG_BUILD_TRIPLE)))
$(eval $(call libdocng,extra,$(EXTRALIB_CRATE),$(CFG_BUILD_TRIPLE)))
$(eval $(call libdoc,std,$(STDLIB_CRATE),$(CFG_BUILD_TRIPLE)))
$(eval $(call libdoc,extra,$(EXTRALIB_CRATE),$(CFG_BUILD_TRIPLE)))
endif

View File

@ -104,7 +104,6 @@ install-target-$(1)-host-$(2): $$(CSREQ$$(ISTAGE)_T_$(1)_H_$(2))
$$(Q)$$(call INSTALL_LIB,$$(LIBSYNTAX_GLOB_$(1)))
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTPKG_GLOB_$(1)))
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTDOC_GLOB_$(1)))
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTDOCNG_GLOB_$(1)))
$$(Q)$$(call INSTALL_LIB,$$(LIBRUSTI_GLOB_$(1)))
$$(Q)$$(call INSTALL_LIB,$$(LIBRUST_GLOB_$(1)))
$$(Q)$$(call INSTALL_LIB,libmorestack.a)
@ -140,7 +139,6 @@ install-host: $(CSREQ$(ISTAGE)_T_$(CFG_BUILD_TRIPLE)_H_$(CFG_BUILD_TRIPLE))
$(Q)$(call INSTALL,$(HB2),$(PHB),rustc$(X_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL,$(HB2),$(PHB),rustpkg$(X_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL,$(HB2),$(PHB),rustdoc$(X_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL,$(HB2),$(PHB),rustdoc_ng$(X_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL,$(HB2),$(PHB),rusti$(X_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL,$(HB2),$(PHB),rust$(X_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL_LIB,$(STDLIB_GLOB_$(CFG_BUILD_TRIPLE)))
@ -151,7 +149,6 @@ install-host: $(CSREQ$(ISTAGE)_T_$(CFG_BUILD_TRIPLE)_H_$(CFG_BUILD_TRIPLE))
$(Q)$(call INSTALL_LIB,$(LIBRUST_GLOB_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL_LIB,$(LIBRUSTPKG_GLOB_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL_LIB,$(LIBRUSTDOC_GLOB_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL_LIB,$(LIBRUSTDOCNG_GLOB_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_RUNTIME_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL,$(HL),$(PHL),$(CFG_RUSTLLVM_$(CFG_BUILD_TRIPLE)))
$(Q)$(call INSTALL,$(S)/man, $(PREFIX_ROOT)/share/man/man1,rust.1)
@ -172,7 +169,6 @@ uninstall:
$(Q)rm -f $(PHB)/rusti$(X_$(CFG_BUILD_TRIPLE))
$(Q)rm -f $(PHB)/rust$(X_$(CFG_BUILD_TRIPLE))
$(Q)rm -f $(PHB)/rustdoc$(X_$(CFG_BUILD_TRIPLE))
$(Q)rm -f $(PHB)/rustdoc_ng$(X_$(CFG_BUILD_TRIPLE))
$(Q)rm -f $(PHL)/$(CFG_RUSTLLVM_$(CFG_BUILD_TRIPLE))
$(Q)rm -f $(PHL)/$(CFG_RUNTIME_$(CFG_BUILD_TRIPLE))
$(Q)for i in \
@ -182,7 +178,6 @@ uninstall:
$(call HOST_LIB_FROM_HL_GLOB,$(LIBSYNTAX_GLOB_$(CFG_BUILD_TRIPLE))) \
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUSTPKG_GLOB_$(CFG_BUILD_TRIPLE))) \
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUSTDOC_GLOB_$(CFG_BUILD_TRIPLE))) \
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUSTDOCNG_GLOB_$(CFG_BUILD_TRIPLE))) \
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUSTI_GLOB_$(CFG_BUILD_TRIPLE))) \
$(call HOST_LIB_FROM_HL_GLOB,$(LIBRUST_GLOB_$(CFG_BUILD_TRIPLE))) \
; \

View File

@ -15,7 +15,7 @@
# The names of crates that must be tested
TEST_TARGET_CRATES = std extra
TEST_HOST_CRATES = rust rusti rustpkg rustc rustdoc rustdocng syntax
TEST_HOST_CRATES = rust rusti rustpkg rustc rustdoc syntax
TEST_CRATES = $(TEST_TARGET_CRATES) $(TEST_HOST_CRATES)
# Markdown files under doc/ that should have their code extracted and run
@ -393,14 +393,6 @@ $(3)/stage$(1)/test/rustdoctest-$(2)$$(X_$(2)): \
@$$(call E, compile_and_link: $$@)
$$(STAGE$(1)_T_$(2)_H_$(3)) -o $$@ $$< --test
$(3)/stage$(1)/test/rustdocngtest-$(2)$$(X_$(2)): \
$$(RUSTDOCNG_LIB) $$(RUSTDOCNG_INPUTS) \
$$(SREQ$(1)_T_$(2)_H_$(3)) \
$$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_LIBSYNTAX_$(2)) \
$$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_LIBRUSTC_$(2))
@$$(call E, compile_and_link: $$@)
$$(STAGE$(1)_T_$(2)_H_$(3)) -o $$@ $$< --test
endef
$(foreach host,$(CFG_HOST_TRIPLES), \

View File

@ -23,11 +23,6 @@ RUSTPKG_INPUTS := $(wildcard $(S)src/librustpkg/*.rs)
RUSTDOC_LIB := $(S)src/librustdoc/rustdoc.rs
RUSTDOC_INPUTS := $(wildcard $(S)src/librustdoc/*.rs)
# rustdoc_ng, the next generation documentation tool
RUSTDOCNG_LIB := $(S)src/rustdoc_ng/rustdoc_ng.rs
RUSTDOCNG_INPUTS := $(wildcard $(S)src/rustdoc_ng/*.rs)
# Rusti, the JIT REPL
RUSTI_LIB := $(S)src/librusti/rusti.rs
RUSTI_INPUTS := $(wildcard $(S)src/librusti/*.rs)
@ -83,24 +78,6 @@ $$(TBIN$(1)_T_$(4)_H_$(3))/rustdoc$$(X_$(4)): \
@$$(call E, compile_and_link: $$@)
$$(STAGE$(1)_T_$(4)_H_$(3)) --cfg rustdoc -o $$@ $$<
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(4)): \
$$(RUSTDOCNG_LIB) $$(RUSTDOCNG_INPUTS) \
$$(SREQ$(1)_T_$(4)_H_$(3)) \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTC_$(4)) \
| $$(TLIB$(1)_T_$(4)_H_$(3))/
@$$(call E, compile_and_link: $$@)
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTDOCNG_GLOB_$(4)),$$(notdir $$@))
$$(STAGE$(1)_T_$(4)_H_$(3)) --out-dir $$(@D) $$< && touch $$@
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTDOCNG_GLOB_$(4)),$$(notdir $$@))
$$(TBIN$(1)_T_$(4)_H_$(3))/rustdoc_ng$$(X_$(4)): \
$$(DRIVER_CRATE) \
$$(TSREQ$(1)_T_$(4)_H_$(3)) \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(4)) \
| $$(TBIN$(1)_T_$(4)_H_$(3))/
@$$(call E, compile_and_link: $$@)
$$(STAGE$(1)_T_$(4)_H_$(3)) --cfg rustdoc_ng -o $$@ $$<
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTI_$(4)): \
$$(RUSTI_LIB) $$(RUSTI_INPUTS) \
$$(SREQ$(1)_T_$(4)_H_$(3)) \
@ -125,7 +102,6 @@ $$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUST_$(4)): \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTPKG_$(4)) \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTI_$(4)) \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTDOC_$(4)) \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(4)) \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTC_$(4)) \
| $$(TLIB$(1)_T_$(4)_H_$(3))/
@$$(call E, compile_and_link: $$@)
@ -195,27 +171,6 @@ $$(HBIN$(2)_H_$(4))/rustdoc$$(X_$(4)): \
@$$(call E, cp: $$@)
$$(Q)cp $$< $$@
$$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTDOCNG_$(4)): \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTDOCNG_$(4)) \
$$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTC_$(4)) \
$$(HSREQ$(2)_H_$(4)) \
| $$(HLIB$(2)_H_$(4))/
@$$(call E, cp: $$@)
$$(call REMOVE_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTDOCNG_GLOB_$(4)),$$(notdir $$@))
$$(Q)cp $$< $$@
$$(call LIST_ALL_OLD_GLOB_MATCHES_EXCEPT,$$(dir $$@),$(LIBRUSTDOCNG_GLOB_$(4)),$$(notdir $$@))
$$(Q)cp -R $$(TLIB$(1)_T_$(4)_H_$(3))/$(LIBRUSTDOCNG_GLOB_$(4)) \
$$(wildcard $$(TLIB$(1)_T_$(4)_H_$(3))/$(LIBRUSTDOCNG_DSYM_GLOB_$(4))) \
$$(HLIB$(2)_H_$(4))
$$(HBIN$(2)_H_$(4))/rustdoc_ng$$(X_$(4)): \
$$(TBIN$(1)_T_$(4)_H_$(3))/rustdoc_ng$$(X_$(4)) \
$$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTDOCNG_$(4)) \
$$(HSREQ$(2)_H_$(4)) \
| $$(HBIN$(2)_H_$(4))/
@$$(call E, cp: $$@)
$$(Q)cp $$< $$@
$$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTI_$(4)): \
$$(TLIB$(1)_T_$(4)_H_$(3))/$(CFG_LIBRUSTI_$(4)) \
$$(HLIB$(2)_H_$(4))/$(CFG_LIBRUSTC_$(4)) \

View File

@ -92,7 +92,7 @@ static COMMANDS: &'static [Command<'static>] = &'static [
cmd: "doc",
action: CallMain("rustdoc", rustdoc::main_args),
usage_line: "generate documentation from doc comments",
usage_full: UsgCall(rustdoc::config::usage),
usage_full: UsgCall(rustdoc_help),
},
Command {
cmd: "pkg",
@ -122,6 +122,10 @@ fn rustc_help() {
rustc::usage(os::args()[0].clone())
}
fn rustdoc_help() {
rustdoc::usage(os::args()[0].clone())
}
fn find_cmd(command_string: &str) -> Option<Command> {
do COMMANDS.iter().find |command| {
command.cmd == command_string

View File

@ -1,186 +0,0 @@
// Copyright 2012-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.
/*!
Provides all access to AST-related, non-sendable info
Rustdoc is intended to be parallel, and the rustc AST is filled with
shared boxes. The AST service attempts to provide a single place to
query AST-related information, shielding the rest of Rustdoc from its
non-sendableness.
*/
use parse;
use std::cell::Cell;
use std::comm::{stream, SharedChan, Port};
use std::task;
use rustc::driver::driver;
use rustc::driver::session::Session;
use rustc::driver::session::{basic_options, options};
use rustc::front;
use syntax::ast;
use syntax::ast_map;
use syntax::ast_util;
use syntax;
pub struct Ctxt {
ast: @ast::Crate,
ast_map: ast_map::map
}
type SrvOwner<'self,T> = &'self fn(srv: Srv) -> T;
pub type CtxtHandler<T> = ~fn(ctxt: Ctxt) -> T;
type Parser = ~fn(Session, s: @str) -> @ast::Crate;
enum Msg {
HandleRequest(~fn(Ctxt)),
Exit
}
#[deriving(Clone)]
pub struct Srv {
ch: SharedChan<Msg>
}
pub fn from_str<T>(source: ~str, owner: SrvOwner<T>) -> T {
run(owner, source.clone(), parse::from_str_sess)
}
pub fn from_file<T>(file: ~str, owner: SrvOwner<T>) -> T {
run(owner, file.clone(), |sess, f| parse::from_file_sess(sess, &Path(f)))
}
fn run<T>(owner: SrvOwner<T>, source: ~str, parse: Parser) -> T {
let (po, ch) = stream();
let source = Cell::new(source);
let parse = Cell::new(parse);
do task::spawn {
act(&po, source.take().to_managed(), parse.take());
}
let srv_ = Srv {
ch: SharedChan::new(ch)
};
let res = owner(srv_.clone());
srv_.ch.send(Exit);
res
}
fn act(po: &Port<Msg>, source: @str, parse: Parser) {
let sess = build_session();
let ctxt = build_ctxt(
sess,
parse(sess, source)
);
let mut keep_going = true;
while keep_going {
match po.recv() {
HandleRequest(f) => {
f(ctxt);
}
Exit => {
keep_going = false;
}
}
}
}
pub fn exec<T:Send>(
srv: Srv,
f: ~fn(ctxt: Ctxt) -> T
) -> T {
let (po, ch) = stream();
let msg = HandleRequest(|ctxt| ch.send(f(ctxt)));
srv.ch.send(msg);
po.recv()
}
fn assign_node_ids(crate: @ast::Crate) -> @ast::Crate {
let next_id = @mut 0;
let fold = ast_util::node_id_assigner(|| {
let i = *next_id;
*next_id += 1;
i
});
@fold.fold_crate(crate)
}
fn build_ctxt(sess: Session,
ast: @ast::Crate) -> Ctxt {
use rustc::front::config;
let ast = syntax::ext::expand::inject_std_macros(sess.parse_sess,
sess.opts.cfg.clone(),
ast);
let ast = config::strip_unconfigured_items(ast);
let ast = syntax::ext::expand::expand_crate(sess.parse_sess,
sess.opts.cfg.clone(),
ast);
let ast = front::test::modify_for_testing(sess, ast);
let ast = assign_node_ids(ast);
let ast_map = ast_map::map_crate(sess.diagnostic(), ast);
Ctxt {
ast: ast,
ast_map: ast_map,
}
}
fn build_session() -> Session {
let sopts: @options = basic_options();
let emitter = syntax::diagnostic::emit;
let session = driver::build_session(sopts, emitter);
session
}
#[test]
fn should_prune_unconfigured_items() {
let source = ~"#[cfg(shut_up_and_leave_me_alone)]fn a() { }";
do from_str(source) |srv| {
do exec(srv) |ctxt| {
// one item: the __std_macros secret module
assert_eq!(ctxt.ast.module.items.len(), 1);
}
}
}
#[test]
fn srv_should_build_ast_map() {
let source = ~"fn a() { }";
do from_str(source) |srv| {
do exec(srv) |ctxt| {
assert!(!ctxt.ast_map.is_empty())
};
}
}
#[test]
fn should_ignore_external_import_paths_that_dont_exist() {
let source = ~"use forble; use forble::bippy;";
from_str(source, |_srv| { } )
}
#[test]
fn srv_should_return_request_result() {
let source = ~"fn a() { }";
do from_str(source) |srv| {
let result = exec(srv, |_ctxt| 1000 );
assert_eq!(result, 1000);
}
}

View File

@ -1,149 +0,0 @@
// Copyright 2012-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.
/*!
Attribute parsing
The attribute parser provides methods for pulling documentation out of
an AST's attributes.
*/
use syntax::ast;
use syntax::attr;
use syntax::attr::{AttrMetaMethods, AttributeMethods};
pub struct CrateAttrs {
name: Option<~str>
}
fn doc_metas(attrs: ~[ast::Attribute]) -> ~[@ast::MetaItem] {
attrs.iter()
.filter(|at| "doc" == at.name())
.map(|at| at.desugar_doc().meta())
.collect()
}
pub fn parse_crate(attrs: ~[ast::Attribute]) -> CrateAttrs {
let link_metas = attr::find_linkage_metas(attrs);
let name = attr::last_meta_item_value_str_by_name(link_metas, "name");
CrateAttrs {
name: name.map(|s| s.to_owned())
}
}
pub fn parse_desc(attrs: ~[ast::Attribute]) -> Option<~str> {
let doc_strs = do doc_metas(attrs).move_iter().filter_map |meta| {
meta.value_str()
}.collect::<~[@str]>();
if doc_strs.is_empty() {
None
} else {
Some(doc_strs.connect("\n"))
}
}
pub fn parse_hidden(attrs: ~[ast::Attribute]) -> bool {
let r = doc_metas(attrs);
do r.iter().any |meta| {
match meta.meta_item_list() {
Some(metas) => attr::contains_name(metas, "hidden"),
None => false
}
}
}
#[cfg(test)]
mod test {
use syntax::ast;
use syntax;
use super::{parse_hidden, parse_crate, parse_desc};
fn parse_attributes(source: @str) -> ~[ast::Attribute] {
use syntax::parse;
use syntax::parse::attr::parser_attr;
let parse_sess = syntax::parse::new_parse_sess(None);
let parser = parse::new_parser_from_source_str(
parse_sess, ~[], @"-", source);
parser.parse_outer_attributes()
}
#[test]
fn should_extract_crate_name_from_link_attribute() {
let source = @"#[link(name = \"snuggles\")]";
let attrs = parse_attributes(source);
let attrs = parse_crate(attrs);
assert!(attrs.name == Some(~"snuggles"));
}
#[test]
fn should_not_extract_crate_name_if_no_link_attribute() {
let source = @"";
let attrs = parse_attributes(source);
let attrs = parse_crate(attrs);
assert!(attrs.name == None);
}
#[test]
fn should_not_extract_crate_name_if_no_name_value_in_link_attribute() {
let source = @"#[link(whatever)]";
let attrs = parse_attributes(source);
let attrs = parse_crate(attrs);
assert!(attrs.name == None);
}
#[test]
fn parse_desc_should_handle_undocumented_mods() {
let source = @"";
let attrs = parse_attributes(source);
let attrs = parse_desc(attrs);
assert!(attrs == None);
}
#[test]
fn parse_desc_should_parse_simple_doc_attributes() {
let source = @"#[doc = \"basic\"]";
let attrs = parse_attributes(source);
let attrs = parse_desc(attrs);
assert!(attrs == Some(~"basic"));
}
#[test]
fn should_parse_hidden_attribute() {
let source = @"#[doc(hidden)]";
let attrs = parse_attributes(source);
assert!(parse_hidden(attrs) == true);
}
#[test]
fn should_parse_hidden_attribute_with_other_docs() {
let source = @"#[doc = \"foo\"] #[doc(hidden)] #[doc = \"foo\"]";
let attrs = parse_attributes(source);
assert!(parse_hidden(attrs) == true);
}
#[test]
fn should_not_parse_non_hidden_attribute() {
let source = @"#[doc = \"\"]";
let attrs = parse_attributes(source);
assert!(parse_hidden(attrs) == false);
}
#[test]
fn should_concatenate_multiple_doc_comments() {
let source = @"/// foo\n/// bar";
let desc = parse_desc(parse_attributes(source));
assert!(desc == Some(~" foo\n bar"));
}
}

View File

@ -1,326 +0,0 @@
// Copyright 2012 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.
/*!
The attribute parsing pass
Traverses the document tree, pulling relevant documention out of the
corresponding AST nodes. The information gathered here is the basis
of the natural-language documentation for a crate.
*/
use astsrv;
use attr_parser;
use doc::ItemUtils;
use doc;
use extract::to_str;
use fold::Fold;
use fold;
use pass::Pass;
use syntax::ast;
use syntax::ast_map;
pub fn mk_pass() -> Pass {
Pass {
name: ~"attr",
f: run
}
}
pub fn run(
srv: astsrv::Srv,
doc: doc::Doc
) -> doc::Doc {
let fold = Fold {
ctxt: srv.clone(),
fold_crate: fold_crate,
fold_item: fold_item,
fold_enum: fold_enum,
fold_trait: fold_trait,
fold_impl: fold_impl,
.. fold::default_any_fold(srv)
};
(fold.fold_doc)(&fold, doc)
}
fn fold_crate(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::CrateDoc
) -> doc::CrateDoc {
let srv = fold.ctxt.clone();
let doc = fold::default_seq_fold_crate(fold, doc);
let attrs = do astsrv::exec(srv) |ctxt| {
let attrs = ctxt.ast.attrs.clone();
attr_parser::parse_crate(attrs)
};
doc::CrateDoc {
topmod: doc::ModDoc {
item: doc::ItemDoc {
name: attrs.name.clone().unwrap_or(doc.topmod.name_()),
.. doc.topmod.item.clone()
},
.. doc.topmod.clone()
}
}
}
fn fold_item(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::ItemDoc
) -> doc::ItemDoc {
let srv = fold.ctxt.clone();
let doc = fold::default_seq_fold_item(fold, doc);
let desc = if doc.id == ast::CRATE_NODE_ID {
// This is the top-level mod, use the crate attributes
do astsrv::exec(srv) |ctxt| {
attr_parser::parse_desc(ctxt.ast.attrs.clone())
}
} else {
parse_item_attrs(srv, doc.id, attr_parser::parse_desc)
};
doc::ItemDoc {
desc: desc,
.. doc
}
}
fn parse_item_attrs<T:Send>(
srv: astsrv::Srv,
id: doc::AstId,
parse_attrs: ~fn(a: ~[ast::Attribute]) -> T) -> T {
do astsrv::exec(srv) |ctxt| {
let attrs = match ctxt.ast_map.get_copy(&id) {
ast_map::node_item(item, _) => item.attrs.clone(),
ast_map::node_foreign_item(item, _, _, _) => item.attrs.clone(),
_ => fail!("parse_item_attrs: not an item")
};
parse_attrs(attrs)
}
}
fn fold_enum(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::EnumDoc
) -> doc::EnumDoc {
let srv = fold.ctxt.clone();
let doc_id = doc.id();
let doc = fold::default_seq_fold_enum(fold, doc);
doc::EnumDoc {
variants: do doc.variants.iter().map |variant| {
let variant = (*variant).clone();
let desc = {
let variant = variant.clone();
do astsrv::exec(srv.clone()) |ctxt| {
match ctxt.ast_map.get_copy(&doc_id) {
ast_map::node_item(@ast::item {
node: ast::item_enum(ref enum_definition, _), _
}, _) => {
let ast_variant =
(*enum_definition.variants.iter().find(|v| {
to_str(v.node.name) == variant.name
}).unwrap()).clone();
attr_parser::parse_desc(
ast_variant.node.attrs.clone())
}
_ => {
fail!("Enum variant %s has id that's not bound to an enum item",
variant.name)
}
}
}
};
doc::VariantDoc {
desc: desc,
.. variant
}
}.collect(),
.. doc
}
}
fn fold_trait(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::TraitDoc
) -> doc::TraitDoc {
let srv = fold.ctxt.clone();
let doc = fold::default_seq_fold_trait(fold, doc);
doc::TraitDoc {
methods: merge_method_attrs(srv, doc.id(), doc.methods.clone()),
.. doc
}
}
fn merge_method_attrs(
srv: astsrv::Srv,
item_id: doc::AstId,
docs: ~[doc::MethodDoc]
) -> ~[doc::MethodDoc] {
// Create an assoc list from method name to attributes
let attrs: ~[(~str, Option<~str>)] = do astsrv::exec(srv) |ctxt| {
match ctxt.ast_map.get_copy(&item_id) {
ast_map::node_item(@ast::item {
node: ast::item_trait(_, _, ref methods), _
}, _) => {
methods.iter().map(|method| {
match (*method).clone() {
ast::required(ty_m) => {
(to_str(ty_m.ident),
attr_parser::parse_desc(ty_m.attrs.clone()))
}
ast::provided(m) => {
(to_str(m.ident), attr_parser::parse_desc(m.attrs.clone()))
}
}
}).collect()
}
ast_map::node_item(@ast::item {
node: ast::item_impl(_, _, _, ref methods), _
}, _) => {
methods.iter().map(|method| {
(to_str(method.ident),
attr_parser::parse_desc(method.attrs.clone()))
}).collect()
}
_ => fail!("unexpected item")
}
};
do docs.iter().zip(attrs.iter()).map |(doc, attrs)| {
assert!(doc.name == attrs.first());
let desc = attrs.second();
doc::MethodDoc {
desc: desc,
.. (*doc).clone()
}
}.collect()
}
fn fold_impl(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::ImplDoc
) -> doc::ImplDoc {
let srv = fold.ctxt.clone();
let doc = fold::default_seq_fold_impl(fold, doc);
doc::ImplDoc {
methods: merge_method_attrs(srv, doc.id(), doc.methods.clone()),
.. doc
}
}
#[cfg(test)]
mod test {
use astsrv;
use attr_pass::run;
use doc;
use extract;
fn mk_doc(source: ~str) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
run(srv.clone(), doc)
}
}
#[test]
fn should_replace_top_module_name_with_crate_name() {
let doc = mk_doc(~"#[link(name = \"bond\")];");
assert!(doc.cratemod().name_() == ~"bond");
}
#[test]
fn should_should_extract_mod_attributes() {
let doc = mk_doc(~"#[doc = \"test\"] mod a { }");
// hidden __std_macros module at the start.
assert!(doc.cratemod().mods()[1].desc() == Some(~"test"));
}
#[test]
fn should_extract_top_mod_attributes() {
let doc = mk_doc(~"#[doc = \"test\"];");
assert!(doc.cratemod().desc() == Some(~"test"));
}
#[test]
fn should_extract_foreign_fn_attributes() {
let doc = mk_doc(~"extern { #[doc = \"test\"] fn a(); }");
assert!(doc.cratemod().nmods()[0].fns[0].desc() == Some(~"test"));
}
#[test]
fn should_extract_fn_attributes() {
let doc = mk_doc(~"#[doc = \"test\"] fn a() -> int { }");
assert!(doc.cratemod().fns()[0].desc() == Some(~"test"));
}
#[test]
fn should_extract_enum_docs() {
let doc = mk_doc(~"#[doc = \"b\"]\
enum a { v }");
debug!("%?", doc);
assert!(doc.cratemod().enums()[0].desc() == Some(~"b"));
}
#[test]
fn should_extract_variant_docs() {
let doc = mk_doc(~"enum a { #[doc = \"c\"] v }");
assert!(doc.cratemod().enums()[0].variants[0].desc == Some(~"c"));
}
#[test]
fn should_extract_trait_docs() {
let doc = mk_doc(~"#[doc = \"whatever\"] trait i { fn a(); }");
assert!(doc.cratemod().traits()[0].desc() == Some(~"whatever"));
}
#[test]
fn should_extract_trait_method_docs() {
let doc = mk_doc(
~"trait i {\
#[doc = \"desc\"]\
fn f(a: bool) -> bool;\
}");
assert!(doc.cratemod().traits()[0].methods[0].desc == Some(~"desc"));
}
#[test]
fn should_extract_impl_docs() {
let doc = mk_doc(
~"#[doc = \"whatever\"] impl int { fn a() { } }");
assert!(doc.cratemod().impls()[0].desc() == Some(~"whatever"));
}
#[test]
fn should_extract_impl_method_docs() {
let doc = mk_doc(
~"impl int {\
#[doc = \"desc\"]\
fn f(a: bool) -> bool { }\
}");
assert!(doc.cratemod().impls()[0].methods[0].desc == Some(~"desc"));
}
}

View File

@ -1,373 +0,0 @@
// Copyright 2012-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::cell::Cell;
use std::os;
use std::result::Result;
use std::result;
use std::run::ProcessOutput;
use std::run;
use std::vec;
use extra::getopts;
/// The type of document to output
#[deriving(Clone, Eq)]
pub enum OutputFormat {
/// Markdown
Markdown,
/// HTML, via markdown and pandoc
PandocHtml
}
/// How to organize the output
#[deriving(Clone, Eq)]
pub enum OutputStyle {
/// All in a single document
DocPerCrate,
/// Each module in its own document
DocPerMod
}
/// The configuration for a rustdoc session
#[deriving(Clone)]
pub struct Config {
input_crate: Path,
output_dir: Path,
output_format: OutputFormat,
output_style: OutputStyle,
pandoc_cmd: Option<~str>
}
fn opt_output_dir() -> ~str { ~"output-dir" }
fn opt_output_format() -> ~str { ~"output-format" }
fn opt_output_style() -> ~str { ~"output-style" }
fn opt_pandoc_cmd() -> ~str { ~"pandoc-cmd" }
fn opt_help() -> ~str { ~"h" }
fn opts() -> ~[(getopts::Opt, ~str)] {
~[
(getopts::optopt(opt_output_dir()),
~"--output-dir <val> Put documents here (default: .)"),
(getopts::optopt(opt_output_format()),
~"--output-format <val> 'markdown' or 'html' (default)"),
(getopts::optopt(opt_output_style()),
~"--output-style <val> 'doc-per-crate' or 'doc-per-mod' (default)"),
(getopts::optopt(opt_pandoc_cmd()),
~"--pandoc-cmd <val> Command for running pandoc"),
(getopts::optflag(opt_help()),
~"-h, --help Print help")
]
}
pub fn usage() {
use std::io::println;
println("Usage: rustdoc [options] <cratefile>\n");
println("Options:\n");
let r = opts();
for opt in r.iter() {
printfln!(" %s", opt.second());
}
println("");
}
pub fn default_config(input_crate: &Path) -> Config {
Config {
input_crate: (*input_crate).clone(),
output_dir: Path("."),
output_format: PandocHtml,
output_style: DocPerMod,
pandoc_cmd: None
}
}
type Process = ~fn((&str), (&[~str])) -> ProcessOutput;
pub fn mock_process_output(_prog: &str, _args: &[~str]) -> ProcessOutput {
ProcessOutput {
status: 0,
output: ~[],
error: ~[]
}
}
pub fn process_output(prog: &str, args: &[~str]) -> ProcessOutput {
run::process_output(prog, args)
}
pub fn parse_config(args: &[~str]) -> Result<Config, ~str> {
parse_config_(args, process_output)
}
pub fn parse_config_(
args: &[~str],
process_output: Process
) -> Result<Config, ~str> {
let args = args.tail();
let opts = vec::unzip(opts().move_iter()).first();
match getopts::getopts(args, opts) {
Ok(matches) => {
if matches.free.len() == 1 {
let input_crate = Path(*matches.free.head());
config_from_opts(&input_crate, &matches, process_output)
} else if matches.free.is_empty() {
Err(~"no crates specified")
} else {
Err(~"multiple crates specified")
}
}
Err(f) => {
Err(f.to_err_msg())
}
}
}
fn config_from_opts(
input_crate: &Path,
matches: &getopts::Matches,
process_output: Process
) -> Result<Config, ~str> {
let config = default_config(input_crate);
let result = result::Ok(config);
let result = do result.and_then |config| {
let output_dir = matches.opt_str(opt_output_dir());
let output_dir = output_dir.map_move(|s| Path(s));
result::Ok(Config {
output_dir: output_dir.unwrap_or(config.output_dir.clone()),
.. config
})
};
let result = do result.and_then |config| {
let output_format = matches.opt_str(opt_output_format());
do output_format.map_move_default(result::Ok(config.clone())) |output_format| {
do parse_output_format(output_format).and_then |output_format| {
result::Ok(Config {
output_format: output_format,
.. config.clone()
})
}
}
};
let result = do result.and_then |config| {
let output_style =
matches.opt_str(opt_output_style());
do output_style.map_move_default(result::Ok(config.clone())) |output_style| {
do parse_output_style(output_style).and_then |output_style| {
result::Ok(Config {
output_style: output_style,
.. config.clone()
})
}
}
};
let process_output = Cell::new(process_output);
let result = do result.and_then |config| {
let pandoc_cmd = matches.opt_str(opt_pandoc_cmd());
let pandoc_cmd = maybe_find_pandoc(
&config, pandoc_cmd, process_output.take());
do pandoc_cmd.and_then |pandoc_cmd| {
result::Ok(Config {
pandoc_cmd: pandoc_cmd,
.. config.clone()
})
}
};
return result;
}
fn parse_output_format(output_format: &str) -> Result<OutputFormat, ~str> {
match output_format.to_str() {
~"markdown" => result::Ok(Markdown),
~"html" => result::Ok(PandocHtml),
_ => result::Err(fmt!("unknown output format '%s'", output_format))
}
}
fn parse_output_style(output_style: &str) -> Result<OutputStyle, ~str> {
match output_style.to_str() {
~"doc-per-crate" => result::Ok(DocPerCrate),
~"doc-per-mod" => result::Ok(DocPerMod),
_ => result::Err(fmt!("unknown output style '%s'", output_style))
}
}
pub fn maybe_find_pandoc(
config: &Config,
maybe_pandoc_cmd: Option<~str>,
process_output: Process
) -> Result<Option<~str>, ~str> {
if config.output_format != PandocHtml {
return result::Ok(maybe_pandoc_cmd);
}
let possible_pandocs = match maybe_pandoc_cmd {
Some(pandoc_cmd) => ~[pandoc_cmd],
None => {
~[~"pandoc"] + match os::homedir() {
Some(dir) => {
~[dir.push_rel(&Path(".cabal/bin/pandoc")).to_str()]
}
None => ~[]
}
}
};
let pandoc = do possible_pandocs.iter().find |&pandoc| {
let output = process_output(*pandoc, [~"--version"]);
debug!("testing pandoc cmd %s: %?", *pandoc, output);
output.status == 0
};
match pandoc {
Some(x) => Ok(Some((*x).clone())), // ugly, shouldn't be doubly wrapped
None => Err(~"couldn't find pandoc")
}
}
#[cfg(test)]
mod test {
use config::*;
use std::result;
use std::run::ProcessOutput;
fn parse_config(args: &[~str]) -> Result<Config, ~str> {
parse_config_(args, mock_process_output)
}
#[test]
fn should_find_pandoc() {
let config = Config {
output_format: PandocHtml,
.. default_config(&Path("test"))
};
let mock_process_output: ~fn(&str, &[~str]) -> ProcessOutput = |_, _| {
ProcessOutput { status: 0, output: "pandoc 1.8.2.1".as_bytes().to_owned(), error: ~[] }
};
let result = maybe_find_pandoc(&config, None, mock_process_output);
assert!(result == result::Ok(Some(~"pandoc")));
}
#[test]
fn should_error_with_no_pandoc() {
let config = Config {
output_format: PandocHtml,
.. default_config(&Path("test"))
};
let mock_process_output: ~fn(&str, &[~str]) -> ProcessOutput = |_, _| {
ProcessOutput { status: 1, output: ~[], error: ~[] }
};
let result = maybe_find_pandoc(&config, None, mock_process_output);
assert!(result == result::Err(~"couldn't find pandoc"));
}
#[test]
fn should_error_with_no_crates() {
let config = parse_config([~"rustdoc"]);
assert!(config.unwrap_err() == ~"no crates specified");
}
#[test]
fn should_error_with_multiple_crates() {
let config =
parse_config([~"rustdoc", ~"crate1.rc", ~"crate2.rc"]);
assert!(config.unwrap_err() == ~"multiple crates specified");
}
#[test]
fn should_set_output_dir_to_cwd_if_not_provided() {
let config = parse_config([~"rustdoc", ~"crate.rc"]);
assert!(config.unwrap().output_dir == Path("."));
}
#[test]
fn should_set_output_dir_if_provided() {
let config = parse_config([
~"rustdoc", ~"crate.rc", ~"--output-dir", ~"snuggles"
]);
assert!(config.unwrap().output_dir == Path("snuggles"));
}
#[test]
fn should_set_output_format_to_pandoc_html_if_not_provided() {
let config = parse_config([~"rustdoc", ~"crate.rc"]);
assert!(config.unwrap().output_format == PandocHtml);
}
#[test]
fn should_set_output_format_to_markdown_if_requested() {
let config = parse_config([
~"rustdoc", ~"crate.rc", ~"--output-format", ~"markdown"
]);
assert!(config.unwrap().output_format == Markdown);
}
#[test]
fn should_set_output_format_to_pandoc_html_if_requested() {
let config = parse_config([
~"rustdoc", ~"crate.rc", ~"--output-format", ~"html"
]);
assert!(config.unwrap().output_format == PandocHtml);
}
#[test]
fn should_error_on_bogus_format() {
let config = parse_config([
~"rustdoc", ~"crate.rc", ~"--output-format", ~"bogus"
]);
assert!(config.unwrap_err() == ~"unknown output format 'bogus'");
}
#[test]
fn should_set_output_style_to_doc_per_mod_by_default() {
let config = parse_config([~"rustdoc", ~"crate.rc"]);
assert!(config.unwrap().output_style == DocPerMod);
}
#[test]
fn should_set_output_style_to_one_doc_if_requested() {
let config = parse_config([
~"rustdoc", ~"crate.rc", ~"--output-style", ~"doc-per-crate"
]);
assert!(config.unwrap().output_style == DocPerCrate);
}
#[test]
fn should_set_output_style_to_doc_per_mod_if_requested() {
let config = parse_config([
~"rustdoc", ~"crate.rc", ~"--output-style", ~"doc-per-mod"
]);
assert!(config.unwrap().output_style == DocPerMod);
}
#[test]
fn should_error_on_bogus_output_style() {
let config = parse_config([
~"rustdoc", ~"crate.rc", ~"--output-style", ~"bogus"
]);
assert!(config.unwrap_err() == ~"unknown output style 'bogus'");
}
#[test]
fn should_set_pandoc_command_if_requested() {
let config = parse_config([
~"rustdoc", ~"crate.rc", ~"--pandoc-cmd", ~"panda-bear-doc"
]);
assert!(config.unwrap().pandoc_cmd == Some(~"panda-bear-doc"));
}
#[test]
fn should_set_pandoc_command_when_using_pandoc() {
let config = parse_config([~"rustdoc", ~"crate.rc"]);
assert!(config.unwrap().pandoc_cmd == Some(~"pandoc"));
}
}

View File

@ -1,199 +0,0 @@
// Copyright 2012 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.
// no-reformat
/*!
* A demonstration module
*
* Contains documentation in various forms that rustdoc understands,
* for testing purposes. It doesn't surve any functional
* purpose. This here, for instance, is just some filler text.
*
* FIXME (#3731): It would be nice if we could run some automated
* tests on this file
*/
/// The base price of a muffin on a non-holiday
static PRICE_OF_A_MUFFIN: float = 70f;
struct WaitPerson {
hair_color: ~str
}
/// The type of things that produce omnomnom
enum OmNomNomy {
/// Delicious sugar cookies
Cookie,
/// It's pizza
PizzaPie(~[uint])
}
fn take_my_order_please(
_waitperson: WaitPerson,
_order: ~[OmNomNomy]
) -> uint {
/*!
* OMG would you take my order already?
*
* # Arguments
*
* * _waitperson - The waitperson that you want to bother
* * _order - The order vector. It should be filled with food
*
* # Return
*
* The price of the order, including tax
*
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec
* molestie nisl. Duis massa risus, pharetra a scelerisque a,
* molestie eu velit. Donec mattis ligula at ante imperdiet ut
* dapibus mauris malesuada.
*
* Sed gravida nisi a metus elementum sit amet hendrerit dolor
* bibendum. Aenean sit amet neque massa, sed tempus tortor. Sed ut
* lobortis enim. Proin a mauris quis nunc fermentum ultrices eget a
* erat. Mauris in lectus vitae metus sodales auctor. Morbi nunc
* quam, ultricies at venenatis non, pellentesque ac dui.
*
* # Failure
*
* This function is full of fail
*/
fail!();
}
mod fortress_of_solitude {
/*!
* Superman's vacation home
*
* The fortress of solitude is located in the Arctic and it is
* cold. What you may not know about the fortress of solitude
* though is that it contains two separate bowling alleys. One of
* them features bumper-bowling and is kind of lame.
*
* Really, it's pretty cool.
*/
}
mod blade_runner {
/*!
* Blade Runner is probably the best movie ever
*
* I like that in the world of Blade Runner it is always
* raining, and that it's always night time. And Aliens
* was also a really good movie.
*
* Alien 3 was crap though.
*/
}
/**
* Bored
*
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec
* molestie nisl. Duis massa risus, pharetra a scelerisque a,
* molestie eu velit. Donec mattis ligula at ante imperdiet ut
* dapibus mauris malesuada. Sed gravida nisi a metus elementum sit
* amet hendrerit dolor bibendum. Aenean sit amet neque massa, sed
* tempus tortor. Sed ut lobortis enim. Proin a mauris quis nunc
* fermentum ultrices eget a erat. Mauris in lectus vitae metus
* sodales auctor. Morbi nunc quam, ultricies at venenatis non,
* pellentesque ac dui.
*
* Quisque vitae est id eros placerat laoreet sit amet eu
* nisi. Curabitur suscipit neque porttitor est euismod
* lacinia. Curabitur non quam vitae ipsum adipiscing
* condimentum. Mauris ut ante eget metus sollicitudin
* blandit. Aliquam erat volutpat. Morbi sed nisl mauris. Nulla
* facilisi. Phasellus at mollis ipsum. Maecenas sed convallis
* sapien. Nullam in ligula turpis. Pellentesque a neque augue. Sed
* eget ante feugiat tortor congue auctor ac quis ante. Proin
* condimentum lacinia tincidunt.
*/
struct Bored {
bored: bool,
}
impl Drop for Bored {
fn drop(&mut self) { }
}
/**
* The Shunned House
*
* From even the greatest of horrors irony is seldom absent. Sometimes it
* enters directly into the composition of the events, while sometimes it
* relates only to their fortuitous position among persons and
* places. The latter sort is splendidly exemplified by a case in the
* ancient city of Providence, where in the late forties Edgar Allan Poe
* used to sojourn often during his unsuccessful wooing of the gifted
* poetess, Mrs. Whitman. Poe generally stopped at the Mansion House in
* Benefit Street--the renamed Golden Ball Inn whose roof has sheltered
* Washington, Jefferson, and Lafayette--and his favorite walk led
* northward along the same street to Mrs. Whitman's home and the
* neighboring hillside churchyard of St. John's, whose hidden expanse of
* Eighteenth Century gravestones had for him a peculiar fascination.
*/
trait TheShunnedHouse {
/**
* Now the irony is this. In this walk, so many times repeated, the
* world's greatest master of the terrible and the bizarre was
* obliged to pass a particular house on the eastern side of the
* street; a dingy, antiquated structure perched on the abruptly
* rising side hill, with a great unkempt yard dating from a time
* when the region was partly open country. It does not appear that
* he ever wrote or spoke of it, nor is there any evidence that he
* even noticed it. And yet that house, to the two persons in
* possession of certain information, equals or outranks in horror
* the wildest fantasy of the genius who so often passed it
* unknowingly, and stands starkly leering as a symbol of all that is
* unutterably hideous.
*
* # Arguments
*
* * unkempt_yard - A yard dating from a time when the region was partly
* open country
*/
fn dingy_house(&self, unkempt_yard: int);
/**
* The house was--and for that matter still is--of a kind to attract
* the attention of the curious. Originally a farm or semi-farm
* building, it followed the average New England colonial lines of
* the middle Eighteenth Century--the prosperous peaked-roof sort,
* with two stories and dormerless attic, and with the Georgian
* doorway and interior panelling dictated by the progress of taste
* at that time. It faced south, with one gable end buried to the
* lower windows in the eastward rising hill, and the other exposed
* to the foundations toward the street. Its construction, over a
* century and a half ago, had followed the grading and straightening
* of the road in that especial vicinity; for Benefit Street--at
* first called Back Street--was laid out as a lane winding amongst
* the graveyards of the first settlers, and straightened only when
* the removal of the bodies to the North Burial Ground made it
* decently possible to cut through the old family plots.
*/
fn construct(&self) -> bool;
}
/// Whatever
impl TheShunnedHouse for OmNomNomy {
fn dingy_house(&self, _unkempt_yard: int) {
}
fn construct(&self) -> bool {
fail!();
}
}

View File

@ -1,288 +0,0 @@
// Copyright 2012 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.
/*!
Pulls a brief description out of a long description.
If the first paragraph of a long description is short enough then it
is interpreted as the brief description.
*/
use astsrv;
use doc::ItemUtils;
use doc;
use fold::Fold;
use fold;
use pass::Pass;
use std::util;
pub fn mk_pass() -> Pass {
Pass {
name: ~"desc_to_brief",
f: run
}
}
pub fn run(
_srv: astsrv::Srv,
doc: doc::Doc
) -> doc::Doc {
let fold = Fold {
fold_item: fold_item,
fold_trait: fold_trait,
fold_impl: fold_impl,
.. fold::default_any_fold(())
};
(fold.fold_doc)(&fold, doc)
}
fn fold_item(fold: &fold::Fold<()>, doc: doc::ItemDoc) -> doc::ItemDoc {
let doc = fold::default_seq_fold_item(fold, doc);
doc::ItemDoc {
brief: extract(doc.desc.clone()),
.. doc
}
}
fn fold_trait(fold: &fold::Fold<()>, doc: doc::TraitDoc) -> doc::TraitDoc {
let doc =fold::default_seq_fold_trait(fold, doc);
doc::TraitDoc {
methods: doc.methods.map(|doc| doc::MethodDoc {
brief: extract(doc.desc.clone()),
.. (*doc).clone()
}),
.. doc
}
}
fn fold_impl(fold: &fold::Fold<()>, doc: doc::ImplDoc) -> doc::ImplDoc {
let doc =fold::default_seq_fold_impl(fold, doc);
doc::ImplDoc {
methods: doc.methods.map(|doc| doc::MethodDoc {
brief: extract(doc.desc.clone()),
.. (*doc).clone()
}),
.. doc
}
}
pub fn extract(desc: Option<~str>) -> Option<~str> {
if desc.is_none() {
return None
}
parse_desc(desc.clone().unwrap())
}
fn parse_desc(desc: ~str) -> Option<~str> {
static MAX_BRIEF_LEN: uint = 120u;
match first_sentence(desc.clone()) {
Some(first_sentence) => {
if first_sentence.len() <= MAX_BRIEF_LEN {
Some(first_sentence)
} else {
None
}
}
None => None
}
}
fn first_sentence(s: ~str) -> Option<~str> {
let paras = paragraphs(s);
if !paras.is_empty() {
let first_para = paras.head();
Some(first_sentence_(*first_para).replace("\n", " "))
} else {
None
}
}
fn first_sentence_(s: &str) -> ~str {
let mut dotcount = 0;
// The index of the character following a single dot. This allows
// Things like [0..1) to appear in the brief description
let idx = s.find(|ch: char| {
if ch == '.' {
dotcount += 1;
false
} else if dotcount == 1 {
true
} else {
dotcount = 0;
false
}
});
match idx {
Some(idx) if idx > 2u => s.slice(0, idx - 1).to_owned(),
_ => {
if s.ends_with(".") {
s.to_owned()
} else {
s.to_owned()
}
}
}
}
pub fn paragraphs(s: &str) -> ~[~str] {
let mut whitespace_lines = 0;
let mut accum = ~"";
let mut paras = do s.any_line_iter().fold(~[]) |paras, line| {
let mut res = paras;
if line.is_whitespace() {
whitespace_lines += 1;
} else {
if whitespace_lines > 0 {
if !accum.is_empty() {
let v = util::replace(&mut accum, ~"");
res.push(v);
}
}
whitespace_lines = 0;
accum = if accum.is_empty() {
line.to_owned()
} else {
fmt!("%s\n%s", accum, line)
}
}
res
};
if !accum.is_empty() { paras.push(accum); }
paras
}
#[cfg(test)]
mod test {
use astsrv;
use attr_pass;
use super::{extract, paragraphs, run};
use doc;
use extract;
fn mk_doc(source: ~str) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (attr_pass::mk_pass().f)(srv.clone(), doc);
run(srv.clone(), doc)
}
}
#[test]
fn should_promote_desc() {
let doc = mk_doc(~"#[doc = \"desc\"] mod m { }");
// hidden __std_macros module at the start.
assert_eq!(doc.cratemod().mods()[1].brief(), Some(~"desc"));
}
#[test]
fn should_promote_trait_method_desc() {
let doc = mk_doc(~"trait i { #[doc = \"desc\"] fn a(); }");
assert!(doc.cratemod().traits()[0].methods[0].brief ==
Some(~"desc"));
}
#[test]
fn should_promote_impl_method_desc() {
let doc = mk_doc(
~"impl int { #[doc = \"desc\"] fn a() { } }");
assert!(doc.cratemod().impls()[0].methods[0].brief == Some(~"desc"));
}
#[test]
fn test_paragraphs_1() {
let paras = paragraphs("1\n\n2");
assert_eq!(paras, ~[~"1", ~"2"]);
}
#[test]
fn test_paragraphs_2() {
let paras = paragraphs("\n\n1\n1\n\n2\n\n");
assert_eq!(paras, ~[~"1\n1", ~"2"]);
}
#[test]
fn should_promote_short_descs() {
let desc = Some(~"desc");
let brief = extract(desc.clone());
assert_eq!(brief, desc);
}
#[test]
fn should_not_promote_long_descs() {
let desc = Some(~"Warkworth Castle is a ruined medieval building
in the town of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
let brief = extract(desc);
assert_eq!(brief, None);
}
#[test]
fn should_promote_first_sentence() {
let desc = Some(~"Warkworth Castle is a ruined medieval building
in the town. of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
let brief = extract(desc);
assert!(brief == Some(
~"Warkworth Castle is a ruined medieval building in the town"));
}
#[test]
fn should_not_consider_double_period_to_end_sentence() {
let desc = Some(~"Warkworth..Castle is a ruined medieval building
in the town. of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
let brief = extract(desc);
assert!(brief == Some(
~"Warkworth..Castle is a ruined medieval building in the town"));
}
#[test]
fn should_not_consider_triple_period_to_end_sentence() {
let desc = Some(~"Warkworth... Castle is a ruined medieval building
in the town. of the same name in the English county of Northumberland,
and the town and castle occupy a loop of the River Coquet, less than a mile
from England's north-east coast. When the castle was founded is uncertain,
but traditionally its construction has been ascribed to Prince Henry of
Scotland in the mid 12th century, although it may have been built by
King Henry II of England when he took control of England'snorthern
counties.");
let brief = extract(desc);
assert!(brief == Some(
~"Warkworth... Castle is a ruined medieval building in the town"));
}
}

View File

@ -1,393 +0,0 @@
// Copyright 2012 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.
//! The document model
use doc;
pub type AstId = int;
#[deriving(Clone, Eq)]
pub struct Doc {
pages: ~[Page]
}
#[deriving(Clone, Eq)]
pub enum Page {
CratePage(CrateDoc),
ItemPage(ItemTag)
}
#[deriving(Clone, Eq)]
pub enum Implementation {
Required,
Provided,
}
/**
* Most rustdocs can be parsed into 'sections' according to their markdown
* headers
*/
#[deriving(Clone, Eq)]
pub struct Section {
header: ~str,
body: ~str
}
// FIXME (#2596): We currently give topmod the name of the crate. There
// would probably be fewer special cases if the crate had its own name
// and topmod's name was the empty string.
#[deriving(Clone, Eq)]
pub struct CrateDoc {
topmod: ModDoc
}
#[deriving(Clone, Eq)]
pub enum ItemTag {
ModTag(ModDoc),
NmodTag(NmodDoc),
StaticTag(StaticDoc),
FnTag(FnDoc),
EnumTag(EnumDoc),
TraitTag(TraitDoc),
ImplTag(ImplDoc),
TyTag(TyDoc),
StructTag(StructDoc)
}
#[deriving(Clone, Eq)]
pub struct ItemDoc {
id: AstId,
name: ~str,
path: ~[~str],
brief: Option<~str>,
desc: Option<~str>,
sections: ~[Section],
// Indicates that this node is a reexport of a different item
reexport: bool
}
#[deriving(Clone, Eq)]
pub struct SimpleItemDoc {
item: ItemDoc,
sig: Option<~str>
}
#[deriving(Clone, Eq)]
pub struct ModDoc {
item: ItemDoc,
items: ~[ItemTag],
index: Option<Index>
}
#[deriving(Clone, Eq)]
pub struct NmodDoc {
item: ItemDoc,
fns: ~[FnDoc],
index: Option<Index>
}
pub type StaticDoc = SimpleItemDoc;
pub type FnDoc = SimpleItemDoc;
#[deriving(Clone, Eq)]
pub struct EnumDoc {
item: ItemDoc,
variants: ~[VariantDoc]
}
#[deriving(Clone, Eq)]
pub struct VariantDoc {
name: ~str,
desc: Option<~str>,
sig: Option<~str>
}
#[deriving(Clone, Eq)]
pub struct TraitDoc {
item: ItemDoc,
methods: ~[MethodDoc]
}
#[deriving(Clone, Eq)]
pub struct MethodDoc {
name: ~str,
brief: Option<~str>,
desc: Option<~str>,
sections: ~[Section],
sig: Option<~str>,
implementation: Implementation,
}
#[deriving(Clone, Eq)]
pub struct ImplDoc {
item: ItemDoc,
bounds_str: Option<~str>,
trait_types: ~[~str],
self_ty: Option<~str>,
methods: ~[MethodDoc]
}
pub type TyDoc = SimpleItemDoc;
#[deriving(Clone, Eq)]
pub struct StructDoc {
item: ItemDoc,
fields: ~[~str],
sig: Option<~str>
}
#[deriving(Clone, Eq)]
pub struct Index {
entries: ~[IndexEntry]
}
/**
* A single entry in an index
*
* Fields:
*
* * kind - The type of thing being indexed, e.g. 'Module'
* * name - The name of the thing
* * brief - The brief description
* * link - A format-specific string representing the link target
*/
#[deriving(Clone, Eq)]
pub struct IndexEntry {
kind: ~str,
name: ~str,
brief: Option<~str>,
link: ~str
}
impl Doc {
pub fn CrateDoc(&self) -> CrateDoc {
self.pages.iter().fold(None, |_m, page| {
match (*page).clone() {
doc::CratePage(doc) => Some(doc),
_ => None
}
}).unwrap()
}
pub fn cratemod(&self) -> ModDoc {
self.CrateDoc().topmod.clone()
}
}
macro_rules! filt_mapper {
($vec:expr, $pat:pat) => {
do ($vec).iter().filter_map |thing| {
match thing {
&$pat => Some((*x).clone()),
_ => None
}
}.collect()
}
}
macro_rules! md {
($id:ident) => {
filt_mapper!(self.items, $id(ref x))
}
}
/// Some helper methods on ModDoc, mostly for testing
impl ModDoc {
pub fn mods(&self) -> ~[ModDoc] {
md!(ModTag)
}
pub fn nmods(&self) -> ~[NmodDoc] {
md!(NmodTag)
}
pub fn fns(&self) -> ~[FnDoc] {
md!(FnTag)
}
pub fn statics(&self) -> ~[StaticDoc] {
md!(StaticTag)
}
pub fn enums(&self) -> ~[EnumDoc] {
md!(EnumTag)
}
pub fn traits(&self) -> ~[TraitDoc] {
md!(TraitTag)
}
pub fn impls(&self) -> ~[ImplDoc] {
md!(ImplTag)
}
pub fn types(&self) -> ~[TyDoc] {
md!(TyTag)
}
pub fn structs(&self) -> ~[StructDoc] {
md!(StructTag)
}
}
macro_rules! pu {
($id:ident) => {
filt_mapper!(*self, ItemPage($id(ref x)))
}
}
pub trait PageUtils {
fn mods(&self) -> ~[ModDoc];
fn nmods(&self) -> ~[NmodDoc];
fn fns(&self) -> ~[FnDoc];
fn statics(&self) -> ~[StaticDoc];
fn enums(&self) -> ~[EnumDoc];
fn traits(&self) -> ~[TraitDoc];
fn impls(&self) -> ~[ImplDoc];
fn types(&self) -> ~[TyDoc];
}
impl PageUtils for ~[Page] {
fn mods(&self) -> ~[ModDoc] {
pu!(ModTag)
}
fn nmods(&self) -> ~[NmodDoc] {
pu!(NmodTag)
}
fn fns(&self) -> ~[FnDoc] {
pu!(FnTag)
}
fn statics(&self) -> ~[StaticDoc] {
pu!(StaticTag)
}
fn enums(&self) -> ~[EnumDoc] {
pu!(EnumTag)
}
fn traits(&self) -> ~[TraitDoc] {
pu!(TraitTag)
}
fn impls(&self) -> ~[ImplDoc] {
pu!(ImplTag)
}
fn types(&self) -> ~[TyDoc] {
pu!(TyTag)
}
}
pub trait Item {
fn item(&self) -> ItemDoc;
}
impl Item for ItemTag {
fn item(&self) -> ItemDoc {
match self {
&doc::ModTag(ref doc) => doc.item.clone(),
&doc::NmodTag(ref doc) => doc.item.clone(),
&doc::FnTag(ref doc) => doc.item.clone(),
&doc::StaticTag(ref doc) => doc.item.clone(),
&doc::EnumTag(ref doc) => doc.item.clone(),
&doc::TraitTag(ref doc) => doc.item.clone(),
&doc::ImplTag(ref doc) => doc.item.clone(),
&doc::TyTag(ref doc) => doc.item.clone(),
&doc::StructTag(ref doc) => doc.item.clone(),
}
}
}
impl Item for SimpleItemDoc {
fn item(&self) -> ItemDoc {
self.item.clone()
}
}
impl Item for ModDoc {
fn item(&self) -> ItemDoc {
self.item.clone()
}
}
impl Item for NmodDoc {
fn item(&self) -> ItemDoc {
self.item.clone()
}
}
impl Item for EnumDoc {
fn item(&self) -> ItemDoc {
self.item.clone()
}
}
impl Item for TraitDoc {
fn item(&self) -> ItemDoc {
self.item.clone()
}
}
impl Item for ImplDoc {
fn item(&self) -> ItemDoc {
self.item.clone()
}
}
impl Item for StructDoc {
fn item(&self) -> ItemDoc {
self.item.clone()
}
}
pub trait ItemUtils {
fn id(&self) -> AstId;
/// FIXME #5898: This conflicts with
/// syntax::attr::AttrMetaMethods.name; This rustdoc seems to be on
/// the way out so I'm making this one look bad rather than the
/// new methods in attr.
fn name_(&self) -> ~str;
fn path(&self) -> ~[~str];
fn brief(&self) -> Option<~str>;
fn desc(&self) -> Option<~str>;
fn sections(&self) -> ~[Section];
}
impl<A:Item> ItemUtils for A {
fn id(&self) -> AstId {
self.item().id
}
fn name_(&self) -> ~str {
self.item().name.clone()
}
fn path(&self) -> ~[~str] {
self.item().path.clone()
}
fn brief(&self) -> Option<~str> {
self.item().brief.clone()
}
fn desc(&self) -> Option<~str> {
self.item().desc.clone()
}
fn sections(&self) -> ~[Section] {
self.item().sections.clone()
}
}

View File

@ -1,29 +0,0 @@
// Copyright 2012 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.
//! Escapes text sequences
use pass::Pass;
use text_pass;
pub fn mk_pass() -> Pass {
text_pass::mk_pass(~"escape", escape)
}
fn escape(s: &str) -> ~str {
s.replace("\\", "\\\\")
}
#[test]
fn should_escape_backslashes() {
let s = ~"\\n";
let r = escape(s);
assert_eq!(r, ~"\\\\n");
}

View File

@ -1,414 +0,0 @@
// Copyright 2012-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.
//! Converts the Rust AST to the rustdoc document model
use astsrv;
use doc::ItemUtils;
use doc;
use syntax::ast;
use syntax::parse::token::{ident_interner, ident_to_str};
use syntax::parse::token;
// Hack; rather than thread an interner through everywhere, rely on
// thread-local data
// Hack-Becomes-Feature: using thread-local-state everywhere...
pub fn to_str(id: ast::Ident) -> ~str {
/* bad */ ident_to_str(&id).to_owned()
}
// get rid of this pointless function:
pub fn interner() -> @ident_interner {
return token::get_ident_interner();
}
pub fn from_srv(
srv: astsrv::Srv,
default_name: ~str
) -> doc::Doc {
//! Use the AST service to create a document tree
do astsrv::exec(srv) |ctxt| {
extract(ctxt.ast, default_name.clone())
}
}
pub fn extract(
crate: @ast::Crate,
default_name: ~str
) -> doc::Doc {
doc::Doc {
pages: ~[
doc::CratePage(doc::CrateDoc {
topmod: top_moddoc_from_crate(crate, default_name),
})
]
}
}
fn top_moddoc_from_crate(
crate: @ast::Crate,
default_name: ~str
) -> doc::ModDoc {
moddoc_from_mod(mk_itemdoc(ast::CRATE_NODE_ID, default_name),
crate.module.clone())
}
fn mk_itemdoc(id: ast::NodeId, name: ~str) -> doc::ItemDoc {
doc::ItemDoc {
id: id,
name: name,
path: ~[],
brief: None,
desc: None,
sections: ~[],
reexport: false
}
}
fn moddoc_from_mod(
itemdoc: doc::ItemDoc,
module_: ast::_mod
) -> doc::ModDoc {
doc::ModDoc {
item: itemdoc,
items: do module_.items.iter().filter_map |item| {
let ItemDoc = mk_itemdoc(item.id, to_str(item.ident));
match item.node.clone() {
ast::item_mod(m) => {
Some(doc::ModTag(
moddoc_from_mod(ItemDoc, m)
))
}
ast::item_foreign_mod(nm) => {
Some(doc::NmodTag(
nmoddoc_from_mod(ItemDoc, nm)
))
}
ast::item_fn(*) => {
Some(doc::FnTag(
fndoc_from_fn(ItemDoc)
))
}
ast::item_static(*) => {
Some(doc::StaticTag(
staticdoc_from_static(ItemDoc)
))
}
ast::item_enum(enum_definition, _) => {
Some(doc::EnumTag(
enumdoc_from_enum(ItemDoc, enum_definition.variants.clone())
))
}
ast::item_trait(_, _, methods) => {
Some(doc::TraitTag(
traitdoc_from_trait(ItemDoc, methods)
))
}
ast::item_impl(_, _, _, methods) => {
Some(doc::ImplTag(
impldoc_from_impl(ItemDoc, methods)
))
}
ast::item_ty(_, _) => {
Some(doc::TyTag(
tydoc_from_ty(ItemDoc)
))
}
ast::item_struct(def, _) => {
Some(doc::StructTag(
structdoc_from_struct(ItemDoc, def)
))
}
_ => None
}
}.collect(),
index: None
}
}
fn nmoddoc_from_mod(
itemdoc: doc::ItemDoc,
module_: ast::foreign_mod
) -> doc::NmodDoc {
let mut fns = ~[];
for item in module_.items.iter() {
let ItemDoc = mk_itemdoc(item.id, to_str(item.ident));
match item.node {
ast::foreign_item_fn(*) => {
fns.push(fndoc_from_fn(ItemDoc));
}
ast::foreign_item_static(*) => {} // XXX: Not implemented.
}
}
doc::NmodDoc {
item: itemdoc,
fns: fns,
index: None
}
}
fn fndoc_from_fn(itemdoc: doc::ItemDoc) -> doc::FnDoc {
doc::SimpleItemDoc {
item: itemdoc,
sig: None
}
}
fn staticdoc_from_static(itemdoc: doc::ItemDoc) -> doc::StaticDoc {
doc::SimpleItemDoc {
item: itemdoc,
sig: None
}
}
fn enumdoc_from_enum(
itemdoc: doc::ItemDoc,
variants: ~[ast::variant]
) -> doc::EnumDoc {
doc::EnumDoc {
item: itemdoc,
variants: variantdocs_from_variants(variants)
}
}
fn variantdocs_from_variants(
variants: ~[ast::variant]
) -> ~[doc::VariantDoc] {
variants.iter().map(variantdoc_from_variant).collect()
}
fn variantdoc_from_variant(variant: &ast::variant) -> doc::VariantDoc {
doc::VariantDoc {
name: to_str(variant.node.name),
desc: None,
sig: None
}
}
fn traitdoc_from_trait(
itemdoc: doc::ItemDoc,
methods: ~[ast::trait_method]
) -> doc::TraitDoc {
doc::TraitDoc {
item: itemdoc,
methods: do methods.iter().map |method| {
match (*method).clone() {
ast::required(ty_m) => {
doc::MethodDoc {
name: to_str(ty_m.ident),
brief: None,
desc: None,
sections: ~[],
sig: None,
implementation: doc::Required,
}
}
ast::provided(m) => {
doc::MethodDoc {
name: to_str(m.ident),
brief: None,
desc: None,
sections: ~[],
sig: None,
implementation: doc::Provided,
}
}
}
}.collect()
}
}
fn impldoc_from_impl(
itemdoc: doc::ItemDoc,
methods: ~[@ast::method]
) -> doc::ImplDoc {
doc::ImplDoc {
item: itemdoc,
bounds_str: None,
trait_types: ~[],
self_ty: None,
methods: do methods.iter().map |method| {
doc::MethodDoc {
name: to_str(method.ident),
brief: None,
desc: None,
sections: ~[],
sig: None,
implementation: doc::Provided,
}
}.collect()
}
}
fn tydoc_from_ty(
itemdoc: doc::ItemDoc
) -> doc::TyDoc {
doc::SimpleItemDoc {
item: itemdoc,
sig: None
}
}
fn structdoc_from_struct(
itemdoc: doc::ItemDoc,
struct_def: @ast::struct_def
) -> doc::StructDoc {
doc::StructDoc {
item: itemdoc,
fields: do struct_def.fields.map |field| {
match field.node.kind {
ast::named_field(ident, _) => to_str(ident),
ast::unnamed_field => ~"(unnamed)",
}
},
sig: None
}
}
#[cfg(test)]
mod test {
use astsrv;
use doc;
use extract::{extract, from_srv};
use parse;
fn mk_doc(source: @str) -> doc::Doc {
let ast = parse::from_str(source);
debug!("ast=%?", ast);
extract(ast, ~"")
}
#[test]
fn extract_empty_crate() {
let doc = mk_doc(@"");
assert!(doc.cratemod().mods().is_empty());
assert!(doc.cratemod().fns().is_empty());
}
#[test]
fn extract_mods() {
let doc = mk_doc(@"mod a { mod b { } mod c { } }");
assert!(doc.cratemod().mods()[0].name_() == ~"a");
assert!(doc.cratemod().mods()[0].mods()[0].name_() == ~"b");
assert!(doc.cratemod().mods()[0].mods()[1].name_() == ~"c");
}
#[test]
fn extract_fns_from_foreign_mods() {
let doc = mk_doc(@"extern { fn a(); }");
assert!(doc.cratemod().nmods()[0].fns[0].name_() == ~"a");
}
#[test]
fn extract_mods_deep() {
let doc = mk_doc(@"mod a { mod b { mod c { } } }");
assert!(doc.cratemod().mods()[0].mods()[0].mods()[0].name_() ==
~"c");
}
#[test]
fn extract_should_set_mod_ast_id() {
let doc = mk_doc(@"mod a { }");
assert!(doc.cratemod().mods()[0].id() != 0);
}
#[test]
fn extract_fns() {
let doc = mk_doc(
@"fn a() { } \
mod b { fn c() {
} }");
assert!(doc.cratemod().fns()[0].name_() == ~"a");
assert!(doc.cratemod().mods()[0].fns()[0].name_() == ~"c");
}
#[test]
fn extract_should_set_fn_ast_id() {
let doc = mk_doc(@"fn a() { }");
assert!(doc.cratemod().fns()[0].id() != 0);
}
#[test]
fn extract_should_use_default_crate_name() {
let source = @"";
let ast = parse::from_str(source);
let doc = extract(ast, ~"burp");
assert!(doc.cratemod().name_() == ~"burp");
}
#[test]
fn extract_from_seq_srv() {
let source = ~"";
do astsrv::from_str(source) |srv| {
let doc = from_srv(srv, ~"name");
assert!(doc.cratemod().name_() == ~"name");
}
}
#[test]
fn should_extract_static_name_and_id() {
let doc = mk_doc(@"static a: int = 0;");
assert!(doc.cratemod().statics()[0].id() != 0);
assert!(doc.cratemod().statics()[0].name_() == ~"a");
}
#[test]
fn should_extract_enums() {
let doc = mk_doc(@"enum e { v }");
assert!(doc.cratemod().enums()[0].id() != 0);
assert!(doc.cratemod().enums()[0].name_() == ~"e");
}
#[test]
fn should_extract_enum_variants() {
let doc = mk_doc(@"enum e { v }");
assert!(doc.cratemod().enums()[0].variants[0].name == ~"v");
}
#[test]
fn should_extract_traits() {
let doc = mk_doc(@"trait i { fn f(); }");
assert!(doc.cratemod().traits()[0].name_() == ~"i");
}
#[test]
fn should_extract_trait_methods() {
let doc = mk_doc(@"trait i { fn f(); }");
assert!(doc.cratemod().traits()[0].methods[0].name == ~"f");
}
#[test]
fn should_extract_impl_methods() {
let doc = mk_doc(@"impl int { fn f() { } }");
assert!(doc.cratemod().impls()[0].methods[0].name == ~"f");
}
#[test]
fn should_extract_tys() {
let doc = mk_doc(@"type a = int;");
assert!(doc.cratemod().types()[0].name_() == ~"a");
}
#[test]
fn should_extract_structs() {
let doc = mk_doc(@"struct Foo { field: () }");
assert!(doc.cratemod().structs()[0].name_() == ~"Foo");
}
#[test]
fn should_extract_struct_fields() {
let doc = mk_doc(@"struct Foo { field: () }");
assert!(doc.cratemod().structs()[0].fields[0] == ~"field");
}
}

View File

@ -1,4 +1,4 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
@ -8,397 +8,92 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std;
use clean::*;
use std::iter::Extendable;
use doc;
#[cfg(test)] use extract;
#[cfg(test)] use parse;
pub trait DocFolder {
fn fold_item(&mut self, item: Item) -> Option<Item> {
self.fold_item_recur(item)
}
pub struct Fold<T> {
ctxt: T,
fold_doc: FoldDoc<T>,
fold_crate: FoldCrate<T>,
fold_item: FoldItem<T>,
fold_mod: FoldMod<T>,
fold_nmod: FoldNmod<T>,
fold_fn: FoldFn<T>,
fold_static: FoldStatic<T>,
fold_enum: FoldEnum<T>,
fold_trait: FoldTrait<T>,
fold_impl: FoldImpl<T>,
fold_type: FoldType<T>,
fold_struct: FoldStruct<T>
}
/// don't override!
fn fold_item_recur(&mut self, item: Item) -> Option<Item> {
use std::util::swap;
let Item { attrs, name, source, visibility, id, inner } = item;
let inner = inner;
let c = |x| self.fold_item(x);
let inner = match inner {
StructItem(i) => {
let mut i = i;
let mut foo = ~[]; swap(&mut foo, &mut i.fields);
i.fields.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
StructItem(i)
},
ModuleItem(i) => {
ModuleItem(self.fold_mod(i))
},
EnumItem(i) => {
let mut i = i;
let mut foo = ~[]; swap(&mut foo, &mut i.variants);
i.variants.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
EnumItem(i)
},
TraitItem(i) => {
fn vtrm<T: DocFolder>(this: &mut T, trm: TraitMethod) -> Option<TraitMethod> {
match trm {
Required(it) => {
match this.fold_item(it) {
Some(x) => return Some(Required(x)),
None => return None,
}
},
Provided(it) => {
match this.fold_item(it) {
Some(x) => return Some(Provided(x)),
None => return None,
}
},
}
}
let mut i = i;
let mut foo = ~[]; swap(&mut foo, &mut i.methods);
i.methods.extend(&mut foo.move_iter().filter_map(|x| vtrm(self, x)));
TraitItem(i)
},
ImplItem(i) => {
let mut i = i;
let mut foo = ~[]; swap(&mut foo, &mut i.methods);
i.methods.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
ImplItem(i)
},
VariantItem(i) => {
let i2 = i.clone(); // this clone is small
match i.kind {
StructVariant(j) => {
let mut j = j;
let mut foo = ~[]; swap(&mut foo, &mut j.fields);
j.fields.extend(&mut foo.move_iter().filter_map(c));
VariantItem(Variant {kind: StructVariant(j), ..i2})
},
_ => VariantItem(i2)
}
},
x => x
};
impl<T:Clone> Clone for Fold<T> {
fn clone(&self) -> Fold<T> {
Fold {
ctxt: self.ctxt.clone(),
fold_doc: self.fold_doc,
fold_crate: self.fold_crate,
fold_item: self.fold_item,
fold_mod: self.fold_mod,
fold_nmod: self.fold_nmod,
fold_fn: self.fold_fn,
fold_static: self.fold_static,
fold_enum: self.fold_enum,
fold_trait: self.fold_trait,
fold_impl: self.fold_impl,
fold_type: self.fold_type,
fold_struct: self.fold_struct
}
Some(Item { attrs: attrs, name: name, source: source, inner: inner,
visibility: visibility, id: id })
}
fn fold_mod(&mut self, m: Module) -> Module {
Module { items: m.items.move_iter().filter_map(|i| self.fold_item(i)).collect() }
}
fn fold_crate(&mut self, mut c: Crate) -> Crate {
c.module = match std::util::replace(&mut c.module, None) {
Some(module) => self.fold_item(module), None => None
};
return c;
}
}
type FoldDoc<T> = @fn(fold: &Fold<T>, doc: doc::Doc) -> doc::Doc;
type FoldCrate<T> = @fn(fold: &Fold<T>, doc: doc::CrateDoc) -> doc::CrateDoc;
type FoldItem<T> = @fn(fold: &Fold<T>, doc: doc::ItemDoc) -> doc::ItemDoc;
type FoldMod<T> = @fn(fold: &Fold<T>, doc: doc::ModDoc) -> doc::ModDoc;
type FoldNmod<T> = @fn(fold: &Fold<T>, doc: doc::NmodDoc) -> doc::NmodDoc;
type FoldFn<T> = @fn(fold: &Fold<T>, doc: doc::FnDoc) -> doc::FnDoc;
type FoldStatic<T> = @fn(fold: &Fold<T>, doc: doc::StaticDoc) -> doc::StaticDoc;
type FoldEnum<T> = @fn(fold: &Fold<T>, doc: doc::EnumDoc) -> doc::EnumDoc;
type FoldTrait<T> = @fn(fold: &Fold<T>, doc: doc::TraitDoc) -> doc::TraitDoc;
type FoldImpl<T> = @fn(fold: &Fold<T>, doc: doc::ImplDoc) -> doc::ImplDoc;
type FoldType<T> = @fn(fold: &Fold<T>, doc: doc::TyDoc) -> doc::TyDoc;
type FoldStruct<T> = @fn(fold: &Fold<T>,
doc: doc::StructDoc) -> doc::StructDoc;
// This exists because fn types don't infer correctly as record
// initializers, but they do as function arguments
fn mk_fold<T>(
ctxt: T,
fold_doc: FoldDoc<T>,
fold_crate: FoldCrate<T>,
fold_item: FoldItem<T>,
fold_mod: FoldMod<T>,
fold_nmod: FoldNmod<T>,
fold_fn: FoldFn<T>,
fold_static: FoldStatic<T>,
fold_enum: FoldEnum<T>,
fold_trait: FoldTrait<T>,
fold_impl: FoldImpl<T>,
fold_type: FoldType<T>,
fold_struct: FoldStruct<T>
) -> Fold<T> {
Fold {
ctxt: ctxt,
fold_doc: fold_doc,
fold_crate: fold_crate,
fold_item: fold_item,
fold_mod: fold_mod,
fold_nmod: fold_nmod,
fold_fn: fold_fn,
fold_static: fold_static,
fold_enum: fold_enum,
fold_trait: fold_trait,
fold_impl: fold_impl,
fold_type: fold_type,
fold_struct: fold_struct
}
}
pub fn default_any_fold<T:Clone>(ctxt: T) -> Fold<T> {
mk_fold(
ctxt,
|f, d| default_seq_fold_doc(f, d),
|f, d| default_seq_fold_crate(f, d),
|f, d| default_seq_fold_item(f, d),
|f, d| default_any_fold_mod(f, d),
|f, d| default_any_fold_nmod(f, d),
|f, d| default_seq_fold_fn(f, d),
|f, d| default_seq_fold_static(f, d),
|f, d| default_seq_fold_enum(f, d),
|f, d| default_seq_fold_trait(f, d),
|f, d| default_seq_fold_impl(f, d),
|f, d| default_seq_fold_type(f, d),
|f, d| default_seq_fold_struct(f, d)
)
}
pub fn default_seq_fold<T:Clone>(ctxt: T) -> Fold<T> {
mk_fold(
ctxt,
|f, d| default_seq_fold_doc(f, d),
|f, d| default_seq_fold_crate(f, d),
|f, d| default_seq_fold_item(f, d),
|f, d| default_seq_fold_mod(f, d),
|f, d| default_seq_fold_nmod(f, d),
|f, d| default_seq_fold_fn(f, d),
|f, d| default_seq_fold_static(f, d),
|f, d| default_seq_fold_enum(f, d),
|f, d| default_seq_fold_trait(f, d),
|f, d| default_seq_fold_impl(f, d),
|f, d| default_seq_fold_type(f, d),
|f, d| default_seq_fold_struct(f, d)
)
}
pub fn default_par_fold<T:Clone>(ctxt: T) -> Fold<T> {
mk_fold(
ctxt,
|f, d| default_seq_fold_doc(f, d),
|f, d| default_seq_fold_crate(f, d),
|f, d| default_seq_fold_item(f, d),
|f, d| default_par_fold_mod(f, d),
|f, d| default_par_fold_nmod(f, d),
|f, d| default_seq_fold_fn(f, d),
|f, d| default_seq_fold_static(f, d),
|f, d| default_seq_fold_enum(f, d),
|f, d| default_seq_fold_trait(f, d),
|f, d| default_seq_fold_impl(f, d),
|f, d| default_seq_fold_type(f, d),
|f, d| default_seq_fold_struct(f, d)
)
}
pub fn default_seq_fold_doc<T>(fold: &Fold<T>, doc: doc::Doc) -> doc::Doc {
doc::Doc {
pages: do doc.pages.iter().map |page| {
match (*page).clone() {
doc::CratePage(doc) => {
doc::CratePage((fold.fold_crate)(fold, doc))
}
doc::ItemPage(doc) => {
doc::ItemPage(fold_ItemTag(fold, doc))
}
}
}.collect(),
.. doc
}
}
pub fn default_seq_fold_crate<T>(
fold: &Fold<T>,
doc: doc::CrateDoc
) -> doc::CrateDoc {
doc::CrateDoc {
topmod: (fold.fold_mod)(fold, doc.topmod.clone())
}
}
pub fn default_seq_fold_item<T>(
_fold: &Fold<T>,
doc: doc::ItemDoc
) -> doc::ItemDoc {
doc
}
pub fn default_any_fold_mod<T:Clone>(
fold: &Fold<T>,
doc: doc::ModDoc
) -> doc::ModDoc {
doc::ModDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
items: doc.items.iter().map(|ItemTag| {
fold_ItemTag(fold, (*ItemTag).clone())
}).collect(),
.. doc
}
}
pub fn default_seq_fold_mod<T>(
fold: &Fold<T>,
doc: doc::ModDoc
) -> doc::ModDoc {
doc::ModDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
items: doc.items.iter().map(|ItemTag| {
fold_ItemTag(fold, (*ItemTag).clone())
}).collect(),
.. doc
}
}
pub fn default_par_fold_mod<T:Clone>(
fold: &Fold<T>,
doc: doc::ModDoc
) -> doc::ModDoc {
doc::ModDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
items: doc.items.iter().map(|ItemTag| {
fold_ItemTag(fold, (*ItemTag).clone())
}).collect(),
.. doc
}
}
pub fn default_any_fold_nmod<T:Clone>(
fold: &Fold<T>,
doc: doc::NmodDoc
) -> doc::NmodDoc {
doc::NmodDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
fns: doc.fns.iter().map(|FnDoc| {
(fold.fold_fn)(fold, (*FnDoc).clone())
}).collect(),
.. doc
}
}
pub fn default_seq_fold_nmod<T>(
fold: &Fold<T>,
doc: doc::NmodDoc
) -> doc::NmodDoc {
doc::NmodDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
fns: doc.fns.iter().map(|FnDoc| {
(fold.fold_fn)(fold, (*FnDoc).clone())
}).collect(),
.. doc
}
}
pub fn default_par_fold_nmod<T:Clone>(
fold: &Fold<T>,
doc: doc::NmodDoc
) -> doc::NmodDoc {
doc::NmodDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
fns: doc.fns.iter().map(|FnDoc| {
(fold.fold_fn)(fold, (*FnDoc).clone())
}).collect(),
.. doc
}
}
pub fn fold_ItemTag<T>(fold: &Fold<T>, doc: doc::ItemTag) -> doc::ItemTag {
match doc {
doc::ModTag(ModDoc) => {
doc::ModTag((fold.fold_mod)(fold, ModDoc))
}
doc::NmodTag(nModDoc) => {
doc::NmodTag((fold.fold_nmod)(fold, nModDoc))
}
doc::FnTag(FnDoc) => {
doc::FnTag((fold.fold_fn)(fold, FnDoc))
}
doc::StaticTag(StaticDoc) => {
doc::StaticTag((fold.fold_static)(fold, StaticDoc))
}
doc::EnumTag(EnumDoc) => {
doc::EnumTag((fold.fold_enum)(fold, EnumDoc))
}
doc::TraitTag(TraitDoc) => {
doc::TraitTag((fold.fold_trait)(fold, TraitDoc))
}
doc::ImplTag(ImplDoc) => {
doc::ImplTag((fold.fold_impl)(fold, ImplDoc))
}
doc::TyTag(TyDoc) => {
doc::TyTag((fold.fold_type)(fold, TyDoc))
}
doc::StructTag(StructDoc) => {
doc::StructTag((fold.fold_struct)(fold, StructDoc))
}
}
}
pub fn default_seq_fold_fn<T>(
fold: &Fold<T>,
doc: doc::FnDoc
) -> doc::FnDoc {
doc::SimpleItemDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
pub fn default_seq_fold_static<T>(
fold: &Fold<T>,
doc: doc::StaticDoc
) -> doc::StaticDoc {
doc::SimpleItemDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
pub fn default_seq_fold_enum<T>(
fold: &Fold<T>,
doc: doc::EnumDoc
) -> doc::EnumDoc {
doc::EnumDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
pub fn default_seq_fold_trait<T>(
fold: &Fold<T>,
doc: doc::TraitDoc
) -> doc::TraitDoc {
doc::TraitDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
pub fn default_seq_fold_impl<T>(
fold: &Fold<T>,
doc: doc::ImplDoc
) -> doc::ImplDoc {
doc::ImplDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
pub fn default_seq_fold_type<T>(
fold: &Fold<T>,
doc: doc::TyDoc
) -> doc::TyDoc {
doc::SimpleItemDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
pub fn default_seq_fold_struct<T>(
fold: &Fold<T>,
doc: doc::StructDoc
) -> doc::StructDoc {
doc::StructDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
#[test]
fn default_fold_should_produce_same_doc() {
let source = @"mod a { fn b() { } mod c { fn d() { } } }";
let ast = parse::from_str(source);
let doc = extract::extract(ast, ~"");
let fld = default_seq_fold(());
let folded = (fld.fold_doc)(&fld, doc.clone());
assert_eq!(doc, folded);
}
#[test]
fn default_fold_should_produce_same_statics() {
let source = @"static a: int = 0;";
let ast = parse::from_str(source);
let doc = extract::extract(ast, ~"");
let fld = default_seq_fold(());
let folded = (fld.fold_doc)(&fld, doc.clone());
assert_eq!(doc, folded);
}
#[test]
fn default_fold_should_produce_same_enums() {
let source = @"enum a { b }";
let ast = parse::from_str(source);
let doc = extract::extract(ast, ~"");
let fld = default_seq_fold(());
let folded = (fld.fold_doc)(&fld, doc.clone());
assert_eq!(doc, folded);
}
#[test]
fn default_parallel_fold_should_produce_same_doc() {
let source = @"mod a { fn b() { } mod c { fn d() { } } }";
let ast = parse::from_str(source);
let doc = extract::extract(ast, ~"");
let fld = default_par_fold(());
let folded = (fld.fold_doc)(&fld, doc.clone());
assert_eq!(doc, folded);
}

View File

@ -1,284 +0,0 @@
// Copyright 2012 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.
//! Build indexes as appropriate for the markdown pass
use astsrv;
use config;
use doc::ItemUtils;
use doc;
use fold::Fold;
use fold;
use markdown_pass;
use markdown_writer;
use pass::Pass;
pub fn mk_pass(config: config::Config) -> Pass {
Pass {
name: ~"markdown_index",
f: |srv, doc| run(srv, doc, config.clone())
}
}
pub fn run(
_srv: astsrv::Srv,
doc: doc::Doc,
config: config::Config
) -> doc::Doc {
let fold = Fold {
fold_mod: fold_mod,
fold_nmod: fold_nmod,
.. fold::default_any_fold(config)
};
(fold.fold_doc)(&fold, doc)
}
fn fold_mod(
fold: &fold::Fold<config::Config>,
doc: doc::ModDoc
) -> doc::ModDoc {
let doc = fold::default_any_fold_mod(fold, doc);
doc::ModDoc {
index: Some(build_mod_index(doc.clone(), fold.ctxt.clone())),
.. doc
}
}
fn fold_nmod(
fold: &fold::Fold<config::Config>,
doc: doc::NmodDoc
) -> doc::NmodDoc {
let doc = fold::default_any_fold_nmod(fold, doc);
doc::NmodDoc {
index: Some(build_nmod_index(doc.clone(), fold.ctxt.clone())),
.. doc
}
}
fn build_mod_index(
doc: doc::ModDoc,
config: config::Config
) -> doc::Index {
doc::Index {
entries: doc.items.map(|doc| {
item_to_entry((*doc).clone(), &config)
})
}
}
fn build_nmod_index(
doc: doc::NmodDoc,
config: config::Config
) -> doc::Index {
doc::Index {
entries: doc.fns.map(|doc| {
item_to_entry(doc::FnTag((*doc).clone()), &config)
})
}
}
fn item_to_entry(
doc: doc::ItemTag,
config: &config::Config
) -> doc::IndexEntry {
let link = match doc {
doc::ModTag(_) | doc::NmodTag(_)
if config.output_style == config::DocPerMod => {
markdown_writer::make_filename(config,
doc::ItemPage(doc.clone())).to_str()
}
_ => {
~"#" + pandoc_header_id(markdown_pass::header_text(doc.clone()))
}
};
doc::IndexEntry {
kind: markdown_pass::header_kind(doc.clone()),
name: markdown_pass::header_name(doc.clone()),
brief: doc.brief(),
link: link
}
}
pub fn pandoc_header_id(header: &str) -> ~str {
// http://johnmacfarlane.net/pandoc/README.html#headers
let header = remove_formatting(header);
let header = remove_punctuation(header);
let header = replace_with_hyphens(header);
let header = convert_to_lowercase(header);
let header = remove_up_to_first_letter(header);
let header = maybe_use_section_id(header);
return header;
fn remove_formatting(s: &str) -> ~str {
s.replace("`", "")
}
fn remove_punctuation(s: &str) -> ~str {
let s = s.replace("<", "");
let s = s.replace(">", "");
let s = s.replace("[", "");
let s = s.replace("]", "");
let s = s.replace("(", "");
let s = s.replace(")", "");
let s = s.replace("@~", "");
let s = s.replace("~", "");
let s = s.replace("/", "");
let s = s.replace(":", "");
let s = s.replace("&", "");
let s = s.replace("^", "");
let s = s.replace(",", "");
let s = s.replace("'", "");
let s = s.replace("+", "");
return s;
}
fn replace_with_hyphens(s: &str) -> ~str {
// Collapse sequences of whitespace to a single dash
// XXX: Hacky implementation here that only covers
// one or two spaces.
let s = s.trim();
let s = s.replace(" ", "-");
let s = s.replace(" ", "-");
return s;
}
// FIXME: #4318 Instead of to_ascii and to_str_ascii, could use
// to_ascii_consume and to_str_consume to not do a unnecessary copy.
fn convert_to_lowercase(s: &str) -> ~str { s.to_ascii().to_lower().to_str_ascii() }
fn remove_up_to_first_letter(s: &str) -> ~str { s.to_str() }
fn maybe_use_section_id(s: &str) -> ~str { s.to_str() }
}
#[cfg(test)]
mod test {
use astsrv;
use attr_pass;
use config;
use desc_to_brief_pass;
use doc;
use extract;
use markdown_index_pass::run;
use path_pass;
use prune_hidden_pass;
use super::pandoc_header_id;
fn mk_doc(output_style: config::OutputStyle, source: ~str)
-> doc::Doc {
do astsrv::from_str(source) |srv| {
let config = config::Config {
output_style: output_style,
.. config::default_config(&Path("whatever"))
};
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (attr_pass::mk_pass().f)(srv.clone(), doc);
let doc = (prune_hidden_pass::mk_pass().f)(srv.clone(), doc);
let doc = (desc_to_brief_pass::mk_pass().f)(srv.clone(), doc);
let doc = (path_pass::mk_pass().f)(srv.clone(), doc);
run(srv.clone(), doc, config)
}
}
#[test]
fn should_remove_punctuation_from_headers() {
assert!(pandoc_header_id("impl foo of bar<A>") ==
~"impl-foo-of-bara");
assert!(pandoc_header_id("impl of num::num for int")
== ~"impl-of-numnum-for-int");
assert!(pandoc_header_id("impl of num::num for int/&")
== ~"impl-of-numnum-for-int");
assert!(pandoc_header_id("impl of num::num for ^int")
== ~"impl-of-numnum-for-int");
assert!(pandoc_header_id("impl for & condvar")
== ~"impl-for-condvar");
assert!(pandoc_header_id("impl of Select<T, U> for (Left, Right)")
== ~"impl-of-selectt-u-for-left-right");
assert!(pandoc_header_id("impl of Condition<'self, T, U>")
== ~"impl-of-conditionself-t-u");
assert!(pandoc_header_id("impl of Condition<T: Clone>")
== ~"impl-of-conditiont-clone");
}
#[test]
fn should_trim_whitespace_after_removing_punctuation() {
assert_eq!(pandoc_header_id("impl foo for ()"), ~"impl-foo-for");
}
#[test]
fn should_index_mod_contents() {
let doc = mk_doc(
config::DocPerCrate,
~"mod a { } fn b() { }"
);
assert!(doc.cratemod().index.unwrap().entries[0] == doc::IndexEntry {
kind: ~"Module",
name: ~"a",
brief: None,
link: ~"#module-a"
});
assert!(doc.cratemod().index.unwrap().entries[1] == doc::IndexEntry {
kind: ~"Function",
name: ~"b",
brief: None,
link: ~"#function-b"
});
}
#[test]
fn should_index_mod_contents_multi_page() {
let doc = mk_doc(
config::DocPerMod,
~"mod a { } fn b() { }"
);
assert_eq!(doc.cratemod().index.unwrap().entries[0], doc::IndexEntry {
kind: ~"Module",
name: ~"a",
brief: None,
link: ~"a.html"
});
assert_eq!(doc.cratemod().index.unwrap().entries[1], doc::IndexEntry {
kind: ~"Function",
name: ~"b",
brief: None,
link: ~"#function-b"
});
}
#[test]
fn should_add_brief_desc_to_index() {
let doc = mk_doc(
config::DocPerMod,
~"#[doc = \"test\"] mod a { }"
);
assert_eq!(doc.cratemod().index.unwrap().entries[0].brief, Some(~"test"));
}
#[test]
fn should_index_foreign_mod_contents() {
let doc = mk_doc(
config::DocPerCrate,
~"extern { fn b(); }"
);
// hidden __std_macros module at the start.
assert_eq!(doc.cratemod().nmods()[0].index.unwrap().entries[0],
doc::IndexEntry {
kind: ~"Function",
name: ~"b",
brief: None,
link: ~"#function-b"
});
}
}

View File

@ -1,941 +0,0 @@
// Copyright 2012 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.
//! Generate markdown from a document tree
use astsrv;
use doc::ItemUtils;
use doc;
use markdown_pass;
use markdown_writer::Writer;
use markdown_writer::WriterUtils;
use markdown_writer::WriterFactory;
use pass::Pass;
use sort_pass;
use std::cell::Cell;
use std::str;
use std::vec;
use syntax;
pub fn mk_pass(writer_factory: WriterFactory) -> Pass {
let writer_factory = Cell::new(writer_factory);
Pass {
name: ~"markdown",
f: |srv, doc| run(srv, doc, writer_factory.take())
}
}
fn run(
srv: astsrv::Srv,
doc: doc::Doc,
writer_factory: WriterFactory
) -> doc::Doc {
fn mods_last(item1: &doc::ItemTag, item2: &doc::ItemTag) -> bool {
fn is_mod(item: &doc::ItemTag) -> bool {
match *item {
doc::ModTag(_) => true,
_ => false
}
}
let lteq = !is_mod(item1) || is_mod(item2);
lteq
}
// Sort the items so mods come last. All mods will be
// output at the same header level so sorting mods last
// makes the headers come out nested correctly.
let sorted_doc = (sort_pass::mk_pass(
~"mods last", mods_last
).f)(srv, doc.clone());
write_markdown(sorted_doc, writer_factory);
return doc;
}
struct Ctxt {
w: Writer
}
pub fn write_markdown(
doc: doc::Doc,
writer_factory: WriterFactory
) {
// There is easy parallelism to be had here, but
// we don't want to spawn too many pandoc processes.
// (See #2484, which is closed.)
do doc.pages.map |page| {
let ctxt = Ctxt {
w: writer_factory((*page).clone())
};
write_page(&ctxt, page)
};
}
fn write_page(ctxt: &Ctxt, page: &doc::Page) {
write_title(ctxt, (*page).clone());
match (*page).clone() {
doc::CratePage(doc) => {
write_crate(ctxt, doc);
}
doc::ItemPage(doc) => {
// We don't write a header for item's pages because their
// header in the html output is created by the page title
write_item_no_header(ctxt, doc);
}
}
ctxt.w.put_done();
}
fn write_title(ctxt: &Ctxt, page: doc::Page) {
ctxt.w.put_line(fmt!("%% %s", make_title(page)));
ctxt.w.put_line(~"");
}
fn make_title(page: doc::Page) -> ~str {
let item = match page {
doc::CratePage(CrateDoc) => {
doc::ModTag(CrateDoc.topmod.clone())
}
doc::ItemPage(ItemTag) => {
ItemTag
}
};
let title = markdown_pass::header_text(item);
let title = title.replace("`", "");
return title;
}
enum Hlvl {
H1 = 1,
H2 = 2,
H3 = 3,
H4 = 4
}
fn write_header(ctxt: &Ctxt, lvl: Hlvl, doc: doc::ItemTag) {
let text = header_text(doc);
write_header_(ctxt, lvl, text);
}
fn write_header_(ctxt: &Ctxt, lvl: Hlvl, title: ~str) {
let hashes = str::from_chars(vec::from_elem(lvl as uint, '#'));
ctxt.w.put_line(fmt!("%s %s", hashes, title));
ctxt.w.put_line(~"");
}
pub fn header_kind(doc: doc::ItemTag) -> ~str {
match doc {
doc::ModTag(_) => {
if doc.id() == syntax::ast::CRATE_NODE_ID {
~"Crate"
} else {
~"Module"
}
}
doc::NmodTag(_) => {
~"Foreign module"
}
doc::FnTag(_) => {
~"Function"
}
doc::StaticTag(_) => {
~"Static"
}
doc::EnumTag(_) => {
~"Enum"
}
doc::TraitTag(_) => {
~"Trait"
}
doc::ImplTag(_) => {
~"Implementation"
}
doc::TyTag(_) => {
~"Type"
}
doc::StructTag(_) => {
~"Struct"
}
}
}
pub fn header_name(doc: doc::ItemTag) -> ~str {
let fullpath = (doc.path() + &[doc.name_()]).connect("::");
match &doc {
&doc::ModTag(_) if doc.id() != syntax::ast::CRATE_NODE_ID => {
fullpath
}
&doc::NmodTag(_) => {
fullpath
}
&doc::ImplTag(ref doc) => {
assert!(doc.self_ty.is_some());
let bounds = if doc.bounds_str.is_some() {
fmt!(" where %s", *doc.bounds_str.get_ref())
} else {
~""
};
let self_ty = doc.self_ty.get_ref();
let mut trait_part = ~"";
for (i, trait_type) in doc.trait_types.iter().enumerate() {
if i == 0 {
trait_part.push_str(" of ");
} else {
trait_part.push_str(", ");
}
trait_part.push_str(*trait_type);
}
fmt!("%s for %s%s", trait_part, *self_ty, bounds)
}
_ => {
doc.name_()
}
}
}
pub fn header_text(doc: doc::ItemTag) -> ~str {
match &doc {
&doc::ImplTag(ref ImplDoc) => {
let header_kind = header_kind(doc.clone());
let bounds = if ImplDoc.bounds_str.is_some() {
fmt!(" where `%s`", *ImplDoc.bounds_str.get_ref())
} else {
~""
};
let desc = if ImplDoc.trait_types.is_empty() {
fmt!("for `%s`%s", *ImplDoc.self_ty.get_ref(), bounds)
} else {
fmt!("of `%s` for `%s`%s",
ImplDoc.trait_types[0],
*ImplDoc.self_ty.get_ref(),
bounds)
};
return fmt!("%s %s", header_kind, desc);
}
_ => {}
}
header_text_(header_kind(doc.clone()),
header_name(doc))
}
fn header_text_(kind: &str, name: &str) -> ~str {
fmt!("%s `%s`", kind, name)
}
fn write_crate(
ctxt: &Ctxt,
doc: doc::CrateDoc
) {
write_top_module(ctxt, doc.topmod.clone());
}
fn write_top_module(
ctxt: &Ctxt,
ModDoc: doc::ModDoc
) {
write_mod_contents(ctxt, ModDoc);
}
fn write_mod(
ctxt: &Ctxt,
ModDoc: doc::ModDoc
) {
write_mod_contents(ctxt, ModDoc);
}
fn write_common(
ctxt: &Ctxt,
desc: Option<~str>,
sections: &[doc::Section]
) {
write_desc(ctxt, desc);
write_sections(ctxt, sections);
}
fn write_desc(
ctxt: &Ctxt,
desc: Option<~str>
) {
match desc {
Some(desc) => {
ctxt.w.put_line(desc);
ctxt.w.put_line(~"");
}
None => ()
}
}
fn write_sections(ctxt: &Ctxt, sections: &[doc::Section]) {
for section in sections.iter() {
write_section(ctxt, (*section).clone());
}
}
fn write_section(ctxt: &Ctxt, section: doc::Section) {
write_header_(ctxt, H4, section.header.clone());
ctxt.w.put_line(section.body.clone());
ctxt.w.put_line(~"");
}
fn write_mod_contents(
ctxt: &Ctxt,
doc: doc::ModDoc
) {
write_common(ctxt, doc.desc(), doc.sections());
if doc.index.is_some() {
write_index(ctxt, doc.index.get_ref());
}
for itemTag in doc.items.iter() {
write_item(ctxt, (*itemTag).clone());
}
}
fn write_item(ctxt: &Ctxt, doc: doc::ItemTag) {
write_item_(ctxt, doc, true);
}
fn write_item_no_header(ctxt: &Ctxt, doc: doc::ItemTag) {
write_item_(ctxt, doc, false);
}
fn write_item_(ctxt: &Ctxt, doc: doc::ItemTag, write_header: bool) {
if write_header {
write_item_header(ctxt, doc.clone());
}
match doc {
doc::ModTag(ModDoc) => write_mod(ctxt, ModDoc),
doc::NmodTag(nModDoc) => write_nmod(ctxt, nModDoc),
doc::FnTag(FnDoc) => write_fn(ctxt, FnDoc),
doc::StaticTag(StaticDoc) => write_static(ctxt, StaticDoc),
doc::EnumTag(EnumDoc) => write_enum(ctxt, EnumDoc),
doc::TraitTag(TraitDoc) => write_trait(ctxt, TraitDoc),
doc::ImplTag(ImplDoc) => write_impl(ctxt, ImplDoc),
doc::TyTag(TyDoc) => write_type(ctxt, TyDoc),
doc::StructTag(StructDoc) => put_struct(ctxt, StructDoc),
}
}
fn write_item_header(ctxt: &Ctxt, doc: doc::ItemTag) {
write_header(ctxt, item_header_lvl(&doc), doc);
}
fn item_header_lvl(doc: &doc::ItemTag) -> Hlvl {
match doc {
&doc::ModTag(_) | &doc::NmodTag(_) => H1,
_ => H2
}
}
fn write_index(ctxt: &Ctxt, index: &doc::Index) {
if index.entries.is_empty() {
return;
}
ctxt.w.put_line(~"<div class='index'>");
ctxt.w.put_line(~"");
for entry in index.entries.iter() {
let header = header_text_(entry.kind, entry.name);
let id = entry.link.clone();
if entry.brief.is_some() {
ctxt.w.put_line(fmt!("* [%s](%s) - %s",
header, id, *entry.brief.get_ref()));
} else {
ctxt.w.put_line(fmt!("* [%s](%s)", header, id));
}
}
ctxt.w.put_line(~"");
ctxt.w.put_line(~"</div>");
ctxt.w.put_line(~"");
}
fn write_nmod(ctxt: &Ctxt, doc: doc::NmodDoc) {
write_common(ctxt, doc.desc(), doc.sections());
if doc.index.is_some() {
write_index(ctxt, doc.index.get_ref());
}
for FnDoc in doc.fns.iter() {
write_item_header(ctxt, doc::FnTag((*FnDoc).clone()));
write_fn(ctxt, (*FnDoc).clone());
}
}
fn write_fn(
ctxt: &Ctxt,
doc: doc::FnDoc
) {
write_fnlike(ctxt, doc.sig.clone(), doc.desc(), doc.sections());
}
fn write_fnlike(
ctxt: &Ctxt,
sig: Option<~str>,
desc: Option<~str>,
sections: &[doc::Section]
) {
write_sig(ctxt, sig);
write_common(ctxt, desc, sections);
}
fn write_sig(ctxt: &Ctxt, sig: Option<~str>) {
match sig {
Some(sig) => {
ctxt.w.put_line(code_block(sig));
ctxt.w.put_line(~"");
}
None => fail!("unimplemented")
}
}
fn code_block(s: ~str) -> ~str {
fmt!("~~~ {.rust}
%s
~~~", s)
}
fn write_static(
ctxt: &Ctxt,
doc: doc::StaticDoc
) {
write_sig(ctxt, doc.sig.clone());
write_common(ctxt, doc.desc(), doc.sections());
}
fn write_enum(
ctxt: &Ctxt,
doc: doc::EnumDoc
) {
write_common(ctxt, doc.desc(), doc.sections());
write_variants(ctxt, doc.variants);
}
fn write_variants(
ctxt: &Ctxt,
docs: &[doc::VariantDoc]
) {
if docs.is_empty() {
return;
}
write_header_(ctxt, H4, ~"Variants");
for variant in docs.iter() {
write_variant(ctxt, (*variant).clone());
}
ctxt.w.put_line(~"");
}
fn write_variant(ctxt: &Ctxt, doc: doc::VariantDoc) {
assert!(doc.sig.is_some());
let sig = doc.sig.get_ref();
// space out list items so they all end up within paragraph elements
ctxt.w.put_line(~"");
match doc.desc.clone() {
Some(desc) => {
ctxt.w.put_line(list_item_indent(fmt!("* `%s` - %s", *sig, desc)));
}
None => {
ctxt.w.put_line(fmt!("* `%s`", *sig));
}
}
}
fn list_item_indent(item: &str) -> ~str {
let indented = item.any_line_iter().collect::<~[&str]>();
// separate markdown elements within `*` lists must be indented by four
// spaces, or they will escape the list context. indenting everything
// seems fine though.
indented.connect("\n ")
}
fn write_trait(ctxt: &Ctxt, doc: doc::TraitDoc) {
write_common(ctxt, doc.desc(), doc.sections());
write_methods(ctxt, doc.methods);
}
fn write_methods(ctxt: &Ctxt, docs: &[doc::MethodDoc]) {
for doc in docs.iter() {
write_method(ctxt, (*doc).clone());
}
}
fn write_method(ctxt: &Ctxt, doc: doc::MethodDoc) {
write_header_(ctxt, H3, header_text_("Method", doc.name));
write_fnlike(ctxt, doc.sig.clone(), doc.desc.clone(), doc.sections);
}
fn write_impl(ctxt: &Ctxt, doc: doc::ImplDoc) {
write_common(ctxt, doc.desc(), doc.sections());
write_methods(ctxt, doc.methods);
}
fn write_type(
ctxt: &Ctxt,
doc: doc::TyDoc
) {
write_sig(ctxt, doc.sig.clone());
write_common(ctxt, doc.desc(), doc.sections());
}
fn put_struct(
ctxt: &Ctxt,
doc: doc::StructDoc
) {
write_sig(ctxt, doc.sig.clone());
write_common(ctxt, doc.desc(), doc.sections());
}
#[cfg(test)]
mod test {
use astsrv;
use attr_pass;
use config;
use desc_to_brief_pass;
use doc;
use extract;
use markdown_index_pass;
use markdown_pass::{mk_pass, write_markdown};
use markdown_writer;
use path_pass;
use page_pass;
use prune_hidden_pass;
use sectionalize_pass;
use trim_pass;
use tystr_pass;
use unindent_pass;
fn render(source: ~str) -> ~str {
let (srv, doc) = create_doc_srv(source);
let markdown = write_markdown_str_srv(srv, doc);
debug!("markdown: %s", markdown);
markdown
}
fn create_doc_srv(source: ~str) -> (astsrv::Srv, doc::Doc) {
do astsrv::from_str(source) |srv| {
let config = config::Config {
output_style: config::DocPerCrate,
.. config::default_config(&Path("whatever"))
};
let doc = extract::from_srv(srv.clone(), ~"");
debug!("doc (extract): %?", doc);
let doc = (tystr_pass::mk_pass().f)(srv.clone(), doc);
debug!("doc (tystr): %?", doc);
let doc = (path_pass::mk_pass().f)(srv.clone(), doc);
debug!("doc (path): %?", doc);
let doc = (attr_pass::mk_pass().f)(srv.clone(), doc);
debug!("doc (attr): %?", doc);
let doc = (prune_hidden_pass::mk_pass().f)(srv.clone(), doc);
debug!("doc (prune_hidden): %?", doc);
let doc = (desc_to_brief_pass::mk_pass().f)(srv.clone(), doc);
debug!("doc (desc_to_brief): %?", doc);
let doc = (unindent_pass::mk_pass().f)(srv.clone(), doc);
debug!("doc (unindent): %?", doc);
let doc = (sectionalize_pass::mk_pass().f)(srv.clone(), doc);
debug!("doc (trim): %?", doc);
let doc = (trim_pass::mk_pass().f)(srv.clone(), doc);
debug!("doc (sectionalize): %?", doc);
let doc = (markdown_index_pass::mk_pass(config).f)(
srv.clone(), doc);
debug!("doc (index): %?", doc);
(srv.clone(), doc)
}
}
fn create_doc(source: ~str) -> doc::Doc {
let (_, doc) = create_doc_srv(source);
doc
}
fn write_markdown_str(
doc: doc::Doc
) -> ~str {
let (writer_factory, po) = markdown_writer::future_writer_factory();
write_markdown(doc, writer_factory);
return po.recv().second();
}
fn write_markdown_str_srv(
srv: astsrv::Srv,
doc: doc::Doc
) -> ~str {
let (writer_factory, po) = markdown_writer::future_writer_factory();
let pass = mk_pass(writer_factory);
(pass.f)(srv, doc);
return po.recv().second();
}
#[test]
fn write_markdown_should_write_mod_headers() {
let markdown = render(~"mod moo { }");
assert!(markdown.contains("# Module `moo`"));
}
#[test]
fn should_leave_blank_line_after_header() {
let markdown = render(~"mod morp { }");
assert!(markdown.contains("Module `morp`\n\n"));
}
#[test]
fn should_write_modules_last() {
/*
Because the markdown pass writes all modules at the same level of
indentation (it doesn't 'nest' them), we need to make sure that we
write all of the modules contained in each module after all other
types of items, or else the header nesting will end up wrong, with
modules appearing to contain items that they do not.
*/
let markdown = render(
~"mod a { }\
fn b() { }\
mod c {
}\
fn d() { }"
);
let idx_a = markdown.find_str("# Module `a`").unwrap();
let idx_b = markdown.find_str("## Function `b`").unwrap();
let idx_c = markdown.find_str("# Module `c`").unwrap();
let idx_d = markdown.find_str("## Function `d`").unwrap();
assert!(idx_b < idx_d);
assert!(idx_d < idx_a);
assert!(idx_a < idx_c);
}
#[test]
fn should_request_new_writer_for_each_page() {
// This port will send us a (page, str) pair for every writer
// that was created
let (writer_factory, po) = markdown_writer::future_writer_factory();
let (srv, doc) = create_doc_srv(~"mod a { }");
// Split the document up into pages
let doc = (page_pass::mk_pass(config::DocPerMod).f)(srv, doc);
write_markdown(doc, writer_factory);
// We expect two pages to have been written
for _ in range(0, 2u) {
po.recv();
}
}
#[test]
fn should_write_title_for_each_page() {
let (writer_factory, po) = markdown_writer::future_writer_factory();
let (srv, doc) = create_doc_srv(
~"#[link(name = \"core\")]; mod a { }");
let doc = (page_pass::mk_pass(config::DocPerMod).f)(srv, doc);
write_markdown(doc, writer_factory);
for _ in range(0, 2u) {
let (page, markdown) = po.recv();
match page {
doc::CratePage(_) => {
assert!(markdown.contains("% Crate core"));
}
doc::ItemPage(_) => {
assert!(markdown.contains("% Module a"));
}
}
}
}
#[test]
fn should_write_full_path_to_mod() {
let markdown = render(~"mod a { mod b { mod c { } } }");
assert!(markdown.contains("# Module `a::b::c`"));
}
#[test]
fn should_write_sections() {
let markdown = render(
~"#[doc = \"\
# Header\n\
Body\"]\
mod a {
}");
assert!(markdown.contains("#### Header\n\nBody\n\n"));
}
#[test]
fn should_write_crate_description() {
let markdown = render(~"#[doc = \"this is the crate\"];");
assert!(markdown.contains("this is the crate"));
}
#[test]
fn should_write_index() {
let markdown = render(~"mod a { } mod b { }");
assert!(markdown.contains(
"\n\n* [Module `a`](#module-a)\n\
* [Module `b`](#module-b)\n\n"
));
}
#[test]
fn should_write_index_brief() {
let markdown = render(~"#[doc = \"test\"] mod a { }");
assert!(markdown.contains("(#module-a) - test\n"));
}
#[test]
fn should_not_write_index_if_no_entries() {
let markdown = render(~"");
assert!(!markdown.contains("\n\n\n"));
}
#[test]
fn should_write_index_for_foreign_mods() {
let markdown = render(~"extern { fn a(); }");
assert!(markdown.contains(
"\n\n* [Function `a`](#function-a)\n\n"
));
}
#[test]
fn should_write_foreign_fns() {
let markdown = render(
~"extern { #[doc = \"test\"] fn a(); }");
assert!(markdown.contains("test"));
}
#[test]
fn should_write_foreign_fn_headers() {
let markdown = render(
~"extern { #[doc = \"test\"] fn a(); }");
assert!(markdown.contains("## Function `a`"));
}
#[test]
fn write_markdown_should_write_function_header() {
let markdown = render(~"fn func() { }");
assert!(markdown.contains("## Function `func`"));
}
#[test]
fn should_write_the_function_signature() {
let markdown = render(~"#[doc = \"f\"] fn a() { }");
assert!(markdown.contains("\n~~~ {.rust}\nfn a()\n"));
}
#[test]
fn should_insert_blank_line_after_fn_signature() {
let markdown = render(~"#[doc = \"f\"] fn a() { }");
assert!(markdown.contains("fn a()\n~~~\n\n"));
}
#[test]
fn should_correctly_bracket_fn_signature() {
let doc = create_doc(~"fn a() { }");
let doc = doc::Doc{
pages: ~[
doc::CratePage(doc::CrateDoc{
topmod: doc::ModDoc{
items: ~[doc::FnTag(doc::SimpleItemDoc{
sig: Some(~"line 1\nline 2"),
.. (doc.cratemod().fns()[0]).clone()
})],
.. doc.cratemod()
},
.. doc.CrateDoc()
})
]
};
let markdown = write_markdown_str(doc);
assert!(markdown.contains("~~~ {.rust}\nline 1\nline 2\n~~~"));
}
#[test]
fn should_leave_blank_line_between_fn_header_and_sig() {
let markdown = render(~"fn a() { }");
assert!(markdown.contains("Function `a`\n\n~~~ {.rust}\nfn a()"));
}
#[test]
fn should_write_static_header() {
let markdown = render(~"static a: bool = true;");
assert!(markdown.contains("## Static `a`\n\n"));
}
#[test]
fn should_write_static_description() {
let markdown = render(
~"#[doc = \"b\"]\
static a: bool = true;");
assert!(markdown.contains("\n\nb\n\n"));
}
#[test]
fn should_write_enum_header() {
let markdown = render(~"enum a { b }");
assert!(markdown.contains("## Enum `a`\n\n"));
}
#[test]
fn should_write_enum_description() {
let markdown = render(~"#[doc = \"b\"] enum a { b }");
assert!(markdown.contains("\n\nb\n\n"));
}
#[test]
fn should_write_variant_list() {
let markdown = render(
~"enum a { \
#[doc = \"test\"] b, \
#[doc = \"test\"] c }");
assert!(markdown.contains(
"\n\n#### Variants\n\
\n\
\n* `b` - test\
\n\
\n* `c` - test\n\n"));
}
#[test]
fn should_write_variant_list_without_descs() {
let markdown = render(~"enum a { b, c }");
assert!(markdown.contains(
"\n\n#### Variants\n\
\n\
\n* `b`\
\n\
\n* `c`\n\n"));
}
#[test]
fn should_write_variant_list_with_indent() {
let markdown = render(
~"enum a { #[doc = \"line 1\\n\\nline 2\"] b, c }");
assert!(markdown.contains(
"\n\n#### Variants\n\
\n\
\n* `b` - line 1\
\n \
\n line 2\
\n\
\n* `c`\n\n"));
}
#[test]
fn should_write_variant_list_with_signatures() {
let markdown = render(~"enum a { b(int), #[doc = \"a\"] c(int) }");
assert!(markdown.contains(
"\n\n#### Variants\n\
\n\
\n* `b(int)`\
\n\
\n* `c(int)` - a\n\n"));
}
#[test]
fn should_write_trait_header() {
let markdown = render(~"trait i { fn a(); }");
assert!(markdown.contains("## Trait `i`"));
}
#[test]
fn should_write_trait_desc() {
let markdown = render(~"#[doc = \"desc\"] trait i { fn a(); }");
assert!(markdown.contains("desc"));
}
#[test]
fn should_write_trait_method_header() {
let markdown = render(~"trait i { fn a(); }");
assert!(markdown.contains("### Method `a`"));
}
#[test]
fn should_write_trait_method_signature() {
let markdown = render(~"trait i { fn a(&self); }");
assert!(markdown.contains("\n~~~ {.rust}\nfn a(&self)"));
}
#[test]
fn should_write_impl_header() {
let markdown = render(~"impl int { fn a() { } }");
assert!(markdown.contains("## Implementation for `int`"));
}
#[test]
fn should_write_impl_header_with_bounds() {
let markdown = render(~"impl <T> int<T> { }");
assert!(markdown.contains("## Implementation for `int<T>` where `<T>`"));
}
#[test]
fn should_write_impl_header_with_trait() {
let markdown = render(~"impl j for int { fn a() { } }");
assert!(markdown.contains(
"## Implementation of `j` for `int`"));
}
#[test]
fn should_write_impl_desc() {
let markdown = render(
~"#[doc = \"desc\"] impl int { fn a() { } }");
assert!(markdown.contains("desc"));
}
#[test]
fn should_write_impl_method_header() {
let markdown = render(
~"impl int { fn a() { } }");
assert!(markdown.contains("### Method `a`"));
}
#[test]
fn should_write_impl_method_signature() {
let markdown = render(
~"impl int { fn a(&mut self) { } }");
assert!(markdown.contains("~~~ {.rust}\nfn a(&mut self)"));
}
#[test]
fn should_write_type_header() {
let markdown = render(~"type t = int;");
assert!(markdown.contains("## Type `t`"));
}
#[test]
fn should_write_type_desc() {
let markdown = render(
~"#[doc = \"desc\"] type t = int;");
assert!(markdown.contains("\n\ndesc\n\n"));
}
#[test]
fn should_write_type_signature() {
let markdown = render(~"type t = int;");
assert!(markdown.contains("\n\n~~~ {.rust}\ntype t = int\n~~~\n"));
}
#[test]
fn should_put_struct_header() {
let markdown = render(~"struct S { field: () }");
assert!(markdown.contains("## Struct `S`\n\n"));
}
}

View File

@ -1,287 +0,0 @@
// Copyright 2012-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 config;
use doc::ItemUtils;
use doc;
use std::comm::*;
use std::comm;
use std::io;
use std::result;
use std::run;
use std::str;
use std::task;
use extra::future::Future;
#[deriving(Clone)]
pub enum WriteInstr {
Write(~str),
Done
}
pub type Writer = ~fn(v: WriteInstr);
pub type WriterFactory = ~fn(page: doc::Page) -> Writer;
pub trait WriterUtils {
fn put_str(&self, str: ~str);
fn put_line(&self, str: ~str);
fn put_done(&self);
}
impl WriterUtils for Writer {
fn put_str(&self, str: ~str) {
(*self)(Write(str));
}
fn put_line(&self, str: ~str) {
self.put_str(str + "\n");
}
fn put_done(&self) {
(*self)(Done)
}
}
pub fn make_writer_factory(config: config::Config) -> WriterFactory {
match config.output_format {
config::Markdown => {
markdown_writer_factory(config)
}
config::PandocHtml => {
pandoc_writer_factory(config)
}
}
}
fn markdown_writer_factory(config: config::Config) -> WriterFactory {
let result: ~fn(page: doc::Page) -> Writer = |page| {
markdown_writer(&config, page)
};
result
}
fn pandoc_writer_factory(config: config::Config) -> WriterFactory {
let result: ~fn(doc::Page) -> Writer = |page| {
pandoc_writer(&config, page)
};
result
}
fn markdown_writer(
config: &config::Config,
page: doc::Page
) -> Writer {
let filename = make_local_filename(config, page);
do generic_writer |markdown| {
write_file(&filename, markdown);
}
}
fn pandoc_writer(
config: &config::Config,
page: doc::Page
) -> Writer {
assert!(config.pandoc_cmd.is_some());
let pandoc_cmd = (*config.pandoc_cmd.get_ref()).clone();
let filename = make_local_filename(config, page);
let pandoc_args = ~[
~"--standalone",
~"--section-divs",
~"--from=markdown",
~"--to=html",
~"--css=rust.css",
~"--output=" + filename.to_str()
];
do generic_writer |markdown| {
use std::io::WriterUtil;
debug!("pandoc cmd: %s", pandoc_cmd);
debug!("pandoc args: %s", pandoc_args.connect(" "));
let mut proc = run::Process::new(pandoc_cmd, pandoc_args, run::ProcessOptions::new());
proc.input().write_str(markdown);
let output = proc.finish_with_output();
debug!("pandoc result: %i", output.status);
if output.status != 0 {
error!("pandoc-out: %s", str::from_utf8(output.output));
error!("pandoc-err: %s", str::from_utf8(output.error));
fail!("pandoc failed");
}
}
}
fn generic_writer(process: ~fn(markdown: ~str)) -> Writer {
let (po, ch) = stream::<WriteInstr>();
do task::spawn || {
let mut markdown = ~"";
let mut keep_going = true;
while keep_going {
match po.recv() {
Write(s) => markdown.push_str(s),
Done => keep_going = false
}
}
process(markdown);
};
let result: ~fn(instr: WriteInstr) = |instr| ch.send(instr);
result
}
pub fn make_local_filename(
config: &config::Config,
page: doc::Page
) -> Path {
let filename = make_filename(config, page);
config.output_dir.push_rel(&filename)
}
pub fn make_filename(
config: &config::Config,
page: doc::Page
) -> Path {
let filename = {
match page {
doc::CratePage(doc) => {
if config.output_format == config::PandocHtml &&
config.output_style == config::DocPerMod {
~"index"
} else {
assert!(doc.topmod.name_() != ~"");
doc.topmod.name_()
}
}
doc::ItemPage(doc) => {
(doc.path() + &[doc.name_()]).connect("_")
}
}
};
let ext = match config.output_format {
config::Markdown => ~"md",
config::PandocHtml => ~"html"
};
Path(filename).with_filetype(ext)
}
fn write_file(path: &Path, s: ~str) {
use std::io::WriterUtil;
match io::file_writer(path, [io::Create, io::Truncate]) {
result::Ok(writer) => {
writer.write_str(s);
}
result::Err(e) => fail!(e)
}
}
pub fn future_writer_factory(
) -> (WriterFactory, Port<(doc::Page, ~str)>) {
let (markdown_po, markdown_ch) = stream();
let markdown_ch = SharedChan::new(markdown_ch);
let writer_factory: WriterFactory = |page| {
let (writer_po, writer_ch) = comm::stream();
let markdown_ch = markdown_ch.clone();
do task::spawn || {
let (writer, future) = future_writer();
let mut future = future;
writer_ch.send(writer);
let s = future.get();
markdown_ch.send((page.clone(), s));
}
writer_po.recv()
};
(writer_factory, markdown_po)
}
fn future_writer() -> (Writer, Future<~str>) {
let (port, chan) = comm::stream();
let writer: ~fn(instr: WriteInstr) = |instr| chan.send(instr.clone());
let future = do Future::from_fn || {
let mut res = ~"";
loop {
match port.recv() {
Write(s) => res.push_str(s),
Done => break
}
}
res
};
(writer, future)
}
#[cfg(test)]
mod test {
use astsrv;
use doc;
use extract;
use path_pass;
use config;
use super::make_local_filename;
fn mk_doc(name: ~str, source: ~str) -> doc::Doc {
do astsrv::from_str(source) |srv| {
let doc = extract::from_srv(srv.clone(), name.clone());
let doc = (path_pass::mk_pass().f)(srv.clone(), doc);
doc
}
}
#[test]
fn should_use_markdown_file_name_based_off_crate() {
let config = config::Config {
output_dir: Path("output/dir"),
output_format: config::Markdown,
output_style: config::DocPerCrate,
.. config::default_config(&Path("input/test.rc"))
};
let doc = mk_doc(~"test", ~"");
let page = doc::CratePage(doc.CrateDoc());
let filename = make_local_filename(&config, page);
assert_eq!(filename.to_str(), ~"output/dir/test.md");
}
#[test]
fn should_name_html_crate_file_name_index_html_when_doc_per_mod() {
let config = config::Config {
output_dir: Path("output/dir"),
output_format: config::PandocHtml,
output_style: config::DocPerMod,
.. config::default_config(&Path("input/test.rc"))
};
let doc = mk_doc(~"", ~"");
let page = doc::CratePage(doc.CrateDoc());
let filename = make_local_filename(&config, page);
assert_eq!(filename.to_str(), ~"output/dir/index.html");
}
#[test]
fn should_name_mod_file_names_by_path() {
let config = config::Config {
output_dir: Path("output/dir"),
output_format: config::PandocHtml,
output_style: config::DocPerMod,
.. config::default_config(&Path("input/test.rc"))
};
let doc = mk_doc(~"", ~"mod a { mod b { } }");
// hidden __std_macros module at the start.
let modb = doc.cratemod().mods()[1].mods()[0].clone();
let page = doc::ItemPage(doc::ModTag(modb));
let filename = make_local_filename(&config, page);
assert_eq!(filename, Path("output/dir/a_b.html"));
}
}

View File

@ -1,185 +0,0 @@
// Copyright 2012 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.
/*!
Divides the document tree into pages.
Each page corresponds is a logical section. There may be pages for
individual modules, pages for the crate, indexes, etc.
*/
use astsrv;
use config;
use doc::ItemUtils;
use doc;
use fold::Fold;
use fold;
use pass::Pass;
use std::comm::*;
use std::task;
use syntax::ast;
#[cfg(test)] use doc::PageUtils;
pub fn mk_pass(output_style: config::OutputStyle) -> Pass {
Pass {
name: ~"page",
f: |srv, doc| run(srv, doc, output_style)
}
}
pub fn run(
_srv: astsrv::Srv,
doc: doc::Doc,
output_style: config::OutputStyle
) -> doc::Doc {
if output_style == config::DocPerCrate {
return doc;
}
let (result_port, result_chan) = stream();
let (page_port, page_chan) = stream();
let page_chan = SharedChan::new(page_chan);
do task::spawn {
result_chan.send(make_doc_from_pages(&page_port));
};
find_pages(doc, page_chan);
result_port.recv()
}
type PagePort = Port<Option<doc::Page>>;
type PageChan = SharedChan<Option<doc::Page>>;
fn make_doc_from_pages(page_port: &PagePort) -> doc::Doc {
let mut pages = ~[];
loop {
let val = page_port.recv();
if val.is_some() {
pages.push(val.unwrap());
} else {
break;
}
}
doc::Doc {
pages: pages
}
}
fn find_pages(doc: doc::Doc, page_chan: PageChan) {
let fold = Fold {
ctxt: page_chan.clone(),
fold_crate: fold_crate,
fold_mod: fold_mod,
fold_nmod: fold_nmod,
.. fold::default_any_fold(page_chan.clone())
};
(fold.fold_doc)(&fold, doc.clone());
page_chan.send(None);
}
fn fold_crate(fold: &fold::Fold<PageChan>, doc: doc::CrateDoc)
-> doc::CrateDoc {
let doc = fold::default_seq_fold_crate(fold, doc);
let page = doc::CratePage(doc::CrateDoc {
topmod: strip_mod(doc.topmod.clone()),
.. doc.clone()
});
fold.ctxt.send(Some(page));
doc
}
fn fold_mod(fold: &fold::Fold<PageChan>, doc: doc::ModDoc) -> doc::ModDoc {
let doc = fold::default_any_fold_mod(fold, doc);
if doc.id() != ast::CRATE_NODE_ID {
let doc = strip_mod(doc.clone());
let page = doc::ItemPage(doc::ModTag(doc));
fold.ctxt.send(Some(page));
}
doc
}
fn strip_mod(doc: doc::ModDoc) -> doc::ModDoc {
doc::ModDoc {
items: do doc.items.iter().filter |item| {
match **item {
doc::ModTag(_) | doc::NmodTag(_) => false,
_ => true
}
}.map(|x| (*x).clone()).collect::<~[doc::ItemTag]>(),
.. doc.clone()
}
}
fn fold_nmod(fold: &fold::Fold<PageChan>, doc: doc::NmodDoc) -> doc::NmodDoc {
let doc = fold::default_seq_fold_nmod(fold, doc);
let page = doc::ItemPage(doc::NmodTag(doc.clone()));
fold.ctxt.send(Some(page));
return doc;
}
#[cfg(test)]
mod test {
use astsrv;
use config;
use attr_pass;
use doc;
use extract;
use prune_hidden_pass;
use page_pass::run;
fn mk_doc_(
output_style: config::OutputStyle,
source: ~str
) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (attr_pass::mk_pass().f)(srv.clone(), doc);
let doc = (prune_hidden_pass::mk_pass().f)(srv.clone(), doc);
run(srv.clone(), doc, output_style)
}
}
fn mk_doc(source: ~str) -> doc::Doc {
mk_doc_(config::DocPerMod, source.clone())
}
#[test]
fn should_not_split_the_doc_into_pages_for_doc_per_crate() {
let doc = mk_doc_(
config::DocPerCrate,
~"mod a { } mod b { mod c { } }"
);
assert_eq!(doc.pages.len(), 1u);
}
#[test]
fn should_make_a_page_for_every_mod() {
let doc = mk_doc(~"mod a { }");
// hidden __std_macros module at the start.
assert_eq!(doc.pages.mods()[0].name_(), ~"a");
}
#[test]
fn should_remove_mods_from_containing_mods() {
let doc = mk_doc(~"mod a { }");
assert!(doc.cratemod().mods().is_empty());
}
}

View File

@ -1,41 +0,0 @@
// Copyright 2012 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.
//! AST-parsing helpers
use rustc::driver::driver;
use rustc::driver::session;
use syntax::ast;
use syntax::parse;
pub fn from_file(file: &Path) -> @ast::Crate {
parse::parse_crate_from_file(
file, ~[], parse::new_parse_sess(None))
}
pub fn from_str(source: @str) -> @ast::Crate {
parse::parse_crate_from_source_str(
@"-", source, ~[], parse::new_parse_sess(None))
}
pub fn from_file_sess(sess: session::Session, file: &Path) -> @ast::Crate {
parse::parse_crate_from_file(
file, cfg(sess), sess.parse_sess)
}
pub fn from_str_sess(sess: session::Session, source: @str) -> @ast::Crate {
parse::parse_crate_from_source_str(
@"-", source, cfg(sess), sess.parse_sess)
}
fn cfg(sess: session::Session) -> ast::CrateConfig {
driver::build_configuration(sess)
}

View File

@ -1,96 +0,0 @@
// Copyright 2012 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 astsrv;
use doc;
use time;
#[cfg(test)] use extract;
/// A single operation on the document model
pub struct Pass {
name: ~str,
f: @fn(srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc
}
pub fn run_passes(
srv: astsrv::Srv,
doc: doc::Doc,
passes: ~[Pass]
) -> doc::Doc {
let mut passno = 0;
do passes.iter().fold(doc) |doc, pass| {
debug!("pass #%d", passno);
passno += 1;
do time(pass.name.clone()) {
(pass.f)(srv.clone(), doc.clone())
}
}
}
#[test]
fn test_run_passes() {
fn pass1(
_srv: astsrv::Srv,
doc: doc::Doc
) -> doc::Doc {
doc::Doc{
pages: ~[
doc::CratePage(doc::CrateDoc{
topmod: doc::ModDoc{
item: doc::ItemDoc {
name: doc.cratemod().name_() + "two",
.. doc.cratemod().item.clone()
},
items: ~[],
index: None
}
})
]
}
}
fn pass2(
_srv: astsrv::Srv,
doc: doc::Doc
) -> doc::Doc {
doc::Doc{
pages: ~[
doc::CratePage(doc::CrateDoc{
topmod: doc::ModDoc{
item: doc::ItemDoc {
name: doc.cratemod().name_() + "three",
.. doc.cratemod().item.clone()
},
items: ~[],
index: None
}
})
]
}
}
let source = ~"";
do astsrv::from_str(source) |srv| {
let passes = ~[
Pass {
name: ~"",
f: pass1
},
Pass {
name: ~"",
f: pass2
}
];
let doc = extract::from_srv(srv.clone(), ~"one");
let doc = run_passes(srv, doc, passes);
assert_eq!(doc.cratemod().name_(), ~"onetwothree");
}
}

View File

@ -1,115 +0,0 @@
// Copyright 2012 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.
//! Records the full path to items
use astsrv;
use doc::ItemUtils;
use doc;
use fold::Fold;
use fold;
use pass::Pass;
#[cfg(test)] use extract;
use syntax::ast;
pub fn mk_pass() -> Pass {
Pass {
name: ~"path",
f: run
}
}
struct Ctxt {
srv: astsrv::Srv,
path: @mut ~[~str]
}
impl Clone for Ctxt {
fn clone(&self) -> Ctxt {
Ctxt {
srv: self.srv.clone(),
path: @mut (*self.path).clone()
}
}
}
fn run(srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc {
let ctxt = Ctxt {
srv: srv,
path: @mut ~[]
};
let fold = Fold {
ctxt: ctxt.clone(),
fold_item: fold_item,
fold_mod: fold_mod,
fold_nmod: fold_nmod,
.. fold::default_any_fold(ctxt)
};
(fold.fold_doc)(&fold, doc)
}
fn fold_item(fold: &fold::Fold<Ctxt>, doc: doc::ItemDoc) -> doc::ItemDoc {
doc::ItemDoc {
path: (*fold.ctxt.path).clone(),
.. doc
}
}
fn fold_mod(fold: &fold::Fold<Ctxt>, doc: doc::ModDoc) -> doc::ModDoc {
let is_topmod = doc.id() == ast::CRATE_NODE_ID;
if !is_topmod { fold.ctxt.path.push(doc.name_()); }
let doc = fold::default_any_fold_mod(fold, doc);
if !is_topmod { fold.ctxt.path.pop(); }
doc::ModDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
fn fold_nmod(fold: &fold::Fold<Ctxt>, doc: doc::NmodDoc) -> doc::NmodDoc {
fold.ctxt.path.push(doc.name_());
let doc = fold::default_seq_fold_nmod(fold, doc);
fold.ctxt.path.pop();
doc::NmodDoc {
item: (fold.fold_item)(fold, doc.item.clone()),
.. doc
}
}
#[test]
fn should_record_mod_paths() {
let source = ~"mod a { mod b { mod c { } } mod d { mod e { } } }";
do astsrv::from_str(source) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = run(srv.clone(), doc);
// hidden __std_macros module at the start.
assert_eq!(doc.cratemod().mods()[1].mods()[0].mods()[0].path(),
~[~"a", ~"b"]);
assert_eq!(doc.cratemod().mods()[1].mods()[1].mods()[0].path(),
~[~"a", ~"d"]);
}
}
#[test]
fn should_record_fn_paths() {
let source = ~"mod a { fn b() { } }";
do astsrv::from_str(source) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = run(srv.clone(), doc);
// hidden __std_macros module at the start.
assert_eq!(doc.cratemod().mods()[1].fns()[0].path(), ~[~"a"]);
}
}

View File

@ -1,83 +0,0 @@
// Copyright 2012 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.
//! Prunes things with the #[doc(hidden)] attribute
use astsrv;
use attr_parser;
use doc::ItemUtils;
use doc;
use fold::Fold;
use fold;
use pass::Pass;
pub fn mk_pass() -> Pass {
Pass {
name: ~"prune_hidden",
f: run
}
}
pub fn run(srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc {
let fold = Fold {
ctxt: srv.clone(),
fold_mod: fold_mod,
.. fold::default_any_fold(srv)
};
(fold.fold_doc)(&fold, doc)
}
fn fold_mod(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::ModDoc
) -> doc::ModDoc {
let doc = fold::default_any_fold_mod(fold, doc);
doc::ModDoc {
items: do doc.items.iter().filter |item_tag| {
!is_hidden(fold.ctxt.clone(), item_tag.item())
}.map(|x| (*x).clone()).collect(),
.. doc
}
}
fn is_hidden(srv: astsrv::Srv, doc: doc::ItemDoc) -> bool {
use syntax::ast_map;
let id = doc.id;
do astsrv::exec(srv) |ctxt| {
let attrs = match ctxt.ast_map.get_copy(&id) {
ast_map::node_item(item, _) => item.attrs.clone(),
_ => ~[]
};
attr_parser::parse_hidden(attrs)
}
}
#[cfg(test)]
mod test {
use astsrv;
use doc;
use extract;
use prune_hidden_pass::run;
fn mk_doc(source: ~str) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
run(srv.clone(), doc)
}
}
#[test]
fn should_prune_hidden_items() {
let doc = mk_doc(~"#[doc(hidden)] mod a { }");
assert!(doc.cratemod().mods().is_empty())
}
}

View File

@ -1,255 +0,0 @@
// Copyright 2012 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.
//! Prune things that are private
use extract;
use syntax::ast;
use syntax::ast_map;
use astsrv;
use doc;
use fold::Fold;
use fold;
use pass::Pass;
pub fn mk_pass() -> Pass {
Pass {
name: ~"prune_private",
f: run
}
}
pub fn run(srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc {
// First strip private methods out of impls
let fold = Fold {
ctxt: srv.clone(),
fold_impl: fold_impl,
.. fold::default_any_fold(srv.clone())
};
let doc = (fold.fold_doc)(&fold, doc);
// Then strip private items and empty impls
let fold = Fold {
ctxt: srv.clone(),
fold_mod: fold_mod,
.. fold::default_any_fold(srv)
};
let doc = (fold.fold_doc)(&fold, doc);
return doc;
}
fn fold_impl(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::ImplDoc
) -> doc::ImplDoc {
let doc = fold::default_seq_fold_impl(fold, doc);
do astsrv::exec(fold.ctxt.clone()) |ctxt| {
match ctxt.ast_map.get_copy(&doc.item.id) {
ast_map::node_item(item, _) => {
match item.node {
ast::item_impl(_, None, _, ref methods) => {
// Associated impls have complex rules for method visibility
strip_priv_methods(doc.clone(), *methods, item.vis)
}
ast::item_impl(_, Some(_), _ ,_) => {
// Trait impls don't
doc.clone()
}
_ => fail!()
}
}
_ => fail!()
}
}
}
fn strip_priv_methods(
doc: doc::ImplDoc,
methods: &[@ast::method],
item_vis: ast::visibility
) -> doc::ImplDoc {
let methods = do doc.methods.iter().filter |method| {
let ast_method = do methods.iter().find |m| {
extract::to_str(m.ident) == method.name
};
assert!(ast_method.is_some());
let ast_method = ast_method.unwrap();
match ast_method.vis {
ast::public => true,
ast::private => false,
ast::inherited => item_vis == ast::public
}
}.map(|x| (*x).clone()).collect();
doc::ImplDoc {
methods: methods,
.. doc
}
}
fn fold_mod(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::ModDoc
) -> doc::ModDoc {
let doc = fold::default_any_fold_mod(fold, doc);
doc::ModDoc {
items: doc.items.iter().filter(|item_tag| {
match item_tag {
& &doc::ImplTag(ref doc) => {
if doc.trait_types.is_empty() {
// This is an associated impl. We have already pruned the
// non-visible methods. If there are any left then
// retain the impl, otherwise throw it away
!doc.methods.is_empty()
} else {
// This is a trait implementation, make it visible
// NB: This is not quite right since this could be an impl
// of a private trait. We can't know that without running
// resolve though.
true
}
}
_ => {
is_visible(fold.ctxt.clone(), item_tag.item())
}
}
}).map(|x| (*x).clone()).collect(),
.. doc
}
}
fn is_visible(srv: astsrv::Srv, doc: doc::ItemDoc) -> bool {
let id = doc.id;
do astsrv::exec(srv) |ctxt| {
match ctxt.ast_map.get_copy(&id) {
ast_map::node_item(item, _) => {
match &item.node {
&ast::item_impl(*) => {
// Impls handled elsewhere
fail!()
}
_ => {
// Otherwise just look at the visibility
item.vis == ast::public
}
}
}
_ => unreachable!()
}
}
}
#[cfg(test)]
mod test {
use astsrv;
use doc;
use extract;
use tystr_pass;
use prune_private_pass::run;
fn mk_doc(source: ~str) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = tystr_pass::run(srv.clone(), doc);
run(srv.clone(), doc)
}
}
#[test]
fn should_prune_items_without_pub_modifier() {
let doc = mk_doc(~"mod a { }");
assert!(doc.cratemod().mods().is_empty());
}
#[test]
fn should_not_prune_trait_impls() {
// Impls are more complicated
let doc = mk_doc(
~" \
trait Foo { } \
impl Foo for int { } \
");
assert!(!doc.cratemod().impls().is_empty());
}
#[test]
fn should_prune_associated_methods_without_vis_modifier_on_impls_without_vis_modifier() {
let doc = mk_doc(
~"impl Foo {\
pub fn bar() { }\
fn baz() { }\
}");
assert_eq!(doc.cratemod().impls()[0].methods.len(), 1);
}
#[test]
fn should_prune_priv_associated_methods_on_impls_without_vis_modifier() {
let doc = mk_doc(
~"impl Foo {\
pub fn bar() { }\
fn baz() { }\
}");
assert_eq!(doc.cratemod().impls()[0].methods.len(), 1);
}
#[test]
fn should_prune_priv_associated_methods_on_pub_impls() {
let doc = mk_doc(
~"impl Foo {\
pub fn bar() { }\
fn baz() { }\
}");
assert_eq!(doc.cratemod().impls()[0].methods.len(), 1);
}
#[test]
fn should_prune_associated_methods_without_vis_modifier_on_priv_impls() {
let doc = mk_doc(
~"impl Foo {\
pub fn bar() { }\
fn baz() { }\
}");
assert_eq!(doc.cratemod().impls()[0].methods.len(), 1);
}
#[test]
fn should_prune_priv_associated_methods_on_priv_impls() {
let doc = mk_doc(
~"impl Foo {\
pub fn bar() { }\
fn baz() { }\
}");
assert_eq!(doc.cratemod().impls()[0].methods.len(), 1);
}
#[test]
fn should_prune_associated_impls_with_no_pub_methods() {
let doc = mk_doc(
~"impl Foo {\
fn baz() { }\
}");
assert!(doc.cratemod().impls().is_empty());
}
#[test]
fn should_not_prune_associated_impls_with_pub_methods() {
let doc = mk_doc(
~" \
impl Foo { pub fn bar() { } } \
");
assert!(!doc.cratemod().impls().is_empty());
}
}

View File

@ -8,140 +8,209 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Rustdoc - The Rust documentation generator
#[link(name = "rustdoc",
vers = "0.8",
uuid = "f8abd014-b281-484d-a0c3-26e3de8e2412",
url = "https://github.com/mozilla/rust/tree/master/src/rustdoc")];
uuid = "8c6e4598-1596-4aa5-a24c-b811914bbbc6",
url = "https://github.com/mozilla/rust/tree/master/src/librustdoc")];
#[comment = "The Rust documentation generator"];
#[desc = "rustdoc, the Rust documentation extractor"];
#[license = "MIT/ASL2"];
#[crate_type = "lib"];
extern mod extra;
extern mod rustc;
extern mod syntax;
extern mod rustc;
extern mod extra;
use std::os;
use extra::serialize::Encodable;
use extra::time;
use extra::getopts::groups;
use std::cell::Cell;
use std::rt::io;
use std::rt::io::Writer;
use std::rt::io::file::FileInfo;
use config::Config;
use doc::Item;
use doc::ItemUtils;
pub mod pass;
pub mod config;
pub mod parse;
pub mod extract;
pub mod attr_parser;
pub mod doc;
pub mod markdown_index_pass;
pub mod markdown_pass;
pub mod markdown_writer;
pub mod clean;
pub mod core;
pub mod doctree;
pub mod fold;
pub mod path_pass;
pub mod attr_pass;
pub mod tystr_pass;
pub mod prune_hidden_pass;
pub mod desc_to_brief_pass;
pub mod text_pass;
pub mod unindent_pass;
pub mod trim_pass;
pub mod astsrv;
pub mod demo;
pub mod sort_pass;
pub mod sort_item_name_pass;
pub mod sort_item_type_pass;
pub mod page_pass;
pub mod sectionalize_pass;
pub mod escape_pass;
pub mod prune_private_pass;
pub mod html {
pub mod render;
pub mod layout;
pub mod markdown;
pub mod format;
}
pub mod passes;
pub mod plugins;
pub mod visit_ast;
pub static SCHEMA_VERSION: &'static str = "0.8.0";
local_data_key!(pub ctxtkey: @core::DocContext)
enum OutputFormat {
HTML, JSON
}
pub fn main() {
let args = os::args();
main_args(args);
main_args(std::os::args());
}
pub fn opts() -> ~[groups::OptGroup] {
use extra::getopts::groups::*;
~[
optmulti("L", "library-path", "directory to add to crate search path",
"DIR"),
optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
optmulti("", "passes", "space separated list of passes to also run",
"PASSES"),
optmulti("", "plugins", "space separated list of plugins to also load",
"PLUGINS"),
optflag("h", "help", "show this help message"),
optflag("", "nodefaults", "don't run the default passes"),
optopt("o", "output", "where to place the output", "PATH"),
]
}
pub fn usage(argv0: &str) {
println(groups::usage(format!("{} [options] [html|json] <crate>",
argv0), opts()));
}
pub fn main_args(args: &[~str]) {
if args.iter().any(|x| "-h" == *x) || args.iter().any(|x| "--help" == *x) {
config::usage();
//use extra::getopts::groups::*;
let matches = groups::getopts(args.tail(), opts()).unwrap();
if matches.opt_present("h") || matches.opt_present("help") {
usage(args[0]);
return;
}
let config = match config::parse_config(args) {
Ok(config) => config,
Err(err) => {
printfln!("error: %s", err);
return;
}
let (format, cratefile) = match matches.free.clone() {
[~"json", crate] => (JSON, crate),
[~"html", crate] => (HTML, crate),
[s, _] => {
println!("Unknown output format: `{}`", s);
usage(args[0]);
exit(1);
}
[_, .._] => {
println!("Expected exactly one crate to process");
usage(args[0]);
exit(1);
}
_ => {
println!("Expected an output format and then one crate");
usage(args[0]);
exit(1);
}
};
run(config);
}
// First, parse the crate and extract all relevant information.
let libs = Cell::new(matches.opt_strs("L").map(|s| Path(*s)));
let cr = Cell::new(Path(cratefile));
info2!("starting to run rustc");
let crate = do std::task::try {
let cr = cr.take();
core::run_core(libs.take(), &cr)
}.unwrap();
info2!("finished with rustc");
/// Runs rustdoc over the given file
fn run(config: Config) {
let source_file = config.input_crate.clone();
// Create an AST service from the source code
do astsrv::from_file(source_file.to_str()) |srv| {
// Just time how long it takes for the AST to become available
do time(~"wait_ast") {
do astsrv::exec(srv.clone()) |_ctxt| { }
};
// Extract the initial doc tree from the AST. This contains
// just names and node ids.
let doc = time(~"extract", || {
let default_name = source_file.clone();
extract::from_srv(srv.clone(), default_name.to_str())
});
// Refine and publish the document
pass::run_passes(srv, doc, ~[
// Generate type and signature strings
tystr_pass::mk_pass(),
// Record the full paths to various nodes
path_pass::mk_pass(),
// Extract the docs attributes and attach them to doc nodes
attr_pass::mk_pass(),
// Perform various text escaping
escape_pass::mk_pass(),
// Remove things marked doc(hidden)
prune_hidden_pass::mk_pass(),
// Remove things that are private
prune_private_pass::mk_pass(),
// Extract brief documentation from the full descriptions
desc_to_brief_pass::mk_pass(),
// Massage the text to remove extra indentation
unindent_pass::mk_pass(),
// Split text into multiple sections according to headers
sectionalize_pass::mk_pass(),
// Trim extra spaces from text
trim_pass::mk_pass(),
// Sort items by name
sort_item_name_pass::mk_pass(),
// Sort items again by kind
sort_item_type_pass::mk_pass(),
// Create indexes appropriate for markdown
markdown_index_pass::mk_pass(config.clone()),
// Break the document into pages if required by the
// output format
page_pass::mk_pass(config.output_style),
// Render
markdown_pass::mk_pass(
markdown_writer::make_writer_factory(config.clone())
)
]);
// Process all of the crate attributes, extracting plugin metadata along
// with the passes which we are supposed to run.
let mut default_passes = !matches.opt_present("nodefaults");
let mut passes = matches.opt_strs("passes");
let mut plugins = matches.opt_strs("plugins");
match crate.module.get_ref().doc_list() {
Some(nested) => {
for inner in nested.iter() {
match *inner {
clean::Word(~"no_default_passes") => {
default_passes = false;
}
clean::NameValue(~"passes", ref value) => {
for pass in value.word_iter() {
passes.push(pass.to_owned());
}
}
clean::NameValue(~"plugins", ref value) => {
for p in value.word_iter() {
plugins.push(p.to_owned());
}
}
_ => {}
}
}
}
None => {}
}
if default_passes {
passes.unshift(~"collapse-docs");
passes.unshift(~"unindent-comments");
}
// Load all plugins/passes into a PluginManager
let mut pm = plugins::PluginManager::new(Path("/tmp/rustdoc_ng/plugins"));
for pass in passes.iter() {
let plugin = match pass.as_slice() {
"strip-hidden" => passes::strip_hidden,
"unindent-comments" => passes::unindent_comments,
"collapse-docs" => passes::collapse_docs,
"collapse-privacy" => passes::collapse_privacy,
s => { error!("unknown pass %s, skipping", s); loop },
};
pm.add_plugin(plugin);
}
info2!("loading plugins...");
for pname in plugins.move_iter() {
pm.load_plugin(pname);
}
// Run everything!
info2!("Executing passes/plugins");
let (crate, res) = pm.run_plugins(crate);
info2!("going to format");
let started = time::precise_time_ns();
let output = matches.opt_str("o").map(|s| Path(*s));
match format {
HTML => { html::render::run(crate, output.unwrap_or(Path("doc"))) }
JSON => { jsonify(crate, res, output.unwrap_or(Path("doc.json"))) }
}
let ended = time::precise_time_ns();
info2!("Took {:.03f}s", (ended as f64 - started as f64) / 1000000000f64);
}
pub fn time<T>(what: ~str, f: &fn() -> T) -> T {
let start = extra::time::precise_time_s();
let rv = f();
let end = extra::time::precise_time_s();
info!("time: %3.3f s %s", end - start, what);
rv
fn jsonify(crate: clean::Crate, res: ~[plugins::PluginJson], dst: Path) {
// {
// "schema": version,
// "crate": { parsed crate ... },
// "plugins": { output of plugins ... }
// }
let mut json = ~extra::treemap::TreeMap::new();
json.insert(~"schema", extra::json::String(SCHEMA_VERSION.to_owned()));
let plugins_json = ~res.move_iter().filter_map(|opt| opt).collect();
// FIXME #8335: yuck, Rust -> str -> JSON round trip! No way to .encode
// straight to the Rust JSON representation.
let crate_json_str = do std::io::with_str_writer |w| {
crate.encode(&mut extra::json::Encoder(w));
};
let crate_json = match extra::json::from_str(crate_json_str) {
Ok(j) => j,
Err(_) => fail!("Rust generated JSON is invalid??")
};
json.insert(~"crate", crate_json);
json.insert(~"plugins", extra::json::Object(plugins_json));
let mut file = dst.open_writer(io::Create).unwrap();
let output = extra::json::Object(json).to_str();
file.write(output.as_bytes());
}
fn exit(status: int) -> ! {
#[fixed_stack_segment]; #[inline(never)];
use std::libc;
unsafe { libc::exit(status as libc::c_int) }
}

View File

@ -1,257 +0,0 @@
// Copyright 2012 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.
//! Breaks rustdocs into sections according to their headers
use astsrv;
use doc::ItemUtils;
use doc;
use fold::Fold;
use fold;
use pass::Pass;
pub fn mk_pass() -> Pass {
Pass {
name: ~"sectionalize",
f: run
}
}
pub fn run(_srv: astsrv::Srv, doc: doc::Doc) -> doc::Doc {
let fold = Fold {
fold_item: fold_item,
fold_trait: fold_trait,
fold_impl: fold_impl,
.. fold::default_any_fold(())
};
(fold.fold_doc)(&fold, doc)
}
fn fold_item(fold: &fold::Fold<()>, doc: doc::ItemDoc) -> doc::ItemDoc {
let doc = fold::default_seq_fold_item(fold, doc);
let (desc, sections) = sectionalize(doc.desc.clone());
doc::ItemDoc {
desc: desc,
sections: sections,
.. doc
}
}
fn fold_trait(fold: &fold::Fold<()>, doc: doc::TraitDoc) -> doc::TraitDoc {
let doc = fold::default_seq_fold_trait(fold, doc);
doc::TraitDoc {
methods: do doc.methods.map |method| {
let (desc, sections) = sectionalize(method.desc.clone());
doc::MethodDoc {
desc: desc,
sections: sections,
.. (*method).clone()
}
},
.. doc
}
}
fn fold_impl(fold: &fold::Fold<()>, doc: doc::ImplDoc) -> doc::ImplDoc {
let doc = fold::default_seq_fold_impl(fold, doc);
doc::ImplDoc {
methods: do doc.methods.map |method| {
let (desc, sections) = sectionalize(method.desc.clone());
doc::MethodDoc {
desc: desc,
sections: sections,
.. (*method).clone()
}
},
.. doc
}
}
fn sectionalize(desc: Option<~str>) -> (Option<~str>, ~[doc::Section]) {
/*!
* Take a description of the form
*
* General text
*
* # Section header
*
* Section text
*
* # Section header
*
* Section text
*
* and remove each header and accompanying text into section records.
*/
if desc.is_none() {
return (None, ~[]);
}
let mut new_desc = None::<~str>;
let mut current_section: Option<doc::Section> = None;
let mut sections = ~[];
for line in desc.get_ref().any_line_iter() {
match parse_header(line) {
Some(header) => {
if current_section.is_some() {
sections.push((*current_section.get_ref()).clone());
}
current_section = Some(doc::Section {
header: header.to_owned(),
body: ~""
});
}
None => {
match current_section.clone() {
Some(section) => {
current_section = Some(doc::Section {
body: fmt!("%s\n%s", section.body, line),
.. section
});
}
None => {
new_desc = match new_desc.clone() {
Some(desc) => {
Some(fmt!("%s\n%s", desc, line))
}
None => {
Some(line.to_owned())
}
};
}
}
}
}
}
if current_section.is_some() {
sections.push(current_section.unwrap());
}
(new_desc, sections)
}
fn parse_header<'a>(line: &'a str) -> Option<&'a str> {
if line.starts_with("# ") {
Some(line.slice_from(2))
} else {
None
}
}
#[cfg(test)]
mod test {
use astsrv;
use attr_pass;
use doc;
use extract;
use prune_hidden_pass;
use sectionalize_pass::run;
fn mk_doc(source: ~str) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (attr_pass::mk_pass().f)(srv.clone(), doc);
let doc = (prune_hidden_pass::mk_pass().f)(srv.clone(), doc);
run(srv.clone(), doc)
}
}
#[test]
fn should_create_section_headers() {
let doc = mk_doc(
~"#[doc = \"\
# Header\n\
Body\"]\
mod a {
}");
assert!(doc.cratemod().mods()[0].item.sections[0].header.contains("Header"));
}
#[test]
fn should_create_section_bodies() {
let doc = mk_doc(
~"#[doc = \"\
# Header\n\
Body\"]\
mod a {
}");
assert!(doc.cratemod().mods()[0].item.sections[0].body.contains("Body"));
}
#[test]
fn should_not_create_sections_from_indented_headers() {
let doc = mk_doc(
~"#[doc = \"\n\
Text\n # Header\n\
Body\"]\
mod a {
}");
assert!(doc.cratemod().mods()[0].item.sections.is_empty());
}
#[test]
fn should_remove_section_text_from_main_desc() {
let doc = mk_doc(
~"#[doc = \"\
Description\n\n\
# Header\n\
Body\"]\
mod a {
}");
assert!(!doc.cratemod().mods()[0].desc().unwrap().contains("Header"));
assert!(!doc.cratemod().mods()[0].desc().unwrap().contains("Body"));
}
#[test]
fn should_eliminate_desc_if_it_is_just_whitespace() {
let doc = mk_doc(
~"#[doc = \"\
# Header\n\
Body\"]\
mod a {
}");
assert_eq!(doc.cratemod().mods()[0].desc(), None);
}
#[test]
fn should_sectionalize_trait_methods() {
let doc = mk_doc(
~"trait i {
#[doc = \"\
# Header\n\
Body\"]\
fn a(); }");
assert_eq!(doc.cratemod().traits()[0].methods[0].sections.len(), 1u);
}
#[test]
fn should_sectionalize_impl_methods() {
let doc = mk_doc(
~"impl bool {
#[doc = \"\
# Header\n\
Body\"]\
fn a() { } }");
assert_eq!(doc.cratemod().impls()[0].methods[0].sections.len(), 1u);
}
}

View File

@ -1,38 +0,0 @@
// Copyright 2012 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.
//! Sorts items by name
use doc::ItemUtils;
use doc;
use pass::Pass;
use sort_pass;
pub fn mk_pass() -> Pass {
fn by_item_name(item1: &doc::ItemTag, item2: &doc::ItemTag) -> bool {
(*item1).name_() <= (*item2).name_()
}
sort_pass::mk_pass(~"sort_item_name", by_item_name)
}
#[test]
fn test() {
use astsrv;
use extract;
let source = ~"mod z { } fn y() { }";
do astsrv::from_str(source) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (mk_pass().f)(srv.clone(), doc);
// hidden __std_macros module at the start.
assert_eq!(doc.cratemod().items[1].name_(), ~"y");
assert_eq!(doc.cratemod().items[2].name_(), ~"z");
}
}

View File

@ -1,67 +0,0 @@
// Copyright 2012 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.
//! Sorts items by type
use doc;
use pass::Pass;
use sort_pass;
pub fn mk_pass() -> Pass {
fn by_score(item1: &doc::ItemTag, item2: &doc::ItemTag) -> bool {
fn score(item: &doc::ItemTag) -> int {
match *item {
doc::StaticTag(_) => 0,
doc::TyTag(_) => 1,
doc::EnumTag(_) => 2,
doc::StructTag(_) => 3,
doc::TraitTag(_) => 4,
doc::ImplTag(_) => 5,
doc::FnTag(_) => 6,
doc::ModTag(_) => 7,
doc::NmodTag(_) => 8
}
}
score(item1) <= score(item2)
}
sort_pass::mk_pass(~"sort_item_type", by_score)
}
#[test]
fn test() {
use astsrv;
use extract;
let source =
~"mod imod { } \
static istatic: int = 0; \
fn ifn() { } \
enum ienum { ivar } \
trait itrait { fn a(); } \
impl int { fn a() { } } \
type itype = int; \
struct istruct { f: () }";
do astsrv::from_str(source) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (mk_pass().f)(srv.clone(), doc);
// hidden __std_macros module at the start.
assert_eq!(doc.cratemod().items[0].name_(), ~"istatic");
assert_eq!(doc.cratemod().items[1].name_(), ~"itype");
assert_eq!(doc.cratemod().items[2].name_(), ~"ienum");
assert_eq!(doc.cratemod().items[3].name_(), ~"istruct");
assert_eq!(doc.cratemod().items[4].name_(), ~"itrait");
assert_eq!(doc.cratemod().items[5].name_(), ~"__extensions__");
assert_eq!(doc.cratemod().items[6].name_(), ~"ifn");
// hidden __std_macros module fits here.
assert_eq!(doc.cratemod().items[8].name_(), ~"imod");
}
}

View File

@ -1,103 +0,0 @@
// Copyright 2012 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.
//! A general sorting pass
use astsrv;
use doc;
use fold::Fold;
use fold;
use pass::Pass;
#[cfg(test)] use extract;
use extra::sort;
use std::clone::Clone;
pub type ItemLtEqOp = @fn(v1: &doc::ItemTag, v2: &doc::ItemTag) -> bool;
struct ItemLtEq {
op: ItemLtEqOp,
}
impl Clone for ItemLtEq {
fn clone(&self) -> ItemLtEq {
ItemLtEq {
op: self.op,
}
}
}
pub fn mk_pass(name: ~str, lteq: ItemLtEqOp) -> Pass {
Pass {
name: name.clone(),
f: |srv, doc| run(srv, doc, ItemLtEq { op: lteq })
}
}
fn run(
_srv: astsrv::Srv,
doc: doc::Doc,
lteq: ItemLtEq
) -> doc::Doc {
let fold = Fold {
fold_mod: fold_mod,
.. fold::default_any_fold(lteq)
};
(fold.fold_doc)(&fold, doc)
}
fn fold_mod(
fold: &fold::Fold<ItemLtEq>,
doc: doc::ModDoc
) -> doc::ModDoc {
let doc = fold::default_any_fold_mod(fold, doc);
doc::ModDoc {
items: sort::merge_sort(doc.items, fold.ctxt.op),
.. doc
}
}
#[test]
fn test() {
fn name_lteq(item1: &doc::ItemTag, item2: &doc::ItemTag) -> bool {
(*item1).name_() <= (*item2).name_()
}
let source = ~"mod z { mod y { } fn x() { } } mod w { }";
do astsrv::from_str(source) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (mk_pass(~"", name_lteq).f)(srv.clone(), doc);
// hidden __std_macros module at the start.
assert_eq!(doc.cratemod().mods()[1].name_(), ~"w");
assert_eq!(doc.cratemod().mods()[2].items[0].name_(), ~"x");
assert_eq!(doc.cratemod().mods()[2].items[1].name_(), ~"y");
assert_eq!(doc.cratemod().mods()[2].name_(), ~"z");
}
}
#[test]
fn should_be_stable() {
fn always_eq(_item1: &doc::ItemTag, _item2: &doc::ItemTag) -> bool {
true
}
let source = ~"mod a { mod b { } } mod c { mod d { } }";
do astsrv::from_str(source) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (mk_pass(~"", always_eq).f)(srv.clone(), doc);
// hidden __std_macros module at the start.
assert_eq!(doc.cratemod().mods()[1].items[0].name_(), ~"b");
assert_eq!(doc.cratemod().mods()[2].items[0].name_(), ~"d");
let doc = (mk_pass(~"", always_eq).f)(srv.clone(), doc);
assert_eq!(doc.cratemod().mods()[1].items[0].name_(), ~"b");
assert_eq!(doc.cratemod().mods()[2].items[0].name_(), ~"d");
}
}

View File

@ -1,313 +0,0 @@
// Copyright 2012 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.
//! Generic pass for performing an operation on all descriptions
use astsrv;
use doc::ItemUtils;
use doc;
use fold::Fold;
use fold;
use pass::Pass;
use std::cell::Cell;
pub fn mk_pass(name: ~str, op: @fn(&str) -> ~str) -> Pass {
let op = Cell::new(op);
Pass {
name: name.clone(),
f: |srv: astsrv::Srv, doc: doc::Doc| -> doc::Doc {
run(srv, doc, op.take())
}
}
}
type Op = @fn(&str) -> ~str;
struct WrappedOp {
op: Op,
}
impl Clone for WrappedOp {
fn clone(&self) -> WrappedOp {
WrappedOp {
op: self.op,
}
}
}
fn run(
_srv: astsrv::Srv,
doc: doc::Doc,
op: Op
) -> doc::Doc {
let op = WrappedOp {
op: op
};
let fold = Fold {
fold_item: fold_item,
fold_enum: fold_enum,
fold_trait: fold_trait,
fold_impl: fold_impl,
.. fold::default_any_fold(op)
};
(fold.fold_doc)(&fold, doc)
}
fn maybe_apply_op(op: WrappedOp, s: &Option<~str>) -> Option<~str> {
s.map(|s| (op.op)(*s) )
}
fn fold_item(fold: &fold::Fold<WrappedOp>, doc: doc::ItemDoc)
-> doc::ItemDoc {
let doc = fold::default_seq_fold_item(fold, doc);
doc::ItemDoc {
brief: maybe_apply_op(fold.ctxt, &doc.brief),
desc: maybe_apply_op(fold.ctxt, &doc.desc),
sections: apply_to_sections(fold.ctxt, doc.sections.clone()),
.. doc
}
}
fn apply_to_sections(op: WrappedOp, sections: ~[doc::Section])
-> ~[doc::Section] {
sections.map(|section| doc::Section {
header: (op.op)(section.header.clone()),
body: (op.op)(section.body.clone())
})
}
fn fold_enum(fold: &fold::Fold<WrappedOp>, doc: doc::EnumDoc)
-> doc::EnumDoc {
let doc = fold::default_seq_fold_enum(fold, doc);
let fold_copy = *fold;
doc::EnumDoc {
variants: do doc.variants.map |variant| {
doc::VariantDoc {
desc: maybe_apply_op(fold_copy.ctxt, &variant.desc),
.. (*variant).clone()
}
},
.. doc
}
}
fn fold_trait(fold: &fold::Fold<WrappedOp>, doc: doc::TraitDoc)
-> doc::TraitDoc {
let doc = fold::default_seq_fold_trait(fold, doc);
doc::TraitDoc {
methods: apply_to_methods(fold.ctxt, doc.methods.clone()),
.. doc
}
}
fn apply_to_methods(op: WrappedOp, docs: ~[doc::MethodDoc])
-> ~[doc::MethodDoc] {
do docs.map |doc| {
doc::MethodDoc {
brief: maybe_apply_op(op, &doc.brief),
desc: maybe_apply_op(op, &doc.desc),
sections: apply_to_sections(op, doc.sections.clone()),
.. (*doc).clone()
}
}
}
fn fold_impl(fold: &fold::Fold<WrappedOp>, doc: doc::ImplDoc)
-> doc::ImplDoc {
let doc = fold::default_seq_fold_impl(fold, doc);
doc::ImplDoc {
methods: apply_to_methods(fold.ctxt, doc.methods.clone()),
.. doc
}
}
#[cfg(test)]
mod test {
use astsrv;
use attr_pass;
use desc_to_brief_pass;
use doc;
use extract;
use sectionalize_pass;
use text_pass::mk_pass;
fn mk_doc(source: ~str) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (attr_pass::mk_pass().f)(srv.clone(), doc);
let doc = (desc_to_brief_pass::mk_pass().f)(srv.clone(), doc);
let doc = (sectionalize_pass::mk_pass().f)(srv.clone(), doc);
(mk_pass(~"", |s| s.trim().to_owned() ).f)(srv.clone(), doc)
}
}
#[test]
fn should_execute_op_on_enum_brief() {
let doc = mk_doc(~"#[doc = \" a \"] enum a { b }");
assert_eq!(doc.cratemod().enums()[0].brief(), Some(~"a"));
}
#[test]
fn should_execute_op_on_enum_desc() {
let doc = mk_doc(~"#[doc = \" a \"] enum a { b }");
assert_eq!(doc.cratemod().enums()[0].desc(), Some(~"a"));
}
#[test]
fn should_execute_op_on_variant_desc() {
let doc = mk_doc(~"enum a { #[doc = \" a \"] b }");
assert!(doc.cratemod().enums()[0].variants[0].desc == Some(~"a"));
}
#[test]
fn should_execute_op_on_trait_brief() {
let doc = mk_doc(
~"#[doc = \" a \"] trait i { fn a(); }");
assert_eq!(doc.cratemod().traits()[0].brief(), Some(~"a"));
}
#[test]
fn should_execute_op_on_trait_desc() {
let doc = mk_doc(
~"#[doc = \" a \"] trait i { fn a(); }");
assert_eq!(doc.cratemod().traits()[0].desc(), Some(~"a"));
}
#[test]
fn should_execute_op_on_trait_method_brief() {
let doc = mk_doc(
~"trait i { #[doc = \" a \"] fn a(); }");
assert!(doc.cratemod().traits()[0].methods[0].brief == Some(~"a"));
}
#[test]
fn should_execute_op_on_trait_method_desc() {
let doc = mk_doc(
~"trait i { #[doc = \" a \"] fn a(); }");
assert!(doc.cratemod().traits()[0].methods[0].desc == Some(~"a"));
}
#[test]
fn should_execute_op_on_impl_brief() {
let doc = mk_doc(
~"#[doc = \" a \"] impl int { fn a() { } }");
assert_eq!(doc.cratemod().impls()[0].brief(), Some(~"a"));
}
#[test]
fn should_execute_op_on_impl_desc() {
let doc = mk_doc(
~"#[doc = \" a \"] impl int { fn a() { } }");
assert_eq!(doc.cratemod().impls()[0].desc(), Some(~"a"));
}
#[test]
fn should_execute_op_on_impl_method_brief() {
let doc = mk_doc(
~"impl int { #[doc = \" a \"] fn a() { } }");
assert!(doc.cratemod().impls()[0].methods[0].brief == Some(~"a"));
}
#[test]
fn should_execute_op_on_impl_method_desc() {
let doc = mk_doc(
~"impl int { #[doc = \" a \"] fn a() { } }");
assert!(doc.cratemod().impls()[0].methods[0].desc == Some(~"a"));
}
#[test]
fn should_execute_op_on_type_brief() {
let doc = mk_doc(
~"#[doc = \" a \"] type t = int;");
assert_eq!(doc.cratemod().types()[0].brief(), Some(~"a"));
}
#[test]
fn should_execute_op_on_type_desc() {
let doc = mk_doc(
~"#[doc = \" a \"] type t = int;");
assert_eq!(doc.cratemod().types()[0].desc(), Some(~"a"));
}
#[test]
fn should_execute_on_item_section_headers() {
let doc = mk_doc(
~"#[doc = \"\
# Header \n\
Body\"]\
fn a() { }");
assert!(doc.cratemod().fns()[0].sections()[0].header == ~"Header");
}
#[test]
fn should_execute_on_item_section_bodies() {
let doc = mk_doc(
~"#[doc = \"\
# Header\n\
Body \"]\
fn a() { }");
assert!(doc.cratemod().fns()[0].sections()[0].body == ~"Body");
}
#[test]
fn should_execute_on_trait_method_section_headers() {
let doc = mk_doc(
~"trait i {
#[doc = \"\
# Header \n\
Body\"]\
fn a(); }");
assert!(doc.cratemod().traits()[0].methods[0].sections[0].header
== ~"Header");
}
#[test]
fn should_execute_on_trait_method_section_bodies() {
let doc = mk_doc(
~"trait i {
#[doc = \"\
# Header\n\
Body \"]\
fn a(); }");
assert!(doc.cratemod().traits()[0].methods[0].sections[0].body ==
~"Body");
}
#[test]
fn should_execute_on_impl_method_section_headers() {
let doc = mk_doc(
~"impl bool {
#[doc = \"\
# Header \n\
Body\"]\
fn a() { } }");
assert!(doc.cratemod().impls()[0].methods[0].sections[0].header
== ~"Header");
}
#[test]
fn should_execute_on_impl_method_section_bodies() {
let doc = mk_doc(
~"impl bool {
#[doc = \"\
# Header\n\
Body \"]\
fn a() { } }");
assert!(doc.cratemod().impls()[0].methods[0].sections[0].body ==
~"Body");
}
}

View File

@ -1,52 +0,0 @@
// Copyright 2012 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.
/*!
Pulls a brief description out of a long description.
If the first paragraph of a long description is short enough then it
is interpreted as the brief description.
*/
use pass::Pass;
use text_pass;
pub fn mk_pass() -> Pass {
text_pass::mk_pass(~"trim", |s| s.trim().to_owned() )
}
#[cfg(test)]
mod test {
use astsrv;
use attr_pass;
use doc;
use extract;
use prune_hidden_pass;
use trim_pass::mk_pass;
fn mk_doc(source: ~str) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
let doc = (attr_pass::mk_pass().f)(srv.clone(), doc);
let doc = (prune_hidden_pass::mk_pass().f)(srv.clone(), doc);
(mk_pass().f)(srv.clone(), doc)
}
}
#[test]
fn should_trim_text() {
use std::option::Some;
let doc = mk_doc(~"#[doc = \" desc \"] \
mod m {
}");
assert_eq!(doc.cratemod().mods()[0].desc(), Some(~"desc"));
}
}

View File

@ -1,456 +0,0 @@
// Copyright 2012 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.
//! Pulls type information out of the AST and attaches it to the document
use astsrv;
use doc::ItemUtils;
use doc;
use extract::to_str;
use extract;
use fold::Fold;
use fold;
use pass::Pass;
use syntax::ast;
use syntax::print::pprust;
use syntax::parse::token;
use syntax::ast_map;
pub fn mk_pass() -> Pass {
Pass {
name: ~"tystr",
f: run
}
}
pub fn run(
srv: astsrv::Srv,
doc: doc::Doc
) -> doc::Doc {
let fold = Fold {
ctxt: srv.clone(),
fold_fn: fold_fn,
fold_static: fold_static,
fold_enum: fold_enum,
fold_trait: fold_trait,
fold_impl: fold_impl,
fold_type: fold_type,
fold_struct: fold_struct,
.. fold::default_any_fold(srv)
};
(fold.fold_doc)(&fold, doc)
}
fn fold_fn(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::FnDoc
) -> doc::FnDoc {
let srv = fold.ctxt.clone();
doc::SimpleItemDoc {
sig: get_fn_sig(srv, doc.id()),
.. doc
}
}
fn get_fn_sig(srv: astsrv::Srv, fn_id: doc::AstId) -> Option<~str> {
do astsrv::exec(srv) |ctxt| {
match ctxt.ast_map.get_copy(&fn_id) {
ast_map::node_item(@ast::item {
ident: ident,
node: ast::item_fn(ref decl, purity, _, ref tys, _), _
}, _) => {
Some(pprust::fun_to_str(decl,
purity,
ident,
None,
tys,
token::get_ident_interner()))
}
ast_map::node_foreign_item(@ast::foreign_item {
ident: ident,
node: ast::foreign_item_fn(ref decl, ref tys), _
}, _, _, _) => {
Some(pprust::fun_to_str(decl,
ast::impure_fn,
ident,
None,
tys,
token::get_ident_interner()))
}
_ => fail!("get_fn_sig: fn_id not bound to a fn item")
}
}
}
fn fold_static(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::StaticDoc
) -> doc::StaticDoc {
let srv = fold.ctxt.clone();
doc::SimpleItemDoc {
sig: Some({
let doc = doc.clone();
do astsrv::exec(srv) |ctxt| {
match ctxt.ast_map.get_copy(&doc.id()) {
ast_map::node_item(@ast::item {
node: ast::item_static(ref ty, _, _), _
}, _) => {
pprust::ty_to_str(ty, extract::interner())
}
_ => fail!("fold_static: id not bound to a static item")
}
}}),
.. doc
}
}
fn fold_enum(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::EnumDoc
) -> doc::EnumDoc {
let doc_id = doc.id();
let srv = fold.ctxt.clone();
doc::EnumDoc {
variants: do doc.variants.iter().map |variant| {
let sig = {
let variant = (*variant).clone();
do astsrv::exec(srv.clone()) |ctxt| {
match ctxt.ast_map.get_copy(&doc_id) {
ast_map::node_item(@ast::item {
node: ast::item_enum(ref enum_definition, _), _
}, _) => {
let ast_variant =
(*do enum_definition.variants.iter().find |v| {
to_str(v.node.name) == variant.name
}.unwrap()).clone();
pprust::variant_to_str(
&ast_variant, extract::interner())
}
_ => fail!("enum variant not bound to an enum item")
}
}
};
doc::VariantDoc {
sig: Some(sig),
.. (*variant).clone()
}
}.collect(),
.. doc
}
}
fn fold_trait(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::TraitDoc
) -> doc::TraitDoc {
doc::TraitDoc {
methods: merge_methods(fold.ctxt.clone(), doc.id(), doc.methods.clone()),
.. doc
}
}
fn merge_methods(
srv: astsrv::Srv,
item_id: doc::AstId,
docs: ~[doc::MethodDoc]
) -> ~[doc::MethodDoc] {
do docs.iter().map |doc| {
doc::MethodDoc {
sig: get_method_sig(srv.clone(), item_id, doc.name.clone()),
.. (*doc).clone()
}
}.collect()
}
fn get_method_sig(
srv: astsrv::Srv,
item_id: doc::AstId,
method_name: ~str
) -> Option<~str> {
do astsrv::exec(srv) |ctxt| {
match ctxt.ast_map.get_copy(&item_id) {
ast_map::node_item(@ast::item {
node: ast::item_trait(_, _, ref methods), _
}, _) => {
match methods.iter().find(|&method| {
match (*method).clone() {
ast::required(ty_m) => to_str(ty_m.ident) == method_name,
ast::provided(m) => to_str(m.ident) == method_name,
}
}) {
Some(method) => {
match (*method).clone() {
ast::required(ty_m) => {
Some(pprust::fun_to_str(
&ty_m.decl,
ty_m.purity,
ty_m.ident,
Some(ty_m.explicit_self.node),
&ty_m.generics,
extract::interner()
))
}
ast::provided(m) => {
Some(pprust::fun_to_str(
&m.decl,
m.purity,
m.ident,
Some(m.explicit_self.node),
&m.generics,
extract::interner()
))
}
}
}
_ => fail!("method not found")
}
}
ast_map::node_item(@ast::item {
node: ast::item_impl(_, _, _, ref methods), _
}, _) => {
match methods.iter().find(|method| {
to_str(method.ident) == method_name
}) {
Some(method) => {
Some(pprust::fun_to_str(
&method.decl,
method.purity,
method.ident,
Some(method.explicit_self.node),
&method.generics,
extract::interner()
))
}
None => fail!("method not found")
}
}
_ => fail!("get_method_sig: item ID not bound to trait or impl")
}
}
}
fn fold_impl(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::ImplDoc
) -> doc::ImplDoc {
let srv = fold.ctxt.clone();
let (bounds, trait_types, self_ty) = {
let doc = doc.clone();
do astsrv::exec(srv) |ctxt| {
match ctxt.ast_map.get_copy(&doc.id()) {
ast_map::node_item(@ast::item {
node: ast::item_impl(ref generics, ref opt_trait_type, ref self_ty, _), _
}, _) => {
let bounds = pprust::generics_to_str(generics, extract::interner());
let bounds = if bounds.is_empty() { None } else { Some(bounds) };
let trait_types = do opt_trait_type.map_default(~[]) |p| {
~[pprust::path_to_str(&p.path, extract::interner())]
};
(bounds,
trait_types,
Some(pprust::ty_to_str(
self_ty, extract::interner())))
}
_ => fail!("expected impl")
}
}
};
doc::ImplDoc {
bounds_str: bounds,
trait_types: trait_types,
self_ty: self_ty,
methods: merge_methods(fold.ctxt.clone(), doc.id(), doc.methods.clone()),
.. doc
}
}
fn fold_type(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::TyDoc
) -> doc::TyDoc {
let srv = fold.ctxt.clone();
doc::SimpleItemDoc {
sig: {
let doc = doc.clone();
do astsrv::exec(srv) |ctxt| {
match ctxt.ast_map.get_copy(&doc.id()) {
ast_map::node_item(@ast::item {
ident: ident,
node: ast::item_ty(ref ty, ref params), _
}, _) => {
Some(fmt!(
"type %s%s = %s",
to_str(ident),
pprust::generics_to_str(params,
extract::interner()),
pprust::ty_to_str(ty, extract::interner())
))
}
_ => fail!("expected type")
}
}
},
.. doc
}
}
fn fold_struct(
fold: &fold::Fold<astsrv::Srv>,
doc: doc::StructDoc
) -> doc::StructDoc {
let srv = fold.ctxt.clone();
doc::StructDoc {
sig: {
let doc = doc.clone();
do astsrv::exec(srv) |ctxt| {
match ctxt.ast_map.get_copy(&doc.id()) {
ast_map::node_item(item, _) => {
let item = strip_struct_extra_stuff(item);
Some(pprust::item_to_str(item,
extract::interner()))
}
_ => fail!("not an item")
}
}
},
.. doc
}
}
/// Removes various things from the struct item definition that
/// shouldn't be displayed in the struct signature. Probably there
/// should be a simple pprust::struct_to_str function that does
/// what I actually want
fn strip_struct_extra_stuff(item: @ast::item) -> @ast::item {
let node = match item.node.clone() {
ast::item_struct(def, tys) => ast::item_struct(def, tys),
_ => fail!("not a struct")
};
@ast::item {
attrs: ~[], // Remove the attributes
node: node,
.. (*item).clone()
}
}
#[cfg(test)]
mod test {
use astsrv;
use doc;
use extract;
use tystr_pass::run;
fn mk_doc(source: ~str) -> doc::Doc {
do astsrv::from_str(source.clone()) |srv| {
let doc = extract::from_srv(srv.clone(), ~"");
run(srv.clone(), doc)
}
}
#[test]
fn should_add_fn_sig() {
let doc = mk_doc(~"fn a<T>() -> int { }");
assert!(doc.cratemod().fns()[0].sig == Some(~"fn a<T>() -> int"));
}
#[test]
fn should_add_foreign_fn_sig() {
let doc = mk_doc(~"extern { fn a<T>() -> int; }");
assert!(doc.cratemod().nmods()[0].fns[0].sig ==
Some(~"fn a<T>() -> int"));
}
#[test]
fn should_add_static_types() {
let doc = mk_doc(~"static a: bool = true;");
assert!(doc.cratemod().statics()[0].sig == Some(~"bool"));
}
#[test]
fn should_add_variant_sigs() {
let doc = mk_doc(~"enum a { b(int) }");
assert!(doc.cratemod().enums()[0].variants[0].sig ==
Some(~"b(int)"));
}
#[test]
fn should_add_trait_method_sigs() {
let doc = mk_doc(~"trait i { fn a<T>(&mut self) -> int; }");
assert!(doc.cratemod().traits()[0].methods[0].sig
== Some(~"fn a<T>(&mut self) -> int"));
}
#[test]
fn should_add_impl_bounds() {
let doc = mk_doc(~"impl<T, U, V: Clone> Option<T, U, V> { }");
assert!(doc.cratemod().impls()[0].bounds_str == Some(~"<T, U, V: Clone>"));
}
#[test]
fn should_add_impl_trait_types() {
let doc = mk_doc(~"impl j for int { fn a<T>() { } }");
assert!(doc.cratemod().impls()[0].trait_types[0] == ~"j");
}
#[test]
fn should_not_add_impl_trait_types_if_none() {
let doc = mk_doc(~"impl int { fn a() { } }");
assert!(doc.cratemod().impls()[0].trait_types.len() == 0);
}
#[test]
fn should_add_impl_self_ty() {
let doc = mk_doc(~"impl int { fn a() { } }");
assert!(doc.cratemod().impls()[0].self_ty == Some(~"int"));
}
#[test]
fn should_add_impl_method_sigs() {
let doc = mk_doc(~"impl int { fn a<T>(&self) -> int { fail!() } }");
assert!(doc.cratemod().impls()[0].methods[0].sig
== Some(~"fn a<T>(&self) -> int"));
}
#[test]
fn should_add_type_signatures() {
let doc = mk_doc(~"type t<T> = int;");
assert!(doc.cratemod().types()[0].sig == Some(~"type t<T> = int"));
}
#[test]
fn should_add_struct_defs() {
let doc = mk_doc(~"struct S { field: () }");
assert!(doc.cratemod().structs()[0].sig.unwrap().contains(
"struct S {"));
}
#[test]
fn should_not_serialize_struct_attrs() {
// All we care about are the fields
let doc = mk_doc(~"#[wut] struct S { field: () }");
assert!(!doc.cratemod().structs()[0].sig.unwrap().contains("wut"));
}
}

View File

@ -1,134 +0,0 @@
// Copyright 2012-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.
/*!
Removes the common level of indention from description strings. For
instance, if an entire doc comment is indented 8 spaces we want to
remove those 8 spaces from every line.
The first line of a string is allowed to be intend less than
subsequent lines in the same paragraph in order to account for
instances where the string containing the doc comment is opened in the
middle of a line, and each of the following lines is indented.
*/
use std::num;
use std::uint;
use pass::Pass;
use text_pass;
pub fn mk_pass() -> Pass {
text_pass::mk_pass(~"unindent", unindent)
}
fn unindent(s: &str) -> ~str {
let lines = s.any_line_iter().collect::<~[&str]>();
let mut saw_first_line = false;
let mut saw_second_line = false;
let min_indent = do lines.iter().fold(uint::max_value)
|min_indent, line| {
// After we see the first non-whitespace line, look at
// the line we have. If it is not whitespace, and therefore
// part of the first paragraph, then ignore the indentation
// level of the first line
let ignore_previous_indents =
saw_first_line &&
!saw_second_line &&
!line.is_whitespace();
let min_indent = if ignore_previous_indents {
uint::max_value
} else {
min_indent
};
if saw_first_line {
saw_second_line = true;
}
if line.is_whitespace() {
min_indent
} else {
saw_first_line = true;
let mut spaces = 0;
do line.iter().all |char| {
// Only comparing against space because I wouldn't
// know what to do with mixed whitespace chars
if char == ' ' {
spaces += 1;
true
} else {
false
}
};
num::min(min_indent, spaces)
}
};
match lines {
[head, .. tail] => {
let mut unindented = ~[ head.trim() ];
unindented.push_all(do tail.map |&line| {
if line.is_whitespace() {
line
} else {
assert!(line.len() >= min_indent);
line.slice_from(min_indent)
}
});
unindented.connect("\n")
}
[] => s.to_owned()
}
}
#[test]
fn should_unindent() {
let s = ~" line1\n line2";
let r = unindent(s);
assert_eq!(r, ~"line1\nline2");
}
#[test]
fn should_unindent_multiple_paragraphs() {
let s = ~" line1\n\n line2";
let r = unindent(s);
assert_eq!(r, ~"line1\n\nline2");
}
#[test]
fn should_leave_multiple_indent_levels() {
// Line 2 is indented another level beyond the
// base indentation and should be preserved
let s = ~" line1\n\n line2";
let r = unindent(s);
assert_eq!(r, ~"line1\n\n line2");
}
#[test]
fn should_ignore_first_line_indent() {
// Thi first line of the first paragraph may not be indented as
// far due to the way the doc string was written:
//
// #[doc = "Start way over here
// and continue here"]
let s = ~"line1\n line2";
let r = unindent(s);
assert_eq!(r, ~"line1\nline2");
}
#[test]
fn should_not_ignore_first_line_indent_in_a_single_line_para() {
let s = ~"line1\n\n line2";
let r = unindent(s);
assert_eq!(r, ~"line1\n\n line2");
}

View File

@ -1,2 +0,0 @@
*.swp
main

View File

@ -1,99 +0,0 @@
// Copyright 2012-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;
use clean::*;
use std::iter::Extendable;
pub trait DocFolder {
fn fold_item(&mut self, item: Item) -> Option<Item> {
self.fold_item_recur(item)
}
/// don't override!
fn fold_item_recur(&mut self, item: Item) -> Option<Item> {
use std::util::swap;
let Item { attrs, name, source, visibility, id, inner } = item;
let inner = inner;
let c = |x| self.fold_item(x);
let inner = match inner {
StructItem(i) => {
let mut i = i;
let mut foo = ~[]; swap(&mut foo, &mut i.fields);
i.fields.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
StructItem(i)
},
ModuleItem(i) => {
ModuleItem(self.fold_mod(i))
},
EnumItem(i) => {
let mut i = i;
let mut foo = ~[]; swap(&mut foo, &mut i.variants);
i.variants.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
EnumItem(i)
},
TraitItem(i) => {
fn vtrm<T: DocFolder>(this: &mut T, trm: TraitMethod) -> Option<TraitMethod> {
match trm {
Required(it) => {
match this.fold_item(it) {
Some(x) => return Some(Required(x)),
None => return None,
}
},
Provided(it) => {
match this.fold_item(it) {
Some(x) => return Some(Provided(x)),
None => return None,
}
},
}
}
let mut i = i;
let mut foo = ~[]; swap(&mut foo, &mut i.methods);
i.methods.extend(&mut foo.move_iter().filter_map(|x| vtrm(self, x)));
TraitItem(i)
},
ImplItem(i) => {
let mut i = i;
let mut foo = ~[]; swap(&mut foo, &mut i.methods);
i.methods.extend(&mut foo.move_iter().filter_map(|x| self.fold_item(x)));
ImplItem(i)
},
VariantItem(i) => {
let i2 = i.clone(); // this clone is small
match i.kind {
StructVariant(j) => {
let mut j = j;
let mut foo = ~[]; swap(&mut foo, &mut j.fields);
j.fields.extend(&mut foo.move_iter().filter_map(c));
VariantItem(Variant {kind: StructVariant(j), ..i2})
},
_ => VariantItem(i2)
}
},
x => x
};
Some(Item { attrs: attrs, name: name, source: source, inner: inner,
visibility: visibility, id: id })
}
fn fold_mod(&mut self, m: Module) -> Module {
Module { items: m.items.move_iter().filter_map(|i| self.fold_item(i)).collect() }
}
fn fold_crate(&mut self, mut c: Crate) -> Crate {
c.module = match std::util::replace(&mut c.module, None) {
Some(module) => self.fold_item(module), None => None
};
return c;
}
}

View File

@ -1,211 +0,0 @@
// Copyright 2012-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.
#[link(name = "rustdoc_ng",
vers = "0.8",
uuid = "8c6e4598-1596-4aa5-a24c-b811914bbbc6",
url = "https://github.com/mozilla/rust/tree/master/src/rustdoc_ng")];
#[desc = "rustdoc, the Rust documentation extractor"];
#[license = "MIT/ASL2"];
#[crate_type = "lib"];
extern mod syntax;
extern mod rustc;
extern mod extra;
use extra::serialize::Encodable;
use extra::time;
use std::cell::Cell;
use std::rt::io;
use std::rt::io::Writer;
use std::rt::io::file::FileInfo;
pub mod clean;
pub mod core;
pub mod doctree;
pub mod fold;
pub mod html {
pub mod render;
pub mod layout;
pub mod markdown;
pub mod format;
}
pub mod passes;
pub mod plugins;
pub mod visit_ast;
pub static SCHEMA_VERSION: &'static str = "0.8.0";
local_data_key!(pub ctxtkey: @core::DocContext)
enum OutputFormat {
HTML, JSON
}
pub fn main() {
main_args(std::os::args());
}
pub fn main_args(args: &[~str]) {
use extra::getopts::groups::*;
let opts = ~[
optmulti("L", "library-path", "directory to add to crate search path",
"DIR"),
optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
optmulti("", "passes", "space separated list of passes to also run",
"PASSES"),
optmulti("", "plugins", "space separated list of plugins to also load",
"PLUGINS"),
optflag("h", "help", "show this help message"),
optflag("", "nodefaults", "don't run the default passes"),
optopt("o", "output", "where to place the output", "PATH"),
];
let matches = getopts(args.tail(), opts).unwrap();
let myusage = || {
println(usage(format!("{} [options] [html|json] <crate>", args[0]), opts));
};
if matches.opt_present("h") || matches.opt_present("help") {
myusage();
return;
}
let (format, cratefile) = match matches.free.clone() {
[~"json", crate] => (JSON, crate),
[~"html", crate] => (HTML, crate),
[s, _] => {
println!("Unknown output format: `{}`", s);
myusage();
exit(1);
}
[_, .._] => {
println!("Expected exactly one crate to process");
myusage();
exit(1);
}
_ => {
println!("Expected an output format and then one crate");
myusage();
exit(1);
}
};
// First, parse the crate and extract all relevant information.
let libs = Cell::new(matches.opt_strs("L").map(|s| Path(*s)));
let cr = Cell::new(Path(cratefile));
info2!("starting to run rustc");
let crate = do std::task::try {
let cr = cr.take();
core::run_core(libs.take(), &cr)
}.unwrap();
info2!("finished with rustc");
// Process all of the crate attributes, extracting plugin metadata along
// with the passes which we are supposed to run.
let mut default_passes = !matches.opt_present("nodefaults");
let mut passes = matches.opt_strs("passes");
let mut plugins = matches.opt_strs("plugins");
match crate.module.get_ref().doc_list() {
Some(nested) => {
for inner in nested.iter() {
match *inner {
clean::Word(~"no_default_passes") => {
default_passes = false;
}
clean::NameValue(~"passes", ref value) => {
for pass in value.word_iter() {
passes.push(pass.to_owned());
}
}
clean::NameValue(~"plugins", ref value) => {
for p in value.word_iter() {
plugins.push(p.to_owned());
}
}
_ => {}
}
}
}
None => {}
}
if default_passes {
passes.unshift(~"collapse-docs");
passes.unshift(~"unindent-comments");
}
// Load all plugins/passes into a PluginManager
let mut pm = plugins::PluginManager::new(Path("/tmp/rustdoc_ng/plugins"));
for pass in passes.iter() {
let plugin = match pass.as_slice() {
"strip-hidden" => passes::strip_hidden,
"unindent-comments" => passes::unindent_comments,
"collapse-docs" => passes::collapse_docs,
"collapse-privacy" => passes::collapse_privacy,
s => { error!("unknown pass %s, skipping", s); loop },
};
pm.add_plugin(plugin);
}
info2!("loading plugins...");
for pname in plugins.move_iter() {
pm.load_plugin(pname);
}
// Run everything!
info2!("Executing passes/plugins");
let (crate, res) = pm.run_plugins(crate);
info2!("going to format");
let started = time::precise_time_ns();
let output = matches.opt_str("o").map(|s| Path(*s));
match format {
HTML => { html::render::run(crate, output.unwrap_or(Path("doc"))) }
JSON => { jsonify(crate, res, output.unwrap_or(Path("doc.json"))) }
}
let ended = time::precise_time_ns();
info2!("Took {:.03f}s", (ended as f64 - started as f64) / 1000000000f64);
}
fn jsonify(crate: clean::Crate, res: ~[plugins::PluginJson], dst: Path) {
// {
// "schema": version,
// "crate": { parsed crate ... },
// "plugins": { output of plugins ... }
// }
let mut json = ~extra::treemap::TreeMap::new();
json.insert(~"schema", extra::json::String(SCHEMA_VERSION.to_owned()));
let plugins_json = ~res.move_iter().filter_map(|opt| opt).collect();
// FIXME #8335: yuck, Rust -> str -> JSON round trip! No way to .encode
// straight to the Rust JSON representation.
let crate_json_str = do std::io::with_str_writer |w| {
crate.encode(&mut extra::json::Encoder(w));
};
let crate_json = match extra::json::from_str(crate_json_str) {
Ok(j) => j,
Err(_) => fail!("Rust generated JSON is invalid??")
};
json.insert(~"crate", crate_json);
json.insert(~"plugins", extra::json::Object(plugins_json));
let mut file = dst.open_writer(io::Create).unwrap();
let output = extra::json::Object(json).to_str();
file.write(output.as_bytes());
}
fn exit(status: int) -> ! {
#[fixed_stack_segment]; #[inline(never)];
use std::libc;
unsafe { libc::exit(status as libc::c_int) }
}