From 41f6a5d1e1afd9cfc5a6601631b762507a7ce259 Mon Sep 17 00:00:00 2001 From: Jilles van Gurp Date: Fri, 29 Dec 2023 11:24:19 +0100 Subject: [PATCH] update readme for new refactored code --- README.md | 280 ++++++++++++------ .../kotlin4example/Kotlin4Example.kt | 160 +++++++++- .../com/jillesvangurp/kotlin4example/Page.kt | 16 +- .../kotlin4example/DocGenTest.kt | 14 +- .../kotlin4example/KotlinForExampleTest.kt | 72 +++-- .../kotlin4example/docs/readme.kt | 151 ++++++---- 6 files changed, 496 insertions(+), 197 deletions(-) diff --git a/README.md b/README.md index 297936f..3dfa075 100644 --- a/README.md +++ b/README.md @@ -34,152 +34,240 @@ And there's of course nothing wrong with that approach and Kotlin4example actual Kotlin has multi line strings, templating, and some built in support for creating your own DSLs. So, I created a simple Kotlin DSL that generates markdown by concatenating strings (with Markdown) and executable kotlin blocks. The executable blocks basically contain the source code I want to show in a Markdown code block. So, the block figures out the source file it is in and the exact line it starts at and we grab exactly those lines and turn them into a markdown code block. We can also grab the output (optional) when it runs and can grab that. -## Example +## Usage + +### Example blocks + +With Kotlin4Example you can mix examples and markdown easily. +An example is a code block +and it is executed by default. Because it is a code block, + you are forced to ensure +it is syntactically correct and compiles. + +By executing it, you can further guarantee it does what it +is supposed to and you can +intercept output and integrate that into your documentation. + +For example: ```kotlin -// documentation inception -// this is technically a block within a block, just so I can show you -// how you would use it. -block { - println("Hello World") -} +print("Hello World") ``` -Here's the same block as above running as part of this -[readme.kt](https://github.com/jillesvangurp/kotlin4example/tree/master/src/test/kotlin/com/jillesvangurp/kotlin4example/docs/readme.kt) file. +This example prints **Hello World** when it executes. ```kotlin -println("Hello World") + // out is an ExampleOutput instance + // with both stdout and the return + // value as a Result. Any exceptions + // are captured as well. + val out = example { + print("Hello World") + } +// this is how you can append arbitrary markdown ++""" + This example prints **${out.stdOut}** when it executes. +""".trimIndent() ``` -Captured Output: +### Suspending examples -``` -Hello World +If you use co-routines, you can use a suspendingExample +```kotlin +// call some suspending code ``` -As you can see, we indeed show a pretty printed block, ran it, and -grabbed the output as well. Observant readers will also note that -the nested block above did not run. The reason for this is that -the outer `block` call for that has a `runBlock` parameter that -you can use to prevent this. If you look at the source code -for the readme, you will see we used `block(runBlock = false)` +```kotlin +// runs the example in a runBlocking { .. } +suspendingExample { + // call some suspending code +} +``` -You can also return a value from the block and capture that: +### Configuring blocks ```kotlin -fun aFunctionThatReturnsAnInt() = 1 + 1 +// sometimes you just want to show but not run the code +example( + runExample = false, +) { + // your example goes here +} -// call the function to make the block return something -aFunctionThatReturnsAnInt() +// making sure the example fits in a web page +// long lines tend to look ugly in documentation +example( + // default is 80 + lineLength = 120, + // default is false + wrap = true, + // default is false + allowLongLines = true, + +) { + // more code here +} ``` --> +### Code snippets -``` -2 -``` +While it is nice to have executable blocks, +sometimes you just want to grab +code directly from a file. You can do that with snippets. -Note how that captured the return value and printed that -without us using `print` or `println`. +```kotlin +// BEGIN_MY_CODE_SNIPPET +println("Example code") +// END_MY_CODE_SNIPPET +exampleFromSnippet("readme.kt","MY_CODE_SNIPPET") +``` -You can also use suspendingBlock if you use co-routines +### Markdown ```kotlin -suspend fun foo() {} - -suspendingBlock { - // call some suspending logic - foo() +section("Section") { + +""" + You can use string literals, templates ${1+1}, + and [links](https://github.com/jillesvangurp/kotlin4example) + or other markdown formatting. + """.trimIndent() } +// you can also just include markdown files +includeMdFile("intro.md") +// link to things in your git repository +mdLink(DocGenTest::class) +mdLinkToRepoResource("build file","build.gradle.kts") ``` ## This README is generated -This README.md is actually created from kotlin code that +This README.md is of course created from kotlin code that runs as part of the test suite. You can look at the kotlin source code that generates this markdown [here](https://github.com/jillesvangurp/kotlin4example/tree/master/src/test/kotlin/com/jillesvangurp/kotlin4example/docs/readme.kt). ```kotlin -val readme by k4ERepo.md { +val readmeMarkdown by k4ERepo.md { // for larger bits of text, it's nice to load them from a markdown file includeMdFile("intro.md") - section("Example") { - block(runBlock = false) { - // documentation inception - // this is technically a block within a block, just so I can show you - // how you would use it. - block { - println("Hello World") + section("Usage") { + subSection("Example blocks") { + +""" + With Kotlin4Example you can mix examples and markdown easily. + An example is a code block + and it is executed by default. Because it is a code block, + you are forced to ensure + it is syntactically correct and compiles. + + By executing it, you can further guarantee it does what it + is supposed to and you can + intercept output and integrate that into your documentation. + + For example: + """.trimIndent() + + // a bit of kotlin4example inception here, but it works +example { + // out is an ExampleOutput instance + // with both stdout and the return + // value as a Result. Any exceptions + // are captured as well. + val out = example { + print("Hello World") + } +// this is how you can append arbitrary markdown ++""" + This example prints **${out.stdOut}** when it executes. +""".trimIndent() } } - // of course you can inline a Kotlin multiline string with some markdown - // note the use of templating here and the helper function to generate - // a link - +""" - Here's the same block as above running as part of this - ${mdLinkToSelf("readme.kt")} file. - """ - - block { - println("Hello World") + subSection("Suspending examples") { + +"If you use co-routines, you can use a suspendingExample" + + example { + // runs the example in a runBlocking { .. } + suspendingExample { + // call some suspending code + } + } } - - +""" - As you can see, we indeed show a pretty printed block, ran it, and - grabbed the output as well. Observant readers will also note that - the nested block above did not run. The reason for this is that - the outer `block` call for that has a `runBlock` parameter that - you can use to prevent this. If you look at the source code - for the readme, you will see we used `block(runBlock = false)` - - You can also return a value from the block and capture that: - """ - - block { - fun aFunctionThatReturnsAnInt() = 1 + 1 - - // call the function to make the block return something - aFunctionThatReturnsAnInt() + subSection("Configuring blocks") { + + example(runExample = false) { + // sometimes you just want to show but not run the code + example( + runExample = false, + ) { + // your example goes here + } + + // making sure the example fits in a web page + // long lines tend to look ugly in documentation + example( + // default is 80 + lineLength = 120, + // default is false + wrap = true, + // default is false + allowLongLines = true, + + ) { + // more code here + } + } } - - +""" - Note how that captured the return value and printed that - without us using `print` or `println`. - - You can also use suspendingBlock if you use co-routines - """ - - block(runBlock = false) { - suspend fun foo() {} - - suspendingBlock { - // call some suspending logic - foo() + subSection("Code snippets") { + +""" + While it is nice to have executable blocks, + sometimes you just want to grab + code directly from a file. You can do that with snippets. + """.trimIndent() + + example { + // BEGIN_MY_CODE_SNIPPET + println("Example code") + // END_MY_CODE_SNIPPET + exampleFromSnippet("readme.kt","MY_CODE_SNIPPET") + } + } + subSection("Markdown") { + // you can use our Kotlin DSL to structure your documentation. + example(runExample = false) { + section("Section") { + +""" + You can use string literals, templates ${1+1}, + and [links](https://github.com/jillesvangurp/kotlin4example) + or other markdown formatting. + """.trimIndent() + } + // you can also just include markdown files + includeMdFile("intro.md") + // link to things in your git repository + mdLink(DocGenTest::class) + mdLinkToRepoResource("build file","build.gradle.kts") } } } section("This README is generated") { +""" - This README.md is actually created from kotlin code that + This README.md is of course created from kotlin code that runs as part of the test suite. You can look at the kotlin source code that generates this markdown ${mdLinkToSelf("here")}. """.trimIndent() // little string concatenation hack so it will read // until the end marker instead of stopping here - snippetFromSourceFile( + exampleFromSnippet( "com/jillesvangurp/kotlin4example/docs/readme.kt", "README" + "CODE" ) """ - And the code that actually writes the file is a test: + And the code that actually writes the `README.md file` is a test: """.trimIndent() - snippetBlockFromClass(DocGenTest::class, "READMEWRITE") + exampleFromSnippet(DocGenTest::class, "READMEWRITE") } includeMdFile("outro.md") @@ -187,10 +275,16 @@ val readme by k4ERepo.md { ``` ```kotlin -@Test -fun `generate readme for this project`() { - val readmeMd = Page("Kotlin4Example",fileName = "README.md") - readmeMd.write(markdown = readme) +/** + * The readme is generated when the tests run. + */ +class DocGenTest { + @Test + fun `generate readme for this project`() { + val readmePage = Page("Kotlin4Example",fileName = "README.md") + // readmeMarkdown is a lazy of the markdown content + readmePage.write(markdown = readmeMarkdown) + } } ``` diff --git a/src/main/kotlin/com/jillesvangurp/kotlin4example/Kotlin4Example.kt b/src/main/kotlin/com/jillesvangurp/kotlin4example/Kotlin4Example.kt index aaa1394..1377019 100644 --- a/src/main/kotlin/com/jillesvangurp/kotlin4example/Kotlin4Example.kt +++ b/src/main/kotlin/com/jillesvangurp/kotlin4example/Kotlin4Example.kt @@ -41,33 +41,40 @@ class BlockOutputCapture { } } +data class ExampleOutput( + val result: Result, + val stdOut: String, +) @Suppress("MemberVisibilityCanBePrivate") class Kotlin4Example( private val sourceRepository: SourceRepository -) : AutoCloseable { +) { private val buf = StringBuilder() - private val patternForBlock = "(suspendingBlock|block).*?\\{+".toRegex(RegexOption.MULTILINE) + private val patternForBlock = "(suspendingBlock|block|example|suspendingExample).*?\\{+".toRegex(RegexOption.MULTILINE) + /** + * Append some arbitrary markdown. Tip, you can use raw strings and string templates """${1+1}""" + */ operator fun String.unaryPlus() { buf.appendLine(this.trimIndent().trimMargin()) buf.appendLine() } - fun section(title: String, block: (Kotlin4Example.() -> Unit)? = null) { + fun section(title: String, block: (Kotlin4Example.() -> Unit)? = null) { buf.appendLine("## $title") buf.appendLine() block?.invoke(this) } - fun subSection(title: String, block: (Kotlin4Example.() -> Unit)? = null) { + fun subSection(title: String, block: (Kotlin4Example.() -> Unit)? = null) { buf.appendLine("### $title") buf.appendLine() block?.invoke(this) } - fun mdCodeBlock( + private fun mdCodeBlock( code: String, type: String = "kotlin", allowLongLines: Boolean = false, @@ -129,6 +136,7 @@ class Kotlin4Example( return mdLink(title, "${sourceRepository.repoUrl}/tree/${sourceRepository.branch}/${path}") } + @Deprecated("Use exampleFromSnippet", ReplaceWith("exampleFromSnippet(clazz, snippetId, allowLongLines, wrap, lineLength, type)")) fun snippetBlockFromClass( clazz: KClass<*>, snippetId: String, @@ -136,10 +144,20 @@ class Kotlin4Example( wrap: Boolean = false, lineLength: Int = 80, type: String = "kotlin" + ) { + exampleFromSnippet(clazz, snippetId, allowLongLines, wrap, lineLength, type) + } + fun exampleFromSnippet( + clazz: KClass<*>, + snippetId: String, + allowLongLines: Boolean = false, + wrap: Boolean = false, + lineLength: Int = 80, + type: String = "kotlin" ) { val fileName = sourcePathForClass(clazz) - snippetFromSourceFile( - fileName = fileName, + exampleFromSnippet( + sourceFileName = fileName, snippetId = snippetId, allowLongLines = allowLongLines, wrap = wrap, @@ -148,6 +166,7 @@ class Kotlin4Example( ) } + @Deprecated("Use exampleFromSnippet", ReplaceWith("exampleFromSnippet(fileName,snippetId, allowLongLines, wrap, lineLength, type)")) fun snippetFromSourceFile( fileName: String, snippetId: String, @@ -155,10 +174,23 @@ class Kotlin4Example( wrap: Boolean = false, lineLength: Int = 80, type: String = "kotlin" + ) { + exampleFromSnippet(fileName,snippetId, allowLongLines, wrap, lineLength, type) + } + + fun exampleFromSnippet( + sourceFileName: String, + snippetId: String, + allowLongLines: Boolean = false, + wrap: Boolean = false, + lineLength: Int = 80, + type: String = "kotlin" ) { val snippetLines = mutableListOf() - val lines = File(sourceRepository.sourcePaths.map { File(it, fileName) }.firstOrNull { it.exists() }?.path ?: fileName).readLines() + val lines = File(sourceRepository.sourcePaths.map { File(it, sourceFileName) }.firstOrNull { it.exists() }?.path + ?: sourceFileName + ).readLines() var inSnippet = false for (line in lines) { if (inSnippet && line.contains(snippetId)) { @@ -173,7 +205,7 @@ class Kotlin4Example( } } if (snippetLines.size == 0) { - throw IllegalArgumentException("Snippet $snippetId not found in $fileName") + throw IllegalArgumentException("Snippet $snippetId not found in $sourceFileName") } mdCodeBlock( snippetLines.joinToString("\n").trimIndent(), @@ -184,6 +216,103 @@ class Kotlin4Example( ) } + fun suspendingExample( + runExample: Boolean = true, + type: String = "kotlin", + allowLongLines: Boolean = false, + wrap: Boolean = false, + lineLength: Int = 80, + block: suspend BlockOutputCapture.() -> T + ): ExampleOutput { + val state = BlockOutputCapture() + val returnVal = try { + if (runExample) { + runBlocking { + Result.success(block.invoke(state)) + } + } else { + Result.success(null) + } + } catch (e: Exception) { + Result.failure(e) + } + val callerSourceBlock = + getCallerSourceBlock() ?: throw IllegalStateException("source block could not be extracted") + mdCodeBlock( + code = callerSourceBlock, + allowLongLines = allowLongLines, + wrap = wrap, + lineLength = lineLength, + type = type + ) + + return ExampleOutput(returnVal, state.output()) + } + fun example( + runExample: Boolean = true, + type: String = "kotlin", + allowLongLines: Boolean = false, + wrap: Boolean = false, + lineLength: Int = 80, + block: BlockOutputCapture.() -> T + ): ExampleOutput { + val state = BlockOutputCapture() + val returnVal = try { + if (runExample) { + Result.success(block.invoke(state)) + } else { + Result.success(null) + } + } catch (e: Exception) { + Result.failure(e) + } + val callerSourceBlock = + getCallerSourceBlock() ?: throw IllegalStateException("source block could not be extracted") + mdCodeBlock( + code = callerSourceBlock, + allowLongLines = allowLongLines, + wrap = wrap, + lineLength = lineLength, + type = type + ) + + return ExampleOutput(returnVal, state.output().trimIndent()) + } + + fun renderExampleOutput( + exampleOutput: ExampleOutput, + stdOutOnly: Boolean = true, + allowLongLines: Boolean = false, + wrap: Boolean = false, + lineLength: Int = 80, + ) { + if(!stdOutOnly) { + exampleOutput.result.let { r -> + r.getOrNull()?.let { returnValue -> + if(returnValue !is Unit) { + mdCodeBlock( + returnValue.toString(), + allowLongLines = allowLongLines, + wrap = wrap, + lineLength = lineLength, + type = "text" + ) + } + } + } + } + exampleOutput.stdOut.takeIf { it.isNotBlank() }?.let { + mdCodeBlock( + it, + allowLongLines = allowLongLines, + wrap = wrap, + lineLength = lineLength, + type = "text" + ) + } + } + + @Deprecated("Use the new example function", ReplaceWith("""renderExampleOutput(example(runBlock,type,allowLongLines,wrap,lineLength,block),!captureBlockReturnValue,allowLongLines,wrap,lineLength)""")) fun block( runBlock: Boolean = true, type: String = "kotlin", @@ -197,6 +326,7 @@ class Kotlin4Example( block: BlockOutputCapture.() -> T ) { val state = BlockOutputCapture() + @Suppress("DEPRECATION") block( allowLongLines = allowLongLines, type = type, @@ -212,6 +342,7 @@ class Kotlin4Example( ) } + @Deprecated("Use the new example function", ReplaceWith("""renderExampleOutput(example(runBlock,type,allowLongLines,wrap,lineLength,block),!captureBlockReturnValue,allowLongLines,wrap,lineLength)""")) fun block( runBlock: Boolean = true, type: String = "kotlin", @@ -238,7 +369,7 @@ class Kotlin4Example( if (runBlock) { val response = block.invoke(blockCapture) if (response !is Unit) { - if(captureBlockReturnValue) { + if (captureBlockReturnValue) { buf.appendLine("$returnValuePrefix\n") mdCodeBlock(response.toString(), type = "") } @@ -262,6 +393,7 @@ class Kotlin4Example( } } + @Deprecated("Use the new suspendingExample function", ReplaceWith("""renderExampleOutput(suspendingExample(runBlock,type,allowLongLines,wrap,lineLength,block),!captureBlockReturnValue,allowLongLines,wrap,lineLength)""")) fun suspendingBlock( runBlock: Boolean = true, type: String = "kotlin", @@ -275,6 +407,7 @@ class Kotlin4Example( block: suspend BlockOutputCapture.() -> T ) { val state = BlockOutputCapture() + @Suppress("DEPRECATION") suspendingBlock( allowLongLines = allowLongLines, type = type, @@ -289,6 +422,8 @@ class Kotlin4Example( stdOutPrefix = stdOutPrefix ) } + + @Deprecated("Use the new suspendingExample function", ReplaceWith("""renderExampleOutput(suspendingExample(runBlock,type,allowLongLines,wrap,lineLength,block),!captureBlockReturnValue,allowLongLines,wrap,lineLength)""")) fun suspendingBlock( runBlock: Boolean = true, type: String = "kotlin", @@ -423,16 +558,13 @@ class Kotlin4Example( } } - override fun close() { - } - companion object { fun markdown( sourceRepository: SourceRepository, block: Kotlin4Example.() -> Unit ): String { val example = Kotlin4Example(sourceRepository) - example.use(block) + example.apply(block) return example.buf.toString() } } diff --git a/src/main/kotlin/com/jillesvangurp/kotlin4example/Page.kt b/src/main/kotlin/com/jillesvangurp/kotlin4example/Page.kt index 6a88234..43119c6 100644 --- a/src/main/kotlin/com/jillesvangurp/kotlin4example/Page.kt +++ b/src/main/kotlin/com/jillesvangurp/kotlin4example/Page.kt @@ -8,15 +8,15 @@ data class Page( val outputDir: String = ".", val fileName: String = "${title.lowercase().replace("""\s+""", "-")}.md" ) { - val file = File(outputDir,fileName) + val file = File(outputDir, fileName) fun write(markdown: String) { - file.writeText(""" - # $title - - - """.trimIndent() + markdown) - - + file.writeText(markdownContent(markdown)) } + + fun markdownContent(markdown: String) = """ + # $title + + + """.trimIndent() + markdown } diff --git a/src/test/kotlin/com/jillesvangurp/kotlin4example/DocGenTest.kt b/src/test/kotlin/com/jillesvangurp/kotlin4example/DocGenTest.kt index 691aeb4..992944f 100644 --- a/src/test/kotlin/com/jillesvangurp/kotlin4example/DocGenTest.kt +++ b/src/test/kotlin/com/jillesvangurp/kotlin4example/DocGenTest.kt @@ -1,14 +1,18 @@ package com.jillesvangurp.kotlin4example -import com.jillesvangurp.kotlin4example.docs.readme +import com.jillesvangurp.kotlin4example.docs.readmeMarkdown import org.junit.jupiter.api.Test +// READMEWRITEBEGIN +/** + * The readme is generated when the tests run. + */ class DocGenTest { - // READMEWRITEBEGIN @Test fun `generate readme for this project`() { - val readmeMd = Page("Kotlin4Example",fileName = "README.md") - readmeMd.write(markdown = readme) + val readmePage = Page("Kotlin4Example",fileName = "README.md") + // readmeMarkdown is a lazy of the markdown content + readmePage.write(markdown = readmeMarkdown) } - // READMEWRITEEND } +// READMEWRITEEND diff --git a/src/test/kotlin/com/jillesvangurp/kotlin4example/KotlinForExampleTest.kt b/src/test/kotlin/com/jillesvangurp/kotlin4example/KotlinForExampleTest.kt index 302f6fe..6391924 100644 --- a/src/test/kotlin/com/jillesvangurp/kotlin4example/KotlinForExampleTest.kt +++ b/src/test/kotlin/com/jillesvangurp/kotlin4example/KotlinForExampleTest.kt @@ -3,6 +3,7 @@ package com.jillesvangurp.kotlin4example import io.kotest.matchers.ints.shouldBeLessThanOrEqual import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldNotContain +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -35,21 +36,33 @@ class KotlinForExampleTest { fun `do not allow long source lines`() { assertThrows { repo.md { - block { - // too long - println("****************************************************************************************************") - } + // too long + renderExampleOutput( + example(true, "kotlin", false, false, 80) { + // too long + println("****************************************************************************************************") + }, + false + ) + }.value // make sure to access the value } } @Test fun `wrap long source lines`() { + repo.md { - block(wrap = true) { - // too long but will be wrapped - println("****************************************************************************************************") - } + // too long but will be wrapped + renderExampleOutput( + example(wrap = true, block = { + // too long but will be wrapped + println("****************************************************************************************************") + }), + false, + wrap = true + ) + }.value.lines().forEach { it.length shouldBeLessThanOrEqual 80 } // make sure to access the value @@ -58,41 +71,54 @@ class KotlinForExampleTest { @Test fun `capture return value`() { repo.md { - block { - 1+1 - } + + renderExampleOutput( + example(true, "kotlin", false, false, 80, fun BlockOutputCapture.(): Int { + return 1 + 1 + }), + false + ) }.value shouldContain "2" } @Test fun `capture return value in suspendingBlock`() { repo.md { - suspendingBlock { - 1+1 - } + renderExampleOutput( + suspendingExample { + 1 + 1 + }, + false + ) }.value shouldContain "2" } @Test fun `capture output from multiple blocks`() { val out1 = repo.md { - block(printStdOut = false) { + example(true, "kotlin", false, false, 80, fun BlockOutputCapture.() { print("hel") print("lo") } + ) }.value // if we disable printing nothing gets printed out1 shouldNotContain "hello" - val bo = BlockOutputCapture() val out2 = repo.md { - block(printStdOut = false, blockCapture = bo) { - print("hel") - print("lo") - } - block(printStdOut = true, blockCapture = bo) { - println("world") - } + renderExampleOutput( + example(true, "kotlin", false, false, 80, fun BlockOutputCapture.() { + print("hel") + print("lo") + }), + true + ) + renderExampleOutput( + example(true, "kotlin", false, false, 80, fun BlockOutputCapture.() { + println("world") + }), + false + ) }.value // but we can reuse the same block capture and print at the end out2 shouldContain "hello" diff --git a/src/test/kotlin/com/jillesvangurp/kotlin4example/docs/readme.kt b/src/test/kotlin/com/jillesvangurp/kotlin4example/docs/readme.kt index 9792f6a..087a427 100644 --- a/src/test/kotlin/com/jillesvangurp/kotlin4example/docs/readme.kt +++ b/src/test/kotlin/com/jillesvangurp/kotlin4example/docs/readme.kt @@ -5,87 +5,130 @@ import com.jillesvangurp.kotlin4example.SourceRepository val k4ERepo = SourceRepository("https://github.com/jillesvangurp/kotlin4example") -// You can still use comment markers to grab larger sections of code +// You can use comment markers to grab larger sections of code // or to grab code from e.g. the main source tree. // READMECODESTART -val readme by k4ERepo.md { +val readmeMarkdown by k4ERepo.md { // for larger bits of text, it's nice to load them from a markdown file includeMdFile("intro.md") - section("Example") { - block(runBlock = false) { - // documentation inception - // this is technically a block within a block, just so I can show you - // how you would use it. - block { - println("Hello World") + section("Usage") { + subSection("Example blocks") { + +""" + With Kotlin4Example you can mix examples and markdown easily. + An example is a code block + and it is executed by default. Because it is a code block, + you are forced to ensure + it is syntactically correct and compiles. + + By executing it, you can further guarantee it does what it + is supposed to and you can + intercept output and integrate that into your documentation. + + For example: + """.trimIndent() + + // a bit of kotlin4example inception here, but it works +example { + // out is an ExampleOutput instance + // with both stdout and the return + // value as a Result. Any exceptions + // are captured as well. + val out = example { + print("Hello World") + } +// this is how you can append arbitrary markdown ++""" + This example prints **${out.stdOut}** when it executes. +""".trimIndent() } } - // of course you can inline a Kotlin multiline string with some markdown - // note the use of templating here and the helper function to generate - // a link - +""" - Here's the same block as above running as part of this - ${mdLinkToSelf("readme.kt")} file. - """ - - block { - println("Hello World") + subSection("Suspending examples") { + +"If you use co-routines, you can use a suspendingExample" + + example { + // runs the example in a runBlocking { .. } + suspendingExample { + // call some suspending code + } + } } - - +""" - As you can see, we indeed show a pretty printed block, ran it, and - grabbed the output as well. Observant readers will also note that - the nested block above did not run. The reason for this is that - the outer `block` call for that has a `runBlock` parameter that - you can use to prevent this. If you look at the source code - for the readme, you will see we used `block(runBlock = false)` - - You can also return a value from the block and capture that: - """ - - block { - fun aFunctionThatReturnsAnInt() = 1 + 1 - - // call the function to make the block return something - aFunctionThatReturnsAnInt() + subSection("Configuring blocks") { + + example(runExample = false) { + // sometimes you just want to show but not run the code + example( + runExample = false, + ) { + // your example goes here + } + + // making sure the example fits in a web page + // long lines tend to look ugly in documentation + example( + // default is 80 + lineLength = 120, + // default is false + wrap = true, + // default is false + allowLongLines = true, + + ) { + // more code here + } + } } - - +""" - Note how that captured the return value and printed that - without us using `print` or `println`. - - You can also use suspendingBlock if you use co-routines - """ - - block(runBlock = false) { - suspend fun foo() {} - - suspendingBlock { - // call some suspending logic - foo() + subSection("Code snippets") { + +""" + While it is nice to have executable blocks, + sometimes you just want to grab + code directly from a file. You can do that with snippets. + """.trimIndent() + + example { + // BEGIN_MY_CODE_SNIPPET + println("Example code") + // END_MY_CODE_SNIPPET + exampleFromSnippet("readme.kt","MY_CODE_SNIPPET") + } + } + subSection("Markdown") { + // you can use our Kotlin DSL to structure your documentation. + example(runExample = false) { + section("Section") { + +""" + You can use string literals, templates ${1+1}, + and [links](https://github.com/jillesvangurp/kotlin4example) + or other markdown formatting. + """.trimIndent() + } + // you can also just include markdown files + includeMdFile("intro.md") + // link to things in your git repository + mdLink(DocGenTest::class) + mdLinkToRepoResource("build file","build.gradle.kts") } } } section("This README is generated") { +""" - This README.md is actually created from kotlin code that + This README.md is of course created from kotlin code that runs as part of the test suite. You can look at the kotlin source code that generates this markdown ${mdLinkToSelf("here")}. """.trimIndent() // little string concatenation hack so it will read // until the end marker instead of stopping here - snippetFromSourceFile( + exampleFromSnippet( "com/jillesvangurp/kotlin4example/docs/readme.kt", "README" + "CODE" ) """ - And the code that actually writes the file is a test: + And the code that actually writes the `README.md file` is a test: """.trimIndent() - snippetBlockFromClass(DocGenTest::class, "READMEWRITE") + exampleFromSnippet(DocGenTest::class, "READMEWRITE") } includeMdFile("outro.md")