Skip to content

Commit

Permalink
use suspending blocks by default
Browse files Browse the repository at this point in the history
  • Loading branch information
jillesvangurp committed Dec 31, 2023
1 parent 48df00e commit 1d94f7c
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 245 deletions.
187 changes: 41 additions & 146 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,29 @@ val out = example {
""".trimIndent()
```

### Suspending examples
The block you pass to example can be a suspending block. It uses `runBlocking` to run it. Earlier
versions of this library had a separate function for this; this is no longer needed.

If you use co-routines, you can use a suspendingExample
### Configuring examples

```kotlin
// runs the example in a runBlocking { .. }
suspendingExample {
// call some suspending code
}
```

### Configuring blocks
Sometimes you just want to show but not run the code. You can control this with the
`runExample` parameter.

```kotlin
// sometimes you just want to show but not run the code
//
example(
runExample = false,
) {
// your example goes here
// your code goes here
}
```

The library imposes a line length of 80 characters on your examples. The
reason is that code blocks with horizontal scroll bars look ugly.

You can of course turn this off or turn on the built in wrapping (wraps at the 80th character)

```kotlin

// making sure the example fits in a web page
// long lines tend to look ugly in documentation
Expand All @@ -105,8 +108,8 @@ example(
// default is false
allowLongLines = true,

) {
// more code here
) {
// your code goes here
}
```

Expand All @@ -117,162 +120,54 @@ sometimes you just want to grab
code directly from a file. You can do that with snippets.

```kotlin
// the BEGIN_ and END_ are optional but I find it
// helps for readability.
// BEGIN_MY_CODE_SNIPPET
println("Example code")
println("Example code that shows in a snippet")
// END_MY_CODE_SNIPPET
exampleFromSnippet("readme.kt","MY_CODE_SNIPPET")
exampleFromSnippet("readme.kt", "MY_CODE_SNIPPET")
```

### Markdown

```kotlin
section("Section") {
+"""
You can use string literals, templates ${1+1},
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
// useful if you have a lot of markdown
// content without code examples
includeMdFile("intro.md")
// link to things in your git repository
mdLink(DocGenTest::class)
mdLinkToRepoResource("build file","build.gradle.kts")
mdLinkToRepoResource("build file", "build.gradle.kts")
mdLinkToSelf("This class")
```

## This README is generated
### Source code blocks

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).
You can add your own source code blocks as well.

```kotlin
val readmeMarkdown by k4ERepo.md {
// for larger bits of text, it's nice to load them from a markdown file
includeMdFile("intro.md")

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<T>. 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()
}
}
subSection("Suspending examples") {
+"If you use co-routines, you can use a suspendingExample"

example(runExample = false) {
// runs the example in a runBlocking { .. }
suspendingExample {
// call some suspending code
}
}
}
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
}
}
}
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")
}
}
}
mdCodeBlock(
code = """
Useful if you have some non kotlin code that you want to show
""".trimIndent(),
type = "text"
)
```

section("This README is generated") {
+"""
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
exampleFromSnippet(
"com/jillesvangurp/kotlin4example/docs/readme.kt",
"README" + "CODE"
)

"""
And the code that actually writes the `README.md file` is a test:
""".trimIndent()
exampleFromSnippet(DocGenTest::class, "READMEWRITE")
}
### This README is generated

includeMdFile("outro.md")
}
```
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).

The code that writes the `README.md file` is as follows:

```kotlin
/**
Expand Down
50 changes: 21 additions & 29 deletions src/main/kotlin/com/jillesvangurp/kotlin4example/Kotlin4Example.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,24 @@ class Kotlin4Example(
block?.invoke(this)
}

private fun mdCodeBlock(
fun mdCodeBlock(
code: String,
type: String = "kotlin",
type: String,
allowLongLines: Boolean = false,
wrap: Boolean = false,
lineLength: Int = 80
) {
var c = code.replace(" ", " ")
if (wrap) {
var l = 1
c = c.lines().flatMap { line ->
if (line.length <= lineLength) {
listOf(line)
} else {
logger.warn { "wrapping line longer than 80 characters at line $l:\n$line" }
line.chunked(lineLength)
}.also {
l++
}
}.joinToString("\n")
}
Expand All @@ -96,7 +100,7 @@ class Kotlin4Example(
var error = 0
c.lines().forEach {
if (it.length > lineLength) {
logger.warn { "code block contains lines longer than 80 characters at line $l:\n$it" }
logger.error { "code block contains lines longer than 80 characters at line $l:\n$it" }
error++
}
l++
Expand All @@ -122,7 +126,6 @@ class Kotlin4Example(
return mdLink(
title = "`${clazz.simpleName!!}`",
target = sourceRepository.urlForFile(sourcePathForClass(clazz))

)
}

Expand Down Expand Up @@ -216,6 +219,7 @@ class Kotlin4Example(
)
}

@Deprecated("Use example, which now takes a suspending block by default", ReplaceWith("example(runExample, type, allowLongLines, wrap, lineLength, block)"))
fun <T> suspendingExample(
runExample: Boolean = true,
type: String = "kotlin",
Expand All @@ -224,42 +228,30 @@ class Kotlin4Example(
lineLength: Int = 80,
block: suspend BlockOutputCapture.() -> T
): ExampleOutput<T> {
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())
return example(
runExample = runExample,
type = type,
allowLongLines = allowLongLines,
wrap = wrap,
lineLength = lineLength,
block = block
)
}

fun <T> example(
runExample: Boolean = true,
type: String = "kotlin",
allowLongLines: Boolean = false,
wrap: Boolean = false,
lineLength: Int = 80,
block: BlockOutputCapture.() -> T
block: suspend BlockOutputCapture.() -> T
): ExampleOutput<T> {
val state = BlockOutputCapture()
val returnVal = try {
if (runExample) {
Result.success(block.invoke(state))
runBlocking {
Result.success(block.invoke(state))
}
} else {
Result.success(null)
}
Expand Down
Loading

0 comments on commit 1d94f7c

Please sign in to comment.