1414 * limitations under the License.
1515 */
1616
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
1835
1936const val DIRECTIVE_START = " <!--- "
2037const val DIRECTIVE_END = " -->"
@@ -27,8 +44,7 @@ const val TEST_DIRECTIVE = "TEST"
2744
2845const val TEST_OUT_DIRECTIVE = " TEST_OUT"
2946
30- const val SITE_ROOT_DIRECTIVE = " SITE_ROOT"
31- const val DOCS_ROOT_DIRECTIVE = " DOCS_ROOT"
47+ const val MODULE_DIRECTIVE = " MODULE"
3248const val INDEX_DIRECTIVE = " INDEX"
3349
3450const val CODE_START = " ```kotlin"
@@ -54,10 +70,12 @@ fun main(args: Array<String>) {
5470 println (" Usage: Knit <markdown-files>" )
5571 return
5672 }
57- args.forEach(::knit)
73+ args.forEach {
74+ if (! knit(it)) System .exit(1 ) // abort on first error with error exit code
75+ }
5876}
5977
60- fun knit (markdownFileName : String ) {
78+ fun knit (markdownFileName : String ): Boolean {
6179 println (" *** Reading $markdownFileName " )
6280 val markdownFile = File (markdownFileName)
6381 val tocLines = arrayListOf<String >()
@@ -71,8 +89,8 @@ fun knit(markdownFileName: String) {
7189 val files = mutableSetOf<File >()
7290 val allApiRefs = arrayListOf<ApiRef >()
7391 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()
7694 // read markdown file
7795 var putBackLine: String? = null
7896 val markdown = markdownFile.withMarkdownTextReader {
@@ -129,7 +147,7 @@ fun knit(markdownFileName: String) {
129147 TEST_DIRECTIVE -> {
130148 require(lastPgk != null ) { " '$PACKAGE_PREFIX ' prefix was not found in emitted code" }
131149 require(testOut != null ) { " $TEST_OUT_DIRECTIVE directive was not specified" }
132- var predicate = directive.param
150+ val predicate = directive.param
133151 if (testLines.isEmpty()) {
134152 if (directive.singleLine) {
135153 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) {
141159 makeTest(testOutLines, lastPgk!! , testLines, predicate)
142160 testLines.clear()
143161 }
144- SITE_ROOT_DIRECTIVE -> {
162+ MODULE_DIRECTIVE -> {
145163 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
151166 }
152167 INDEX_DIRECTIVE -> {
153168 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} " )
157171 skip = true
158172 while (true ) {
159173 val skipLine = readLine() ? : break @mainLoop
@@ -210,7 +224,7 @@ fun knit(markdownFileName: String) {
210224 writeLinesIfNeeded(file, outLines)
211225 }
212226 }
213- }
227+ } ? : return false // false when failed
214228 // update markdown file with toc
215229 val newLines = buildList<String > {
216230 addAll(markdown.preTocText)
@@ -230,6 +244,7 @@ fun knit(markdownFileName: String) {
230244 }
231245 // write test output
232246 flushTestOut(markdownFile.parentFile, testOut, testOutLines)
247+ return true
233248}
234249
235250fun makeTest (testOutLines : MutableList <String >, pgk : String , test : List <String >, predicate : String ) {
@@ -360,19 +375,20 @@ class MarkdownTextReader(r: Reader) : LineNumberReader(r) {
360375 }
361376}
362377
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 ? {
364379 val reader = factory(reader())
365380 reader.use {
366381 try {
367382 it.block()
368- } catch (e: IllegalArgumentException ) {
383+ } catch (e: Exception ) {
369384 println (" ERROR: ${this @withLineNumberReader} : ${it.lineNumber} : ${e.message} " )
385+ return null
370386 }
371387 }
372388 return reader
373389}
374390
375- fun File.withMarkdownTextReader (block : MarkdownTextReader .() -> Unit ): MarkdownTextReader =
391+ fun File.withMarkdownTextReader (block : MarkdownTextReader .() -> Unit ): MarkdownTextReader ? =
376392 withLineNumberReader<MarkdownTextReader >(::MarkdownTextReader , block)
377393
378394fun writeLinesIfNeeded (file : File , outLines : List <String >) {
@@ -392,6 +408,12 @@ fun writeLines(file: File, lines: List<String>) {
392408 }
393409}
394410
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+
395417data class ApiIndexKey (
396418 val docsRoot : String ,
397419 val pkg : String
@@ -408,7 +430,7 @@ fun loadApiIndex(
408430 path : String ,
409431 pkg : String ,
410432 namePrefix : String = ""
411- ): Map <String , String > {
433+ ): Map <String , String >? {
412434 val fileName = docsRoot + " /" + path + INDEX_MD
413435 val visited = mutableSetOf<String >()
414436 val map = HashMap <String ,String >()
@@ -425,10 +447,11 @@ fun loadApiIndex(
425447 if (visited.add(refLink)) {
426448 val path2 = path + " /" + refLink.substring(0 , refLink.length - INDEX_HTML .length)
427449 map + = loadApiIndex(docsRoot, path2, pkg, refName + " ." )
450+ ? : throw IllegalArgumentException (" Failed to parse ${docsRoot + " /" + path2} " )
428451 }
429452 }
430453 }
431- }
454+ } ? : return null // return null on failure
432455 return map
433456}
434457
@@ -437,11 +460,11 @@ fun processApiIndex(
437460 docsRoot : String ,
438461 pkg : String ,
439462 remainingApiRefNames : MutableSet <String >
440- ): List <String > {
463+ ): List <String >? {
441464 val key = ApiIndexKey (docsRoot, pkg)
442465 val map = apiIndexCache.getOrPut(key, {
443466 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
445468 println (" ${result.size} definitions" )
446469 result
447470 })
0 commit comments