Skip to content

Instantly share code, notes, and snippets.

@Scorpio93
Last active November 29, 2019 13:48
Show Gist options
  • Save Scorpio93/573c91be2abccb7c9f5a4adb4dabc016 to your computer and use it in GitHub Desktop.
Save Scorpio93/573c91be2abccb7c9f5a4adb4dabc016 to your computer and use it in GitHub Desktop.
Markdown generator

I think first of all need to explain about some functions as markdown(init: MARKDOWN.() -> Unit), h1(init: HEADER.() -> Unit), h2(init: HEADER.() -> Unit) etc. All of them called high-ordered functions because they operate with other functions in any way as that possible, in this case, i passed anonymous function(also known as lambdas) into each of them. Also, we should remember about type args limitation in these functions, for example, we can't use BOLD() type without P() wrapper. MARKDOWN, HEADER, BOLD and other classes are support classes, which means they are not used explicitly and that why they have uppercase names. Each of them should call lambdas after initialization, that why we need doInit(child: T, init: T.() -> Unit) function which does that. Another important thing that we need to know about operator overloading which gives us the ability to call +"Some string ", but underhood it just means call extension functions String.unaryPlus() and adding the processed string to the collection.

fun main() {
println(result())
}
fun result() = markdown {
h1 { +"I am heading level 1" }
h2 { +"I am heading level 2" }
p {
+"First line of paragraphs"
br() //line break
bold { +"I am bold text" }
italic { +"I am italic text" }
}
blockquotes {
+"blockqoutes"
blockquotes { +"nested blockqoutes" }
}
orderedList {
+"First item"
+"Second item"
orderedList {
+"Indented first item"
+"Indeted second item"
}
}
}
private const val START_BLOCK_POSITION = 1
private const val FIRST_HEADING_LEVEL = 1
private const val SECOND_HEADING_LEVEL = 2
private const val HEADER_TEXT_TAG = "#"
private const val BOLD_TEXT_TAG = "**"
private const val ITALIC_TEXT_TAG = "_"
private const val BLOCKQUOTE_TEXT_TAG = ">"
open class Tag(
private val name: String = "",
private val isSingleTag: Boolean = false
) {
companion object {
val processedResults = arrayListOf<Tag>()
}
protected fun <T : Tag> doInit(child: T, init: T.() -> Unit) {
child.init()
}
protected fun <T : Tag> doSingleInit(child: T) {
processedResults.add(child)
}
override fun toString(): String {
return processedResults.joinToString("\n") { it.name }
}
// Default implementation of adding processed strings to shared storage
open operator fun String.unaryPlus() {
val tagPattern = if (isSingleTag) "$name $this" else "$name$this$name "
processedResults.add(Tag(tagPattern))
}
}
fun markdown(init: MARKDOWN.() -> Unit) = MARKDOWN().apply(init)
// We don't need any tags for root element in markdown
class MARKDOWN : Tag() {
fun h1(init: HEADER.() -> Unit) = doInit(HEADER(FIRST_HEADING_LEVEL), init)
fun h2(init: HEADER.() -> Unit) = doInit(HEADER(SECOND_HEADING_LEVEL), init)
fun p(init: P.() -> Unit) = doInit(P(), init)
fun blockquotes(init: BLOCKQUOTES.() -> Unit) = doInit(BLOCKQUOTES(), init)
fun orderedList(init: ORDEREDLIST.() -> Unit) = doInit(ORDEREDLIST(), init)
}
class P : Tag() {
fun br() = doSingleInit(BR())
fun bold(init: BOLD.() -> Unit) = doInit(BOLD(), init)
fun italic(init: ITALIC.() -> Unit) = doInit(ITALIC(), init)
}
class HEADER(size: Int) : Tag(HEADER_TEXT_TAG.repeat(size), true)
class BR : Tag()
class BOLD : Tag(BOLD_TEXT_TAG)
class ITALIC : Tag(ITALIC_TEXT_TAG)
class BLOCKQUOTES(var nestedLayer: Int = START_BLOCK_POSITION) : Tag(BLOCKQUOTE_TEXT_TAG.repeat(nestedLayer), true) {
fun blockquotes(init: BLOCKQUOTES.() -> Unit) = doInit(BLOCKQUOTES(++nestedLayer), init)
}
class ORDEREDLIST : Tag() {
private var counter = 0
override operator fun String.unaryPlus() {
++counter
val tagPattern = "$counter. $this"
processedResults.add(Tag(tagPattern))
}
fun orderedList(init: NESTEDLIST.() -> Unit) = doInit(NESTEDLIST(), init)
}
class NESTEDLIST : Tag() {
private var nestedCounter = 0
// We should override plus operator for nested list, because it must to be countable
override operator fun String.unaryPlus() {
++nestedCounter
val tagPattern = "\t$nestedCounter. $this"
processedResults.add(Tag(tagPattern))
}
}
internal class MainKtTest {
@Test
fun headerTest() {
val result = markdown {
h1 { +"First header" }
h2 { +"Second header" }
}
val expectedResult =
"# First header\n" +
"## Second header"
assertEquals(expectedResult, result.toString())
}
@Test
fun blockquotesTest() {
val result = markdown {
blockquotes {
+"First layer"
blockquotes {
+"Second layer"
blockquotes {
+"Third layer"
}
}
}
}
val expectedResult =
"> First layer\n" +
">> Second layer\n" +
">>> Third layer"
assertEquals(expectedResult, result.toString())
}
@Test
fun orderedListTest() {
val result = markdown {
orderedList {
+"First item"
orderedList {
+"First inner item"
+"Second inner item"
}
+"Second item"
+"Third item"
orderedList {
+"First inner item"
+"Second inner item"
}
}
}
val expectedResult =
"1. First item\n" +
"\t1. First inner item\n" +
"\t2. Second inner item\n" +
"2. Second item\n" +
"3. Third item\n" +
"\t1. First inner item\n" +
"\t2. Second inner item"
assertEquals(expectedResult, result.toString())
}
}

I am heading level 1

I am heading level 2

First line of paragraphs

I am bold text
I am italic text

blockqoutes

nested blockqoutes

  1. First item
  2. Second item
    1. Indented first item
    2. Indeted second item
# I am heading level 1
## I am heading level 2
First line of paragraphs
**I am bold text**
_I am italic text_
> blockqoutes
>> nested blockqoutes
1. First item
2. Second item
1. Indented first item
2. Indeted second item
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment