diff --git a/base/markdown/Common/Common.jl b/base/markdown/Common/Common.jl index 6b5807775f347..3546c9e50f55e 100644 --- a/base/markdown/Common/Common.jl +++ b/base/markdown/Common/Common.jl @@ -1,10 +1,21 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license -include("block.jl") -include("inline.jl") +include("blockquote.jl") +include("bold.jl") +include("code.jl") +include("escapes.jl") +include("header.jl") +include("horizontalrule.jl") +include("image.jl") +include("italic.jl") +include("linebreak.jl") +include("link.jl") +include("list.jl") +include("paragraph.jl") +include("vector.jl") @flavor common [list, indentcode, blockquote, hashheader, horizontalrule, - paragraph, + fencedcode, paragraph, linebreak, escapes, inline_code, asterisk_bold, asterisk_italic, image, link] diff --git a/base/markdown/Common/block.jl b/base/markdown/Common/block.jl deleted file mode 100644 index 0b735f0c7f8fb..0000000000000 --- a/base/markdown/Common/block.jl +++ /dev/null @@ -1,238 +0,0 @@ -# This file is a part of Julia. License is MIT: http://julialang.org/license - -# –––––––––– -# Paragraphs -# –––––––––– - -type Paragraph - content -end - -Paragraph() = Paragraph([]) - -function paragraph(stream::IO, md::MD) - 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, breaking = true) - break - else - write(buffer, ' ') - end - else - write(buffer, char) - end - end - p.content = parseinline(seek(buffer, 0), md) - 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) - 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 = readline(stream) |> strip - 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 = readline(stream) |> strip - header == "" && return false - - eatindent(stream) || return false - underline = readline(stream) |> strip - 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 -# –––– - -type Code - language::UTF8String - code::UTF8String -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)) - elseif blankline(stream) - write(buffer, '\n') - else - break - end - end - code = takebuf_string(buffer) - !isempty(code) && (push!(block, Code(rstrip(code))); return true) - return false - end -end - -# –––––– -# Quotes -# –––––– - -type 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)) - empty = false - end - empty && return false - - md = takebuf_string(buffer) - push!(block, BlockQuote(parse(md, flavor = config(block)).content)) - return true - end -end - -# ––––– -# Lists -# ––––– - -type List - items::Vector{Any} - ordered::Bool - - List(x::AbstractVector, b::Bool) = new(x, b) - List(x::AbstractVector) = new(x, false) - List(b::Bool) = new(Any[], b) -end - -List(xs...) = List(vcat(xs...)) - -const bullets = "*•+-" -const num_or_bullets = r"^(\*|•|\+|-|\d+(\.|\))) " - -# Todo: ordered lists, inline formatting -function list(stream::IO, block::MD) - withstream(stream) do - eatindent(stream) || return false - b = startswith(stream, num_or_bullets) - (b == nothing || b == "") && return false - ordered = !(b[1] in bullets) - if ordered - b = b[end - 1] == '.' ? r"^\d+\. " : r"^\d+\) " - # TODO start value - end - the_list = List(ordered) - - buffer = IOBuffer() - fresh_line = false - while !eof(stream) - if fresh_line - sp = startswith(stream, r"^ {0,3}") - if !(startswith(stream, b) in [false, ""]) - push!(the_list.items, parseinline(takebuf_string(buffer), block)) - buffer = IOBuffer() - else - # TODO write a newline here, and deal with nested - write(buffer, ' ', sp) - 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, parseinline(takebuf_string(buffer), block)) - push!(block, the_list) - return true - end -end - -# –––––––––––––– -# HorizontalRule -# –––––––––––––– - -type 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 diff --git a/base/markdown/Common/blockquote.jl b/base/markdown/Common/blockquote.jl new file mode 100644 index 0000000000000..9f08f4417710a --- /dev/null +++ b/base/markdown/Common/blockquote.jl @@ -0,0 +1,45 @@ +type 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)) + empty = false + end + empty && return false + + md = takebuf_string(buffer) + push!(block, BlockQuote(parse(md, flavor = config(block)).content)) + return true + end +end + +function html(io::IO, md::BlockQuote) + withtag(io, :blockquote) do + println(io) + html(io, md.content) + end +end + +function latex(io::IO, md::BlockQuote) + wrapblock(io, "quote") do + latex(io, md.content) + end +end + +function term(io::IO, md::BlockQuote, columns) + s = sprint(io->term(io, md.content, columns - 10)) + for line in split(rstrip(s), "\n") + println(io, " "^margin, "|", line) + end + println(io) +end \ No newline at end of file diff --git a/base/markdown/Common/bold.jl b/base/markdown/Common/bold.jl new file mode 100644 index 0000000000000..f7ecc1a4dfbf1 --- /dev/null +++ b/base/markdown/Common/bold.jl @@ -0,0 +1,27 @@ +type 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 + +function htmlinline(io::IO, md::Bold) + withtag(io, :strong) do + htmlinline(io, md.text) + end +end + +function latexinline(io::IO, md::Bold) + wrapinline(io, "textbf") do + latexinline(io, md.text) + end +end + +plaininline(io::IO, md::Bold) = plaininline(io, "**", md.text, "**") + +function terminline(io::IO, md::Bold) + with_output_format(:bold, terminline, io, md.text) +end \ No newline at end of file diff --git a/base/markdown/Common/code.jl b/base/markdown/Common/code.jl new file mode 100644 index 0000000000000..c110738a34045 --- /dev/null +++ b/base/markdown/Common/code.jl @@ -0,0 +1,110 @@ +type Code + language::UTF8String + code::UTF8String +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)) + elseif blankline(stream) + write(buffer, '\n') + else + break + end + end + code = takebuf_string(buffer) + !isempty(code) && (push!(block, Code(rstrip(code))); return true) + return false + end +end + +@trigger '`' -> +function inline_code(stream::IO, md::MD) + result = parse_inline_wrapper(stream, "`"; rep=true) + return result == nothing ? nothing : Code(result) +end + +@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)) + push!(block, Code(flavor, takebuf_string(buffer) |> chomp)) + return true + else + seek(stream, line_start) + end + end + write(buffer, readline(stream)) + end + return false + end +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 terminline(io::IO, code::Code) + print_with_format(:cyan, io, code.code) +end + +function html(io::IO, code::Code) + withtag(io, :pre) do + maybe_lang = 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 htmlinline(io::IO, code::Code) + withtag(io, :code) do + htmlesc(io, code.code) + end +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, code.code) + end +end + +function plain(io::IO, code::Code) + println(io, "```", code.language) + println(io, code.code) + println(io, "```") +end + +plaininline(io::IO, md::Code) = print(io, "`", md.code, "`") diff --git a/base/markdown/Common/escapes.jl b/base/markdown/Common/escapes.jl new file mode 100644 index 0000000000000..8b90a0eaf1a6a --- /dev/null +++ b/base/markdown/Common/escapes.jl @@ -0,0 +1,12 @@ +# This file is a part of Julia. License is MIT: http://julialang.org/license + +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 diff --git a/base/markdown/Common/header.jl b/base/markdown/Common/header.jl new file mode 100644 index 0000000000000..8a0699fa3e40c --- /dev/null +++ b/base/markdown/Common/header.jl @@ -0,0 +1,93 @@ +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) + 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 = readline(stream) |> strip + 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 = readline(stream) |> strip + header == "" && return false + + eatindent(stream) || return false + underline = readline(stream) |> strip + 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 + +function html{l}(io::IO, header::Header{l}) + withtag(io, "h$l") do + htmlinline(io, header.text) + 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 plain{l}(io::IO, header::Header{l}) + print(io, "#"^l*" ") + plaininline(io, header.text) + println(io) +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 diff --git a/base/markdown/Common/horizontalrule.jl b/base/markdown/Common/horizontalrule.jl new file mode 100644 index 0000000000000..b38d5ae400437 --- /dev/null +++ b/base/markdown/Common/horizontalrule.jl @@ -0,0 +1,38 @@ +type 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 + +function html(io::IO, md::HorizontalRule) + tag(io, :hr) +end + +function latex(io::IO, md::HorizontalRule) + println(io, "\\rule{\\textwidth}{1pt}") +end + +function plain(io::IO, md::HorizontalRule) + println(io, "–" ^ 3) +end + +function term(io::IO, br::HorizontalRule, columns) + println(io, " " ^ margin, "-" ^ (columns - 2margin)) +end \ No newline at end of file diff --git a/base/markdown/Common/image.jl b/base/markdown/Common/image.jl new file mode 100644 index 0000000000000..c29c7580a7109 --- /dev/null +++ b/base/markdown/Common/image.jl @@ -0,0 +1,42 @@ +type Image + url::UTF8String + alt::UTF8String +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 + +function htmlinline(io::IO, md::Image) + tag(io, :img, :src=>md.url, :alt=>md.alt) +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 + +plaininline(io::IO, md::Image) = plaininline(io, "![", md.alt, "](", md.url, ")") + +function terminline(io::IO, md::Image) + print(io, "(Image: $(md.alt))") +end diff --git a/base/markdown/Common/inline.jl b/base/markdown/Common/inline.jl deleted file mode 100644 index 1b86e30ea839c..0000000000000 --- a/base/markdown/Common/inline.jl +++ /dev/null @@ -1,108 +0,0 @@ -# This file is a part of Julia. License is MIT: http://julialang.org/license - -# –––––––– -# Emphasis -# –––––––– - -type 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 - -type 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) - result = parse_inline_wrapper(stream, "`"; rep=true) - return result == nothing ? nothing : Code(result) -end - -# –––––––––––––– -# Images & Links -# –––––––––––––– - -type Image - url::UTF8String - alt::UTF8String -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 - -type Link - text - url::UTF8String -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 - -# ––––––––––– -# Punctuation -# ––––––––––– - -type 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 diff --git a/base/markdown/Common/italic.jl b/base/markdown/Common/italic.jl new file mode 100644 index 0000000000000..0ff5fcbebcc1f --- /dev/null +++ b/base/markdown/Common/italic.jl @@ -0,0 +1,27 @@ +type 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 + +function htmlinline(io::IO, md::Italic) + withtag(io, :em) do + htmlinline(io, md.text) + end +end + +function latexinline(io::IO, md::Italic) + wrapinline(io, "emph") do + latexinline(io, md.text) + end +end + +plaininline(io::IO, md::Italic) = plaininline(io, "*", md.text, "*") + +function terminline(io::IO, md::Italic) + with_output_format(:underline, terminline, io, md.text) +end \ No newline at end of file diff --git a/base/markdown/Common/linebreak.jl b/base/markdown/Common/linebreak.jl new file mode 100644 index 0000000000000..b08dd64e65baa --- /dev/null +++ b/base/markdown/Common/linebreak.jl @@ -0,0 +1,20 @@ +type LineBreak end + +@trigger '\\' -> +function linebreak(stream::IO, md::MD) + if startswith(stream, "\\\n") + return LineBreak() + end +end + +function htmlinline(io::IO, br::LineBreak) + tag(io, :br) +end + +# TODO latex + +plaininline(io::IO, br::LineBreak) = println(io) + +function term(io::IO, br::LineBreak, columns) + println(io) +end \ No newline at end of file diff --git a/base/markdown/Common/link.jl b/base/markdown/Common/link.jl new file mode 100644 index 0000000000000..2fae447b07a08 --- /dev/null +++ b/base/markdown/Common/link.jl @@ -0,0 +1,42 @@ +type Link + text + url::UTF8String +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 + +function htmlinline(io::IO, link::Link) + withtag(io, :a, :href=>link.url) do + htmlinline(io, link.text) + end +end + +function latexinline(io::IO, md::Link) + wrapinline(io, "href") do + print(io, md.url) + end + print(io, "{") + latexinline(io, md.text) + print(io, "}") +end + +function terminline(io::IO, md::Link) + terminline(io, md.text) +end + +function plaininline(io::IO, md::Link) + print(io, "[$(md.text)]($(md.url))") +end + diff --git a/base/markdown/Common/list.jl b/base/markdown/Common/list.jl new file mode 100644 index 0000000000000..02042e2984616 --- /dev/null +++ b/base/markdown/Common/list.jl @@ -0,0 +1,101 @@ +type List + items::Vector{Any} + ordered::Bool + + List(x::AbstractVector, b::Bool) = new(x, b) + List(x::AbstractVector) = new(x, false) + List(b::Bool) = new(Any[], b) +end + +List(xs...) = List(vcat(xs...)) + +const bullets = "*•+-" +const num_or_bullets = r"^(\*|•|\+|-|\d+(\.|\))) " + +# Todo: ordered lists, inline formatting +function list(stream::IO, block::MD) + withstream(stream) do + eatindent(stream) || return false + b = startswith(stream, num_or_bullets) + (b == nothing || b == "") && return false + ordered = !(b[1] in bullets) + if ordered + b = b[end - 1] == '.' ? r"^\d+\. " : r"^\d+\) " + # TODO start value + end + the_list = List(ordered) + + buffer = IOBuffer() + fresh_line = false + while !eof(stream) + if fresh_line + sp = startswith(stream, r"^ {0,3}") + if !(startswith(stream, b) in [false, ""]) + push!(the_list.items, parseinline(takebuf_string(buffer), block)) + buffer = IOBuffer() + else + # TODO write a newline here, and deal with nested + write(buffer, ' ', sp) + 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, parseinline(takebuf_string(buffer), block)) + push!(block, the_list) + return true + end +end + +function html(io::IO, md::List) + withtag(io, md.ordered ? :ol : :ul) do + for item in md.items + println(io) + withtag(io, :li) do + htmlinline(io, item) + end + end + println(io) + end +end + +function latex(io::IO, md::List) + env = md.ordered ? "enumerate" : "itemize" + wrapblock(io, env) do + for item in md.items + print(io, "\\item ") + latexinline(io, item) + println(io) + end + end +end + +function plain(io::IO, list::List) + for (i, item) in enumerate(list.items) + print(io, list.ordered ? "$i. " : " * ") + plaininline(io, item) + println(io) + end +end + +function term(io::IO, md::List, columns) + for (i, point) in enumerate(md.items) + print(io, " "^2margin, md.ordered ? "$i. " : "• ") + print_wrapped(io, width = columns-(4margin+2), pre = " "^(2margin+2), + i = 2margin+2) do io + terminline(io, point) + end + end +end \ No newline at end of file diff --git a/base/markdown/Common/paragraph.jl b/base/markdown/Common/paragraph.jl new file mode 100644 index 0000000000000..93bbf36c8f725 --- /dev/null +++ b/base/markdown/Common/paragraph.jl @@ -0,0 +1,51 @@ +type Paragraph + content +end + +Paragraph() = Paragraph([]) + +function paragraph(stream::IO, md::MD) + 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, breaking = true) + break + else + write(buffer, ' ') + end + else + write(buffer, char) + end + end + p.content = parseinline(seek(buffer, 0), md) + return true +end + +function html(io::IO, md::Paragraph) + withtag(io, :p) do + htmlinline(io, md.content) + end +end + +function latex(io::IO, md::Paragraph) + for md in md.content + latexinline(io, md) + end + println(io) +end + +function plain(io::IO, p::Paragraph) + plaininline(io, p.content) + println(io) +end + +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 diff --git a/base/markdown/Common/vector.jl b/base/markdown/Common/vector.jl new file mode 100644 index 0000000000000..7b49c37013c39 --- /dev/null +++ b/base/markdown/Common/vector.jl @@ -0,0 +1,50 @@ +function htmlinline(io::IO, content::Vector) + for x in content + htmlinline(io, x) + end +end + +function html(io::IO, content::Vector) + for md in content + html(io, md) + println(io) + end +end + +function latex(io::IO, content::Vector) + for c in content + latex(io, c) + end +end + +function latexinline(io::IO, md::Vector) + for c in md + latexinline(io, c) + end +end + +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 + +plaininline(io::IO, md::Vector) = !isempty(md) && plaininline(io, md...) + +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 + +function terminline(io::IO, content::Vector) + for md in content + terminline(io, md) + end +end diff --git a/base/markdown/GitHub/GitHub.jl b/base/markdown/GitHub/GitHub.jl index c0ed6b9b036f8..44dc11b6eaa89 100644 --- a/base/markdown/GitHub/GitHub.jl +++ b/base/markdown/GitHub/GitHub.jl @@ -2,36 +2,6 @@ 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)) - push!(block, Code(flavor, takebuf_string(buffer) |> chomp)) - return true - else - seek(stream, line_start) - end - end - write(buffer, readline(stream)) - end - return false - end -end - function github_paragraph(stream::IO, md::MD) skipwhitespace(stream) buffer = IOBuffer() @@ -57,5 +27,5 @@ end @flavor github [list, indentcode, blockquote, fencedcode, hashheader, github_table, github_paragraph, - linebreak, escapes, en_dash, inline_code, asterisk_bold, + linebreak, escapes, inline_code, asterisk_bold, asterisk_italic, image, link] diff --git a/base/markdown/IPython/IPython.jl b/base/markdown/IPython/IPython.jl index fead30f770d35..8cd5022d0cc8a 100644 --- a/base/markdown/IPython/IPython.jl +++ b/base/markdown/IPython/IPython.jl @@ -4,26 +4,36 @@ type LaTeX formula::UTF8String end +@breaking true -> +function textex(stream::IO, block::MD) + withstream(stream) do + startswith(stream, "\$\$", padding = true) || return false + tex = readuntil(stream, "\$\$", newlines = true) + tex ≡ nothing && return false + push!(block, LaTeX(tex)) + return true + end +end + @trigger '$' -> function tex(stream::IO, md::MD) result = parse_inline_wrapper(stream, "\$", rep = true) - return result == nothing ? nothing : LaTeX(result) + 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 + ex ≡ nothing && return false + push!(md, ex) + return true end end -writemime(io::IO, ::MIME"text/plain", tex::LaTeX) = - print(io, '$', tex.formula, '$') +# TODO html and htmlinline + +latex(io::IO, tex::LaTeX) = print(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) diff --git a/base/markdown/IPython/latex.jl b/base/markdown/IPython/latex.jl new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/base/markdown/Julia/Julia.jl b/base/markdown/Julia/Julia.jl index 333fbbc1c3663..f2704e9759501 100644 --- a/base/markdown/Julia/Julia.jl +++ b/base/markdown/Julia/Julia.jl @@ -12,5 +12,5 @@ include("interp.jl") @flavor julia [blocktex, blockinterp, hashheader, list, indentcode, fencedcode, blockquote, github_table, horizontalrule, setextheader, paragraph, - linebreak, escapes, tex, interp, en_dash, inline_code, + linebreak, escapes, tex, interp, inline_code, asterisk_bold, asterisk_italic, image, link] diff --git a/base/markdown/Markdown.jl b/base/markdown/Markdown.jl index 62559a9068a3f..601119f3a98a9 100644 --- a/base/markdown/Markdown.jl +++ b/base/markdown/Markdown.jl @@ -10,17 +10,17 @@ include("parse/config.jl") include("parse/util.jl") include("parse/parse.jl") -include("Common/Common.jl") -include("GitHub/GitHub.jl") -include("IPython/IPython.jl") -include("Julia/Julia.jl") - include("render/plain.jl") include("render/html.jl") include("render/latex.jl") include("render/terminal/render.jl") +include("Common/Common.jl") +include("GitHub/GitHub.jl") +include("IPython/IPython.jl") +include("Julia/Julia.jl") + export readme, license, @md_str, @doc_str parse(markdown::String; flavor = julia) = parse(IOBuffer(markdown), flavor = flavor) diff --git a/base/markdown/render/html.jl b/base/markdown/render/html.jl index ac74fcc04155f..0dd5cecd67aa0 100644 --- a/base/markdown/render/html.jl +++ b/base/markdown/render/html.jl @@ -50,106 +50,16 @@ 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 = 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, md::List) - withtag(io, md.ordered ? :ol : :ul) do - for item in md.items - println(io) - withtag(io, :li) do - htmlinline(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, String)) 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, 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 diff --git a/base/markdown/render/latex.jl b/base/markdown/render/latex.jl index ebcc822f621f8..04d95684d11d1 100644 --- a/base/markdown/render/latex.jl +++ b/base/markdown/render/latex.jl @@ -18,108 +18,12 @@ end 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, code.code) - end -end - -function latex(io::IO, md::Paragraph) - for md in md.content - latexinline(io, md) - end - println(io) -end - -function latex(io::IO, md::BlockQuote) - wrapblock(io, "quote") do - latex(io, md.content) - end -end - -function latex(io::IO, md::List) - env = md.ordered ? "enumerate" : "itemize" - wrapblock(io, env) do - for item in md.items - print(io, "\\item ") - latexinline(io, item) - println(io) - end - end -end - -function writemime(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::String) 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 - -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, String}( '~'=>"{\\sim}", '^'=>"\\^{}", '\\'=>"{\\textbackslash}") for ch in "&%\$#_{}" diff --git a/base/markdown/render/plain.jl b/base/markdown/render/plain.jl index 6261a9aec3ad6..daf760500b35d 100644 --- a/base/markdown/render/plain.jl +++ b/base/markdown/render/plain.jl @@ -2,46 +2,8 @@ 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{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) - plaininline(io, p.content) - println(io) -end - -function plain(io::IO, list::List) - for (i, item) in enumerate(list.items) - print(io, list.ordered ? "$i. " : " * ") - plaininline(io, item) - println(io) - end -end - -function plain(io::IO, md::HorizontalRule) - println(io, "–" ^ 3) -end - # Inline elements plaininline(x) = sprint(plaininline, x) @@ -52,20 +14,8 @@ function plaininline(io::IO, md...) end end -plaininline(io::IO, md::Vector) = !isempty(md) && plaininline(io, md...) - -plaininline(io::IO, md::Image) = plaininline(io, "![", md.alt, "](", md.url, ")") - plaininline(io::IO, s::String) = print(io, s) -plaininline(io::IO, md::Bold) = plaininline(io, "**", md.text, "**") - -plaininline(io::IO, md::Italic) = plaininline(io, "*", md.text, "*") - -plaininline(io::IO, md::Code) = print(io, "`", md.code, "`") - -plaininline(io::IO, br::LineBreak) = println(io) - plaininline(io::IO, x) = writemime(io, MIME"text/plain"(), x) # writemime diff --git a/base/markdown/render/terminal/render.jl b/base/markdown/render/terminal/render.jl index af5dd39198114..71b4276dfeba7 100644 --- a/base/markdown/render/terminal/render.jl +++ b/base/markdown/render/terminal/render.jl @@ -5,117 +5,18 @@ include("formatting.jl") const margin = 2 cols() = Base.tty_size()[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()) = 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, 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 (i, point) in enumerate(md.items) - print(io, " "^2margin, md.ordered ? "$i. " : "• ") - print_wrapped(io, width = columns-(4margin+2), pre = " "^(2margin+2), - i = 2margin+2) do io - terminline(io, point) - 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, _) = 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(io, md) 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::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