diff --git a/base/docs.jl b/base/docs.jl
new file mode 100644
index 0000000000000..2280a2687475d
--- /dev/null
+++ b/base/docs.jl
@@ -0,0 +1,316 @@
+module Docs
+
+import Base.Markdown: @doc_str, @doc_mstr
+
+export doc, @doc
+
+# Basic API / Storage
+
+const modules = Module[]
+
+meta() = current_module().META
+
+macro init ()
+ META = esc(:META)
+ quote
+ if !isdefined(:META)
+ const $META = ObjectIdDict()
+ doc($META, doc"Documentation metadata for $(string(current_module())).")
+ push!(modules, current_module())
+ nothing
+ end
+ end
+end
+
+function doc(obj, data)
+ meta()[obj] = data
+end
+
+function doc(obj)
+ for mod in modules
+ haskey(mod.META, obj) && return mod.META[obj]
+ end
+end
+
+function doc(obj::Union(Symbol, AbstractString))
+ doc(current_module().(symbol(obj)))
+end
+
+# Function / Method support
+
+function newmethod(defs)
+ keylen = -1
+ key = nothing
+ for def in defs
+ length(def.sig) > keylen && (keylen = length(def.sig); key = def)
+ end
+ return key
+end
+
+function newmethod(funcs, f)
+ applicable = Method[]
+ for def in methods(f)
+ (!haskey(funcs, def) || funcs[def] != def.func) && push!(applicable, def)
+ end
+ return newmethod(applicable)
+end
+
+function trackmethod(def)
+ name = namify(unblock(def))
+ f = esc(name)
+ quote
+ if isdefined($(Expr(:quote, name))) && isgeneric($f)
+ funcs = [def => def.func for def in methods($f)]
+ $(esc(def))
+ $f, newmethod(funcs, $f)
+ else
+ $(esc(def))
+ $f, newmethod(methods($f))
+ end
+ end
+end
+
+type FuncDoc
+ order::Vector{Method}
+ meta::Dict{Method, Any}
+ source::Dict{Method, Any}
+end
+
+FuncDoc() = FuncDoc(Method[], Dict(), Dict())
+
+getset(coll, key, default) = coll[key] = get(coll, key, default)
+
+function doc(f::Function, m::Method, data, source)
+ fd = getset(meta(), f, FuncDoc())
+ isa(fd, FuncDoc) || error("Can't document a method when the function already has metadata")
+ !haskey(fd.meta, m) && push!(fd.order, m)
+ fd.meta[m] = data
+ fd.source[m] = source
+end
+
+function doc(f::Function)
+ docs = []
+ for mod in modules
+ if haskey(mod.META, f)
+ fd = mod.META[f]
+ if isa(fd, FuncDoc)
+ for m in fd.order
+ push!(docs, fd.meta[m])
+ end
+ elseif length(docs) == 0
+ return fd
+ end
+ end
+ end
+ return catdoc(docs...)
+end
+
+catdoc() = nothing
+catdoc(xs...) = [xs...]
+
+# Macros
+
+isexpr(x::Expr, ts...) = x.head in ts
+isexpr{T}(x::T, ts...) = T in ts
+
+function unblock(ex)
+ isexpr(ex, :block) || return ex
+ exs = filter(ex->!isexpr(ex, :line), ex.args)
+ length(exs) == 1 || return ex
+ return exs[1]
+end
+
+namify(ex::Expr) = namify(ex.args[1])
+namify(sy::Symbol) = sy
+
+function mdify(ex)
+ if isa(ex, AbstractString)
+ :(@doc_str $(esc(ex)))
+ elseif isexpr(ex, :macrocall) && namify(ex) == symbol("@mstr")
+ :(@doc_mstr $(esc(ex.args[2])))
+ else
+ esc(ex)
+ end
+end
+
+function namedoc(meta, def, name)
+ quote
+ @init
+ $(esc(def))
+ doc($(esc(name)), $(mdify(meta)))
+ nothing
+ end
+end
+
+function funcdoc(meta, def)
+ quote
+ @init
+ f, m = $(trackmethod(def))
+ doc(f, m, $(mdify(meta)), $(esc(Expr(:quote, def))))
+ f
+ end
+end
+
+function objdoc(meta, def)
+ quote
+ @init
+ f = $(esc(def))
+ doc(f, $(mdify(meta)))
+ f
+ end
+end
+
+fexpr(ex) = isexpr(ex, :function) || (isexpr(ex, :(=)) && isexpr(ex.args[1], :call))
+
+function docm(meta, def)
+ def′ = unblock(def)
+ isexpr(def′, :macro) && return namedoc(meta, def,
+ symbol(string("@", namify(def′))))
+ isexpr(def′, :type) && return namedoc(meta, def, namify(def′.args[2]))
+ fexpr(def′) && return funcdoc(meta, def)
+ isexpr(def, :macrocall) && (def = namify(def))
+ return objdoc(meta, def)
+end
+
+function docm(ex)
+ isexpr(ex, :->) && return docm(ex.args...)
+ isexpr(ex, :macrocall) && (ex = namify(ex))
+ :(doc($(esc(ex))))
+end
+
+macro doc (args...)
+ docm(args...)
+end
+
+# Metametadata
+
+@doc """
+ # Documentation
+ The `@doc` macro can be used to both set and retrieve documentation /
+ metadata. By default, documentation is written as Markdown, but any
+ object can be placed before the arrow. For example:
+
+ @doc \"""
+ # The Foo Function
+ `foo(x)`: Foo the living hell out of `x`.
+ \""" ->
+ function foo() ...
+
+ The `->` is not required if the object is on the same line, e.g.
+
+ @doc "foo" foo
+
+ # Retrieving Documentation
+ You can retrieve docs for functions, macros and other objects as
+ follows:
+
+ @doc foo
+ @doc @time
+ @doc md""
+
+ # Functions & Methods
+ Placing documentation before a method definition (e.g. `function foo()
+ ...` or `foo() = ...`) will cause that specific method to be
+ documented, as opposed to the whole function. Method docs are
+ concatenated together in the order they were defined to provide docs
+ for the function.
+ """ @doc
+
+@doc "`doc(obj)`: Get the doc metadata for `obj`." doc
+
+@doc """
+ `catdoc(xs...)`: Combine the documentation metadata `xs` into a single meta object.
+ """ catdoc
+
+# Text / HTML objects
+
+import Base: print, writemime
+
+export HTML, @html_str, @html_mstr
+
+export HTML, Text
+
+@doc """
+`HTML(s)`: Create an object that renders `s` as html.
+
+ HTML("
foo
")
+
+You can also use a stream for large amounts of data:
+
+ HTML() do io
+ println(io, "foo
")
+ end
+""" ->
+type HTML{T}
+ content::T
+end
+
+function HTML(xs...)
+ HTML() do io
+ for x in xs
+ writemime(io, MIME"text/html"(), x)
+ end
+ end
+end
+
+writemime(io::IO, ::MIME"text/html", h::HTML) = print(io, h.content)
+writemime(io::IO, ::MIME"text/html", h::HTML{Function}) = h.content(io)
+
+@doc "Create an `HTML` object from a literal string." ->
+macro html_str (s)
+ :(HTML($s))
+end
+
+@doc (@doc html"") ->
+macro html_mstr (s)
+ :(HTML($(Base.triplequoted(s))))
+end
+
+function catdoc(xs::HTML...)
+ HTML() do io
+ for x in xs
+ writemime(io, MIME"text/html"(), x)
+ end
+ end
+end
+
+export Text, @text_str, @text_mstr
+
+# @doc """
+# `Text(s)`: Create an object that renders `s` as plain text.
+
+# HTML("foo")
+
+# You can also use a stream for large amounts of data:
+
+# Text() do io
+# println(io, "foo")
+# end
+# """ ->
+type Text{T}
+ content::T
+end
+
+print(io::IO, t::Text) = print(io, t.content)
+print(io::IO, t::Text{Function}) = t.content(io)
+writemime(io::IO, ::MIME"text/plain", t::Text) = print(io, t)
+
+@doc "Create a `Text` object from a literal string." ->
+macro text_str (s)
+ :(Text($s))
+end
+
+@doc (@doc text"") ->
+macro text_mstr (s)
+ :(Text($(Base.triplequoted(s))))
+end
+
+function catdoc(xs::Text...)
+ Text() do io
+ for x in xs
+ writemime(io, MIME"text/plain"(), x)
+ end
+ end
+end
+
+end
diff --git a/base/exports.jl b/base/exports.jl
index 5ace9b39d0856..ea14ae36e8d98 100644
--- a/base/exports.jl
+++ b/base/exports.jl
@@ -12,6 +12,8 @@ export
Test,
BLAS,
LAPACK,
+ Docs,
+ Markdown,
# Types
AbstractMatrix,
@@ -1221,6 +1223,8 @@ export
popdisplay,
pushdisplay,
redisplay,
+ HTML,
+ Text,
# distributed arrays
dfill,
@@ -1339,6 +1343,10 @@ export
@r_str,
@r_mstr,
@v_str,
+ @text_str,
+ @text_mstr,
+ @html_str,
+ @html_mstr,
@int128_str,
@uint128_str,
@bigint_str,
@@ -1392,4 +1400,7 @@ export
@simd,
@label,
@goto,
- @inline
+ @inline,
+ @doc,
+ @doc_str,
+ @doc_mstr
diff --git a/base/help.jl b/base/help.jl
index 164f63eb51b34..3da19c9757dbd 100644
--- a/base/help.jl
+++ b/base/help.jl
@@ -186,7 +186,7 @@ isname(n::Symbol) = true
isname(ex::Expr) = ((ex.head == :. && isname(ex.args[1]) && isname(ex.args[2]))
|| (ex.head == :quote && isname(ex.args[1])))
-macro help(ex)
+macro help_(ex)
if ex === :? || ex === :help
return Expr(:call, :help)
elseif !isa(ex, Expr) || isname(ex)
@@ -199,4 +199,16 @@ macro help(ex)
end
end
+macro help (ex)
+ if ex === :? || ex === :help
+ return :(@help_ $(esc(ex)))
+ else
+ quote
+ let doc = @doc $(esc(ex))
+ doc ≠ nothing ? doc : @help_ $(esc(ex))
+ end
+ end
+ end
+end
+
end # module
diff --git a/base/markdown/Common/Common.jl b/base/markdown/Common/Common.jl
new file mode 100644
index 0000000000000..b98cb68d97cf7
--- /dev/null
+++ b/base/markdown/Common/Common.jl
@@ -0,0 +1,5 @@
+include("block.jl")
+include("inline.jl")
+
+@flavor common [list, indentcode, blockquote, hashheader, paragraph,
+ escapes, en_dash, inline_code, asterisk_bold, asterisk_italic, image, link]
diff --git a/base/markdown/Common/block.jl b/base/markdown/Common/block.jl
new file mode 100644
index 0000000000000..ae1e982705e66
--- /dev/null
+++ b/base/markdown/Common/block.jl
@@ -0,0 +1,166 @@
+# ––––––––––
+# Paragraphs
+# ––––––––––
+
+type Paragraph
+ content
+end
+
+Paragraph() = Paragraph([])
+
+function paragraph(stream::IO, md::MD, config::Config)
+ buffer = IOBuffer()
+ p = Paragraph()
+ push!(md, p)
+ skipwhitespace(stream)
+ while !eof(stream)
+ char = read(stream, Char)
+ if char == '\n' || char == '\r'
+ if blankline(stream) || parse(stream, md, config, breaking = true)
+ break
+ else
+ write(buffer, ' ')
+ end
+ else
+ write(buffer, char)
+ end
+ end
+ p.content = parseinline(seek(buffer, 0), config)
+ return true
+end
+
+# –––––––
+# Headers
+# –––––––
+
+type Header{level}
+ text
+end
+
+Header(s, level::Int) = Header{level}(s)
+Header(s) = Header(s, 1)
+
+@breaking true ->
+function hashheader(stream::IO, md::MD, config::Config)
+ startswith(stream, "#") || return false
+ level = 1
+ while startswith(stream, "#")
+ level += 1
+ end
+ h = readline(stream) |> chomp
+ h = match(r"\s*(.*)(?
+function blockquote(stream::IO, block::MD, config::Config)
+ withstream(stream) do
+ buffer = IOBuffer()
+ while startswith(stream, ">")
+ startswith(stream, " ")
+ write(buffer, readline(stream))
+ end
+ md = takebuf_string(buffer)
+ if !isempty(md)
+ push!(block, BlockQuote(parse(md, flavor = config).content))
+ return true
+ else
+ return false
+ end
+ end
+end
+
+# –––––
+# Lists
+# –––––
+
+type List
+ items::Vector{Any}
+ ordered::Bool
+
+ List(x::AbstractVector) = new(x)
+end
+
+List(xs...) = List([xs...])
+
+const bullets = ["* ", "• ", "+ ", "- "]
+
+# Todo: ordered lists, inline formatting
+function list(stream::IO, block::MD, config::Config)
+ withstream(stream) do
+ skipwhitespace(stream)
+ startswith(stream, bullets) || return false
+ the_list = List()
+ buffer = IOBuffer()
+ fresh_line = false
+ while !eof(stream)
+ if fresh_line
+ skipwhitespace(stream)
+ if startswith(stream, bullets)
+ push!(the_list.items, takebuf_string(buffer))
+ buffer = IOBuffer()
+ else
+ write(buffer, ' ')
+ end
+ fresh_line = false
+ else
+ c = read(stream, Char)
+ if c == '\n'
+ eof(stream) && break
+ next = peek(stream)
+ if next == '\n'
+ break
+ else
+ fresh_line = true
+ end
+ else
+ write(buffer, c)
+ end
+ end
+ end
+ push!(the_list.items, takebuf_string(buffer))
+ push!(block, the_list)
+ return true
+ end
+end
diff --git a/base/markdown/Common/inline.jl b/base/markdown/Common/inline.jl
new file mode 100644
index 0000000000000..199257146e273
--- /dev/null
+++ b/base/markdown/Common/inline.jl
@@ -0,0 +1,97 @@
+# ––––––––
+# Emphasis
+# ––––––––
+
+type Italic
+ text
+end
+
+@trigger '*' ->
+function asterisk_italic(stream::IO)
+ result = parse_inline_wrapper(stream, "*")
+ return result == nothing ? nothing : Italic(result)
+end
+
+type Bold
+ text
+end
+
+@trigger '*' ->
+function asterisk_bold(stream::IO)
+ result = parse_inline_wrapper(stream, "**")
+ return result == nothing ? nothing : Bold(result)
+end
+
+# ––––
+# Code
+# ––––
+
+@trigger '`' ->
+function inline_code(stream::IO)
+ result = parse_inline_wrapper(stream, "`")
+ return result == nothing ? nothing : Code(result)
+end
+
+# ––––––––––––––
+# Images & Links
+# ––––––––––––––
+
+type Image
+ url::UTF8String
+ alt::UTF8String
+end
+
+@trigger '!' ->
+function image(stream::IO)
+ withstream(stream) do
+ startswith(stream, "![") || return
+ alt = readuntil(stream, ']', match = '[')
+ alt ≡ nothing && return
+ skipwhitespace(stream)
+ startswith(stream, '(') || return
+ url = readuntil(stream, ')', match = '(')
+ url ≡ nothing && return
+ return Image(url, alt)
+ end
+end
+
+type Link
+ text
+ url::UTF8String
+end
+
+@trigger '[' ->
+function link(stream::IO)
+ withstream(stream) do
+ startswith(stream, '[') || return
+ text = readuntil(stream, ']', match = '[')
+ text ≡ nothing && return
+ skipwhitespace(stream)
+ startswith(stream, '(') || return
+ url = readuntil(stream, ')', match = '(')
+ url ≡ nothing && return
+ return Link(parseinline(text), url)
+ end
+end
+
+# –––––––––––
+# Punctuation
+# –––––––––––
+
+@trigger '-' ->
+function en_dash(stream::IO)
+ if startswith(stream, "--")
+ return "–"
+ end
+end
+
+const escape_chars = "\\`*_#+-.!{[("
+
+@trigger '\\' ->
+function escapes(stream::IO)
+ withstream(stream) do
+ if startswith(stream, "\\") && !eof(stream) && (c = read(stream, Char)) in escape_chars
+ return string(c)
+ end
+ end
+end
diff --git a/base/markdown/GitHub/GitHub.jl b/base/markdown/GitHub/GitHub.jl
new file mode 100644
index 0000000000000..74cc9566691f2
--- /dev/null
+++ b/base/markdown/GitHub/GitHub.jl
@@ -0,0 +1,39 @@
+@breaking true ->
+function fencedcode(stream::IO, block::MD, config::Config)
+ startswith(stream, "```", padding = true) || return false
+ readline(stream)
+ buffer = IOBuffer()
+ while !eof(stream)
+ startswith(stream, "```") && break
+ write(buffer, readline(stream))
+ end
+ push!(block, Code(takebuf_string(buffer) |> chomp))
+ return true
+end
+
+function github_paragraph(stream::IO, md::MD, config::Config)
+ skipwhitespace(stream)
+ buffer = IOBuffer()
+ p = Paragraph()
+ push!(md, p)
+ while !eof(stream)
+ char = read(stream, Char)
+ if char == '\n'
+ eof(stream) && break
+ if blankline(stream) || parse(stream, md, config, breaking = true)
+ break
+ else
+ write(buffer, '\n')
+ end
+ else
+ write(buffer, char)
+ end
+ end
+ p.content = parseinline(seek(buffer, 0), config)
+ return true
+end
+
+# TODO: tables
+
+@flavor github [list, indentcode, blockquote, fencedcode, hashheader, github_paragraph,
+ en_dash, inline_code, asterisk_bold, asterisk_italic, image, link]
diff --git a/base/markdown/Julia/Julia.jl b/base/markdown/Julia/Julia.jl
new file mode 100644
index 0000000000000..500420d486579
--- /dev/null
+++ b/base/markdown/Julia/Julia.jl
@@ -0,0 +1,11 @@
+"""
+This file contains markdown extensions designed to make documenting
+Julia easy peasy.
+
+We start by borrowing GitHub's `fencedcode` extension – more to follow.
+"""
+
+include("interp.jl")
+
+@flavor julia [blockinterp, hashheader, list, indentcode, fencedcode, blockquote, paragraph,
+ escapes, interp, en_dash, inline_code, asterisk_bold, asterisk_italic, image, link]
diff --git a/base/markdown/Julia/interp.jl b/base/markdown/Julia/interp.jl
new file mode 100644
index 0000000000000..735477221ffb8
--- /dev/null
+++ b/base/markdown/Julia/interp.jl
@@ -0,0 +1,50 @@
+function Base.parse(stream::IOBuffer; greedy::Bool = true, raise::Bool = true)
+ pos = position(stream)
+ ex, Δ = Base.parse(readall(stream), 1, greedy = greedy, raise = raise)
+ seek(stream, pos + Δ - 1)
+ return ex
+end
+
+function interpinner(stream::IO, greedy = false)
+ startswith(stream, '$') || return
+ (eof(stream) || peek(stream) in whitespace) && return
+ try
+ return Base.parse(stream::IOBuffer, greedy = greedy)
+ catch e
+ return
+ end
+end
+
+@trigger '$' ->
+function interp(stream::IO)
+ withstream(stream) do
+ ex = interpinner(stream)
+ return ex
+ end
+end
+
+function blockinterp(stream::IO, md::MD, config::Config)
+ r=withstream(stream) do
+ ex = interpinner(stream, true)
+ if ex ≡ nothing
+ return false
+ else
+ push!(md, ex)
+ return true
+ end
+ end
+ return r
+end
+
+toexpr(x) = x
+
+toexpr(xs::Vector{Any}) = Expr(:cell1d, map(toexpr, xs)...)
+
+function deftoexpr(T)
+ @eval function toexpr(md::$T)
+ Expr(:call, $T, $(map(x->:(toexpr(md.$x)), names(T))...))
+ end
+end
+
+map(deftoexpr, [MD, Paragraph, Header,
+ Link])
diff --git a/base/markdown/Markdown.jl b/base/markdown/Markdown.jl
new file mode 100644
index 0000000000000..56339ef88f5e7
--- /dev/null
+++ b/base/markdown/Markdown.jl
@@ -0,0 +1,75 @@
+module Markdown
+
+typealias String AbstractString
+
+include("parse/config.jl")
+include("parse/util.jl")
+include("parse/parse.jl")
+
+include("Common/Common.jl")
+include("GitHub/GitHub.jl")
+include("Julia/Julia.jl")
+
+include("render/plain.jl")
+include("render/html.jl")
+# include("render/latex.jl")
+
+include("render/terminal/render.jl")
+
+export readme, license, @md_str, @md_mstr, @doc_str, @doc_mstr
+
+parse(markdown::String; flavor = julia) = parse(IOBuffer(markdown), flavor = flavor)
+parse_file(file::String; flavor = julia) = parse(readall(file), flavor = flavor)
+
+readme(pkg::String; flavor = github) = parse_file(Pkg.dir(pkg, "README.md"), flavor = flavor)
+readme(pkg::Module; flavor = github) = readme(string(pkg), flavor = flavor)
+
+license(pkg::String; flavor = github) = parse_file(Pkg.dir(pkg, "LICENSE.md"), flavor = flavor)
+license(pkg::Module; flavor = github) = license(string(pkg), flavor = flavor)
+
+function mdexpr(s, flavor = :julia)
+ md = parse(s, flavor = symbol(flavor))
+ esc(toexpr(md))
+end
+
+function docexpr(s, flavor = :julia)
+ quote
+ let md = $(mdexpr(s, flavor))
+ md.meta[:path] = @__FILE__
+ md.meta[:module] = current_module()
+ md
+ end
+ end
+end
+
+macro md_str(s, t...)
+ mdexpr(s, t...)
+end
+
+macro md_mstr(s, t...)
+ s = Base.triplequoted(s)
+ mdexpr(s, t...)
+end
+
+macro doc_str(s, t...)
+ docexpr(s, t...)
+end
+
+macro doc_mstr(s, t...)
+ s = Base.triplequoted(s)
+ docexpr(s, t...)
+end
+
+function writemime(io::IO, m, md::Vector{MD})
+ for md in md
+ writemime(io, m, md)
+ end
+end
+
+function Base.display(d::Base.REPL.REPLDisplay, md::Vector{MD})
+ for md in md
+ display(d, md)
+ end
+end
+
+end
diff --git a/base/markdown/parse/config.jl b/base/markdown/parse/config.jl
new file mode 100644
index 0000000000000..822b0d6013f89
--- /dev/null
+++ b/base/markdown/parse/config.jl
@@ -0,0 +1,95 @@
+typealias InnerConfig Dict{Char, Vector{Function}}
+
+type Config
+ breaking::Vector{Function}
+ regular::Vector{Function}
+ inner::InnerConfig
+end
+
+Config() = Config(Function[], Function[], InnerConfig())
+
+const META = Dict{Function, Dict{Symbol, Any}}()
+
+getset(coll, key, default) = coll[key] = get(coll, key, default)
+
+meta(f) = getset(META, f, Dict{Symbol, Any}())
+
+breaking!(f) = meta(f)[:breaking] = true
+breaking(f) = get(meta(f), :breaking, false)
+
+triggers!(f, ts) = meta(f)[:triggers] = Set{Char}(ts)
+triggers(f) = get(meta(f), :triggers, Set{Char}())
+
+# Macros
+
+isexpr(x::Expr, ts...) = x.head in ts
+isexpr{T}(x::T, ts...) = T in ts
+
+macro breaking (ex)
+ isexpr(ex, :->) || error("invalid @breaking form, use ->")
+ b, def = ex.args
+ if b
+ quote
+ f = $(esc(def))
+ breaking!(f)
+ f
+ end
+ else
+ esc(def)
+ end
+end
+
+macro trigger (ex)
+ isexpr(ex, :->) || error("invalid @triggers form, use ->")
+ ts, def = ex.args
+ quote
+ f = $(esc(def))
+ triggers!(f, $ts)
+ f
+ end
+end
+
+# Construction
+
+function config(parsers::Function...)
+ c = Config()
+ for parser in parsers
+ ts = triggers(parser)
+ if breaking(parser)
+ push!(c.breaking, parser)
+ elseif !isempty(ts)
+ for t in ts
+ push!(getset(c.inner, t, Function[]), parser)
+ end
+ else
+ push!(c.regular, parser)
+ end
+ end
+ return c
+end
+
+# Flavour definitions
+
+const flavors = Dict{Symbol, Config}()
+
+macro flavor (name, features)
+ quote
+ const $(esc(name)) = config($(map(esc,features.args)...))
+ flavors[$(Expr(:quote, name))] = $(esc(name))
+ end
+end
+
+# Dynamic scoping of current config
+
+_config_ = nothing
+
+function withconfig(f, config)
+ global _config_
+ old = _config_
+ _config_ = config
+ try
+ f()
+ finally
+ _config_ = old
+ end
+end
diff --git a/base/markdown/parse/parse.jl b/base/markdown/parse/parse.jl
new file mode 100644
index 0000000000000..a13571b2cef9a
--- /dev/null
+++ b/base/markdown/parse/parse.jl
@@ -0,0 +1,84 @@
+type MD
+ content::Vector{Any}
+ meta::Dict{Any, Any}
+
+ MD(content::AbstractVector, meta::Dict = Dict()) =
+ new(content, meta)
+end
+
+MD(xs...) = MD([xs...])
+
+# Forward some array methods
+
+Base.push!(md::MD, x) = push!(md.content, x)
+Base.getindex(md::MD, args...) = md.content[args...]
+Base.setindex!(md::MD, args...) = setindex!(md.content, args...)
+Base.endof(md::MD) = endof(md.content)
+Base.length(md::MD) = length(md.content)
+Base.isempty(md::MD) = isempty(md.content)
+
+# Parser functions:
+# md – should be modified appropriately
+# return – basically, true if parse was successful
+# false uses the next parser in the queue, true
+# goes back to the beginning
+#
+# Inner parsers:
+# return – element to use or nothing
+
+# Inner parsing
+
+function innerparse(stream::IO, parsers::Vector{Function})
+ for parser in parsers
+ inner = parser(stream)
+ inner ≡ nothing || return inner
+ end
+end
+
+innerparse(stream::IO, config::Config) =
+ innerparse(stream, config.inner.parsers)
+
+function parseinline(stream::IO, config::Config)
+ content = []
+ buffer = IOBuffer()
+ while !eof(stream)
+ char = peek(stream)
+ if haskey(config.inner, char) &&
+ (inner = innerparse(stream, config.inner[char])) != nothing
+ c = takebuf_string(buffer)
+ !isempty(c) && push!(content, c)
+ buffer = IOBuffer()
+ push!(content, inner)
+ else
+ write(buffer, read(stream, Char))
+ end
+ end
+ c = takebuf_string(buffer)
+ !isempty(c) && push!(content, c)
+ return content
+end
+
+parseinline(s::String, c::Config) =
+ parseinline(IOBuffer(s), c)
+
+parseinline(s) = parseinline(s, _config_)
+
+# Block parsing
+
+function parse(stream::IO, block::MD, config::Config; breaking = false)
+ skipblank(stream)
+ eof(stream) && return false
+ for parser in (breaking ? config.breaking : [config.breaking, config.regular])
+ parser(stream, block, config) && return true
+ end
+ return false
+end
+
+function parse(stream::IO; flavor = julia)
+ isa(flavor, Symbol) && (flavor = flavors[flavor])
+ markdown = MD()
+ withconfig(flavor) do
+ while parse(stream, markdown, flavor) end
+ end
+ return markdown
+end
diff --git a/base/markdown/parse/util.jl b/base/markdown/parse/util.jl
new file mode 100644
index 0000000000000..0962bcee7ba41
--- /dev/null
+++ b/base/markdown/parse/util.jl
@@ -0,0 +1,171 @@
+import Base: peek
+
+macro dotimes(n, body)
+ quote
+ for i = 1:$(esc(n))
+ $(esc(body))
+ end
+ end
+end
+
+const whitespace = " \t\r"
+
+"""
+Skip any leading whitespace. Returns io.
+"""
+function skipwhitespace(io::IO; newlines = true)
+ while !eof(io) && (peek(io) in whitespace || (newlines && peek(io) == '\n'))
+ read(io, Char)
+ end
+ return io
+end
+
+"""
+Skip any leading blank lines. Returns the number skipped.
+"""
+function skipblank(io::IO)
+ start = position(io)
+ i = 0
+ while !eof(io)
+ c = read(io, Char)
+ c == '\n' && (start = position(io); i+=1; continue)
+ c in whitespace || break
+ end
+ seek(io, start)
+ return i
+end
+
+"""
+Returns true if the line contains only (and
+at least one of) the characters given.
+"""
+function linecontains(io::IO, chars; allow_whitespace = true,
+ eat = true,
+ allowempty = false)
+ start = position(io)
+ l = readline(io) |> chomp
+ length(l) == 0 && return allowempty
+
+ result = false
+ for c in l
+ c in whitespace && (allow_whitespace ? continue : (result = false; break))
+ c in chars && (result = true; continue)
+ result = false; break
+ end
+ !(result && eat) && seek(io, start)
+ return result
+end
+
+blankline(io::IO; eat = true) =
+ linecontains(io, "",
+ allow_whitespace = true,
+ allowempty = true,
+ eat = eat)
+
+"""
+Test if the stream starts with the given string.
+`eat` specifies whether to advance on success (true by default).
+`padding` specifies whether leading whitespace should be ignored.
+"""
+function startswith(stream::IO, s::String; eat = true, padding = false, newlines = true)
+ start = position(stream)
+ padding && skipwhitespace(stream, newlines = newlines)
+ result = true
+ for char in s
+ !eof(stream) && read(stream, Char) == char ||
+ (result = false; break)
+ end
+ !(result && eat) && seek(stream, start)
+ return result
+end
+
+function startswith(stream::IO, c::Char; eat = true)
+ if peek(stream) == c
+ eat && read(stream, Char)
+ return true
+ else
+ return false
+ end
+end
+
+function startswith{T<:String}(stream::IO, ss::Vector{T}; kws...)
+ any(s->startswith(stream, s; kws...), ss)
+end
+
+function startswith(stream::IO, r::Regex; eat = true, padding = false)
+ @assert beginswith(r.pattern, "^")
+ start = position(stream)
+ padding && skipwhitespace(stream)
+ line = chomp(readline(stream))
+ seek(stream, start)
+ m = match(r, line)
+ m == nothing && return ""
+ eat && @dotimes length(m.match) read(stream, Char)
+ return m.match
+end
+
+"""
+Executes the block of code, and if the return value is `nothing`,
+returns the stream to its initial position.
+"""
+function withstream(f, stream)
+ pos = position(stream)
+ result = f()
+ (result ≡ nothing || result ≡ false) && seek(stream, pos)
+ return result
+end
+
+"""
+Read the stream until startswith(stream, delim)
+The delimiter is consumed but not included.
+Returns nothing and resets the stream if delim is
+not found.
+"""
+function readuntil(stream::IO, delimiter; newlines = false, match = nothing)
+ withstream(stream) do
+ buffer = IOBuffer()
+ count = 0
+ while !eof(stream)
+ if startswith(stream, delimiter)
+ if count == 0
+ return takebuf_string(buffer)
+ else
+ count -= 1
+ write(buffer, delimiter)
+ end
+ end
+ char = read(stream, Char)
+ char == match && (count += 1)
+ !newlines && char == '\n' && break
+ write(buffer, char)
+ end
+ end
+end
+
+"""
+Parse a symmetrical delimiter which wraps words.
+i.e. `*word word*` but not `*word * word`
+"""
+function parse_inline_wrapper(stream::IO, delimiter::String, no_newlines = true)
+ withstream(stream) do
+ startswith(stream, delimiter) || return nothing
+
+ buffer = IOBuffer()
+ while !eof(stream)
+ char = read(stream, Char)
+ no_newlines && char == '\n' && break
+ if !(char in whitespace) && startswith(stream, delimiter)
+ write(buffer, char)
+ return takebuf_string(buffer)
+ end
+ write(buffer, char)
+ end
+ end
+end
+
+function showrest(io::IO)
+ start = position(io)
+ show(readall(io))
+ println()
+ seek(io, start)
+end
diff --git a/base/markdown/render/html.jl b/base/markdown/render/html.jl
new file mode 100644
index 0000000000000..fcf26b4f8c3ee
--- /dev/null
+++ b/base/markdown/render/html.jl
@@ -0,0 +1,113 @@
+include("rich.jl")
+
+# Utils
+
+function withtag(f, io, tag)
+ print(io, "<$tag>")
+ f()
+ print(io, "$tag>")
+end
+
+# Block elements
+
+function html(io::IO, content::Vector)
+ for md in content
+ html(io, md)
+ println(io)
+ end
+end
+
+html(io::IO, md::MD) = html(io, md.content)
+
+function html{l}(io::IO, header::Header{l})
+ withtag(io, "h$l") do
+ htmlinline(io, header.text)
+ end
+end
+
+function html(io::IO, code::Code)
+ withtag(io, :pre) do
+ withtag(io, :code) do
+ print(io, code.code)
+ end
+ end
+end
+
+function html(io::IO, md::Paragraph)
+ withtag(io, :p) do
+ htmlinline(io, md.content)
+ end
+end
+
+function html(io::IO, md::BlockQuote)
+ withtag(io, :blockquote) do
+ html(io, block.content)
+ end
+end
+
+function html(io::IO, md::List)
+ withtag(io, :ul) do
+ for item in md.items
+ withtag(io, :li) do
+ htmlinline(io, item)
+ println(io)
+ end
+ end
+ end
+end
+
+html(io::IO, x) = tohtml(io, x)
+
+# Inline elements
+
+function htmlinline(io::IO, content::Vector)
+ for x in content
+ htmlinline(io, x)
+ end
+end
+
+function htmlinline(io::IO, code::Code)
+ withtag(io, :code) do
+ print(io, code.code)
+ end
+end
+
+function htmlinline(io::IO, md::String)
+ print(io, md)
+end
+
+function htmlinline(io::IO, md::Bold)
+ withtag(io, :strong) do
+ print(io, md.text)
+ end
+end
+
+function htmlinline(io::IO, md::Italic)
+ withtag(io, :em) do
+ print(io, md.text)
+ end
+end
+
+function htmlinline(io::IO, md::Image)
+ print(io, """""")
+end
+
+function htmlinline(io::IO, link::Link)
+ print(io, """""")
+ htmlinline(io, link.text)
+ print(io,"""""")
+end
+
+htmlinline(io::IO, x) = tohtml(io, x)
+
+# API
+
+export html
+
+html(md) = sprint(html, md)
+
+function Base.writemime(io::IO, ::MIME"text/html", md::MD)
+ println(io, """""")
+ html(io, md)
+ println(io, """
""")
+end
diff --git a/base/markdown/render/latex.jl b/base/markdown/render/latex.jl
new file mode 100644
index 0000000000000..e9034efb76ad2
--- /dev/null
+++ b/base/markdown/render/latex.jl
@@ -0,0 +1,112 @@
+import Base.writemime
+
+export latex
+
+function wrapblock(f, io, env)
+ println(io, "\\begin{", env, "}")
+ f()
+ println(io, "\\end{", env, "}")
+end
+
+function wrapinline(f, io, cmd)
+ print(io, "\\", cmd, "{")
+ f()
+ print(io, "}")
+end
+
+writemime(io::IO, ::MIME"text/latex", md::Content) =
+ writemime(io, "text/plain", md)
+
+function writemime(io::IO, mime::MIME"text/latex", block::Block)
+ for md in block.content[1:end-1]
+ writemime(io::IO, mime, md)
+ println(io)
+ end
+ writemime(io::IO, mime, block.content[end])
+end
+
+function writemime{l}(io::IO, mime::MIME"text/latex", header::Header{l})
+ tag = l < 4 ? "sub"^(l-1) * "section" : "sub"^(l-4) * "paragraph"
+ wrapinline(io, tag) do
+ print(io, header.text)
+ end
+ println(io)
+end
+
+function writemime(io::IO, ::MIME"text/latex", code::BlockCode)
+ wrapblock(io, "verbatim") do
+ println(io, code.code)
+ end
+end
+
+function writemime(io::IO, ::MIME"text/latex", code::InlineCode)
+ wrapinline(io, "texttt") do
+ print(io, code.code)
+ end
+end
+
+function writemime(io::IO, ::MIME"text/latex", md::Paragraph)
+ for md in md.content
+ latex_inline(io, md)
+ end
+ println(io)
+end
+
+function writemime(io::IO, ::MIME"text/latex", md::BlockQuote)
+ wrapblock(io, "quote") do
+ writemime(io, "text/latex", Block(md.content))
+ end
+end
+
+function writemime(io::IO, ::MIME"text/latex", md::List)
+ wrapblock(io, "itemize") do
+ for item in md.content
+ print(io, "\\item ")
+ latex_inline(io, item)
+ println(io)
+ end
+ end
+end
+
+# Inline elements
+
+function writemime(io::IO, ::MIME"text/latex", md::Plain)
+ print(io, md.text)
+end
+
+function writemime(io::IO, ::MIME"text/latex", md::Bold)
+ wrapinline(io, "textbf") do
+ print(io, md.text)
+ end
+end
+
+function writemime(io::IO, ::MIME"text/latex", md::Italic)
+ wrapinline(io, "emph") do
+ print(io, md.text)
+ end
+end
+
+function writemime(io::IO, ::MIME"text/latex", md::Image)
+ wrapblock(io, "figure") do
+ println(io, "\\centering")
+ wrapinline(io, "includegraphics") do
+ print(io, md.url)
+ end
+ println(io)
+ wrapinline(io, "caption") do
+ print(io, md.alt)
+ end
+ println(io)
+ end
+end
+
+function writemime(io::IO, ::MIME"text/latex", md::Link)
+ wrapinline(io, "href") do
+ print(io, md.url)
+ end
+ print(io, "{", md.text, "}")
+end
+
+latex_inline(io::IO, el::Content) = writemime(io, "text/latex", el)
+
+latex(md::Content) = stringmime("text/latex", md)
diff --git a/base/markdown/render/plain.jl b/base/markdown/render/plain.jl
new file mode 100644
index 0000000000000..eadfa87c8ad68
--- /dev/null
+++ b/base/markdown/render/plain.jl
@@ -0,0 +1,64 @@
+plain(x) = sprint(plain, x)
+
+function plain(io::IO, content::Vector)
+ for md in content[1:end-1]
+ plain(io, md)
+ println(io)
+ end
+ plain(io, content[end])
+end
+
+plain(io::IO, md::MD) = plain(io, md.content)
+
+function plain{l}(io::IO, header::Header{l})
+ print(io, "#"^l*" ")
+ plaininline(io, header.text)
+ println(io)
+end
+
+function plain(io::IO, code::Code)
+ println(io, "```", code.language)
+ println(io, code.code)
+ println(io, "```")
+end
+
+function plain(io::IO, p::Paragraph)
+ for md in p.content
+ plaininline(io, md)
+ end
+ println(io)
+end
+
+function plain(io::IO, list::List)
+ for item in list.items
+ print(io, " * ")
+ plaininline(io, item)
+ println(io)
+ end
+end
+
+plain(io::IO, x) = tohtml(io, x)
+
+# Inline elements
+
+function plaininline(io::IO, md::Vector)
+ for el in md
+ plaininline(io, el)
+ end
+end
+
+plaininline(io::IO, md::Image) = print(io, "![$(md.alt)]($(md.url))")
+
+plaininline(io::IO, s::String) = print(io, s)
+
+plaininline(io::IO, md::Bold) = print(io, "**", md.text, "**")
+
+plaininline(io::IO, md::Italic) = print(io, "*", md.text, "*")
+
+plaininline(io::IO, md::Code) = print(io, "`", md.code, "`")
+
+plaininline(io::IO, x) = writemime(io, MIME"text/plain"(), x)
+
+# writemime
+
+Base.writemime(io::IO, ::MIME"text/plain", md::MD) = plain(io, md)
diff --git a/base/markdown/render/rich.jl b/base/markdown/render/rich.jl
new file mode 100644
index 0000000000000..89a70662d850b
--- /dev/null
+++ b/base/markdown/render/rich.jl
@@ -0,0 +1,28 @@
+function tohtml(io::IO, m::MIME"text/html", x)
+ writemime(io, m, x)
+end
+
+function tohtml(io::IO, m::MIME"text/plain", x)
+ writemime(io, m, x)
+end
+
+function tohtml(io::IO, m::MIME"image/png", img)
+ print(io, """")
+end
+
+function tohtml(m::MIME"image/svg+xml", img)
+ writemime(io, m, img)
+end
+
+# Display infrastructure
+
+function bestmime(val)
+ for mime in ("text/html", "image/svg+xml", "image/png", "text/plain")
+ mimewritable(mime, val) && return MIME(symbol(mime))
+ end
+ error("Cannot render $val to Markdown.")
+end
+
+tohtml(io::IO, x) = tohtml(io, bestmime(x), x)
diff --git a/base/markdown/render/terminal/formatting.jl b/base/markdown/render/terminal/formatting.jl
new file mode 100644
index 0000000000000..323b99297a973
--- /dev/null
+++ b/base/markdown/render/terminal/formatting.jl
@@ -0,0 +1,96 @@
+# Styles
+
+const text_formats = Dict(
+ :black => "\e[30m",
+ :red => "\e[31m",
+ :green => "\e[32m",
+ :yellow => "\e[33m",
+ :blue => "\e[34m",
+ :magenta => "\e[35m",
+ :cyan => "\e[36m",
+ :white => "\e[37m",
+ :reset => "\e[0m",
+ :bold => "\e[1m",
+ :underline => "\e[4m",
+ :blink => "\e[5m",
+ :negative => "\e[7m")
+
+function with_output_format(f::Function, formats::Vector{Symbol}, io::IO, args...)
+ Base.have_color && for format in formats
+ print(io, get(text_formats, format, ""))
+ end
+ try f(io, args...)
+ finally
+ Base.have_color && print(io, text_formats[:reset])
+ end
+end
+
+with_output_format(f::Function, format::Symbol, args...) =
+ with_output_format(f, [format], args...)
+
+function print_with_format(format, io::IO, x)
+ with_output_format(format, io) do io
+ print(io, x)
+ end
+end
+
+function println_with_format(format, io::IO, x)
+ print_with_format(format, io, x)
+ println(io)
+end
+
+# Wrapping
+
+function ansi_length(s)
+ replace(s, r"\e\[[0-9]+m", "") |> length
+end
+
+words(s) = split(s, " ")
+lines(s) = split(s, "\n")
+
+# This could really be more efficient
+function wrapped_lines(s::String; width = 80, i = 0)
+ if ismatch(r"\n", s)
+ return [map(s->wrapped_lines(s, width = width, i = i), split(s, "\n"))...]
+ end
+ ws = words(s)
+ lines = String[ws[1]]
+ i += ws[1] |> ansi_length
+ for word in ws[2:end]
+ word_length = ansi_length(word)
+ if i + word_length + 1 > width
+ i = word_length
+ push!(lines, word)
+ else
+ i += word_length + 1
+ lines[end] *= " " * word
+ end
+ end
+ return lines
+end
+
+wrapped_lines(f::Function, args...; width = 80, i = 0) =
+ wrapped_lines(sprint(f, args...), width = width, i = 0)
+
+function print_wrapped(io::IO, s...; width = 80, pre = "", i = 0)
+ lines = wrapped_lines(s..., width = width, i = i)
+ println(io, lines[1])
+ for line in lines[2:end]
+ println(io, pre, line)
+ end
+end
+
+print_wrapped(f::Function, io::IO, args...; kws...) = print_wrapped(io, f, args...; kws...)
+
+function print_centred(io::IO, s...; columns = 80, width = columns)
+ lines = wrapped_lines(s..., width = width)
+ for line in lines
+ print(io, " "^(div(columns-ansi_length(line), 2)))
+ println(io, line)
+ end
+end
+
+function centred(s, columns)
+ pad = div(columns - ansi_length(s), 2)
+ " "^pad * s
+end
diff --git a/base/markdown/render/terminal/render.jl b/base/markdown/render/terminal/render.jl
new file mode 100644
index 0000000000000..38dfcb0f61776
--- /dev/null
+++ b/base/markdown/render/terminal/render.jl
@@ -0,0 +1,103 @@
+include("formatting.jl")
+
+const margin = 2
+cols() = Base.tty_size()[2]
+
+function term(io::IO, content::Vector, cols)
+ for md in content[1:end-1]
+ term(io, md, cols)
+ println(io)
+ end
+ term(io, content[end], cols)
+end
+
+term(io::IO, md::MD, columns = cols()) = term(io, md.content, columns)
+
+function term(io::IO, md::Paragraph, columns)
+ print(io, " "^margin)
+ print_wrapped(io, width = columns-2margin, pre = " "^margin) do io
+ terminline(io, md.content)
+ end
+end
+
+function term(io::IO, md::BlockQuote, columns)
+ s = sprint(io->term(io, Block(md.content), columns - 10))
+ for line in split(rstrip(s), "\n")
+ println(io, " "^margin, "|", line)
+ end
+ println(io)
+end
+
+function term(io::IO, md::List, columns)
+ for point in md.items
+ print(io, " "^2margin, "• ")
+ print_wrapped(io, width = columns-(4margin+2), pre = " "^(2margin+2), i = 2margin+2) do io
+ terminline(io, point)
+ end
+ end
+end
+
+function term(io::IO, md::Header{1}, columns)
+ text = terminline(md.text)
+ with_output_format(:bold, io) do io
+ print_centred(io, text, width = columns - 4margin, columns = columns)
+ end
+ print_centred(io, "-"*"–"^min(length(text), div(columns, 2))*"-", columns = columns)
+end
+
+function term{l}(io::IO, md::Header{l}, columns)
+ print(io, "#"^l, " ")
+ terminline(io, md.text)
+ println(io)
+end
+
+function term(io::IO, md::Code, columns)
+ with_output_format(:cyan, io) do io
+ for line in lines(md.code)
+ print(io, " "^margin)
+ println(io, line)
+ end
+ end
+end
+
+term(io::IO, x, _) = writemime(io, MIME"text/plain"(), x)
+
+# Inline Content
+
+terminline(md) = sprint(terminline, md)
+
+function terminline(io::IO, content::Vector)
+ for md in content
+ terminline(io, md)
+ end
+end
+
+function terminline(io::IO, md::String)
+ print_with_format(:normal, io, md)
+end
+
+function terminline(io::IO, md::Bold)
+ print_with_format(:bold, io, md.text)
+end
+
+function terminline(io::IO, md::Italic)
+ print_with_format(:underline, io, md.text)
+end
+
+function terminline(io::IO, md::Image)
+ print(io, "(Image: $(md.alt))")
+end
+
+function terminline(io::IO, md::Link)
+ terminline(io, md.text)
+end
+
+function terminline(io::IO, code::Code)
+ print_with_format(:cyan, io, code.code)
+end
+
+terminline(io::IO, x) = writemime(io, MIME"text/plain"(), x)
+
+# Show in terminal
+
+Base.display(d::Base.REPL.REPLDisplay, md::MD) = term(Base.REPL.outstream(d.repl), md)
diff --git a/base/sysimg.jl b/base/sysimg.jl
index 8c4e5e99f5752..0857ff93dfe5f 100644
--- a/base/sysimg.jl
+++ b/base/sysimg.jl
@@ -219,6 +219,13 @@ include("REPLCompletions.jl")
include("REPL.jl")
include("client.jl")
+# Documentation
+
+include("markdown/Markdown.jl")
+include("docs.jl")
+using .Docs
+using .Markdown
+
# (s)printf macros
include("printf.jl")
importall .Printf