14
14
* limitations under the License.
15
15
*/
16
16
17
- import java.io.*
17
+ import java.io.File
18
+ import java.io.IOException
19
+ import java.io.LineNumberReader
20
+ import java.io.Reader
21
+ import java.util.*
22
+ import kotlin.properties.Delegates
23
+
24
+ // --- props in knit.properties
25
+
26
+ val knitProperties = ClassLoader .getSystemClassLoader()
27
+ .getResource(" knit.properties" ).openStream().use { Properties ().apply { load(it) } }
28
+
29
+ val siteRoot = knitProperties.getProperty(" site.root" )!!
30
+ val moduleRoots = knitProperties.getProperty(" module.roots" ).split(" " )
31
+ val moduleMarker = knitProperties.getProperty(" module.marker" )!!
32
+ val moduleDocs = knitProperties.getProperty(" module.docs" )!!
33
+
34
+ // --- markdown syntax
18
35
19
36
const val DIRECTIVE_START = " <!--- "
20
37
const val DIRECTIVE_END = " -->"
@@ -27,8 +44,7 @@ const val TEST_DIRECTIVE = "TEST"
27
44
28
45
const val TEST_OUT_DIRECTIVE = " TEST_OUT"
29
46
30
- const val SITE_ROOT_DIRECTIVE = " SITE_ROOT"
31
- const val DOCS_ROOT_DIRECTIVE = " DOCS_ROOT"
47
+ const val MODULE_DIRECTIVE = " MODULE"
32
48
const val INDEX_DIRECTIVE = " INDEX"
33
49
34
50
const val CODE_START = " ```kotlin"
@@ -54,10 +70,12 @@ fun main(args: Array<String>) {
54
70
println (" Usage: Knit <markdown-files>" )
55
71
return
56
72
}
57
- args.forEach(::knit)
73
+ args.forEach {
74
+ if (! knit(it)) System .exit(1 ) // abort on first error with error exit code
75
+ }
58
76
}
59
77
60
- fun knit (markdownFileName : String ) {
78
+ fun knit (markdownFileName : String ): Boolean {
61
79
println (" *** Reading $markdownFileName " )
62
80
val markdownFile = File (markdownFileName)
63
81
val tocLines = arrayListOf<String >()
@@ -71,8 +89,8 @@ fun knit(markdownFileName: String) {
71
89
val files = mutableSetOf<File >()
72
90
val allApiRefs = arrayListOf<ApiRef >()
73
91
val remainingApiRefNames = mutableSetOf<String >()
74
- var siteRoot : String? = null
75
- var docsRoot: String? = null
92
+ var moduleName : String by Delegates .notNull()
93
+ var docsRoot: String by Delegates .notNull()
76
94
// read markdown file
77
95
var putBackLine: String? = null
78
96
val markdown = markdownFile.withMarkdownTextReader {
@@ -129,7 +147,7 @@ fun knit(markdownFileName: String) {
129
147
TEST_DIRECTIVE -> {
130
148
require(lastPgk != null ) { " '$PACKAGE_PREFIX ' prefix was not found in emitted code" }
131
149
require(testOut != null ) { " $TEST_OUT_DIRECTIVE directive was not specified" }
132
- var predicate = directive.param
150
+ val predicate = directive.param
133
151
if (testLines.isEmpty()) {
134
152
if (directive.singleLine) {
135
153
require(! predicate.isEmpty()) { " $TEST_OUT_DIRECTIVE must be preceded by $TEST_START block or contain test predicate" }
@@ -141,19 +159,15 @@ fun knit(markdownFileName: String) {
141
159
makeTest(testOutLines, lastPgk!! , testLines, predicate)
142
160
testLines.clear()
143
161
}
144
- SITE_ROOT_DIRECTIVE -> {
162
+ MODULE_DIRECTIVE -> {
145
163
requireSingleLine(directive)
146
- siteRoot = directive.param
147
- }
148
- DOCS_ROOT_DIRECTIVE -> {
149
- requireSingleLine(directive)
150
- docsRoot = directive.param
164
+ moduleName = directive.param
165
+ docsRoot = findModuleRootDir(moduleName) + " /" + moduleDocs + " /" + moduleName
151
166
}
152
167
INDEX_DIRECTIVE -> {
153
168
requireSingleLine(directive)
154
- require(siteRoot != null ) { " $SITE_ROOT_DIRECTIVE must be specified" }
155
- require(docsRoot != null ) { " $DOCS_ROOT_DIRECTIVE must be specified" }
156
- val indexLines = processApiIndex(siteRoot!! , docsRoot!! , directive.param, remainingApiRefNames)
169
+ val indexLines = processApiIndex(siteRoot + " /" + moduleName, docsRoot, directive.param, remainingApiRefNames)
170
+ ? : throw IllegalArgumentException (" Failed to load index for ${directive.param} " )
157
171
skip = true
158
172
while (true ) {
159
173
val skipLine = readLine() ? : break @mainLoop
@@ -210,7 +224,7 @@ fun knit(markdownFileName: String) {
210
224
writeLinesIfNeeded(file, outLines)
211
225
}
212
226
}
213
- }
227
+ } ? : return false // false when failed
214
228
// update markdown file with toc
215
229
val newLines = buildList<String > {
216
230
addAll(markdown.preTocText)
@@ -230,6 +244,7 @@ fun knit(markdownFileName: String) {
230
244
}
231
245
// write test output
232
246
flushTestOut(markdownFile.parentFile, testOut, testOutLines)
247
+ return true
233
248
}
234
249
235
250
fun makeTest (testOutLines : MutableList <String >, pgk : String , test : List <String >, predicate : String ) {
@@ -360,19 +375,20 @@ class MarkdownTextReader(r: Reader) : LineNumberReader(r) {
360
375
}
361
376
}
362
377
363
- fun <T : LineNumberReader > File.withLineNumberReader (factory : (Reader ) -> T , block : T .() -> Unit ): T {
378
+ fun <T : LineNumberReader > File.withLineNumberReader (factory : (Reader ) -> T , block : T .() -> Unit ): T ? {
364
379
val reader = factory(reader())
365
380
reader.use {
366
381
try {
367
382
it.block()
368
- } catch (e: IllegalArgumentException ) {
383
+ } catch (e: Exception ) {
369
384
println (" ERROR: ${this @withLineNumberReader} : ${it.lineNumber} : ${e.message} " )
385
+ return null
370
386
}
371
387
}
372
388
return reader
373
389
}
374
390
375
- fun File.withMarkdownTextReader (block : MarkdownTextReader .() -> Unit ): MarkdownTextReader =
391
+ fun File.withMarkdownTextReader (block : MarkdownTextReader .() -> Unit ): MarkdownTextReader ? =
376
392
withLineNumberReader<MarkdownTextReader >(::MarkdownTextReader , block)
377
393
378
394
fun writeLinesIfNeeded (file : File , outLines : List <String >) {
@@ -392,6 +408,12 @@ fun writeLines(file: File, lines: List<String>) {
392
408
}
393
409
}
394
410
411
+ fun findModuleRootDir (name : String ): String =
412
+ moduleRoots
413
+ .map { it + " /" + name }
414
+ .firstOrNull { File (it + " /" + moduleMarker).exists() }
415
+ ? : throw IllegalArgumentException (" Module $name is not found in any of the module root dirs" )
416
+
395
417
data class ApiIndexKey (
396
418
val docsRoot : String ,
397
419
val pkg : String
@@ -408,7 +430,7 @@ fun loadApiIndex(
408
430
path : String ,
409
431
pkg : String ,
410
432
namePrefix : String = ""
411
- ): Map <String , String > {
433
+ ): Map <String , String >? {
412
434
val fileName = docsRoot + " /" + path + INDEX_MD
413
435
val visited = mutableSetOf<String >()
414
436
val map = HashMap <String ,String >()
@@ -425,10 +447,11 @@ fun loadApiIndex(
425
447
if (visited.add(refLink)) {
426
448
val path2 = path + " /" + refLink.substring(0 , refLink.length - INDEX_HTML .length)
427
449
map + = loadApiIndex(docsRoot, path2, pkg, refName + " ." )
450
+ ? : throw IllegalArgumentException (" Failed to parse ${docsRoot + " /" + path2} " )
428
451
}
429
452
}
430
453
}
431
- }
454
+ } ? : return null // return null on failure
432
455
return map
433
456
}
434
457
@@ -437,11 +460,11 @@ fun processApiIndex(
437
460
docsRoot : String ,
438
461
pkg : String ,
439
462
remainingApiRefNames : MutableSet <String >
440
- ): List <String > {
463
+ ): List <String >? {
441
464
val key = ApiIndexKey (docsRoot, pkg)
442
465
val map = apiIndexCache.getOrPut(key, {
443
466
print (" Parsing API docs at $docsRoot /$pkg : " )
444
- val result = loadApiIndex(docsRoot, pkg, pkg)
467
+ val result = loadApiIndex(docsRoot, pkg, pkg) ? : return null // null on failure
445
468
println (" ${result.size} definitions" )
446
469
result
447
470
})
0 commit comments