-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMarkdownToHtml.scala
218 lines (162 loc) · 8.22 KB
/
MarkdownToHtml.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package transformers
//import scala.scalajs.js.annotation.JSExportTopLevel
import ast.Li.{Oli, Uli}
import ast.{Markdown, MarkdownDoc}
import ast.Tree.{Branch, Leaf}
import parser._
import scala.annotation.tailrec
/**
* Created by robertk on 14/05/17.
*/
/**
* Type class to transform a Document.
*
*/
trait Transformer[T] {
type IN
type OUT
def run(t: T, in: IN): OUT
}
object Transformer {
// aux pattern
type Aux[T0,IN0,OUT0] = Transformer[T0] { type IN = IN0; type OUT = OUT0 }
// idiomatic typeclass "Summoner" so I don't need to use implicitly which would loose type info of In Out
def apply[T,A,B](implicit evidence: Transformer[T]): Aux[T,evidence.IN,evidence.OUT] = evidence
// idiomatic typclass "Constructor" method
def instance[T,A,B](func: (T,A) => B): Aux[T,A,B] = {
new Transformer[T] {
type IN = A; type OUT = B;
def run(t: T, in: A): B = func(t,in)
}
}
// syntax pimps
implicit class TransformerOps[T0](foo: T0) {
/**
* magic wand. pimp alias of Transformer.run eg a transform function
* @param bar
* @return
*/
def ---*[A,B](bar: A) (implicit aux: Transformer.Aux[T0,A,B]) : B = {
aux.run(foo, bar)
}
def transform[A,B](bar: A)(implicit aux: Transformer.Aux[T0,A,B]) : B = {
aux.run(foo, bar)
}
}
}
object TransformerApi {
import Transformer._
def transform[T,A,B](t: T)(in: A)(implicit aux: Transformer.Aux[T,A,B]) :B = {
t ---* in
}
}
// Specific Markdown Transformer
case class MarkdownToHtml(p: String => ParseState[MarkdownDoc], r: ParseState[MarkdownDoc] => Html) {
// def biMap[PRE](mIn: PRE => String, mOut: )
}
object MarkdownToHtml {
import Transformer._
import ast._
// Use instance to add MarkdownToHtml to the Transformer typeclass
implicit val m2hTransformer: Transformer.Aux[MarkdownToHtml,String, parser.Html] = instance( (t, in) => t.r(t.p(in)) )
// @JSExportTopLevel("mdmagic")
def transform(md: String): String = simple ---* md
import parser.markdownParser.markdownDocumentParser
val simple = MarkdownToHtml( markdownDocumentParser.run , _ match {
case ParseKo(error) =>
s"""
|<p>
| <h1>Oops Exception parsing markdown</h1>
| <h5>$error</h5>
|</p>
|""".stripMargin
case ParseOk(remainingInput, MDDoc( mds, refLinkMap)) => {
val sep = sys.props("line.separator")
val indent: Int = 2
//Close over
// variable to keep track of paragraph parens
var pp: Boolean = false
def renderRecursivly(mds: List[Markdown], acc: String, closings: List[String]): String = mds match {
case Nil =>
if(closings.isEmpty) acc // terminal case
else renderRecursivly(mds, acc + closings.head, closings.tail) //run the closings down
case m::ms => m match {
case RawHtml(v) => renderRecursivly(ms, acc + v, closings)
case Br => renderRecursivly(ms, acc + s"<br/>$sep", closings)
case Paragraph => pp match {
case false => {pp = true; renderRecursivly(ms, acc + s"$sep<p>$sep", closings)} // no previous so open paragraph
case true => {pp = false; renderRecursivly(ms, acc + s"$sep</p>$sep", closings)} // end of a paragraph ??? TODO pos open another?
}
case H1(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<h1>", s"</h1>$sep"::Nil), closings)
case H2(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<h2>", s"</h2>$sep"::Nil), closings)
case H3(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<h3>", s"</h3>$sep"::Nil), closings)
case H4(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<h4>", s"</h4>$sep"::Nil), closings)
case H5(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<h5>", s"</h5>$sep"::Nil), closings)
case H6(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<h6>", s"</h6>$sep"::Nil), closings)
case H7(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<h7>", s"</h7>$sep"::Nil), closings)
case Blockquote(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<blockquote>", s"</blockquote>$sep"::Nil), closings)
case BlockCode(str) => renderRecursivly(ms, acc+s"<pre>$sep<code>$str</code>$sep</pre>", closings)
case MdForest(forest) => renderRecursivly(ms, acc+renderForest(forest), closings)
// a newline not prefixed with two or more spaces is just hardwrap by the author just visually add a space and a newline
case Hardwrap => renderRecursivly(ms, acc + s" $sep", closings)
case Bold(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<strong>", s"</strong>"::Nil), closings)
case Italic(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<em>", s"</em>"::Nil), closings)
case Strikethrough(md) => renderRecursivly(ms, renderRecursivly(md, acc+"<s>", s"</s>"::Nil), closings)
case Link(text, uri, title) => title match {
case None => renderRecursivly(ms, renderRecursivly(List(text), acc+s"""<a href="${uri.uri}">""", "</a>"::Nil), closings)
case Some(tit) => renderRecursivly(ms, renderRecursivly(List(text), acc+s"""<a href="${uri.uri}" title="$tit">""", "</a>"::Nil), closings)
}
case RefLink(text,key) => renderRecursivly(ms, renderRecursivly(List(text), acc+s"""<a href="${refLinkMap.getOrElse(key,Uri("http://badlink.badlink.com")).uri}">""", "</a>"::Nil), closings)
case Img(text, uri, title) => title match {
case None => renderRecursivly(ms, acc+s"""<img src="${uri.uri}" alt="$text">""", closings)
case Some(tit) => renderRecursivly(ms, acc+s"""<img src="${uri.uri}" title="$tit">""", closings)
}
case RefImg(text,key) => renderRecursivly(ms, acc+s"""<img src="${refLinkMap.getOrElse(key,Uri("http://badlink.badlink.com")).uri}">""", closings)
case RefLinkUri(key,Uri(uri)) => renderRecursivly(ms, acc+s"""<!-- [$key] $uri -->""", closings)
case InlineCode(str) => renderRecursivly(ms, acc+s"<code>$str</code>", closings)
case _ => s"WTF not implemented for $m"
}
}
//helper funcs for list render
// helper do the li with indent
import ast.Li._
def doIndent(depth: Int, str: String): String = s"""${" " * (indent * depth)}$str"""
// list items can contain nested markdown so render recursivly
def doLi(li: Li): String = renderRecursivly(li.value, doIndent(li.indent + indent, "<li>"), s"</li>$sep"::Nil)
// @tailrec
def renderTrees(trees: List[Tree[Li]], acc: String, closings: List[String]): String = trees match {
case Nil =>
if(closings.isEmpty) acc // terminal case
else renderTrees(trees, acc + closings.head, closings.tail) // run down the closings
case t::ts => t match {
//leaf should already be in a branch's list so no need to do wrap in open or close
case Leaf(value,depth) => renderTrees(ts, acc + doLi(value), closings)
case Branch(branches, depth) => branches match {
case Nil =>
if(closings.isEmpty) acc // terminal case
else renderTrees(branches, acc + closings.head, closings.tail) // run down the closings
// new branch so add our opening and closing and recurse down the trees in the branch
case b::bs => {
val ordered = t.firstValue.getOrElse(Uli(Nil,depth)).isOrdered
val open = doIndent(depth, s"""<${if(ordered) "ol" else "ul"}>$sep""")
val close = doIndent(depth, s"""</${if(ordered) "ol" else "ul"}>$sep""")
renderTrees(ts, renderTrees(branches, acc + open, close::Nil), closings)
}
}
}
}
def renderForest(forest: List[Tree[Li]]): String = {
// //handle edge case where the tree is a single leaf, wrap it in a branch so the appropriate list open tags are wrapped around it
// val newforest = forest map { t => t match {
// case Leaf(value,depth) => Branch(List(t),depth)
// case _ => t
// }}
renderTrees(forest, "", Nil)
}
//Do the render
val ret = renderRecursivly(mds,"",Nil)
// tidy up trailing paragraph
if(pp) ret + s"</p>$sep" else ret
}
} )
}