Merge pull request #19 from orangy/ant-task

Ant task for Dokka
diff --git a/.idea/ant.xml b/.idea/ant.xml
new file mode 100644
index 0000000..a2a4769
--- /dev/null
+++ b/.idea/ant.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="AntConfiguration">
+    <buildFile url="file://$PROJECT_DIR$/build.xml" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/artifacts/dokka_jar.xml b/.idea/artifacts/dokka_jar.xml
index 9aba0ca..fd03fd0 100644
--- a/.idea/artifacts/dokka_jar.xml
+++ b/.idea/artifacts/dokka_jar.xml
@@ -5,8 +5,8 @@
       <element id="module-output" name="dokka" />
       <element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-runtime.jar" path-in-jar="/" />
       <element id="extracted-dir" path="$KOTLIN_BUNDLED$/lib/kotlin-compiler.jar" path-in-jar="/" />
-      <element id="extracted-dir" path="$PROJECT_DIR$/lib/junit-4.11.jar" path-in-jar="/" />
-      <element id="extracted-dir" path="$PROJECT_DIR$/lib/hamcrest-core-1.3.jar" path-in-jar="/" />
+      <element id="module-output" name="ant" />
+      <element id="extracted-dir" path="$PROJECT_DIR$/lib/markdown.jar" path-in-jar="/" />
     </root>
   </artifact>
 </component>
\ No newline at end of file
diff --git a/.idea/libraries/ant_1_9_4.xml b/.idea/libraries/ant_1_9_4.xml
new file mode 100644
index 0000000..f68d9a6
--- /dev/null
+++ b/.idea/libraries/ant_1_9_4.xml
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="ant-1.9.4">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/lib/ant-1.9.4.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 142d525..bfcf875 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,8 +2,8 @@
 <project version="4">
   <component name="ProjectModuleManager">
     <modules>
+      <module fileurl="file://$PROJECT_DIR$/ant/ant.iml" filepath="$PROJECT_DIR$/ant/ant.iml" />
       <module fileurl="file://$PROJECT_DIR$/dokka.iml" filepath="$PROJECT_DIR$/dokka.iml" />
     </modules>
   </component>
-</project>
-
+</project>
\ No newline at end of file
diff --git a/ant/ant.iml b/ant/ant.iml
new file mode 100644
index 0000000..0761035
--- /dev/null
+++ b/ant/ant.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="kotlin" level="project" />
+    <orderEntry type="library" name="ant-1.9.4" level="project" />
+    <orderEntry type="module" module-name="dokka" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/ant/src/dokka-antlib.xml b/ant/src/dokka-antlib.xml
new file mode 100644
index 0000000..9c3373d
--- /dev/null
+++ b/ant/src/dokka-antlib.xml
@@ -0,0 +1,3 @@
+<antlib>
+    <taskdef name="dokka" classname="org.jetbrains.dokka.ant.DokkaAntTask"/>
+</antlib>
diff --git a/ant/src/dokka.kt b/ant/src/dokka.kt
new file mode 100644
index 0000000..051b6fe
--- /dev/null
+++ b/ant/src/dokka.kt
@@ -0,0 +1,105 @@
+package org.jetbrains.dokka.ant
+
+import org.apache.tools.ant.Task
+import org.apache.tools.ant.types.Path
+import org.apache.tools.ant.types.Reference
+import org.apache.tools.ant.BuildException
+import org.apache.tools.ant.Project
+import org.jetbrains.dokka.DokkaLogger
+import org.jetbrains.dokka.DokkaGenerator
+import org.jetbrains.dokka.SourceLinkDefinition
+import java.io.File
+
+class AntLogger(val task: Task): DokkaLogger {
+    override fun info(message: String) = task.log(message, Project.MSG_INFO)
+    override fun warn(message: String) = task.log(message, Project.MSG_WARN)
+    override fun error(message: String) = task.log(message, Project.MSG_ERR)
+}
+
+class AntSourceLinkDefinition(var path: String? = null, var url: String? = null, var lineSuffix: String? = null)
+
+class DokkaAntTask(): Task() {
+    public var moduleName: String? = null
+    public var outputDir: String? = null
+    public var outputFormat: String = "html"
+
+    public val compileClasspath: Path = Path(getProject())
+    public val sourcePath: Path = Path(getProject())
+    public val samplesPath: Path = Path(getProject())
+    public val includesPath: Path = Path(getProject())
+
+    public val antSourceLinks: MutableList<AntSourceLinkDefinition> = arrayListOf()
+
+    public fun setClasspath(classpath: Path) {
+        compileClasspath.append(classpath)
+    }
+
+    public fun setClasspathRef(ref: Reference) {
+        compileClasspath.createPath().setRefid(ref)
+    }
+
+    public fun setSrc(src: Path) {
+        sourcePath.append(src)
+    }
+
+    public fun setSrcRef(ref: Reference) {
+        sourcePath.createPath().setRefid(ref)
+    }
+
+    public fun setSamples(samples: Path) {
+        samplesPath.append(samples)
+    }
+
+    public fun setSamplesRef(ref: Reference) {
+        samplesPath.createPath().setRefid(ref)
+    }
+
+    public fun setInclude(include: Path) {
+        includesPath.append(include)
+    }
+
+    public fun createSourceLink(): AntSourceLinkDefinition {
+        val def = AntSourceLinkDefinition()
+        antSourceLinks.add(def)
+        return def
+    }
+
+    override fun execute() {
+        if (sourcePath.list().size() == 0) {
+            throw BuildException("At least one source path needs to be specified")
+        }
+        if (moduleName == null) {
+            throw BuildException("Module name needs to be specified")
+        }
+        if (outputDir == null) {
+            throw BuildException("Output directory needs to be specified")
+        }
+        val sourceLinks = antSourceLinks.map {
+            val path = it.path
+            if (path == null) {
+                throw BuildException("Path attribute of a <sourceLink> element is required")
+            }
+            val url = it.url
+            if (url == null) {
+                throw BuildException("Path attribute of a <sourceLink> element is required")
+            }
+            SourceLinkDefinition(File(path).getCanonicalFile().getAbsolutePath(), url, it.lineSuffix)
+        }
+
+        val url = javaClass<DokkaAntTask>().getResource("/org/jetbrains/dokka/ant/DokkaAntTask.class")
+        val jarRoot = url.getPath().substringBefore("!/").trimLeading("file:")
+
+        val generator = DokkaGenerator(
+                AntLogger(this),
+                listOf(jarRoot) + compileClasspath.list().toList(),
+                sourcePath.list().toList(),
+                samplesPath.list().toList(),
+                includesPath.list().toList(),
+                moduleName!!,
+                outputDir!!,
+                outputFormat,
+                sourceLinks
+        )
+        generator.generate()
+    }
+}
\ No newline at end of file
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..a11200f
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,11 @@
+<project name="Dokka" default="document">
+    <!-- Demonstrates the usage of the Dokka Ant task. Assumes Dokka has already been compiled -->
+
+    <typedef resource="dokka-antlib.xml" classpath="out/artifacts/dokka.jar"/>
+
+    <target name="document">
+        <dokka src="src" outputdir="doc" modulename="dokka">
+            <sourcelink path="." url="https://github.com/orangy/dokka/blob/master" linesuffix="#L"/>
+        </dokka>
+    </target>
+</project>
diff --git a/lib/ant-1.9.4.jar b/lib/ant-1.9.4.jar
new file mode 100644
index 0000000..24641e7
--- /dev/null
+++ b/lib/ant-1.9.4.jar
Binary files differ
diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt
index c053343..976626d 100644
--- a/src/Kotlin/DocumentationBuilder.kt
+++ b/src/Kotlin/DocumentationBuilder.kt
@@ -32,7 +32,7 @@
     return package1 != null && package2 != null && package1.fqName == package2.fqName
 }
 
-class DocumentationBuilder(val session: ResolveSession, val options: DocumentationOptions) {
+class DocumentationBuilder(val session: ResolveSession, val options: DocumentationOptions, val logger: DokkaLogger) {
     val visibleToDocumentation = setOf(Visibilities.INTERNAL, Visibilities.PROTECTED, Visibilities.PUBLIC)
     val descriptorToNode = hashMapOf<DeclarationDescriptor, DocumentationNode>()
     val nodeToDescriptor = hashMapOf<DocumentationNode, DeclarationDescriptor>()
@@ -246,7 +246,7 @@
             descriptors.put(name.asString(), parts.flatMap { it.getMemberScope().getAllDescriptors() })
         }
         for ((packageName, declarations) in descriptors) {
-            println("  package $packageName: ${declarations.count()} declarations")
+            logger.info("  package $packageName: ${declarations.count()} declarations")
             val packageNode = DocumentationNode(packageName, Content.Empty, Kind.Package)
             val externalClassNodes = hashMapOf<FqName, DocumentationNode>()
             declarations.forEach { descriptor ->
diff --git a/src/main.kt b/src/main.kt
index 9ebe0b1..521adb6 100644
--- a/src/main.kt
+++ b/src/main.kt
@@ -68,95 +68,144 @@
         listOf()
     }
 
-    val environment = AnalysisEnvironment(MessageCollectorPlainTextToStream.PLAIN_TEXT_TO_SYSTEM_ERR) {
-        addClasspath(PathUtil.getJdkClassesRoots())
-        //   addClasspath(PathUtil.getKotlinPathsForCompiler().getRuntimePath())
-        for (element in arguments.classpath.split(File.pathSeparatorChar)) {
-            addClasspath(File(element))
-        }
+    val classPath = arguments.classpath.split(File.pathSeparatorChar).toList()
+    val generator = DokkaGenerator(
+            DokkaConsoleLogger,
+            classPath,
+            sources,
+            samples,
+            includes,
+            arguments.moduleName,
+            arguments.outputDir,
+            arguments.outputFormat,
+            sourceLinks)
 
-        addSources(sources)
-        addSources(samples)
-    }
-
-    println("Module: ${arguments.moduleName}")
-    println("Output: ${arguments.outputDir}")
-    println("Sources: ${environment.sources.join()}")
-    println("Classpath: ${environment.classpath.joinToString()}")
-
-    println()
-
-    println("Analysing sources and libraries... ")
-    val startAnalyse = System.currentTimeMillis()
-
-
-    val documentation = environment.withContext { environment, session ->
-        val fragmentFiles = environment.getSourceFiles().filter {
-            val sourceFile = File(it.getVirtualFile()!!.getPath())
-            samples.none { sample ->
-                val canonicalSample = File(sample).canonicalPath
-                val canonicalSource = sourceFile.canonicalPath
-                canonicalSource.startsWith(canonicalSample)
-            }
-        }
-        val fragments = fragmentFiles.map { session.getPackageFragment(it.getPackageFqName()) }.filterNotNull().distinct()
-        val options = DocumentationOptions(false, sourceLinks)
-        val documentationBuilder = DocumentationBuilder(session, options)
-
-        with(documentationBuilder) {
-
-            val moduleContent = Content()
-            for (include in includes) {
-                val file = File(include)
-                if (file.exists()) {
-                    val text = file.readText()
-                    val tree = parseMarkdown(text)
-                    val content = buildContent(tree)
-                    moduleContent.children.addAll(content.children)
-                } else {
-                    println("WARN: Include file $file was not found.")
-                }
-            }
-
-            val documentationModule = DocumentationModule(arguments.moduleName, moduleContent)
-            documentationModule.appendFragments(fragments)
-            documentationBuilder.resolveReferences(documentationModule)
-            documentationModule
-        }
-    }
-
-    val timeAnalyse = System.currentTimeMillis() - startAnalyse
-    println("done in ${timeAnalyse / 1000} secs")
-
-    val startBuild = System.currentTimeMillis()
-    val signatureGenerator = KotlinLanguageService()
-    val locationService = FoldersLocationService(arguments.outputDir)
-    val templateService = HtmlTemplateService.default("/dokka/styles/style.css")
-
-    val (formatter, outlineFormatter) = when (arguments.outputFormat) {
-        "html" -> {
-            val htmlFormatService = HtmlFormatService(locationService, signatureGenerator, templateService)
-            htmlFormatService to htmlFormatService
-        }
-        "markdown" -> MarkdownFormatService(locationService, signatureGenerator) to null
-        "jekyll" -> JekyllFormatService(locationService, signatureGenerator) to null
-        "kotlin-website" -> KotlinWebsiteFormatService(locationService, signatureGenerator) to
-                    YamlOutlineService(locationService, signatureGenerator)
-        else -> {
-            print("Unrecognized output format ${arguments.outputFormat}")
-            null to null
-        }
-    }
-    if (formatter == null) return
-
-    val generator = FileGenerator(signatureGenerator, locationService, formatter, outlineFormatter)
-    print("Generating pages... ")
-    generator.buildPage(documentation)
-    generator.buildOutline(documentation)
-    val timeBuild = System.currentTimeMillis() - startBuild
-    println("done in ${timeBuild / 1000} secs")
-    println()
-    println("Done.")
-    Disposer.dispose(environment)
+    generator.generate()
 }
 
+trait DokkaLogger {
+    fun info(message: String)
+    fun warn(message: String)
+    fun error(message: String)
+}
+
+object DokkaConsoleLogger: DokkaLogger {
+    override fun info(message: String) = println(message)
+    override fun warn(message: String) = println("WARN: $message")
+    override fun error(message: String) = println("ERROR: $message")
+}
+
+class DokkaMessageCollector(val logger: DokkaLogger): MessageCollector {
+    override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
+        logger.error(MessageRenderer.PLAIN_FULL_PATHS.render(severity, message, location))
+    }
+}
+
+class DokkaGenerator(val logger: DokkaLogger,
+                     val classpath: List<String>,
+                     val sources: List<String>,
+                     val samples: List<String>,
+                     val includes: List<String>,
+                     val moduleName: String,
+                     val outputDir: String,
+                     val outputFormat: String,
+                     val sourceLinks: List<SourceLinkDefinition>) {
+    fun generate() {
+        val environment = createAnalysisEnvironment()
+
+        logger.info("Module: ${moduleName}")
+        logger.info("Output: ${outputDir}")
+        logger.info("Sources: ${environment.sources.join()}")
+        logger.info("Classpath: ${environment.classpath.joinToString()}")
+
+        logger.info("Analysing sources and libraries... ")
+        val startAnalyse = System.currentTimeMillis()
+
+        val documentation = buildDocumentationModule(environment)
+
+        val timeAnalyse = System.currentTimeMillis() - startAnalyse
+        logger.info("done in ${timeAnalyse / 1000} secs")
+
+        val startBuild = System.currentTimeMillis()
+        val signatureGenerator = KotlinLanguageService()
+        val locationService = FoldersLocationService(outputDir)
+        val templateService = HtmlTemplateService.default("/dokka/styles/style.css")
+
+        val (formatter, outlineFormatter) = when (outputFormat) {
+            "html" -> {
+                val htmlFormatService = HtmlFormatService(locationService, signatureGenerator, templateService)
+                htmlFormatService to htmlFormatService
+            }
+            "markdown" -> MarkdownFormatService(locationService, signatureGenerator) to null
+            "jekyll" -> JekyllFormatService(locationService, signatureGenerator) to null
+            "kotlin-website" -> KotlinWebsiteFormatService(locationService, signatureGenerator) to
+                    YamlOutlineService(locationService, signatureGenerator)
+            else -> {
+                logger.error("Unrecognized output format ${outputFormat}")
+                null to null
+            }
+        }
+        if (formatter == null) return
+
+        val generator = FileGenerator(signatureGenerator, locationService, formatter, outlineFormatter)
+        logger.info("Generating pages... ")
+        generator.buildPage(documentation)
+        generator.buildOutline(documentation)
+        val timeBuild = System.currentTimeMillis() - startBuild
+        logger.info("done in ${timeBuild / 1000} secs")
+        Disposer.dispose(environment)
+    }
+
+    fun createAnalysisEnvironment(): AnalysisEnvironment {
+        val environment = AnalysisEnvironment(DokkaMessageCollector(logger)) {
+            addClasspath(PathUtil.getJdkClassesRoots())
+            //   addClasspath(PathUtil.getKotlinPathsForCompiler().getRuntimePath())
+            for (element in this@DokkaGenerator.classpath) {
+                addClasspath(File(element))
+            }
+
+            addSources(this@DokkaGenerator.sources)
+            addSources(this@DokkaGenerator.samples)
+        }
+        return environment
+    }
+
+    fun buildDocumentationModule(environment: AnalysisEnvironment): DocumentationModule {
+        val documentation = environment.withContext { environment, session ->
+            val fragmentFiles = environment.getSourceFiles().filter {
+                val sourceFile = File(it.getVirtualFile()!!.getPath())
+                samples.none { sample ->
+                    val canonicalSample = File(sample).canonicalPath
+                    val canonicalSource = sourceFile.canonicalPath
+                    canonicalSource.startsWith(canonicalSample)
+                }
+            }
+            val fragments = fragmentFiles.map { session.getPackageFragment(it.getPackageFqName()) }.filterNotNull().distinct()
+            val options = DocumentationOptions(false, sourceLinks)
+            val documentationBuilder = DocumentationBuilder(session, options, logger)
+
+            with(documentationBuilder) {
+
+                val moduleContent = Content()
+                for (include in includes) {
+                    val file = File(include)
+                    if (file.exists()) {
+                        val text = file.readText()
+                        val tree = parseMarkdown(text)
+                        val content = buildContent(tree)
+                        moduleContent.children.addAll(content.children)
+                    } else {
+                        logger.warn("Include file $file was not found.")
+                    }
+                }
+
+                val documentationModule = DocumentationModule(moduleName, moduleContent)
+                documentationModule.appendFragments(fragments)
+                documentationBuilder.resolveReferences(documentationModule)
+                documentationModule
+            }
+        }
+
+        return documentation
+    }
+}
diff --git a/test/src/TestAPI.kt b/test/src/TestAPI.kt
index 2cdaad5..b0e23e0 100644
--- a/test/src/TestAPI.kt
+++ b/test/src/TestAPI.kt
@@ -38,7 +38,7 @@
         val fragments = environment.getSourceFiles().map { session.getPackageFragment(it.getPackageFqName()) }.filterNotNull().distinct()
 
         val documentationModule = DocumentationModule("test")
-        val documentationBuilder = DocumentationBuilder(session, options)
+        val documentationBuilder = DocumentationBuilder(session, options, DokkaConsoleLogger)
         with(documentationBuilder) {
             documentationModule.appendFragments(fragments)
         }