fix incorrect folder name for julia-0.6.x

Former-commit-id: ef2c7401e0876f22d2f7762d182cfbcd5a7d9c70
This commit is contained in:
2018-06-11 03:28:36 -07:00
parent 5e0e436e4e
commit 0e4acfb8f2
722 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
include("block.jl")
include("inline.jl")
@flavor common [list, indentcode, blockquote, admonition, footnote, hashheader, horizontalrule,
paragraph,
linebreak, escapes, inline_code,
asterisk_bold, asterisk_italic, image, footnote_link, link, autolink]

View File

@@ -0,0 +1,356 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
#
# Paragraphs
#
mutable struct Paragraph
content
end
Paragraph() = Paragraph([])
function paragraph(stream::IO, md::MD)
buffer = IOBuffer()
p = Paragraph()
push!(md, p)
skipwhitespace(stream)
prev_char = '\n'
while !eof(stream)
char = read(stream, Char)
if char == '\n' || char == '\r'
char == '\r' && !eof(stream) && Char(peek(stream)) == '\n' && read(stream, Char)
if prev_char == '\\'
write(buffer, '\n')
elseif blankline(stream) || parse(stream, md, breaking = true)
break
else
write(buffer, ' ')
end
else
write(buffer, char)
end
prev_char = char
end
p.content = parseinline(seek(buffer, 0), md)
return true
end
#
# Headers
#
mutable struct Header{level}
text
end
Header(s, level::Int) = Header{level}(s)
Header(s) = Header(s, 1)
@breaking true ->
function hashheader(stream::IO, md::MD)
withstream(stream) do
eatindent(stream) || return false
level = 0
while startswith(stream, '#') level += 1 end
level < 1 || level > 6 && return false
c = ' '
# Allow empty headers, but require a space
!eof(stream) && (c = read(stream, Char); !(c in " \n")) &&
return false
if c != '\n' # Empty header
h = strip(readline(stream))
h = match(r"(.*?)( +#+)?$", h).captures[1]
buffer = IOBuffer()
print(buffer, h)
push!(md.content, Header(parseinline(seek(buffer, 0), md), level))
else
push!(md.content, Header("", level))
end
return true
end
end
function setextheader(stream::IO, md::MD)
withstream(stream) do
eatindent(stream) || return false
header = strip(readline(stream))
isempty(header) && return false
eatindent(stream) || return false
underline = strip(readline(stream))
length(underline) < 3 && return false
u = underline[1]
u in "-=" || return false
all(c -> c == u, underline) || return false
level = (u == '=') ? 1 : 2
push!(md.content, Header(parseinline(header, md), level))
return true
end
end
#
# Code
#
mutable struct Code
language::String
code::String
end
Code(code) = Code("", code)
function indentcode(stream::IO, block::MD)
withstream(stream) do
buffer = IOBuffer()
while !eof(stream)
if startswith(stream, " ") || startswith(stream, "\t")
write(buffer, readline(stream, chomp=false))
elseif blankline(stream)
write(buffer, '\n')
else
break
end
end
code = String(take!(buffer))
!isempty(code) && (push!(block, Code(rstrip(code))); return true)
return false
end
end
# --------
# Footnote
# --------
mutable struct Footnote
id::String
text
end
function footnote(stream::IO, block::MD)
withstream(stream) do
regex = r"^\[\^(\w+)\]:"
str = startswith(stream, regex)
if isempty(str)
return false
else
ref = match(regex, str).captures[1]
buffer = IOBuffer()
write(buffer, readline(stream, chomp=false))
while !eof(stream)
if startswith(stream, " ")
write(buffer, readline(stream, chomp=false))
elseif blankline(stream)
write(buffer, '\n')
else
break
end
end
content = parse(seekstart(buffer)).content
push!(block, Footnote(ref, content))
return true
end
end
end
#
# Quotes
#
mutable struct BlockQuote
content
end
BlockQuote() = BlockQuote([])
# TODO: Laziness
@breaking true ->
function blockquote(stream::IO, block::MD)
withstream(stream) do
buffer = IOBuffer()
empty = true
while eatindent(stream) && startswith(stream, '>')
startswith(stream, " ")
write(buffer, readline(stream, chomp=false))
empty = false
end
empty && return false
md = String(take!(buffer))
push!(block, BlockQuote(parse(md, flavor = config(block)).content))
return true
end
end
# -----------
# Admonitions
# -----------
mutable struct Admonition
category::String
title::String
content::Vector
end
@breaking true ->
function admonition(stream::IO, block::MD)
withstream(stream) do
# Admonition syntax:
#
# !!! category "optional explicit title within double quotes"
# Any number of other indented markdown elements.
#
# This is the second paragraph.
#
startswith(stream, "!!! ") || return false
# Extract the category of admonition and its title:
category, title =
let untitled = r"^([a-z]+)$", # !!! <CATEGORY_NAME>
titled = r"^([a-z]+) \"(.*)\"$", # !!! <CATEGORY_NAME> "<TITLE>"
line = strip(readline(stream))
if ismatch(untitled, line)
m = match(untitled, line)
# When no title is provided we use CATEGORY_NAME, capitalising it.
m.captures[1], ucfirst(m.captures[1])
elseif ismatch(titled, line)
m = match(titled, line)
# To have a blank TITLE provide an explicit empty string as TITLE.
m.captures[1], m.captures[2]
else
# Admonition header is invalid so we give up parsing here and move
# on to the next parser.
return false
end
end
# Consume the following indented (4 spaces) block.
buffer = IOBuffer()
while !eof(stream)
if startswith(stream, " ")
write(buffer, readline(stream, chomp=false))
elseif blankline(stream)
write(buffer, '\n')
else
break
end
end
# Parse the nested block as markdown and create a new Admonition block.
nested = parse(String(take!(buffer)), flavor = config(block))
push!(block, Admonition(category, title, nested.content))
return true
end
end
#
# Lists
#
mutable struct List
items::Vector{Any}
ordered::Int # `-1` is unordered, `>= 0` is ordered.
List(x::AbstractVector, b::Integer) = new(x, b)
List(x::AbstractVector) = new(x, -1)
List(b::Integer) = new(Any[], b)
end
List(xs...) = List(vcat(xs...))
isordered(list::List) = list.ordered >= 0
const BULLETS = r"^ {0,3}(\*|\+|-)( |$)"
const NUM_OR_BULLETS = r"^ {0,3}(\*|\+|-|\d+(\.|\)))( |$)"
@breaking true ->
function list(stream::IO, block::MD)
withstream(stream) do
bullet = startswith(stream, NUM_OR_BULLETS; eat = false)
indent = isempty(bullet) ? (return false) : length(bullet)
# Calculate the starting number and regex to use for bullet matching.
initial, regex =
if ismatch(BULLETS, bullet)
# An unordered list. Use `-1` to flag the list as unordered.
-1, BULLETS
elseif ismatch(r"^ {0,3}\d+(\.|\))( |$)", bullet)
# An ordered list. Either with `1. ` or `1) ` style numbering.
r = contains(bullet, ".") ? r"^ {0,3}(\d+)\.( |$)" : r"^ {0,3}(\d+)\)( |$)"
Base.parse(Int, match(r, bullet).captures[1]), r
else
# Failed to match any bullets. This branch shouldn't actually be needed
# since the `NUM_OR_BULLETS` regex should cover this, but we include it
# simply for thoroughness.
return false
end
# Initialise the empty list object: either ordered or unordered.
list = List(initial)
buffer = IOBuffer() # For capturing nested text for recursive parsing.
newline = false # For checking if we have two consecutive newlines: end of list.
count = 0 # Count of list items. Used to check if we need to push remaining
# content in `buffer` after leaving the `while` loop.
while !eof(stream)
if startswith(stream, "\n")
if newline
# Double newline ends the current list.
pushitem!(list, buffer)
break
else
newline = true
println(buffer)
end
else
newline = false
if startswith(stream, " "^indent)
# Indented text that is part of the current list item.
print(buffer, readline(stream, chomp=false))
else
matched = startswith(stream, regex)
if isempty(matched)
# Unindented text meaning we have left the current list.
pushitem!(list, buffer)
break
else
# Start of a new list item.
count += 1
count > 1 && pushitem!(list, buffer)
print(buffer, readline(stream, chomp=false))
end
end
end
end
count == length(list.items) || pushitem!(list, buffer)
push!(block, list)
return true
end
end
pushitem!(list, buffer) = push!(list.items, parse(String(take!(buffer))).content)
#
# HorizontalRule
#
mutable struct HorizontalRule
end
function horizontalrule(stream::IO, block::MD)
withstream(stream) do
n, rule = 0, ' '
while !eof(stream)
char = read(stream, Char)
char == '\n' && break
isspace(char) && continue
if n==0 || char==rule
rule = char
n += 1
else
return false
end
end
is_hr = (n 3 && rule in "*-")
is_hr && push!(block, HorizontalRule())
return is_hr
end
end

View File

@@ -0,0 +1,175 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
#
# Emphasis
#
mutable struct Italic
text
end
@trigger '*' ->
function asterisk_italic(stream::IO, md::MD)
result = parse_inline_wrapper(stream, "*")
return result === nothing ? nothing : Italic(parseinline(result, md))
end
mutable struct Bold
text
end
@trigger '*' ->
function asterisk_bold(stream::IO, md::MD)
result = parse_inline_wrapper(stream, "**")
return result === nothing ? nothing : Bold(parseinline(result, md))
end
#
# Code
#
@trigger '`' ->
function inline_code(stream::IO, md::MD)
withstream(stream) do
ticks = startswith(stream, r"^(`+)")
result = readuntil(stream, ticks)
if result === nothing
nothing
else
result = strip(result)
# An odd number of backticks wrapping the text will produce a `Code` node, while
# an even number will result in a `LaTeX` node. This allows for arbitary
# backtick combinations to be embedded inside the resulting node, i.e.
#
# `a`, ``a``, `` `a` ``, ``` ``a`` ```, ``` `a` ```, etc.
# ^ ^ ^ ^ ^
# C L L C C with C=Code and L=LaTeX.
#
isodd(length(ticks)) ? Code(result) : LaTeX(result)
end
end
end
#
# Images & Links
#
mutable struct Image
url::String
alt::String
end
@trigger '!' ->
function image(stream::IO, md::MD)
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
mutable struct Link
text
url::String
end
@trigger '[' ->
function link(stream::IO, md::MD)
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, md), url)
end
end
@trigger '[' ->
function footnote_link(stream::IO, md::MD)
withstream(stream) do
regex = r"^\[\^(\w+)\]"
str = startswith(stream, regex)
if isempty(str)
return
else
ref = match(regex, str).captures[1]
return Footnote(ref, nothing)
end
end
end
@trigger '<' ->
function autolink(stream::IO, md::MD)
withstream(stream) do
startswith(stream, '<') || return
url = readuntil(stream, '>')
url nothing && return
_is_link(url) && return Link(url, url)
_is_mailto(url) && return Link(url, url)
return
end
end
# This list is taken from the commonmark spec
# http://spec.commonmark.org/0.19/#absolute-uri
const _allowable_schemes = Set(split("coap doi javascript aaa aaas about acap cap cid crid data dav dict dns file ftp geo go gopher h323 http https iax icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lwz ldap mailto mid msrp msrps mtqp mupdate news nfs ni nih nntp opaquelocktoken pop pres rtsp service session shttp sieve sip sips sms snmp,soap.beep soap.beeps tag tel telnet tftp thismessage tn3270 tip tv urn vemmi ws wss xcon xcon-userid xmlrpc.beep xmlrpc.beeps xmpp z39.50r z39.50s
adiumxtra afp afs aim apt,attachment aw beshare bitcoin bolo callto chrome,chrome-extension com-eventbrite-attendee content cvs,dlna-playsingle dlna-playcontainer dtn dvb ed2k facetime feed finger fish gg git gizmoproject gtalk hcp icon ipn irc irc6 ircs itms jar jms keyparc lastfm ldaps magnet maps market,message mms ms-help msnim mumble mvn notes oid palm paparazzi platform proxy psyc query res resource rmi rsync rtmp secondlife sftp sgn skype smb soldat spotify ssh steam svn teamspeak
things udp unreal ut2004 ventrilo view-source webcal wtai wyciwyg xfire xri ymsgr"))
function _is_link(s::AbstractString)
'<' in s && return false
m = match(r"^(.*)://(\S+?)(:\S*)?$", s)
m nothing && return false
scheme = lowercase(m.captures[1])
return scheme in _allowable_schemes
end
# non-normative regex from the HTML5 spec
const _email_regex = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
function _is_mailto(s::AbstractString)
length(s) < 6 && return false
# slicing strings is a bit risky, but this equality check is safe
lowercase(s[1:6]) == "mailto:" || return false
return ismatch(_email_regex, s[6:end])
end
#
# Punctuation
#
mutable struct LineBreak end
@trigger '\\' ->
function linebreak(stream::IO, md::MD)
if startswith(stream, "\\\n")
return LineBreak()
end
end
@trigger '-' ->
function en_dash(stream::IO, md::MD)
if startswith(stream, "--")
return ""
end
end
const escape_chars = "\\`*_#+-.!{}[]()\$"
@trigger '\\' ->
function escapes(stream::IO, md::MD)
withstream(stream) do
if startswith(stream, "\\") && !eof(stream) && (c = read(stream, Char)) in escape_chars
return string(c)
end
end
end

View File

@@ -0,0 +1,66 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
include("table.jl")
@breaking true ->
function fencedcode(stream::IO, block::MD)
withstream(stream) do
startswith(stream, "~~~", padding = true) || startswith(stream, "```", padding = true) || return false
skip(stream, -1)
ch = read(stream, Char)
trailing = strip(readline(stream))
flavor = lstrip(trailing, ch)
n = 3 + length(trailing) - length(flavor)
# inline code block
ch in flavor && return false
buffer = IOBuffer()
while !eof(stream)
line_start = position(stream)
if startswith(stream, string(ch) ^ n)
if !startswith(stream, string(ch))
if flavor == "math"
push!(block, LaTeX(String(take!(buffer)) |> chomp))
else
push!(block, Code(flavor, String(take!(buffer)) |> chomp))
end
return true
else
seek(stream, line_start)
end
end
write(buffer, readline(stream, chomp=false))
end
return false
end
end
function github_paragraph(stream::IO, md::MD)
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, breaking = true)
break
else
write(buffer, '\n')
end
else
write(buffer, char)
end
end
p.content = parseinline(seek(buffer, 0), md)
return true
end
@flavor github [list, indentcode, blockquote, admonition, footnote, fencedcode, hashheader,
github_table, github_paragraph,
linebreak, escapes, en_dash, inline_code, asterisk_bold,
asterisk_italic, image, footnote_link, link, autolink]

View File

@@ -0,0 +1,168 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
mutable struct Table
rows::Vector{Vector{Any}}
align::Vector{Symbol}
end
function parserow(stream::IO)
withstream(stream) do
line = readline(stream)
row = split(line, r"(?<!\\)\|")
length(row) == 1 && return
isempty(row[1]) && shift!(row)
map!(x -> strip(replace(x, "\\|", "|")), row, row)
isempty(row[end]) && pop!(row)
return row
end
end
function rowlength!(row, len)
while length(row) < len push!(row, "") end
while length(row) > len pop!(row) end
return row
end
const default_align = :r
function parsealign(row)
align = Symbol[]
for s in row
(length(s) 3 && s Set("-:")) || return
push!(align,
s[1] == ':' ? (s[end] == ':' ? :c : :l) :
s[end] == ':' ? :r :
default_align)
end
return align
end
function github_table(stream::IO, md::MD)
withstream(stream) do
skipblank(stream)
rows = []
cols = 0
align = nothing
while (row = parserow(stream)) !== nothing
if length(rows) == 0
isempty(row[1]) && return false
cols = length(row)
end
if align === nothing && length(rows) == 1 # Must have a --- row
align = parsealign(row)
(align === nothing || length(align) != cols) && return false
else
push!(rows, map(x -> parseinline(x, md), rowlength!(row, cols)))
end
end
length(rows) <= 1 && return false
push!(md, Table(rows, align))
return true
end
end
function html(io::IO, md::Table)
withtag(io, :table) do
for (i, row) in enumerate(md.rows)
withtag(io, :tr) do
for c in md.rows[i]
withtag(io, i == 1 ? :th : :td) do
htmlinline(io, c)
end
end
end
end
end
end
mapmap(f, xss) = map(xs->map(f, xs), xss)
colwidths(rows; len = length, min = 0) =
reduce((x,y) -> max.(x,y), [min; convert(Vector{Vector{Int}}, mapmap(len, rows))])
padding(width, twidth, a) =
a == :l ? (0, twidth - width) :
a == :r ? (twidth - width, 0) :
a == :c ? (floor(Int, (twidth-width)/2), ceil(Int, (twidth-width)/2)) :
error("Invalid alignment $a")
function padcells!(rows, align; len = length, min = 0)
widths = colwidths(rows, len = len, min = min)
for i = 1:length(rows), j = indices(rows[1],1)
cell = rows[i][j]
lpad, rpad = padding(len(cell), widths[j], align[j])
rows[i][j] = " "^lpad * cell * " "^rpad
end
return rows
end
_dash(width, align) =
align == :l ? ":" * "-"^width * " " :
align == :r ? " " * "-"^width * ":" :
align == :c ? ":" * "-"^width * ":" :
throw(ArgumentError("Invalid alignment $align"))
function plain(io::IO, md::Table)
cells = mapmap(md.rows) do each
replace(plaininline(each), "|", "\\|")
end
padcells!(cells, md.align, len = length, min = 3)
for i = indices(cells,1)
print(io, "| ")
join(io, cells[i], " | ")
println(io, " |")
if i == 1
print(io, "|")
join(io, [_dash(length(cells[i][j]), md.align[j]) for j = indices(cells[1],1)], "|")
println(io, "|")
end
end
end
function rst(io::IO, md::Table)
cells = mapmap(rstinline, md.rows)
padcells!(cells, md.align, len = length, min = 3)
single = ["-"^length(c) for c in cells[1]]
double = ["="^length(c) for c in cells[1]]
function print_row(row, row_sep, col_sep)
print(io, col_sep, row_sep)
join(io, row, string(row_sep, col_sep, row_sep))
println(io, row_sep, col_sep)
end
print_row(single, '-', '+')
for i = 1:length(cells)
print_row(cells[i], ' ', '|')
i 1 ? print_row(double, '=', '+') :
print_row(single, '-', '+')
end
end
function term(io::IO, md::Table, columns)
cells = mapmap(terminline, md.rows)
padcells!(cells, md.align, len = ansi_length)
for i = 1:length(cells)
join(io, cells[i], " ")
println(io)
if i == 1
join(io, [""^ansi_length(cells[i][j]) for j = 1:length(cells[1])], " ")
println(io)
end
end
end
function latex(io::IO, md::Table)
wrapblock(io, "tabular") do
align = md.align
println(io, "{$(join(align, " | "))}")
for (i, row) in enumerate(md.rows)
for (j, cell) in enumerate(row)
j != 1 && print(io, " & ")
latexinline(io, cell)
end
println(io, " \\\\")
if i == 1
println("\\hline")
end
end
end
end

View File

@@ -0,0 +1,35 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
mutable struct LaTeX
formula::String
end
@trigger '$' ->
function tex(stream::IO, md::MD)
result = parse_inline_wrapper(stream, "\$", rep = true)
return result === nothing ? nothing : LaTeX(result)
end
function blocktex(stream::IO, md::MD)
withstream(stream) do
ex = tex(stream, md)
if ex nothing
return false
else
push!(md, ex)
return true
end
end
end
show(io::IO, tex::LaTeX) =
print(io, '$', tex.formula, '$')
latex(io::IO, tex::LaTeX) =
println(io, "\$\$", tex.formula, "\$\$")
latexinline(io::IO, tex::LaTeX) =
print(io, '$', tex.formula, '$')
term(io::IO, tex::LaTeX, cols) = println_with_format(:magenta, io, tex.formula)
terminline(io::IO, tex::LaTeX) = print_with_format(:magenta, io, tex.formula)

View File

@@ -0,0 +1,15 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# 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 [blocktex, blockinterp, hashheader, list, indentcode, fencedcode,
blockquote, admonition, footnote, github_table, horizontalrule, setextheader, paragraph,
linebreak, escapes, tex, interp, en_dash, inline_code,
asterisk_bold, asterisk_italic, image, footnote_link, link, autolink]

View File

@@ -0,0 +1,48 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function Base.parse(stream::IO; greedy::Bool = true, raise::Bool = true)
pos = position(stream)
ex, Δ = Base.parse(readstring(stream), 1, greedy = greedy, raise = raise)
seek(stream, pos + Δ - 1)
return ex
end
function interpinner(stream::IO, greedy = false)
startswith(stream, '$') || return
(eof(stream) || Char(peek(stream)) in whitespace) && return
try
return Base.parse(stream::IOBuffer, greedy = greedy)
catch e
return
end
end
@trigger '$' ->
function interp(stream::IO, md::MD)
withstream(stream) do
ex = interpinner(stream)
return ex
end
end
function blockinterp(stream::IO, md::MD)
withstream(stream) do
ex = interpinner(stream)
if ex nothing
return false
else
push!(md, ex)
return true
end
end
end
toexpr(x) = x
toexpr(xs::Vector{Any}) = Expr(:call, GlobalRef(Base,:vector_any), map(toexpr, xs)...)
for T in Any[MD, Paragraph, Header, Link, Bold, Italic]
@eval function toexpr(md::$T)
Expr(:call, typeof(md), $(map(x->:(toexpr(md.$x)), fieldnames(Base.unwrap_unionall(T)))...))
end
end

View File

@@ -0,0 +1,67 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
module Markdown
import Base: show, ==
import Core: @doc_str
include(joinpath("parse", "config.jl"))
include(joinpath("parse", "util.jl"))
include(joinpath("parse", "parse.jl"))
include(joinpath("Common", "Common.jl"))
include(joinpath("GitHub", "GitHub.jl"))
include(joinpath("IPython", "IPython.jl"))
include(joinpath("Julia", "Julia.jl"))
include(joinpath("render", "plain.jl"))
include(joinpath("render", "html.jl"))
include(joinpath("render", "latex.jl"))
include(joinpath("render", "rst.jl"))
include(joinpath("render", "terminal", "render.jl"))
export readme, license, @md_str, @doc_str
parse(markdown::AbstractString; flavor = julia) = parse(IOBuffer(markdown), flavor = flavor)
parse_file(file::AbstractString; flavor = julia) = parse(readstring(file), flavor = flavor)
readme(pkg::AbstractString; flavor = github) = parse_file(Pkg.dir(pkg, "README.md"), flavor = flavor)
readme(pkg::Module; flavor = github) = readme(string(pkg), flavor = flavor)
license(pkg::AbstractString; 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
doc_str(md, file, mod) = (md.meta[:path] = file; md.meta[:module] = mod; md)
doc_str(md::AbstractString, file, mod) = doc_str(parse(md), file, mod)
macro doc_str(s::AbstractString, t...)
:($(doc_str)($(mdexpr(s, t...)), $(Base).@__FILE__, $(current_module)()))
end
function Base.display(d::Base.REPL.REPLDisplay, md::Vector{MD})
for md in md
display(d, md)
end
end
end

View File

@@ -0,0 +1,82 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
const InnerConfig = Dict{Char, Vector{Function}}
mutable struct 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(x::T, ts...) where {T} = 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

View File

@@ -0,0 +1,96 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
mutable struct MD
content::Vector{Any}
meta::Dict{Any, Any}
MD(content::AbstractVector, meta::Dict = Dict()) =
new(content, meta)
end
MD(xs...) = MD(vcat(xs...))
function MD(cfg::Config, xs...)
md = MD(xs...)
md.meta[:config] = cfg
return md
end
config(md::MD) = md.meta[:config]::Config
# 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)
==(a::MD, b::MD) = (html(a) == html(b))
# 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 parseinline(stream::IO, md::MD, parsers::Vector{Function})
for parser in parsers
inner = parser(stream, md)
inner nothing || return inner
end
end
function parseinline(stream::IO, md::MD, config::Config)
content = []
buffer = IOBuffer()
while !eof(stream)
# FIXME: this is broken if we're looking for non-ASCII
# characters because peek only returns a single byte.
char = Char(peek(stream))
if haskey(config.inner, char) &&
(inner = parseinline(stream, md, config.inner[char])) !== nothing
c = String(take!(buffer))
!isempty(c) && push!(content, c)
buffer = IOBuffer()
push!(content, inner)
else
write(buffer, read(stream, Char))
end
end
c = String(take!(buffer))
!isempty(c) && push!(content, c)
return content
end
parseinline(s::AbstractString, md::MD, c::Config) =
parseinline(IOBuffer(s), md, c)
parseinline(s, md::MD) = parseinline(s, md, config(md))
# 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) && return true
end
return false
end
parse(stream::IO, block::MD; breaking = false) =
parse(stream, block, config(block), breaking = breaking)
function parse(stream::IO; flavor = julia)
isa(flavor, Symbol) && (flavor = flavors[flavor])
markdown = MD(flavor)
while parse(stream, markdown, flavor) end
return markdown
end

View File

@@ -0,0 +1,206 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
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) && (Char(peek(io)) in whitespace || (newlines && peek(io) == UInt8('\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 == '\r' && (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, unless allowempty,
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)
length(l) == 0 && return allowempty
result = allowempty
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::AbstractString; 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 !eof(stream) && peek(stream) == UInt8(c)
eat && read(stream, Char)
return true
else
return false
end
end
function startswith(stream::IO, ss::Vector{<:AbstractString}; kws...)
any(s->startswith(stream, s; kws...), ss)
end
function startswith(stream::IO, r::Regex; eat = true, padding = false)
@assert Base.startswith(r.pattern, "^")
start = position(stream)
padding && skipwhitespace(stream)
line = 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
"""
Consume the standard allowed markdown indent of
three spaces. Returns false if there are more than
three present.
"""
function eatindent(io::IO, n = 3)
withstream(io) do
m = 0
while startswith(io, ' ') m += 1 end
return m <= n
end
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 String(take!(buffer))
else
count -= 1
write(buffer, delimiter)
continue
end
end
char = read(stream, Char)
char == match && (count += 1)
!newlines && char == '\n' && break
write(buffer, char)
end
end
end
# TODO: refactor this. If we're going to assume
# the delimiter is a single character + a minimum
# repeat we may as well just pass that into the
# function.
"""
Parse a symmetrical delimiter which wraps words.
i.e. `*word word*` but not `*word * word`.
`repeat` specifies whether the delimiter can be repeated.
Escaped delimiters are not yet supported.
"""
function parse_inline_wrapper(stream::IO, delimiter::AbstractString; rep = false)
delimiter, nmin = string(delimiter[1]), length(delimiter)
withstream(stream) do
if position(stream) >= 1
# check the previous byte isn't a delimiter
skip(stream, -1)
(read(stream, Char) in delimiter) && return nothing
end
n = nmin
startswith(stream, delimiter^n) || return nothing
while startswith(stream, delimiter); n += 1; end
!rep && n > nmin && return nothing
!eof(stream) && Char(peek(stream)) in whitespace && return nothing
buffer = IOBuffer()
while !eof(stream)
char = read(stream, Char)
write(buffer, char)
if !(char in whitespace || char == '\n' || char in delimiter) && startswith(stream, delimiter^n)
trailing = 0
while startswith(stream, delimiter); trailing += 1; end
trailing == 0 && return String(take!(buffer))
write(buffer, delimiter ^ (n + trailing))
end
end
end
end
function showrest(io::IO)
start = position(io)
show(readstring(io))
println()
seek(io, start)
end

View File

@@ -0,0 +1,191 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
include("rich.jl")
# Utils
function withtag(f, io::IO, tag, attrs...)
print(io, "<$tag")
for (attr, value) in attrs
print(io, " ")
htmlesc(io, attr)
print(io, "=\"")
htmlesc(io, value)
print(io, "\"")
end
f === nothing && return print(io, " />")
print(io, ">")
f()
print(io, "</$tag>")
end
tag(io::IO, tag, attrs...) = withtag(nothing, io, tag, attrs...)
const _htmlescape_chars = Dict('<'=>"&lt;", '>'=>"&gt;",
'"'=>"&quot;", '&'=>"&amp;",
# ' '=>"&nbsp;",
)
for ch in "'`!\$\%()=+{}[]"
_htmlescape_chars[ch] = "&#$(Int(ch));"
end
function htmlesc(io::IO, s::AbstractString)
# s1 = replace(s, r"&(?!(\w+|\#\d+);)", "&amp;")
for ch in s
print(io, get(_htmlescape_chars, ch, ch))
end
end
function htmlesc(io::IO, s::Symbol)
htmlesc(io, string(s))
end
function htmlesc(io::IO, xs::Union{AbstractString,Symbol}...)
for s in xs
htmlesc(io, s)
end
end
function htmlesc(s::Union{AbstractString,Symbol})
sprint(htmlesc, s)
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
maybe_lang = !isempty(code.language) ? Any[:class=>"language-$(code.language)"] : []
withtag(io, :code, maybe_lang...) do
htmlesc(io, code.code)
# TODO should print newline if this is longer than one line ?
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
println(io)
html(io, md.content)
end
end
function html(io::IO, f::Footnote)
withtag(io, :div, :class => "footnote", :id => "footnote-$(f.id)") do
withtag(io, :p, :class => "footnote-title") do
print(io, f.id)
end
html(io, f.text)
end
end
function html(io::IO, md::Admonition)
withtag(io, :div, :class => "admonition $(md.category)") do
withtag(io, :p, :class => "admonition-title") do
print(io, md.title)
end
html(io, md.content)
end
end
function html(io::IO, md::List)
maybe_attr = md.ordered > 1 ? Any[:start => string(md.ordered)] : []
withtag(io, isordered(md) ? :ol : :ul, maybe_attr...) do
for item in md.items
println(io)
withtag(io, :li) do
html(io, item)
end
end
println(io)
end
end
function html(io::IO, md::HorizontalRule)
tag(io, :hr)
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
htmlesc(io, code.code)
end
end
function htmlinline(io::IO, md::Union{Symbol,AbstractString})
htmlesc(io, md)
end
function htmlinline(io::IO, md::Bold)
withtag(io, :strong) do
htmlinline(io, md.text)
end
end
function htmlinline(io::IO, md::Italic)
withtag(io, :em) do
htmlinline(io, md.text)
end
end
function htmlinline(io::IO, md::Image)
tag(io, :img, :src=>md.url, :alt=>md.alt)
end
function htmlinline(io::IO, f::Footnote)
withtag(io, :a, :href => "#footnote-$(f.id)", :class => "footnote") do
print(io, "[", f.id, "]")
end
end
function htmlinline(io::IO, link::Link)
withtag(io, :a, :href=>link.url) do
htmlinline(io, link.text)
end
end
function htmlinline(io::IO, br::LineBreak)
tag(io, :br)
end
htmlinline(io::IO, x) = tohtml(io, x)
# API
export html
html(md) = sprint(html, md)
function show(io::IO, ::MIME"text/html", md::MD)
withtag(io, :div, :class=>"markdown") do
html(io, md)
end
end

View File

@@ -0,0 +1,172 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
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
# Block elements
latex(io::IO, md::MD) = latex(io, md.content)
function latex(io::IO, content::Vector)
for c in content
latex(io, c)
end
end
function latex{l}(io::IO, header::Header{l})
tag = l < 4 ? "sub"^(l-1) * "section" : "sub"^(l-4) * "paragraph"
wrapinline(io, tag) do
latexinline(io, header.text)
end
println(io)
end
function latex(io::IO, code::Code)
wrapblock(io, "verbatim") do
# TODO latex escape
println(io, code.code)
end
end
function latexinline(io::IO, code::Code)
wrapinline(io, "texttt") do
print(io, latexesc(code.code))
end
end
function latex(io::IO, md::Paragraph)
for md in md.content
latexinline(io, md)
end
println(io)
println(io)
end
function latex(io::IO, md::BlockQuote)
wrapblock(io, "quote") do
latex(io, md.content)
end
end
function latex(io::IO, f::Footnote)
print(io, "\\footnotetext[", f.id, "]{")
latex(io, f.text)
println(io, "}")
end
function latex(io::IO, md::Admonition)
wrapblock(io, "quote") do
wrapinline(io, "textbf") do
print(io, md.category)
end
println(io, "\n\n", md.title, "\n")
latex(io, md.content)
end
end
function latex(io::IO, md::List)
# `\begin{itemize}` is used here for both ordered and unordered lists since providing
# custom starting numbers for enumerated lists is simpler to do by manually assigning
# each number to `\item` ourselves rather than using `\setcounter{enumi}{<start>}`.
#
# For an ordered list starting at 5 the following will be generated:
#
# \begin{itemize}
# \item[5. ] ...
# \item[6. ] ...
# ...
# \end{itemize}
#
pad = ndigits(md.ordered + length(md.items)) + 2
fmt = n -> (isordered(md) ? "[$(rpad("$(n + md.ordered - 1).", pad))]" : "")
wrapblock(io, "itemize") do
for (n, item) in enumerate(md.items)
print(io, "\\item$(fmt(n)) ")
latex(io, item)
n < length(md.items) && println(io)
end
end
end
function show(io::IO, ::MIME"text/latex", md::HorizontalRule)
println(io, "\\rule{\\textwidth}{1pt}")
end
# Inline elements
function latexinline(io::IO, md::Vector)
for c in md
latexinline(io, c)
end
end
function latexinline(io::IO, md::AbstractString)
latexesc(io, md)
end
function latexinline(io::IO, md::Bold)
wrapinline(io, "textbf") do
latexinline(io, md.text)
end
end
function latexinline(io::IO, md::Italic)
wrapinline(io, "emph") do
latexinline(io, md.text)
end
end
function latexinline(io::IO, md::Image)
wrapblock(io, "figure") do
println(io, "\\centering")
wrapinline(io, "includegraphics") do
print(io, md.url)
end
println(io)
wrapinline(io, "caption") do
latexinline(io, md.alt)
end
println(io)
end
end
latexinline(io::IO, f::Footnote) = print(io, "\\footnotemark[", f.id, "]")
function latexinline(io::IO, md::Link)
wrapinline(io, "href") do
print(io, md.url)
end
print(io, "{")
latexinline(io, md.text)
print(io, "}")
end
const _latexescape_chars = Dict{Char, AbstractString}(
'~'=>"{\\sim}", '^'=>"\\^{}", '\\'=>"{\\textbackslash}")
for ch in "&%\$#_{}"
_latexescape_chars[ch] = "\\$ch"
end
function latexesc(io, s::AbstractString)
for ch in s
print(io, get(_latexescape_chars, ch, ch))
end
end
latex(md) = sprint(latex, md)
latexinline(md) = sprint(latexinline, md)
latexesc(s) = sprint(latexesc, s)
show(io::IO, ::MIME"text/latex", md::MD) = latex(io, md)

View File

@@ -0,0 +1,140 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
plain(x) = sprint(plain, x)
function plain(io::IO, content::Vector)
isempty(content) && return
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(io::IO, header::Header{l}) where l
print(io, "#"^l*" ")
plaininline(io, header.text)
println(io)
end
function plain(io::IO, code::Code)
# If the code includes a fenced block this will break parsing,
# so it must be enclosed by a longer ````-sequence.
n = mapreduce(length, max, 2, matchall(r"^`+"m, code.code)) + 1
println(io, "`" ^ n, code.language)
println(io, code.code)
println(io, "`" ^ n)
end
function plain(io::IO, p::Paragraph)
plaininline(io, p.content)
println(io)
end
function plain(io::IO, list::List)
for (i, item) in enumerate(list.items)
print(io, isordered(list) ? "$(i + list.ordered - 1). " : " * ")
lines = split(rstrip(sprint(plain, item)), "\n")
for (n, line) in enumerate(lines)
print(io, (n == 1 || isempty(line)) ? "" : " ", line)
n < length(lines) && println(io)
end
println(io)
end
end
function plain(io::IO, q::BlockQuote)
s = sprint(plain, q.content)
for line in split(rstrip(s), "\n")
println(io, isempty(line) ? ">" : "> ", line)
end
println(io)
end
function plain(io::IO, f::Footnote)
print(io, "[^", f.id, "]:")
s = sprint(plain, f.text)
lines = split(rstrip(s), "\n")
# Single line footnotes are printed on the same line as their label
# rather than taking up an additional line.
if length(lines) == 1
println(io, " ", lines[1])
else
println(io)
for line in lines
println(io, isempty(line) ? "" : " ", line)
end
println(io)
end
end
function plain(io::IO, md::Admonition)
s = sprint(plain, md.content)
title = md.title == ucfirst(md.category) ? "" : " \"$(md.title)\""
println(io, "!!! ", md.category, title)
for line in split(rstrip(s), "\n")
println(io, isempty(line) ? "" : " ", line)
end
println(io)
end
function plain(io::IO, md::HorizontalRule)
println(io, "-" ^ 3)
end
function plain(io::IO, l::LaTeX)
println(io, '$', '$')
println(io, l.formula)
println(io, '$', '$')
end
function plain(io::IO, md)
show(io, MIME"text/plain"(), md)
println(io)
end
# Inline elements
plaininline(x) = sprint(plaininline, x)
function plaininline(io::IO, md...)
for el in md
plaininline(io, el)
end
end
plaininline(io::IO, md::Vector) = !isempty(md) && plaininline(io, md...)
plaininline(io::IO, f::Footnote) = print(io, "[^", f.id, "]")
plaininline(io::IO, link::Link) = plaininline(io, "[", link.text, "](", link.url, ")")
plaininline(io::IO, md::Image) = plaininline(io, "![", md.alt, "](", md.url, ")")
plaininline(io::IO, s::AbstractString) = print(io, s)
plaininline(io::IO, md::Bold) = plaininline(io, "**", md.text, "**")
plaininline(io::IO, md::Italic) = plaininline(io, "*", md.text, "*")
function plaininline(io::IO, md::Code)
if contains(md.code, "`")
n = maximum(length(m) for m in matchall(r"(`+)", md.code))
s = "`"^((iseven(n) ? 1 : 2) + n)
print(io, s, Base.startswith(md.code, "`") ? " " : "")
print(io, md.code, endswith(md.code, "`") ? " " : "", s)
else
print(io, "`", md.code, "`")
end
end
plaininline(io::IO, br::LineBreak) = println(io)
plaininline(io::IO, x) = show(io, MIME"text/plain"(), x)
# show
Base.show(io::IO, md::MD) = plain(io, md)
Base.show(io::IO, ::MIME"text/markdown", md::MD) = plain(io, md)

View File

@@ -0,0 +1,30 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
function tohtml(io::IO, m::MIME"text/html", x)
show(io, m, x)
end
function tohtml(io::IO, m::MIME"text/plain", x)
htmlesc(io, sprint(show, m, x))
end
function tohtml(io::IO, m::MIME"image/png", img)
print(io, """<img src="data:image/png;base64,""")
print(io, stringmime(m, img))
print(io, "\" />")
end
function tohtml(io::IO, m::MIME"image/svg+xml", img)
show(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)

View File

@@ -0,0 +1,145 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
rst(x) = sprint(rst, x)
function rst(io::IO, content::Vector)
isempty(content) && return
for md in content[1:end-1]
rst(io, md)
println(io)
end
rst(io, content[end])
end
rst(io::IO, md::MD) = rst(io, md.content)
function rst{l}(io::IO, header::Header{l})
s = rstinline(header.text)
println(io, s)
println(io, string("*=-~:.^"[l])^length(s))
println(io)
end
function rst(io::IO, code::Code)
if code.language == "jldoctest"
println(io, ".. doctest::\n")
elseif code.language != "rst"
println(io, ".. code-block:: julia\n")
end
for l in lines(code.code)
println(io, " ", l)
end
end
function rst(io::IO, p::Paragraph)
rstinline(io, p.content)
println(io)
end
function rst(io::IO, list::List)
for (i, item) in enumerate(list.items)
bullet = isordered(list) ? "$(i + list.ordered - 1). " : "* "
print(io, bullet)
lines = split(rstrip(sprint(rst, item)), '\n')
for (n, line) in enumerate(lines)
print(io, (n == 1 || isempty(line)) ? "" : " "^length(bullet), line)
n < length(lines) && println(io)
end
println(io)
end
end
function rst(io::IO, q::BlockQuote)
s = sprint(rst, q.content)
for line in split(rstrip(s), "\n")
println(io, " ", line)
end
println(io)
end
function rst(io::IO, f::Footnote)
print(io, ".. [", f.id, "]")
s = sprint(rst, f.text)
lines = split(rstrip(s), "\n")
# Single line footnotes are printed on the same line as their label
# rather than taking up an additional line.
if length(lines) == 1
println(io, " ", lines[1])
else
println(io)
for line in lines
println(io, isempty(line) ? "" : " ", rstrip(line))
end
println(io)
end
end
function rst(io::IO, md::Admonition)
s = sprint(rst, md.content)
title = md.title == ucfirst(md.category) ? "" : md.title
println(io, ".. ", md.category, "::", isempty(title) ? "" : " $title")
for line in split(rstrip(s), "\n")
println(io, isempty(line) ? "" : " ", line)
end
println(io)
end
function rst(io::IO, md::HorizontalRule)
println(io, "" ^ 5)
end
function rst(io::IO, l::LaTeX)
println(io, ".. math::\n")
for l in lines(l.formula)
println(io, " ", l)
end
end
rst(io::IO, md) = show(io, "text/rst", md)
# Inline elements
rstinline(x) = sprint(rstinline, x)
function rstinline(io::IO, md...)
wasCode = false
for el in md
wasCode && isa(el, AbstractString) && !Base.startswith(el, " ") && print(io, "\\ ")
wasCode = (isa(el, Code) || isa(el, LaTeX) || isa(el, Link)) && (wasCode = true)
rstinline(io, el)
end
end
rstinline(io::IO, md::Vector) = !isempty(md) && rstinline(io, md...)
# rstinline(io::IO, md::Image) = rstinline(io, ".. image:: ", md.url)
function rstinline(io::IO, md::Link)
if ismatch(r":(func|obj|ref|exc|class|const|data):`\.*", md.url)
rstinline(io, md.url)
else
rstinline(io, "`", md.text, " <", md.url, ">`_")
end
end
rstinline(io::IO, f::Footnote) = print(io, "[", f.id, "]_")
rstescape(s) = replace(s, "\\", "\\\\")
rstinline(io::IO, s::AbstractString) = print(io, rstescape(s))
rstinline(io::IO, md::Bold) = rstinline(io, "**", md.text, "**")
rstinline(io::IO, md::Italic) = rstinline(io, "*", md.text, "*")
rstinline(io::IO, md::Code) = print(io, "``", md.code, "``")
rstinline(io::IO, br::LineBreak) = println(io)
rstinline(io::IO, l::LaTeX) = print(io, ":math:`", l.formula, "`")
rstinline(io::IO, x) = show(io, MIME"text/rst"(), x)
# show
Base.show(io::IO, ::MIME"text/rst", md::MD) = rst(io, md)

View File

@@ -0,0 +1,107 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
# Styles
const text_formats = Dict(
:black => ("\e[30m", "\e[39m"),
:red => ("\e[31m", "\e[39m"),
:green => ("\e[32m", "\e[39m"),
:yellow => ("\e[33m", "\e[39m"),
:blue => ("\e[34m", "\e[39m"),
:magenta => ("\e[35m", "\e[39m"),
:cyan => ("\e[36m", "\e[39m"),
:white => ("\e[37m", "\e[39m"),
:reset => ("\e[0m", "\e[0m"),
:bold => ("\e[1m", "\e[22m"),
:underline => ("\e[4m", "\e[24m"),
:blink => ("\e[5m", "\e[25m"),
:negative => ("\e[7m", "\e[27m"))
function with_output_format(f::Function, formats::Vector{Symbol}, io::IO, args...)
Base.have_color && for format in formats
haskey(text_formats, format) &&
print(io, text_formats[format][1])
end
try f(io, args...)
finally
Base.have_color && for format in formats
haskey(text_formats, format) &&
print(io, text_formats[format][2])
end
end
end
with_output_format(f::Function, format::Symbol, args...) =
with_output_format(f, [format], args...)
with_output_format(format, f::Function, 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::AbstractString; width = 80, i = 0)
if ismatch(r"\n", s)
return vcat(map(s->wrapped_lines(s, width = width, i = i), split(s, "\n"))...)
end
ws = words(s)
lines = AbstractString[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
length(lines), length(pre) + ansi_length(lines[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
length(lines), length(pre) + length(lines[end])
end
function centred(s, columns)
pad = div(columns - ansi_length(s), 2)
" "^pad * s
end

View File

@@ -0,0 +1,148 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license
include("formatting.jl")
const margin = 2
cols(io) = displaysize(io)[2]
function term(io::IO, content::Vector, cols)
isempty(content) && return
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(io)) = 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(term, md.content, columns - 10)
for line in split(rstrip(s), "\n")
println(io, " "^margin, "|", line)
end
end
function term(io::IO, md::Admonition, columns)
print(io, " "^margin, "| ")
with_output_format(:bold, print, io, isempty(md.title) ? md.category : md.title)
println(io, "\n", " "^margin, "|")
s = sprint(term, md.content, columns - 10)
for line in split(rstrip(s), "\n")
println(io, " "^margin, "|", line)
end
end
function term(io::IO, f::Footnote, columns)
print(io, " "^margin, "| ")
with_output_format(:bold, print, io, "[^$(f.id)]")
println(io, "\n", " "^margin, "|")
s = sprint(term, f.text, columns - 10)
for line in split(rstrip(s), "\n")
println(io, " "^margin, "|", line)
end
end
function term(io::IO, md::List, columns)
for (i, point) in enumerate(md.items)
print(io, " "^2margin, isordered(md) ? "$(i + md.ordered - 1). " : "")
print_wrapped(io, width = columns-(4margin+2), pre = " "^(2margin+2),
i = 2margin+2) do io
term(io, point, columns - 10)
end
end
end
function _term_header(io::IO, md, char, columns)
text = terminline(md.text)
with_output_format(:bold, io) do io
print(io, " "^(2margin), " ")
line_no, lastline_width = print_wrapped(io, text,
width=columns - 4margin; pre=" ")
line_width = min(1 + lastline_width, columns)
if line_no > 1
line_width = max(line_width, div(columns, 3))
end
char != ' ' && println(io, " "^(2margin), string(char) ^ line_width)
end
end
const _header_underlines = collect("≡=-⋅ ")
# TODO settle on another option with unicode e.g. "≡=≃–∼⋅" ?
function term{l}(io::IO, md::Header{l}, columns)
underline = _header_underlines[l]
_term_header(io, md, underline, columns)
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
function term(io::IO, br::LineBreak, columns)
println(io)
end
function term(io::IO, br::HorizontalRule, columns)
println(io, " " ^ margin, "-" ^ (columns - 2margin))
end
term(io::IO, x, _) = show(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::AbstractString)
print(io, replace(md, r"[\s\t\n]+", " "))
end
function terminline(io::IO, md::Bold)
with_output_format(:bold, terminline, io, md.text)
end
function terminline(io::IO, md::Italic)
with_output_format(:underline, terminline, io, md.text)
end
function terminline(io::IO, md::LineBreak)
println(io)
end
function terminline(io::IO, md::Image)
terminline(io, "(Image: $(md.alt))")
end
terminline(io::IO, f::Footnote) = with_output_format(:bold, terminline, io, "[^$(f.id)]")
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) = show(io, MIME"text/plain"(), x)
# Show in terminal
Base.display(d::Base.REPL.REPLDisplay, md::MD) = term(Base.REPL.outstream(d.repl), md)