X-Git-Url: http://nitlanguage.org diff --git a/lib/markdown/markdown.nit b/lib/markdown/markdown.nit index fd33c59..2426d53 100644 --- a/lib/markdown/markdown.nit +++ b/lib/markdown/markdown.nit @@ -30,10 +30,7 @@ import template # SEE: `String::md_to_html` for a shortcut. class MarkdownProcessor - # `MarkdownEmitter` used for ouput. - var emitter: MarkdownEmitter is noinit - - # Work in extended mode. + # Work in extended mode (default). # # Behavior changes when using extended mode: # @@ -41,54 +38,108 @@ class MarkdownProcessor # # In normal markdown the following: # - # This is a paragraph - # * and this is not a list + # ~~~md + # This is a paragraph + # * and this is not a list + # ~~~ # # Will produce: # - #
This is a paragraph - # * and this is not a list
+ # ~~~html + #This is a paragraph + # * and this is not a list
+ # ~~~ # - # When using extended mode this changes to: + # When using extended mode this changes to: # - #This is a paragraph
- #This is a paragraph
+ #import markdown
#
- # ```
- # fun test do
- # print "Hello World!"
- # end
- # ```
+ # print "Hello World!".md_to_html
+ #
+ # ~~~
#
# * Underscores (Emphasis)
#
# Underscores in the middle of a word like:
#
- # Con_cat_this
+ # ~~~md
+ # Con_cat_this
+ # ~~~
#
- # normally produces this:
+ # normally produces this:
#
- # Concatthis
+ # ~~~html + #Concatthis
+ # ~~~ # # With extended mode they don't result in emphasis. # - #Con_cat_this
+ # ~~~html + #Con_cat_this
+ # ~~~ # - var ext_mode = false + # * Strikethrough + # + # Like in [GFM](https://help.github.com/articles/github-flavored-markdown), + # strikethrought span is marked with `~~`. + # + # ~~~md + # ~~Mistaken text.~~ + # ~~~ + # + # becomes + # + # ~~~html + #"
+ var meta = block.meta
+ if meta != null then
+ v.add ""
+ else
+ v.add ""
+ end
v.emit_in block
v.add "
\n"
end
@@ -688,6 +803,12 @@ class HTMLDecorator
v.add ""
end
+ redef fun add_strike(v, text) do
+ v.add ""
+ v.add text
+ v.add ""
+ end
+
redef fun add_image(v, link, name, comment) do
v.add "
0 then return
var level = 0
var line = block.first_line
+ if line == null then return
if line.is_empty then return
var start = line.leading
while start < line.value.length and line.value[start] == '#' do
@@ -1116,6 +1314,7 @@ class BlockHeadline
line.leading = 0
line.trailing = 0
end
+ self.start = start
depth = level.min(6)
end
end
@@ -1135,6 +1334,7 @@ abstract class BlockList
# Split list block into list items sub-blocks.
private fun init_block(v: MarkdownProcessor) do
var line = block.first_line
+ if line == null then return
line = line.next
while line != null do
var t = v.line_kind(line)
@@ -1230,6 +1430,9 @@ end
# A markdown line.
class MDLine
+ # Location of `self` in the original input.
+ var location: MDLocation
+
# Text contained in this line.
var value: String is writable
@@ -1264,8 +1467,8 @@ class MDLine
leading = 0
trailing = 0
is_empty = true
- if prev != null then prev.next_empty = true
- if next != null then next.prev_empty = true
+ if prev != null then prev.as(not null).next_empty = true
+ if next != null then next.as(not null).prev_empty = true
end
# Number or leading spaces on this line.
@@ -1399,8 +1602,8 @@ class MDLine
# Used by `check_html`.
private fun read_xml_comment(first_line: MDLine, start: Int): Int do
var line: nullable MDLine = first_line
- if start + 3 < line.value.length then
- if line.value[2] == '-' and line.value[3] == '-' then
+ if start + 3 < line.as(not null).value.length then
+ if line.as(not null).value[2] == '-' and line.as(not null).value[3] == '-' then
var pos = start + 4
while line != null do
while pos < line.value.length and line.value[pos] != '-' do
@@ -1441,7 +1644,7 @@ class LineEmpty
super Line
redef fun process(v) do
- v.current_line = v.current_line.next
+ v.current_line = v.current_line.as(not null).next
end
end
@@ -1453,7 +1656,7 @@ class LineOther
redef fun process(v) do
var line = v.current_line
# go to block end
- var was_empty = line.prev_empty
+ var was_empty = line.as(not null).prev_empty
while line != null and not line.is_empty do
var t = v.line_kind(line)
if (v.in_list or v.ext_mode) and t isa LineList then
@@ -1469,29 +1672,30 @@ class LineOther
line = line.next
end
# build block
+ var current_block = v.current_block.as(not null)
if line != null and not line.is_empty then
- var block = v.current_block.split(line.prev.as(not null))
+ var block = current_block.split(line.prev.as(not null))
if v.in_list and not was_empty then
block.kind = new BlockNone(block)
else
block.kind = new BlockParagraph(block)
end
- v.current_block.remove_leading_empty_lines
+ current_block.remove_leading_empty_lines
else
var block: MDBlock
if line != null then
- block = v.current_block.split(line)
+ block = current_block.split(line)
else
- block = v.current_block.split(v.current_block.last_line.as(not null))
+ block = current_block.split(current_block.last_line.as(not null))
end
if v.in_list and (line == null or not line.is_empty) and not was_empty then
block.kind = new BlockNone(block)
else
block.kind = new BlockParagraph(block)
end
- v.current_block.remove_leading_empty_lines
+ current_block.remove_leading_empty_lines
end
- v.current_line = v.current_block.first_line
+ v.current_line = current_block.first_line
end
end
@@ -1506,15 +1710,16 @@ class LineCode
line = line.next
end
# split at block end line
+ var current_block = v.current_block.as(not null)
var block: MDBlock
if line != null then
- block = v.current_block.split(line.prev.as(not null))
+ block = current_block.split(line.prev.as(not null))
else
- block = v.current_block.split(v.current_block.last_line.as(not null))
+ block = current_block.split(current_block.last_line.as(not null))
end
block.kind = new BlockCode(block)
block.remove_surrounding_empty_lines
- v.current_line = v.current_block.first_line
+ v.current_line = current_block.first_line
end
end
@@ -1524,12 +1729,14 @@ class LineXML
redef fun process(v) do
var line = v.current_line
+ if line == null then return
+ var current_block = v.current_block.as(not null)
var prev = line.prev
- if prev != null then v.current_block.split(prev)
- var block = v.current_block.split(line.xml_end_line.as(not null))
+ if prev != null then current_block.split(prev)
+ var block = current_block.split(line.xml_end_line.as(not null))
block.kind = new BlockXML(block)
- v.current_block.remove_leading_empty_lines
- v.current_line = v.current_block.first_line
+ current_block.remove_leading_empty_lines
+ v.current_line = current_block.first_line
end
end
@@ -1539,6 +1746,7 @@ class LineBlockquote
redef fun process(v) do
var line = v.current_line
+ var current_block = v.current_block.as(not null)
# go to bquote end
while line != null do
if not line.is_empty and (line.prev_empty and
@@ -1549,9 +1757,9 @@ class LineBlockquote
# build sub block
var block: MDBlock
if line != null then
- block = v.current_block.split(line.prev.as(not null))
+ block = current_block.split(line.prev.as(not null))
else
- block = v.current_block.split(v.current_block.last_line.as(not null))
+ block = current_block.split(current_block.last_line.as(not null))
end
var kind = new BlockQuote(block)
block.kind = kind
@@ -1559,7 +1767,7 @@ class LineBlockquote
kind.remove_block_quote_prefix(block)
v.current_line = line
v.recurse(block, false)
- v.current_line = v.current_block.first_line
+ v.current_line = current_block.first_line
end
end
@@ -1569,11 +1777,13 @@ class LineHR
redef fun process(v) do
var line = v.current_line
- if line.prev != null then v.current_block.split(line.prev.as(not null))
- var block = v.current_block.split(line.as(not null))
+ if line == null then return
+ var current_block = v.current_block.as(not null)
+ if line.prev != null then current_block.split(line.prev.as(not null))
+ var block = current_block.split(line)
block.kind = new BlockRuler(block)
- v.current_block.remove_leading_empty_lines
- v.current_line = v.current_block.first_line
+ current_block.remove_leading_empty_lines
+ v.current_line = current_block.first_line
end
end
@@ -1583,7 +1793,8 @@ class LineFence
redef fun process(v) do
# go to fence end
- var line = v.current_line.next
+ var line = v.current_line.as(not null).next
+ var current_block = v.current_block.as(not null)
while line != null do
if v.line_kind(line) isa LineFence then break
line = line.next
@@ -1594,15 +1805,17 @@ class LineFence
# build fence block
var block: MDBlock
if line != null then
- block = v.current_block.split(line.prev.as(not null))
+ block = current_block.split(line.prev.as(not null))
else
- block = v.current_block.split(v.current_block.last_line.as(not null))
+ block = current_block.split(current_block.last_line.as(not null))
end
- block.kind = new BlockFence(block)
- block.first_line.clear
+ block.remove_surrounding_empty_lines
+ var meta = block.first_line.as(not null).value.meta_from_fence
+ block.kind = new BlockFence(block, meta)
+ block.first_line.as(not null).clear
var last = block.last_line
if last != null and v.line_kind(last) isa LineFence then
- block.last_line.clear
+ block.last_line.as(not null).clear
end
block.remove_surrounding_empty_lines
v.current_line = line
@@ -1615,14 +1828,16 @@ class LineHeadline
redef fun process(v) do
var line = v.current_line
+ if line == null then return
+ var current_block = v.current_block.as(not null)
var lprev = line.prev
- if lprev != null then v.current_block.split(lprev)
- var block = v.current_block.split(line.as(not null))
+ if lprev != null then current_block.split(lprev)
+ var block = current_block.split(line)
var kind = new BlockHeadline(block)
block.kind = kind
kind.transform_headline(block)
- v.current_block.remove_leading_empty_lines
- v.current_line = v.current_block.first_line
+ current_block.remove_leading_empty_lines
+ v.current_line = current_block.first_line
end
end
@@ -1632,16 +1847,18 @@ class LineHeadline1
redef fun process(v) do
var line = v.current_line
+ if line == null then return
+ var current_block = v.current_block.as(not null)
var lprev = line.prev
- if lprev != null then v.current_block.split(lprev)
- line.next.clear
- var block = v.current_block.split(line.as(not null))
+ if lprev != null then current_block.split(lprev)
+ line.next.as(not null).clear
+ var block = current_block.split(line)
var kind = new BlockHeadline(block)
kind.depth = 1
kind.transform_headline(block)
block.kind = kind
- v.current_block.remove_leading_empty_lines
- v.current_line = v.current_block.first_line
+ current_block.remove_leading_empty_lines
+ v.current_line = current_block.first_line
end
end
@@ -1651,22 +1868,24 @@ class LineHeadline2
redef fun process(v) do
var line = v.current_line
+ if line == null then return
+ var current_block = v.current_block.as(not null)
var lprev = line.prev
- if lprev != null then v.current_block.split(lprev)
- line.next.clear
- var block = v.current_block.split(line.as(not null))
+ if lprev != null then current_block.split(lprev)
+ line.next.as(not null).clear
+ var block = current_block.split(line)
var kind = new BlockHeadline(block)
kind.depth = 2
kind.transform_headline(block)
block.kind = kind
- v.current_block.remove_leading_empty_lines
- v.current_line = v.current_block.first_line
+ current_block.remove_leading_empty_lines
+ v.current_line = current_block.first_line
end
end
# A markdown list line.
# Mainly used to factorize code between ordered and unordered lists.
-class LineList
+abstract class LineList
super Line
redef fun process(v) do
@@ -1679,19 +1898,20 @@ class LineList
line = line.next
end
# build list block
+ var current_block = v.current_block.as(not null)
var list: MDBlock
if line != null then
- list = v.current_block.split(line.prev.as(not null))
+ list = current_block.split(line.prev.as(not null))
else
- list = v.current_block.split(v.current_block.last_line.as(not null))
+ list = current_block.split(current_block.last_line.as(not null))
end
var kind = block_kind(list)
list.kind = kind
- list.first_line.prev_empty = false
- list.last_line.next_empty = false
+ list.first_line.as(not null).prev_empty = false
+ list.last_line.as(not null).next_empty = false
list.remove_surrounding_empty_lines
- list.first_line.prev_empty = false
- list.last_line.next_empty = false
+ list.first_line.as(not null).prev_empty = false
+ list.last_line.as(not null).next_empty = false
kind.init_block(v)
var block = list.first_block
while block != null do
@@ -1736,14 +1956,17 @@ end
# Some tokens have a specific markup behaviour that is handled here.
abstract class Token
- # Position of `self` in markdown input.
+ # Location of `self` in the original input.
+ var location: nullable MDLocation
+
+ # Position of `self` in input independant from lines.
var pos: Int
# Character found at `pos` in the markdown input.
var char: Char
# Output that token using `MarkdownEmitter::decorator`.
- fun emit(v: MarkdownEmitter) do v.addc char
+ fun emit(v: MarkdownProcessor) do v.decorator.add_char(v, char)
end
# A token without a specific meaning.
@@ -1811,14 +2034,15 @@ abstract class TokenCode
super Token
redef fun emit(v) do
+ var current_text = v.current_text.as(not null)
var a = pos + next_pos + 1
- var b = v.processor.find_token(v.current_text.as(not null), a, self)
+ var b = v.find_token(current_text, a, self)
if b > 0 then
v.current_pos = b + next_pos
- while a < b and v.current_text[a] == ' ' do a += 1
+ while a < b and current_text[a] == ' ' do a += 1
if a < b then
- while v.current_text[b - 1] == ' ' do b -= 1
- v.decorator.add_span_code(v, v.current_text.as(not null), a, b)
+ while current_text[b - 1] == ' ' do b -= 1
+ v.decorator.add_span_code(v, current_text, a, b)
end
else
v.addc char
@@ -1871,11 +2095,12 @@ abstract class TokenLinkOrImage
end
# Emit the hyperlink as link or image.
- private fun emit_hyper(v: MarkdownEmitter) is abstract
+ private fun emit_hyper(v: MarkdownProcessor) is abstract
# Check if the link is a valid link.
- private fun check_link(v: MarkdownEmitter, out: FlatBuffer, start: Int, token: Token): Int do
+ private fun check_link(v: MarkdownProcessor, out: FlatBuffer, start: Int, token: Token): Int do
var md = v.current_text
+ if md == null then return -1
var pos
if token isa TokenLink then
pos = start + 1
@@ -1890,9 +2115,9 @@ abstract class TokenLinkOrImage
pos += 1
pos = md.skip_spaces(pos)
if pos < start then
- var tid = name.write_to_string.to_lower
- if v.processor.link_refs.has_key(tid) then
- var lr = v.processor.link_refs[tid]
+ var tid = name.as(not null).write_to_string.to_lower
+ if v.link_refs.has_key(tid) then
+ var lr = v.link_refs[tid]
is_abbrev = lr.is_abbrev
link = lr.link
comment = lr.title
@@ -1927,6 +2152,7 @@ abstract class TokenLinkOrImage
if pos == -1 then return -1
end
end
+ if pos < start then return -1
if md[pos] != ')' then return -1
else if md[pos] == '[' then
pos += 1
@@ -1939,16 +2165,16 @@ abstract class TokenLinkOrImage
else
id = name
end
- var tid = id.write_to_string.to_lower
- if v.processor.link_refs.has_key(tid) then
- var lr = v.processor.link_refs[tid]
+ var tid = id.as(not null).write_to_string.to_lower
+ if v.link_refs.has_key(tid) then
+ var lr = v.link_refs[tid]
link = lr.link
comment = lr.title
end
else
- var tid = name.write_to_string.replace("\n", " ").to_lower
- if v.processor.link_refs.has_key(tid) then
- var lr = v.processor.link_refs[tid]
+ var tid = name.as(not null).write_to_string.replace("\n", " ").to_lower
+ if v.link_refs.has_key(tid) then
+ var lr = v.link_refs[tid]
link = lr.link
comment = lr.title
pos = old_pos
@@ -2000,7 +2226,7 @@ class TokenHTML
# Is the HTML valid?
# Also take care of link and mailto shortcuts.
- private fun check_html(v: MarkdownEmitter, out: FlatBuffer, md: Text, start: Int): Int do
+ private fun check_html(v: MarkdownProcessor, out: FlatBuffer, md: Text, start: Int): Int do
# check for auto links
var tmp = new FlatBuffer
var pos = md.read_until(tmp, start + 1, ':', ' ', '>', '\n')
@@ -2081,7 +2307,26 @@ class TokenEscape
redef fun emit(v) do
v.current_pos += 1
- v.addc v.current_text[v.current_pos]
+ v.addc v.current_text.as(not null)[v.current_pos]
+ end
+end
+
+# A markdown strike token.
+#
+# Extended mode only (see `MarkdownProcessor::ext_mode`)
+class TokenStrike
+ super Token
+
+ redef fun emit(v) do
+ var tmp = v.push_buffer
+ var b = v.emit_text_until(v.current_text.as(not null), pos + 2, self)
+ v.pop_buffer
+ if b > 0 then
+ v.decorator.add_strike(v, tmp)
+ v.current_pos = b + 1
+ else
+ v.addc char
+ end
end
end
@@ -2106,18 +2351,11 @@ redef class Text
if c == '\\' and pos + 1 < length then
pos = escape(out, self[pos + 1], pos)
else
- var end_reached = false
- for n in nend do
- if c == n then
- end_reached = true
- break
- end
- end
- if end_reached then break
+ for n in nend do if c == n then break label
out.add c
end
pos += 1
- end
+ end label
if pos == length then return -1
return pos
end
@@ -2193,6 +2431,7 @@ redef class Text
# Safe mode can be activated to limit reading to valid xml.
private fun read_xml(out: FlatBuffer, start: Int, safe_mode: Bool): Int do
var pos = 0
+ var is_valid = true
var is_close_tag = false
if start + 1 >= length then return -1
if self[start + 1] == '/' then
@@ -2210,7 +2449,11 @@ redef class Text
pos = read_xml_until(tmp, pos, ' ', '/', '>')
if pos == -1 then return -1
var tag = tmp.write_to_string.trim.to_lower
- if tag.is_html_unsafe then
+ if not tag.is_valid_html_tag then
+ out.append "<"
+ pos = -1
+ else if tag.is_html_unsafe then
+ is_valid = false
out.append "<"
if is_close_tag then out.add '/'
out.append tmp
@@ -2233,7 +2476,11 @@ redef class Text
if pos == -1 then return -1
end
if self[pos] == '>' then
- out.add '>'
+ if is_valid then
+ out.add '>'
+ else
+ out.append ">"
+ end
return pos
end
return -1
@@ -2305,6 +2552,14 @@ redef class Text
return tpl.write_to_string.to_lower
end
+ private fun is_valid_html_tag: Bool do
+ if is_empty then return false
+ for c in self do
+ if not c.is_alpha then return false
+ end
+ return true
+ end
+
# Read and escape the markdown contained in `self`.
private fun escape(out: FlatBuffer, c: Char, pos: Int): Int do
if c == '\\' or c == '[' or c == ']' or c == '(' or c == ')' or c == '{' or
@@ -2318,6 +2573,17 @@ redef class Text
return pos
end
+ # Extract string found at end of fence opening.
+ private fun meta_from_fence: nullable Text do
+ for i in [0..chars.length[ do
+ var c = chars[i]
+ if c != ' ' and c != '`' and c != '~' then
+ return substring_from(i).trim
+ end
+ end
+ return null
+ end
+
# Is `self` an unsafe HTML element?
private fun is_html_unsafe: Bool do return html_unsafe_tags.has(self.write_to_string)
@@ -2341,7 +2607,7 @@ redef class String
# var md = "**Hello World!**"
# var html = md.md_to_html
# assert html == "Hello World!
\n"
- fun md_to_html: Streamable do
+ fun md_to_html: Writable do
var processor = new MarkdownProcessor
return processor.process(self)
end