Skip to content

Commit

Permalink
feat: add documentation code generation (#553)
Browse files Browse the repository at this point in the history
* feat: add documentation code generation

* update generated-docs in example

* apply formatting

* lint
  • Loading branch information
Cole Turner authored May 12, 2023
1 parent 51969ec commit c153b6f
Show file tree
Hide file tree
Showing 13 changed files with 480 additions and 5 deletions.
3 changes: 3 additions & 0 deletions graphql-dgs-codegen-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ plugins {
id 'java-library'
id 'application'
id 'com.netflix.nebula.integtest'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.10'
}


Expand All @@ -38,6 +39,8 @@ dependencies {
implementation 'com.squareup:kotlinpoet:1.12.0'
implementation 'com.github.ajalt.clikt:clikt:3.5.0'

implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0'

testImplementation 'com.google.testing.compile:compile-testing:0.+'
testImplementation "org.jetbrains.kotlin:kotlin-compiler:${Versions.KOTLIN_VERSION}"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ package com.netflix.graphql.dgs.codegen
import com.netflix.graphql.dgs.codegen.generators.java.*
import com.netflix.graphql.dgs.codegen.generators.kotlin.*
import com.netflix.graphql.dgs.codegen.generators.kotlin2.*
import com.netflix.graphql.dgs.codegen.generators.shared.DocFileSpec
import com.netflix.graphql.dgs.codegen.generators.shared.DocGenerator
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findEnumExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInputExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInterfaceExtensions
Expand Down Expand Up @@ -94,6 +96,7 @@ class CodeGen(private val config: CodeGenConfig) {
codeGenResult.kotlinDataFetchers.forEach { it.writeTo(config.examplesOutputDir) }
codeGenResult.kotlinConstants.forEach { it.writeTo(config.outputDir) }
codeGenResult.kotlinClientTypes.forEach { it.writeTo(config.outputDir) }
codeGenResult.docFiles.forEach { it.writeTo(config.generatedDocsFolder) }
}

return codeGenResult
Expand Down Expand Up @@ -180,6 +183,7 @@ class CodeGen(private val config: CodeGenConfig) {
// Data Fetchers
val dataFetchersResult = generateJavaDataFetchers(definitions)
val generatedAnnotation = generateJavaGeneratedAnnotation(config)
var docFiles = generateDocFiles(definitions)

return dataTypesResult
.merge(dataFetchersResult)
Expand All @@ -192,6 +196,7 @@ class CodeGen(private val config: CodeGenConfig) {
.merge(entitiesRepresentationsTypes)
.merge(constantsClass)
.merge(generatedAnnotation)
.merge(docFiles)
}

private fun generateJavaEnums(definitions: Collection<Definition<*>>): CodeGenResult {
Expand Down Expand Up @@ -456,6 +461,18 @@ class CodeGen(private val config: CodeGenConfig) {
}
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
}

private fun generateDocFiles(definitions: Collection<Definition<*>>): CodeGenResult {
if (!config.generateDocs) {
return CodeGenResult()
}

return definitions.asSequence()
.map {
DocGenerator(config, document).generate(it)
}
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
}
}

class CodeGenConfig(
Expand All @@ -469,6 +486,7 @@ class CodeGenConfig(
private val subPackageNameClient: String = "client",
private val subPackageNameDatafetchers: String = "datafetchers",
private val subPackageNameTypes: String = "types",
private val subPackageNameDocs: String = "docs",
var language: Language = Language.JAVA,
var generateBoxedTypes: Boolean = false,
var generateClientApi: Boolean = false,
Expand All @@ -490,6 +508,8 @@ class CodeGenConfig(
var snakeCaseConstantNames: Boolean = false,
var generateInterfaceSetters: Boolean = true,
var generateInterfaceMethodsForInterfaceFields: Boolean = false,
var generateDocs: Boolean = false,
var generatedDocsFolder: Path = Paths.get("generated-docs"),
var includeImports: Map<String, String> = emptyMap(),
var includeEnumImports: Map<String, Map<String, String>> = emptyMap(),
var includeClassImports: Map<String, Map<String, String>> = emptyMap(),
Expand All @@ -504,6 +524,7 @@ class CodeGenConfig(
val packageNameDatafetchers: String = "$packageName.$subPackageNameDatafetchers"

val packageNameTypes: String = "$packageName.$subPackageNameTypes"
val packageNameDocs: String = "$packageName.$subPackageNameDocs"

override fun toString(): String {
return """
Expand All @@ -512,6 +533,7 @@ class CodeGenConfig(
--sub-package-name-client=$subPackageNameClient
--sub-package-name-datafetchers=$subPackageNameDatafetchers
--sub-package-name-types=$subPackageNameTypes
--sub-package-name-docs=$subPackageNameDocs
${if (generateBoxedTypes) "--generate-boxed-types" else ""}
${if (writeToFiles) "--write-to-disk" else ""}
--language=$language
Expand Down Expand Up @@ -547,7 +569,8 @@ data class CodeGenResult(
val kotlinEnumTypes: List<FileSpec> = listOf(),
val kotlinDataFetchers: List<FileSpec> = listOf(),
val kotlinConstants: List<FileSpec> = listOf(),
val kotlinClientTypes: List<FileSpec> = listOf()
val kotlinClientTypes: List<FileSpec> = listOf(),
val docFiles: List<DocFileSpec> = listOf()
) {
fun merge(current: CodeGenResult): CodeGenResult {
val javaDataTypes = this.javaDataTypes.plus(current.javaDataTypes)
Expand All @@ -564,6 +587,7 @@ data class CodeGenResult(
val kotlinDataFetchers = this.kotlinDataFetchers.plus(current.kotlinDataFetchers)
val kotlinConstants = this.kotlinConstants.plus(current.kotlinConstants)
val kotlinClientTypes = this.kotlinClientTypes.plus(current.kotlinClientTypes)
val docFiles = this.docFiles.plus(current.docFiles)

return CodeGenResult(
javaDataTypes = javaDataTypes,
Expand All @@ -579,7 +603,8 @@ data class CodeGenResult(
kotlinEnumTypes = kotlinEnumTypes,
kotlinDataFetchers = kotlinDataFetchers,
kotlinConstants = kotlinConstants,
kotlinClientTypes = kotlinClientTypes
kotlinClientTypes = kotlinClientTypes,
docFiles = docFiles
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
private val typeMapping: Map<String, String> by option("--type-mapping").associate()
private val shortProjectionNames by option("--short-projection-names").flag()
private val generateInterfaceSetters by option("--generate-interface-setters").flag()
private val generateDocs by option("--generate-docs").flag()

override fun run() {
val inputSchemas = if (schemas.isEmpty()) {
Expand Down Expand Up @@ -92,7 +93,8 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
shortProjectionNames = shortProjectionNames,
generateDataTypes = generateDataTypes,
generateInterfaces = generateInterfaces,
generateInterfaceSetters = generateInterfaceSetters
generateInterfaceSetters = generateInterfaceSetters,
generateDocs = generateDocs
)
} else {
CodeGenConfig(
Expand All @@ -112,7 +114,8 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
shortProjectionNames = shortProjectionNames,
generateDataTypes = generateDataTypes,
generateInterfaces = generateInterfaces,
generateInterfaceSetters = generateInterfaceSetters
generateInterfaceSetters = generateInterfaceSetters,
generateDocs = generateDocs
)
}
).generate()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
*
* Copyright 2020 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.netflix.graphql.dgs.codegen.generators.shared

import com.squareup.kotlinpoet.*
import java.io.File
import java.io.IOException
import java.io.OutputStreamWriter
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import javax.annotation.processing.Filer
import javax.tools.StandardLocation

class DocFileSpec private constructor(
builder: DocFileSpec.Builder
) {
private val extension = "md"
private val packageName: String = builder.packageName
private val markdownText: String = builder.markdownText

@Throws(IOException::class)
public fun writeTo(out: Appendable) {
out.append(markdownText)
}

/** Writes this to `directory` as UTF-8 using the standard directory structure. */
@Throws(IOException::class)
public fun writeTo(directory: Path) {
require(Files.notExists(directory) || Files.isDirectory(directory)) {
"path $directory exists but is not a directory."
}

Files.createDirectories(directory)

val outputPath = directory.resolve("$packageName.$extension")
OutputStreamWriter(Files.newOutputStream(outputPath), StandardCharsets.UTF_8).use { writer -> writeTo(writer) }
}

/** Writes this to `directory` as UTF-8 using the standard directory structure. */
@Throws(IOException::class)
public fun writeTo(directory: File): Unit = writeTo(directory.toPath())

/** Writes this to `filer`. */
@Throws(IOException::class)
public fun writeTo(filer: Filer) {
val filerSourceFile = filer.createResource(
StandardLocation.SOURCE_OUTPUT,
packageName,
"$packageName.$extension"
)
try {
filerSourceFile.openWriter().use { writer -> writeTo(writer) }
} catch (e: Exception) {
try {
filerSourceFile.delete()
} catch (ignored: Exception) {
}
throw e
}
}

public class Builder internal constructor(
public val packageName: String,
public var markdownText: String
) {
public fun setMarkdownText(markdownText: String): DocFileSpec.Builder = apply {
this.markdownText = markdownText
}

public fun build(): DocFileSpec {
return DocFileSpec(this)
}
}
public companion object {
@JvmStatic public fun get(packageName: String, markdownText: String): DocFileSpec {
return builder(packageName, markdownText).build()
}

@JvmStatic public fun builder(packageName: String, markdownText: String): DocFileSpec.Builder =
DocFileSpec.Builder(packageName, markdownText)
}
}
Loading

0 comments on commit c153b6f

Please sign in to comment.