Skip to content

Commit 8111b21

Browse files
committed
Knit: Get rid of custom templating, simplify knit name generation
* All templating is done via FreeMarker now. * No need to customize knit.pattern anymore (works out-of-the box) * "knit.name" is automatically generated based on example's file name "example-basic-01" -> "exampleBasic01" * Not match-pattern-group woodoo anymore, "knit.pattern" cannot have any user-defined match groups. * No need to look for "package xxx" in knitted sources, as the example's package is always ${knit.package}.${knit.name} * Simpler test names: testKotlinxCoroutinesGuideBasic01 -> testExampleBasic01
1 parent cb411c7 commit 8111b21

File tree

127 files changed

+382
-383
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+382
-383
lines changed

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,6 @@ The `develop` branch is pushed to `master` during release.
273273
[ListenableFuture.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/com.google.common.util.concurrent.-listenable-future/await.html
274274
<!--- MODULE kotlinx-coroutines-play-services -->
275275
<!--- INDEX kotlinx.coroutines.tasks -->
276-
[Task.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/await.html
277276
<!--- MODULE kotlinx-coroutines-reactive -->
278277
<!--- INDEX kotlinx.coroutines.reactive -->
279278
[Publisher.collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/collect.html

knit/resources/knit.code.include

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
*/
44

55
// This file was automatically generated from ${file.name} by Knit tool. Do not edit.
6-
package ${knit.package}.$$1$$2
6+
package ${knit.package}.${knit.name}

knit/resources/knit.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module.roots=. integration reactive ui
88
module.marker=build.gradle
99
module.docs=build/dokka
1010

11-
knit.pattern=example-([a-z]+)-(##)\\.kt
11+
knit.pattern=example-[a-zA-Z0-9-]+-##\\.kt
1212
knit.include=knit.code.include
1313

1414
test.include=knit.test.include

knit/src/Knit.kt

+68-66
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ val moduleRoots = globalProperties.getValue("module.roots").split(" ")
1313
val moduleMarker = globalProperties.getValue("module.marker")
1414
val moduleDocs = globalProperties.getValue("module.docs")
1515

16+
// --- other props
17+
18+
const val TEST_NAME_PROP = "test.name"
19+
const val TEST_DIR_PROP = "test.dir"
20+
const val TEST_INCLUDE_PROP = "test.include"
21+
22+
const val KNIT_PACKAGE_PROP = "knit.package"
23+
const val KNIT_PATTERN_PROP = "knit.pattern"
24+
const val KNIT_DIR_PROP = "knit.dir"
25+
const val KNIT_INCLUDE_PROP = "knit.include"
26+
1627
// --- markdown syntax
1728

1829
const val DIRECTIVE_START = "<!--- "
@@ -25,10 +36,9 @@ const val CLEAR_DIRECTIVE = "CLEAR"
2536
const val TEST_DIRECTIVE = "TEST"
2637

2738
const val KNIT_AUTONUMBER_PLACEHOLDER = '#'
28-
const val KNIT_AUTONUMBER_REGEX = "[0-9a-z]+"
39+
const val KNIT_AUTONUMBER_REGEX = "([0-9a-z]+)"
2940

3041
const val TEST_NAME_DIRECTIVE = "TEST_NAME"
31-
const val TEST_NAME_PROP = "test.name"
3242

3343
const val MODULE_DIRECTIVE = "MODULE"
3444
const val INDEX_DIRECTIVE = "INDEX"
@@ -44,7 +54,6 @@ const val TEST_END = "```"
4454

4555
const val SECTION_START = "##"
4656

47-
const val PACKAGE_PREFIX = "package "
4857
const val STARTS_WITH_PREDICATE = "STARTS_WITH"
4958
const val ARBITRARY_TIME_PREDICATE = "ARBITRARY_TIME"
5059
const val FLEXIBLE_TIME_PREDICATE = "FLEXIBLE_TIME"
@@ -75,70 +84,69 @@ fun main(args: Array<String>) {
7584
class KnitConfig(
7685
val path: String,
7786
val regex: Regex,
78-
val autonumberGroup: Int,
7987
val autonumberDigits: Int
8088
)
8189

8290
fun KnitProps.knitConfig(): KnitConfig? {
83-
val dir = this["knit.dir"] ?: return null
84-
var pattern = getValue("knit.pattern")
91+
val dir = this[KNIT_DIR_PROP] ?: return null
92+
var pattern = getValue(KNIT_PATTERN_PROP)
8593
val i = pattern.indexOf(KNIT_AUTONUMBER_PLACEHOLDER)
86-
var autonumberGroup = 0
8794
var autonumberDigits = 0
8895
if (i >= 0) {
8996
val j = pattern.lastIndexOf(KNIT_AUTONUMBER_PLACEHOLDER)
9097
autonumberDigits = j - i + 1
9198
require(pattern.substring(i, j + 1) == KNIT_AUTONUMBER_PLACEHOLDER.toString().repeat(autonumberDigits)) {
92-
"knit.pattern can only use a contiguous range of '$KNIT_AUTONUMBER_PLACEHOLDER' for auto-numbering"
99+
"$KNIT_PATTERN_PROP property can only use a contiguous range of '$KNIT_AUTONUMBER_PLACEHOLDER' for auto-numbering"
93100
}
94-
autonumberGroup = pattern.substring(0, i).count { it == '(' } + 1 // note: it does not understand escaped open braces
95-
var replacementRegex = KNIT_AUTONUMBER_REGEX
96-
if (pattern.getOrNull(i - 1) != '(' || pattern.getOrNull(j + 1) != ')') {
97-
// needs its own group to extract number
98-
autonumberGroup++
99-
replacementRegex = "($replacementRegex)"
101+
require('(' !in pattern && ')' !in pattern) {
102+
"$KNIT_PATTERN_PROP property cannot have match groups"
100103
}
101-
pattern = pattern.substring(0, i) + replacementRegex + pattern.substring(j + 1)
104+
pattern = pattern.substring(0, i) + KNIT_AUTONUMBER_REGEX + pattern.substring(j + 1)
102105
}
103-
val path = "$dir$pattern"
104-
return KnitConfig(path, Regex("\\(($path)\\)"), autonumberGroup, autonumberDigits)
106+
val path = "$dir($pattern)"
107+
return KnitConfig(path, Regex("\\(($path)\\)"), autonumberDigits)
105108
}
106109

110+
@Suppress("unused") // This class is passed to freemarker template
107111
class KnitIncludeEnv(
108112
val file: File,
109-
props: KnitProps
113+
props: KnitProps,
114+
knitName: String
110115
) {
111-
val knit = props.getMap("knit")
116+
val knit = props.getMap("knit") + mapOf("name" to knitName)
112117
}
113118

114-
fun KnitConfig.loadMainInclude(file: File, props: KnitProps): Include {
119+
fun KnitConfig.loadMainInclude(file: File, props: KnitProps, knitName: String): Include {
115120
val include = Include(Regex(path))
116-
include.lines += props.loadTemplateLines("knit.include", KnitIncludeEnv(file, props))
121+
include.lines += props.loadTemplateLines(KNIT_INCLUDE_PROP, KnitIncludeEnv(file, props, knitName))
117122
include.lines += ""
118123
return include
119124
}
120125

126+
// Reference to knitted example's full package (pkg.name)
127+
class KnitRef(val pkg: String, val name: String) {
128+
override fun toString(): String = "$pkg.$name"
129+
}
130+
121131
fun knit(markdownFile: File): Boolean {
122132
println("*** Reading $markdownFile")
123133
val props = markdownFile.findProps()
124134
val knit = props.knitConfig()
125-
var knitAutonumberIndex = HashMap<String, Int>()
135+
val knitAutonumberIndex = HashMap<String, Int>()
126136
val tocLines = arrayListOf<String>()
127137
val includes = arrayListOf<Include>()
128138
val codeLines = arrayListOf<String>()
129139
val testLines = arrayListOf<String>()
130140
var testName: String? = props[TEST_NAME_PROP]
131141
val testOutLines = arrayListOf<String>()
132-
var lastPgk: String? = null
142+
var lastKnit: KnitRef? = null
133143
val files = mutableSetOf<File>()
134144
val allApiRefs = arrayListOf<ApiRef>()
135145
val remainingApiRefNames = mutableSetOf<String>()
136146
var moduleName: String by Delegates.notNull()
137147
var docsRoot: String by Delegates.notNull()
138148
var retryKnitLater = false
139149
val tocRefs = ArrayList<TocRef>().also { tocRefMap[markdownFile] = it }
140-
// load main includes (if defined)
141-
knit?.loadMainInclude(markdownFile, props)?.let { includes += it }
142150
// read markdown file
143151
val markdown = markdownFile.withMarkdownTextReader {
144152
mainLoop@ while (true) {
@@ -200,18 +208,18 @@ fun knit(markdownFile: File): Boolean {
200208
testName = directive.param
201209
}
202210
TEST_DIRECTIVE -> {
203-
require(lastPgk != null) { "'$PACKAGE_PREFIX' prefix was not found in emitted code"}
211+
require(lastKnit != null) { "$TEST_DIRECTIVE must be preceded by knitted file" }
204212
require(testName != null) { "Neither $TEST_NAME_DIRECTIVE directive nor '$TEST_NAME_PROP'property was specified" }
205213
val predicate = directive.param
206214
if (testLines.isEmpty()) {
207215
if (directive.singleLine) {
208-
require(!predicate.isEmpty()) { "$TEST_DIRECTIVE must be preceded by $TEST_START block or contain test predicate"}
216+
require(predicate.isNotEmpty()) { "$TEST_DIRECTIVE must be preceded by $TEST_START block or contain test predicate"}
209217
} else
210218
testLines += readUntil(DIRECTIVE_END)
211219
} else {
212220
requireSingleLine(directive)
213221
}
214-
makeTest(testOutLines, lastPgk!!, testLines, predicate)
222+
makeTest(testOutLines, lastKnit!!, testLines, predicate)
215223
testLines.clear()
216224
}
217225
MODULE_DIRECTIVE -> {
@@ -261,12 +269,13 @@ fun knit(markdownFile: File): Boolean {
261269
}
262270
}
263271
knit?.regex?.find(inLine)?.let knitRegexMatch@{ knitMatch ->
264-
val fileName = knitMatch.groups[1]!!.value
272+
val path = knitMatch.groups[1]!!.value // full matched knit path dir dir & file name
273+
val fileGroup = knitMatch.groups[2]!!
274+
val fileName = fileGroup.value // knitted file name like "example-basic-01.kt"
265275
if (knit.autonumberDigits != 0) {
266-
val numGroup = knitMatch.groups[knit.autonumberGroup]!!
267-
val key = knitMatch.groupValues.withIndex()
268-
.filter { it.index > 1 && it.index != knit.autonumberGroup }
269-
.joinToString("-") { it.value }
276+
val numGroup = knitMatch.groups[3]!! // file number part like "01"
277+
val key = inLine.substring(fileGroup.range.first, numGroup.range.first) +
278+
inLine.substring(numGroup.range.last + 1, fileGroup.range.last + 1)
270279
val index = knitAutonumberIndex.getOrElse(key) { 1 }
271280
val num = index.toString().padStart(knit.autonumberDigits, '0')
272281
if (numGroup.value != num) { // update and retry with this line if a different number
@@ -277,25 +286,23 @@ fun knit(markdownFile: File): Boolean {
277286
}
278287
knitAutonumberIndex[key] = index + 1
279288
}
280-
val file = File(markdownFile.parentFile, fileName)
289+
val file = File(markdownFile.parentFile, path)
281290
require(files.add(file)) { "Duplicate file: $file"}
282291
println("Knitting $file ...")
283292
val outLines = arrayListOf<String>()
284-
for (include in includes) {
285-
val includeMatch = include.regex.matchEntire(fileName) ?: continue
286-
include.lines.forEach { includeLine ->
287-
val line = makeReplacements(includeLine, includeMatch)
288-
if (line.startsWith(PACKAGE_PREFIX))
289-
lastPgk = line.substring(PACKAGE_PREFIX.length).trim()
290-
outLines += line
291-
}
292-
}
293+
val fileIncludes = arrayListOf<Include>()
294+
// load & process template of the main include
295+
val knitName = fileName.toKnitName()
296+
fileIncludes += knit.loadMainInclude(markdownFile, props, knitName)
297+
fileIncludes += includes.filter { it.regex.matches(path) }
298+
for (include in fileIncludes) outLines += include.lines
293299
if (outLines.last().isNotBlank()) outLines += ""
294300
for (code in codeLines) {
295301
outLines += code.replace("System.currentTimeMillis()", "currentTimeMillis()")
296302
}
297303
codeLines.clear()
298304
writeLinesIfNeeded(file, outLines)
305+
lastKnit = KnitRef(props.getValue(KNIT_PACKAGE_PROP), knitName)
299306
}
300307
}
301308
} ?: return false // false when failed
@@ -326,24 +333,27 @@ fun knit(markdownFile: File): Boolean {
326333
return true
327334
}
328335

329-
data class TocRef(val levelPrefix: String, val name: String, val ref: String)
336+
// Converts file name like "example-basic-01.kt" to unique knit.name for package like "exampleBasic01"
337+
private fun String.toKnitName(): String = substringBefore('.').capitalizeAfter('-')
330338

331-
fun makeTest(testOutLines: MutableList<String>, pgk: String, test: List<String>, predicate: String) {
332-
val funName = buildString {
333-
var cap = true
334-
for (c in pgk) {
335-
cap = if (c == '.') {
336-
true
337-
} else {
338-
append(if (cap) c.toUpperCase() else c)
339-
false
340-
}
339+
private fun String.capitalizeAfter(char: Char): String = buildString {
340+
var cap = false
341+
for (c in this@capitalizeAfter) {
342+
cap = if (c == char) true else {
343+
append(if (cap) c.toUpperCase() else c)
344+
false
341345
}
342346
}
347+
}
348+
349+
data class TocRef(val levelPrefix: String, val name: String, val ref: String)
350+
351+
fun makeTest(testOutLines: MutableList<String>, knit: KnitRef, test: List<String>, predicate: String) {
352+
val funName = knit.name.capitalize()
343353
testOutLines += ""
344354
testOutLines += " @Test"
345355
testOutLines += " fun test$funName() {"
346-
val prefix = " test(\"$funName\") { $pgk.main() }"
356+
val prefix = " test(\"$funName\") { $knit.main() }"
347357
when (predicate) {
348358
"" -> makeTestLines(testOutLines, prefix, "verifyLines", test)
349359
STARTS_WITH_PREDICATE -> makeTestLines(testOutLines, prefix, "verifyLinesStartWith", test)
@@ -372,15 +382,7 @@ private fun makeTestLines(testOutLines: MutableList<String>, prefix: String, met
372382
testOutLines += " )"
373383
}
374384

375-
private fun makeReplacements(line: String, match: MatchResult): String {
376-
var result = line
377-
for ((id, group) in match.groups.withIndex()) {
378-
if (group != null)
379-
result = result.replace("\$\$$id", group.value)
380-
}
381-
return result
382-
}
383-
385+
@Suppress("unused") // This class is passed to freemarker template
384386
class TestTemplateEnv(
385387
val file: File,
386388
props: KnitProps,
@@ -393,10 +395,10 @@ private fun flushTestOut(file: File, props: KnitProps, testName: String?, testOu
393395
if (testOutLines.isEmpty()) return
394396
if (testName == null) return
395397
val lines = arrayListOf<String>()
396-
lines += props.loadTemplateLines("test.include", TestTemplateEnv(file, props, testName))
398+
lines += props.loadTemplateLines(TEST_INCLUDE_PROP, TestTemplateEnv(file, props, testName))
397399
lines += testOutLines
398400
lines += "}"
399-
val testFile = File(props.getFile("test.dir"), "$testName.kt")
401+
val testFile = File(props.getFile(TEST_DIR_PROP), "$testName.kt")
400402
println("Checking $testFile")
401403
writeLinesIfNeeded(testFile, lines)
402404
testOutLines.clear()

kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic01
6+
package kotlinx.coroutines.guide.exampleBasic01
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic02
6+
package kotlinx.coroutines.guide.exampleBasic02
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic03
6+
package kotlinx.coroutines.guide.exampleBasic03
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic04
6+
package kotlinx.coroutines.guide.exampleBasic04
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic05
6+
package kotlinx.coroutines.guide.exampleBasic05
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic06
6+
package kotlinx.coroutines.guide.exampleBasic06
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic07
6+
package kotlinx.coroutines.guide.exampleBasic07
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-basic-08.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic08
6+
package kotlinx.coroutines.guide.exampleBasic08
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-basic-09.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from basics.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.basic09
6+
package kotlinx.coroutines.guide.exampleBasic09
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from cancellation-and-timeouts.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.cancel01
6+
package kotlinx.coroutines.guide.exampleCancel01
77

88
import kotlinx.coroutines.*
99

kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
// This file was automatically generated from cancellation-and-timeouts.md by Knit tool. Do not edit.
6-
package kotlinx.coroutines.guide.cancel02
6+
package kotlinx.coroutines.guide.exampleCancel02
77

88
import kotlinx.coroutines.*
99

0 commit comments

Comments
 (0)