Skip to content

Commit c153b6f

Browse files
author
Cole Turner
authored
feat: add documentation code generation (#553)
* feat: add documentation code generation * update generated-docs in example * apply formatting * lint
1 parent 51969ec commit c153b6f

File tree

13 files changed

+480
-5
lines changed

13 files changed

+480
-5
lines changed

graphql-dgs-codegen-core/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ plugins {
2020
id 'java-library'
2121
id 'application'
2222
id 'com.netflix.nebula.integtest'
23+
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.10'
2324
}
2425

2526

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

42+
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0'
43+
4144
testImplementation 'com.google.testing.compile:compile-testing:0.+'
4245
testImplementation "org.jetbrains.kotlin:kotlin-compiler:${Versions.KOTLIN_VERSION}"
4346
}

graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt

+27-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ package com.netflix.graphql.dgs.codegen
2121
import com.netflix.graphql.dgs.codegen.generators.java.*
2222
import com.netflix.graphql.dgs.codegen.generators.kotlin.*
2323
import com.netflix.graphql.dgs.codegen.generators.kotlin2.*
24+
import com.netflix.graphql.dgs.codegen.generators.shared.DocFileSpec
25+
import com.netflix.graphql.dgs.codegen.generators.shared.DocGenerator
2426
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findEnumExtensions
2527
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInputExtensions
2628
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInterfaceExtensions
@@ -94,6 +96,7 @@ class CodeGen(private val config: CodeGenConfig) {
9496
codeGenResult.kotlinDataFetchers.forEach { it.writeTo(config.examplesOutputDir) }
9597
codeGenResult.kotlinConstants.forEach { it.writeTo(config.outputDir) }
9698
codeGenResult.kotlinClientTypes.forEach { it.writeTo(config.outputDir) }
99+
codeGenResult.docFiles.forEach { it.writeTo(config.generatedDocsFolder) }
97100
}
98101

99102
return codeGenResult
@@ -180,6 +183,7 @@ class CodeGen(private val config: CodeGenConfig) {
180183
// Data Fetchers
181184
val dataFetchersResult = generateJavaDataFetchers(definitions)
182185
val generatedAnnotation = generateJavaGeneratedAnnotation(config)
186+
var docFiles = generateDocFiles(definitions)
183187

184188
return dataTypesResult
185189
.merge(dataFetchersResult)
@@ -192,6 +196,7 @@ class CodeGen(private val config: CodeGenConfig) {
192196
.merge(entitiesRepresentationsTypes)
193197
.merge(constantsClass)
194198
.merge(generatedAnnotation)
199+
.merge(docFiles)
195200
}
196201

197202
private fun generateJavaEnums(definitions: Collection<Definition<*>>): CodeGenResult {
@@ -456,6 +461,18 @@ class CodeGen(private val config: CodeGenConfig) {
456461
}
457462
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
458463
}
464+
465+
private fun generateDocFiles(definitions: Collection<Definition<*>>): CodeGenResult {
466+
if (!config.generateDocs) {
467+
return CodeGenResult()
468+
}
469+
470+
return definitions.asSequence()
471+
.map {
472+
DocGenerator(config, document).generate(it)
473+
}
474+
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
475+
}
459476
}
460477

461478
class CodeGenConfig(
@@ -469,6 +486,7 @@ class CodeGenConfig(
469486
private val subPackageNameClient: String = "client",
470487
private val subPackageNameDatafetchers: String = "datafetchers",
471488
private val subPackageNameTypes: String = "types",
489+
private val subPackageNameDocs: String = "docs",
472490
var language: Language = Language.JAVA,
473491
var generateBoxedTypes: Boolean = false,
474492
var generateClientApi: Boolean = false,
@@ -490,6 +508,8 @@ class CodeGenConfig(
490508
var snakeCaseConstantNames: Boolean = false,
491509
var generateInterfaceSetters: Boolean = true,
492510
var generateInterfaceMethodsForInterfaceFields: Boolean = false,
511+
var generateDocs: Boolean = false,
512+
var generatedDocsFolder: Path = Paths.get("generated-docs"),
493513
var includeImports: Map<String, String> = emptyMap(),
494514
var includeEnumImports: Map<String, Map<String, String>> = emptyMap(),
495515
var includeClassImports: Map<String, Map<String, String>> = emptyMap(),
@@ -504,6 +524,7 @@ class CodeGenConfig(
504524
val packageNameDatafetchers: String = "$packageName.$subPackageNameDatafetchers"
505525

506526
val packageNameTypes: String = "$packageName.$subPackageNameTypes"
527+
val packageNameDocs: String = "$packageName.$subPackageNameDocs"
507528

508529
override fun toString(): String {
509530
return """
@@ -512,6 +533,7 @@ class CodeGenConfig(
512533
--sub-package-name-client=$subPackageNameClient
513534
--sub-package-name-datafetchers=$subPackageNameDatafetchers
514535
--sub-package-name-types=$subPackageNameTypes
536+
--sub-package-name-docs=$subPackageNameDocs
515537
${if (generateBoxedTypes) "--generate-boxed-types" else ""}
516538
${if (writeToFiles) "--write-to-disk" else ""}
517539
--language=$language
@@ -547,7 +569,8 @@ data class CodeGenResult(
547569
val kotlinEnumTypes: List<FileSpec> = listOf(),
548570
val kotlinDataFetchers: List<FileSpec> = listOf(),
549571
val kotlinConstants: List<FileSpec> = listOf(),
550-
val kotlinClientTypes: List<FileSpec> = listOf()
572+
val kotlinClientTypes: List<FileSpec> = listOf(),
573+
val docFiles: List<DocFileSpec> = listOf()
551574
) {
552575
fun merge(current: CodeGenResult): CodeGenResult {
553576
val javaDataTypes = this.javaDataTypes.plus(current.javaDataTypes)
@@ -564,6 +587,7 @@ data class CodeGenResult(
564587
val kotlinDataFetchers = this.kotlinDataFetchers.plus(current.kotlinDataFetchers)
565588
val kotlinConstants = this.kotlinConstants.plus(current.kotlinConstants)
566589
val kotlinClientTypes = this.kotlinClientTypes.plus(current.kotlinClientTypes)
590+
val docFiles = this.docFiles.plus(current.docFiles)
567591

568592
return CodeGenResult(
569593
javaDataTypes = javaDataTypes,
@@ -579,7 +603,8 @@ data class CodeGenResult(
579603
kotlinEnumTypes = kotlinEnumTypes,
580604
kotlinDataFetchers = kotlinDataFetchers,
581605
kotlinConstants = kotlinConstants,
582-
kotlinClientTypes = kotlinClientTypes
606+
kotlinClientTypes = kotlinClientTypes,
607+
docFiles = docFiles
583608
)
584609
}
585610

graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGenCli.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
5858
private val typeMapping: Map<String, String> by option("--type-mapping").associate()
5959
private val shortProjectionNames by option("--short-projection-names").flag()
6060
private val generateInterfaceSetters by option("--generate-interface-setters").flag()
61+
private val generateDocs by option("--generate-docs").flag()
6162

6263
override fun run() {
6364
val inputSchemas = if (schemas.isEmpty()) {
@@ -92,7 +93,8 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
9293
shortProjectionNames = shortProjectionNames,
9394
generateDataTypes = generateDataTypes,
9495
generateInterfaces = generateInterfaces,
95-
generateInterfaceSetters = generateInterfaceSetters
96+
generateInterfaceSetters = generateInterfaceSetters,
97+
generateDocs = generateDocs
9698
)
9799
} else {
98100
CodeGenConfig(
@@ -112,7 +114,8 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
112114
shortProjectionNames = shortProjectionNames,
113115
generateDataTypes = generateDataTypes,
114116
generateInterfaces = generateInterfaces,
115-
generateInterfaceSetters = generateInterfaceSetters
117+
generateInterfaceSetters = generateInterfaceSetters,
118+
generateDocs = generateDocs
116119
)
117120
}
118121
).generate()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
*
3+
* Copyright 2020 Netflix, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package com.netflix.graphql.dgs.codegen.generators.shared
20+
21+
import com.squareup.kotlinpoet.*
22+
import java.io.File
23+
import java.io.IOException
24+
import java.io.OutputStreamWriter
25+
import java.nio.charset.StandardCharsets
26+
import java.nio.file.Files
27+
import java.nio.file.Path
28+
import javax.annotation.processing.Filer
29+
import javax.tools.StandardLocation
30+
31+
class DocFileSpec private constructor(
32+
builder: DocFileSpec.Builder
33+
) {
34+
private val extension = "md"
35+
private val packageName: String = builder.packageName
36+
private val markdownText: String = builder.markdownText
37+
38+
@Throws(IOException::class)
39+
public fun writeTo(out: Appendable) {
40+
out.append(markdownText)
41+
}
42+
43+
/** Writes this to `directory` as UTF-8 using the standard directory structure. */
44+
@Throws(IOException::class)
45+
public fun writeTo(directory: Path) {
46+
require(Files.notExists(directory) || Files.isDirectory(directory)) {
47+
"path $directory exists but is not a directory."
48+
}
49+
50+
Files.createDirectories(directory)
51+
52+
val outputPath = directory.resolve("$packageName.$extension")
53+
OutputStreamWriter(Files.newOutputStream(outputPath), StandardCharsets.UTF_8).use { writer -> writeTo(writer) }
54+
}
55+
56+
/** Writes this to `directory` as UTF-8 using the standard directory structure. */
57+
@Throws(IOException::class)
58+
public fun writeTo(directory: File): Unit = writeTo(directory.toPath())
59+
60+
/** Writes this to `filer`. */
61+
@Throws(IOException::class)
62+
public fun writeTo(filer: Filer) {
63+
val filerSourceFile = filer.createResource(
64+
StandardLocation.SOURCE_OUTPUT,
65+
packageName,
66+
"$packageName.$extension"
67+
)
68+
try {
69+
filerSourceFile.openWriter().use { writer -> writeTo(writer) }
70+
} catch (e: Exception) {
71+
try {
72+
filerSourceFile.delete()
73+
} catch (ignored: Exception) {
74+
}
75+
throw e
76+
}
77+
}
78+
79+
public class Builder internal constructor(
80+
public val packageName: String,
81+
public var markdownText: String
82+
) {
83+
public fun setMarkdownText(markdownText: String): DocFileSpec.Builder = apply {
84+
this.markdownText = markdownText
85+
}
86+
87+
public fun build(): DocFileSpec {
88+
return DocFileSpec(this)
89+
}
90+
}
91+
public companion object {
92+
@JvmStatic public fun get(packageName: String, markdownText: String): DocFileSpec {
93+
return builder(packageName, markdownText).build()
94+
}
95+
96+
@JvmStatic public fun builder(packageName: String, markdownText: String): DocFileSpec.Builder =
97+
DocFileSpec.Builder(packageName, markdownText)
98+
}
99+
}

0 commit comments

Comments
 (0)