diff options
Diffstat (limited to 'runners/cli/src')
-rw-r--r-- | runners/cli/src/main/kotlin/cli/DokkaArgumentsParser.kt | 185 | ||||
-rw-r--r-- | runners/cli/src/main/kotlin/cli/main.kt | 334 |
2 files changed, 395 insertions, 124 deletions
diff --git a/runners/cli/src/main/kotlin/cli/DokkaArgumentsParser.kt b/runners/cli/src/main/kotlin/cli/DokkaArgumentsParser.kt new file mode 100644 index 00000000..5d795da7 --- /dev/null +++ b/runners/cli/src/main/kotlin/cli/DokkaArgumentsParser.kt @@ -0,0 +1,185 @@ +package org.jetbrains.dokka + +import kotlinx.cli.* +import kotlin.reflect.KProperty + +class ParseContext(val cli: CommandLineInterface = CommandLineInterface("dokka")) { + private val transformActions = mutableMapOf<KProperty<*>, (String) -> Unit>() + private val flagActions = mutableMapOf<KProperty<*>, () -> Unit>() + + fun registerFlagAction( + keys: List<String>, + help: String, + property: KProperty<*>, + invoke: () -> Unit + ) { + if (property !in flagActions.keys) { + cli.flagAction(keys, help) { + flagActions[property]!!() + } + } + flagActions[property] = invoke + + } + + fun registerSingleOption( + keys: List<String>, + help: String, + property: KProperty<*>, + invoke: (String) -> Unit + ) { + if (property !in transformActions.keys) { + cli.singleAction(keys, help) { + transformActions[property]!!(it) + } + } + transformActions[property] = invoke + } + + fun registerRepeatableOption( + keys: List<String>, + help: String, + property: KProperty<*>, + invoke: (String) -> Unit + ) { + if (property !in transformActions.keys) { + cli.repeatingAction(keys, help) { + transformActions[property]!!(it) + } + } + transformActions[property] = invoke + } + + fun parse(args: Array<String>) { + cli.parseArgs(*args) + } + +} + +fun CommandLineInterface.singleAction( + keys: List<String>, + help: String, + invoke: (String) -> Unit +) = registerAction( + object : FlagActionBase(keys, help) { + override fun invoke(arguments: ListIterator<String>) { + if (arguments.hasNext()) { + val msg = arguments.next() + invoke(msg) + } + } + + override fun invoke() { + error("should be never called") + } + } +) + +fun CommandLineInterface.repeatingAction( + keys: List<String>, + help: String, + invoke: (String) -> Unit +) = registerAction( + object : FlagActionBase(keys, help) { + override fun invoke(arguments: ListIterator<String>) { + while (arguments.hasNext()) { + val message = arguments.next() + + if (this@repeatingAction.getFlagAction(message) != null) { + arguments.previous() + break + } + invoke(message) + } + } + + override fun invoke() { + error("should be never called") + } + } + +) + + +class DokkaArgumentsParser(val args: Array<String>, val parseContext: ParseContext) { + class OptionDelegate<T>( + var value: T, + private val action: (delegate: OptionDelegate<T>, property: KProperty<*>) -> Unit + ) { + operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value + operator fun provideDelegate(thisRef: Any, property: KProperty<*>): OptionDelegate<T> { + action(this, property) + return this + } + } + + fun <T> parseInto(dest: T): T { + // TODO: constructor: (DokkaArgumentsParser) -> T + parseContext.parse(args) + return dest + } + + fun <T> repeatableOption( + keys: List<String>, + help: String, + transform: (String) -> T + ) = OptionDelegate(mutableListOf<T>()) { delegate, property -> + parseContext.registerRepeatableOption(keys, help, property) { + delegate.value.add(transform(it)) + } + } + + fun <T : String?> repeatableOption( + keys: List<String>, + help: String + ) = repeatableOption(keys, help) { it as T } + + fun <T> repeatableFlag( + keys: List<String>, + help: String, + initElement: (ParseContext) -> T + ) = OptionDelegate(mutableListOf<T>()) { delegate, property -> + parseContext.registerFlagAction(keys, help, property) { + delegate.value.add(initElement(parseContext)) + } + } + + fun <T> singleFlag( + keys: List<String>, + help: String, + initElement: (ParseContext) -> T, + transform: () -> T + ) = OptionDelegate(initElement(parseContext)) { delegate, property -> + parseContext.registerFlagAction(keys, help, property) { + delegate.value = transform() + } + } + + fun singleFlag( + keys: List<String>, + help: String + ) = singleFlag(keys, help, { false }, { true }) + + fun <T : String?> stringOption( + keys: List<String>, + help: String, + defaultValue: T + ) = singleOption(keys, help, { it as T }, { defaultValue }) + + fun <T> singleOption( + keys: List<String>, + help: String, + transform: (String) -> T, + initElement: (ParseContext) -> T + ) = OptionDelegate(initElement(parseContext)) { delegate, property -> + parseContext.registerSingleOption(keys, help, property) { + val toAdd = transform(it) + delegate.value = toAdd + } + } +} + + +//`(-perPackage fqName [-include-non-public] [...other flags])*` (edited) +//`(-sourceLink dir url [-urlSuffix value])*` +//`(-extLink url [packageListUrl])*`
\ No newline at end of file diff --git a/runners/cli/src/main/kotlin/cli/main.kt b/runners/cli/src/main/kotlin/cli/main.kt index f871f406..52815e75 100644 --- a/runners/cli/src/main/kotlin/cli/main.kt +++ b/runners/cli/src/main/kotlin/cli/main.kt @@ -1,142 +1,190 @@ package org.jetbrains.dokka - -import com.sampullara.cli.Args -import com.sampullara.cli.Argument import org.jetbrains.dokka.DokkaConfiguration.ExternalDocumentationLink - import java.io.File import java.net.MalformedURLException import java.net.URL import java.net.URLClassLoader -class DokkaArguments { - @set:Argument(value = "src", description = "Source file or directory (allows many paths separated by the system path separator)") - var src: String = "" - - @set:Argument(value = "srcLink", description = "Mapping between a source directory and a Web site for browsing the code") - var srcLink: String = "" - - @set:Argument(value = "include", description = "Markdown files to load (allows many paths separated by the system path separator)") - var include: String = "" - - @set:Argument(value = "samples", description = "Source root for samples") - var samples: String = "" - - @set:Argument(value = "output", description = "Output directory path") - var outputDir: String = "out/doc/" - - @set:Argument(value = "format", description = "Output format (text, html, markdown, jekyll, kotlin-website)") - var outputFormat: String = "html" - - @set:Argument(value = "module", description = "Name of the documentation module") - var moduleName: String = "" - - @set:Argument(value = "classpath", description = "Classpath for symbol resolution") - var classpath: String = "" - - @set:Argument(value = "nodeprecated", description = "Exclude deprecated members from documentation") - var nodeprecated: Boolean = false - - @set:Argument(value = "jdkVersion", description = "Version of JDK to use for linking to JDK JavaDoc") - var jdkVersion: Int = 6 - - @set:Argument(value = "impliedPlatforms", description = "List of implied platforms (comma-separated)") - var impliedPlatforms: String = "" - - @set:Argument(value = "packageOptions", description = "List of package options in format \"prefix,-deprecated,-privateApi,+warnUndocumented,+suppress;...\" ") - var packageOptions: String = "" - - @set:Argument(value = "links", description = "External documentation links in format url^packageListUrl^^url2...") - var links: String = "" - - @set:Argument(value = "noStdlibLink", description = "Disable documentation link to stdlib") - var noStdlibLink: Boolean = false - - @set:Argument(value = "noJdkLink", description = "Disable documentation link to jdk") - var noJdkLink: Boolean = false - - @set:Argument(value = "cacheRoot", description = "Path to cache folder, or 'default' to use ~/.cache/dokka, if not provided caching is disabled") - var cacheRoot: String? = null - - @set:Argument(value = "languageVersion", description = "Language Version to pass to Kotlin Analysis") - var languageVersion: String? = null - - @set:Argument(value = "apiVersion", description = "Kotlin Api Version to pass to Kotlin Analysis") - var apiVersion: String? = null - - @set:Argument(value = "collectInheritedExtensionsFromLibraries", description = "Search for applicable extensions in libraries") - var collectInheritedExtensionsFromLibraries: Boolean = false - +open class GlobalArguments(parser: DokkaArgumentsParser) : DokkaConfiguration { + override val outputDir: String by parser.stringOption( + listOf("-output"), + "Output directory path", + "") + + override val format: String by parser.stringOption( + listOf("-format"), + "Output format (text, html, markdown, jekyll, kotlin-website)", + "") + + override val generateIndexPages: Boolean by parser.singleFlag( + listOf("-generateIndexPages"), + "Generate index page" + ) + + override val cacheRoot: String? by parser.stringOption( + listOf("-cacheRoot"), + "Path to cache folder, or 'default' to use ~/.cache/dokka, if not provided caching is disabled", + null) + + override val impliedPlatforms: List<String> = emptyList() + + override val passesConfigurations: List<Arguments> by parser.repeatableFlag( + listOf("-pass"), + "Single dokka pass" + ) { + Arguments(parser) + } } +class Arguments(val parser: DokkaArgumentsParser) : DokkaConfiguration.PassConfiguration { + override val moduleName: String by parser.stringOption( + listOf("-module"), + "Name of the documentation module", + "") + + override val classpath: List<String> by parser.repeatableOption( + listOf("-classpath"), + "Classpath for symbol resolution" + ) + + override val sourceRoots: List<DokkaConfiguration.SourceRoot> by parser.repeatableOption( + listOf("-src"), + "Source file or directory (allows many paths separated by the system path separator)" + ) { SourceRootImpl(it) } + + override val samples: List<String> by parser.repeatableOption( + listOf("-sample"), + "Source root for samples" + ) + + override val includes: List<String> by parser.repeatableOption( + listOf("-include"), + "Markdown files to load (allows many paths separated by the system path separator)" + ) + + override val includeNonPublic: Boolean by parser.singleFlag( + listOf("-includeNonPublic"), + "Include non public") + + override val includeRootPackage: Boolean by parser.singleFlag( + listOf("-includeRootPackage"), + "Include root package") + + override val reportUndocumented: Boolean by parser.singleFlag( + listOf("-reportUndocumented"), + "Report undocumented members") + + override val skipEmptyPackages: Boolean by parser.singleFlag( + listOf("-skipEmptyPackages"), + "Do not create index pages for empty packages") + + override val skipDeprecated: Boolean by parser.singleFlag( + listOf("-skipDeprecated"), + "Do not output deprecated members") + + override val jdkVersion: Int by parser.singleOption( + listOf("-jdkVersion"), + "Version of JDK to use for linking to JDK JavaDoc", + { it.toInt() }, + { 6 } + ) + + override val languageVersion: String? by parser.stringOption( + listOf("-languageVersion"), + "Language Version to pass to Kotlin Analysis", + null) + + override val apiVersion: String? by parser.stringOption( + listOf("-apiVersion"), + "Kotlin Api Version to pass to Kotlin Analysis", + null + ) + + override val noStdlibLink: Boolean by parser.singleFlag( + listOf("-noStdlibLink"), + "Disable documentation link to stdlib") + + override val noJdkLink: Boolean by parser.singleFlag( + listOf("-noJdkLink"), + "Disable documentation link to JDK") + + override val suppressedFiles: List<String> by parser.repeatableOption( + listOf("-suppressedFile"), + "" + ) + + override val sinceKotlin: String? by parser.stringOption( + listOf("-sinceKotlin"), + "Kotlin Api version to use as base version, if none specified", + null + ) + + override val collectInheritedExtensionsFromLibraries: Boolean by parser.singleFlag( + listOf("-collectInheritedExtensionsFromLibraries"), + "Search for applicable extensions in libraries") + + override val analysisPlatform: Platform by parser.singleOption( + listOf("-analysisPlatform"), + "Platform for analysis", + { Platform.fromString(it) }, + { Platform.DEFAULT } + ) + + override val targets: List<String> by parser.repeatableOption( + listOf("-target"), + "Generation targets" + ) + + override val perPackageOptions: MutableList<DokkaConfiguration.PackageOptions> by parser.singleOption( + listOf("-packageOptions"), + "List of package passConfiguration in format \"prefix,-deprecated,-privateApi,+warnUndocumented,+suppress;...\" ", + { parsePerPackageOptions(it).toMutableList() }, + { mutableListOf() } + ) + + override val externalDocumentationLinks: MutableList<DokkaConfiguration.ExternalDocumentationLink> by parser.singleOption( + listOf("-links"), + "External documentation links in format url^packageListUrl^^url2...", + { MainKt.parseLinks(it).toMutableList() }, + { mutableListOf() } + ) + + override val sourceLinks: MutableList<DokkaConfiguration.SourceLinkDefinition> by parser.repeatableOption( + listOf("-srcLink"), + "Mapping between a source directory and a Web site for browsing the code" + ) { + if (it.isNotEmpty() && it.contains("=")) + SourceLinkDefinitionImpl.parseSourceLinkDefinition(it) + else { + throw IllegalArgumentException("Warning: Invalid -srcLink syntax. Expected: <path>=<url>[#lineSuffix]. No source links will be generated.") + } + } +} object MainKt { - fun parseLinks(links: String): List<ExternalDocumentationLink> { val (parsedLinks, parsedOfflineLinks) = links.split("^^") - .map { it.split("^").map { it.trim() }.filter { it.isNotBlank() } } - .filter { it.isNotEmpty() } - .partition { it.size == 1 } + .map { it.split("^").map { it.trim() }.filter { it.isNotBlank() } } + .filter { it.isNotEmpty() } + .partition { it.size == 1 } return parsedLinks.map { (root) -> ExternalDocumentationLink.Builder(root).build() } + parsedOfflineLinks.map { (root, packageList) -> val rootUrl = URL(root) val packageListUrl = - try { - URL(packageList) - } catch (ex: MalformedURLException) { - File(packageList).toURI().toURL() - } + try { + URL(packageList) + } catch (ex: MalformedURLException) { + File(packageList).toURI().toURL() + } ExternalDocumentationLink.Builder(rootUrl, packageListUrl).build() } } @JvmStatic - fun entry(args: Array<String>) { - val arguments = DokkaArguments() - val freeArgs: List<String> = Args.parse(arguments, args) ?: listOf() - val sources = if (arguments.src.isNotEmpty()) arguments.src.split(File.pathSeparatorChar).toList() + freeArgs else freeArgs - val samples = if (arguments.samples.isNotEmpty()) arguments.samples.split(File.pathSeparatorChar).toList() else listOf() - val includes = if (arguments.include.isNotEmpty()) arguments.include.split(File.pathSeparatorChar).toList() else listOf() - - val sourceLinks = if (arguments.srcLink.isNotEmpty() && arguments.srcLink.contains("=")) - listOf(SourceLinkDefinitionImpl.parseSourceLinkDefinition(arguments.srcLink)) - else { - if (arguments.srcLink.isNotEmpty()) { - println("Warning: Invalid -srcLink syntax. Expected: <path>=<url>[#lineSuffix]. No source links will be generated.") - } - listOf() - } - - val classPath = arguments.classpath.split(File.pathSeparatorChar).toList() - - val documentationOptions = DocumentationOptions( - arguments.outputDir.let { if (it.endsWith('/')) it else it + '/' }, - arguments.outputFormat, - skipDeprecated = arguments.nodeprecated, - sourceLinks = sourceLinks, - impliedPlatforms = arguments.impliedPlatforms.split(','), - perPackageOptions = parsePerPackageOptions(arguments.packageOptions), - jdkVersion = arguments.jdkVersion, - externalDocumentationLinks = parseLinks(arguments.links), - noStdlibLink = arguments.noStdlibLink, - cacheRoot = arguments.cacheRoot, - languageVersion = arguments.languageVersion, - apiVersion = arguments.apiVersion, - collectInheritedExtensionsFromLibraries = arguments.collectInheritedExtensionsFromLibraries, - noJdkLink = arguments.noJdkLink - ) - - val generator = DokkaGenerator( - DokkaConsoleLogger, - classPath, - sources.map(SourceRootImpl.Companion::parseSourceRoot), - samples, - includes, - arguments.moduleName, - documentationOptions) - + fun entry(configuration: DokkaConfiguration) { + val generator = DokkaGenerator(configuration, DokkaConsoleLogger) generator.generate() DokkaConsoleLogger.report() } @@ -162,27 +210,65 @@ object MainKt { return URLClassLoader(urls, ClassLoader.getSystemClassLoader().parent) } - fun startWithToolsJar(args: Array<String>) { + fun startWithToolsJar(configuration: DokkaConfiguration) { try { javaClass.classLoader.loadClass("com.sun.tools.doclets.formats.html.HtmlDoclet") - entry(args) + entry(configuration) } catch (e: ClassNotFoundException) { val classLoader = createClassLoaderWithTools() classLoader.loadClass("org.jetbrains.dokka.MainKt") - .methods.find { it.name == "entry" }!! - .invoke(null, args) + .methods.find { it.name == "entry" }!! + .invoke(null, configuration) + } + } + + fun createConfiguration(args: Array<String>): GlobalArguments { + val parseContext = ParseContext() + val parser = DokkaArgumentsParser(args, parseContext) + val configuration = GlobalArguments(parser) + + parseContext.cli.singleAction( + listOf("-globalPackageOptions"), + "List of package passConfiguration in format \"prefix,-deprecated,-privateApi,+warnUndocumented,+suppress;...\" " + ) { link -> + configuration.passesConfigurations.all { it.perPackageOptions.addAll(parsePerPackageOptions(link)) } } + + parseContext.cli.singleAction( + listOf("-globalLinks"), + "External documentation links in format url^packageListUrl^^url2..." + ) { link -> + configuration.passesConfigurations.all { it.externalDocumentationLinks.addAll(parseLinks(link)) } + } + + parseContext.cli.repeatingAction( + listOf("-globalSrcLink"), + "Mapping between a source directory and a Web site for browsing the code" + ) { + val newSourceLinks = if (it.isNotEmpty() && it.contains("=")) + listOf(SourceLinkDefinitionImpl.parseSourceLinkDefinition(it)) + else { + if (it.isNotEmpty()) { + println("Warning: Invalid -srcLink syntax. Expected: <path>=<url>[#lineSuffix]. No source links will be generated.") + } + listOf() + } + + configuration.passesConfigurations.all { it.sourceLinks.addAll(newSourceLinks) } + } + + parser.parseInto(configuration) + return configuration } @JvmStatic fun main(args: Array<String>) { - val arguments = DokkaArguments() - Args.parse(arguments, args) + val configuration = createConfiguration(args) - if (arguments.outputFormat == "javadoc") - startWithToolsJar(args) + if (configuration.format.toLowerCase() == "javadoc") + startWithToolsJar(configuration) else - entry(args) + entry(configuration) } } |