auto merge of #12747 : huonw/rust/rustdoc-markdown, r=alexcrichton

This gives Rustdoc the ability to render our guides, tutorial and manual and modifies the those documents (minor modifications) and makefiles to achieve this.

See commits for more details (especially the makefile rewrite).
This commit is contained in:
bors 2014-03-09 03:06:42 -07:00
commit 62f1d68439
22 changed files with 1026 additions and 670 deletions

View File

@ -41,7 +41,6 @@ clean-misc:
@$(call E, cleaning)
$(Q)rm -f $(RUNTIME_OBJS) $(RUNTIME_DEF)
$(Q)rm -f $(RUSTLLVM_LIB_OBJS) $(RUSTLLVM_OBJS_OBJS) $(RUSTLLVM_DEF)
$(Q)rm -Rf $(DOCS)
$(Q)rm -Rf $(GENERATED)
$(Q)rm -Rf tmp/*
$(Q)rm -Rf rust-stage0-*.tar.bz2 $(PKG_NAME)-*.tar.gz $(PKG_NAME)-*.exe dist

View File

@ -92,6 +92,7 @@ TOOL_SOURCE_rustc := $(S)src/driver/driver.rs
################################################################################
DOC_CRATES := $(filter-out rustc, $(filter-out syntax, $(CRATES)))
COMPILER_DOC_CRATES := rustc syntax
# This macro creates some simple definitions for each crate being built, just
# some munging of all of the parameters above.

View File

@ -9,24 +9,96 @@
# except according to those terms.
######################################################################
# Doc variables and rules
# The various pieces of standalone documentation: guides, tutorial,
# manual etc.
#
# The DOCS variable is their names (with no file extension).
#
# PDF_DOCS lists the targets for which PDF documentation should be
# build.
#
# RUSTDOC_FLAGS_xyz variables are extra arguments to pass to the
# rustdoc invocation for xyz.
#
# RUSTDOC_DEPS_xyz are extra dependencies for the rustdoc invocation
# on xyz.
#
# L10N_LANGS are the languages for which the docs have been
# translated.
######################################################################
DOCS := index tutorial guide-ffi guide-macros guide-lifetimes \
guide-tasks guide-container guide-pointers \
complement-cheatsheet guide-runtime \
rust rustdoc
DOCS :=
CDOCS :=
DOCS_L10N :=
HTML_DEPS := doc/
PDF_DOCS := tutorial rust
BASE_DOC_OPTS := --standalone --toc --number-sections
HTML_OPTS = $(BASE_DOC_OPTS) --to=html5 --section-divs --css=rust.css \
--include-before-body=doc/version_info.html \
--include-in-header=doc/favicon.inc --include-after-body=doc/footer.inc
TEX_OPTS = $(BASE_DOC_OPTS) --include-before-body=doc/version.md \
--from=markdown --include-before-body=doc/footer.tex --to=latex
EPUB_OPTS = $(BASE_DOC_OPTS) --to=epub
RUSTDOC_DEPS_rust := doc/full-toc.inc
RUSTDOC_FLAGS_rust := --markdown-in-header=doc/full-toc.inc
L10N_LANGS := ja
# Generally no need to edit below here.
# The options are passed to the documentation generators.
RUSTDOC_HTML_OPTS = --markdown-css rust.css \
--markdown-before-content=doc/version_info.html \
--markdown-in-header=doc/favicon.inc --markdown-after-content=doc/footer.inc
PANDOC_BASE_OPTS := --standalone --toc --number-sections
PANDOC_TEX_OPTS = $(PANDOC_BASE_OPTS) --include-before-body=doc/version.md \
--from=markdown --include-before-body=doc/footer.tex --to=latex
PANDOC_EPUB_OPTS = $(PANDOC_BASE_OPTS) --to=epub
# The rustdoc executable...
RUSTDOC_EXE = $(HBIN2_H_$(CFG_BUILD))/rustdoc$(X_$(CFG_BUILD))
# ...with rpath included in case --disable-rpath was provided to
# ./configure
RUSTDOC = $(RPATH_VAR2_T_$(CFG_BUILD)_H_$(CFG_BUILD)) $(RUSTDOC_EXE)
D := $(S)src/doc
DOC_TARGETS :=
COMPILER_DOC_TARGETS :=
DOC_L10N_TARGETS :=
# If NO_REBUILD is set then break the dependencies on rustdoc so we
# build the documentation without having to rebuild rustdoc.
ifeq ($(NO_REBUILD),)
HTML_DEPS := $(RUSTDOC_EXE)
else
HTML_DEPS :=
endif
# Check for the various external utilities for the EPUB/PDF docs:
ifeq ($(CFG_PDFLATEX),)
$(info cfg: no pdflatex found, omitting doc/rust.pdf)
NO_PDF_DOCS = 1
else
ifeq ($(CFG_XETEX),)
$(info cfg: no xetex found, disabling doc/rust.pdf)
NO_PDF_DOCS = 1
else
ifeq ($(CFG_LUATEX),)
$(info cfg: lacking luatex, disabling pdflatex)
NO_PDF_DOCS = 1
endif
endif
endif
ifeq ($(CFG_PANDOC),)
$(info cfg: no pandoc found, omitting PDF and EPUB docs)
ONLY_HTML_DOCS = 1
endif
ifeq ($(CFG_NODE),)
$(info cfg: no node found, omitting PDF and EPUB docs)
ONLY_HTML_DOCS = 1
endif
######################################################################
# Rust version
######################################################################
@ -46,7 +118,7 @@ doc/version_info.html: $(D)/version_info.html.template $(MKFILE_DEPS) \
GENERATED += doc/version.md doc/version_info.html
######################################################################
# Docs, from pandoc, rustdoc (which runs pandoc), and node
# Docs, from rustdoc and sometimes pandoc & node
######################################################################
doc/:
@ -75,184 +147,85 @@ doc/footer.tex: $(D)/footer.tex | doc/
@$(call E, cp: $@)
$(Q)cp -a $< $@ 2> /dev/null
ifeq ($(CFG_PANDOC),)
$(info cfg: no pandoc found, omitting docs)
NO_DOCS = 1
endif
# The (english) documentation for each doc item.
ifeq ($(CFG_NODE),)
$(info cfg: no node found, omitting docs)
NO_DOCS = 1
endif
define DEF_SHOULD_BUILD_PDF_DOC
SHOULD_BUILD_PDF_DOC_$(1) = 1
endef
$(foreach docname,$(PDF_DOCS),$(eval $(call DEF_SHOULD_BUILD_PDF_DOC,$(docname))))
ifneq ($(NO_DOCS),1)
define DEF_DOC
DOCS += doc/rust.html
doc/rust.html: $(D)/rust.md doc/full-toc.inc $(HTML_DEPS) | doc/
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --include-in-header=doc/full-toc.inc --output=$@
# HTML (rustdoc)
DOC_TARGETS += doc/$(1).html
doc/$(1).html: $$(D)/$(1).md $$(HTML_DEPS) $$(RUSTDOC_DEPS_$(1)) | doc/
@$$(call E, rustdoc: $$@)
$$(RUSTDOC) $$(RUSTDOC_HTML_OPTS) $$(RUSTDOC_FLAGS_$(1)) $$<
DOCS += doc/rust.tex
doc/rust.tex: $(D)/rust.md doc/footer.tex doc/version.md | doc/
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js $< | \
$(CFG_PANDOC) $(TEX_OPTS) --output=$@
ifneq ($(ONLY_HTML_DOCS),1)
DOCS += doc/rust.epub
doc/rust.epub: $(D)/rust.md | doc/
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(EPUB_OPTS) --output=$@
# EPUB (pandoc directly)
DOC_TARGETS += doc/$(1).epub
doc/$(1).epub: $$(D)/$(1).md | doc/
@$$(call E, pandoc: $$@)
$$(Q)$$(CFG_NODE) $$(D)/prep.js --highlight $$< | \
$$(CFG_PANDOC) $$(PANDOC_EPUB_OPTS) --output=$$@
DOCS += doc/rustdoc.html
doc/rustdoc.html: $(D)/rustdoc.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
# PDF (md =(pandoc)=> tex =(pdflatex)=> pdf)
DOC_TARGETS += doc/$(1).tex
doc/$(1).tex: $$(D)/$(1).md doc/footer.tex doc/version.md | doc/
@$$(call E, pandoc: $$@)
$$(Q)$$(CFG_NODE) $$(D)/prep.js $$< | \
$$(CFG_PANDOC) $$(PANDOC_TEX_OPTS) --output=$$@
DOCS += doc/tutorial.html
doc/tutorial.html: $(D)/tutorial.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
ifneq ($(NO_PDF_DOCS),1)
ifeq ($$(SHOULD_BUILD_PDF_DOC_$(1)),1)
DOC_TARGETS += doc/$(1).pdf
doc/$(1).pdf: doc/$(1).tex
@$$(call E, pdflatex: $$@)
$$(Q)$$(CFG_PDFLATEX) \
-interaction=batchmode \
-output-directory=doc \
$$<
endif # SHOULD_BUILD_PDF_DOCS_$(1)
endif # NO_PDF_DOCS
DOCS += doc/tutorial.tex
doc/tutorial.tex: $(D)/tutorial.md doc/footer.tex doc/version.md
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js $< | \
$(CFG_PANDOC) $(TEX_OPTS) --output=$@
endif # ONLY_HTML_DOCS
DOCS += doc/tutorial.epub
doc/tutorial.epub: $(D)/tutorial.md
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(EPUB_OPTS) --output=$@
endef
$(foreach docname,$(DOCS),$(eval $(call DEF_DOC,$(docname))))
DOCS_L10N += doc/l10n/ja/tutorial.html
doc/l10n/ja/tutorial.html: doc/l10n/ja/tutorial.md doc/version_info.html doc/rust.css
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight doc/l10n/ja/tutorial.md | \
$(CFG_PANDOC) --standalone --toc \
--section-divs --number-sections \
--from=markdown --to=html5 --css=../../rust.css \
--include-before-body=doc/version_info.html \
--output=$@
# Localized documentation
# Complementary documentation
# FIXME: I (huonw) haven't actually been able to test properly, since
# e.g. (by default) I'm doing an out-of-tree build (#12763), but even
# adjusting for that, the files are too old(?) and are rejected by
# po4a.
#
DOCS += doc/index.html
doc/index.html: $(D)/index.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
# As such, I've attempted to get it working as much as possible (and
# switching from pandoc to rustdoc), but preserving the old behaviour
# (e.g. only running on the tutorial)
.PHONY: l10n-mds
l10n-mds: $(D)/po4a.conf \
$(foreach lang,$(L10N_LANG),$(D)/po/$(lang)/*.md.po)
$(warning WARNING: localized documentation is experimental)
po4a --copyright-holder="The Rust Project Developers" \
--package-name="Rust" \
--package-version="$(CFG_RELEASE)" \
-M UTF-8 -L UTF-8 \
$(D)/po4a.conf
DOCS += doc/complement-lang-faq.html
doc/complement-lang-faq.html: $(D)/complement-lang-faq.md doc/full-toc.inc $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --include-in-header=doc/full-toc.inc --output=$@
define DEF_L10N_DOC
DOC_L10N_TARGETS += doc/l10n/$(1)/$(2).html
doc/l10n/$(1)/$(2).html: l10n-mds $$(HTML_DEPS) $$(RUSTDOC_DEPS_$(2))
@$$(call E, rustdoc: $$@)
$$(RUSTDOC) $$(RUSTDOC_HTML_OPTS) $$(RUSTDOC_FLAGS_$(1)) doc/l10n/$(1)/$(2).md
endef
DOCS += doc/complement-project-faq.html
doc/complement-project-faq.html: $(D)/complement-project-faq.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
$(foreach lang,$(L10N_LANGS),$(eval $(call DEF_L10N_DOC,$(lang),tutorial)))
DOCS += doc/complement-cheatsheet.html
doc/complement-cheatsheet.html: $(D)/complement-cheatsheet.md doc/full-toc.inc $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --include-in-header=doc/full-toc.inc --output=$@
DOCS += doc/complement-bugreport.html
doc/complement-bugreport.html: $(D)/complement-bugreport.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
# Guides
DOCS += doc/guide-macros.html
doc/guide-macros.html: $(D)/guide-macros.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-container.html
doc/guide-container.html: $(D)/guide-container.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-ffi.html
doc/guide-ffi.html: $(D)/guide-ffi.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-testing.html
doc/guide-testing.html: $(D)/guide-testing.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-lifetimes.html
doc/guide-lifetimes.html: $(D)/guide-lifetimes.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-tasks.html
doc/guide-tasks.html: $(D)/guide-tasks.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-pointers.html
doc/guide-pointers.html: $(D)/guide-pointers.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-runtime.html
doc/guide-runtime.html: $(D)/guide-runtime.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
ifeq ($(CFG_PDFLATEX),)
$(info cfg: no pdflatex found, omitting doc/rust.pdf)
else
ifeq ($(CFG_XETEX),)
$(info cfg: no xetex found, disabling doc/rust.pdf)
else
ifeq ($(CFG_LUATEX),)
$(info cfg: lacking luatex, disabling pdflatex)
else
DOCS += doc/rust.pdf
doc/rust.pdf: doc/rust.tex
@$(call E, pdflatex: $@)
$(Q)$(CFG_PDFLATEX) \
-interaction=batchmode \
-output-directory=doc \
$<
DOCS += doc/tutorial.pdf
doc/tutorial.pdf: doc/tutorial.tex
@$(call E, pdflatex: $@)
$(Q)$(CFG_PDFLATEX) \
-interaction=batchmode \
-output-directory=doc \
$<
endif
endif
endif
endif # No pandoc / node
######################################################################
# LLnextgen (grammar analysis from refman)
@ -278,50 +251,44 @@ endif
# Rustdoc (libstd/extra)
######################################################################
# The rustdoc executable, rpath included in case --disable-rpath was provided to
# ./configure
RUSTDOC = $(HBIN2_H_$(CFG_BUILD))/rustdoc$(X_$(CFG_BUILD))
# The library documenting macro
#
# $(1) - The crate name (std/extra)
#
# Passes --cfg stage2 to rustdoc because it uses the stage2 librustc.
define libdoc
doc/$(1)/index.html: \
$$(CRATEFILE_$(1)) \
$$(RSINPUTS_$(1)) \
$$(RUSTDOC) \
$$(foreach dep,$$(RUST_DEPS_$(1)), \
$$(TLIB2_T_$(CFG_BUILD)_H_$(CFG_BUILD))/stamp.$$(dep))
@$$(call E, rustdoc: $$@)
$$(Q)$$(RPATH_VAR2_T_$(CFG_BUILD)_H_$(CFG_BUILD)) $$(RUSTDOC) \
--cfg stage2 $$<
define DEF_LIB_DOC
# If NO_REBUILD is set then break the dependencies on rustdoc so we
# build crate documentation without having to rebuild rustdoc.
ifeq ($(NO_REBUILD),)
LIB_DOC_DEP_$(1) = \
$$(CRATEFILE_$(1)) \
$$(RSINPUTS_$(1)) \
$$(RUSTDOC_EXE) \
$$(foreach dep,$$(RUST_DEPS_$(1)), \
$$(TLIB2_T_$(CFG_BUILD)_H_$(CFG_BUILD))/stamp.$$(dep))
else
LIB_DOC_DEP_$(1) = $$(CRATEFILE_$(1)) $$(RSINPUTS_$(1))
endif
$(2) += doc/$(1)/index.html
doc/$(1)/index.html: $$(LIB_DOC_DEP_$(1))
@$$(call E, rustdoc $$@)
$$(Q)$$(RUSTDOC) --cfg stage2 $$<
endef
$(foreach crate,$(CRATES),$(eval $(call libdoc,$(crate))))
DOCS += $(DOC_CRATES:%=doc/%/index.html)
CDOCS += doc/rustc/index.html
CDOCS += doc/syntax/index.html
$(foreach crate,$(DOC_CRATES),$(eval $(call DEF_LIB_DOC,$(crate),DOC_TARGETS)))
$(foreach crate,$(COMPILER_DOC_CRATES),$(eval $(call DEF_LIB_DOC,$(crate),COMPILER_DOC_TARGETS)))
ifdef CFG_DISABLE_DOCS
$(info cfg: disabling doc build (CFG_DISABLE_DOCS))
DOCS :=
DOC_TARGETS :=
endif
docs: $(DOCS)
compiler-docs: $(CDOCS)
docs: $(DOC_TARGETS)
compiler-docs: $(COMPILER_DOC_TARGETS)
docs-l10n: $(DOCS_L10N)
doc/l10n/%.md: doc/po/%.md.po doc/po4a.conf
po4a --copyright-holder="The Rust Project Developers" \
--package-name="Rust" \
--package-version="$(CFG_RELEASE)" \
-M UTF-8 -L UTF-8 \
doc/po4a.conf
docs-l10n: $(DOC_L10N_TARGETS)
.PHONY: docs-l10n

View File

@ -19,12 +19,6 @@ TEST_DOC_CRATES = $(DOC_CRATES)
TEST_HOST_CRATES = $(HOST_CRATES)
TEST_CRATES = $(TEST_TARGET_CRATES) $(TEST_HOST_CRATES)
# Markdown files under doc/ that should have their code extracted and run
DOC_TEST_NAMES = tutorial guide-ffi guide-macros guide-lifetimes \
guide-tasks guide-container guide-pointers \
complement-cheatsheet guide-runtime \
rust
######################################################################
# Environment configuration
######################################################################
@ -315,10 +309,10 @@ endif
check-stage$(1)-T-$(2)-H-$(3)-doc-crates-exec: \
$$(foreach crate,$$(TEST_DOC_CRATES), \
check-stage$(1)-T-$(2)-H-$(3)-doc-$$(crate)-exec)
check-stage$(1)-T-$(2)-H-$(3)-doc-crate-$$(crate)-exec)
check-stage$(1)-T-$(2)-H-$(3)-doc-exec: \
$$(foreach docname,$$(DOC_TEST_NAMES), \
$$(foreach docname,$$(DOCS), \
check-stage$(1)-T-$(2)-H-$(3)-doc-$$(docname)-exec)
check-stage$(1)-T-$(2)-H-$(3)-pretty-exec: \
@ -662,32 +656,56 @@ $(foreach host,$(CFG_HOST), \
$(foreach pretty-name,$(PRETTY_NAMES), \
$(eval $(call DEF_RUN_PRETTY_TEST,$(stage),$(target),$(host),$(pretty-name)))))))
define DEF_RUN_DOC_TEST
DOC_TEST_ARGS$(1)-T-$(2)-H-$(3)-doc-$(4) := \
$$(CTEST_COMMON_ARGS$(1)-T-$(2)-H-$(3)) \
--src-base $(3)/test/doc-$(4)/ \
--build-base $(3)/test/doc-$(4)/ \
--mode run-pass
check-stage$(1)-T-$(2)-H-$(3)-doc-$(4)-exec: $$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4))
$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): \
$$(TEST_SREQ$(1)_T_$(2)_H_$(3)) \
doc-$(4)-extract$(3)
@$$(call E, run doc-$(4) [$(2)]: $$<)
$$(Q)$$(call CFG_RUN_CTEST_$(2),$(1),$$<,$(3)) \
$$(DOC_TEST_ARGS$(1)-T-$(2)-H-$(3)-doc-$(4)) \
--logfile $$(call TEST_LOG_FILE,$(1),$(2),$(3),doc-$(4)) \
&& touch $$@
######################################################################
# Crate & freestanding documentation tests
######################################################################
define DEF_RUSTDOC
RUSTDOC_EXE_$(1)_T_$(2)_H_$(3) := $$(HBIN$(1)_H_$(3))/rustdoc$$(X_$(3))
RUSTDOC_$(1)_T_$(2)_H_$(3) := $$(RPATH_VAR$(1)_T_$(2)_H_$(3)) $$(RUSTDOC_EXE_$(1)_T_$(2)_H_$(3))
endef
$(foreach host,$(CFG_HOST), \
$(foreach target,$(CFG_TARGET), \
$(foreach stage,$(STAGES), \
$(foreach docname,$(DOC_TEST_NAMES), \
$(eval $(call DEF_RUN_DOC_TEST,$(stage),$(target),$(host),$(docname)))))))
$(eval $(call DEF_RUSTDOC,$(stage),$(target),$(host))))))
# Freestanding
define DEF_DOC_TEST
check-stage$(1)-T-$(2)-H-$(3)-doc-$(4)-exec: $$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4))
# If NO_REBUILD is set then break the dependencies on everything but
# the source files so we can test documentation without rebuilding
# rustdoc etc.
ifeq ($(NO_REBUILD),)
DOCTESTDEP_$(1)_$(2)_$(3)_$(4) = \
$$(D)/$(4).md \
$$(TEST_SREQ$(1)_T_$(2)_H_$(3)) \
$$(RUSTDOC_EXE_$(1)_T_$(2)_H_$(3))
else
DOCTESTDEP_$(1)_$(2)_$(3)_$(4) = $$(D)/$(4).md
endif
ifeq ($(2),$$(CFG_BUILD))
$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): $$(DOCTESTDEP_$(1)_$(2)_$(3)_$(4))
@$$(call E, run doc-$(4) [$(2)])
$$(Q)$$(RUSTDOC_$(1)_T_$(2)_H_$(3)) --test $$< --test-args "$$(TESTARGS)" && touch $$@
else
$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)):
touch $$@
endif
endef
$(foreach host,$(CFG_HOST), \
$(foreach target,$(CFG_TARGET), \
$(foreach stage,$(STAGES), \
$(foreach docname,$(DOCS), \
$(eval $(call DEF_DOC_TEST,$(stage),$(target),$(host),$(docname)))))))
# Crates
define DEF_CRATE_DOC_TEST
@ -695,24 +713,24 @@ define DEF_CRATE_DOC_TEST
# the source files so we can test crate documentation without
# rebuilding any of the parent crates.
ifeq ($(NO_REBUILD),)
DOCTESTDEP_$(1)_$(2)_$(3)_$(4) = \
CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4) = \
$$(TEST_SREQ$(1)_T_$(2)_H_$(3)) \
$$(CRATE_FULLDEPS_$(1)_T_$(2)_H_$(3)_$(4)) \
$$(HBIN$(1)_H_$(3))/rustdoc$$(X_$(3))
$$(RUSTDOC_EXE_$(1)_T_$(2)_H_$(3))
else
DOCTESTDEP_$(1)_$(2)_$(3)_$(4) = $$(RSINPUTS_$(4))
CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4) = $$(RSINPUTS_$(4))
endif
check-stage$(1)-T-$(2)-H-$(3)-doc-$(4)-exec: $$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4))
check-stage$(1)-T-$(2)-H-$(3)-doc-crate-$(4)-exec: \
$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-crate-$(4))
ifeq ($(2),$$(CFG_BUILD))
$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)): $$(DOCTESTDEP_$(1)_$(2)_$(3)_$(4))
@$$(call E, run doc-$(4) [$(2)])
$$(Q)$$(RPATH_VAR$(1)_T_$(2)_H_$(3)) \
$$(HBIN$(1)_H_$(3))/rustdoc$$(X_$(3)) --test \
$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-crate-$(4)): $$(CRATEDOCTESTDEP_$(1)_$(2)_$(3)_$(4))
@$$(call E, run doc-crate-$(4) [$(2)])
$$(Q)$$(RUSTDOC_$(1)_T_$(2)_H_$(3)) --test \
$$(CRATEFILE_$(4)) --test-args "$$(TESTARGS)" && touch $$@
else
$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-$(4)):
$$(call TEST_OK_FILE,$(1),$(2),$(3),doc-crate-$(4)):
touch $$@
endif
@ -724,26 +742,6 @@ $(foreach host,$(CFG_HOST), \
$(foreach crate,$(TEST_DOC_CRATES), \
$(eval $(call DEF_CRATE_DOC_TEST,$(stage),$(target),$(host),$(crate)))))))
######################################################################
# Extracting tests for docs
######################################################################
EXTRACT_TESTS := "$(CFG_PYTHON)" $(S)src/etc/extract-tests.py
define DEF_DOC_TEST_HOST
doc-$(2)-extract$(1):
@$$(call E, extract: $(2) tests)
$$(Q)rm -f $(1)/test/doc-$(2)/*.rs
$$(Q)$$(EXTRACT_TESTS) $$(D)/$(2).md $(1)/test/doc-$(2)
endef
$(foreach host,$(CFG_HOST), \
$(foreach docname,$(DOC_TEST_NAMES), \
$(eval $(call DEF_DOC_TEST_HOST,$(host),$(docname)))))
######################################################################
# Shortcut rules
######################################################################
@ -751,7 +749,7 @@ $(foreach host,$(CFG_HOST), \
TEST_GROUPS = \
crates \
$(foreach crate,$(TEST_CRATES),$(crate)) \
$(foreach crate,$(TEST_DOC_CRATES),doc-$(crate)) \
$(foreach crate,$(TEST_DOC_CRATES),doc-crate-$(crate)) \
rpass \
rpass-full \
rfail \
@ -762,7 +760,7 @@ TEST_GROUPS = \
debuginfo \
codegen \
doc \
$(foreach docname,$(DOC_TEST_NAMES),doc-$(docname)) \
$(foreach docname,$(DOCS),doc-$(docname)) \
pretty \
pretty-rpass \
pretty-rpass-full \
@ -830,10 +828,10 @@ $(foreach stage,$(STAGES), \
$(eval $(call DEF_CHECK_FOR_STAGE_AND_HOSTS_AND_GROUP,$(stage),$(host),$(group))))))
define DEF_CHECK_DOC_FOR_STAGE
check-stage$(1)-docs: $$(foreach docname,$$(DOC_TEST_NAMES),\
check-stage$(1)-docs: $$(foreach docname,$$(DOCS),\
check-stage$(1)-T-$$(CFG_BUILD)-H-$$(CFG_BUILD)-doc-$$(docname)) \
$$(foreach crate,$$(DOC_CRATE_NAMES),\
check-stage$(1)-T-$$(CFG_BUILD)-H-$$(CFG_BUILD)-doc-$$(crate))
$$(foreach crate,$$(TEST_DOC_CRATES),\
check-stage$(1)-T-$$(CFG_BUILD)-H-$$(CFG_BUILD)-doc-crate-$$(crate))
endef
$(foreach stage,$(STAGES), \

View File

@ -135,7 +135,7 @@ For simplicity, we do not plan to do so. Implementing automatic semicolon insert
**Short answer** set the RUST_LOG environment variable to the name of your source file, sans extension.
```sh
``` {.sh .notrust}
rustc hello.rs
export RUST_LOG=hello
./hello

View File

@ -263,6 +263,7 @@ to the C library and afterwards be invoked from there.
A basic example is:
Rust code:
~~~~ {.ignore}
extern fn callback(a:i32) {
println!("I'm called from C with value {0}", a);
@ -283,7 +284,8 @@ fn main() {
~~~~
C code:
~~~~ {.ignore}
~~~~ {.notrust}
typedef void (*rust_callback)(int32_t);
rust_callback cb;
@ -314,6 +316,7 @@ the notification. This will allow the callback to unsafely access the
referenced Rust object.
Rust code:
~~~~ {.ignore}
struct RustObject {
@ -346,7 +349,8 @@ fn main() {
~~~~
C code:
~~~~ {.ignore}
~~~~ {.notrust}
typedef void (*rust_callback)(int32_t);
void* cb_target;
rust_callback cb;

View File

@ -205,7 +205,7 @@ struct X { f: int }
fn example1() {
let mut x = X { f: 3 };
let y = &mut x.f; // -+ L
... // |
// ... // |
} // -+
~~~
@ -221,7 +221,7 @@ The situation gets more complex when borrowing data inside heap boxes:
fn example2() {
let mut x = @X { f: 3 };
let y = &x.f; // -+ L
... // |
// ... // |
} // -+
~~~
@ -251,7 +251,7 @@ fn example2() {
let mut x = @X {f: 3};
let x1 = x;
let y = &x1.f; // -+ L
... // |
// ... // |
} // -+
~~~
@ -282,7 +282,7 @@ fn example3() -> int {
return *y; // |
} // -+
x = ~Foo {f: 4};
...
// ...
# return 0;
}
~~~

View File

@ -185,6 +185,7 @@ amount.
For example:
~~~
# #[allow(unused_imports)];
extern crate test;
use std::vec;
@ -201,6 +202,8 @@ fn initialise_a_vector(b: &mut BenchHarness) {
b.iter(|| {vec::from_elem(1024, 0u64);} );
b.bytes = 1024 * 8;
}
# fn main() {}
~~~
The benchmark runner will calibrate measurement of the benchmark
@ -244,6 +247,7 @@ recognize that some calculation has no external effects and remove
it entirely.
~~~
# #[allow(unused_imports)];
extern crate test;
use test::BenchHarness;
@ -253,6 +257,8 @@ fn bench_xor_1000_ints(bh: &mut BenchHarness) {
range(0, 1000).fold(0, |old, new| old ^ new);
});
}
# fn main() {}
~~~
gives the following results
@ -271,6 +277,7 @@ cannot remove the computation entirely. This could be done for the
example above by adjusting the `bh.iter` call to
~~~
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let bh = X;
bh.iter(|| range(0, 1000).fold(0, |old, new| old ^ new))
~~~
@ -281,9 +288,12 @@ forces it to consider any argument as used.
~~~
extern crate test;
# fn main() {
# struct X; impl X { fn iter<T>(&self, _: || -> T) {} } let bh = X;
bh.iter(|| {
test::black_box(range(0, 1000).fold(0, |old, new| old ^ new));
});
# }
~~~
Neither of these read or modify the value, and are very cheap for

View File

@ -25,6 +25,8 @@ li {list-style-type: none; }
* [The standard library, `std`](std/index.html)
<!-- force the two lists to be separate -->
* [The `arena` allocation library](arena/index.html)
* [The `collections` library](collections/index.html)
* [The `extra` library of extra stuff](extra/index.html)

View File

@ -142,25 +142,18 @@ pre code {
}
/* Code highlighting */
.cm-s-default span.cm-keyword {color: #8959A8;}
.cm-s-default span.cm-atom {color: #219;}
.cm-s-default span.cm-number {color: #3E999F;}
.cm-s-default span.cm-def {color: #4271AE;}
/*.cm-s-default span.cm-variable {color: #C82829;}*/
.cm-s-default span.cm-variable-2 {color: #6F906C;}
.cm-s-default span.cm-variable-3 {color: #B76514;}
.cm-s-default span.cm-property {color: black;}
.cm-s-default span.cm-operator {color: black;}
.cm-s-default span.cm-comment {color: #8E908C;}
.cm-s-default span.cm-string {color: #718C00;}
.cm-s-default span.cm-string-2 {color: #866544;}
.cm-s-default span.cm-meta {color: #555;}
/*.cm-s-default span.cm-error {color: #F00;}*/
.cm-s-default span.cm-qualifier {color: #555;}
.cm-s-default span.cm-builtin {color: #30A;}
.cm-s-default span.cm-bracket {color: #CC7;}
.cm-s-default span.cm-tag {color: #C82829;}
.cm-s-default span.cm-attribute {color: #00C;}
pre.rust .kw { color: #8959A8; }
pre.rust .kw-2, pre.rust .prelude-ty { color: #4271AE; }
pre.rust .number { color: #718C00; }
pre.rust .self { color: #C13928; }
pre.rust .boolval { color: #C13928; }
pre.rust .prelude-val { color: #C13928; }
pre.rust .comment { color: #8E908C; }
pre.rust .doccomment { color: #4D4D4C; }
pre.rust .macro, pre.rust .macro-nonterminal { color: #3E999f; }
pre.rust .string { color: #718C00; }
pre.rust .lifetime { color: #C13928; }
pre.rust .attribute, pre.rust .attribute .ident { color: #C82829; }
/* The rest
========================================================================== */

View File

@ -60,7 +60,7 @@ dialect of EBNF supported by common automated LL(k) parsing tools such as
`llgen`, rather than the dialect given in ISO 14977. The dialect can be
defined self-referentially as follows:
~~~~ {.ebnf .notation}
~~~~ {.notrust .ebnf .notation}
grammar : rule + ;
rule : nonterminal ':' productionrule ';' ;
productionrule : production [ '|' production ] * ;
@ -150,7 +150,7 @@ Some productions are defined by exclusion of particular Unicode characters:
## Comments
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
comment : block_comment | line_comment ;
block_comment : "/*" block_comment_body * '*' + '/' ;
block_comment_body : (block_comment | character) * ;
@ -171,7 +171,7 @@ Non-doc comments are interpreted as a form of whitespace.
## Whitespace
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
whitespace_char : '\x20' | '\x09' | '\x0a' | '\x0d' ;
whitespace : [ whitespace_char | comment ] + ;
~~~~
@ -188,7 +188,7 @@ with any other legal whitespace element, such as a single space character.
## Tokens
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
simple_token : keyword | unop | binop ;
token : simple_token | ident | literal | symbol | whitespace token ;
~~~~
@ -202,7 +202,7 @@ grammar as double-quoted strings. Other tokens have exact rules given.
The keywords are the following strings:
~~~~ {.keyword}
~~~~ {.notrust .keyword}
as
break
do
@ -230,13 +230,13 @@ evaluates to, rather than referring to it by name or some other evaluation
rule. A literal is a form of constant expression, so is evaluated (primarily)
at compile time.
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
literal : string_lit | char_lit | num_lit ;
~~~~
#### Character and string literals
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
char_lit : '\x27' char_body '\x27' ;
string_lit : '"' string_body * '"' | 'r' raw_string ;
@ -318,7 +318,7 @@ r##"foo #"# bar"##; // foo #"# bar
#### Number literals
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
num_lit : nonzero_dec [ dec_digit | '_' ] * num_suffix ?
| '0' [ [ dec_digit | '_' ] * num_suffix ?
| 'b' [ '1' | '0' | '_' ] + int_suffix ?
@ -416,7 +416,7 @@ The two values of the boolean type are written `true` and `false`.
### Symbols
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
symbol : "::" "->"
| '#' | '[' | ']' | '(' | ')' | '{' | '}'
| ',' | ';' ;
@ -431,7 +431,7 @@ operators](#binary-operator-expressions), or [keywords](#keywords).
## Paths
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
expr_path : ident [ "::" expr_path_tail ] + ;
expr_path_tail : '<' type_expr [ ',' type_expr ] + '>'
| expr_path ;
@ -495,7 +495,7 @@ All of the above extensions are expressions with values.
## Macros
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
expr_macro_rules : "macro_rules" '!' ident '(' macro_rule * ')'
macro_rule : '(' matcher * ')' "=>" '(' transcriber * ')' ';'
matcher : '(' matcher * ')' | '[' matcher * ']'
@ -635,7 +635,7 @@ each of which may have some number of [attributes](#attributes) attached to it.
## Items
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
item : mod_item | fn_item | type_item | struct_item | enum_item
| static_item | trait_item | impl_item | extern_block ;
~~~~
@ -683,7 +683,7 @@ That is, Rust has no notion of type abstraction: there are no first-class "foral
### Modules
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
mod_item : "mod" ident ( ';' | '{' mod '}' );
mod : [ view_item | item ] * ;
~~~~
@ -703,15 +703,15 @@ An example of a module:
mod math {
type Complex = (f64, f64);
fn sin(f: f64) -> f64 {
...
/* ... */
# fail!();
}
fn cos(f: f64) -> f64 {
...
/* ... */
# fail!();
}
fn tan(f: f64) -> f64 {
...
/* ... */
# fail!();
}
}
@ -751,7 +751,7 @@ mod task {
#### View items
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
view_item : extern_crate_decl | use_decl ;
~~~~
@ -764,7 +764,7 @@ There are several kinds of view item:
##### Extern crate declarations
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
extern_crate_decl : "extern" "crate" ident [ '(' link_attrs ')' ] ? [ '=' string_lit ] ? ;
link_attrs : link_attr [ ',' link_attrs ] + ;
link_attr : ident '=' literal ;
@ -796,7 +796,7 @@ extern crate foo = "some/where/rust-foo#foo:1.0"; // a full package ID for exter
##### Use declarations
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
use_decl : "pub" ? "use" ident [ '=' path
| "::" path_glob ] ;
@ -1205,7 +1205,7 @@ whereas `Dog` is simply called an enum variant.
### Static items
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
static_item : "static" ident ':' type '=' expr ';' ;
~~~~
@ -1447,7 +1447,7 @@ Implementation parameters are written after the `impl` keyword.
# trait Seq<T> { }
impl<T> Seq<T> for ~[T] {
...
/* ... */
}
impl Seq<bool> for u32 {
/* Treat the integer as a sequence of bits */
@ -1456,7 +1456,7 @@ impl Seq<bool> for u32 {
### External blocks
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
extern_block_item : "extern" '{' extern_block '} ;
extern_block : [ foreign_fn ] * ;
~~~~
@ -1682,7 +1682,7 @@ import public items from their destination, not private items.
## Attributes
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
attribute : '#' '[' attr_list ']' ;
attr_list : attr [ ',' attr_list ]*
attr : ident [ '=' literal
@ -2226,7 +2226,7 @@ declaring a function-local item.
#### Slot declarations
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
let_decl : "let" pat [':' type ] ? [ init ] ? ';' ;
init : [ '=' ] expr ;
~~~~
@ -2326,7 +2326,7 @@ values.
### Structure expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
struct_expr : expr_path '{' ident ':' expr
[ ',' ident ':' expr ] *
[ ".." expr ] '}' |
@ -2380,7 +2380,7 @@ Point3d {y: 0, z: 10, .. base};
### Block expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
block_expr : '{' [ view_item ] *
[ stmt ';' | item ] *
[ expr ] '}'
@ -2398,7 +2398,7 @@ of the block are that of the expression itself.
### Method-call expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
method_call_expr : expr '.' ident paren_expr_list ;
~~~~
@ -2409,7 +2409,7 @@ or dynamically dispatching if the left-hand-side expression is an indirect [obje
### Field expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
field_expr : expr '.' ident
~~~~
@ -2417,9 +2417,10 @@ A _field expression_ consists of an expression followed by a single dot and an i
when not immediately followed by a parenthesized expression-list (the latter is a [method call expression](#method-call-expressions)).
A field expression denotes a field of a [structure](#structure-types).
~~~~ {.field}
~~~~ {.ignore .field}
myrecord.myfield;
{a: 10, b: 20}.a;
foo().x;
(Struct {a: 10, b: 20}).a;
~~~~
A field access on a record is an [lvalue](#lvalues-rvalues-and-temporaries) referring to the value of that field.
@ -2430,7 +2431,7 @@ it is automatically dereferenced to make the field access possible.
### Vector expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
vec_expr : '[' "mut" ? vec_elems? ']'
vec_elems : [expr [',' expr]*] | [expr ',' ".." expr]
@ -2452,7 +2453,7 @@ as a [literal](#literals) or a [static item](#static-items).
### Index expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
idx_expr : expr '[' expr ']'
~~~~
@ -2504,7 +2505,7 @@ before the expression they apply to.
### Binary operator expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
binop_expr : expr binop expr ;
~~~~
@ -2645,7 +2646,7 @@ Any such expression always has the [`unit`](#primitive-types) type.
The precedence of Rust binary operators is ordered as follows, going
from strong to weak:
~~~~ {.precedence}
~~~~ {.notrust .precedence}
* / %
as
+ -
@ -2669,7 +2670,7 @@ An expression enclosed in parentheses evaluates to the result of the enclosed
expression. Parentheses can be used to explicitly specify evaluation order
within an expression.
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
paren_expr : '(' expr ')' ;
~~~~
@ -2682,7 +2683,7 @@ let x = (2 + 3) * 4;
### Call expressions
~~~~ {.abnf .gram}
~~~~ {.notrust .ebnf .gram}
expr_list : [ expr [ ',' expr ]* ] ? ;
paren_expr_list : '(' expr_list ')' ;
call_expr : expr paren_expr_list ;
@ -2705,7 +2706,7 @@ let pi: Option<f32> = FromStr::from_str("3.14");
### Lambda expressions
~~~~ {.abnf .gram}
~~~~ {.notrust .ebnf .gram}
ident_list : [ ident [ ',' ident ]* ] ? ;
lambda_expr : '|' ident_list '|' expr ;
~~~~
@ -2748,7 +2749,7 @@ ten_times(|j| println!("hello, {}", j));
### While loops
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
while_expr : "while" expr '{' block '}' ;
~~~~
@ -2774,7 +2775,7 @@ The keyword `loop` in Rust appears both in _loop expressions_ and in _continue e
A loop expression denotes an infinite loop;
see [Continue expressions](#continue-expressions) for continue expressions.
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
loop_expr : [ lifetime ':' ] "loop" '{' block '}';
~~~~
@ -2785,7 +2786,7 @@ See [Break expressions](#break-expressions).
### Break expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
break_expr : "break" [ lifetime ];
~~~~
@ -2798,7 +2799,7 @@ but must enclose it.
### Continue expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
continue_expr : "loop" [ lifetime ];
~~~~
@ -2817,7 +2818,7 @@ A `loop` expression is only permitted in the body of a loop.
### For expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
for_expr : "for" pat "in" expr '{' block '}' ;
~~~~
@ -2851,7 +2852,7 @@ for i in range(0u, 256) {
### If expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
if_expr : "if" expr '{' block '}'
else_tail ? ;
@ -2872,7 +2873,7 @@ then any `else` block is executed.
### Match expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
match_expr : "match" expr '{' match_arm [ '|' match_arm ] * '}' ;
match_arm : match_pat '=>' [ expr "," | '{' block '}' ] ;
@ -3063,7 +3064,7 @@ let message = match maybe_digit {
### Return expressions
~~~~ {.ebnf .gram}
~~~~ {.notrust .ebnf .gram}
return_expr : "return" expr ? ;
~~~~
@ -3895,7 +3896,7 @@ fn main() {
These four log levels correspond to levels 1-4, as controlled by `RUST_LOG`:
```bash
``` {.bash .notrust}
$ RUST_LOG=rust=3 ./rust
This is an error log
This is a warn log

View File

@ -20,6 +20,7 @@ comments":
//! the crate index page. The ! makes it apply to the parent of the comment,
//! rather than what follows).
# mod workaround_the_outer_function_rustdoc_inserts {
/// Widgets are very common (this is a doc comment, and will show up on
/// Widget's documentation).
pub struct Widget {
@ -36,6 +37,7 @@ pub fn recalibrate() {
//! `recalibrate`).
/* ... */
}
# }
~~~
Doc comments are markdown, and are currently parsed with the
@ -94,7 +96,7 @@ source code.
To test documentation, the `--test` argument is passed to rustdoc:
~~~
~~~ {.notrust}
rustdoc --test crate.rs
~~~
@ -105,35 +107,44 @@ code blocks as testable-by-default. In order to not run a test over a block of
code, the `ignore` string can be added to the three-backtick form of markdown
code block.
~~~
```
// This is a testable code block
```
/**
# nested codefences confuse sundown => indentation + comment to
# avoid failing tests
```
// This is a testable code block
```
```ignore
// This is not a testable code block
```
```ignore
// This is not a testable code block
```
// This is a testable code block (4-space indent)
~~~
// This is a testable code block (4-space indent)
*/
# fn foo() {}
You can specify that the test's execution should fail with the `should_fail`
directive.
~~~
```should_fail
// This code block is expected to generate a failure when run
```
~~~
/**
# nested codefences confuse sundown => indentation + comment to
# avoid failing tests
```should_fail
// This code block is expected to generate a failure when run
```
*/
# fn foo() {}
You can specify that the code block should be compiled but not run with the
`no_run` directive.
~~~
```no_run
// This code will be compiled but not executed
```
~~~
/**
# nested codefences confuse sundown => indentation + comment to
# avoid failing tests
```no_run
// This code will be compiled but not executed
```
*/
# fn foo() {}
Rustdoc also supplies some extra sugar for helping with some tedious
documentation examples. If a line is prefixed with `# `, then the line
@ -141,20 +152,23 @@ will not show up in the HTML documentation, but it will be used when
testing the code block (NB. the space after the `#` is required, so
that one can still write things like `#[deriving(Eq)]`).
~~~
```rust
# /!\ The three following lines are comments, which are usually stripped off by
# the doc-generating tool. In order to display them anyway in this particular
# case, the character following the leading '#' is not a usual space like in
# these first five lines but a non breakable one.
#
# // showing 'fib' in this documentation would just be tedious and detracts from
# // what's actualy being documented.
# fn fib(n: int) { n + 2 }
/**
# nested codefences confuse sundown => indentation + comment to
# avoid failing tests
```rust
# /!\ The three following lines are comments, which are usually stripped off by
# the doc-generating tool. In order to display them anyway in this particular
# case, the character following the leading '#' is not a usual space like in
# these first five lines but a non breakable one.
#
# // showing 'fib' in this documentation would just be tedious and detracts from
# // what's actualy being documented.
# fn fib(n: int) { n + 2 }
do spawn { fib(200); }
```
~~~
do spawn { fib(200); }
```
*/
# fn foo() {}
The documentation online would look like `do spawn { fib(200); }`, but when
testing this code, the `fib` function will be included (so it can compile).
@ -167,12 +181,12 @@ uses is build on crate `test`, which is also used when you compile crates with
rustc's `--test` flag. Extra arguments can be passed to rustdoc's test harness
with the `--test-args` flag.
~~~
// Only run tests containing 'foo' in their name
rustdoc --test lib.rs --test-args 'foo'
~~~ {.notrust}
$ # Only run tests containing 'foo' in their name
$ rustdoc --test lib.rs --test-args 'foo'
// See what's possible when running tests
rustdoc --test lib.rs --test-args '--help'
$ # See what's possible when running tests
$ rustdoc --test lib.rs --test-args '--help'
~~~
When testing a library, code examples will often show how functions are used,
@ -181,3 +195,29 @@ rustdoc will implicitly add `extern crate <crate>;` where `<crate>` is the name
the crate being tested to the top of each code example. This means that rustdoc
must be able to find a compiled version of the library crate being tested. Extra
search paths may be added via the `-L` flag to `rustdoc`.
# Standalone Markdown files
As well as Rust crates, rustdoc supports rendering pure Markdown files
into HTML and testing the code snippets from them. A Markdown file is
detected by a `.md` or `.markdown` extension.
There are 4 options to modify the output that Rustdoc creates.
- `--markdown-css PATH`: adds a `<link rel="stylesheet">` tag pointing to `PATH`.
- `--markdown-in-header FILE`: includes the contents of `FILE` at the
end of the `<head>...</head>` section.
- `--markdown-before-content FILE`: includes the contents of `FILE`
directly after `<body>`, before the rendered content (including the
title).
- `--markdown-after-content FILE`: includes the contents of `FILE`
directly before `</body>`, after all the rendered content.
All of these can be specified multiple times, and they are output in
the order in which they are specified. The first line of the file must
be the title, prefixed with `%` (e.g. this page has `% Rust
Documentation` on the first line).
Like with a Rust crate, the `--test` argument will run the code
examples to check they compile, and obeys any `--test-args` flags. The
tests are named after the last `#` heading.

View File

@ -1058,10 +1058,12 @@ being destroyed along with the owner. Since the `list` variable above is
immutable, the whole list is immutable. The memory allocation itself is the
box, while the owner holds onto a pointer to it:
List box List box List box List box
+--------------+ +--------------+ +--------------+ +--------------+
list -> | Cons | 1 | ~ | -> | Cons | 2 | ~ | -> | Cons | 3 | ~ | -> | Nil |
+--------------+ +--------------+ +--------------+ +--------------+
~~~ {.notrust}
List box List box List box List box
+--------------+ +--------------+ +--------------+ +--------------+
list -> | Cons | 1 | ~ | -> | Cons | 2 | ~ | -> | Cons | 3 | ~ | -> | Nil |
+--------------+ +--------------+ +--------------+ +--------------+
~~~
> ***Note:*** the above diagram shows the logical contents of the enum. The actual
> memory layout of the enum may vary. For example, for the `List` enum shown
@ -1173,7 +1175,7 @@ ownership of a list to be passed in rather than just mutating it in-place.
The obvious signature for a `List` equality comparison is the following:
~~~{.ignore}
fn eq(xs: List, ys: List) -> bool { ... }
fn eq(xs: List, ys: List) -> bool { /* ... */ }
~~~
However, this will cause both lists to be moved into the function. Ownership
@ -1181,7 +1183,7 @@ isn't required to compare the lists, so the function should take *references*
(&T) instead.
~~~{.ignore}
fn eq(xs: &List, ys: &List) -> bool { ... }
fn eq(xs: &List, ys: &List) -> bool { /* ... */ }
~~~
A reference is a *non-owning* view of a value. A reference can be obtained with the `&` (address-of)
@ -1881,9 +1883,9 @@ A caller must in turn have a compatible pointer type to call the method.
# Rectangle(Point, Point)
# }
impl Shape {
fn draw_reference(&self) { ... }
fn draw_owned(~self) { ... }
fn draw_value(self) { ... }
fn draw_reference(&self) { /* ... */ }
fn draw_owned(~self) { /* ... */ }
fn draw_value(self) { /* ... */ }
}
let s = Circle(Point { x: 1.0, y: 2.0 }, 3.0);
@ -1906,9 +1908,9 @@ to a reference.
# Rectangle(Point, Point)
# }
# impl Shape {
# fn draw_reference(&self) { ... }
# fn draw_owned(~self) { ... }
# fn draw_value(self) { ... }
# fn draw_reference(&self) { /* ... */ }
# fn draw_owned(~self) { /* ... */ }
# fn draw_value(self) { /* ... */ }
# }
# let s = Circle(Point { x: 1.0, y: 2.0 }, 3.0);
// As with typical function arguments, managed and owned pointers
@ -1934,8 +1936,8 @@ These methods are the preferred way to define constructor functions.
~~~~ {.ignore}
impl Circle {
fn area(&self) -> f64 { ... }
fn new(area: f64) -> Circle { ... }
fn area(&self) -> f64 { /* ... */ }
fn new(area: f64) -> Circle { /* ... */ }
}
~~~~
@ -2395,8 +2397,8 @@ to an object:
# fn new_rectangle() -> Rectangle { true }
# fn draw_all(shapes: &[~Drawable]) {}
impl Drawable for Circle { fn draw(&self) { ... } }
impl Drawable for Rectangle { fn draw(&self) { ... } }
impl Drawable for Circle { fn draw(&self) { /* ... */ } }
impl Drawable for Rectangle { fn draw(&self) { /* ... */ } }
let c: ~Circle = ~new_circle();
let r: ~Rectangle = ~new_rectangle();
@ -2510,7 +2512,7 @@ use std::f64::consts::PI;
# impl Circle for CircleStruct { fn radius(&self) -> f64 { (self.area() / PI).sqrt() } }
# impl Shape for CircleStruct { fn area(&self) -> f64 { PI * square(self.radius) } }
let concrete = @CircleStruct{center:Point{x:3f,y:4f},radius:5f};
let concrete = @CircleStruct{center:Point{x:3.0,y:4.0},radius:5.0};
let mycircle: @Circle = concrete as @Circle;
let nonsense = mycircle.radius() * mycircle.area();
~~~
@ -2667,8 +2669,8 @@ mod farm {
}
impl Farm {
fn feed_chickens(&self) { ... }
pub fn add_chicken(&self, c: Chicken) { ... }
fn feed_chickens(&self) { /* ... */ }
pub fn add_chicken(&self, c: Chicken) { /* ... */ }
}
pub fn feed_animals(farm: &Farm) {
@ -3144,9 +3146,10 @@ We define two crates, and use one of them as a library in the other.
~~~~
// `world.rs`
#[crate_id = "world#0.42"];
# extern crate extra;
# mod secret_module_to_make_this_test_run {
pub fn explore() -> &'static str { "world" }
# fn main() {}
# }
~~~~
~~~~ {.ignore}

View File

@ -1,217 +0,0 @@
# Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution and at
# http://rust-lang.org/COPYRIGHT.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
"""
Script for extracting compilable fragments from markdown documentation. See
prep.js for a description of the format recognized by this tool. Expects
a directory fragments/ to exist under the current directory, and writes the
fragments in there as individual .rs files.
"""
from __future__ import print_function
from codecs import open
from collections import deque
from itertools import imap
import os
import re
import sys
# regexes
CHAPTER_NAME_REGEX = re.compile(r'# (.*)')
CODE_BLOCK_DELIM_REGEX = re.compile(r'~~~')
COMMENT_REGEX = re.compile(r'^# ')
COMPILER_DIRECTIVE_REGEX = re.compile(r'\#\[(.*)\];')
ELLIPSES_REGEX = re.compile(r'\.\.\.')
EXTERN_CRATE_REGEX = re.compile(r'\bextern crate extra\b')
MAIN_FUNCTION_REGEX = re.compile(r'\bfn main\b')
TAGS_REGEX = re.compile(r'\.([\w-]*)')
# tags to ignore
IGNORE_TAGS = \
frozenset(["abnf", "ebnf", "field", "keyword", "notrust", "precedence"])
# header for code snippet files
OUTPUT_BLOCK_HEADER = '\n'.join((
"#[ deny(warnings) ];",
"#[ allow(unused_variable) ];",
"#[ allow(dead_assignment) ];",
"#[ allow(unused_mut) ];",
"#[ allow(attribute_usage) ];",
"#[ allow(dead_code) ];",
"#[ feature(macro_rules, globs, struct_variant, managed_boxes) ];\n",))
def add_extern_mod(block):
if not has_extern_mod(block):
# add `extern crate extra;` after compiler directives
directives = []
while len(block) and is_compiler_directive(block[0]):
directives.append(block.popleft())
block.appendleft("\nextern crate extra;\n\n")
block.extendleft(reversed(directives))
return block
def add_main_function(block):
if not has_main_function(block):
prepend_spaces = lambda x: ' ' + x
block = deque(imap(prepend_spaces, block))
block.appendleft("\nfn main() {\n")
block.append("\n}\n")
return block
def extract_code_fragments(dest_dir, lines):
"""
Extracts all the code fragments from a file that do not have ignored tags
writing them to the following file:
[dest dir]/[chapter name]_[chapter_index].rs
"""
chapter_name = None
chapter_index = 0
for line in lines:
if is_chapter_title(line):
chapter_name = get_chapter_name(line)
chapter_index = 1
continue
if not is_code_block_delim(line):
continue
assert chapter_name, "Chapter name missing for code block."
tags = get_tags(line)
block = get_code_block(lines)
if tags & IGNORE_TAGS:
continue
block = add_extern_mod(add_main_function(block))
block.appendleft(OUTPUT_BLOCK_HEADER)
if "ignore" in tags:
block.appendleft("//ignore-test\n")
elif "should_fail" in tags:
block.appendleft("//should-fail\n")
output_filename = os.path.join(
dest_dir,
chapter_name + '_' + str(chapter_index) + '.rs')
write_file(output_filename, block)
chapter_index += 1
def has_extern_mod(block):
"""Checks if a code block has the line `extern crate extra`."""
find_extern_mod = lambda x: re.search(EXTERN_CRATE_REGEX, x)
return any(imap(find_extern_mod, block))
def has_main_function(block):
"""Checks if a code block has a main function."""
find_main_fn = lambda x: re.search(MAIN_FUNCTION_REGEX, x)
return any(imap(find_main_fn, block))
def is_chapter_title(line):
return re.match(CHAPTER_NAME_REGEX, line)
def is_code_block_delim(line):
return re.match(CODE_BLOCK_DELIM_REGEX, line)
def is_compiler_directive(line):
return re.match(COMPILER_DIRECTIVE_REGEX, line)
def get_chapter_name(line):
"""Get the chapter name from a `# Containers` line."""
return re.sub(
r'\W',
'_',
re.match(CHAPTER_NAME_REGEX, line).group(1)).lower()
def get_code_block(lines):
"""
Get a code block surrounded by ~~~, for example:
1: ~~~ { .tag }
2: let u: ~[u32] = ~[0, 1, 2];
3: let v: &[u32] = &[0, 1, 2, 3];
4: let w: [u32, .. 5] = [0, 1, 2, 3, 4];
5:
6: println!("u: {}, v: {}, w: {}", u.len(), v.len(), w.len());
7: ~~~
Returns lines 2-6. Assumes line 1 has been consumed by the caller.
"""
strip_comments = lambda x: re.sub(COMMENT_REGEX, '', x)
strip_ellipses = lambda x: re.sub(ELLIPSES_REGEX, '', x)
result = deque()
for line in lines:
if is_code_block_delim(line):
break
result.append(strip_comments(strip_ellipses(line)))
return result
def get_lines(filename):
with open(filename) as f:
for line in f:
yield line
def get_tags(line):
"""
Retrieves all tags from the line format:
~~~ { .tag1 .tag2 .tag3 }
"""
return set(re.findall(TAGS_REGEX, line))
def write_file(filename, lines):
with open(filename, 'w', encoding='utf-8') as f:
for line in lines:
f.write(unicode(line, encoding='utf-8', errors='replace'))
def main(argv=None):
if not argv:
argv = sys.argv
if len(sys.argv) < 2:
sys.stderr.write("Please provide an input filename.")
sys.exit(1)
elif len(sys.argv) < 3:
sys.stderr.write("Please provide a destination directory.")
sys.exit(1)
input_file = sys.argv[1]
dest_dir = sys.argv[2]
if not os.path.exists(input_file):
sys.stderr.write("Input file does not exist.")
sys.exit(1)
if not os.path.exists(dest_dir):
os.mkdir(dest_dir)
extract_code_fragments(dest_dir, get_lines(input_file))
if __name__ == "__main__":
sys.exit(main())

View File

@ -28,7 +28,6 @@
use std::cast;
use std::fmt;
use std::intrinsics;
use std::io;
use std::libc;
use std::local_data;
@ -37,12 +36,16 @@ use std::str;
use std::vec;
use collections::HashMap;
use html::toc::TocBuilder;
use html::highlight;
/// A unit struct which has the `fmt::Show` trait implemented. When
/// formatted, this struct will emit the HTML corresponding to the rendered
/// version of the contained markdown string.
pub struct Markdown<'a>(&'a str);
/// A unit struct like `Markdown`, that renders the markdown with a
/// table of contents.
pub struct MarkdownWithToc<'a>(&'a str);
static OUTPUT_UNIT: libc::size_t = 64;
static MKDEXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 0;
@ -76,6 +79,7 @@ struct html_renderopt {
struct my_opaque {
opt: html_renderopt,
dfltblk: extern "C" fn(*buf, *buf, *buf, *libc::c_void),
toc_builder: Option<TocBuilder>,
}
struct buf {
@ -122,7 +126,7 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
local_data_key!(used_header_map: HashMap<~str, uint>)
pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
pub fn render(w: &mut io::Writer, s: &str, print_toc: bool) -> fmt::Result {
extern fn block(ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) {
unsafe {
let my_opaque: &my_opaque = cast::transmute(opaque);
@ -163,7 +167,7 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
}
extern fn header(ob: *buf, text: *buf, level: libc::c_int,
_opaque: *libc::c_void) {
opaque: *libc::c_void) {
// sundown does this, we may as well too
"\n".with_c_str(|p| unsafe { bufputs(ob, p) });
@ -184,6 +188,8 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
}
}).to_owned_vec().connect("-");
let opaque = unsafe {&mut *(opaque as *mut my_opaque)};
// Make sure our hyphenated ID is unique for this page
let id = local_data::get_mut(used_header_map, |map| {
let map = map.unwrap();
@ -195,9 +201,19 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
id.clone()
});
let sec = match opaque.toc_builder {
Some(ref mut builder) => {
builder.push(level as u32, s.clone(), id.clone())
}
None => {""}
};
// Render the HTML
let text = format!(r#"<h{lvl} id="{id}">{}</h{lvl}>"#,
s, lvl = level, id = id);
let text = format!(r#"<h{lvl} id="{id}"><a
href="\#{id}">{sec_len,plural,=0{}other{{sec} }}{}</a></h{lvl}>"#,
s, lvl = level, id = id,
sec_len = sec.len(), sec = sec);
text.with_c_str(|p| unsafe { bufputs(ob, p) });
}
@ -219,23 +235,30 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
let mut callbacks: sd_callbacks = mem::init();
sdhtml_renderer(&callbacks, &options, 0);
let opaque = my_opaque {
let mut opaque = my_opaque {
opt: options,
dfltblk: callbacks.blockcode.unwrap(),
toc_builder: if print_toc {Some(TocBuilder::new())} else {None}
};
callbacks.blockcode = Some(block);
callbacks.header = Some(header);
let markdown = sd_markdown_new(extensions, 16, &callbacks,
&opaque as *my_opaque as *libc::c_void);
&mut opaque as *mut my_opaque as *libc::c_void);
sd_markdown_render(ob, s.as_ptr(), s.len() as libc::size_t, markdown);
sd_markdown_free(markdown);
let ret = vec::raw::buf_as_slice((*ob).data, (*ob).size as uint, |buf| {
w.write(buf)
});
let mut ret = match opaque.toc_builder {
Some(b) => write!(w, "<nav id=\"TOC\">{}</nav>", b.into_toc()),
None => Ok(())
};
if ret.is_ok() {
ret = vec::raw::buf_as_slice((*ob).data, (*ob).size as uint, |buf| {
w.write(buf)
});
}
bufrelease(ob);
ret
}
@ -258,7 +281,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
};
if ignore { return }
vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
let tests: &mut ::test::Collector = intrinsics::transmute(opaque);
let tests = &mut *(opaque as *mut ::test::Collector);
let text = str::from_utf8(text).unwrap();
let mut lines = text.lines().map(|l| stripped_filtered_line(l).unwrap_or(l));
let text = lines.to_owned_vec().connect("\n");
@ -266,6 +289,19 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
})
}
}
extern fn header(_ob: *buf, text: *buf, level: libc::c_int, opaque: *libc::c_void) {
unsafe {
let tests = &mut *(opaque as *mut ::test::Collector);
if text.is_null() {
tests.register_header("", level as u32);
} else {
vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
let text = str::from_utf8(text).unwrap();
tests.register_header(text, level as u32);
})
}
}
}
unsafe {
let ob = bufnew(OUTPUT_UNIT);
@ -276,7 +312,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
blockcode: Some(block),
blockquote: None,
blockhtml: None,
header: None,
header: Some(header),
other: mem::init()
};
@ -307,6 +343,13 @@ impl<'a> fmt::Show for Markdown<'a> {
let Markdown(md) = *self;
// This is actually common enough to special-case
if md.len() == 0 { return Ok(()) }
render(fmt.buf, md.as_slice())
render(fmt.buf, md.as_slice(), false)
}
}
impl<'a> fmt::Show for MarkdownWithToc<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let MarkdownWithToc(md) = *self;
render(fmt.buf, md.as_slice(), true)
}
}

View File

@ -1064,7 +1064,8 @@ fn item_module(w: &mut Writer, cx: &Context,
clean::ForeignStaticItem(..) => ("ffi-statics", "Foreign Statics"),
clean::MacroItem(..) => ("macros", "Macros"),
};
try!(write!(w, "<h2 id='{}'>{}</h2>\n<table>", short, name));
try!(write!(w, "<h2 id='{id}'><a href=\"\\#{id}\">{name}</a></h2>\n<table>",
id = short, name = name));
}
match myitem.inner {

View File

@ -320,9 +320,16 @@ pre.rust .string { color: #718C00; }
pre.rust .lifetime { color: #C13928; }
pre.rust .attribute, pre.rust .attribute .ident { color: #C82829; }
h1 a.anchor,
h2 a.anchor,
h3 a.anchor { display: none; }
h1:hover a.anchor,
h2:hover a.anchor,
h3:hover a.anchor { display: inline-block; }
h1:hover a:after,
h2:hover a:after,
h3:hover a:after,
h4:hover a:after,
h5:hover a:after,
h6:hover a:after {
content: ' § ';
}
h1.fqn:hover a:after,
:hover a.fnname:after {
content: none;
}

View File

@ -600,10 +600,4 @@
initSearch(searchIndex);
$.each($('h1, h2, h3'), function(idx, element) {
if ($(element).attr('id') != undefined) {
$(element).append('<a href="#' + $(element).attr('id') + '" ' +
'class="anchor"> § </a>');
}
});
}());

269
src/librustdoc/html/toc.rs Normal file
View File

@ -0,0 +1,269 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Table-of-contents creation.
use std::fmt;
use std::vec_ng::Vec;
/// A (recursive) table of contents
#[deriving(Eq)]
pub struct Toc {
/// The levels are strictly decreasing, i.e.
///
/// entries[0].level >= entries[1].level >= ...
///
/// Normally they are equal, but can differ in cases like A and B,
/// both of which end up in the same `Toc` as they have the same
/// parent (Main).
///
/// # Main
/// ### A
/// ## B
priv entries: Vec<TocEntry>
}
impl Toc {
fn count_entries_with_level(&self, level: u32) -> uint {
self.entries.iter().count(|e| e.level == level)
}
}
#[deriving(Eq)]
pub struct TocEntry {
priv level: u32,
priv sec_number: ~str,
priv name: ~str,
priv id: ~str,
priv children: Toc,
}
/// Progressive construction of a table of contents.
#[deriving(Eq)]
pub struct TocBuilder {
priv top_level: Toc,
/// The current heirachy of parent headings, the levels are
/// strictly increasing (i.e. chain[0].level < chain[1].level <
/// ...) with each entry being the most recent occurance of a
/// heading with that level (it doesn't include the most recent
/// occurences of every level, just, if *is* in `chain` then is is
/// the most recent one).
///
/// We also have `chain[0].level <= top_level.entries[last]`.
priv chain: Vec<TocEntry>
}
impl TocBuilder {
pub fn new() -> TocBuilder {
TocBuilder { top_level: Toc { entries: Vec::new() }, chain: Vec::new() }
}
/// Convert into a true `Toc` struct.
pub fn into_toc(mut self) -> Toc {
// we know all levels are >= 1.
self.fold_until(0);
self.top_level
}
/// Collapse the chain until the first heading more important than
/// `level` (i.e. lower level)
///
/// Example:
///
/// ## A
/// # B
/// # C
/// ## D
/// ## E
/// ### F
/// #### G
/// ### H
///
/// If we are considering H (i.e. level 3), then A and B are in
/// self.top_level, D is in C.children, and C, E, F, G are in
/// self.chain.
///
/// When we attempt to push H, we realise that first G is not the
/// parent (level is too high) so it is popped from chain and put
/// into F.children, then F isn't the parent (level is equal, aka
/// sibling), so it's also popped and put into E.children.
///
/// This leaves us looking at E, which does have a smaller level,
/// and, by construction, it's the most recent thing with smaller
/// level, i.e. it's the immediate parent of H.
fn fold_until(&mut self, level: u32) {
let mut this = None;
loop {
match self.chain.pop() {
Some(mut next) => {
this.map(|e| next.children.entries.push(e));
if next.level < level {
// this is the parent we want, so return it to
// its rightful place.
self.chain.push(next);
return
} else {
this = Some(next);
}
}
None => {
this.map(|e| self.top_level.entries.push(e));
return
}
}
}
}
/// Push a level `level` heading into the appropriate place in the
/// heirarchy, returning a string containing the section number in
/// `<num>.<num>.<num>` format.
pub fn push<'a>(&'a mut self, level: u32, name: ~str, id: ~str) -> &'a str {
assert!(level >= 1);
// collapse all previous sections into their parents until we
// get to relevant heading (i.e. the first one with a smaller
// level than us)
self.fold_until(level);
let mut sec_number;
{
let (toc_level, toc) = match self.chain.last() {
None => {
sec_number = ~"";
(0, &self.top_level)
}
Some(entry) => {
sec_number = entry.sec_number.clone();
sec_number.push_str(".");
(entry.level, &entry.children)
}
};
// fill in any missing zeros, e.g. for
// # Foo (1)
// ### Bar (1.0.1)
for _ in range(toc_level, level - 1) {
sec_number.push_str("0.");
}
let number = toc.count_entries_with_level(level);
sec_number.push_str(format!("{}", number + 1))
}
self.chain.push(TocEntry {
level: level,
name: name,
sec_number: sec_number,
id: id,
children: Toc { entries: Vec::new() }
});
// get the thing we just pushed, so we can borrow the string
// out of it with the right lifetime
let just_inserted = self.chain.mut_last().unwrap();
just_inserted.sec_number.as_slice()
}
}
impl fmt::Show for Toc {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
try!(write!(fmt.buf, "<ul>"));
for entry in self.entries.iter() {
// recursively format this table of contents (the
// `{children}` is the key).
try!(write!(fmt.buf,
"\n<li><a href=\"\\#{id}\">{num} {name}</a>{children}</li>",
id = entry.id,
num = entry.sec_number, name = entry.name,
children = entry.children))
}
write!(fmt.buf, "</ul>")
}
}
#[cfg(test)]
mod test {
use super::{TocBuilder, Toc, TocEntry};
#[test]
fn builder_smoke() {
let mut builder = TocBuilder::new();
// this is purposely not using a fancy macro like below so
// that we're sure that this is doing the correct thing, and
// there's been no macro mistake.
macro_rules! push {
($level: expr, $name: expr) => {
assert_eq!(builder.push($level, $name.to_owned(), ~""), $name);
}
}
push!(2, "0.1");
push!(1, "1");
{
push!(2, "1.1");
{
push!(3, "1.1.1");
push!(3, "1.1.2");
}
push!(2, "1.2");
{
push!(3, "1.2.1");
push!(3, "1.2.2");
}
}
push!(1, "2");
push!(1, "3");
{
push!(4, "3.0.0.1");
{
push!(6, "3.0.0.1.0.1");
}
push!(4, "3.0.0.2");
push!(2, "3.1");
{
push!(4, "3.1.0.1");
}
}
macro_rules! toc {
($(($level: expr, $name: expr, $(($sub: tt))* )),*) => {
Toc {
entries: vec!(
$(
TocEntry {
level: $level,
name: $name.to_owned(),
sec_number: $name.to_owned(),
id: ~"",
children: toc!($($sub),*)
}
),*
)
}
}
}
let expected = toc!(
(2, "0.1", ),
(1, "1",
((2, "1.1", ((3, "1.1.1", )) ((3, "1.1.2", ))))
((2, "1.2", ((3, "1.2.1", )) ((3, "1.2.2", ))))
),
(1, "2", ),
(1, "3",
((4, "3.0.0.1", ((6, "3.0.0.1.0.1", ))))
((4, "3.0.0.2", ))
((2, "3.1", ((4, "3.1.0.1", ))))
)
);
assert_eq!(expected, builder.into_toc());
}
}

View File

@ -14,7 +14,7 @@
#[crate_type = "dylib"];
#[crate_type = "rlib"];
#[feature(globs, struct_variant, managed_boxes)];
#[feature(globs, struct_variant, managed_boxes, macro_rules)];
extern crate syntax;
extern crate rustc;
@ -26,6 +26,7 @@ extern crate collections;
extern crate testing = "test";
extern crate time;
use std::cell::RefCell;
use std::local_data;
use std::io;
use std::io::{File, MemWriter};
@ -43,7 +44,9 @@ pub mod html {
pub mod layout;
pub mod markdown;
pub mod render;
pub mod toc;
}
pub mod markdown;
pub mod passes;
pub mod plugins;
pub mod visit_ast;
@ -105,6 +108,19 @@ pub fn opts() -> ~[getopts::OptGroup] {
optflag("", "test", "run code examples as tests"),
optmulti("", "test-args", "arguments to pass to the test runner",
"ARGS"),
optmulti("", "markdown-css", "CSS files to include via <link> in a rendered Markdown file",
"FILES"),
optmulti("", "markdown-in-header",
"files to include inline in the <head> section of a rendered Markdown file",
"FILES"),
optmulti("", "markdown-before-content",
"files to include inline between <body> and the content of a rendered \
Markdown file",
"FILES"),
optmulti("", "markdown-after-content",
"files to include inline between the content and </body> of a rendered \
Markdown file",
"FILES"),
]
}
@ -137,8 +153,24 @@ pub fn main_args(args: &[~str]) -> int {
}
let input = matches.free[0].as_slice();
if matches.opt_present("test") {
return test::run(input, &matches);
let libs = matches.opt_strs("L").map(|s| Path::new(s.as_slice()));
let libs = @RefCell::new(libs.move_iter().collect());
let test_args = matches.opt_strs("test-args");
let test_args = test_args.iter().flat_map(|s| s.words()).map(|s| s.to_owned()).to_owned_vec();
let should_test = matches.opt_present("test");
let markdown_input = input.ends_with(".md") || input.ends_with(".markdown");
let output = matches.opt_str("o").map(|s| Path::new(s));
match (should_test, markdown_input) {
(true, true) => return markdown::test(input, libs, test_args),
(true, false) => return test::run(input, libs, test_args),
(false, true) => return markdown::render(input, output.unwrap_or(Path::new("doc")),
&matches),
(false, false) => {}
}
if matches.opt_strs("passes") == ~[~"list"] {
@ -163,7 +195,6 @@ pub fn main_args(args: &[~str]) -> int {
info!("going to format");
let started = time::precise_time_ns();
let output = matches.opt_str("o").map(|s| Path::new(s));
match matches.opt_str("w") {
Some(~"html") | None => {
match html::render::run(krate, output.unwrap_or(Path::new("doc"))) {

171
src/librustdoc/markdown.rs Normal file
View File

@ -0,0 +1,171 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::{str, io};
use std::cell::RefCell;
use std::vec_ng::Vec;
use collections::HashSet;
use getopts;
use testing;
use html::escape::Escape;
use html::markdown::{MarkdownWithToc, find_testable_code, reset_headers};
use test::Collector;
fn load_string(input: &Path) -> io::IoResult<Option<~str>> {
let mut f = try!(io::File::open(input));
let d = try!(f.read_to_end());
Ok(str::from_utf8_owned(d))
}
macro_rules! load_or_return {
($input: expr, $cant_read: expr, $not_utf8: expr) => {
{
let input = Path::new($input);
match load_string(&input) {
Err(e) => {
let _ = writeln!(&mut io::stderr(),
"error reading `{}`: {}", input.display(), e);
return $cant_read;
}
Ok(None) => {
let _ = writeln!(&mut io::stderr(),
"error reading `{}`: not UTF-8", input.display());
return $not_utf8;
}
Ok(Some(s)) => s
}
}
}
}
/// Separate any lines at the start of the file that begin with `%`.
fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) {
let mut metadata = Vec::new();
for line in s.lines() {
if line.starts_with("%") {
// remove %<whitespace>
metadata.push(line.slice_from(1).trim_left())
} else {
let line_start_byte = s.subslice_offset(line);
return (metadata, s.slice_from(line_start_byte));
}
}
// if we're here, then all lines were metadata % lines.
(metadata, "")
}
fn load_external_files(names: &[~str]) -> Option<~str> {
let mut out = ~"";
for name in names.iter() {
out.push_str(load_or_return!(name.as_slice(), None, None));
out.push_char('\n');
}
Some(out)
}
/// Render `input` (e.g. "foo.md") into an HTML file in `output`
/// (e.g. output = "bar" => "bar/foo.html").
pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int {
let input_p = Path::new(input);
output.push(input_p.filestem().unwrap());
output.set_extension("html");
let mut css = ~"";
for name in matches.opt_strs("markdown-css").iter() {
let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
css.push_str(s)
}
let input_str = load_or_return!(input, 1, 2);
let (in_header, before_content, after_content) =
match (load_external_files(matches.opt_strs("markdown-in-header")),
load_external_files(matches.opt_strs("markdown-before-content")),
load_external_files(matches.opt_strs("markdown-after-content"))) {
(Some(a), Some(b), Some(c)) => (a,b,c),
_ => return 3
};
let mut out = match io::File::create(&output) {
Err(e) => {
let _ = writeln!(&mut io::stderr(),
"error opening `{}` for writing: {}",
output.display(), e);
return 4;
}
Ok(f) => f
};
let (metadata, text) = extract_leading_metadata(input_str);
if metadata.len() == 0 {
let _ = writeln!(&mut io::stderr(),
"invalid markdown file: expecting initial line with `% ...TITLE...`");
return 5;
}
let title = metadata.get(0).as_slice();
reset_headers();
let err = write!(
&mut out,
r#"<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="generator" content="rustdoc">
<title>{title}</title>
{css}
{in_header}
</head>
<body>
<!--[if lte IE 8]>
<div class="warning">
This old browser is unsupported and will most likely display funky
things.
</div>
<![endif]-->
{before_content}
<h1 class="title">{title}</h1>
{text}
{after_content}
</body>
</html>"#,
title = Escape(title),
css = css,
in_header = in_header,
before_content = before_content,
text = MarkdownWithToc(text),
after_content = after_content);
match err {
Err(e) => {
let _ = writeln!(&mut io::stderr(),
"error writing to `{}`: {}",
output.display(), e);
6
}
Ok(_) => 0
}
}
/// Run any tests/code examples in the markdown file `input`.
pub fn test(input: &str, libs: @RefCell<HashSet<Path>>, mut test_args: ~[~str]) -> int {
let input_str = load_or_return!(input, 1, 2);
let mut collector = Collector::new(input.to_owned(), libs, true, true);
find_testable_code(input_str, &mut collector);
test_args.unshift(~"rustdoctest");
testing::test_main(test_args, collector.tests);
0
}

View File

@ -9,6 +9,7 @@
// except according to those terms.
use std::cell::RefCell;
use std::char;
use std::io;
use std::io::Process;
use std::local_data;
@ -22,7 +23,6 @@ use rustc::back::link;
use rustc::driver::driver;
use rustc::driver::session;
use rustc::metadata::creader::Loader;
use getopts;
use syntax::diagnostic;
use syntax::parse;
use syntax::codemap::CodeMap;
@ -35,11 +35,9 @@ use html::markdown;
use passes;
use visit_ast::RustdocVisitor;
pub fn run(input: &str, matches: &getopts::Matches) -> int {
pub fn run(input: &str, libs: @RefCell<HashSet<Path>>, mut test_args: ~[~str]) -> int {
let input_path = Path::new(input);
let input = driver::FileInput(input_path.clone());
let libs = matches.opt_strs("L").map(|s| Path::new(s.as_slice()));
let libs = @RefCell::new(libs.move_iter().collect());
let sessopts = @session::Options {
maybe_sysroot: Some(@os::self_exe_path().unwrap().dir_path()),
@ -79,28 +77,19 @@ pub fn run(input: &str, matches: &getopts::Matches) -> int {
let (krate, _) = passes::unindent_comments(krate);
let (krate, _) = passes::collapse_docs(krate);
let mut collector = Collector {
tests: ~[],
names: ~[],
cnt: 0,
libs: libs,
cratename: krate.name.to_owned(),
};
let mut collector = Collector::new(krate.name.to_owned(), libs, false, false);
collector.fold_crate(krate);
let args = matches.opt_strs("test-args");
let mut args = args.iter().flat_map(|s| s.words()).map(|s| s.to_owned());
let mut args = args.to_owned_vec();
args.unshift(~"rustdoctest");
test_args.unshift(~"rustdoctest");
testing::test_main(args, collector.tests);
testing::test_main(test_args, collector.tests);
0
}
fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
no_run: bool) {
let test = maketest(test, cratename);
no_run: bool, loose_feature_gating: bool) {
let test = maketest(test, cratename, loose_feature_gating);
let parsesess = parse::new_parse_sess();
let input = driver::StrInput(test);
@ -173,11 +162,18 @@ fn runtest(test: &str, cratename: &str, libs: HashSet<Path>, should_fail: bool,
}
}
fn maketest(s: &str, cratename: &str) -> ~str {
fn maketest(s: &str, cratename: &str, loose_feature_gating: bool) -> ~str {
let mut prog = ~r"
#[deny(warnings)];
#[allow(unused_variable, dead_assignment, unused_mut, attribute_usage, dead_code)];
";
if loose_feature_gating {
// FIXME #12773: avoid inserting these when the tutorial & manual
// etc. have been updated to not use them so prolifically.
prog.push_str("#[ feature(macro_rules, globs, struct_variant, managed_boxes) ];\n");
}
if !s.contains("extern crate") {
if s.contains("extra") {
prog.push_str("extern crate extra;\n");
@ -198,21 +194,45 @@ fn maketest(s: &str, cratename: &str) -> ~str {
}
pub struct Collector {
priv tests: ~[testing::TestDescAndFn],
tests: ~[testing::TestDescAndFn],
priv names: ~[~str],
priv libs: @RefCell<HashSet<Path>>,
priv cnt: uint,
priv use_headers: bool,
priv current_header: Option<~str>,
priv cratename: ~str,
priv loose_feature_gating: bool
}
impl Collector {
pub fn add_test(&mut self, test: &str, should_fail: bool, no_run: bool) {
let test = test.to_owned();
let name = format!("{}_{}", self.names.connect("::"), self.cnt);
pub fn new(cratename: ~str, libs: @RefCell<HashSet<Path>>,
use_headers: bool, loose_feature_gating: bool) -> Collector {
Collector {
tests: ~[],
names: ~[],
libs: libs,
cnt: 0,
use_headers: use_headers,
current_header: None,
cratename: cratename,
loose_feature_gating: loose_feature_gating
}
}
pub fn add_test(&mut self, test: ~str, should_fail: bool, no_run: bool) {
let name = if self.use_headers {
let s = self.current_header.as_ref().map(|s| s.as_slice()).unwrap_or("");
format!("{}_{}", s, self.cnt)
} else {
format!("{}_{}", self.names.connect("::"), self.cnt)
};
self.cnt += 1;
let libs = self.libs.borrow();
let libs = (*libs.get()).clone();
let cratename = self.cratename.to_owned();
let loose_feature_gating = self.loose_feature_gating;
debug!("Creating test {}: {}", name, test);
self.tests.push(testing::TestDescAndFn {
desc: testing::TestDesc {
@ -221,10 +241,29 @@ impl Collector {
should_fail: false, // compiler failures are test failures
},
testfn: testing::DynTestFn(proc() {
runtest(test, cratename, libs, should_fail, no_run);
runtest(test, cratename, libs, should_fail, no_run, loose_feature_gating);
}),
});
}
pub fn register_header(&mut self, name: &str, level: u32) {
if self.use_headers && level == 1 {
// we use these headings as test names, so it's good if
// they're valid identifiers.
let name = name.chars().enumerate().map(|(i, c)| {
if (i == 0 && char::is_XID_start(c)) ||
(i != 0 && char::is_XID_continue(c)) {
c
} else {
'_'
}
}).collect::<~str>();
// new header => reset count.
self.cnt = 0;
self.current_header = Some(name);
}
}
}
impl DocFolder for Collector {
@ -237,7 +276,7 @@ impl DocFolder for Collector {
match item.doc_value() {
Some(doc) => {
self.cnt = 0;
markdown::find_testable_code(doc, self);
markdown::find_testable_code(doc, &mut *self);
}
None => {}
}