-
Notifications
You must be signed in to change notification settings - Fork 351
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2859 from broadinstitute/ks_generate_rest_api_docs
Added REST API docs generator.
- Loading branch information
Showing
7 changed files
with
257 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import java.time.OffsetDateTime | ||
import java.time.format.DateTimeFormatter | ||
|
||
import io.github.swagger2markup.Swagger2MarkupConverter | ||
import io.github.swagger2markup.builder.Swagger2MarkupConfigBuilder | ||
import io.github.swagger2markup.markup.builder.MarkupLanguage | ||
import org.apache.commons.lang3.ClassUtils | ||
import sbt._ | ||
|
||
/** | ||
* Provides a task to generate the REST API markdown from the Swagger YAML. | ||
* | ||
* Uses imports provided by swagger2markup.sbt. | ||
* | ||
* Based partially on https://github.com/21re/sbt-swagger-plugin | ||
*/ | ||
object GenerateRestApiDocs { | ||
private lazy val generateRestApiDocs = taskKey[Unit]("Generates the docs/api/RESTAPI.md") | ||
|
||
/** Returns a timestamped preamble for the modified markdown output. */ | ||
private def preamble(): String = { | ||
val now = OffsetDateTime.now() | ||
s"""|<!-- | ||
|This file was generated by `sbt generateRestApiDocs` on ${now.format(DateTimeFormatter.RFC_1123_DATE_TIME)} | ||
| | ||
|!!! DO NOT CHANGE THIS FILE DIRECTLY !!! | ||
| | ||
|If you wish to change something in this file, either change cromwell.yaml or GenerateRestApiDocs.scala then | ||
|regenerate. | ||
|--> | ||
|""".stripMargin | ||
} | ||
|
||
// Path to the input swagger yaml | ||
private val SwaggerYamlFile = new File("engine/src/main/resources/swagger/cromwell.yaml") | ||
|
||
// Path to the generated markdown | ||
private val RestApiMarkdownFile = new File("docs/api/RESTAPI.md") | ||
|
||
// A regex to locate the collection of swagger paths. | ||
private val PathsRegex = "(?s)(.*## Paths)(.*)(## Definitions.*)".r | ||
// A regex to locate an individual swagger path within the above collection. Each is turned into "## <original text>" | ||
private val PathRegex = "(?s)### (.*)".r | ||
|
||
private val GenericReplacements = List( | ||
// Change a few section headers into bolded text. | ||
"## Overview" -> "**Overview** ", | ||
"### Version information" -> "**Version information** ", | ||
"### License information" -> "**License information** ", | ||
"### Produces" -> "**Produces** ", | ||
// Since individual paths are moved from "###" to "##" we don't need "Paths" anymore. | ||
"<a name=\"paths\"></a>" -> "", | ||
"## Paths" -> "" | ||
) | ||
|
||
/** | ||
* Move contents of the "## Paths" section up a level. | ||
* | ||
* Each "### example path" will be come "## example path". | ||
* | ||
* Should be run before `replaceGenerics` as it looks for the string "## Paths". | ||
* | ||
* @param content The original contents of the RESTAPI.md. | ||
* @return The contents with updated paths. | ||
*/ | ||
private def replacePaths(content: String): String = { | ||
content match { | ||
case PathsRegex(start, paths, end) => | ||
val replacedPaths = paths.linesWithSeparators map { | ||
case PathRegex(description) => s"## $description" | ||
case other => other | ||
} | ||
replacedPaths.mkString(start, "", end) | ||
case _ => throw new IllegalArgumentException( | ||
"Content did not match expected regex. " + | ||
"Did the swagger2markdown format change significantly? " + | ||
"If so, a new regex may be required.") | ||
} | ||
} | ||
|
||
/** | ||
* Replaces generic strings in the generated RESTAPI.md. | ||
* | ||
* @param content The contents of the RESTAPI.md. | ||
* @return The contents with generic replacements. | ||
*/ | ||
private def replaceGenerics(content: String): String = { | ||
GenericReplacements.foldRight(content)(replaceGeneric) | ||
} | ||
|
||
/** | ||
* Replaces a single generic string in the generated RESTAPI.md. | ||
* | ||
* @param tokens The original and replacement strings. | ||
* @param content The contents of the RESTAPI.md. | ||
* @return The contents with generic replacements. | ||
*/ | ||
private def replaceGeneric(tokens: (String, String), content: String): String = { | ||
val (original, replacement) = tokens | ||
content.replace(original, replacement) | ||
} | ||
|
||
/** | ||
* Apache commons tries to dynamically load classes from the current thread classloader. Unfortunately the | ||
* classloader in the current thread from SBT does NOT have all of the libraries loaded. | ||
* | ||
* So-- hack in ClassUtils' classloader instead, that has access to all the required libraries including | ||
* org.apache.commons.configuration2.PropertiesConfiguration | ||
* | ||
* Otherwise, a ClassNotFoundException will occur. | ||
*/ | ||
private def withPatchedClassLoader[A](block: => A): A = { | ||
val classUtilsClassLoader = classOf[ClassUtils].getClassLoader | ||
val currentThread = Thread.currentThread | ||
val originalThreadClassLoader = currentThread.getContextClassLoader | ||
try { | ||
currentThread.setContextClassLoader(classUtilsClassLoader) | ||
block | ||
} finally { | ||
currentThread.setContextClassLoader(originalThreadClassLoader) | ||
} | ||
} | ||
|
||
/** | ||
* Generates the markdown from the swagger YAML, with some Cromwell customizations. | ||
*/ | ||
private def writeModifiedMarkdown(): Unit = { | ||
withPatchedClassLoader { | ||
val config = new Swagger2MarkupConfigBuilder() | ||
.withMarkupLanguage(MarkupLanguage.MARKDOWN) | ||
.build() | ||
val converter = Swagger2MarkupConverter | ||
.from(SwaggerYamlFile.toPath) | ||
.withConfig(config) | ||
.build() | ||
val contents = converter.toString | ||
val replacedContents = preamble() + replaceGenerics(replacePaths(contents)) | ||
IO.write(RestApiMarkdownFile, replacedContents) | ||
} | ||
} | ||
|
||
// Returns a settings including the `generateRestApiDocs` task. | ||
val generateRestApiDocsSettings = List[Setting[_]]( | ||
generateRestApiDocs := writeModifiedMarkdown() | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.