218 lines
6.8 KiB
Ruby
218 lines
6.8 KiB
Ruby
## Filter-based Markdown translator.
|
|
#
|
|
module Markdown
|
|
## Superclass that defines behaviour of all translators
|
|
# @abstract Don't use directly - it only defins the ability to chain translators
|
|
class AbstractTranslator
|
|
attr_accessor :input
|
|
attr_accessor :output
|
|
def initialize()
|
|
@chain = []
|
|
end
|
|
def +(nextTranslator)
|
|
@chain.append nextTranslator
|
|
return self
|
|
end
|
|
def to_html
|
|
output = @output
|
|
@chain.each { |x|
|
|
x = x.new(output) if x.class == Class
|
|
x.to_html
|
|
output = x.output
|
|
}
|
|
return output
|
|
end
|
|
end
|
|
module_function
|
|
def html_highlighter; @html_highlighter end
|
|
def html_highlighter= v; @html_highlighter = v end
|
|
## Translator for linear tags in Markdown.
|
|
# A linear tag is any tag that starts anywhere on the line, and closes on the same exact line.
|
|
class LinearTagTranslator < AbstractTranslator
|
|
def initialize(text)
|
|
@input = text
|
|
@output = text
|
|
super()
|
|
end
|
|
def to_html
|
|
@output = @input
|
|
# Newline
|
|
.sub(/\s{2}[\n\r]/,"<br/>")
|
|
# Inline code (discord style)
|
|
.gsub(/(?<!\\)``(.*?[^\\])``/) {
|
|
code = Regexp.last_match[1]
|
|
"<code>#{code.gsub /[*`~_!\[]/,"\\\\\\0"}</code>"
|
|
}
|
|
# Inline code (Markdown style)
|
|
.gsub(/(?<!\\)`(.*?[^\\])`/) {
|
|
code = Regexp.last_match[1]
|
|
"<code>#{code.gsub /[*`~_!\[]/,"\\\\\\0"}</code>"
|
|
}
|
|
# Bold-italics
|
|
.gsub(/(?<!\\)\*\*\*(.*?[^\\])\*\*\*/,"<i><b>\\1</b></i>")
|
|
# Bold
|
|
.gsub(/(?<!\\)\*\*(.*?[^\\])\*\*/,"<b>\\1</b>")
|
|
# Italics
|
|
.gsub(/(?<!\\)\*(.*?[^\\])\*/,"<i>\\1</i>")
|
|
# Strikethrough
|
|
.gsub(/(?<!\\)~~(.*?[^\\])~~/,"<s>\\1</s>")
|
|
# Underline
|
|
.gsub(/(?<!\\)__(.*?[^\\])__/,"<span style=\"text-decoration: underline\">\\1</span>")
|
|
# Image
|
|
.gsub(/(?<!\\)!\[(.*)\]\((.*)\)/,"<img src=\"\\2\" alt=\"\\1\" />")
|
|
# Link
|
|
.gsub(/(?<!\\)\[(.*)\]\((.*)\)/,"<a href=\"\\2\">\\1</a>")
|
|
super
|
|
end
|
|
end
|
|
## Translator for linear leftmost tags.
|
|
# Leftmost linear tags open on the leftmost end of the string, and close once the line ends. These tags do not need to be explicitly closed.
|
|
class LeftmostTagTranslator < AbstractTranslator
|
|
def initialize(text)
|
|
@input = text
|
|
@output = text
|
|
super()
|
|
end
|
|
def to_html
|
|
# Headers
|
|
@output = @input.split("\n").map do |x|
|
|
x.gsub(/^(?<!\\)(\#{1,4})([^\n\r]*)/) {
|
|
level,content = Regexp.last_match[1..2]
|
|
"<h#{level.length}>"+content+"</h#{level.length}>"
|
|
}.gsub(/^\-{3,}/,"<hr>")
|
|
end.join("\n")
|
|
super
|
|
end
|
|
end
|
|
## Translator for code blocks in markdown
|
|
# Code blocks can have syntax highlighting. This class implements an attribute for providing a syntax highlighter, one handler per requested output.
|
|
class CodeBlockTranslator < AbstractTranslator
|
|
def initialize(text)
|
|
@input = text
|
|
@output = text
|
|
super()
|
|
end
|
|
def to_html
|
|
@output = @input.gsub(/(?:\n|^)```([\w_-]*)([\s\S]+?)```/) {
|
|
language,code = Regexp.last_match[1..2]
|
|
code = Markdown::html_highlighter.call(language,code) if Markdown::html_highlighter
|
|
"<pre><code>#{code.gsub /[|#*`~_!\[]/,"\\\\\\0"}</code></pre>"
|
|
}
|
|
super()
|
|
end
|
|
end
|
|
## Translator for quotes in Markdown.
|
|
# These deserve their own place in hell. As if the "yaml with triangle brackets instead of spaces" syntax wasn't horrible enough, each quote is its own markdown context.
|
|
class QuoteTranslator < AbstractTranslator
|
|
def initialize(text)
|
|
if text.is_a? Array then
|
|
@lines = text
|
|
elsif text.is_a? String then
|
|
@lines = text.split("\n")
|
|
end
|
|
@output = text
|
|
super()
|
|
end
|
|
def input= (v)
|
|
@lines = v.split("\n")
|
|
@output = v
|
|
end
|
|
def input
|
|
@lines.join("\n")
|
|
end
|
|
def to_html
|
|
stack = []
|
|
range = []
|
|
@lines.each_with_index { |x,index|
|
|
if x.match /^\s*> ?/ then
|
|
range[0] = index if not range[0]
|
|
range[1] = index
|
|
else
|
|
stack.append(range[0]..range[1]) if range[0] and range[1]
|
|
range = []
|
|
end
|
|
}
|
|
stack.append(range[0]..range[1]) if range[0] and range[1]
|
|
stack.reverse.each { |r|
|
|
@lines[r.begin] = "<blockquote>\n"+@lines[r.begin]
|
|
@lines[r.end] = @lines[r.end]+"\n</blockquote>"
|
|
@lines[r] = @lines[r].map { |line|
|
|
line.sub /^(\s*)> ?/,"\\1 "
|
|
}
|
|
@lines[r] = QuoteTranslator.new(@lines[r]).to_html
|
|
}
|
|
@output = @lines.join("\n")
|
|
super
|
|
end
|
|
end
|
|
|
|
## Table parser
|
|
# translates tables from a format in markdown to an html table
|
|
class TableTranslator < AbstractTranslator
|
|
def initialize(text)
|
|
@input = text
|
|
@output = text
|
|
super()
|
|
end
|
|
def to_html
|
|
lines = @output.split("\n")
|
|
table_testline = -1
|
|
table_start = -1
|
|
table_column_count = 0
|
|
tables = []
|
|
cur_table = []
|
|
lines.each_with_index { |line,index|
|
|
if (table_start != -1) and (line.match /^\s*\|([^\|]*\|){#{table_column_count-1}}$/) then
|
|
if (table_testline == -1) then
|
|
if (line.match /^\s*\|(\-*\|){#{table_column_count-1}}$/) then
|
|
table_testline = 1
|
|
else
|
|
table_start = -1
|
|
cur_table = []
|
|
end
|
|
else
|
|
cur_table.push (line.split("|").filter_map { |x| x.strip if x.match /\S+/ })
|
|
end
|
|
elsif (table_start != -1) then
|
|
obj = {table: cur_table, start: table_start, end: index}
|
|
tables.push(obj)
|
|
table_start = -1
|
|
cur_table = []
|
|
table_testline = -1
|
|
table_column_count = 0
|
|
end
|
|
if (table_start == -1) and (line.start_with? /\s*\|/ ) and (line.match /^\s*\|.*\|/) then
|
|
table_start = index
|
|
table_column_count = line.count "|"
|
|
cur_table.push (line.split("|").filter_map { |x| x.strip if x.match /\S+/ })
|
|
end
|
|
}
|
|
if cur_table != [] then
|
|
obj = {table: cur_table, start:table_start, end: lines.count-1}
|
|
tables.push(obj)
|
|
end
|
|
tables.reverse.each { |x|
|
|
lines[x[:start]..x[:end]] = (x[:table].map do |a2d|
|
|
(a2d.map { |x| (x.start_with? "#") ? " <th>"+x.sub(/^#\s+/,"")+"</th>" : " <td>"+x+"</td>"}).prepend(" <tr>").append(" </tr>")
|
|
end).flatten.prepend("<table>").append("</table>")
|
|
}
|
|
@output = lines.join("\n")
|
|
super()
|
|
end
|
|
end
|
|
|
|
# Backslash cleaner
|
|
# Cleans excessive backslashes after the translation
|
|
class BackslashTranslator < AbstractTranslator
|
|
def initialize(text)
|
|
@input = text
|
|
@output = text
|
|
end
|
|
def to_html
|
|
@output = @input.gsub(/\\(.)/,"\\1")
|
|
end
|
|
end
|
|
end
|
|
|
|
|