Skip to content

Commit b3d55a5

Browse files
committed
Knit tool for guide examples
1 parent 8feb778 commit b3d55a5

18 files changed

+214
-76
lines changed

coroutines-guide.md

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,16 @@ This is a short guide on core features of `kotlinx.coroutines` with a series of
2222
* [Sequential by default](#sequential-by-default)
2323
* [Concurrent using deferred value](#concurrent-using-deferred-value)
2424
* [Lazily deferred value](#lazily-deferred-value)
25+
26+
<!--- KNIT kotlinx-coroutines-core/src/test/kotlin/guide/.*\.kt -->
2527

28+
<!--- INCLUDE .*/example-([0-9]+)\.kt
29+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
30+
package guide.example$$1
31+
32+
import kotlinx.coroutines.experimental.*
33+
-->
34+
2635
## Coroutine basics
2736

2837
This section covers basic coroutine concepts.
@@ -42,7 +51,7 @@ fun main(args: Array<String>) {
4251
}
4352
```
4453

45-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-11.kt)
54+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-11.kt)
4655
4756
Run this code:
4857

@@ -80,7 +89,7 @@ fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
8089
}
8190
```
8291

83-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-12.kt)
92+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-12.kt)
8493
8594
The result is the same, but this code uses only non-blocking `delay`.
8695

@@ -97,6 +106,8 @@ class MyTest {
97106
}
98107
}
99108
```
109+
110+
<!--- CLEAR -->
100111

101112
### Waiting for a job
102113

@@ -114,7 +125,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
114125
}
115126
```
116127

117-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-13.kt)
128+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-13.kt)
118129
119130
Now the result is still the same, but the code of the main coroutine is not tied to the duration of
120131
the background job in any way. Much better.
@@ -141,7 +152,7 @@ suspend fun doWorld() {
141152
}
142153
```
143154

144-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-14.kt)
155+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-14.kt)
145156
146157
### Coroutines ARE light-weight
147158

@@ -159,7 +170,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
159170
}
160171
```
161172

162-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-15.kt)
173+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-15.kt)
163174
164175
It starts 100K coroutines and, after a second, each coroutine prints a dot.
165176
Now, try that with threads. What would happen? (Most likely your code will produce some sort of out-of-memory error)
@@ -181,7 +192,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
181192
}
182193
```
183194

184-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-16.kt)
195+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-16.kt)
185196
186197
You can run and see that it prints three lines and terminates:
187198

@@ -219,7 +230,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
219230
}
220231
```
221232

222-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-21.kt)
233+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-21.kt)
223234
224235
It produces the following output:
225236

@@ -262,7 +273,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
262273
}
263274
```
264275

265-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-22.kt)
276+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-22.kt)
266277
267278
Run it to see that it continues to print "I'm sleeping" even after cancellation.
268279

@@ -274,7 +285,28 @@ The other one is to explicitly check the cancellation status. Let us try the lat
274285

275286
Replace `while (true)` in the previous example with `while (isActive)` and rerun it.
276287

277-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-23.kt)
288+
```kotlin
289+
fun main(args: Array<String>) = runBlocking<Unit> {
290+
val job = launch(CommonPool) {
291+
var nextPrintTime = 0L
292+
var i = 0
293+
while (isActive) { // cancellable computation loop
294+
val currentTime = System.currentTimeMillis()
295+
if (currentTime >= nextPrintTime) {
296+
println("I'm sleeping ${i++} ...")
297+
nextPrintTime = currentTime + 500L
298+
}
299+
}
300+
}
301+
delay(1300L) // delay a bit
302+
println("main: I'm tired of waiting!")
303+
job.cancel() // cancels the job
304+
delay(1300L) // delay a bit to see if it was cancelled....
305+
println("main: Now I can quit.")
306+
}
307+
```
308+
309+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-23.kt)
278310
279311
As you can see, now this loop can be cancelled. `isActive` is a property that is available inside
280312
the code of coroutines via `CoroutineScope` object.
@@ -305,7 +337,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
305337
}
306338
```
307339

308-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-24.kt)
340+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-24.kt)
309341
310342
The example above produces the following output:
311343

@@ -351,7 +383,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
351383
}
352384
```
353385

354-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-25.kt)
386+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-25.kt)
355387
356388
### Timeout
357389

@@ -372,7 +404,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
372404
}
373405
```
374406

375-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-26.kt)
407+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-26.kt)
376408
377409
It produces the following output:
378410

@@ -422,6 +454,10 @@ We just use a normal sequential invocation, because the code in the coroutine, j
422454
code, is _sequential_ by default. The following example demonstrates that by measuring the total
423455
time it takes to execute both suspending functions:
424456

457+
<!--- INCLUDE .*/example-3[1-9].kt
458+
import kotlin.system.measureTimeMillis
459+
-->
460+
425461
```kotlin
426462
fun main(args: Array<String>) = runBlocking<Unit> {
427463
val time = measureTimeMillis {
@@ -433,7 +469,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
433469
}
434470
```
435471

436-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-31.kt)
472+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-31.kt)
437473
438474
It produces something like this:
439475

@@ -453,6 +489,19 @@ does not carry any resulting value, while `defer` returns a `Deferred` -- a kind
453489
that represent a promise to provide result later. You can use `.await()` on a deferred value to get its eventual result,
454490
but `Deferred` is also a `Job`, so you can cancel it if needed.
455491

492+
<!--- INCLUDE .*/example-3[2-9].kt
493+
494+
suspend fun doSomethingUsefulOne(): Int {
495+
delay(1000L) // pretend we are doing something useful here
496+
return 13
497+
}
498+
499+
suspend fun doSomethingUsefulTwo(): Int {
500+
delay(1000L) // pretend we are doing something useful here, too
501+
return 29
502+
}
503+
-->
504+
456505
```kotlin
457506
fun main(args: Array<String>) = runBlocking<Unit> {
458507
val time = measureTimeMillis {
@@ -464,7 +513,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
464513
}
465514
```
466515

467-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-32.kt)
516+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-32.kt)
468517
469518
It produces something like this:
470519

@@ -493,7 +542,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
493542
}
494543
```
495544

496-
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/examples/example-33.kt)
545+
> You can get full code [here](kotlinx-coroutines-core/src/test/kotlin/guide/example-33.kt)
497546
498547
It produces something like this:
499548

knit/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Knit
2+
3+
This is a very simple tool that produces Kotlin source example files from a markdown document that includes
4+
snippets of Kotlin code in its body. It is used to produce examples for
5+
[coroutines guide](../coroutines-guide.md).
6+

knit/src/Knit.kt

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import java.io.File
2+
import java.io.IOException
3+
import java.io.LineNumberReader
4+
5+
val KNIT_DIRECTIVE = "<!--- KNIT "
6+
val INCLUDE_DIRECTIVE = "<!--- INCLUDE "
7+
val CLEAR_DIRECTIVE = "<!--- CLEAR "
8+
val DIRECTIVE_END = "-->"
9+
10+
val CODE_START = "```kotlin"
11+
val CODE_END = "```"
12+
13+
fun main(args: Array<String>) {
14+
if(args.size != 1) {
15+
println("Usage: Knit <markdown-file>")
16+
return
17+
}
18+
var knitRegex: Regex? = null
19+
val includes = arrayListOf<Include>()
20+
val code = arrayListOf<String>()
21+
val files = mutableSetOf<String>()
22+
File(args[0]).withLineNumberReader {
23+
mainLoop@ while (true) {
24+
val inLine = readLine() ?: break
25+
when {
26+
inLine.startsWith(KNIT_DIRECTIVE) -> {
27+
require(inLine.endsWith(DIRECTIVE_END)) { "KNIT directive must end on the same line with '$DIRECTIVE_END'" }
28+
require(knitRegex == null) { "Only one KNIT directive is supported"}
29+
knitRegex = Regex("\\((" + inLine.substring(KNIT_DIRECTIVE.length, inLine.length - DIRECTIVE_END.length).trim() + ")\\)")
30+
continue@mainLoop
31+
}
32+
inLine.startsWith(INCLUDE_DIRECTIVE) -> {
33+
val include = Include(Regex(inLine.substring(INCLUDE_DIRECTIVE.length).trim()))
34+
while (true) {
35+
val includeLine = readLine() ?: break
36+
if (includeLine.startsWith(DIRECTIVE_END)) break
37+
include.lines += includeLine
38+
}
39+
includes += include
40+
continue@mainLoop
41+
}
42+
inLine.startsWith(CODE_START) -> {
43+
code += ""
44+
while (true) {
45+
val codeLine = readLine() ?: break
46+
if (codeLine.startsWith(CODE_END)) break
47+
code += codeLine
48+
}
49+
continue@mainLoop
50+
}
51+
inLine.startsWith(CLEAR_DIRECTIVE) -> {
52+
require(inLine.endsWith(DIRECTIVE_END)) { "CLEAR directive must end on the same line with '$DIRECTIVE_END'" }
53+
code.clear()
54+
continue@mainLoop
55+
}
56+
}
57+
knitRegex?.find(inLine)?.let { knitMatch ->
58+
val file = knitMatch.groups[1]!!.value
59+
require(files.add(file)) { "Duplicate file name: $file"}
60+
println("Knitting $file ...")
61+
val outLines = arrayListOf<String>()
62+
for (include in includes) {
63+
val includeMatch = include.regex.matchEntire(file) ?: continue
64+
include.lines.forEach { includeLine ->
65+
var toOutLine = includeLine
66+
for ((id, group) in includeMatch.groups.withIndex()) {
67+
if (group != null)
68+
toOutLine = toOutLine.replace("\$\$$id", group.value)
69+
}
70+
outLines += toOutLine
71+
}
72+
}
73+
outLines += code
74+
code.clear()
75+
val oldLines = try { File(file).readLines() } catch (e: IOException) { emptyList<String>() }
76+
if (outLines != oldLines) {
77+
println(" Writing $file ...")
78+
File(file).printWriter().use { out ->
79+
outLines.forEach { out.println(it) }
80+
}
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
class Include(val regex: Regex, val lines: MutableList<String> = arrayListOf())
88+
89+
fun File.withLineNumberReader(block: LineNumberReader.() -> Unit) {
90+
LineNumberReader(reader()).use {
91+
try {
92+
it.block()
93+
} catch (e: IllegalArgumentException) {
94+
println("ERROR: $this: ${it.lineNumber}: ${e.message}")
95+
}
96+
}
97+
}

kotlinx-coroutines-core/src/test/kotlin/examples/example-11.kt renamed to kotlinx-coroutines-core/src/test/kotlin/guide/example-11.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
package example11
1+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
2+
package guide.example11
23

3-
import kotlinx.coroutines.experimental.CommonPool
4-
import kotlinx.coroutines.experimental.delay
5-
import kotlinx.coroutines.experimental.launch
4+
import kotlinx.coroutines.experimental.*
65

76
fun main(args: Array<String>) {
87
launch(CommonPool) { // create new coroutine in common thread pool

kotlinx-coroutines-core/src/test/kotlin/examples/example-12.kt renamed to kotlinx-coroutines-core/src/test/kotlin/guide/example-12.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
package example12
1+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
2+
package guide.example12
23

3-
import kotlinx.coroutines.experimental.CommonPool
4-
import kotlinx.coroutines.experimental.delay
5-
import kotlinx.coroutines.experimental.launch
6-
import kotlinx.coroutines.experimental.runBlocking
4+
import kotlinx.coroutines.experimental.*
75

86
fun main(args: Array<String>) = runBlocking<Unit> { // start main coroutine
97
launch(CommonPool) { // create new coroutine in common thread pool

kotlinx-coroutines-core/src/test/kotlin/examples/example-13.kt renamed to kotlinx-coroutines-core/src/test/kotlin/guide/example-13.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package example13
1+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
2+
package guide.example13
23

34
import kotlinx.coroutines.experimental.*
45

kotlinx-coroutines-core/src/test/kotlin/examples/example-14.kt renamed to kotlinx-coroutines-core/src/test/kotlin/guide/example-14.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package example14
1+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
2+
package guide.example14
23

34
import kotlinx.coroutines.experimental.*
45

kotlinx-coroutines-core/src/test/kotlin/examples/example-15.kt renamed to kotlinx-coroutines-core/src/test/kotlin/guide/example-15.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package example15
1+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
2+
package guide.example15
23

34
import kotlinx.coroutines.experimental.*
45

Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
package example16
1+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
2+
package guide.example16
23

3-
import kotlinx.coroutines.experimental.CommonPool
4-
import kotlinx.coroutines.experimental.delay
5-
import kotlinx.coroutines.experimental.launch
6-
import kotlinx.coroutines.experimental.runBlocking
4+
import kotlinx.coroutines.experimental.*
75

86
fun main(args: Array<String>) = runBlocking<Unit> {
97
launch(CommonPool) {
@@ -13,4 +11,4 @@ fun main(args: Array<String>) = runBlocking<Unit> {
1311
}
1412
}
1513
delay(1300L) // just quit after delay
16-
}
14+
}

0 commit comments

Comments
 (0)