From b6b8c0db872fb4a418ae496e89c7e656e14be165 Mon Sep 17 00:00:00 2001 From: Jim Schubert Date: Thu, 31 May 2018 07:23:05 -0400 Subject: [PATCH] [gradle-plugin] Initial implementation (#162) * [gradle-plugin] Initial commit * Clarify comments on file constraints When a user sets the models, apis, or supportingFiles environment variables, any one of these being set disables generation for the other two. This could be confusing to users, so I've added some clarification text in the comments for these properties. In addition, I've cleaned up the extension on Property.ifNotEmpty, to avoid using Suppress annotations where it's not necessary. The change creates a local variable of type T?, allowing Kotlin to track the variable's nullable state at compile time. * Move gradle plugin under modules * Move kt files under kotlin source set. Add sample. * [gradle] map-like options as maps * Add tests for gradle validate task * Apply gradle plugin to mvn install phase * [gradle] Testing remaining gradle tasks * Add gradle plugin to the integration doc * Update gradle plugin README with task options * Gradle readme formatting --- docs/integration.md | 13 +- .../.gitignore | 152 +++++ .../README.adoc | 452 +++++++++++++++ .../build.gradle | 89 +++ .../gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54413 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../openapi-generator-gradle-plugin/gradlew | 172 ++++++ .../gradlew.bat | 84 +++ .../openapi-generator-gradle-plugin/pom.xml | 59 ++ .../samples/local-spec/.gitignore | 3 + .../samples/local-spec/README.md | 14 + .../samples/local-spec/build.gradle | 49 ++ .../local-spec/petstore-v3.0-invalid.yaml | 103 ++++ .../samples/local-spec/petstore-v3.0.yaml | 109 ++++ .../samples/local-spec/settings.gradle | 0 .../settings.gradle | 2 + .../gradle/plugin/OpenApiGeneratorPlugin.kt | 130 +++++ .../OpenApiGeneratorGenerateExtension.kt | 275 +++++++++ .../OpenApiGeneratorMetaExtension.kt | 48 ++ .../OpenApiGeneratorValidateExtension.kt | 32 ++ .../gradle/plugin/tasks/GenerateTask.kt | 541 ++++++++++++++++++ .../gradle/plugin/tasks/GeneratorsTask.kt | 71 +++ .../generator/gradle/plugin/tasks/MetaTask.kt | 132 +++++ .../gradle/plugin/tasks/ValidateTask.kt | 82 +++ .../src/test/kotlin/GenerateTaskDslTest.kt | 71 +++ .../src/test/kotlin/GeneratorsTaskDslTest.kt | 38 ++ .../src/test/kotlin/MetaTaskDslTest.kt | 58 ++ .../src/test/kotlin/TestBase.kt | 34 ++ .../src/test/kotlin/ValidateTaskDslTest.kt | 99 ++++ .../specs/petstore-v3.0-invalid.yaml | 103 ++++ .../test/resources/specs/petstore-v3.0.yaml | 109 ++++ pom.xml | 1 + 33 files changed, 3130 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator-gradle-plugin/.gitignore create mode 100644 modules/openapi-generator-gradle-plugin/README.adoc create mode 100644 modules/openapi-generator-gradle-plugin/build.gradle create mode 100644 modules/openapi-generator-gradle-plugin/gradle.properties create mode 100644 modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.jar create mode 100644 modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.properties create mode 100755 modules/openapi-generator-gradle-plugin/gradlew create mode 100644 modules/openapi-generator-gradle-plugin/gradlew.bat create mode 100644 modules/openapi-generator-gradle-plugin/pom.xml create mode 100644 modules/openapi-generator-gradle-plugin/samples/local-spec/.gitignore create mode 100644 modules/openapi-generator-gradle-plugin/samples/local-spec/README.md create mode 100644 modules/openapi-generator-gradle-plugin/samples/local-spec/build.gradle create mode 100644 modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0-invalid.yaml create mode 100644 modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0.yaml create mode 100644 modules/openapi-generator-gradle-plugin/samples/local-spec/settings.gradle create mode 100644 modules/openapi-generator-gradle-plugin/settings.gradle create mode 100644 modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorMetaExtension.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/test/kotlin/GenerateTaskDslTest.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/test/kotlin/GeneratorsTaskDslTest.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/test/kotlin/MetaTaskDslTest.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/test/kotlin/TestBase.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt create mode 100644 modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-invalid.yaml create mode 100644 modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0.yaml diff --git a/docs/integration.md b/docs/integration.md index b0f4ed335ed..99c76b25f92 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -1,8 +1,19 @@ ## Workflow Integration (Maven, Github, CI/CD) +### Gradle Integration + +See the [openapi-generator-gradle-plugin README](../modules/openapi-generator-gradle-plugin/README.adoc) for details related to configuring and using the Gradle Plugin. + +Supported tasks include: + +* Listing generators +* Validation of Open API 2.0 and 3.0 Specs +* Generating "Meta" generators +* Generating all generators supported by OpenAPI Generator + ### Maven Integration -You can use the [openapi-generator-maven-plugin](../modules/openapi-generator-maven-plugin/README.md) for integrating with your workflow, and generating any codegen target. +See the [openapi-generator-maven-plugin README](../modules/openapi-generator-maven-plugin/README.md) for details related to configuring and using the Maven Plugin. ### GitHub Integration diff --git a/modules/openapi-generator-gradle-plugin/.gitignore b/modules/openapi-generator-gradle-plugin/.gitignore new file mode 100644 index 00000000000..67bffab9eec --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/.gitignore @@ -0,0 +1,152 @@ + +# Created by https://www.gitignore.io/api/gradle,kotlin,intellij,linux,osx + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Kotlin ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Gradle ### +.gradle +**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + + +# End of https://www.gitignore.io/api/gradle,kotlin,intellij,linux,osx diff --git a/modules/openapi-generator-gradle-plugin/README.adoc b/modules/openapi-generator-gradle-plugin/README.adoc new file mode 100644 index 00000000000..93c46aa2ce6 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/README.adoc @@ -0,0 +1,452 @@ += OpenAPI Generator Gradle Plugin + +This document describes the gradle plugin for OpenAPI Generator. + +== Tasks + +Tasks are listed under the "OpenAPI Tools" tasks heading. + + +.OpenAPI Tools Tasks +|=== +|task name |description + +|*openApiGenerate* +|Generate code via Open API Tools Generator for Open API 2.0 or 3.x specification documents. + +|*openApiGenerators* +|Lists generators available via Open API Generators. + +|*openApiMeta* +|Generates a new generator to be consumed via Open API Generator. + +|*openApiValidate* +|Validates an Open API 2.0 or 3.x specification document. +|=== + +== Plugin Setup + +[source,groovy] +---- +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + dependencies { + classpath "org.openapitools:openapi-generator-gradle-plugin:3.0.0-SNAPSHOT" + } +} + +apply plugin: 'org.openapi.generator' +---- + +[NOTE] +==== +The gradle plugin is not currently published to https://plugins.gradle.org/m2/. +==== + +== Configuration + +=== openApiGenerate + +.Options +|=== +|Key |Data Type |Default |Description + +|verbose +|Boolean +|false +|The verbosity of generation + +|generatorName +|String +|None +|The name of the generator which will handle codegen. + +|outputDir +|String +|None +|The output target directory into which code will be generated. + +|inputSpec +|String +|None +|The Open API 2.0/3.x specification location. + +|templateDir +|String +|None +|The template directory holding a custom template. + +|auth +|String +|None +|Adds authorization headers when fetching the OpenAPI definitions remotely. Pass in a URL-encoded string of name:header with a comma separating multiple values. + +|systemProperties +|Map(String,String) +|None +|Sets specified system properties. + +|configFile +|String +|None +|Path to json configuration file. See OpenAPI Generator readme for structure details. + +|skipOverwrite +|Boolean +|false +|Specifies if the existing files should be overwritten during the generation. + +|apiPackage +|String +|(generator specific) +|Package for generated api classes. + +|modelPackage +|String +|(generator specific) +|Package for generated model classes. + +|modelNamePrefix +|String +|None +|Prefix that will be prepended to all model names. + +|modelNameSuffix +|String +|None +|Suffix that will be appended to all model names. + +|instantiationTypes +|Map(String,String) +|None +|Sets instantiation type mappings. + +|typeMappings +|Map(String,String) +|None +|Sets mappings between OpenAPI spec types and generated code types. + +|additionalProperties +|Map(String,String) +|None +|Sets additional properties that can be referenced by the mustache templates. + +|languageSpecificPrimitives +|List(String) +|None +|Specifies additional language specific primitive types in the format of type1,type2,type3,type3. For example: String,boolean,Boolean,Double. + +|importMappings +|Map(String,String) +|None +|Specifies mappings between a given class and the import that should be used for that class. + +|invokerPackage +|String +|None +|Root package for generated code. + +|groupId +|String +|None +|GroupId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + +|id +|String +|None +|ArtifactId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + +|version +|String +|None +|Artifact version in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + +|library +|String +|None +|Reference the library template (sub-template) of a generator. + +|gitUserId +|String +|None +|Git user ID, e.g. openapitools. + +|gitRepoId +|String +|None +|Git repo ID, e.g. openapi-generator. + +|releaseNote +|String +|'Minor update' +|Release note. + +|httpUserAgent +|String +|None +|HTTP user agent, e.g. codegen_csharp_api_client. Generator default is 'OpenAPI-Generator/{packageVersion}}/{language}', but may be generator-specific. + +|reservedWordsMappings +|Map(String,String) +|None +|Specifies how a reserved name should be escaped to. Otherwise, the default _ is used. + +|ignoreFileOverride +|String +|None +|Specifies an override location for the .openapi-generator-ignore file. Most useful on initial generation. + +|removeOperationIdPrefix +|Boolean +|false +|Remove prefix of operationId, e.g. config_getId => getId. + +|apiFilesConstrainedTo +|List(String) +|None +|Defines which API-related files should be generated. This allows you to create a subset of generated files (or none at all). See Note Below. + +|modelFilesConstrainedTo +|List(String) +|None +|Defines which model-related files should be generated. This allows you to create a subset of generated files (or none at all). See Note Below. + +|supportingFilesConstrainedTo +|List(String) +|None +|Defines which supporting files should be generated. This allows you to create a subset of generated files (or none at all). See Note Below. + +|generateModelTests +|Boolean +|true +|Defines whether or not model-related _test_ files should be generated. + +|generateModelDocumentation +|Boolean +|true +|Defines whether or not model-related _documentation_ files should be generated. + +|generateApiTests +|Boolean +|true +|Defines whether or not api-related _test_ files should be generated. + +|generateApiDocumentation +|Boolean +|true +|Defines whether or not api-related _documentation_ files should be generated. + +|withXml +|Boolean +|false +|A special-case setting which configures some generators with XML support. In some cases, this forces json OR xml, so the default here is false. + +|configOptions +|Map(String,String) +|None +|A map of options specific to a generator. + +|=== + +[NOTE] +==== +Configuring any one of `apiFilesConstrainedTo`, `modelFilesConstrainedTo`, or `supportingFilesConstrainedTo` results +in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + +For more control over generation of individual files, configure an ignore file and refer to it via `ignoreFileOverride`. +==== + +=== openApiValidate + +.Options +|=== +|Key |Data Type |Default |Description + +|inputSpec +|String +|None +|The input specification to validate. Supports all formats supported by the Parser. + +|=== + +=== openApiMeta + +.Options +|=== +|Key |Data Type |Default |Description + +|generatorName +|String +|None +|The human-readable generator name of the newly created template generator. + +|packageName +|String +|org.openapitools.codegen +|The packageName generatorName to put the main class into. + +|outputFolder +|String +|Current Directory +|Where to write the generated files + +|=== + + +== Examples + +=== openApiGenerate + +This task exposes all options available via OpenAPI Generator CLI and the OpenAPI Generator Maven Plugin. + +.in build.gradle +[source,groovy] +---- +openApiGenerate { + generatorName = "kotlin" + inputSpec = "$rootDir/specs/petstore-v3.0.yaml".toString() + outputDir = "$buildDir/generated".toString() + apiPackage = "org.openapi.example.api" + invokerPackage = "org.openapi.example.invoker" + modelPackage = "org.openapi.example.model" + modelFilesConstrainedTo = [ + "Error" + ] + configOptions = [ + dateLibrary: "java8" + ] +} +---- + +The above code demonstrates configuration of global options as well as generator-specific config options. + +=== openApiGenerators + +This is an output-only listing task. There's no need to add configuration to build.gradle. + +.Example output of openApiGenerators task +[source,terminal] +---- +$ ./gradlew openApiGenerators + +> Task :openApiGenerators +The following generators are available: + +CLIENT generators: + - ada +… + +SERVER generators: + - ada-server +… + +DOCUMENTATION generators: + - cwiki +… + +CONFIG generators: + - apache2 + +OTHER generators: +… + +BUILD SUCCESSFUL in 0s +1 actionable task: 1 executed +---- + +[NOTE] +==== +Generator type listings in the above example have been truncated to avoid potential confusion with changing generator support. + +Please run the above task to list all available generators. +==== + +=== openApiMeta + +.in build.gradle +[source,groovy] +---- +openApiMeta { + generatorName = "Jim" + packageName = "us.jimschubert.example" +} +---- + +.Example output of openApiMeta task +[source,terminal] +---- +$ ./gradlew openApiMeta + +> Task :openApiMeta +Wrote file to /Users/jim/my_project/pom.xml +Wrote file to /Users/jim/my_project/src/main/java/us/jimschubert/example/JimGenerator.java +Wrote file to /Users/jim/my_project/README.md +Wrote file to /Users/jim/my_project/src/main/resources/jim/api.mustache +Wrote file to /Users/jim/my_project/src/main/resources/jim/model.mustache +Wrote file to /Users/jim/my_project/src/main/resources/jim/myFile.mustache +Wrote file to /Users/jim/my_project/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +Created generator JimGenerator + +BUILD SUCCESSFUL in 0s +1 actionable task: 1 executed +---- + + +=== openApiValidate + +.in buid.gradle +[source,groovy] +---- +openApiValidate { + inputSpec = "/src/openapi-generator/modules/openapi-generator/src/test/resources/3_0/petstore.yaml" +} +---- + +.Example output of openApiValidate task (success) +[source,terminal] +---- +$ ./gradlew openApiValidate --input=/Users/jim/projects/openapi-generator/modules/openapi-generator/src/test/resources/3_0/ping.yaml + +> Task :openApiValidate +Validating spec /Users/jim/projects/openapi-generator/modules/openapi-generator/src/test/resources/3_0/ping.yaml +Spec is valid. + +BUILD SUCCESSFUL in 0s +1 actionable task: 1 executed +---- + +.Example output of openApiValidate task (failure) +[source,terminal] +---- +$ ./gradlew openApiValidate + +> Task :openApiValidate FAILED +Validating spec /Users/jim/projects/openapi-generator/modules/openapi-generator/src/test/resources/3_0/petstore.yaml + +Spec is invalid. +Issues: + + attribute info is missing + + +FAILURE: Build failed with an exception. + +* What went wrong: +Execution failed for task ':openApiValidate'. +> Validation failed. + +* Try: +Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. + +* Get more help at https://help.gradle.org + +---- + +.in terminal (alternate) +[source,terminal] +---- +$ ./gradlew openApiValidate --input=/Users/jim/projects/openapi-generator/modules/openapi-generator/src/test/resources/3_0/petstore.yaml +---- \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/build.gradle b/modules/openapi-generator-gradle-plugin/build.gradle new file mode 100644 index 00000000000..9392744804b --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/build.gradle @@ -0,0 +1,89 @@ +buildscript { + ext.kotlin_version = '1.2.41' + repositories { + mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "gradle.plugin.org.gradle.kotlin:gradle-kotlin-dsl-plugins:0.17.5" + } +} + +group 'org.openapitools' +// Shared OpenAPI Generator version be passed via command line arg as -PopenApiGeneratorVersion=VERSION +version "$openApiGeneratorVersion" +description = """ +This plugin supports common functionality found in Open API Generator CLI as a gradle plugin. + +This gives you the ability to generate client SDKs, documentation, new generators, and to validate Open API 2.0 and 3.x +specifications as part of your build. Other tasks are available as command line tasks. +""" + +apply plugin: 'java-gradle-plugin' +apply plugin: 'maven-publish' +apply plugin: 'kotlin' +apply plugin: "org.gradle.kotlin.kotlin-dsl" + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + compile gradleApi() + // Shared OpenAPI Generator version be passed via command line arg as -PopenApiGeneratorVersion=VERSION + compile "org.openapitools:openapi-generator:$openApiGeneratorVersion" + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + testCompile 'org.testng:testng:6.9.6', + "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + + testCompile "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version" +} + +test { + useTestNG() + testClassesDirs = files(project.tasks.compileTestKotlin.destinationDir) + testLogging.showStandardStreams = false + + beforeTest { descriptor -> + logger.lifecycle("Running test: " + descriptor) + } + + failFast = true + + onOutput { descriptor, event -> + // SLF4J may complain about multiple bindings dependign on how this is run. + // This is just a warning, but can make test output less readable. So we ignore it specifically. + if (!event.message.contains("SLF4J:")) { + logger.lifecycle("Test: " + descriptor + " produced standard out/err: " + event.message) + } + } +} + + +gradlePlugin { + plugins { + openApiGenerator { + id = 'org.openapi.generator' + implementationClass = 'org.openapitools.generator.gradle.plugin.OpenApiGeneratorPlugin' + } + } +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/gradle.properties b/modules/openapi-generator-gradle-plugin/gradle.properties new file mode 100644 index 00000000000..8d733fd5b05 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/gradle.properties @@ -0,0 +1 @@ +openApiGeneratorVersion=3.0.0-SNAPSHOT diff --git a/modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..91ca28c8b802289c3a438766657a5e98f20eff03 GIT binary patch literal 54413 zcmafaV|Zr4wq`oEZQHiZj%|LijZQlLf{tz5M#r{o+fI6V=G-$g=gzrzeyqLskF}nv zRZs0&c;EUi2L_G~0s;*U0szbK}f6%Pvi zRZ#mYf6f1oqJoH`jHHCB8l!^by~4z}yc`4LEP@;Z?bO6{g9`Hk+s@(L1jC5Tq{1Yf z4E;CQvrx0-gF+peRxFC*gF=&$zNYk(w0q}U=WqXMz`tYs@0o%B{dRD+{C_6(f9t^g zhmNJQv6-#;f2)f2uc{u-#*U8W&i{|ewYN^n_1~cv|1J!}zc&$eaBy{T{cEpa46s*q zHFkD2cV;xTHFj}{*3kBt*FgS4A5SI|$F%$gB@It9FlC}D3y`sbZG{2P6gGwC$U`6O zb_cId9AhQl#A<&=x>-xDD%=Ppt$;y71@Lwsl{x943#T@8*?cbR<~d`@@}4V${+r$jICUIOzgZJy_9I zu*eA(F)$~J07zX%tmQN}1^wj+RM|9bbwhQA=xrPE*{vB_P!pPYT5{Or^m*;Qz#@Bl zRywCG_RDyM6bf~=xn}FtiFAw|rrUxa1+z^H`j6e|GwKDuq}P)z&@J>MEhsVBvnF|O zOEm)dADU1wi8~mX(j_8`DwMT_OUAnjbWYer;P*^Uku_qMu3}qJU zTAkza-K9aj&wcsGuhQ>RQoD?gz~L8RwCHOZDzhBD$az*$TQ3!uygnx_rsXG`#_x5t zn*lb(%JI3%G^MpYp-Y(KI4@_!&kBRa3q z|Fzn&3R%ZsoMNEn4pN3-BSw2S_{IB8RzRv(eQ1X zyBQZHJ<(~PfUZ~EoI!Aj`9k<+Cy z2DtI<+9sXQu!6&-Sk4SW3oz}?Q~mFvy(urUy<)x!KQ>#7yIPC)(ORhKl7k)4eSy~} z7#H3KG<|lt68$tk^`=yjev%^usOfpQ#+Tqyx|b#dVA(>fPlGuS@9ydo z!Cs#hse9nUETfGX-7lg;F>9)+ml@M8OO^q|W~NiysX2N|2dH>qj%NM`=*d3GvES_# zyLEHw&1Fx<-dYxCQbk_wk^CI?W44%Q9!!9aJKZW-bGVhK?N;q`+Cgc*WqyXcxZ%U5QXKu!Xn)u_dxeQ z;uw9Vysk!3OFzUmVoe)qt3ifPin0h25TU zrG*03L~0|aaBg7^YPEW^Yq3>mSNQgk-o^CEH?wXZ^QiPiuH}jGk;75PUMNquJjm$3 zLcXN*uDRf$Jukqg3;046b;3s8zkxa_6yAlG{+7{81O3w96i_A$KcJhD&+oz1<>?lun#C3+X0q zO4JxN{qZ!e#FCl@e_3G?0I^$CX6e$cy7$BL#4<`AA)Lw+k`^15pmb-447~5lkSMZ` z>Ce|adKhb-F%yy!vx>yQbXFgHyl(an=x^zi(!-~|k;G1=E(e@JgqbAF{;nv`3i)oi zDeT*Q+Mp{+NkURoabYb9@#Bi5FMQnBFEU?H{~9c;g3K%m{+^hNe}(MdpPb?j9`?2l z#%AO!|2QxGq7-2Jn2|%atvGb(+?j&lmP509i5y87`9*BSY++<%%DXb)kaqG0(4Eft zj|2!Od~2TfVTi^0dazAIeVe&b#{J4DjN6;4W;M{yWj7#+oLhJyqeRaO;>?%mX>Ec{Mp~;`bo}p;`)@5dA8fNQ38FyMf;wUPOdZS{U*8SN6xa z-kq3>*Zos!2`FMA7qjhw-`^3ci%c91Lh`;h{qX1r;x1}eW2hYaE*3lTk4GwenoxQ1kHt1Lw!*N8Z%DdZSGg5~Bw}+L!1#d$u+S=Bzo7gi zqGsBV29i)Jw(vix>De)H&PC; z-t2OX_ak#~eSJ?Xq=q9A#0oaP*dO7*MqV;dJv|aUG00UX=cIhdaet|YEIhv6AUuyM zH1h7fK9-AV)k8sr#POIhl+?Z^r?wI^GE)ZI=H!WR<|UI(3_YUaD#TYV$Fxd015^mT zpy&#-IK>ahfBlJm-J(n(A%cKV;)8&Y{P!E|AHPtRHk=XqvYUX?+9po4B$0-6t74UUef${01V{QLEE8gzw* z5nFnvJ|T4dlRiW9;Ed_yB{R@)fC=zo4hCtD?TPW*WJmMXYxN_&@YQYg zBQ$XRHa&EE;YJrS{bn7q?}Y&DH*h;){5MmE(9A6aSU|W?{3Ox%5fHLFScv7O-txuRbPG1KQtI`Oay=IcEG=+hPhlnYC;`wSHeo|XGio0aTS6&W($E$ z?N&?TK*l8;Y^-xPl-WVZwrfdiQv10KdsAb9u-*1co*0-Z(h#H)k{Vc5CT!708cs%sExvPC+7-^UY~jTfFq=cj z!Dmy<+NtKp&}}$}rD{l?%MwHdpE(cPCd;-QFPk1`E5EVNY2i6E`;^aBlx4}h*l42z zpY#2cYzC1l6EDrOY*ccb%kP;k8LHE3tP>l3iK?XZ%FI<3666yPw1rM%>eCgnv^JS_ zK7c~;g7yXt9fz@(49}Dj7VO%+P!eEm& z;z8UXs%NsQ%@2S5nve)@;yT^61BpVlc}=+i6{ZZ9r7<({yUYqe==9*Z+HguP3`sA& z{`inI4G)eLieUQ*pH9M@)u7yVnWTQva;|xq&-B<>MoP(|xP(HqeCk1&h>DHNLT>Zi zQ$uH%s6GoPAi0~)sC;`;ngsk+StYL9NFzhFEoT&Hzfma1f|tEnL0 zMWdX4(@Y*?*tM2@H<#^_l}BC&;PYJl%~E#veQ61{wG6!~nyop<^e)scV5#VkGjYc2 z$u)AW-NmMm%T7WschOnQ!Hbbw&?`oMZrJ&%dVlN3VNra1d0TKfbOz{dHfrCmJ2Jj= zS#Gr}JQcVD?S9X!u|oQ7LZ+qcq{$40 ziG5=X^+WqeqxU00YuftU7o;db=K+Tq!y^daCZgQ)O=M} zK>j*<3oxs=Rcr&W2h%w?0Cn3);~vqG>JO_tTOzuom^g&^vzlEjkx>Sv!@NNX%_C!v zaMpB>%yVb}&ND9b*O>?HxQ$5-%@xMGe4XKjWh7X>CYoRI2^JIwi&3Q5UM)?G^k8;8 zmY$u;(KjZx>vb3fe2zgD7V;T2_|1KZQW$Yq%y5Ioxmna9#xktcgVitv7Sb3SlLd6D zfmBM9Vs4rt1s0M}c_&%iP5O{Dnyp|g1(cLYz^qLqTfN6`+o}59Zlu%~oR3Q3?{Bnr zkx+wTpeag^G12fb_%SghFcl|p2~<)Av?Agumf@v7y-)ecVs`US=q~=QG%(_RTsqQi z%B&JdbOBOmoywgDW|DKR5>l$1^FPhxsBrja<&}*pfvE|5dQ7j-wV|ur%QUCRCzBR3q*X`05O3U@?#$<>@e+Zh&Z&`KfuM!0XL& zI$gc@ZpM4o>d&5)mg7+-Mmp98K^b*28(|Ew8kW}XEV7k^vnX-$onm9OtaO@NU9a|as7iA%5Wrw9*%UtJYacltplA5}gx^YQM` zVkn`TIw~avq)mIQO0F0xg)w$c)=8~6Jl|gdqnO6<5XD)&e7z7ypd3HOIR+ss0ikSVrWar?548HFQ*+hC)NPCq*;cG#B$7 z!n?{e9`&Nh-y}v=nK&PR>PFdut*q&i81Id`Z<0vXUPEbbJ|<~_D!)DJMqSF~ly$tN zygoa)um~xdYT<7%%m!K8+V(&%83{758b0}`b&=`))Tuv_)OL6pf=XOdFk&Mfx9y{! z6nL>V?t=#eFfM$GgGT8DgbGRCF@0ZcWaNs_#yl+6&sK~(JFwJmN-aHX{#Xkpmg;!} zgNyYYrtZdLzW1tN#QZAh!z5>h|At3m+ryJ-DFl%V>w?cmVTxt^DsCi1ZwPaCe*D{) z?#AZV6Debz{*D#C2>44Czy^yT3y92AYDcIXtZrK{L-XacVl$4i=X2|K=Fy5vAzhk{ zu3qG=qSb_YYh^HirWf~n!_Hn;TwV8FU9H8+=BO)XVFV`nt)b>5yACVr!b98QlLOBDY=^KS<*m9@_h3;64VhBQzb_QI)gbM zSDto2i*iFrvxSmAIrePB3i`Ib>LdM8wXq8(R{-)P6DjUi{2;?}9S7l7bND4w%L2!; zUh~sJ(?Yp}o!q6)2CwG*mgUUWlZ;xJZo`U`tiqa)H4j>QVC_dE7ha0)nP5mWGB268 zn~MVG<#fP#R%F=Ic@(&Va4dMk$ysM$^Avr1&hS!p=-7F>UMzd(M^N9Ijb|364}qcj zcIIh7suk$fQE3?Z^W4XKIPh~|+3(@{8*dSo&+Kr(J4^VtC{z*_{2}ld<`+mDE2)S| zQ}G#Q0@ffZCw!%ZGc@kNoMIdQ?1db%N1O0{IPPesUHI;(h8I}ETudk5ESK#boZgln z(0kvE`&6z1xH!s&={%wQe;{^&5e@N0s7IqR?L*x%iXM_czI5R1aU?!bA7)#c4UN2u zc_LZU+@elD5iZ=4*X&8%7~mA;SA$SJ-8q^tL6y)d150iM)!-ry@TI<=cnS#$kJAS# zq%eK**T*Wi2OlJ#w+d_}4=VN^A%1O+{?`BK00wkm)g8;u?vM;RR+F1G?}({ENT3i= zQsjJkp-dmJ&3-jMNo)wrz0!g*1z!V7D(StmL(A}gr^H-CZ~G9u?*Uhcx|x7rb`v^X z9~QGx;wdF4VcxCmEBp$F#sms@MR?CF67)rlpMxvwhEZLgp2?wQq|ci#rLtrYRV~iR zN?UrkDDTu114&d~Utjcyh#tXE_1x%!dY?G>qb81pWWH)Ku@Kxbnq0=zL#x@sCB(gs zm}COI(!{6-XO5li0>1n}Wz?w7AT-Sp+=NQ1aV@fM$`PGZjs*L+H^EW&s!XafStI!S zzgdntht=*p#R*o8-ZiSb5zf6z?TZr$^BtmIfGAGK;cdg=EyEG)fc*E<*T=#a?l=R5 zv#J;6C(umoSfc)W*EODW4z6czg3tXIm?x8{+8i^b;$|w~k)KLhJQnNW7kWXcR^sol z1GYOp?)a+}9Dg*nJ4fy*_riThdkbHO37^csfZRGN;CvQOtRacu6uoh^gg%_oEZKDd z?X_k67s$`|Q&huidfEonytrq!wOg07H&z@`&BU6D114p!rtT2|iukF}>k?71-3Hk< zs6yvmsMRO%KBQ44X4_FEYW~$yx@Y9tKrQ|rC1%W$6w}-9!2%4Zk%NycTzCB=nb)r6*92_Dg+c0;a%l1 zsJ$X)iyYR2iSh|%pIzYV1OUWER&np{w1+RXb~ zMUMRymjAw*{M)UtbT)T!kq5ZAn%n=gq3ssk3mYViE^$paZ;c^7{vXDJ`)q<}QKd2?{r9`X3mpZ{AW^UaRe2^wWxIZ$tuyKzp#!X-hXkHwfD zj@2tA--vFi3o_6B?|I%uwD~emwn0a z+?2Lc1xs(`H{Xu>IHXpz=@-84uw%dNV;{|c&ub|nFz(=W-t4|MME(dE4tZQi?0CE|4_?O_dyZj1)r zBcqB8I^Lt*#)ABdw#yq{OtNgf240Jvjm8^zdSf40 z;H)cp*rj>WhGSy|RC5A@mwnmQ`y4{O*SJ&S@UFbvLWyPdh)QnM=(+m3p;0&$^ysbZ zJt!ZkNQ%3hOY*sF2_~-*`aP|3Jq7_<18PX*MEUH*)t{eIx%#ibC|d&^L5FwoBN}Oe z?!)9RS@Zz%X1mqpHgym75{_BM4g)k1!L{$r4(2kL<#Oh$Ei7koqoccI3(MN1+6cDJ zp=xQhmilz1?+ZjkX%kfn4{_6K_D{wb~rdbkh!!k!Z@cE z^&jz55*QtsuNSlGPrU=R?}{*_8?4L7(+?>?(^3Ss)f!ou&{6<9QgH>#2$?-HfmDPN z6oIJ$lRbDZb)h-fFEm^1-v?Slb8udG{7GhbaGD_JJ8a9f{6{TqQN;m@$&)t81k77A z?{{)61za|e2GEq2)-OqcEjP`fhIlUs_Es-dfgX-3{S08g`w=wGj2{?`k^GD8d$}6Z zBT0T1lNw~fuwjO5BurKM593NGYGWAK%UCYiq{$p^GoYz^Uq0$YQ$j5CBXyog8(p_E znTC+$D`*^PFNc3Ih3b!2Lu|OOH6@46D)bbvaZHy%-9=$cz}V^|VPBpmPB6Ivzlu&c zPq6s7(2c4=1M;xlr}bkSmo9P`DAF>?Y*K%VPsY`cVZ{mN&0I=jagJ?GA!I;R)i&@{ z0Gl^%TLf_N`)`WKs?zlWolWvEM_?{vVyo(!taG$`FH2bqB`(o50pA=W34kl-qI62lt z1~4LG_j%sR2tBFteI{&mOTRVU7AH>>-4ZCD_p6;-J<=qrod`YFBwJz(Siu(`S}&}1 z6&OVJS@(O!=HKr-Xyzuhi;swJYK*ums~y1ePdX#~*04=b9)UqHHg;*XJOxnS6XK#j zG|O$>^2eW2ZVczP8#$C`EpcWwPFX4^}$omn{;P(fL z>J~%-r5}*D3$Kii z34r@JmMW2XEa~UV{bYP=F;Y5=9miJ+Jw6tjkR+cUD5+5TuKI`mSnEaYE2=usXNBs9 zac}V13%|q&Yg6**?H9D620qj62dM+&&1&a{NjF}JqmIP1I1RGppZ|oIfR}l1>itC% zl>ed${{_}8^}m2^br*AIX$L!Vc?Sm@H^=|LnpJg`a7EC+B;)j#9#tx-o0_e4!F5-4 zF4gA;#>*qrpow9W%tBzQ89U6hZ9g=-$gQpCh6Nv_I0X7t=th2ajJ8dBbh{i)Ok4{I z`Gacpl?N$LjC$tp&}7Sm(?A;;Nb0>rAWPN~@3sZ~0_j5bR+dz;Qs|R|k%LdreS3Nn zp*36^t#&ASm=jT)PIjNqaSe4mTjAzlAFr*@nQ~F+Xdh$VjHWZMKaI+s#FF#zjx)BJ zufxkW_JQcPcHa9PviuAu$lhwPR{R{7CzMUi49=MaOA%ElpK;A)6Sgsl7lw)D$8FwE zi(O6g;m*86kcJQ{KIT-Rv&cbv_SY4 zpm1|lSL*o_1LGOlBK0KuU2?vWcEcQ6f4;&K=&?|f`~X+s8H)se?|~2HcJo{M?Ity) zE9U!EKGz2^NgB6Ud;?GcV*1xC^1RYIp&0fr;DrqWLi_Kts()-#&3|wz{wFQsKfnnsC||T?oIgUp z{O(?Df7&vW!i#_~*@naguLLjDAz+)~*_xV2iz2?(N|0y8DMneikrT*dG`mu6vdK`% z=&nX5{F-V!Reau}+w_V3)4?}h@A@O)6GCY7eXC{p-5~p8x{cH=hNR;Sb{*XloSZ_%0ZKYG=w<|!vy?spR4!6mF!sXMUB5S9o_lh^g0!=2m55hGR; z-&*BZ*&;YSo474=SAM!WzrvjmNtq17L`kxbrZ8RN419e=5CiQ-bP1j-C#@@-&5*(8 zRQdU~+e(teUf}I3tu%PB1@Tr{r=?@0KOi3+Dy8}+y#bvgeY(FdN!!`Kb>-nM;7u=6 z;0yBwOJ6OdWn0gnuM{0`*fd=C(f8ASnH5aNYJjpbY1apTAY$-%)uDi$%2)lpH=#)=HH z<9JaYwPKil@QbfGOWvJ?cN6RPBr`f+jBC|-dO|W@x_Vv~)bmY(U(!cs6cnhe0z31O z>yTtL4@KJ*ac85u9|=LFST22~!lb>n7IeHs)_(P_gU}|8G>{D_fJX)8BJ;Se? z67QTTlTzZykb^4!{xF!=C}VeFd@n!9E)JAK4|vWVwWop5vSWcD<;2!88v-lS&ve7C zuYRH^85#hGKX(Mrk};f$j_V&`Nb}MZy1mmfz(e`nnI4Vpq(R}26pZx?fq%^|(n~>* z5a5OFtFJJfrZmgjyHbj1`9||Yp?~`p2?4NCwu_!!*4w8K`&G7U_|np&g7oY*-i;sI zu)~kYH;FddS{7Ri#Z5)U&X3h1$Mj{{yk1Q6bh4!7!)r&rqO6K~{afz@bis?*a56i& zxi#(Ss6tkU5hDQJ0{4sKfM*ah0f$>WvuRL zunQ-eOqa3&(rv4kiQ(N4`FO6w+nko_HggKFWx@5aYr}<~8wuEbD(Icvyl~9QL^MBt zSvD)*C#{2}!Z55k1ukV$kcJLtW2d~%z$t0qMe(%2qG`iF9K_Gsae7OO%Tf8E>ooch ztAw01`WVv6?*14e1w%Wovtj7jz_)4bGAqqo zvTD|B4)Ls8x7-yr6%tYp)A7|A)x{WcI&|&DTQR&2ir(KGR7~_RhNOft)wS<+vQ*|sf;d>s zEfl&B^*ZJp$|N`w**cXOza8(ARhJT{O3np#OlfxP9Nnle4Sto)Fv{w6ifKIN^f1qO*m8+MOgA1^Du!=(@MAh8)@wU8t=Ymh!iuT_lzfm za~xEazL-0xwy9$48!+?^lBwMV{!Gx)N>}CDi?Jwax^YX@_bxl*+4itP;DrTswv~n{ zZ0P>@EB({J9ZJ(^|ptn4ks^Z2UI&87d~J_^z0&vD2yb%*H^AE!w= zm&FiH*c%vvm{v&i3S>_hacFH${|(2+q!`X~zn4$aJDAry>=n|{C7le(0a)nyV{kAD zlud4-6X>1@-XZd`3SKKHm*XNn_zCyKHmf*`C_O509$iy$Wj`Sm3y?nWLCDy>MUx1x zl-sz7^{m(&NUk*%_0(G^>wLDnXW90FzNi$Tu6* z<+{ePBD`%IByu977rI^x;gO5M)Tfa-l*A2mU-#IL2?+NXK-?np<&2rlF;5kaGGrx2 zy8Xrz`kHtTVlSSlC=nlV4_oCsbwyVHG4@Adb6RWzd|Otr!LU=% zEjM5sZ#Ib4#jF(l!)8Na%$5VK#tzS>=05GpV?&o* z3goH1co0YR=)98rPJ~PuHvkA59KUi#i(Mq_$rApn1o&n1mUuZfFLjx@3;h`0^|S##QiTP8rD`r8P+#D@gvDJh>amMIl065I)PxT6Hg(lJ?X7*|XF2Le zv36p8dWHCo)f#C&(|@i1RAag->5ch8TY!LJ3(+KBmLxyMA%8*X%_ARR*!$AL66nF= z=D}uH)D)dKGZ5AG)8N-;Il*-QJ&d8u30&$_Q0n1B58S0ykyDAyGa+BZ>FkiOHm1*& zNOVH;#>Hg5p?3f(7#q*dL74;$4!t?a#6cfy#}9H3IFGiCmevir5@zXQj6~)@zYrWZ zRl*e66rjwksx-)Flr|Kzd#Bg>We+a&E{h7bKSae9P~ z(g|zuXmZ zD?R*MlmoZ##+0c|cJ(O{*h(JtRdA#lChYhfsx25(Z`@AK?Q-S8_PQqk z>|Z@Ki1=wL1_c6giS%E4YVYD|Y-{^ZzFwB*yN8-4#+TxeQ`jhks7|SBu7X|g=!_XL z`mY=0^chZfXm%2DYHJ4z#soO7=NONxn^K3WX={dV>$CTWSZe@<81-8DVtJEw#Uhd3 zxZx+($6%4a&y_rD8a&E`4$pD6-_zZJ%LEE*1|!9uOm!kYXW< zOBXZAowsX-&$5C`xgWkC43GcnY)UQt2Qkib4!!8Mh-Q!_M%5{EC=Gim@_;0+lP%O^ zG~Q$QmatQk{Mu&l{q~#kOD;T-{b1P5u7)o-QPPnqi?7~5?7%IIFKdj{;3~Hu#iS|j z)Zoo2wjf%+rRj?vzWz(6JU`=7H}WxLF*|?WE)ci7aK?SCmd}pMW<{#1Z!_7BmVP{w zSrG>?t}yNyCR%ZFP?;}e8_ zRy67~&u11TN4UlopWGj6IokS{vB!v!n~TJYD6k?~XQkpiPMUGLG2j;lh>Eb5bLTkX zx>CZlXdoJsiPx=E48a4Fkla>8dZYB%^;Xkd(BZK$z3J&@({A`aspC6$qnK`BWL;*O z-nRF{XRS`3Y&b+}G&|pE1K-Ll_NpT!%4@7~l=-TtYRW0JJ!s2C-_UsRBQ=v@VQ+4> z*6jF0;R@5XLHO^&PFyaMDvyo?-lAD(@H61l-No#t@at@Le9xOgTFqkc%07KL^&iss z!S2Ghm)u#26D(e1Q7E;L`rxOy-N{kJ zTgfw}az9=9Su?NEMMtpRlYwDxUAUr8F+P=+9pkX4%iA4&&D<|=B|~s*-U+q6cq`y* zIE+;2rD7&D5X;VAv=5rC5&nP$E9Z3HKTqIFCEV%V;b)Y|dY?8ySn|FD?s3IO>VZ&&f)idp_7AGnwVd1Z znBUOBA}~wogNpEWTt^1Rm-(YLftB=SU|#o&pT7vTr`bQo;=ZqJHIj2MP{JuXQPV7% z0k$5Ha6##aGly<}u>d&d{Hkpu?ZQeL_*M%A8IaXq2SQl35yW9zs4^CZheVgHF`%r= zs(Z|N!gU5gj-B^5{*sF>;~fauKVTq-Ml2>t>E0xl9wywD&nVYZfs1F9Lq}(clpNLz z4O(gm_i}!k`wUoKr|H#j#@XOXQ<#eDGJ=eRJjhOUtiKOG;hym-1Hu)1JYj+Kl*To<8( za1Kf4_Y@Cy>eoC59HZ4o&xY@!G(2p^=wTCV>?rQE`Upo^pbhWdM$WP4HFdDy$HiZ~ zRUJFWTII{J$GLVWR?miDjowFk<1#foE3}C2AKTNFku+BhLUuT>?PATB?WVLzEYyu+ zM*x((pGdotzLJ{}R=OD*jUexKi`mb1MaN0Hr(Wk8-Uj0zA;^1w2rmxLI$qq68D>^$ zj@)~T1l@K|~@YJ6+@1vlWl zHg5g%F{@fW5K!u>4LX8W;ua(t6YCCO_oNu}IIvI6>Fo@MilYuwUR?9p)rKNzDmTAN zzN2d>=Za&?Z!rJFV*;mJ&-sBV80%<-HN1;ciLb*Jk^p?u<~T25%7jjFnorfr={+wm zzl5Q6O>tsN8q*?>uSU6#xG}FpAVEQ_++@}G$?;S7owlK~@trhc#C)TeIYj^N(R&a} zypm~c=fIs;M!YQrL}5{xl=tUU-Tfc0ZfhQuA-u5(*w5RXg!2kChQRd$Fa8xQ0CQIU zC`cZ*!!|O!*y1k1J^m8IIi|Sl3R}gm@CC&;4840^9_bb9%&IZTRk#=^H0w%`5pMDCUef5 zYt-KpWp2ijh+FM`!zZ35>+7eLN;s3*P!bp%-oSx34fdTZ14Tsf2v7ZrP+mitUx$rS zW(sOi^CFxe$g3$x45snQwPV5wpf}>5OB?}&Gh<~i(mU&ss#7;utaLZ!|KaTHniGO9 zVC9OTzuMKz)afey_{93x5S*Hfp$+r*W>O^$2ng|ik!<`U1pkxm3*)PH*d#>7md1y} zs7u^a8zW8bvl92iN;*hfOc-=P7{lJeJ|3=NfX{(XRXr;*W3j845SKG&%N zuBqCtDWj*>KooINK1 zFPCsCWr!-8G}G)X*QM~34R*k zmRmDGF*QE?jCeNfc?k{w<}@29e}W|qKJ1K|AX!htt2|B`nL=HkC4?1bEaHtGBg}V( zl(A`6z*tck_F$4;kz-TNF%7?=20iqQo&ohf@S{_!TTXnVh}FaW2jxAh(DI0f*SDG- z7tqf5X@p#l?7pUNI(BGi>n_phw=lDm>2OgHx-{`T>KP2YH9Gm5ma zb{>7>`tZ>0d5K$j|s2!{^sFWQo3+xDb~#=9-jp(1ydI3_&RXGB~rxWSMgDCGQG)oNoc#>)td zqE|X->35U?_M6{^lB4l(HSN|`TC2U*-`1jSQeiXPtvVXdN-?i1?d#;pw%RfQuKJ|e zjg75M+Q4F0p@8I3ECpBhGs^kK;^0;7O@MV=sX^EJLVJf>L;GmO z3}EbTcoom7QbI(N8ad!z(!6$!MzKaajSRb0c+ZDQ($kFT&&?GvXmu7+V3^_(VJx1z zP-1kW_AB&_A;cxm*g`$ z#Pl@Cg{siF0ST2-w)zJkzi@X)5i@)Z;7M5ewX+xcY36IaE0#flASPY2WmF8St0am{ zV|P|j9wqcMi%r-TaU>(l*=HxnrN?&qAyzimA@wtf;#^%{$G7i4nXu=Pp2#r@O~wi)zB>@25A*|axl zEclXBlXx1LP3x0yrSx@s-kVW4qlF+idF+{M7RG54CgA&soDU-3SfHW@-6_ z+*;{n_SixmGCeZjHmEE!IF}!#aswth_{zm5Qhj0z-@I}pR?cu=P)HJUBClC;U+9;$#@xia30o$% zDw%BgOl>%vRenxL#|M$s^9X}diJ9q7wI1-0n2#6>@q}rK@ng(4M68(t52H_Jc{f&M9NPxRr->vj-88hoI?pvpn}llcv_r0`;uN>wuE{ z&TOx_i4==o;)>V4vCqG)A!mW>dI^Ql8BmhOy$6^>OaUAnI3>mN!Zr#qo4A>BegYj` zNG_)2Nvy2Cqxs1SF9A5HHhL7sai#Umw%K@+riaF+q)7&MUJvA&;$`(w)+B@c6!kX@ zzuY;LGu6|Q2eu^06PzSLspV2v4E?IPf`?Su_g8CX!75l)PCvyWKi4YRoRThB!-BhG zubQ#<7oCvj@z`^y&mPhSlbMf0<;0D z?5&!I?nV-jh-j1g~&R(YL@c=KB_gNup$8abPzXZN`N|WLqxlN)ZJ+#k4UWq#WqvVD z^|j+8f5uxTJtgcUscKTqKcr?5g-Ih3nmbvWvvEk})u-O}h$=-p4WE^qq7Z|rLas0$ zh0j&lhm@Rk(6ZF0_6^>Rd?Ni-#u1y`;$9tS;~!ph8T7fLlYE{P=XtWfV0Ql z#z{_;A%p|8+LhbZT0D_1!b}}MBx9`R9uM|+*`4l3^O(>Mk%@ha>VDY=nZMMb2TnJ= zGlQ+#+pmE98zuFxwAQcVkH1M887y;Bz&EJ7chIQQe!pgWX>(2ruI(emhz@_6t@k8Z zqFEyJFX2PO`$gJ6p$=ku{7!vR#u+$qo|1r;orjtp9FP^o2`2_vV;W&OT)acRXLN^m zY8a;geAxg!nbVu|uS8>@Gvf@JoL&GP`2v4s$Y^5vE32&l;2)`S%e#AnFI-YY7_>d#IKJI!oL6e z_7W3e=-0iz{bmuB*HP+D{Nb;rn+RyimTFqNV9Bzpa0?l`pWmR0yQOu&9c0S*1EPr1 zdoHMYlr>BycjTm%WeVuFd|QF8I{NPT&`fm=dITj&3(M^q ze2J{_2zB;wDME%}SzVWSW6)>1QtiX)Iiy^p2eT}Ii$E9w$5m)kv(3wSCNWq=#DaKZ zs%P`#^b7F-J0DgQ1?~2M`5ClYtYN{AlU|v4pEg4z03=g6nqH`JjQuM{k`!6jaIL_F zC;sn?1x?~uMo_DFg#ypNeie{3udcm~M&bYJ1LI zE%y}P9oCX3I1Y9yhF(y9Ix_=8L(p)EYr&|XZWCOb$7f2qX|A4aJ9bl7pt40Xr zXUT#NMBB8I@xoIGSHAZkYdCj>eEd#>a;W-?v4k%CwBaR5N>e3IFLRbDQTH#m_H+4b zk2UHVymC`%IqwtHUmpS1!1p-uQB`CW1Y!+VD!N4TT}D8(V0IOL|&R&)Rwj@n8g@=`h&z9YTPDT+R9agnwPuM!JW~=_ya~% zIJ*>$Fl;y7_`B7G4*P!kcy=MnNmR`(WS5_sRsvHF42NJ;EaDram5HwQ4Aw*qbYn0j;#)bh1lyKLg#dYjN*BMlh+fxmCL~?zB;HBWho;20WA==ci0mAqMfyG>1!HW zO7rOga-I9bvut1Ke_1eFo9tbzsoPTXDW1Si4}w3fq^Z|5LGf&egnw%DV=b11$F=P~ z(aV+j8S}m=CkI*8=RcrT>GmuYifP%hCoKY22Z4 zmu}o08h3YhcXx-v-QC??8mDn<+}+*X{+gZH-I;G^|7=1fBveS?J$27H&wV5^V^P$! z84?{UeYSmZ3M!@>UFoIN?GJT@IroYr;X@H~ax*CQ>b5|Xi9FXt5j`AwUPBq`0sWEJ z3O|k+g^JKMl}L(wfCqyMdRj9yS8ncE7nI14Tv#&(?}Q7oZpti{Q{Hw&5rN-&i|=fWH`XTQSu~1jx(hqm$Ibv zRzFW9$xf@oZAxL~wpj<0ZJ3rdPAE=0B>G+495QJ7D>=A&v^zXC9)2$$EnxQJ<^WlV zYKCHb1ZzzB!mBEW2WE|QG@&k?VXarY?umPPQ|kziS4{EqlIxqYHP!HN!ncw6BKQzKjqk!M&IiOJ9M^wc~ZQ1xoaI z;4je%ern~?qi&J?eD!vTl__*kd*nFF0n6mGEwI7%dI9rzCe~8vU1=nE&n4d&8}pdL zaz`QAY?6K@{s2x%Sx%#(y+t6qLw==>2(gb>AksEebXv=@ht>NBpqw=mkJR(c?l7vo z&cV)hxNoYPGqUh9KAKT)kc(NqekzE6(wjjotP(ac?`DJF=Sb7^Xet-A3PRl%n&zKk zruT9cS~vV1{%p>OVm1-miuKr<@rotj*5gd$?K`oteNibI&K?D63RoBjw)SommJ5<4 zus$!C8aCP{JHiFn2>XpX&l&jI7E7DcTjzuLYvON2{rz<)#$HNu(;ie-5$G<%eLKnTK7QXfn(UR(n+vX%aeS6!q6kv z!3nzY76-pdJp339zsl_%EI|;ic_m56({wdc(0C5LvLULW=&tWc5PW-4;&n+hm1m`f zzQV0T>OPSTjw=Ox&UF^y< zarsYKY8}YZF+~k70=olu$b$zdLaozBE|QE@H{_R21QlD5BilYBTOyv$D5DQZ8b1r- zIpSKX!SbA0Pb5#cT)L5!KpxX+x+8DRy&`o-nj+nmgV6-Gm%Fe91R1ca3`nt*hRS|^ z<&we;TJcUuPDqkM7k0S~cR%t7a`YP#80{BI$e=E!pY}am)2v3-Iqk2qvuAa1YM>xj#bh+H2V z{b#St2<;Gg>$orQ)c2a4AwD5iPcgZ7o_}7xhO86(JSJ(q(EWKTJDl|iBjGEMbX8|P z4PQHi+n(wZ_5QrX0?X_J)e_yGcTM#E#R^u_n8pK@l5416`c9S=q-e!%0RjoPyTliO zkp{OC@Ep^#Ig-n!C)K0Cy%8~**Vci8F1U(viN{==KU0nAg2(+K+GD_Gu#Bx!{tmUm zCwTrT(tCr6X8j43_n96H9%>>?4akSGMvgd+krS4wRexwZ1JxrJy!Uhz#yt$-=aq?A z@?*)bRZxjG9OF~7d$J0cwE_^CLceRK=LvjfH-~{S><^D;6B2&p-02?cl?|$@>`Qt$ zP*iaOxg<+(rbk>34VQDQpNQ|a9*)wScu!}<{oXC87hRPqyrNWpo?#=;1%^D2n2+C* zKKQH;?rWn-@%Y9g%NHG&lHwK9pBfV1a`!TqeU_Fv8s6_(@=RHua7`VYO|!W&WL*x= zIWE9eQaPq3zMaXuf)D0$V`RIZ74f)0P73xpeyk4)-?8j;|K%pD$eq4j2%tL=;&+E91O(2p91K|85b)GQcbRe&u6Ilu@SnE={^{Ix1Eqgv8D z4=w65+&36|;5WhBm$!n*!)ACCwT9Sip#1_z&g~E1kB=AlEhO0lu`Ls@6gw*a)lzc# zKx!fFP%eSBBs)U>xIcQKF(r_$SWD3TD@^^2Ylm=kC*tR+I@X>&SoPZdJ2fT!ysjH% z-U%|SznY8Fhsq7Vau%{Ad^Pvbf3IqVk{M2oD+w>MWimJA@VSZC$QooAO3 zC=DplXdkyl>mSp^$zk7&2+eoGQ6VVh_^E#Z3>tX7Dmi<2aqlM&YBmK&U}m>a%8)LQ z8v+c}a0QtXmyd%Kc2QNGf8TK?_EK4wtRUQ*VDnf5jHa?VvH2K(FDZOjAqYufW8oIZ z31|o~MR~T;ZS!Lz%8M0*iVARJ>_G2BXEF8(}6Dmn_rFV~5NI`lJjp`Mi~g7~P%H zO`S&-)Fngo3VXDMo7ImlaZxY^s!>2|csKca6!|m7)l^M0SQT1_L~K29%x4KV8*xiu zwP=GlyIE9YPSTC0BV`6|#)30=hJ~^aYeq7d6TNfoYUkk-^k0!(3qp(7Mo-$|48d8Z2d zrsfsRM)y$5)0G`fNq!V?qQ+nh0xwFbcp{nhW%vZ?h);=LxvM(pWd9FG$Bg1;@Bv)mKDW>AP{ol zD(R~mLzdDrBv$OSi{E%OD`Ano=F^vwc)rNb*Bg3-o)bbAgYE=M7Gj2OHY{8#pM${_^ zwkU|tnTKawxUF7vqM9UfcQ`V49zg78V%W)$#5ssR}Rj7E&p(4_ib^?9luZPJ%iJTvW&-U$nFYky>KJwHpEHHx zVEC;!ETdkCnO|${Vj#CY>LLut_+c|(hpWk8HRgMGRY%E--%oKh@{KnbQ~0GZd}{b@ z`J2qHBcqqjfHk^q=uQL!>6HSSF3LXL*cCd%opM|k#=xTShX~qcxpHTW*BI!c3`)hQq{@!7^mdUaG7sFsFYnl1%blslM;?B8Q zuifKqUAmR=>33g~#>EMNfdye#rz@IHgpM$~Z7c5@bO@S>MyFE3_F}HVNLnG0TjtXU zJeRWH^j5w_qXb$IGs+E>daTa}XPtrUnnpTRO9NEx4g6uaFEfHP9gW;xZnJi{oqAH~ z5dHS(ch3^hbvkv@u3QPLuWa}ImaElDrmIc%5HN<^bwej}3+?g) z-ai7D&6Iq_P(}k`i^4l?hRLbCb>X9iq2UYMl=`9U9Rf=3Y!gnJbr?eJqy>Zpp)m>Ae zcQ4Qfs&AaE?UDTODcEj#$_n4KeERZHx-I+E5I~E#L_T3WI3cj$5EYR75H7hy%80a8Ej?Y6hv+fR6wHN%_0$-xL!eI}fdjOK7(GdFD%`f%-qY@-i@fTAS&ETI99jUVg8 zslPSl#d4zbOcrgvopvB2c2A6r^pEr&Sa5I5%@1~BpGq`Wo|x=&)WnnQjE+)$^U-wW zr2Kv?XJby(8fcn z8JgPn)2_#-OhZ+;72R6PspMfCVvtLxFHeb7d}fo(GRjm_+R(*?9QRBr+yPF(iPO~ zA4Tp1<0}#fa{v0CU6jz}q9;!3Pew>ikG1qh$5WPRTQZ~ExQH}b1hDuzRS1}65uydS z~Te*3@?o8fih=mZ`iI!hL5iv3?VUBLQv0X zLtu58MIE7Jbm?)NFUZuMN2_~eh_Sqq*56yIo!+d_zr@^c@UwR&*j!fati$W<=rGGN zD$X`$lI%8Qe+KzBU*y3O+;f-Csr4$?3_l+uJ=K@dxOfZ?3APc5_x2R=a^kLFoxt*_ z4)nvvP+(zwlT5WYi!4l7+HKqzmXKYyM9kL5wX$dTSFSN&)*-&8Q{Q$K-})rWMin8S zy*5G*tRYNqk7&+v;@+>~EIQgf_SB;VxRTQFcm5VtqtKZ)x=?-f+%OY(VLrXb^6*aP zP&0Nu@~l2L!aF8i2!N~fJiHyxRl?I1QNjB)`uP_DuaU?2W;{?0#RGKTr2qH5QqdhK zP__ojm4WV^PUgmrV)`~f>(769t3|13DrzdDeXxqN6XA|_GK*;zHU()a(20>X{y-x| z2P6Ahq;o=)Nge`l+!+xEwY`7Q(8V=93A9C+WS^W%p&yR)eiSX+lp)?*7&WSYSh4i> zJa6i5T9o;Cd5z%%?FhB?J{l+t_)c&_f86gZMU{HpOA=-KoU5lIL#*&CZ_66O5$3?# ztgjGLo`Y7bj&eYnK#5x1trB_6tpu4$EomotZLb*9l6P(JmqG`{z$?lNKgq?GAVhkA zvw!oFhLyX=$K=jTAMwDQ)E-8ZW5$X%P2$YB5aq!VAnhwGv$VR&;Ix#fu%xlG{|j_K zbEYL&bx%*YpXcaGZj<{Y{k@rsrFKh7(|saspt?OxQ~oj_6En(&!rTZPa7fLCEU~mA zB7tbVs=-;cnzv*#INgF_9f3OZhp8c5yk!Dy1+`uA7@eJfvd~g34~wKI1PW%h(y&nA zRwMni12AHEw36)C4Tr-pt6s82EJa^8N#bjy??F*rg4fS@?6^MbiY3;7x=gd~G|Hi& zwmG+pAn!aV>>nNfP7-Zn8BLbJm&7}&ZX+$|z5*5{{F}BRSxN=JKZTa#{ut$v0Z0Fs za@UjXo#3!wACv+p9k*^9^n+(0(YKIUFo`@ib@bjz?Mh8*+V$`c%`Q>mrc5bs4aEf4 zh0qtL1qNE|xQ9JrM}qE>X>Y@dQ?%` zBx(*|1FMzVY&~|dE^}gHJ37O9bjnk$d8vKipgcf+As(kt2cbxAR3^4d0?`}}hYO*O z{+L&>G>AYaauAxE8=#F&u#1YGv%`d*v+EyDcU2TnqvRE33l1r}p#Vmcl%n>NrYOqV z2Car_^^NsZ&K=a~bj%SZlfxzHAxX$>=Q|Zi;E0oyfhgGgqe1Sd5-E$8KV9=`!3jWZCb2crb;rvQ##iw}xm7Da za!H${ls5Ihwxkh^D)M<4Yy3bp<-0a+&KfV@CVd9X6Q?v)$R3*rfT@jsedSEhoV(vqv?R1E8oWV;_{l_+_6= zLjV^-bZU$D_ocfSpRxDGk*J>n4G6s-e>D8JK6-gA>aM^Hv8@)txvKMi7Pi#DS5Y?r zK0%+L;QJdrIPXS2 ztjWAxkSwt2xG$L)Zb7F??cjs!KCTF+D{mZ5e0^8bdu_NLgFHTnO*wx!_8#}NO^mu{FaYeCXGjnUgt_+B-Ru!2_Ue-0UPg2Y)K3phLmR<4 zqUCWYX!KDU!jYF6c?k;;vF@Qh^q(PWwp1ez#I+0>d7V(u_h|L+kX+MN1f5WqMLn!L z!c(pozt7tRQi&duH8n=t-|d)c^;%K~6Kpyz(o53IQ_J+aCapAif$Ek#i0F9U>i+94 zFb=OH5(fk-o`L(o|DyQ(hlozl*2cu#)Y(D*zgNMi1Z!DTex#w#)x(8A-T=S+eByJW z%-k&|XhdZOWjJ&(FTrZNWRm^pHEot_MRQ_?>tKQ&MB~g(&D_e>-)u|`Ot(4j=UT6? zQ&YMi2UnCKlBpwltP!}8a2NJ`LlfL=k8SQf69U)~=G;bq9<2GU&Q#cHwL|o4?ah1` z;fG)%t0wMC;DR?^!jCoKib_iiIjsxCSxRUgJDCE%0P;4JZhJCy)vR1%zRl>K?V6#) z2lDi*W3q9rA zo;yvMujs+)a&00~W<-MNj=dJ@4%tccwT<@+c$#CPR%#aE#Dra+-5eSDl^E>is2v^~ z8lgRwkpeU$|1LW4yFwA{PQ^A{5JY!N5PCZ=hog~|FyPPK0-i;fCl4a%1 z?&@&E-)b4cK)wjXGq|?Kqv0s7y~xqvSj-NpOImt{Riam*Z!wz-coZIMuQU>M%6ben z>P@#o^W;fizVd#?`eeEPs#Gz^ySqJn+~`Pq%-Ee6*X+E>!PJGU#rs6qu0z5{+?`-N zxf1#+JNk7e6AoJTdQwxs&GMTq?Djch_8^xL^A;9XggtGL>!@0|BRuIdE&j$tzvt7I zr@I@0<0io%lpF697s1|qNS|BsA>!>-9DVlgGgw2;;k;=7)3+&t!);W3ulPgR>#JiV zUerO;WxuJqr$ghj-veVGfKF?O7si#mzX@GVt+F&atsB@NmBoV4dK|!owGP005$7LN7AqCG(S+={YA- zn#I{UoP_$~Epc=j78{(!2NLN)3qSm-1&{F&1z4Dz&7Mj_+SdlR^Q5{J=r822d4A@?Rj~xATaWewHUOus{*C|KoH`G zHB8SUT06GpSt)}cFJ18!$Kp@r+V3tE_L^^J%9$&fcyd_AHB)WBghwqBEWW!oh@StV zDrC?ttu4#?Aun!PhC4_KF1s2#kvIh~zds!y9#PIrnk9BWkJpq}{Hlqi+xPOR&A1oP zB0~1tV$Zt1pQuHpJw1TAOS=3$Jl&n{n!a+&SgYVe%igUtvE>eHqKY0`e5lwAf}2x( zP>9Wz+9uirp7<7kK0m2&Y*mzArUx%$CkV661=AIAS=V=|xY{;$B7cS5q0)=oq0uXU z_roo90&gHSfM6@6kmB_FJZ)3y_tt0}7#PA&pWo@_qzdIMRa-;U*Dy>Oo#S_n61Fn! z%mrH%tRmvQvg%UqN_2(C#LSxgQ>m}FKLGG=uqJQuSkk=S@c~QLi4N+>lr}QcOuP&% zQCP^cRk&rk-@lpa0^Lcvdu`F*qE)-0$TnxJlwZf|dP~s8cjhL%>^+L~{umxl5Xr6@ z^7zVKiN1Xg;-h+kr4Yt2BzjZs-Mo54`pDbLc}fWq{34=6>U9@sBP~iWZE`+FhtU|x zTV}ajn*Hc}Y?3agQ+bV@oIRm=qAu%|zE;hBw7kCcDx{pm!_qCxfPX3sh5^B$k_2d` z6#rAeUZC;e-LuMZ-f?gHeZogOa*mE>ffs+waQ+fQl4YKoAyZii_!O0;h55EMzD{;) z8lSJvv((#UqgJ?SCQFqJ-UU?2(0V{;7zT3TW`u6GH6h4m3}SuAAj_K(raGBu>|S&Q zZGL?r9@caTbmRm7p=&Tv?Y1)60*9At38w)$(1c?4cpFY2RLyw9c<{OwQE{b@WI}FQ zTT<2HOF4222d%k70yL~x_d#6SNz`*%@4++8gYQ8?yq0T@w~bF@aOHL2)T4xj`AVps9k z?m;<2ClJh$B6~fOYTWIV*T9y1BpB1*C?dgE{%lVtIjw>4MK{wP6OKTb znbPWrkZjYCbr`GGa%Xo0h;iFPNJBI3fK5`wtJV?wq_G<_PZ<`eiKtvN$IKfyju*^t zXc}HNg>^PPZ16m6bfTpmaW5=qoSsj>3)HS}teRa~qj+Y}mGRE?cH!qMDBJ8 zJB!&-=MG8Tb;V4cZjI_#{>ca0VhG_P=j0kcXVX5)^Sdpk+LKNv#yhpwC$k@v^Am&! z_cz2^4Cc{_BC!K#zN!KEkPzviUFPJ^N_L-kHG6}(X#$>Q=9?!{$A(=B3)P?PkxG9gs#l! zo6TOHo$F|IvjTC3MW%XrDoc7;m-6wb9mL(^2(>PQXY53hE?%4FW$rTHtN`!VgH72U zRY)#?Y*pMA<)x3B-&fgWQ(TQ6S6nUeSY{9)XOo_k=j$<*mA=f+ghSALYwBw~!Egn!jtjubOh?6Cb-Zi3IYn*fYl()^3u zRiX0I{5QaNPJ9w{yh4(o#$geO7b5lSh<5ZaRg9_=aFdZjxjXv(_SCv^v-{ZKQFtAA}kw=GPC7l81GY zeP@0Da{aR#{6`lbI0ON0y#K=t|L*}MG_HSl$e{U;v=BSs{SU3(e*qa(l%rD;(zM^3 zrRgN3M#Sf(Cr9>v{FtB`8JBK?_zO+~{H_0$lLA!l{YOs9KQd4Zt<3*Ns7dVbT{1Ut z?N9{XkN(96?r(4BH~3qeiJ_CAt+h1}O_4IUF$S(5EyTyo=`{^16P z=VhDY!NxkDukQz>T`0*H=(D3G7Np*2P`s(6M*(*ZJa;?@JYj&_z`d5bap=KK37p3I zr5#`%aC)7fUo#;*X5k7g&gQjxlC9CF{0dz*m2&+mf$Sc1LnyXn9lpZ!!Bl!@hnsE5px};b-b-`qne0Kh;hziNC zXV|zH%+PE!2@-IrIq!HM2+ld;VyNUZiDc@Tjt|-1&kq}>muY;TA3#Oy zWdYGP3NOZWSWtx6?S6ES@>)_Yz%%nLG3P>Z7`SrhkZ?shTfrHkYI;2zAn8h65wV3r z^{4izW-c9!MTge3eN=~r5aTnz6*6l#sD68kJ7Nv2wMbL~Ojj0H;M`mAvk*`Q!`KI? z7nCYBqbu$@MSNd+O&_oWdX()8Eh|Z&v&dJPg*o-sOBb2hriny)< zd(o&&kZM^NDtV=hufp8L zCkKu7)k`+czHaAU567$?GPRGdkb4$37zlIuS&<&1pgArURzoWCbyTEl9OiXZBn4p<$48-Gekh7>e)v*?{9xBt z=|Rx!@Y3N@ffW5*5!bio$jhJ7&{!B&SkAaN`w+&3x|D^o@s{ZAuqNss8K;211tUWIi1B!%-ViYX+Ys6w)Q z^o1{V=hK#+tt&aC(g+^bt-J9zNRdv>ZYm9KV^L0y-yoY7QVZJ_ivBS02I|mGD2;9c zR%+KD&jdXjPiUv#t1VmFOM&=OUE2`SNm4jm&a<;ZH`cYqBZoAglCyixC?+I+}*ScG#;?SEAFob{v0ZKw{`zw*tX}<2k zoH(fNh!>b5w8SWSV}rQ*E24cO=_eQHWy8J!5;Y>Bh|p;|nWH|nK9+ol$k`A*u*Y^Uz^%|h4Owu}Cb$zhIxlVJ8XJ0xtrErT zcK;34CB;ohd|^NfmVIF=XlmB5raI}nXjFz;ObQ4Mpl_`$dUe7sj!P3_WIC~I`_Xy@ z>P5*QE{RSPpuV=3z4p3}dh>Dp0=We@fdaF{sJ|+_E*#jyaTrj-6Y!GfD@#y@DUa;& zu4Iqw5(5AamgF!2SI&WT$rvChhIB$RFFF|W6A>(L9XT{0%DM{L`knIQPC$4F`8FWb zGlem_>>JK-Fib;g*xd<-9^&_ue95grYH>5OvTiM;#uT^LVmNXM-n8chJBD2KeDV7t zbnv3CaiyN>w(HfGv86K5MEM{?f#BTR7**smpNZ}ftm+gafRSt=6fN$(&?#6m3hF!>e$X)hFyCF++Qvx(<~q3esTI zH#8Sv!WIl2<&~=B)#sz1x2=+KTHj=0v&}iAi8eD=M->H|a@Qm|CSSzH#eVIR3_Tvu zG8S**NFbz%*X?DbDuP(oNv2;Lo@#_y4k$W+r^#TtJ8NyL&&Rk;@Q}~24`BB)bgwcp z=a^r(K_NEukZ*|*7c2JKrm&h&NP)9<($f)eTN}3|Rt`$5uB0|!$Xr4Vn#i;muSljn zxG?zbRD(M6+8MzGhbOn%C`M#OcRK!&ZHihwl{F+OAnR>cyg~No44>vliu$8^T!>>*vYQJCJg=EF^lJ*3M^=nGCw`Yg@hCmP(Gq^=eCEE1!t-2>%Al{w@*c% zUK{maww*>K$tu;~I@ERb9*uU@LsIJ|&@qcb!&b zsWIvDo4#9Qbvc#IS%sV1_4>^`newSxEcE08c9?rHY2%TRJfK2}-I=Fq-C)jc`gzV( zCn?^noD(9pAf2MP$>ur0;da`>Hr>o>N@8M;X@&mkf;%2A*2CmQBXirsJLY zlX21ma}mKH_LgYUM-->;tt;6F?E5=fUWDwQhp*drQ%hH0<5t2m)rFP%=6aPIC0j$R znGI0hcV~}vk?^&G`v~YCKc7#DrdMM3TcPBmxx#XUC_JVEt@k=%3-+7<3*fTcQ>f~?TdLjv96nb66xj=wVQfpuCD(?kzs~dUV<}P+Fpd)BOTO^<*E#H zeE80(b~h<*Qgez(iFFOkl!G!6#9NZAnsxghe$L=Twi^(Q&48 zD0ohTj)kGLD){xu%pm|}f#ZaFPYpHtg!HB30>F1c=cP)RqzK2co`01O5qwAP zUJm0jS0#mci>|Nu4#MF@u-%-4t>oUTnn_#3K09Hrwnw13HO@9L;wFJ*Z@=gCgpA@p zMswqk;)PTXWuMC-^MQxyNu8_G-i3W9!MLd2>;cM+;Hf&w| zLv{p*hArp9+h2wsMqT5WVqkkc0>1uokMox{AgAvDG^YJebD-czexMB!lJKWllLoBI zetW2;;FKI1xNtA(ZWys!_un~+834+6y|uV&Lo%dKwhcoDzRADYM*peh{o`-tHvwWIBIXW`PKwS3|M>CW37Z2dr!uJWNFS5UwY4;I zNIy1^sr+@8Fob%DHRNa&G{lm?KWU7sV2x9(Ft5?QKsLXi!v6@n&Iyaz5&U*|hCz+d z9vu60IG<v6+^ZmBs_aN!}p|{f(ikVl&LcB+UY;PPz* zj84Tm>g5~-X=GF_4JrVmtEtm=3mMEL1#z+pc~t^Iify^ft~cE=R0TymXu*iQL+XLX zdSK$~5pglr3f@Lrcp`>==b5Z6r7c=p=@A5nXNacsPfr(5m;~ks@*Wu7A z%WyY$Pt*RAKHz_7cghHuQqdU>hq$vD?plol_1EU(Fkgyo&Q2&2e?FT3;H%!|bhU~D z>VX4-6}JLQz8g3%Bq}n^NhfJur~v5H0dbB^$~+7lY{f3ES}E?|JnoLsAG%l^%eu_PM zEl0W(sbMRB3rFeYG&tR~(i2J0)RjngE`N_Jvxx!UAA1mc7J>9)`c=`}4bVbm8&{A` z3sMPU-!r-8de=P(C@7-{GgB<5I%)x{WfzJwEvG#hn3ict8@mexdoTz*(XX!C&~}L* z^%3eYQ8{Smsmq(GIM4d5ilDUk{t@2@*-aevxhy7yk(wH?8yFz%gOAXRbCYzm)=AsM z?~+vo2;{-jkA%Pqwq&co;|m{=y}y2lN$QPK>G_+jP`&?U&Ubq~T`BzAj1TlC`%8+$ zzdwNf<3suPnbh&`AI7RAYuQ<#!sD|A=ky2?hca{uHsB|0VqShI1G3lG5g}9~WSvy4 zX3p~Us^f5AfXlBZ0hA;mR6aj~Q8yb^QDaS*LFQwg!!<|W!%WX9Yu}HThc7>oC9##H zEW`}UQ%JQ38UdsxEUBrA@=6R-v1P6IoIw8$8fw6F{OSC7`cOr*u?p_0*Jvj|S)1cd z-9T);F8F-Y_*+h-Yt9cQQq{E|y^b@r&6=Cd9j0EZL}Pj*RdyxgJentY49AyC@PM<< zl&*aq_ubX%*pqUkQ^Zsi@DqhIeR&Ad)slJ2g zmeo&+(g!tg$z1ao1a#Qq1J022mH4}y?AvWboI4H028;trScqDQrB36t!gs|uZS9}KG0}DD$ zf2xF}M*@VJSzEJ5>ucf+L_AtN-Ht=34g&C?oPP>W^bwoigIncKUyf61!ce!2zpcNT zj&;rPGI~q2!Sy>Q7_lRX*DoIs-1Cei=Cd=+Xv4=%bn#Yqo@C=V`|QwlF0Y- zONtrwpHQ##4}VCL-1ol(e<~KU9-ja^kryz!g!})y-2S5z2^gE$Isj8l{%tF=Rzy`r z^RcP7vu`jHgHLKUE957n3j+BeE(bf;f)Zw($XaU6rZ26Upl#Yv28=8Y`hew{MbH>* z-sGI6dnb5D&dUCUBS`NLAIBP!Vi!2+~=AU+)^X^IpOEAn#+ab=`7c z%7B|mZ>wU+L;^&abXKan&N)O;=XI#dTV|9OMYxYqLbtT#GY8PP$45Rm2~of+J>>HIKIVn(uQf-rp09_MwOVIp@6!8bKV(C#(KxcW z;Pesq(wSafCc>iJNV8sg&`!g&G55<06{_1pIoL`2<7hPvAzR1+>H6Rx0Ra%4j7H-<-fnivydlm{TBr06;J-Bq8GdE^Amo)ptV>kS!Kyp*`wUx=K@{3cGZnz53`+C zLco1jxLkLNgbEdU)pRKB#Pq(#(Jt>)Yh8M?j^w&RPUueC)X(6`@@2R~PV@G(8xPwO z^B8^+`qZnQr$8AJ7<06J**+T8xIs)XCV6E_3W+al18!ycMqCfV>=rW0KBRjC* zuJkvrv;t&xBpl?OB3+Li(vQsS(-TPZ)Pw2>s8(3eF3=n*i0uqv@RM^T#Ql7(Em{(~%f2Fw|Reg@eSCey~P zBQlW)_DioA*yxxDcER@_=C1MC{UswPMLr5BQ~T6AcRyt0W44ffJG#T~Fk}wU^aYoF zYTayu-s?)<`2H(w+1(6X&I4?m3&8sok^jpXBB<|ZENso#?v@R1^DdVvKoD?}3%@{}}_E7;wt9USgrfR3(wabPRhJ{#1es81yP!o4)n~CGsh2_Yj2F^z|t zk((i&%nDLA%4KFdG96pQR26W>R2^?C1X4+a*hIzL$L=n4M7r$NOTQEo+k|2~SUI{XL{ynLSCPe%gWMMPFLO{&VN2pom zBUCQ(30qj=YtD_6H0-ZrJ46~YY*A;?tmaGvHvS^H&FXUG4)%-a1K~ly6LYaIn+4lG zt=wuGLw!%h=Pyz?TP=?6O-K-sT4W%_|Nl~;k~YA^_`gqfe{Xw=PWn#9f1mNz)sFuL zJbrevo(DPgpirvGMb6ByuEPd=Rgn}fYXqeUKyM+!n(cKeo|IY%p!#va6`D8?A*{u3 zEeWw0*oylJ1X!L#OCKktX2|>-z3#>`9xr~azOH+2dXHRwdfnpri9|xmK^Q~AuY!Fg z`9Xx?hxkJge~)NVkPQ(VaW(Ce2pXEtgY*cL8i4E)mM(iz_vdm|f@%cSb*Lw{WbShh41VGuplex9E^VvW}irx|;_{VK=N_WF39^ zH4<*peWzgc)0UQi4fBk2{FEzldDh5+KlRd!$_*@eYRMMRb1gU~9lSO_>Vh-~q|NTD zL}X*~hgMj$*Gp5AEs~>Bbjjq7G>}>ki1VxA>@kIhLe+(EQS0mjNEP&eXs5)I;7m1a zmK0Ly*!d~Dk4uxRIO%iZ!1-ztZxOG#W!Q_$M7_DKND0OwI+uC;PQCbQ#k#Y=^zQve zTZVepdX>5{JSJb;DX3%3g42Wz2D@%rhIhLBaFmx#ZV8mhya}jo1u{t^tzoiQy=jJp zjY2b7D2f$ZzJx)8fknqdD6fd5-iF8e(V}(@xe)N=fvS%{X$BRvW!N3TS8jn=P%;5j zShSbzsLs3uqycFi3=iSvqH~}bQn1WQGOL4?trj(kl?+q2R23I42!ipQ&`I*&?G#i9 zWvNh8xoGKDt>%@i0+}j?Ykw&_2C4!aYEW0^7)h2Hi7$;qgF3;Go?bs=v)kHmvd|`R z%(n94LdfxxZ)zh$ET8dH1F&J#O5&IcPH3=8o;%>OIT6w$P1Yz4S!}kJHNhMQ1(prc zM-jSA-7Iq=PiqxKSWb+YbLB-)lSkD6=!`4VL~`ExISOh2ud=TI&SKfR4J08Bad&rj zcXxMpcNgOB?w$~L7l^wPcXxw$0=$oV?)`I44)}b#ChS`_lBQhvb6ks?HDr3tFgkg&td19?b8=!sETXtp=&+3T$cCwZe z0nAET-7561gsbBws$TVjP7QxY(NuBYXVn9~9%vyN-B#&tJhWgtL1B<%BTS*-2$xB` zO)cMDHoWsm%JACZF--Pa7oP;f!n%p`*trlpvZ!HKoB={l+-(8O;;eYv2A=ra z3U7rSMCkP_6wAy`l|Se(&5|AefXvV1E#XA(LT!% zjj4|~xlZ-kPLNeQLFyXb%$K}YEfCBvHA-Znw#dZSI6V%3YD{Wj2@utT5Hieyofp6Qi+lz!u)htnI1GWzvQsA)baEuw9|+&(E@p8M+#&fsX@Kf`_YQ>VM+40YLv`3-(!Z7HKYg@+l00WGr779i-%t`kid%e zDtbh8UfBVT3|=8FrNian@aR3*DTUy&u&05x%(Lm3yNoBZXMHWS7OjdqHp>cD>g!wK z#~R{1`%v$IP;rBoP0B0P><;dxN9Xr+fp*s_EK3{EZ94{AV0#Mtv?;$1YaAdEiq5)g zYME;XN9cZs$;*2p63Q9^x&>PaA1p^5m7|W?hrXp2^m;B@xg0bD?J;wIbm6O~Nq^^K z2AYQs@7k)L#tgUkTOUHsh&*6b*EjYmwngU}qesKYPWxU-z_D> zDWr|K)XLf_3#k_9Rd;(@=P^S^?Wqlwert#9(A$*Y$s-Hy)BA0U0+Y58zs~h=YtDKxY0~BO^0&9{?6Nny;3=l59(6ec9j(79M?P1cE zex!T%$Ta-KhjFZLHjmPl_D=NhJULC}i$}9Qt?nm6K6-i8&X_P+i(c*LI3mtl3 z*B+F+7pnAZ5}UU_eImDj(et;Khf-z^4uHwrA7dwAm-e4 zwP1$Ov3NP5ts+e(SvM)u!3aZMuFQq@KE-W;K6 zag=H~vzsua&4Sb$4ja>&cSJ)jjVebuj+?ivYqrwp3!5>ul`B*4hJGrF;!`FaE+wKo z#};5)euvxC1zX0-G;AV@R(ZMl=q_~u8mQ5OYl;@BAkt)~#PynFX#c1K zUQ1^_N8g+IZwUl*n0Bb-vvliVtM=zuMGU-4a8|_8f|2GEd(2zSV?aSHUN9X^GDA8M zgTZW06m*iAy@7l>F3!7+_Y3mj^vjBsAux3$%U#d$BT^fTf-7{Y z_W0l=7$ro5IDt7jp;^cWh^Zl3Ga1qFNrprdu#g=n9=KH!CjLF#ucU5gy6*uASO~|b z7gcqm90K@rqe({P>;ww_q%4}@bq`ST8!0{V08YXY)5&V!>Td)?j7#K}HVaN4FU4DZ z%|7OppQq-h`HJ;rw-BAfH* z1H$ufM~W{%+b@9NK?RAp-$(P0N=b<(;wFbBN0{u5vc+>aoZ|3&^a866X@el7E8!E7 z=9V(Ma**m_{DKZit2k;ZOINI~E$|wO99by=HO{GNc1t?nl8soP@gxk8)WfxhIoxTP zoO`RA0VCaq)&iRDN9yh_@|zqF+f07Esbhe!e-j$^PS57%mq2p=+C%0KiwV#t^%_hH zoO?{^_yk5x~S)haR6akK6d|#2TN& zfWcN zc7QAWl)E9`!KlY>7^DNw$=yYmmRto>w0L(~fe?|n6k2TBsyG@sI)goigj=mn)E)I* z4_AGyEL7?(_+2z=1N@D}9$7FYdTu;%MFGP_mEJXc2OuXEcY1-$fpt8m_r2B|<~Xfs zX@3RQi`E-1}^9N{$(|YS@#{ZWuCxo)91{k>ESD54g_LYhm~vlOK_CAJHeYFfuIVB^%cqCfvpy#sU8Do8u}# z>>%PLKOZ^+$H54o@brtL-hHorSKcsjk_ZibBKBgyHt~L z=T6?e0oLX|h!Z3lbkPMO27MM?xn|uZAJwvmX?Yvp#lE3sQFY)xqet>`S2Y@1t)Z*& z;*I3;Ha8DFhk=YBt~{zp=%%*fEC}_8?9=(-k7HfFeN^GrhNw4e?vx*#oMztnO*&zY zmRT9dGI@O)t^=Wj&Og1R3b%(m*kb&yc;i`^-tqY9(0t!eyOkH<$@~1lXmm!SJllE_ zr~{a&w|8*LI>Z^h!m%YLgKv06Js7j7RaoX}ZJGYirR<#4Mghd{#;38j3|V+&=ZUq#1$ zgZb-7kV)WJUko?{R`hpSrC;w2{qa`(Z4gM5*ZL`|#8szO=PV^vpSI-^K_*OQji^J2 zZ_1142N}zG$1E0fI%uqHOhV+7%Tp{9$bAR=kRRs4{0a`r%o%$;vu!_Xgv;go)3!B#;hC5qD-bcUrKR&Sc%Zb1Y($r78T z=eG`X#IpBzmXm(o6NVmZdCQf6wzqawqI63v@e%3TKuF!cQ#NQbZ^?6K-3`_b=?ztW zA>^?F#dvVH=H-r3;;5%6hTN_KVZ=ps4^YtRk>P1i>uLZ)Ii2G7V5vy;OJ0}0!g>j^ z&TY&E2!|BDIf1}U(+4G5L~X6sQ_e7In0qJmWYpn!5j|2V{1zhjZt9cdKm!we6|Pp$ z07E+C8=tOwF<<}11VgVMzV8tCg+cD_z?u+$sBjwPXl^(Ge7y8-=c=fgNg@FxI1i5Y-HYQMEH z_($je;nw`Otdhd1G{Vn*w*u@j8&T=xnL;X?H6;{=WaFY+NJfB2(xN`G)LW?4u39;x z6?eSh3Wc@LR&yA2tJj;0{+h6rxF zKyHo}N}@004HA(adG~0solJ(7>?LoXKoH0~bm+xItnZ;3)VJt!?ue|~2C=ylHbPP7 zv2{DH()FXXS_ho-sbto)gk|2V#;BThoE}b1EkNYGT8U#0ItdHG>vOZx8JYN*5jUh5Fdr9#12^ zsEyffqFEQD(u&76zA^9Jklbiz#S|o1EET$ujLJAVDYF znX&4%;vPm-rT<8fDutDIPC@L=zskw49`G%}q#l$1G3atT(w70lgCyfYkg7-=+r7$%E`G?1NjiH)MvnKMWo-ivPSQHbk&_l5tedNp|3NbU^wk0SSXF9ohtM zUqXiOg*8ERKx{wO%BimK)=g^?w=pxB1Vu_x<9jKOcU7N;(!o3~UxyO+*ZCw|jy2}V*Z22~KhmvxoTszc+#EMWXTM6QF*ks% zW47#2B~?wS)6>_ciKe1Fu!@Tc6oN7e+6nriSU;qT7}f@DJiDF@P2jXUv|o|Wh1QPf zLG31d>@CpThA+Ex#y)ny8wkC4x-ELYCXGm1rFI=1C4`I5qboYgDf322B_Nk@#eMZ% znluCKW2GZ{r9HR@VY`>sNgy~s+D_GkqFyz6jgXKD)U|*eKBkJRRIz{gm3tUd*yXmR z(O4&#ZA*us6!^O*TzpKAZ#}B5@}?f=vdnqnRmG}xyt=)2o%<9jj>-4wLP1X-bI{(n zD9#|rN#J;G%LJ&$+Gl2eTRPx6BQC6Uc~YK?nMmktvy^E8#Y*6ZJVZ>Y(cgsVnd!tV z!%twMNznd)?}YCWyy1-#P|2Fu%~}hcTGoy>_uawRTVl=(xo5!%F#A38L109wyh@wm zdy+S8E_&$Gjm=7va-b7@Hv=*sNo0{i8B7=n4ex-mfg`$!n#)v@xxyQCr3m&O1Jxg! z+FXX^jtlw=utuQ+>Yj$`9!E<5-c!|FX(~q`mvt6i*K!L(MHaqZBTtuSA9V~V9Q$G? zC8wAV|#XY=;TQD#H;;dcHVb9I7Vu2nI0hHo)!_{qIa@|2}9d ztpC*Q{4Py~2;~6URN^4FBCBip`QDf|O_Y%iZyA0R`^MQf$ce0JuaV(_=YA`knEMXw zP6TbjYSGXi#B4eX=QiWqb3bEw-N*a;Yg?dsVPpeYFS*&AsqtW1j2D$h$*ZOdEb$8n0 zGET4Igs^cMTXWG{2#A7w_usx=KMmNfi4oAk8!MA8Y=Rh9^*r>jEV(-{I0=rc);`Y) zm+6KHz-;MIy|@2todN&F+Yv1e&b&ZvycbTHpDoZ>FIiUn+M-=%A2C(I*^Yx@VKf(Z zxJOny&WoWcyKodkeN^5))aV|-UBFw{?AGo?;NNFFcKzk+6|gYfA#FR=y@?;3IoQ zUMI=7lwo9gV9fRvYi}Nd)&gQw7(K3=a0#p27u6Q)7JlP#A)piUUF8B3Li&38Xk$@| z9OR+tU~qgd3T3322E))eV)hAAHYIj$TmhH#R+C-&E-}5Qd{3B}gD{MXnsrS;{Erv1 z6IyQ=S2qD>Weqqj#Pd65rDSdK54%boN+a?=CkR|agnIP6;INm0A*4gF;G4PlA^3%b zN{H%#wYu|!3fl*UL1~f+Iu|;cqDax?DBkZWSUQodSDL4Es@u6zA>sIm>^Aq-&X#X8 zI=#-ucD|iAodfOIY4AaBL$cFO@s(xJ#&_@ZbtU+jjSAW^g;_w`FK%aH_hAY=!MTjI zwh_OEJ_25zTQv$#9&u0A11x_cGd92E74AbOrD`~f6Ir9ENNQAV2_J2Ig~mHWhaO5a zc>fYG$zke^S+fBupw+klDkiljJAha z6DnTemhkf>hv`8J*W_#wBj-2w(cVtXbkWWtE(3j@!A-IfF?`r$MhVknTs3D1N`rYN zKth9jZtX#>v#%U@^DVN!;ni#n1)U&H_uB{6pcq7$TqXJX!Q0P7U*JUZyclb~)l*DS zOLpoQfW_3;a0S$#V0SOwVeeqE$Hd^L`$;l_~2giLYd?7!gUYIpOs!jqSL~pI)4`YuB_692~A z^T#YYQ_W3Rakk}$SL&{`H8mc{>j+3eKprw6BK`$vSSIn;s31M~YlJLApJ)+Gi1{^- zw96WnT9M0Vr_D=e=a}${raR{(35Q!g+8`}vOFj1e&Or(_wp2U2aVQP0_jP57 z2(R4E(E$n!xl<}Zx38wO;27wuQ`P#_j!}L2 z2qr;As4D4n2X$-Jd_-!fsbu_D(64i;c4cJnP576x_>Q4WNushFwkBV!kVd(AYFXe{ zaqO5`Qfr!#ETmE(B;u_&FITotv~W}QYFCI!&ENKIb1p4fg*Yv1)EDMb==EjHHWM#{ zGMpqb2-LXdHB@D~pE3|+B392Gh4q)y9jBd$a^&cJM60VEUnLtHQD5i-X6PVF>9m_k zDvG3P(?CzdaIrC8s4cu~N9MEb!Tt(g*GK~gIp1Gyeaw3b7#YPx_1T6i zRi#pAMr~PJKe9P~I+ARa$a!K~)t(4LaVbjva1yd;b1Yz2$7MMc`aLmMl(a^DgN(u? zq2o9&Gif@Tq~Yq+qDfx^F*nCnpuPv%hRFc$I!p74*quLt^M}D_rwl10uMTr!)(*=7 zSC5ea@#;l(h87k4T4x)(o^#l76P-GYJA(pOa&F9YT=fS<*O{4agzba^dIrh0hjls<~APlIz9{ zgRY{OMv2s|`;VCoYVj?InYoq^QWuA&*VDyOn@pPvK8l~g#1~~MGVVvtLDt}>id_Z` zn(ihfL?Y}Y4YX335m*Xx(y+bbukchHrM zycIGp#1*K3$!(tgTsMD2VyUSg^yvCwB8*V~sACE(yq2!MS6f+gsxv^GR|Q7R_euYx z&X+@@H?_oQddGxJYS&ZG-9O(X+l{wcw;W7srpYjZZvanY(>Q1utSiyuuonkjh5J0q zGz6`&meSuxixIPt{UoHVupUbFKIA+3V5(?ijn}(C(v>=v?L*lJF8|yRjl-m#^|krg zLVbFV6+VkoEGNz6he;EkP!Z6|a@n8?yCzX9>FEzLnp21JpU0x!Qee}lwVKA})LZJq zlI|C??|;gZ8#fC3`gzDU%7R87KZyd)H__0c^T^$zo@TBKTP*i{)Gp3E0TZ}s3mKSY zix@atp^j#QnSc5K&LsU38#{lUdwj%xF zcx&l^?95uq9on1m*0gp$ruu||5MQo)XaN>|ngV5Jb#^wWH^5AdYcn_1>H~XtNwJd3 zd9&?orMSSuj=lhO?6)Ay7;gdU#E}pTBa5wFu`nejq##Xd71BHzH2XqLA5 zeLEo;9$}~u0pEu@(?hXB_l;{jQ=7m?~mwj-ME~Tw-OHPrR7K2Xq9eCNwQO$hR z3_A?=`FJctNXA#yQEorVoh{RWxJbdQga zU%K##XEPgy?E|K(=o#IPgnbk7E&5%J=VHube|2%!Qp}@LznjE%VQhJ?L(XJOmFVY~ zo-az+^5!Ck7Lo<7b~XC6JFk>17*_dY;=z!<0eSdFD2L?CSp_XB+?;N+(5;@=_Ss3& zXse>@sA7hpq;IAeIp3hTe9^$DVYf&?)={zc9*hZAV)|UgKoD!1w{UVo8D)Htwi8*P z%#NAn+8sd@b{h=O)dy9EGKbpyDtl@NBZw0}+Wd=@65JyQ2QgU}q2ii;ot1OsAj zUI&+Pz+NvuRv#8ugesT<<@l4L$zso0AQMh{we$tkeG*mpLmOTiy8|dNYhsqhp+q*yfZA`Z)UC*(oxTNPfOFk3RXkbzAEPofVUy zZ3A%mO?WyTRh@WdXz+zD!ogo}gbUMV!YtTNhr zrt@3PcP%5F;_SQ>Ui`Gq-lUe&taU4*h2)6RDh@8G1$o!){k~3)DT87%tQeHYdO?B` zAmoJvG6wWS?=0(Cj?Aqj59`p(SIEvYyPGJ^reI z`Hr?3#U2zI7k0=UmqMD35l`>3xMcWlDv$oo6;b`dZq3d!~)W z=4Qk)lE8&>#HV>?kRLOHZYz83{u7?^KoXmM^pazj8`7OwQ=5I!==; zA!uN`Q#n=Drmzg}@^nG!mJp9ml3ukWk96^6*us*;&>s+7hWfLXtl?a}(|-#=P12>A zon1}yqh^?9!;on?tRd6Fk0knQSLl4vBGb87A_kJNDGyrnpmn48lz_%P{* z_G*3D#IR<2SS54L5^h*%=)4D9NPpji7DZ5&lHD|99W86QN_(|aJ<5C~PX%YB`Qt_W z>jF_Os@kI6R!ub4n-!orS(G6~mKL7()1g=Lf~{D!LR7#wRHfLxTjYr{*c{neyhz#U zbm@WBKozE+kTd+h-mgF+ELWqTKin57P;0b){ zii5=(B%S(N!Z=rAFGnM6iePtvpxB_Q9-oq_xH!URn2_d-H~i;lro8r{-g!k-Ydb6_w5K@FOV?zPF_hi z%rlxBv$lQi%bjsu^7KT~@u#*c$2-;AkuP)hVEN?W5MO8C9snj*EC&|M!aK6o12q3+ z8e?+dH17E!A$tRlbJW~GtMDkMPT=m1g-v67q{sznnWOI$`g(8E!Pf!#KpO?FETxLK z2b^8^@mE#AR1z(DT~R3!nnvq}LG2zDGoE1URR=A2SA z%lN$#V@#E&ip_KZL}Q6mvm(dsS?oHoRf8TWL~1)4^5<3JvvVbEsQqSa3(lF*_mA$g zv`LWarC79G)zR0J+#=6kB`SgjQZ2460W zN%lZt%M@=EN>Wz4I;eH>C0VnDyFe)DBS_2{h6=0ZJ*w%s)QFxLq+%L%e~UQ0mM9ud zm&|r){_<*Om%vlT(K9>dE(3AHjSYro5Y1I?ZjMqWyHzuCE0nyCn`6eq%MEt(aY=M2rIzHeMds)4^Aub^iTIT|%*izG4YH;sT`D9MR(eND-SB+e66LZT z2VX)RJsn${O{D48aUBl|(>ocol$1@glsxisc#GE*=DXHXA?|hJT#{;X{i$XibrA}X zFHJa+ssa2$F_UC(o2k2Z0vwx%Wb(<6_bdDO#=a$0gK2NoscCr;vyx?#cF)JjM%;a| z$^GIlIzvz%Hx3WVU481}_e4~aWcyC|j&BZ@uWW1`bH1y9EWXOxd~f-VE5DpueNofN zv7vZeV<*!A^|36hUE;`#x%MHhL(~?eZ5fhA9Ql3KHTWoAeO-^7&|2)$IcD1r5X#-u zN~N0$6pHPhop@t1_d`dO3#TC0>y5jm>8;$F5_A2& zt#=^IDfYv?JjPPTPNx2TL-Lrl82VClQSLWW_$3=XPbH}xM34)cyW5@lnxy=&h%eRq zv29&h^fMoxjsDnmua(>~OnX{Cq!7vM0M4Mr@_18|YuSKPBKUTV$s^So zc}JlAW&bVz|JY#Eyup6Ny{|P_s0Pq;5*tinH+>5Xa--{ z2;?2PBs((S4{g=G`S?B3Ien`o#5DmUVwzpGuABthYG~OKIY`2ms;33SN9u^I8i_H5`BQ%yOfW+N3r|ufHS_;U;TWT5z;b14n1gX%Pn`uuO z6#>Vl)L0*8yl|#mICWQUtgzeFp9$puHl~m&O+vj3Ox#SxQUa?fY*uK?A;00RiFg(G zK?g=7b5~U4QIK`C*um%=Sw=OJ1eeaV@WZ%hh-3<=lR#(Xesk%?)l4p(EpTwPvN99V@TT)!A8SeFTV+frN=r|5l?K#odjijx2nFgc3kI zC$hVs1S-!z9>xn9MZcRk0YXdYlf~8*LfH$IHKD59H&gLz%6 z#mAYSRJufbRi~LRadwM*G!O2>&U<^d`@<)otXZJJxT@G}4kTx0zPDVhVXwiU)$}5Y z`0iV`8EEh&GlUk&VY9m0Mqr*U&|^Bc?FB`<%{x-o0ATntwIA%(YDcxWs$C)%a%d_@ z?fx!Co+@3p7ha$|pWYD}p6#(PG%_h8K7sQjT_P~|3ZEH0DRxa3~bP&&lPMj3C~!H2QD zq>(f^RUFSqf6K3BMBFy$jiuoSE+DhEq$xLDb7{57 z0B|1pSjYJ5F@cHG%qDZ{ogL$P!BK&sR%zD`gbK#9gRZX17EtAJxN% zys^gb2=X9=7HP}N(iRqt(tot2yyeE%s;L}AcMh;~-W~s_eAe!gIUYdQz5j~T)0trh z>#1U$uOyyl%!Pi(gD&)uHe9Q^27_kHyFCC}n^-KL(=OxHqUfex1YS__RJh0m-S>eM zqAk`aSev*z1lI&-?CycgDm=bdQCp}RqS0_d-4Mf&>u2KyGFxKe8JM1N{GNWw0n$FL z1UDp(h0(1I2Jh9I`?IS}h4R~n zRwRz>8?$fFMB2{UPe^$Ifl;Oc>}@Q9`|8DCeR{?LUQLPfaMsxs8ps=D_aAXORZH~< zdcIOca-F;+D3~M+)Vi4h)I4O3<)$65yI)goQ_vk#fb;Uim>UI4Dv9#2b1;N_Wg>-F zNwKeMKY+su#~NL0uE%_$mw1%ddX2Qs2P!ncM+>wnz}OCQX1!q~oS?OqYU;&ESAAwP z452QWL0&u^mraF#=j_ZeBWhm&F|d!QjwRl^7=Bl7@(43=BkN=3{BRv#QHIk>Umc_w zvP>q|q{lJ=zs|W9%a@8%W>C@MYN1D5{(=Af31+pR#kB`cd0-YlQQTg}+ zL|_h=F9JQ|Gux5c0ehaffHNYLf8VwF+qnM6IjBEI_eceee;o;FY@#~FFVsZjBSp!j z8V*Bgmn{RK!!zqGc;jy)z@Zjo>5{%m1?K}fLEL$l6Dl4f=ye0wNI#)2L=^K(&18Gb zJoj8@WBB;P^T#V)I0`aDSy?$rJU{+-5472NyFp>;Vw43j@3Z=;D2eSfyw5*0Q+&ML zsV&&*3c3$pa`qcaGbEB0*CA~Wp3%PkF?B87FV&rWNb|@GU$LB;l|;YutU*k za1hjUL_BX%G^s;BuzRi4Hl?eqC2z&ZrKh1tZDwnufG$g$LX(j!h%F5(n8D@in3lnX z(*8+3ZT6TVYRcSpM1eMeCps=Fz8q%gyM&B=a7(Vf`4k3dN$IM+`BO^_7HZq4BR|7w z+5kOJ;9_$X%-~arA@qmXSzD|+NMh--%5-9u6t(M=f%&z$<_V#Y_lzn{E$MZZG)+A> zu2E`_Y(MBJ2l*AqvCUmU;yBT}#oQ{V=((mC-QGJwsCOH*a;{1JRTKv7DBNG+M!XL7(^jbv&Qy-o9HNFrmN)-`D3WFtXs>1vBOJpI(=x; zKhJlFdfMf^G#oU(w1+ucMKYPZaDp>$kt=wiYsBCjUY-uz<4JziB>6fXDSLH*2Y z&Px5y`#3!fF=c4>fCMdg-tX582pemU@ZxyFbznL8-=TTo1Sybg9>7h*J^9^~XxXJO z`k9v~=4amxl<;FCV9h2k%?^-ZUzQy^#{JleyH23o1S{r<+t#z6jKS<9rbAM96^1iY zi6{IjauB)UwBhC-_L(MzGCxhhv`?ryc zja_Uwi7$8l!}*vjJppGyp#Wz=*?;jC*xQ&J894rql5A$2giJRtV&DWQh#(+Vs3-5_ z69_tj(>8%z1VtVp>a74r5}j2rG%&;uaTQ|fr&r%ew-HO}76i8`&ki%#)~}q4Y|d$_ zfNp9uc#$#OEca>>MaY6rF`dB|5#S)bghf>>TmmE&S~IFw;PF0UztO6+R-0!TSC?QP z{b(RA_;q3QAPW^XN?qQqu{h<}Vfiv}Rr!lA$C79^1=U>+ng9Dh>v{`?AOZt>CrQ=o zI}=mSnR))8fJpO->rcX?H);oqSQUZ?sR!fH2SoFdcPm5*2y<_u;4h;BqcF*XbwWSv zcJN%!g|L(22Xp!^1?c;T&qm%rpkP&2EQC3JF+SENm$+@7#e!UKD1uQ{TDw43?!b!3 zUooS_rt=xJfa&h?c^hfV>YwQXre3qosz_^c#)FO~d!<)2o}Oxz5HWtr<)1Yw012v4 zhv0w(RfJspDnA^-6Jmr;GkWt%{mAYOm6yPb&Vl&rv@D^K&;#?=X{kaK5FhScNJ_3> z#5u(Saisq2(~pVlrfG#@kLM#Ot~5rZZc%B&h1=gen?R+#t^1bYKf zVvtefX=D$*)39e^2@!~A_}9c${Gf0?1;dk=!Itp#s%0>Io%k`9(bDeI-udd&E6Zfu zcaiv(h`DM3W3Mfda)fYwhB=8RAPkotVt5-z21Ij~Ot9A^SK-1u*zFVK&mF?q1;|wy zrF+XWs^5Q-%Z6I62gTwrRe#F>riVM#fv_TihxSJ6to1X7NVszgivoTa!fPfBBYj94 zuc2m zL_k-<1FoORng190; z+@DGs;NHgGW8%wjH$EpvQ-Hd! znZdIh#!H5nOStiOKNV8}QvY~=VMqtG&p$ByF&%pe_gR`|H5ULg47lk20(Xe=k8ptc zn%EmTI7k9gNE=!IN4WnbymtsKoHn2-cL65z^9cQOSp>XFzo;!h*x1s^0U!<{Y-VZ1 zXJ7zekkYf(`@dZ3F9|?O+*dUL4K4?0@V^>I2;k-a1%ZgY9w2|C5r0R5?80e-|&4yEwkklXmZ)!QSYG) zXBKOz|IPC2W_X!t^cgb^@D=|>r@x$f{3Y+`%NoDT^Y@JIuJ%jxe;es9vi`kJmbnPYT%X}rzs0K#=H)Q`)_L7%?KLLJP+0XJbL&JgdJE{i*){MOFSK z{7XUfXZR-Te}aE8RelNkQV0AQ7RC0TVE^o8c!~K^RQ4GY+xed`|A+zjZ(qij@~zLP zkS@Q0`rpM|UsnI6B;_+vw)^iA{n0%C7N~ql@KXNonIOUIHwgYg4Dcn>OOdc=rUl>M zVEQe|u$P=Kb)TL&-2#4t^Pg0pUQ)dj%6O)#3;zwOe~`_1$@Ef`;F+l=>NlAFFbBS0 zN))`LdKnA;OjQ{B+f;z>i|wCv-CmNs46S`8X-oKRl0V+pKZ%XJWO*6G`OMOs^xG_d zj_7-p06{fybw_P;UzX^eX5Pkcrm04%9rPFa56 zyZE \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/modules/openapi-generator-gradle-plugin/gradlew.bat b/modules/openapi-generator-gradle-plugin/gradlew.bat new file mode 100644 index 00000000000..e95643d6a2c --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/modules/openapi-generator-gradle-plugin/pom.xml b/modules/openapi-generator-gradle-plugin/pom.xml new file mode 100644 index 00000000000..28643d2a4a8 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/pom.xml @@ -0,0 +1,59 @@ + + + org.openapitools + openapi-generator-project + 3.0.0-SNAPSHOT + ../.. + + 4.0.0 + + openapi-generator-gradle-plugin + + openapi-generator-gradle-plugin (gradle-plugin) + + + + org.openapitools + openapi-generator + ${project.version} + + + + + + + + + org.fortasoft + gradle-maven-plugin + 1.0.8 + + 4.7 + + -P openApiGeneratorVersion=${project.version} + + + + + install + + + invoke + + + + + clean + build + publishToMavenLocal + + + + + + + + + diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/.gitignore b/modules/openapi-generator-gradle-plugin/samples/local-spec/.gitignore new file mode 100644 index 00000000000..fa897594662 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/.gitignore @@ -0,0 +1,3 @@ +.gradle/ +src/ +build/ diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/README.md b/modules/openapi-generator-gradle-plugin/samples/local-spec/README.md new file mode 100644 index 00000000000..278bddb12b5 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/README.md @@ -0,0 +1,14 @@ +# Local Spec Sample + +This example assumes you have Gradle 4.7+ installed. No gradle wrapper is provided in samples. + +First, publish the openapi-generator-gradle-plugin locally via `sh gradlew build publishToMavenLocal` in the module directory. + +Then, run the following tasks in this example directory. + +```bash +gradle openApiGenerate +gradle openApiMeta +gradle openApiValidate +gradle buildGoSdk +``` diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/build.gradle b/modules/openapi-generator-gradle-plugin/samples/local-spec/build.gradle new file mode 100644 index 00000000000..52f80315398 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/build.gradle @@ -0,0 +1,49 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "org.openapitools:openapi-generator-gradle-plugin:3.0.0-SNAPSHOT" + } +} + +apply plugin: 'org.openapi.generator' + +openApiMeta { + generatorName = "Sample" + packageName = "org.openapitools.example" + outputFolder = "$buildDir/meta".toString() +} + +openApiValidate { + inputSpec = "$rootDir/petstore-v3.0-invalid.yaml".toString() +} + +// Builds a Kotlin client by default. +openApiGenerate { + generatorName = "kotlin" + inputSpec = "$rootDir/petstore-v3.0.yaml".toString() + outputDir = "$buildDir/kotlin".toString() + apiPackage = "org.openapitools.example.api" + invokerPackage = "org.openapitools.example.invoker" + modelPackage = "org.openapitools.example.model" + configOptions = [ + dateLibrary: "java8" + ] +} + +task buildGoSdk(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask){ + generatorName = "go" + inputSpec = "$rootDir/petstore-v3.0.yaml".toString() + additionalProperties = [ + packageName: "petstore" + ] + outputDir = "$buildDir/go".toString() + configOptions = [ + dateLibrary: "threetenp" + ] +} diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0-invalid.yaml b/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0-invalid.yaml new file mode 100644 index 00000000000..0f5c6fc2982 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0-invalid.yaml @@ -0,0 +1,103 @@ +openapi: "3.0.0" +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0.yaml b/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0.yaml new file mode 100644 index 00000000000..264dbeabff1 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0.yaml @@ -0,0 +1,109 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/settings.gradle b/modules/openapi-generator-gradle-plugin/samples/local-spec/settings.gradle new file mode 100644 index 00000000000..e69de29bb2d diff --git a/modules/openapi-generator-gradle-plugin/settings.gradle b/modules/openapi-generator-gradle-plugin/settings.gradle new file mode 100644 index 00000000000..563879e5d5d --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'openapi-generator-gradle-plugin' + diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt new file mode 100644 index 00000000000..dda1bfc2310 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * 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 org.openapitools.generator.gradle.plugin + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.invoke +import org.openapitools.generator.gradle.plugin.extensions.OpenApiGeneratorGenerateExtension +import org.openapitools.generator.gradle.plugin.extensions.OpenApiGeneratorMetaExtension +import org.openapitools.generator.gradle.plugin.extensions.OpenApiGeneratorValidateExtension +import org.openapitools.generator.gradle.plugin.tasks.GenerateTask +import org.openapitools.generator.gradle.plugin.tasks.GeneratorsTask +import org.openapitools.generator.gradle.plugin.tasks.MetaTask +import org.openapitools.generator.gradle.plugin.tasks.ValidateTask + +/** + * A plugin providing common Open API Generator use cases. + * + * @author Jim Schubert + */ +@Suppress("unused") +class OpenApiGeneratorPlugin : Plugin { + override fun apply(project: Project) { + project.run { + val meta = extensions.create( + "openApiMeta", + OpenApiGeneratorMetaExtension::class.java, + project + ) + + val validate = extensions.create( + "openApiValidate", + OpenApiGeneratorValidateExtension::class.java, + project + ) + + val generate = extensions.create( + "openApiGenerate", + OpenApiGeneratorGenerateExtension::class.java, + project + ) + + generate.outputDir.set("$buildDir/generate-resources/main") + + tasks { + "openApiGenerators"(GeneratorsTask::class) { + group = pluginGroup + description = "Lists generators available via Open API Generators." + } + "openApiMeta"(MetaTask::class) { + group = pluginGroup + description = "Generates a new generator to be consumed via Open API Generator." + + generatorName.set(meta.generatorName) + packageName.set(meta.packageName) + outputFolder.set(meta.outputFolder) + } + "openApiValidate"(ValidateTask::class) { + group = pluginGroup + description = "Validates an Open API 2.0 or 3.x specification document." + + inputSpec.set(validate.inputSpec) + } + "openApiGenerate"(GenerateTask::class) { + group = pluginGroup + description = "Generate code via Open API Tools Generator for Open API 2.0 or 3.x specification documents." + + verbose.set(generate.verbose) + generatorName.set(generate.generatorName) + outputDir.set(generate.outputDir) + inputSpec.set(generate.inputSpec) + templateDir.set(generate.templateDir) + auth.set(generate.auth) + systemProperties.set(generate.systemProperties) + configFile.set(generate.configFile) + skipOverwrite.set(generate.skipOverwrite) + apiPackage.set(generate.apiPackage) + modelPackage.set(generate.modelPackage) + modelNamePrefix.set(generate.modelNamePrefix) + modelNameSuffix.set(generate.modelNameSuffix) + instantiationTypes.set(generate.instantiationTypes) + typeMappings.set(generate.typeMappings) + additionalProperties.set(generate.additionalProperties) + languageSpecificPrimitives.set(generate.languageSpecificPrimitives) + importMappings.set(generate.importMappings) + invokerPackage.set(generate.invokerPackage) + groupId.set(generate.groupId) + id.set(generate.id) + version.set(generate.version) + library.set(generate.library) + gitUserId.set(generate.gitUserId) + gitRepoId.set(generate.gitRepoId) + releaseNote.set(generate.releaseNote) + httpUserAgent.set(generate.httpUserAgent) + reservedWordsMappings.set(generate.reservedWordsMappings) + ignoreFileOverride.set(generate.ignoreFileOverride) + removeOperationIdPrefix.set(generate.removeOperationIdPrefix) + apiFilesConstrainedTo.set(generate.apiFilesConstrainedTo) + modelFilesConstrainedTo.set(generate.modelFilesConstrainedTo) + supportingFilesConstrainedTo.set(generate.supportingFilesConstrainedTo) + generateModelTests.set(generate.generateModelTests) + generateModelDocumentation.set(generate.generateModelDocumentation) + generateApiTests.set(generate.generateApiTests) + generateApiDocumentation.set(generate.generateApiDocumentation) + withXml.set(generate.withXml) + configOptions.set(generate.configOptions) + } + } + } + } + + companion object { + const val pluginGroup = "OpenAPI Tools" + } +} + diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt new file mode 100644 index 00000000000..a62efd0bc55 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt @@ -0,0 +1,275 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * 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 org.openapitools.generator.gradle.plugin.extensions + +import org.gradle.api.Project +import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.property + +/** + * Gradle project level extension object definition for the generate task + * + * @author Jim Schubert + */ +open class OpenApiGeneratorGenerateExtension(project: Project) { + + /** + * The verbosity of generation + */ + val verbose = project.objects.property() + + /** + * The name of the generator which will handle codegen. (see "openApiGenerators" task) + */ + val generatorName = project.objects.property() + + /** + * The output target directory into which code will be generated. + */ + val outputDir = project.objects.property() + + /** + * The Open API 2.0/3.x specification location. + */ + val inputSpec = project.objects.property() + + /** + * The template directory holding a custom template. + */ + val templateDir = project.objects.property() + + /** + * Adds authorization headers when fetching the OpenAPI definitions remotely. + * Pass in a URL-encoded string of name:header with a comma separating multiple values + */ + val auth = project.objects.property() + + /** + * Sets specified system properties. + */ + val systemProperties = project.objects.property>() + + /** + * Path to json configuration file. + * File content should be in a json format { "optionKey":"optionValue", "optionKey1":"optionValue1"...} + * Supported options can be different for each language. Run config-help -g {generator name} command for language specific config options. + */ + val configFile = project.objects.property() + + /** + * Specifies if the existing files should be overwritten during the generation. + */ + val skipOverwrite = project.objects.property() + + /** + * Package for generated api classes + */ + val apiPackage = project.objects.property() + + /** + * Package for generated models + */ + val modelPackage = project.objects.property() + + /** + * Prefix that will be prepended to all model names. Default is the empty string. + */ + val modelNamePrefix = project.objects.property() + + /** + * Suffix that will be appended to all model names. Default is the empty string. + */ + val modelNameSuffix = project.objects.property() + + /** + * Sets instantiation type mappings. + */ + val instantiationTypes = project.objects.property>() + + /** + * Sets mappings between OpenAPI spec types and generated code types. + */ + val typeMappings = project.objects.property>() + + /** + * Sets additional properties that can be referenced by the mustache templates. + */ + val additionalProperties = project.objects.property>() + + /** + * Specifies additional language specific primitive types in the format of type1,type2,type3,type3. For example: String,boolean,Boolean,Double. + */ + val languageSpecificPrimitives = project.objects.listProperty() + + /** + * Specifies mappings between a given class and the import that should be used for that class. + */ + val importMappings = project.objects.property>() + + /** + * Root package for generated code. + */ + val invokerPackage = project.objects.property() + + /** + * GroupId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + val groupId = project.objects.property() + + /** + * ArtifactId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + val id = project.objects.property() + + /** + * Artifact version in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + val version = project.objects.property() + + /** + * Reference the library template (sub-template) of a generator. + */ + val library = project.objects.property() + + /** + * Git user ID, e.g. openapitools. + */ + val gitUserId = project.objects.property() + + /** + * Git repo ID, e.g. openapi-generator. + */ + val gitRepoId = project.objects.property() + + /** + * Release note, default to 'Minor update'. + */ + val releaseNote = project.objects.property() + + /** + * HTTP user agent, e.g. codegen_csharp_api_client, default to 'OpenAPI-Generator/{packageVersion}}/{language}' + */ + val httpUserAgent = project.objects.property() + + /** + * Specifies how a reserved name should be escaped to. Otherwise, the default _ is used. + */ + val reservedWordsMappings = project.objects.property>() + + /** + * Specifies an override location for the .openapi-generator-ignore file. Most useful on initial generation. + */ + val ignoreFileOverride = project.objects.property() + + /** + * Remove prefix of operationId, e.g. config_getId => getId + */ + val removeOperationIdPrefix = project.objects.property() + + /** + * Defines which API-related files should be generated. This allows you to create a subset of generated files (or none at all). + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + val apiFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines which model-related files should be generated. This allows you to create a subset of generated files (or none at all). + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + val modelFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines which supporting files should be generated. This allows you to create a subset of generated files (or none at all). + * + * Supporting files are those related to projects/frameworks which may be modified + * by consumers. + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + val supportingFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines whether or not model-related _test_ files should be generated. + * + * This option enables/disables generation of ALL model-related _test_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + val generateModelTests = project.objects.property() + + /** + * Defines whether or not model-related _documentation_ files should be generated. + * + * This option enables/disables generation of ALL model-related _documentation_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + val generateModelDocumentation = project.objects.property() + + /** + * Defines whether or not api-related _test_ files should be generated. + * + * This option enables/disables generation of ALL api-related _test_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + val generateApiTests = project.objects.property() + + /** + * Defines whether or not api-related _documentation_ files should be generated. + * + * This option enables/disables generation of ALL api-related _documentation_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + val generateApiDocumentation = project.objects.property() + + /** + * A special-case setting which configures some generators with XML support. In some cases, + * this forces json OR xml, so the default here is false. + */ + val withXml = project.objects.property() + + /** + * A map of options specific to a generator. + */ + val configOptions = project.objects.property>() + + init { + releaseNote.set("Minor update") + modelNamePrefix.set("") + modelNameSuffix.set("") + generateModelTests.set(true) + generateModelDocumentation.set(true) + generateApiTests.set(true) + generateApiDocumentation.set(true) + withXml.set(false) + configOptions.set(mapOf()) + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorMetaExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorMetaExtension.kt new file mode 100644 index 00000000000..94d298a31b8 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorMetaExtension.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * 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 org.openapitools.generator.gradle.plugin.extensions + +import org.gradle.api.Project +import org.gradle.kotlin.dsl.property + +/** + * Gradle project level extension object definition for the meta-generator task + * + * @author Jim Schubert + */ +open class OpenApiGeneratorMetaExtension(project: Project) { + /** + * The human-readable generator name of the newly created template generator. + */ + val generatorName = project.objects.property() + + /** + * The packageName generatorName to put the main class into (defaults to org.openapitools.codegen) + */ + val packageName = project.objects.property() + + /** + * Where to write the generated files (current dir by default). + */ + val outputFolder = project.objects.property() + + init { + generatorName.set("default") + packageName.set("org.openapitools.codegen") + outputFolder.set("") + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt new file mode 100644 index 00000000000..3b4a1853b94 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * 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 org.openapitools.generator.gradle.plugin.extensions + +import org.gradle.api.Project +import org.gradle.kotlin.dsl.property + +/** + * Gradle project level extension object definition for the generators task + * + * @author Jim Schubert + */ +open class OpenApiGeneratorValidateExtension(project: Project) { + /** + * The input specification to validate. Supports all formats supported by the Parser. + */ + val inputSpec = project.objects.property() +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt new file mode 100644 index 00000000000..dbe0424dbca --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt @@ -0,0 +1,541 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * 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 org.openapitools.generator.gradle.plugin.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.property +import org.openapitools.codegen.CodegenConstants +import org.openapitools.codegen.DefaultGenerator +import org.openapitools.codegen.config.CodegenConfigurator +import org.openapitools.codegen.config.CodegenConfiguratorUtils.* + + +/** + * A task which generates the desired code. + * + * Example (CLI): + * + * ./gradlew -q openApiGenerate + * + * @author Jim Schubert + */ +open class GenerateTask : DefaultTask() { + + /** + * The verbosity of generation + */ + @get:Internal + val verbose = project.objects.property() + + /** + * The name of the generator which will handle codegen. (see "openApiGenerators" task) + */ + @get:Internal + val generatorName = project.objects.property() + + /** + * The output target directory into which code will be generated. + */ + @get:Internal + val outputDir = project.objects.property() + + /** + * The Open API 2.0/3.x specification location. + */ + @get:Internal + val inputSpec = project.objects.property() + + /** + * The template directory holding a custom template. + */ + @get:Internal + val templateDir = project.objects.property() + + /** + * Adds authorization headers when fetching the OpenAPI definitions remotely. + * Pass in a URL-encoded string of name:header with a comma separating multiple values + */ + @get:Internal + val auth = project.objects.property() + + /** + * Sets specified system properties. + */ + @get:Internal + val systemProperties = project.objects.property>() + + /** + * Path to json configuration file. + * File content should be in a json format { "optionKey":"optionValue", "optionKey1":"optionValue1"...} + * Supported options can be different for each language. Run config-help -g {generator name} command for language specific config options. + */ + @get:Internal + val configFile = project.objects.property() + + /** + * Specifies if the existing files should be overwritten during the generation. + */ + @get:Internal + val skipOverwrite = project.objects.property() + + /** + * Package for generated api classes + */ + @get:Internal + val apiPackage = project.objects.property() + + /** + * Package for generated models + */ + @get:Internal + val modelPackage = project.objects.property() + + /** + * Prefix that will be prepended to all model names. Default is the empty string. + */ + @get:Internal + val modelNamePrefix = project.objects.property() + + /** + * Suffix that will be appended to all model names. Default is the empty string. + */ + @get:Internal + val modelNameSuffix = project.objects.property() + + /** + * Sets instantiation type mappings. + */ + @get:Internal + val instantiationTypes = project.objects.property>() + + /** + * Sets mappings between OpenAPI spec types and generated code types. + */ + @get:Internal + val typeMappings = project.objects.property>() + + /** + * Sets additional properties that can be referenced by the mustache templates in the format of name=value,name=value. + * You can also have multiple occurrences of this option. + */ + @get:Internal + val additionalProperties = project.objects.property>() + + /** + * Specifies additional language specific primitive types in the format of type1,type2,type3,type3. For example: String,boolean,Boolean,Double. + */ + @get:Internal + val languageSpecificPrimitives = project.objects.listProperty() + + /** + * Specifies mappings between a given class and the import that should be used for that class. + */ + @get:Internal + val importMappings = project.objects.property>() + + /** + * Root package for generated code. + */ + @get:Internal + val invokerPackage = project.objects.property() + + /** + * GroupId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + @get:Internal + val groupId = project.objects.property() + + /** + * ArtifactId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + @get:Internal + val id = project.objects.property() + + /** + * Artifact version in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + @get:Internal + val version = project.objects.property() + + /** + * Reference the library template (sub-template) of a generator. + */ + @get:Internal + val library = project.objects.property() + + /** + * Git user ID, e.g. openapitools. + */ + @get:Internal + val gitUserId = project.objects.property() + + /** + * Git repo ID, e.g. openapi-generator. + */ + @get:Internal + val gitRepoId = project.objects.property() + + /** + * Release note, default to 'Minor update'. + */ + @get:Internal + val releaseNote = project.objects.property() + + /** + * HTTP user agent, e.g. codegen_csharp_api_client, default to 'OpenAPI-Generator/{packageVersion}}/{language}' + */ + @get:Internal + val httpUserAgent = project.objects.property() + + /** + * Specifies how a reserved name should be escaped to. + */ + @get:Internal + val reservedWordsMappings = project.objects.property>() + + /** + * Specifies an override location for the .openapi-generator-ignore file. Most useful on initial generation. + */ + @get:Internal + val ignoreFileOverride = project.objects.property() + + /** + * Remove prefix of operationId, e.g. config_getId => getId + */ + @get:Internal + val removeOperationIdPrefix = project.objects.property() + + /** + * Defines which API-related files should be generated. This allows you to create a subset of generated files (or none at all). + * + * This option enables/disables generation of ALL api-related files. + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + @get:Internal + val apiFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines which model-related files should be generated. This allows you to create a subset of generated files (or none at all). + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + @get:Internal + val modelFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines which supporting files should be generated. This allows you to create a subset of generated files (or none at all). + * + * Supporting files are those related to projects/frameworks which may be modified + * by consumers. + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + @get:Internal + val supportingFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines whether or not model-related _test_ files should be generated. + * + * This option enables/disables generation of ALL model-related _test_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + @get:Internal + val generateModelTests = project.objects.property() + + /** + * Defines whether or not model-related _documentation_ files should be generated. + * + * This option enables/disables generation of ALL model-related _documentation_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + @get:Internal + val generateModelDocumentation = project.objects.property() + + /** + * Defines whether or not api-related _test_ files should be generated. + * + * This option enables/disables generation of ALL api-related _test_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + @get:Internal + val generateApiTests = project.objects.property() + + /** + * Defines whether or not api-related _documentation_ files should be generated. + * + * This option enables/disables generation of ALL api-related _documentation_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + @get:Internal + val generateApiDocumentation = project.objects.property() + + /** + * A special-case setting which configures some generators with XML support. In some cases, + * this forces json OR xml, so the default here is false. + */ + @get:Internal + val withXml = project.objects.property() + + /** + * A dynamic map of options specific to a generator. + */ + @get:Internal + val configOptions = project.objects.property>() + + private val originalEnvironmentVariables = mutableMapOf() + + private fun Property.ifNotEmpty(block: Property.(T) -> Unit) { + if (isPresent) { + val item: T? = get() + if (item != null) { + when (get()) { + is String -> if ((get() as String).isNotEmpty()) { + block(get()) + } + is String? -> if (true == (get() as String?)?.isNotEmpty()) { + block(get()) + } + else -> block(get()) + } + } + } + } + + @Suppress("unused") + @TaskAction + fun doWork() { + val configurator: CodegenConfigurator = if (configFile.isPresent) { + CodegenConfigurator.fromFile(configFile.get()) + } else CodegenConfigurator() + + try { + if (systemProperties.isPresent) { + systemProperties.get().forEach { (key, value) -> + originalEnvironmentVariables[key] = System.getProperty(key) + System.setProperty(key, value) + configurator.addSystemProperty(key, value) + } + } + + if (supportingFilesConstrainedTo.isPresent && supportingFilesConstrainedTo.get().isNotEmpty()) { + System.setProperty(CodegenConstants.SUPPORTING_FILES, supportingFilesConstrainedTo.get().joinToString(",")) + } else { + System.clearProperty(CodegenConstants.SUPPORTING_FILES) + } + + if (modelFilesConstrainedTo.isPresent && modelFilesConstrainedTo.get().isNotEmpty()) { + System.setProperty(CodegenConstants.MODELS, modelFilesConstrainedTo.get().joinToString(",")) + } else { + System.clearProperty(CodegenConstants.MODELS) + } + + if (apiFilesConstrainedTo.isPresent && apiFilesConstrainedTo.get().isNotEmpty()) { + System.setProperty(CodegenConstants.APIS, apiFilesConstrainedTo.get().joinToString(",")) + } else { + System.clearProperty(CodegenConstants.APIS) + } + + System.setProperty(CodegenConstants.API_DOCS, generateApiDocumentation.get().toString()) + System.setProperty(CodegenConstants.MODEL_DOCS, generateModelDocumentation.get().toString()) + System.setProperty(CodegenConstants.MODEL_TESTS, generateModelTests.get().toString()) + System.setProperty(CodegenConstants.API_TESTS, generateApiTests.get().toString()) + System.setProperty(CodegenConstants.WITH_XML, withXml.get().toString()) + + // now override with any specified parameters + verbose.ifNotEmpty { value -> + configurator.isVerbose = value + } + + skipOverwrite.ifNotEmpty { value -> + configurator.isSkipOverwrite = value ?: false + } + + inputSpec.ifNotEmpty { value -> + configurator.inputSpec = value + } + + generatorName.ifNotEmpty { value -> + configurator.generatorName = value + } + + outputDir.ifNotEmpty { value -> + configurator.outputDir = value + } + + auth.ifNotEmpty { value -> + configurator.auth = value + } + + templateDir.ifNotEmpty { value -> + configurator.templateDir = value + } + + apiPackage.ifNotEmpty { value -> + configurator.apiPackage = value + } + + modelPackage.ifNotEmpty { value -> + configurator.modelPackage = value + } + + modelNamePrefix.ifNotEmpty { value -> + configurator.modelNamePrefix = value + } + + modelNameSuffix.ifNotEmpty { value -> + configurator.modelNameSuffix = value + } + + invokerPackage.ifNotEmpty { value -> + configurator.invokerPackage = value + } + + groupId.ifNotEmpty { value -> + configurator.groupId = value + } + + id.ifNotEmpty { value -> + configurator.artifactId = value + } + + version.ifNotEmpty { value -> + configurator.artifactVersion = value + } + + library.ifNotEmpty { value -> + configurator.library = value + } + + gitUserId.ifNotEmpty { value -> + configurator.gitUserId = value + } + + gitRepoId.ifNotEmpty { value -> + configurator.gitRepoId = value + } + + releaseNote.ifNotEmpty { value -> + configurator.releaseNote = value + } + + httpUserAgent.ifNotEmpty { value -> + configurator.httpUserAgent = value + } + + ignoreFileOverride.ifNotEmpty { value -> + configurator.ignoreFileOverride = value + } + + removeOperationIdPrefix.ifNotEmpty { value -> + configurator.removeOperationIdPrefix = value!! + } + + if (systemProperties.isPresent) { + systemProperties.get().forEach { entry -> + configurator.addSystemProperty(entry.key, entry.value) + } + } + + if (instantiationTypes.isPresent) { + instantiationTypes.get().forEach { entry -> + configurator.addInstantiationType(entry.key, entry.value) + } + } + + if (importMappings.isPresent) { + importMappings.get().forEach { entry -> + configurator.addImportMapping(entry.key, entry.value) + } + } + + if (typeMappings.isPresent) { + typeMappings.get().forEach { entry -> + configurator.addTypeMapping(entry.key, entry.value) + } + } + + if (additionalProperties.isPresent) { + additionalProperties.get().forEach { entry -> + configurator.addAdditionalProperty(entry.key, entry.value) + } + } + + if (languageSpecificPrimitives.isPresent) { + languageSpecificPrimitives.get().forEach { + configurator.addLanguageSpecificPrimitive(it) + } + } + + if (reservedWordsMappings.isPresent) { + reservedWordsMappings.get().forEach { entry -> + configurator.addAdditionalReservedWordMapping(entry.key, entry.value) + } + } + + val clientOptInput = configurator.toClientOptInput() + val codgenConfig = clientOptInput.config + + if (configOptions.isPresent) { + val userSpecifiedConfigOptions = configOptions.get() + codgenConfig.cliOptions().forEach { + if (userSpecifiedConfigOptions.containsKey(it.opt)) { + clientOptInput.config.additionalProperties()[it.opt] = userSpecifiedConfigOptions[it.opt] + } + } + } + + try { + val out = services.get(StyledTextOutputFactory::class.java).create("openapi") + out.withStyle(StyledTextOutput.Style.Success) + + DefaultGenerator().opts(clientOptInput).generate() + + out.println("Successfully generated code to ${configurator.outputDir}") + } catch (e: RuntimeException) { + logger.error(e.message) + throw GradleException("Code generation failed.") + } + } finally { + originalEnvironmentVariables.forEach { entry -> + System.setProperty(entry.key, entry.value) + } + originalEnvironmentVariables.clear() + } + } +} diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt new file mode 100644 index 00000000000..08e821b83a5 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * 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 org.openapitools.generator.gradle.plugin.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.openapitools.codegen.CodegenConfigLoader +import org.openapitools.codegen.CodegenType + +/** + * A task which lists out the generators available in OpenAPI Generator + * + * Example (CLI): + * + * ./gradlew -q openApiGenerators + * + * @author Jim Schubert + */ +open class GeneratorsTask : DefaultTask() { + @Suppress("unused") + @TaskAction + fun doWork() { + val generators = CodegenConfigLoader.getAll() + + val out = services.get(StyledTextOutputFactory::class.java).create("openapi") + + StringBuilder().apply { + val types = CodegenType.values() + + append("The following generators are available:") + + append(System.lineSeparator()) + append(System.lineSeparator()) + + for (type in types) { + append(type.name).append(" generators:") + append(System.lineSeparator()) + + generators.filter { it.tag == type } + .sortedBy { it.name } + .forEach({ generator -> + append(" - ") + append(generator.name) + append(System.lineSeparator()) + }) + + append(System.lineSeparator()) + append(System.lineSeparator()) + } + + out.withStyle(StyledTextOutput.Style.Success) + out.formatln("%s%n", toString()) + } + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt new file mode 100644 index 00000000000..7d11f4d8b6c --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * 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 org.openapitools.generator.gradle.plugin.tasks + +import com.samskivert.mustache.Mustache +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.gradle.kotlin.dsl.property +import org.openapitools.codegen.CodegenConfig +import org.openapitools.codegen.CodegenConstants +import org.openapitools.codegen.DefaultGenerator +import org.openapitools.codegen.SupportingFile +import java.io.File +import java.io.IOException +import java.nio.charset.Charset + +/** + * A task which generates a new generator (meta). Useful for redistributable generator packages. + * + * @author Jim Schubert + */ +open class MetaTask : DefaultTask() { + + @get:Internal + val generatorName = project.objects.property() + + @get:Internal + val packageName = project.objects.property() + + @get:Internal + val outputFolder = project.objects.property() + + @Suppress("unused") + @TaskAction + fun doWork() { + + val packageToPath = packageName.get().replace(".", File.separator) + val dir = File(outputFolder.get()) + val klass = "${generatorName.get().titleCasedTextOnly()}Generator" + + val templateResourceDir = generatorName.get().hyphenatedTextOnly() + + val out = services.get(StyledTextOutputFactory::class.java).create("openapi") + + out.withStyle(StyledTextOutput.Style.Info) + + logger.debug("package: {}", packageName.get()) + logger.debug("dir: {}", dir.absolutePath) + logger.debug("generator class: {}", klass) + + val supportingFiles = listOf( + SupportingFile("pom.mustache", "", "pom.xml"), + SupportingFile("generatorClass.mustache", dir("src", "main", "java", packageToPath), "$klass.java"), + SupportingFile("README.mustache", "", "README.md"), + SupportingFile("api.template", dir("src", "main", "resources", templateResourceDir), "api.mustache"), + SupportingFile("model.template", dir("src", "main", "resources", templateResourceDir), "model.mustache"), + SupportingFile("myFile.template", dir("src", "main", "resources", templateResourceDir), "myFile.mustache"), + SupportingFile("services.mustache", dir("src", "main", "resources", "META-INF", "services"), CodegenConfig::class.java.canonicalName)) + + val currentVersion = CodegenConstants::class.java.`package`.implementationVersion + + val data = mapOf("generatorPackage" to packageToPath, + "generatorClass" to klass, + "name" to templateResourceDir, + "fullyQualifiedGeneratorClass" to "${packageName.get()}.$klass", + "openapiGeneratorVersion" to currentVersion) + + val generator = DefaultGenerator() + supportingFiles.map { + try { + val destinationFolder = File(File(dir.absolutePath), it.folder) + destinationFolder.mkdirs() + val outputFile = File(destinationFolder, it.destinationFilename) + + val template = generator.readTemplate(File("codegen", it.templateFile).path) + var formatted = template + + if (it.templateFile.endsWith(".mustache")) { + formatted = Mustache.compiler() + .withLoader(loader(generator)) + .defaultValue("") + .compile(template).execute(data) + } + + outputFile.writeText(formatted, Charset.forName("UTF8")) + + out.formatln("Wrote file to %s", outputFile.absolutePath) + + // TODO: register outputs + // return outputFile + } catch (e: IOException) { + logger.error(e.message) + throw GradleException("Can't generate project", e) + } + } + out.withStyle(StyledTextOutput.Style.Success) + out.formatln("Created generator %s", klass) + } + + private fun loader(generator: DefaultGenerator): Mustache.TemplateLoader { + return Mustache.TemplateLoader { name -> + generator.getTemplateReader("codegen${File.separator}$name.mustache") + } + } + + private fun String.titleCasedTextOnly(): String = + this.split(Regex("[^a-zA-Z0-9]")).joinToString(separator = "", transform = String::capitalize) + + private fun String.hyphenatedTextOnly(): String = + this.split(Regex("[^a-zA-Z0-9]")).joinToString(separator = "-", transform = String::toLowerCase) + + private fun dir(vararg parts: String): String = + parts.joinToString(separator = File.separator) +} diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt new file mode 100644 index 00000000000..0956ba22c26 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * 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 org.openapitools.generator.gradle.plugin.tasks + +import io.swagger.parser.OpenAPIParser +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.gradle.kotlin.dsl.property + +/** + * A generator which validates an Open API spec. This task outputs a list of validation issues and errors. + * + * Example: + * cli: + * + * ./gradlew openApiValidate --input=/path/to/file + * + * build.gradle: + * + * openApiMeta { + * inputSpec = "path/to/spec.yaml" + * } + * + * @author Jim Schubert + */ +open class ValidateTask : DefaultTask() { + @get:Internal + var inputSpec = project.objects.property() + + @Suppress("unused") + @get:Internal + @set:Option(option = "input", description = "The input specification.") + var input: String? = null + set(value) { + inputSpec.set(value) + } + + @Suppress("unused") + @TaskAction + fun doWork() { + val spec = inputSpec.get() + logger.quiet("Validating spec $spec") + val result = OpenAPIParser().readLocation(spec, null, null) + val messages = result.messages.toSet() + val out = services.get(StyledTextOutputFactory::class.java).create("openapi") + + if (messages.isNotEmpty()) { + + out.withStyle(StyledTextOutput.Style.Error) + out.println("\nSpec is invalid.\nIssues:\n") + + messages.forEach { + out.withStyle(StyledTextOutput.Style.Error) + out.println("\t$it\n") + } + + throw GradleException("Validation failed.") + } else { + out.withStyle(StyledTextOutput.Style.Success) + out.println("Spec is valid.") + } + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/GenerateTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/GenerateTaskDslTest.kt new file mode 100644 index 00000000000..fe84a903bd7 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/GenerateTaskDslTest.kt @@ -0,0 +1,71 @@ +package org.openapitools.generator.gradle.plugin + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.testng.annotations.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class GenerateTaskDslTest : TestBase() { + override var temp: File = createTempDir(javaClass.simpleName) + + private val defaultBuildGradle = """ + plugins { + id 'org.openapi.generator' + } + openApiGenerate { + generatorName = "kotlin" + inputSpec = file("spec.yaml").absolutePath + outputDir = file("build/kotlin").absolutePath + apiPackage = "org.openapitools.example.api" + invokerPackage = "org.openapitools.example.invoker" + modelPackage = "org.openapitools.example.model" + configOptions = [ + dateLibrary: "java8" + ] + } + """.trimIndent() + + @Test + fun `openApiGenerate should create an expected file structure from DSL config`() { + // Arrange + val projectFiles = mapOf( + "spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0-invalid.yaml") + ) + withProject(defaultBuildGradle, projectFiles) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiGenerate") + .withPluginClasspath() + .build() + + // Assert + assertTrue(result.output.contains("Successfully generated code to"), "User friendly generate notice is missing.") + + listOf( + "build/kotlin/.openapi-generator-ignore", + "build/kotlin/docs/PetsApi.md", + "build/kotlin/docs/Pets.md", + "build/kotlin/docs/Error.md", + "build/kotlin/docs/Pet.md", + "build/kotlin/README.md", + "build/kotlin/build.gradle", + "build/kotlin/.openapi-generator/VERSION", + "build/kotlin/settings.gradle", + "build/kotlin/src/main/kotlin/org/openapitools/example/model/Pets.kt", + "build/kotlin/src/main/kotlin/org/openapitools/example/model/Pet.kt", + "build/kotlin/src/main/kotlin/org/openapitools/example/model/Error.kt", + "build/kotlin/src/main/kotlin/org/openapitools/example/api/PetsApi.kt", + "build/kotlin/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt" + ).map { + val f = File(temp, it) + assertTrue(f.exists() && f.isFile, "An expected file was not generated when invoking the generation.") + } + + assertEquals(TaskOutcome.SUCCESS, result.task(":openApiGenerate")?.outcome, + "Expected a successful run, but found ${result.task(":openApiGenerate")?.outcome}") + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/GeneratorsTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/GeneratorsTaskDslTest.kt new file mode 100644 index 00000000000..f3373fd657e --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/GeneratorsTaskDslTest.kt @@ -0,0 +1,38 @@ +package org.openapitools.generator.gradle.plugin + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.testng.annotations.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class GeneratorsTaskDslTest : TestBase() { + override var temp: File = createTempDir(javaClass.simpleName) + + @Test + fun `openApiGenerators should list generators available to the user`() { + // Arrange + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + """.trimMargin()) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiGenerators") + .withPluginClasspath() + .build() + + // Assert + assertTrue(result.output.contains("The following generators are available:"), "User friendly generator notice is missing.") + assertTrue(result.output.contains("CLIENT generators:"), "Expected client generator header is missing.") + assertTrue(result.output.contains("android"), "Spot-checking listed client generators is missing a client generator.") + assertTrue(result.output.contains("SERVER generators:"), "Expected server generator header is missing.") + assertTrue(result.output.contains("kotlin-server"), "Spot-checking listed server generators is missing a server generator.") + assertEquals(TaskOutcome.SUCCESS, result.task(":openApiGenerators")?.outcome, + "Expected a successful run, but found ${result.task(":openApiGenerators")?.outcome}") + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/MetaTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/MetaTaskDslTest.kt new file mode 100644 index 00000000000..fe857d1b94a --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/MetaTaskDslTest.kt @@ -0,0 +1,58 @@ +package org.openapitools.generator.gradle.plugin + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.testng.annotations.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class MetaTaskDslTest : TestBase() { + override var temp: File = createTempDir(javaClass.simpleName) + + @Test + fun `openApiMeta should generate desired project contents`() { + // Arrange + val buildDirReplacement = "\$buildDir/meta" + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiMeta { + | generatorName = "Sample" + | packageName = "org.openapitools.example" + | outputFolder = "$buildDirReplacement".toString() + | } + """.trimMargin()) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiMeta") + .withPluginClasspath() + .build() + + // Assert + assertTrue(result.output.contains("Wrote file to"), "User friendly write notice is missing.") + + // To avoid any OS-specific output causing issues with our stdout comparisons, only compare on expected filenames. + listOf( + "SampleGenerator.java", + "README.md", + "api.mustache", + "model.mustache", + "myFile.mustache", + "org.openapitools.codegen.CodegenConfig", + "pom.xml" + ).map { + assertTrue(result.output.contains(it), "Expected $it to be listed in gradle stdout.") + } + + assertEquals( + TaskOutcome.SUCCESS, + result.task(":openApiMeta")?.outcome, + "Expected a successful run, but found ${result.task(":openApiMeta")?.outcome}" + ) + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/TestBase.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/TestBase.kt new file mode 100644 index 00000000000..47a1bfba9ec --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/TestBase.kt @@ -0,0 +1,34 @@ +package org.openapitools.generator.gradle.plugin + +import org.testng.annotations.AfterMethod +import org.testng.annotations.BeforeMethod +import java.io.File +import java.io.InputStream + +abstract class TestBase { + protected open lateinit var temp: File + + @BeforeMethod + protected fun before() { + temp = createTempDir(javaClass.simpleName) + temp.deleteOnExit() + } + + @AfterMethod + protected fun after(){ + temp.deleteRecursively() + } + + protected fun withProject( + buildContents: String, + projectFiles: Map = mapOf() + ) { + val buildFile = File(temp,"build.gradle") + buildFile.writeText(buildContents) + + projectFiles.forEach { entry -> + val target = File(temp, entry.key) + entry.value.copyTo(target.outputStream()) + } + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt new file mode 100644 index 00000000000..acc066c98da --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt @@ -0,0 +1,99 @@ +package org.openapitools.generator.gradle.plugin + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome.FAILED +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.testng.annotations.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ValidateTaskDslTest : TestBase() { + override var temp: File = createTempDir(javaClass.simpleName) + + @Test + fun `openApiValidate should fail on non-file spec`() { + // Arrange + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiValidate { + | inputSpec = "some_location" + | } + """.trimMargin()) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiValidate") + .withPluginClasspath() + .buildAndFail() + + // Assert + assertTrue(result.output.contains("unable to read location `some_location`"), "Unexpected/no message presented to the user for a spec pointing to an invalid URI.") + assertEquals(FAILED, result.task(":openApiValidate")?.outcome, + "Expected a failed run, but found ${result.task(":openApiValidate")?.outcome}") + } + + @Test + fun `openApiValidate should succeed on valid spec`() { + // Arrange + val projectFiles = mapOf( + "spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0.yaml") + ) + + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiValidate { + | inputSpec = file("spec.yaml").absolutePath + | } + """.trimMargin(), projectFiles) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiValidate") + .withPluginClasspath() + .build() + + // Assert + assertTrue(result.output.contains("Spec is valid."), "Unexpected/no message presented to the user for a valid spec.") + assertEquals(SUCCESS, result.task(":openApiValidate")?.outcome, + "Expected a successful run, but found ${result.task(":openApiValidate")?.outcome}") + } + + @Test + fun `openApiValidate should fail on invalid spec`() { + // Arrange + val projectFiles = mapOf( + "spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0-invalid.yaml") + ) + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiValidate { + | inputSpec = file('spec.yaml').absolutePath + | } + """.trimMargin(), projectFiles) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiValidate") + .withPluginClasspath() + .buildAndFail() + + // Assert + assertTrue(result.output.contains("Spec is invalid."), "Unexpected/no message presented to the user for an invalid spec.") + assertEquals(FAILED, result.task(":openApiValidate")?.outcome, + "Expected a failed run, but found ${result.task(":openApiValidate")?.outcome}") + } + +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-invalid.yaml b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-invalid.yaml new file mode 100644 index 00000000000..0f5c6fc2982 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-invalid.yaml @@ -0,0 +1,103 @@ +openapi: "3.0.0" +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0.yaml b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0.yaml new file mode 100644 index 00000000000..264dbeabff1 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0.yaml @@ -0,0 +1,109 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/pom.xml b/pom.xml index 69818d7bba6..8f6a390c4ca 100644 --- a/pom.xml +++ b/pom.xml @@ -883,6 +883,7 @@ modules/openapi-generator modules/openapi-generator-cli modules/openapi-generator-maven-plugin + modules/openapi-generator-gradle-plugin modules/openapi-generator-online