Compare commits
46 Commits
aaa7a63bfb
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bcc50ea1b | |||
| d063ba1c25 | |||
| 68fdac4d64 | |||
| aa585358be | |||
| d84770970d | |||
| fe03d64122 | |||
| 13e269842b | |||
| d4635f82ae | |||
| 009ddd3d16 | |||
| ed4d9264eb | |||
| bb5e955318 | |||
| 6c0d17299e | |||
| 8e6a14b3e0 | |||
| a1dde23fb3 | |||
| f0155dea31 | |||
| cf09799bc6 | |||
| a856d5e425 | |||
| f56ec93498 | |||
| 66878900f5 | |||
| ce92e1fae9 | |||
| 724f1c87b1 | |||
| 82214d327a | |||
| 92430510b6 | |||
| 8a08520267 | |||
| 0d47b51f3f | |||
| cd628f48b7 | |||
| dc6377179c | |||
| 2c0a1e44ec | |||
| 6f691fcd75 | |||
| 3155a2bbe8 | |||
| 91a82c3736 | |||
| 8ea5207cde | |||
| deb086f4b9 | |||
| c7a199fb2f | |||
| 658ac10375 | |||
| 94269c7a87 | |||
| 8ba3dac0b2 | |||
| ddf8558578 | |||
| 883610878a | |||
| 69e93f4def | |||
| 9365e8f0c5 | |||
| a4a30d1bea | |||
| 7f2256bcbb | |||
| cf46ec26c4 | |||
| 9fb5e43a0b | |||
| 7fc979053e |
@@ -0,0 +1,18 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{kt, kts}]
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
continuation_indent_size=4
|
||||
max_line_length = 120
|
||||
disabled_rules = no-wildcard-imports
|
||||
kotlin_imports_layout = idea
|
||||
+14
-1
@@ -26,4 +26,17 @@ out/
|
||||
.idea_modules/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
*.iws
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# yarn
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.yarn-integrity
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
*.zip
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
FROM openjdk:14-alpine as jdkbuilder
|
||||
RUN apk add --no-cache binutils
|
||||
ENV MODULES java.base,java.xml,jdk.httpserver
|
||||
RUN jlink --output /myjdk --module-path $JAVA_HOME/jmods --add-modules $MODULES --no-header-files --no-man-pages --strip-debug --compress=2
|
||||
RUN strip -p --strip-unneeded /myjdk/lib/server/libjvm.so
|
||||
|
||||
FROM maven:3.6.3-jdk-14 as builder
|
||||
WORKDIR /app
|
||||
COPY pom.xml .
|
||||
RUN mvn verify clean --fail-never
|
||||
COPY src/main src/main
|
||||
RUN mvn package
|
||||
|
||||
FROM alpine
|
||||
ENV APPLICATION_USER app
|
||||
RUN adduser -D -g '' $APPLICATION_USER
|
||||
RUN mkdir /app
|
||||
RUN chown -R $APPLICATION_USER /app
|
||||
USER $APPLICATION_USER
|
||||
COPY --from=builder /app/target/kotlin-starter*.jar /app/app.jar
|
||||
COPY --from=jdkbuilder /myjdk /myjdk
|
||||
COPY config.toml /app/config.toml
|
||||
WORKDIR /app
|
||||
EXPOSE 7000
|
||||
CMD ["/myjdk/bin/java", "-server", "-Xmx64m", "-XX:+UseG1GC", "-XX:+UseStringDeduplication", "-jar", "app.jar"]
|
||||
+144
-18
@@ -16,55 +16,181 @@ display = "Java Version"
|
||||
default = "1.4.10"
|
||||
display = "Kotlin Version"
|
||||
|
||||
[features]
|
||||
|
||||
[features.ktlint]
|
||||
default = true
|
||||
|
||||
[versions]
|
||||
http4k = "3.265.0"
|
||||
pebble = "3.1.4"
|
||||
caffeine = "2.8.5"
|
||||
logback = "1.2.3"
|
||||
mariadb = "2.6.2"
|
||||
h2 = "1.4.200"
|
||||
flyway = "7.0.0"
|
||||
hikaricp = "3.4.5"
|
||||
ktorm = "3.0.0"
|
||||
junit = "5.7.0"
|
||||
mockk = "1.10.0"
|
||||
hamkrest = "1.7.0.3"
|
||||
assertj = "3.17.2"
|
||||
kodein-di = "7.1.0"
|
||||
koin = "2.1.6"
|
||||
jackson = "2.11.2"
|
||||
kotlinx-serialization = "1.0-M1-1.4.0-rc"
|
||||
arrow-core = "0.10.5"
|
||||
|
||||
[repositories]
|
||||
|
||||
[repositories.jcenter]
|
||||
url = "https://jcenter.bintray.com"
|
||||
|
||||
[repositories.arrow]
|
||||
url = "https://dl.bintray.com/arrow-kt/arrow-kt/"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dependencies.http4k]
|
||||
groupId = "org.http4k"
|
||||
artifactId = "http4k-core"
|
||||
version = "3.260.0"
|
||||
category = "http4k"
|
||||
default = true
|
||||
|
||||
[dependencies.javalin]
|
||||
groupId = "io.javalin"
|
||||
artifactId = "javalin"
|
||||
version = "3.10.1"
|
||||
[dependencies.http4k-server-jetty]
|
||||
groupId = "org.http4k"
|
||||
version = "http4k"
|
||||
category = "http4k"
|
||||
default = true
|
||||
logger = "org.eclipse.jetty"
|
||||
|
||||
[dependencies.http4k-server-apache]
|
||||
groupId = "org.http4k"
|
||||
version = "http4k"
|
||||
category = "http4k"
|
||||
|
||||
[dependencies.http4k-client-apache]
|
||||
groupId = "org.http4k"
|
||||
version = "http4k"
|
||||
category = "http4k"
|
||||
|
||||
[dependencies.http4k-format-jackson]
|
||||
groupId = "org.http4k"
|
||||
version = "http4k"
|
||||
category = "http4k"
|
||||
|
||||
[dependencies.http4k-format-kotlinx-serialization]
|
||||
groupId = "org.http4k"
|
||||
version = "http4k"
|
||||
category = "http4k"
|
||||
|
||||
[dependencies.http4k-contract]
|
||||
groupId = "org.http4k"
|
||||
version = "http4k"
|
||||
category = "http4k"
|
||||
|
||||
[dependencies.pebble]
|
||||
groupId = "io.pebbletemplates"
|
||||
artifactId = "pebble"
|
||||
version = "3.1.4"
|
||||
default = true
|
||||
logger = "com.mitchellbosecke.pebble"
|
||||
|
||||
[dependencies.caffeine]
|
||||
groupId = "com.github.ben-manes.caffeine"
|
||||
|
||||
[dependencies.logback]
|
||||
groupId = "ch.qos.logback"
|
||||
artifactId = "logback-classic"
|
||||
version = "1.2.3"
|
||||
default = true
|
||||
|
||||
[dependencies.mariadb]
|
||||
groupId = "org.mariadb.jdbc"
|
||||
artifactId = "mariadb-java-client"
|
||||
version = "2.6.2"
|
||||
category = "database"
|
||||
|
||||
[dependencies.h2]
|
||||
groupId = "com.h2database"
|
||||
artifactId = "h2"
|
||||
version = "1.4.200"
|
||||
category = "database"
|
||||
|
||||
[dependencies.flyway]
|
||||
groupId = "org.flywaydb"
|
||||
artifactId = "flyway-core"
|
||||
version = "6.5.4"
|
||||
category = "database"
|
||||
logger = "org.flywaydb.core"
|
||||
|
||||
[dependencies.HikariCP]
|
||||
[dependencies.hikaricp]
|
||||
groupId = "com.zaxxer"
|
||||
artifactId = "HikariCP"
|
||||
version = "3.4.5"
|
||||
category = "database"
|
||||
logger = "com.zaxxer.hikari"
|
||||
|
||||
[dependencies.Ktorm]
|
||||
[dependencies.ktorm]
|
||||
groupId = "me.liuwj.ktorm"
|
||||
artifactId = "ktorm-core"
|
||||
version = "3.0.0"
|
||||
category = "database"
|
||||
logger = "me.liuwj.ktorm.database"
|
||||
|
||||
[dependencies.Ktorm-Mysql]
|
||||
[dependencies.ktorm-mysql]
|
||||
groupId = "me.liuwj.ktorm"
|
||||
artifactId = "ktorm-support-mysql"
|
||||
version = "3.0.0"
|
||||
version = "ktorm"
|
||||
category = "database"
|
||||
|
||||
[dependencies.junit]
|
||||
groupId = "org.junit.jupiter"
|
||||
artifactId = "junit-jupiter"
|
||||
scope = "test"
|
||||
category = "test"
|
||||
default = true
|
||||
|
||||
[dependencies.junit-params]
|
||||
groupId = "org.junit.jupiter"
|
||||
artifactId = "junit-jupiter-params"
|
||||
version = "junit"
|
||||
scope = "test"
|
||||
category = "test"
|
||||
default = true
|
||||
|
||||
[dependencies.mockk]
|
||||
groupId = "io.mockk"
|
||||
artifactId = "mockk"
|
||||
scope = "test"
|
||||
category = "test"
|
||||
|
||||
[dependencies.hamkrest]
|
||||
groupId = "com.natpryce"
|
||||
scope = "test"
|
||||
category = "test"
|
||||
|
||||
[dependencies.assertj]
|
||||
groupId = "org.assertj"
|
||||
artifactId = "assertj-core"
|
||||
scope = "test"
|
||||
category = "test"
|
||||
default = true
|
||||
|
||||
[dependencies.kodein-di]
|
||||
groupId = "org.kodein.di"
|
||||
artifactId = "kodein-di-jvm"
|
||||
category = "injection"
|
||||
repository = "jcenter"
|
||||
|
||||
[dependencies.koin]
|
||||
groupId = "org.koin"
|
||||
artifactId = "koin-core"
|
||||
category = "injection"
|
||||
repository = "jcenter"
|
||||
default = true
|
||||
|
||||
[dependencies.jackson]
|
||||
groupId = "com.fasterxml.jackson.module"
|
||||
artifactId = "jackson-module-kotlin"
|
||||
category = "serialization"
|
||||
|
||||
[dependencies.kotlinx-serialization]
|
||||
groupId = "org.jetbrains.kotlinx"
|
||||
artifactId = "kotlinx-serialization-runtime"
|
||||
category = "serialization"
|
||||
|
||||
[dependencies.arrow-core]
|
||||
groupId = "io.arrow-kt"
|
||||
repository = "arrow"
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>14</maven.compiler.target>
|
||||
<maven.compiler.source>14</maven.compiler.source>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<kotlin.version>1.4.10</kotlin.version>
|
||||
<kotlin.code.style>official</kotlin.code.style>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
@@ -26,11 +27,6 @@
|
||||
<artifactId>pebble</artifactId>
|
||||
<version>3.1.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.electronwill.night-config</groupId>
|
||||
<artifactId>toml</artifactId>
|
||||
@@ -42,13 +38,50 @@
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.javalin</groupId>
|
||||
<artifactId>javalin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<groupId>org.http4k</groupId>
|
||||
<artifactId>http4k-core</artifactId>
|
||||
<version>3.265.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.20</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.koin</groupId>
|
||||
<artifactId>koin-core</artifactId>
|
||||
<version>2.1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.30</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.17.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -61,13 +94,35 @@
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>starter.KotlinStarterKt</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>com.electronwill.night-config:*</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/maven/**</exclude>
|
||||
<exclude>META-INF/proguard/**</exclude>
|
||||
<exclude>META-INF/*.kotlin_module</exclude>
|
||||
<exclude>META-INF/DEPENDENCIES*</exclude>
|
||||
<exclude>META-INF/NOTICE*</exclude>
|
||||
<exclude>META-INF/LICENSE*</exclude>
|
||||
<exclude>LICENSE*</exclude>
|
||||
<exclude>META-INF/README*</exclude>
|
||||
<exclude>META-INF/native-image/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
@@ -93,7 +148,7 @@
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<jvmTarget>11</jvmTarget>
|
||||
<jvmTarget>14</jvmTarget>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -101,7 +156,51 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>ktlint</id>
|
||||
<phase>verify</phase>
|
||||
<configuration>
|
||||
<target name="ktlint">
|
||||
<java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
|
||||
classname="com.pinterest.ktlint.Main" classpathref="maven.plugin.classpath">
|
||||
<arg value="src/**/*.kt"/>
|
||||
</java>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>ktlint-format</id>
|
||||
<configuration>
|
||||
<target name="ktlint">
|
||||
<java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
|
||||
classname="com.pinterest.ktlint.Main" classpathref="maven.plugin.classpath">
|
||||
<arg value="-F"/>
|
||||
<arg value="src/**/*.kt"/>
|
||||
</java>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.pinterest</groupId>
|
||||
<artifactId>ktlint</artifactId>
|
||||
<version>0.39.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "css",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"css": "NODE_ENV=dev postcss build src/styles.pcss --output ../main/resources/assets/styles.css",
|
||||
"css-purge": "NODE_ENV=production postcss build src/styles.pcss --output ../main/resources/assets/styles.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^9.8.6",
|
||||
"cssnano": "^4.1.10",
|
||||
"postcss-cli": "^7.1.1",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-nested": "^4.2.3",
|
||||
"tailwindcss": "^1.5.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('postcss-import'),
|
||||
require('postcss-nested'),
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
require('cssnano')({
|
||||
preset: ['default', {
|
||||
discardComments: {
|
||||
removeAll: true,
|
||||
},
|
||||
}]
|
||||
}),
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
.btn {
|
||||
@apply font-semibold py-2 px-4 rounded;
|
||||
|
||||
&:focus {
|
||||
@apply outline-none shadow-outline;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply font-extrabold;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-purple {
|
||||
@apply bg-purple-400 text-gray-800;
|
||||
|
||||
&:focus {
|
||||
@apply bg-purple-600;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply bg-purple-600;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.category{
|
||||
@apply text-lg inline-block text-center font-semibold text-gray-800 bg-green-300 rounded-full px-3 py-1 my-2;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.input {
|
||||
@apply bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight;
|
||||
|
||||
&:focus {
|
||||
@apply outline-none bg-white border-purple-500;
|
||||
}
|
||||
}
|
||||
|
||||
.input-label {
|
||||
@apply block text-gray-500 font-bold mb-1 pr-4;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
.input-label {
|
||||
@apply text-right mb-0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*noinspection CssUnknownTarget*/
|
||||
@import "tailwindcss/base";
|
||||
|
||||
/*noinspection CssUnknownTarget*/
|
||||
@import "tailwindcss/components";
|
||||
|
||||
@import "button.pcss";
|
||||
@import "category.pcss";
|
||||
@import "inputs.pcss";
|
||||
|
||||
/*noinspection CssUnknownTarget*/
|
||||
@import "tailwindcss/utilities";
|
||||
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
purge: {
|
||||
content: [
|
||||
'../main/resources/views/**/*.twig',
|
||||
'../main/kotlin/starter/PebbleModule.kt',
|
||||
]
|
||||
},
|
||||
theme: {},
|
||||
variants: {
|
||||
textColor: ['responsive', 'hover', 'focus', 'group-hover'],
|
||||
},
|
||||
plugins: [
|
||||
],
|
||||
future: {
|
||||
removeDeprecatedGapUtilities: true,
|
||||
purgeLayersByDefault: true,
|
||||
},
|
||||
}
|
||||
+1885
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
||||
package starter
|
||||
|
||||
import com.electronwill.nightconfig.core.Config as NightConfig
|
||||
import com.electronwill.nightconfig.core.file.FileConfig
|
||||
|
||||
data class StarterConfig(val dependencies: List<Dependency>, val inputs: List<Input>)
|
||||
|
||||
class Config {
|
||||
fun load(): StarterConfig {
|
||||
val cfg = FileConfig.of("config.toml")
|
||||
cfg.load()
|
||||
val dependenciesNode: NightConfig = cfg["dependencies"]
|
||||
@Suppress("UNCHECKED_CAST") val dependenciesMap = dependenciesNode.valueMap() as Map<String, NightConfig>
|
||||
val dependencies = dependenciesMap.map { (name, values) ->
|
||||
Dependency(name, values["groupId"], values["artifactId"], values["version"], values.getOrElse("default", false))
|
||||
}
|
||||
|
||||
val inputsNode: NightConfig = cfg["inputs"]
|
||||
@Suppress("UNCHECKED_CAST") val inputMap = inputsNode.valueMap() as Map<String, NightConfig>
|
||||
val inputs = inputMap.map { (name, values) ->
|
||||
Input(name, values["display"], values["default"])
|
||||
}
|
||||
|
||||
return StarterConfig(dependencies, inputs)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package starter
|
||||
|
||||
fun main() {
|
||||
val config = Config()
|
||||
val loaded = config.load()
|
||||
val server = Server(Views(PebbleModule().engine()),loaded)
|
||||
server.run()
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package starter
|
||||
|
||||
data class Dependency(val name: String, val groupId: String, val artifactId: String, val version: String, val default: Boolean)
|
||||
data class Input(val name: String, val display: String, val value: String? = null)
|
||||
@@ -1,15 +0,0 @@
|
||||
package starter
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import com.mitchellbosecke.pebble.loader.ClasspathLoader
|
||||
|
||||
class PebbleModule {
|
||||
fun engine(): PebbleEngine {
|
||||
val loader = ClasspathLoader()
|
||||
loader.suffix = ".twig"
|
||||
return PebbleEngine.Builder()
|
||||
.loader(loader)
|
||||
.cacheActive(false)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package starter
|
||||
|
||||
import io.javalin.Javalin
|
||||
|
||||
class Server(private val views: Views, private val conf: StarterConfig) {
|
||||
fun run() {
|
||||
val app = Javalin.create().start(7000)
|
||||
|
||||
app.get("/") { ctx ->
|
||||
ctx.result(views.index(conf.dependencies, conf.inputs))
|
||||
ctx.contentType("text/html")
|
||||
}
|
||||
|
||||
app.post("/") { ctx ->
|
||||
val deps = conf.dependencies.filter {
|
||||
ctx.formParam(it.name) != null
|
||||
}
|
||||
|
||||
val inputKeys = conf.inputs.map { it.name }
|
||||
val inputs = ctx.formParamMap()
|
||||
.filter { it.key in inputKeys }
|
||||
.map { (name, value) ->
|
||||
conf.inputs.find { it.name == name }!!.copy(value = value.first())
|
||||
}
|
||||
|
||||
println()
|
||||
|
||||
val generatedPom = views.pom(deps, inputs)
|
||||
ctx.result(prettyPrintXml(generatedPom))
|
||||
ctx.contentType("text/xml")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package starter
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.StringWriter
|
||||
import java.util.logging.LogManager
|
||||
|
||||
private fun PebbleEngine.render(name: String, args: Map<String, Any?> = mapOf()): String {
|
||||
val template = getTemplate(name)
|
||||
val writer = StringWriter()
|
||||
template.evaluate(writer, args)
|
||||
return writer.toString()
|
||||
}
|
||||
|
||||
class Views(private val engine: PebbleEngine) {
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
fun index(dependencies: List<Dependency>, inputs: List<Input>) = engine.render("views/index",
|
||||
mapOf("dependencies" to dependencies, "inputs" to inputs)
|
||||
)
|
||||
|
||||
fun pom(dependencies: List<Dependency>, inputs: List<Input>): String {
|
||||
val args: MutableMap<String, Any?> = mutableMapOf(
|
||||
"dependencies" to dependencies,
|
||||
)
|
||||
|
||||
inputs.forEach {
|
||||
args[it.name] = it.value
|
||||
}
|
||||
|
||||
logger.debug(args.toString())
|
||||
|
||||
return engine.render("starter/pom",
|
||||
args
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package starter
|
||||
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.unloadKoinModules
|
||||
import starter.modules.*
|
||||
|
||||
fun main() {
|
||||
startKoin {
|
||||
modules(mainModule, pebbleModule, templateModule, configModule, routesModule)
|
||||
}
|
||||
unloadKoinModules(configModule)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package starter
|
||||
|
||||
enum class Category {
|
||||
Http4k, Injection, Database, Serialization, Test, Other
|
||||
}
|
||||
|
||||
enum class Scope {
|
||||
Compile, Test
|
||||
}
|
||||
|
||||
data class Dependency(
|
||||
val name: String,
|
||||
val groupId: String,
|
||||
val artifactId: String,
|
||||
val version: Version,
|
||||
val default: Boolean,
|
||||
val category: Category,
|
||||
val scope: Scope,
|
||||
val logger: String?,
|
||||
val repository: Repository?,
|
||||
)
|
||||
|
||||
data class Repository(val name: String, val url: String)
|
||||
|
||||
data class Input(val name: String, val display: String, val value: String? = null)
|
||||
|
||||
data class Feature(val name: String, val value: Boolean = false)
|
||||
|
||||
data class Project(
|
||||
val name: String,
|
||||
val basePackage: String,
|
||||
val inputs: List<Input>,
|
||||
val features: List<Feature>,
|
||||
val dependencies: List<Dependency>,
|
||||
val repositories: Set<Repository>,
|
||||
)
|
||||
|
||||
data class Version(val name: String, val value: String)
|
||||
@@ -0,0 +1,20 @@
|
||||
package starter
|
||||
|
||||
import starter.templates.Template
|
||||
import starter.utils.ZipOutput
|
||||
import starter.utils.sanitizeFilename
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
class ProjectZip(private val templates: List<Template>) {
|
||||
|
||||
fun createZip(project: Project): ByteArrayOutputStream {
|
||||
val projectName = sanitizeFilename(project.name)
|
||||
val zipOutput = ZipOutput()
|
||||
zipOutput.use { zip ->
|
||||
templates.filter { it.enabled(project) }.forEach { template ->
|
||||
zip.write(projectName + "/" + template.path(project), template.render(project))
|
||||
}
|
||||
}
|
||||
return zipOutput.outputStream
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package starter.config
|
||||
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig
|
||||
import starter.*
|
||||
import com.electronwill.nightconfig.core.Config as NightConfig
|
||||
|
||||
data class StarterConfig(
|
||||
val dependencies: List<Dependency>,
|
||||
val inputs: List<Input>,
|
||||
val features: List<Feature>,
|
||||
)
|
||||
|
||||
class Config(private val cfg: NightConfig) {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun NightConfig.configMap(key: String) = this.get<NightConfig>(key)
|
||||
?.valueMap() as Map<String, NightConfig>?
|
||||
?: emptyMap()
|
||||
|
||||
fun load(): StarterConfig {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val versions = cfg.get<UnmodifiableConfig>("versions")
|
||||
?.valueMap() as Map<String, String>?
|
||||
?: emptyMap()
|
||||
|
||||
val repositories = cfg.configMap("repositories")
|
||||
.map { (name, values) ->
|
||||
Repository(name, values["url"])
|
||||
}
|
||||
|
||||
val dependencies = cfg.configMap("dependencies")
|
||||
.map { (name, values) ->
|
||||
val versionKey: String = values["version"] ?: name
|
||||
val version = versions[versionKey] ?: error("Missing version for $name")
|
||||
|
||||
val repositoryName: String? = values["repository"]
|
||||
val repo = repositoryName?.let { repoName -> repositories.find { it.name == repoName } }
|
||||
|
||||
Dependency(
|
||||
name = name,
|
||||
groupId = values["groupId"],
|
||||
artifactId = values["artifactId"] ?: name,
|
||||
version = Version(versionKey, version),
|
||||
default = values.getOrElse("default", false),
|
||||
category = values.getEnumOrElse("category", Category.Other),
|
||||
scope = values.getEnumOrElse("scope", Scope.Compile),
|
||||
logger = values["logger"],
|
||||
repository = repo,
|
||||
)
|
||||
}
|
||||
|
||||
val inputs = cfg.configMap("inputs")
|
||||
.map { (name, values) ->
|
||||
Input(name, values["display"], values["default"])
|
||||
}
|
||||
|
||||
val features = cfg.configMap("features")
|
||||
.map { (name, values) ->
|
||||
Feature(name, values["default"] ?: false)
|
||||
}
|
||||
|
||||
return StarterConfig(dependencies, inputs, features)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package starter.extensions
|
||||
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.Status
|
||||
import starter.utils.sanitizeFilename
|
||||
import java.io.InputStream
|
||||
|
||||
inline fun Response.Companion.ok() = Response(Status.OK)
|
||||
inline fun Response.Companion.badRequest() = Response(Status.BAD_REQUEST)
|
||||
|
||||
fun attachment(value: InputStream, name: String, contentType: String) = { res: Response ->
|
||||
res.header("Content-Type", contentType)
|
||||
.header("Content-Disposition", "attachment; filename=\"${sanitizeFilename(name)}\"")
|
||||
.body(value)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package starter.modules
|
||||
|
||||
import com.electronwill.nightconfig.core.file.FileConfig
|
||||
import org.koin.dsl.module
|
||||
import starter.config.Config
|
||||
import com.electronwill.nightconfig.core.Config as NightConfig
|
||||
|
||||
val configModule = module {
|
||||
single<NightConfig> { FileConfig.of("config.toml").apply { load() } }
|
||||
single { Config(get()).load() }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package starter.modules
|
||||
|
||||
import org.http4k.routing.RoutingHttpHandler
|
||||
import org.http4k.server.SunHttp
|
||||
import org.http4k.server.asServer
|
||||
import org.koin.dsl.module
|
||||
import org.koin.dsl.onClose
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import starter.ProjectZip
|
||||
import starter.views.Views
|
||||
|
||||
val mainModule = module {
|
||||
single(createdAtStart = true) {
|
||||
get<Logger>().info("Starting on http://localhost:7000")
|
||||
get<RoutingHttpHandler>().asServer(SunHttp(7000)).start()
|
||||
} onClose { it?.stop() }
|
||||
|
||||
single { LoggerFactory.getLogger("Starter") }
|
||||
single { Views(get(), get()) }
|
||||
single { ProjectZip(getAll()) }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package starter.modules
|
||||
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
import starter.pebble.DepAsXmlPebbleFunction
|
||||
import starter.pebble.HasFeatureFilter
|
||||
import starter.utils.PebbleEngineBuilder
|
||||
import starter.utils.PebbleEngineBuilder.CacheType.ConcurrentMap
|
||||
import starter.utils.PebbleFilter
|
||||
import starter.utils.PebbleFunction
|
||||
|
||||
val pebbleModule = module {
|
||||
single { DepAsXmlPebbleFunction() } bind PebbleFunction::class
|
||||
single { HasFeatureFilter() } bind PebbleFilter::class
|
||||
|
||||
single {
|
||||
PebbleEngineBuilder {
|
||||
cache = ConcurrentMap
|
||||
functions(getAll())
|
||||
filters(getAll())
|
||||
classPath { suffix = ".twig" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package starter.modules
|
||||
|
||||
import org.http4k.core.then
|
||||
import org.http4k.filter.ServerFilters
|
||||
import org.http4k.routing.ResourceLoader
|
||||
import org.http4k.routing.routes
|
||||
import org.http4k.routing.static
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
import starter.routes.IndexRouteSupplier
|
||||
import starter.routes.RouteSupplier
|
||||
import starter.routes.ZipRouteSupplier
|
||||
import starter.routes.toRouter
|
||||
import starter.utils.ProjectExtractor
|
||||
import starter.utils.ProjectExtractorImpl
|
||||
|
||||
val routesModule = module {
|
||||
single { IndexRouteSupplier(get()) } bind RouteSupplier::class
|
||||
single { ZipRouteSupplier(get(), get()) } bind RouteSupplier::class
|
||||
single<ProjectExtractor> { ProjectExtractorImpl(get()) }
|
||||
single {
|
||||
ServerFilters.CatchAll().then(
|
||||
routes(
|
||||
static(ResourceLoader.Classpath("/assets")),
|
||||
getAll<RouteSupplier>().toRouter()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package starter.modules
|
||||
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
import starter.templates.*
|
||||
|
||||
val templateModule = module {
|
||||
single { PomTemplate(get()) } bind Template::class
|
||||
single { MainTemplate(get()) } bind Template::class
|
||||
single { LogbackTemplate(get()) } bind Template::class
|
||||
single { GitignoreTemplate(get()) } bind Template::class
|
||||
single { JunitTemplate(get()) } bind Template::class
|
||||
single { EditorConfigTemplate(get()) } bind Template::class
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package starter.pebble
|
||||
|
||||
import com.mitchellbosecke.pebble.extension.escaper.SafeString
|
||||
import com.mitchellbosecke.pebble.template.EvaluationContext
|
||||
import com.mitchellbosecke.pebble.template.PebbleTemplate
|
||||
import org.intellij.lang.annotations.Language
|
||||
import starter.Dependency
|
||||
import starter.utils.PebbleFunction
|
||||
|
||||
// We need a custom function since pebble inserts whitespace everywhere
|
||||
class DepAsXmlPebbleFunction : PebbleFunction {
|
||||
override val name = "depAsXml"
|
||||
override fun getArgumentNames() = listOf("dependency")
|
||||
|
||||
override fun execute(
|
||||
args: Map<String, Any>,
|
||||
self: PebbleTemplate,
|
||||
context: EvaluationContext,
|
||||
lineNumber: Int,
|
||||
): SafeString {
|
||||
val dep = args["dependency"] as Dependency
|
||||
|
||||
fun tagName(name: String) = """<span class="text-red-700">$name</span>"""
|
||||
|
||||
fun startTag(name: String): String {
|
||||
@Language("html") @Suppress("UnnecessaryVariable")
|
||||
val result = """<span class="text-gray-700"><</span>""" +
|
||||
"""${tagName(name)}<span class="text-gray-700">></span>"""
|
||||
return result
|
||||
}
|
||||
|
||||
fun endTag(name: String): String {
|
||||
@Language("html") @Suppress("UnnecessaryVariable")
|
||||
val result = """<span class="text-gray-700"></</span>""" +
|
||||
"""${tagName(name)}<span class="text-gray-700">></span>"""
|
||||
return result
|
||||
}
|
||||
|
||||
fun tag(name: String, content: String) = """${startTag(name)}$content${endTag(name)}"""
|
||||
|
||||
val result = """
|
||||
|${startTag("dependency")}
|
||||
| ${tag("groupId", dep.groupId)}
|
||||
| ${tag("artifactId", dep.artifactId)}
|
||||
| ${tag("version", dep.version.value)}
|
||||
|${endTag("dependency")}
|
||||
""".trimMargin()
|
||||
|
||||
return SafeString(result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package starter.pebble
|
||||
|
||||
import com.mitchellbosecke.pebble.template.EvaluationContext
|
||||
import com.mitchellbosecke.pebble.template.PebbleTemplate
|
||||
import starter.Feature
|
||||
import starter.utils.PebbleFilter
|
||||
|
||||
class HasFeatureFilter : PebbleFilter {
|
||||
override val name = "hasFeature"
|
||||
|
||||
override fun getArgumentNames() = listOf("name")
|
||||
|
||||
override fun apply(
|
||||
input: Any,
|
||||
args: MutableMap<String, Any>,
|
||||
self: PebbleTemplate,
|
||||
context: EvaluationContext,
|
||||
lineNumber: Int,
|
||||
): Boolean {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val features = input as List<Feature>
|
||||
|
||||
val name = args["name"] as String
|
||||
|
||||
return features.any { it.name == name }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package starter.routes
|
||||
|
||||
import org.http4k.core.Method
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.Status
|
||||
import org.http4k.routing.bind
|
||||
import starter.views.Views
|
||||
|
||||
class IndexRouteSupplier(private val views: Views) : RouteSupplier {
|
||||
override fun get() = "/" bind Method.GET to {
|
||||
Response(Status.OK)
|
||||
.body(views.index())
|
||||
.header("Content-Type", "text/html")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package starter.routes
|
||||
|
||||
import org.http4k.routing.RoutingHttpHandler
|
||||
import org.http4k.routing.routes
|
||||
|
||||
interface RouteSupplier {
|
||||
fun get(): RoutingHttpHandler
|
||||
}
|
||||
|
||||
fun List<RouteSupplier>.toRouter() = routes(*map { it.get() }.toTypedArray())
|
||||
@@ -0,0 +1,35 @@
|
||||
package starter.routes
|
||||
|
||||
import org.http4k.core.Method
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.with
|
||||
import org.http4k.routing.bind
|
||||
import starter.ProjectZip
|
||||
import starter.extensions.attachment
|
||||
import starter.extensions.badRequest
|
||||
import starter.extensions.ok
|
||||
import starter.utils.ProjectExtractor
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
class ZipRouteSupplier(
|
||||
private val projectExtractor: ProjectExtractor,
|
||||
private val projectZip: ProjectZip,
|
||||
) : RouteSupplier {
|
||||
|
||||
private fun handle(req: Request): Response {
|
||||
val project = projectExtractor(req) ?: return Response.badRequest()
|
||||
|
||||
val outputStream = projectZip.createZip(project)
|
||||
|
||||
return Response.ok().with(
|
||||
attachment(
|
||||
value = ByteArrayInputStream(outputStream.toByteArray()),
|
||||
name = "${project.name}.zip",
|
||||
contentType = "application/zip"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun get() = "/" bind Method.POST to ::handle
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package starter.templates
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import starter.Project
|
||||
import starter.utils.render
|
||||
|
||||
class EditorConfigTemplate(private val engine: PebbleEngine) : Template {
|
||||
override fun path(project: Project) =
|
||||
".editorconfig"
|
||||
|
||||
override fun enabled(project: Project) = project.features.any { it.name == "ktlint" }
|
||||
|
||||
override fun render(project: Project) =
|
||||
engine.render("starter/editorconfig/index")
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package starter.templates
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import starter.Project
|
||||
import starter.utils.render
|
||||
|
||||
class GitignoreTemplate(private val engine: PebbleEngine) : Template {
|
||||
override fun path(project: Project) =
|
||||
".gitignore"
|
||||
|
||||
override fun render(project: Project) =
|
||||
engine.render("starter/gitignore/index")
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package starter.templates
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import starter.Project
|
||||
import starter.utils.render
|
||||
|
||||
class JunitTemplate(private val engine: PebbleEngine) : Template {
|
||||
override fun path(project: Project) = "src/test/resources/junit-platform.properties"
|
||||
override fun enabled(project: Project) = project.dependencies.any { it.name == "junit" }
|
||||
override fun render(project: Project) = engine.render("starter/junit/index")
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package starter.templates
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import starter.Project
|
||||
import starter.utils.prettyPrintXml
|
||||
import starter.utils.render
|
||||
|
||||
class LogbackTemplate(private val engine: PebbleEngine) : Template {
|
||||
override fun path(project: Project) = "src/main/resources/logback.xml"
|
||||
|
||||
override fun enabled(project: Project) = project.dependencies.any { it.name == "logback" }
|
||||
|
||||
override fun render(project: Project): String {
|
||||
val args = mapOf(
|
||||
"loggers" to project.dependencies.mapNotNull { it.logger }.toSet()
|
||||
)
|
||||
val rendered = engine.render("starter/logback/index", args)
|
||||
return prettyPrintXml(rendered)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package starter.templates
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import starter.Project
|
||||
import starter.utils.render
|
||||
|
||||
class MainTemplate(private val engine: PebbleEngine) : Template {
|
||||
override fun path(project: Project) =
|
||||
"src/main/kotlin/" + project.name.toLowerCase().capitalize() + ".kt"
|
||||
|
||||
override fun render(project: Project) =
|
||||
engine.render("starter/main/index", mapOf("basePackage" to project.basePackage))
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package starter.templates
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import starter.Project
|
||||
import starter.utils.prettyPrintXml
|
||||
import starter.utils.render
|
||||
|
||||
class PomTemplate(private val engine: PebbleEngine) : Template {
|
||||
override fun path(project: Project) = "pom.xml"
|
||||
|
||||
override fun render(project: Project): String {
|
||||
val args: MutableMap<String, Any?> = mutableMapOf(
|
||||
"dependencies" to project.dependencies.sortedBy { it.scope },
|
||||
"repositories" to project.repositories,
|
||||
"kotlinxSerialization" to project.dependencies.any { it.name == "kotlinx-serialization" },
|
||||
"features" to project.features,
|
||||
"versions" to project.dependencies.map { it.version }.toSet()
|
||||
)
|
||||
|
||||
project.inputs.forEach {
|
||||
args[it.name] = it.value
|
||||
}
|
||||
|
||||
val rendered = engine.render("starter/pom/index", args)
|
||||
return prettyPrintXml(rendered)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package starter.templates
|
||||
|
||||
import starter.Project
|
||||
|
||||
interface Template {
|
||||
fun path(project: Project): String
|
||||
fun enabled(project: Project): Boolean = true
|
||||
fun render(project: Project): String
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package starter.utils
|
||||
|
||||
fun sanitizeFilename(inputName: String): String = inputName.replace("[^a-zA-Z0-9-_.]".toRegex(), "_")
|
||||
@@ -0,0 +1,91 @@
|
||||
package starter.utils
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import com.mitchellbosecke.pebble.cache.tag.CaffeineTagCache
|
||||
import com.mitchellbosecke.pebble.cache.tag.ConcurrentMapTagCache
|
||||
import com.mitchellbosecke.pebble.cache.tag.NoOpTagCache
|
||||
import com.mitchellbosecke.pebble.cache.template.CaffeineTemplateCache
|
||||
import com.mitchellbosecke.pebble.cache.template.ConcurrentMapTemplateCache
|
||||
import com.mitchellbosecke.pebble.cache.template.NoOpTemplateCache
|
||||
import com.mitchellbosecke.pebble.extension.AbstractExtension
|
||||
import com.mitchellbosecke.pebble.extension.Filter
|
||||
import com.mitchellbosecke.pebble.extension.Function
|
||||
import com.mitchellbosecke.pebble.loader.ClasspathLoader
|
||||
import java.io.StringWriter
|
||||
|
||||
fun PebbleEngine.render(name: String, args: Map<String, Any?> = mapOf()) =
|
||||
getTemplate(name).let { StringWriter().apply { it.evaluate(this, args) }.toString() }
|
||||
|
||||
fun PebbleEngine.render(name: String, vararg args: Pair<String, Any?>) =
|
||||
render(name, mapOf(*args))
|
||||
|
||||
interface PebbleFunction : Function {
|
||||
val name: String
|
||||
}
|
||||
|
||||
interface PebbleFilter : Filter {
|
||||
val name: String
|
||||
}
|
||||
|
||||
class PebbleEngineBuilder {
|
||||
enum class CacheType {
|
||||
None, Caffeine, ConcurrentMap
|
||||
}
|
||||
|
||||
private var loader = ClasspathLoader()
|
||||
var cache = CacheType.ConcurrentMap
|
||||
|
||||
private val functions = mutableListOf<PebbleFunction>()
|
||||
private val filters = mutableListOf<PebbleFilter>()
|
||||
|
||||
fun function(function: PebbleFunction) {
|
||||
functions.add(function)
|
||||
}
|
||||
|
||||
fun functions(functions: Iterable<PebbleFunction>) {
|
||||
this.functions.addAll(functions)
|
||||
}
|
||||
|
||||
fun filter(filter: PebbleFilter) {
|
||||
filters.add(filter)
|
||||
}
|
||||
|
||||
fun filters(filters: Iterable<PebbleFilter>) {
|
||||
this.filters.addAll(filters)
|
||||
}
|
||||
|
||||
fun classPath(block: ClasspathLoader.() -> Unit) {
|
||||
loader = ClasspathLoader().apply(block)
|
||||
}
|
||||
|
||||
companion object {
|
||||
operator fun invoke(block: PebbleEngineBuilder.() -> Unit): PebbleEngine =
|
||||
PebbleEngineBuilder().apply(block).let { builder ->
|
||||
PebbleEngine.Builder()
|
||||
.loader(builder.loader)
|
||||
.cacheActive(builder.cache != CacheType.None)
|
||||
.templateCache(
|
||||
when (builder.cache) {
|
||||
CacheType.None -> NoOpTemplateCache()
|
||||
CacheType.Caffeine -> CaffeineTemplateCache()
|
||||
CacheType.ConcurrentMap -> ConcurrentMapTemplateCache()
|
||||
}
|
||||
)
|
||||
.tagCache(
|
||||
when (builder.cache) {
|
||||
CacheType.None -> NoOpTagCache()
|
||||
CacheType.Caffeine -> CaffeineTagCache()
|
||||
CacheType.ConcurrentMap -> ConcurrentMapTagCache()
|
||||
}
|
||||
)
|
||||
.extension(object : AbstractExtension() {
|
||||
override fun getFunctions(): Map<String, Function>? =
|
||||
builder.functions.associateBy { it.name }.ifEmpty { null }
|
||||
|
||||
override fun getFilters(): Map<String, Filter>? =
|
||||
builder.filters.associateBy { it.name }.ifEmpty { null }
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package starter.utils
|
||||
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.body.form
|
||||
import org.http4k.core.body.formAsMap
|
||||
import starter.Project
|
||||
import starter.config.StarterConfig
|
||||
|
||||
interface ProjectExtractor {
|
||||
operator fun invoke(request: Request): Project?
|
||||
}
|
||||
|
||||
class ProjectExtractorImpl(private val conf: StarterConfig) : ProjectExtractor {
|
||||
override fun invoke(request: Request): Project? {
|
||||
val deps = conf.dependencies.filter {
|
||||
request.form(it.name) != null
|
||||
}
|
||||
|
||||
val formMap = request.formAsMap()
|
||||
|
||||
val inputKeys = conf.inputs.map { it.name }
|
||||
val inputs = formMap
|
||||
.filter { it.key in inputKeys }
|
||||
.map { (name, value) ->
|
||||
conf.inputs.find { it.name == name }!!.copy(value = value.first())
|
||||
}
|
||||
|
||||
val features = formMap
|
||||
.filter { it.key in conf.features.map { it.name } }
|
||||
.map { (name, _) ->
|
||||
conf.features.find { it.name == name }!!.copy(value = true)
|
||||
}
|
||||
|
||||
val projectName = inputs.find { it.name == "name" }?.value ?: return null
|
||||
val basePackage = inputs.find { it.name == "basePackage" }?.value ?: return null
|
||||
|
||||
return if (basePackage.contains("/") || basePackage.contains("..")) {
|
||||
null
|
||||
} else {
|
||||
val repositories = deps.mapNotNull { it.repository }.toSet()
|
||||
Project(projectName, basePackage, inputs, features, deps, repositories)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package starter
|
||||
package starter.utils
|
||||
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.NodeList
|
||||
@@ -13,22 +13,23 @@ import javax.xml.transform.stream.StreamResult
|
||||
import javax.xml.xpath.XPathConstants
|
||||
import javax.xml.xpath.XPathFactory
|
||||
|
||||
|
||||
/*
|
||||
@see https://stackoverflow.com/a/33541820
|
||||
*/
|
||||
fun prettyPrintXml(xml: String, indent: Int = 4): String {
|
||||
// Turn xml string into a document
|
||||
val document: Document = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder()
|
||||
.parse(InputSource(ByteArrayInputStream(xml.encodeToByteArray())))
|
||||
.newDocumentBuilder()
|
||||
.parse(InputSource(ByteArrayInputStream(xml.encodeToByteArray())))
|
||||
|
||||
// Remove whitespaces outside tags
|
||||
document.normalize()
|
||||
val xPath = XPathFactory.newInstance().newXPath()
|
||||
val nodeList = xPath.evaluate("//text()[normalize-space()='']",
|
||||
document,
|
||||
XPathConstants.NODESET) as NodeList
|
||||
val nodeList = xPath.evaluate(
|
||||
"//text()[normalize-space()='']",
|
||||
document,
|
||||
XPathConstants.NODESET
|
||||
) as NodeList
|
||||
for (i in 0 until nodeList.length) {
|
||||
val node = nodeList.item(i)
|
||||
node.parentNode.removeChild(node)
|
||||
@@ -46,4 +47,4 @@ fun prettyPrintXml(xml: String, indent: Int = 4): String {
|
||||
val stringWriter = StringWriter()
|
||||
transformer.transform(DOMSource(document), StreamResult(stringWriter))
|
||||
return stringWriter.toString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package starter.utils
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
class ZipOutput : AutoCloseable {
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
private val zipOutputStream = ZipArchiveOutputStream(outputStream)
|
||||
|
||||
fun write(path: String, content: String) {
|
||||
val entry = ZipArchiveEntry(path)
|
||||
zipOutputStream.putArchiveEntry(entry)
|
||||
zipOutputStream.write(content.toByteArray())
|
||||
zipOutputStream.closeArchiveEntry()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
zipOutputStream.finish()
|
||||
zipOutputStream.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package starter.views
|
||||
|
||||
import com.mitchellbosecke.pebble.PebbleEngine
|
||||
import starter.config.StarterConfig
|
||||
import starter.utils.render
|
||||
|
||||
class Views(private val engine: PebbleEngine, config: StarterConfig) {
|
||||
private val args = mapOf(
|
||||
"dependencies" to config.dependencies.groupBy { it.category }.toSortedMap(),
|
||||
"inputs" to config.inputs,
|
||||
"features" to config.features,
|
||||
)
|
||||
|
||||
fun index() = engine.render("views/index", args)
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
|
||||
<dependencies>
|
||||
{% for dep in dependencies %}
|
||||
<dependency>
|
||||
<groupId>{{ dep.groupId }}</groupId>
|
||||
<artifactId>{{ dep.artifactId }}</artifactId>
|
||||
<version>{{ dep.version }}</version>
|
||||
</dependency>
|
||||
{% endfor %}
|
||||
</dependencies>
|
||||
@@ -1,11 +0,0 @@
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
{% include "starter/plugins/@default" %}
|
||||
|
||||
{% include "starter/plugins/@kotlin" %}
|
||||
|
||||
{% include "starter/plugins/@shade" %}
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
@@ -1,7 +0,0 @@
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jcenter</id>
|
||||
<name>jcenter</name>
|
||||
<url>https://jcenter.bintray.com</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
@@ -0,0 +1,18 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{kt, kts}]
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
continuation_indent_size=4
|
||||
max_line_length = 120
|
||||
disabled_rules = no-wildcard-imports
|
||||
kotlin_imports_layout = idea
|
||||
@@ -0,0 +1,5 @@
|
||||
target/
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
@@ -0,0 +1,4 @@
|
||||
junit.jupiter.testinstance.lifecycle.default=per_class
|
||||
junit.jupiter.execution.parallel.enabled=true
|
||||
junit.jupiter.execution.parallel.mode.default=same_thread
|
||||
junit.jupiter.execution.parallel.mode.classes.default=concurrent
|
||||
@@ -9,6 +9,7 @@
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
<logger name="com.mitchellbosecke.pebble" level="INFO"/>
|
||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||
{% for logger in loggers %}
|
||||
<logger name="{{ logger }}" level="INFO"/>
|
||||
{% endfor %}
|
||||
</configuration>
|
||||
@@ -0,0 +1,6 @@
|
||||
package {{ basePackage }}
|
||||
|
||||
|
||||
fun main() {
|
||||
println("Hello world!")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
</dependency>
|
||||
{% for dep in dependencies %}
|
||||
<dependency>
|
||||
<groupId>{{ dep.groupId }}</groupId>
|
||||
<artifactId>{{ dep.artifactId }}</artifactId>
|
||||
<version>{{ "${" }}{{ dep.version.name }}{{ ".version}" }}</version>
|
||||
{% if dep.scope == "Test" %}
|
||||
<scope>test</scope>
|
||||
{% endif %}
|
||||
</dependency>
|
||||
{% endfor %}
|
||||
</dependencies>
|
||||
@@ -0,0 +1,11 @@
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-bom</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
@@ -0,0 +1,16 @@
|
||||
<build>
|
||||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||
|
||||
<plugins>
|
||||
|
||||
{% include "starter/pom/plugins/@default" %}
|
||||
|
||||
{% include "starter/pom/plugins/@kotlin" %}
|
||||
|
||||
{% include "starter/pom/plugins/@shade" %}
|
||||
|
||||
{% include "starter/pom/plugins/@ktlint" %}
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
@@ -0,0 +1,10 @@
|
||||
{% if repositories is not empty %}
|
||||
<repositories>
|
||||
{% for repo in repositories %}
|
||||
<repository>
|
||||
<id>{{ repo.name }}</id>
|
||||
<url>{{ repo.url }}</url>
|
||||
</repository>
|
||||
{% endfor %}
|
||||
</repositories>
|
||||
{% endif %}
|
||||
@@ -8,16 +8,24 @@
|
||||
<properties>
|
||||
<java.version>{{ javaVersion }}</java.version>
|
||||
<kotlin.version>{{ kotlinVersion }}</kotlin.version>
|
||||
<kotlin.code.style>official</kotlin.code.style>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<main.class>{{ basePackage }}/{{ name | lower | capitalize }}Kt</main.class>
|
||||
<main.class>{{ basePackage }}.{{ name | lower | capitalize }}Kt</main.class>
|
||||
<!-- versions -->
|
||||
{% for version in versions %}
|
||||
<{{version.name}}.version>{{version.value}}</{{version.name}}.version>
|
||||
{% endfor %}
|
||||
|
||||
</properties>
|
||||
|
||||
{% include "starter/@dependencies" %}
|
||||
{% include "starter/pom/@dependencies" %}
|
||||
|
||||
{% include "starter/@repositories" %}
|
||||
{% include "starter/pom/@repositories" %}
|
||||
|
||||
{% include "starter/@plugins" %}
|
||||
{% include "starter/pom/@dependencyManagement" %}
|
||||
|
||||
</project>
|
||||
{% include "starter/pom/@plugins" %}
|
||||
|
||||
</project>
|
||||
+15
-1
@@ -20,5 +20,19 @@
|
||||
</executions>
|
||||
<configuration>
|
||||
<jvmTarget>${java.version}</jvmTarget>
|
||||
{% if kotlinxSerialization %}
|
||||
<compilerPlugins>
|
||||
<plugin>kotlinx-serialization</plugin>
|
||||
</compilerPlugins>
|
||||
{% endif %}
|
||||
</configuration>
|
||||
</plugin>
|
||||
{% if kotlinxSerialization %}
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-serialization</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
{% endif %}
|
||||
</plugin>
|
||||
@@ -0,0 +1,46 @@
|
||||
{% if features | hasFeature("ktlint") %}
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>ktlint</id>
|
||||
<phase>verify</phase>
|
||||
<configuration>
|
||||
<target name="ktlint">
|
||||
<java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
|
||||
classname="com.pinterest.ktlint.Main" classpathref="maven.plugin.classpath">
|
||||
<arg value="src/**/*.kt"/>
|
||||
</java>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>ktlint-format</id>
|
||||
<configuration>
|
||||
<target name="ktlint">
|
||||
<java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
|
||||
classname="com.pinterest.ktlint.Main" classpathref="maven.plugin.classpath">
|
||||
<arg value="-F"/>
|
||||
<arg value="src/**/*.kt"/>
|
||||
</java>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.pinterest</groupId>
|
||||
<artifactId>ktlint</artifactId>
|
||||
<version>0.39.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
{% endif %}
|
||||
+2
-1
@@ -9,6 +9,7 @@
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
<transformers>
|
||||
<transformer
|
||||
@@ -19,4 +20,4 @@
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugin>
|
||||
@@ -0,0 +1,25 @@
|
||||
{% macro dependency(dependency) %}
|
||||
<details>
|
||||
<summary>
|
||||
<label class="m-2">
|
||||
<input name="{{ dependency.name }}"
|
||||
type="checkbox"{% if dependency.default %} checked{% endif %}>
|
||||
<span>{{ dependency.name }}</span>
|
||||
</label>
|
||||
</summary>
|
||||
<pre><code>{{ depAsXml(dependency) }}</code></pre>
|
||||
</details>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro input(input) %}
|
||||
<div class="md:flex md:items-center mb-6">
|
||||
<div class="md:w-1/3">
|
||||
<label class="input-label" for="{{ input.name }}">
|
||||
{{ input.display }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="md:w-2/3">
|
||||
<input name="{{ input.name }}" id="{{ input.name }}" class="input"{% if input.value %} value="{{ input.value }}"{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -3,10 +3,13 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
<title>Kotlin Starter</title>
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
<main class="container mx-auto">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +1,39 @@
|
||||
{% extends "views/@base" %}
|
||||
|
||||
{% macro dependency(dependency) %}
|
||||
<label>
|
||||
<input name="{{ dependency.name }}" type="checkbox"{% if dependency.default %} checked{% endif %}>
|
||||
<span>{{ dependency.name }}</span>
|
||||
</label>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro input(input) %}
|
||||
<label>
|
||||
<span>{{ input.display }}</span>
|
||||
<input name="{{ input.name }}" type="text"{% if input.value %} value="{{ input.value }}"{% endif %}>
|
||||
</label>
|
||||
{% endmacro %}
|
||||
{% import "views/#macros" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Kotlin Starter</h1>
|
||||
<form method="post">
|
||||
<h1 class="text-2xl font-bold text-purple-800 mb-4">Kotlin Starter</h1>
|
||||
<form method="post" class="w-full max-w-sm mx-auto">
|
||||
{% for input in inputs %}
|
||||
{{ input(input) }}
|
||||
{% endfor %}
|
||||
<br>
|
||||
{% for dependency in dependencies %}
|
||||
{{ dependency(dependency) }}
|
||||
{% endfor %}
|
||||
<br>
|
||||
<button type="submit">Submit</button>
|
||||
|
||||
<div class="mt-4">
|
||||
<section>
|
||||
<h2 class="category">Features</h2>
|
||||
<ul>
|
||||
{% for feature in features %}
|
||||
<label class="m-2">
|
||||
<input name="{{ feature.name }}"
|
||||
type="checkbox"{% if feature.value %} checked{% endif %}>
|
||||
<span>{{ feature.name }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{% for category in dependencies %}
|
||||
<section>
|
||||
<h2 class="category">{{ category.key }}</h2>
|
||||
<ul>
|
||||
{% for dependency in category.value %}
|
||||
<li>{{ dependency(dependency) }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="submit" class="my-4 w-full btn btn-purple">Submit</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package starter
|
||||
|
||||
import starter.config.StarterConfig
|
||||
|
||||
val testConfig = StarterConfig(
|
||||
dependencies = listOf(
|
||||
Dependency(
|
||||
name = "h2",
|
||||
groupId = "com.h2database",
|
||||
artifactId = "h2",
|
||||
version = Version("h2", "1.4.200"),
|
||||
default = false,
|
||||
category = Category.Database,
|
||||
scope = Scope.Compile,
|
||||
logger = null,
|
||||
repository = null,
|
||||
),
|
||||
Dependency(
|
||||
name = "assertj",
|
||||
groupId = "org.assertj",
|
||||
artifactId = "assertj-core",
|
||||
version = Version("assertj", "3.17.2"),
|
||||
default = true,
|
||||
category = Category.Test,
|
||||
scope = Scope.Test,
|
||||
logger = null,
|
||||
repository = null,
|
||||
),
|
||||
Dependency(
|
||||
name = "koin",
|
||||
groupId = "org.koin",
|
||||
artifactId = "koin-core",
|
||||
version = Version("koin", "2.1.6"),
|
||||
default = true,
|
||||
category = Category.Injection,
|
||||
scope = Scope.Compile,
|
||||
logger = null,
|
||||
repository = Repository("jcenter", "https://jcenter.bintray.com"),
|
||||
),
|
||||
),
|
||||
inputs = listOf(
|
||||
Input(name = "name", display = "name", value = "example"),
|
||||
Input(name = "basePackage", display = "Base package", value = "org.example"),
|
||||
),
|
||||
features = listOf(Feature("ktlint", true))
|
||||
)
|
||||
@@ -0,0 +1,114 @@
|
||||
package starter.config
|
||||
|
||||
import com.electronwill.nightconfig.toml.TomlParser
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.jupiter.api.Test
|
||||
import starter.*
|
||||
import java.io.StringReader
|
||||
|
||||
internal class ConfigTest {
|
||||
private val parser = TomlParser()
|
||||
private fun parse(config: String) = parser.parse(StringReader(config))
|
||||
|
||||
@Test
|
||||
fun inputs() {
|
||||
@Language("toml")
|
||||
val toml = """
|
||||
[inputs]
|
||||
|
||||
[inputs.name]
|
||||
default = "example"
|
||||
display = "Project Name"
|
||||
|
||||
[inputs.basePackage]
|
||||
default = "org.example"
|
||||
display = "Base package"
|
||||
""".trimIndent()
|
||||
|
||||
val starterConfig = Config(parse(toml)).load()
|
||||
|
||||
val expected = listOf(
|
||||
Input("basePackage", "Base package", "org.example"),
|
||||
Input("name", "Project Name", "example"),
|
||||
)
|
||||
|
||||
assertThat(starterConfig.inputs).containsExactlyInAnyOrderElementsOf(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dependencies() {
|
||||
@Language("toml")
|
||||
val toml = """
|
||||
[versions]
|
||||
assertj = "3.17.2"
|
||||
koin = "2.1.6"
|
||||
h2 = "1.4.200"
|
||||
|
||||
[repositories]
|
||||
|
||||
[repositories.jcenter]
|
||||
url = "https://jcenter.bintray.com"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dependencies.assertj]
|
||||
groupId = "org.assertj"
|
||||
artifactId = "assertj-core"
|
||||
scope = "test"
|
||||
category = "test"
|
||||
default = true
|
||||
|
||||
[dependencies.koin]
|
||||
groupId = "org.koin"
|
||||
artifactId = "koin-core"
|
||||
category = "injection"
|
||||
repository = "jcenter"
|
||||
default = true
|
||||
|
||||
[dependencies.h2]
|
||||
groupId = "com.h2database"
|
||||
category = "database"
|
||||
""".trimIndent()
|
||||
|
||||
val starterConfig = Config(parse(toml)).load()
|
||||
|
||||
val expected = listOf(
|
||||
Dependency(
|
||||
name = "h2",
|
||||
groupId = "com.h2database",
|
||||
artifactId = "h2",
|
||||
version = Version("h2", "1.4.200"),
|
||||
default = false,
|
||||
category = Category.Database,
|
||||
scope = Scope.Compile,
|
||||
logger = null,
|
||||
repository = null,
|
||||
),
|
||||
Dependency(
|
||||
name = "assertj",
|
||||
groupId = "org.assertj",
|
||||
artifactId = "assertj-core",
|
||||
version = Version("assertj", "3.17.2"),
|
||||
default = true,
|
||||
category = Category.Test,
|
||||
scope = Scope.Test,
|
||||
logger = null,
|
||||
repository = null,
|
||||
),
|
||||
Dependency(
|
||||
name = "koin",
|
||||
groupId = "org.koin",
|
||||
artifactId = "koin-core",
|
||||
version = Version("koin", "2.1.6"),
|
||||
default = true,
|
||||
category = Category.Injection,
|
||||
scope = Scope.Compile,
|
||||
logger = null,
|
||||
repository = Repository("jcenter", "https://jcenter.bintray.com"),
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(starterConfig.dependencies).containsExactlyInAnyOrderElementsOf(expected)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package starter.templates
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.koin.dsl.koinApplication
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
import starter.Project
|
||||
import starter.config.StarterConfig
|
||||
import starter.modules.configModule
|
||||
import starter.modules.pebbleModule
|
||||
import starter.modules.templateModule
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.xpath.XPathConstants
|
||||
import javax.xml.xpath.XPathFactory
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class PomTemplateTest {
|
||||
|
||||
private val koin = koinApplication {
|
||||
modules(configModule, pebbleModule, templateModule)
|
||||
}.koin
|
||||
|
||||
private val pomTemplate = koin.get<PomTemplate>()
|
||||
private val conf = koin.get<StarterConfig>()
|
||||
|
||||
private val project = Project(
|
||||
name = "Test",
|
||||
basePackage = "org.test",
|
||||
inputs = conf.inputs,
|
||||
features = emptyList(),
|
||||
dependencies = conf.dependencies,
|
||||
repositories = conf.dependencies.mapNotNull { it.repository }.toSet()
|
||||
)
|
||||
|
||||
private val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
private val xPath = XPathFactory.newInstance().newXPath()
|
||||
|
||||
// region xml utils
|
||||
private fun String.extract(expression: String): String {
|
||||
val doc = docBuilder.parse(this.byteInputStream())
|
||||
xPath.compile(expression).evaluate(doc, XPathConstants.NODESET)
|
||||
return xPath.compile(expression).evaluate(doc)
|
||||
}
|
||||
|
||||
private fun <T> String.extractAll(expression: String, mapper: (Node) -> T): List<T> {
|
||||
val doc = docBuilder.parse(this.byteInputStream())
|
||||
val res = xPath.compile(expression).evaluate(doc, XPathConstants.NODESET) as NodeList
|
||||
return res.asList().map(mapper)
|
||||
}
|
||||
|
||||
private fun NodeList.asList(): List<Node> = (0 until length).map { item(it) }
|
||||
// endregion
|
||||
|
||||
@Test
|
||||
fun javaVersion() {
|
||||
val xml = pomTemplate.render(project)
|
||||
assertThat(xml.extract("/project/properties/java.version"))
|
||||
.isEqualTo(project.inputs.find { it.name == "javaVersion" }?.value)
|
||||
|
||||
println(xml)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dependencies() {
|
||||
val xml = pomTemplate.render(project)
|
||||
|
||||
val deps = xml.extractAll("/project/dependencies/dependency") {
|
||||
val map = it.childNodes.asList()
|
||||
.filter { it.nodeType == Node.ELEMENT_NODE }
|
||||
.associate { it.nodeName to it.firstChild.nodeValue }
|
||||
|
||||
Triple(map["groupId"]!!, map["artifactId"]!!, map["version"] ?: "")
|
||||
}.filterNot { it.second == "kotlin-stdlib-jdk8" }
|
||||
|
||||
println(deps.joinToString("\n"))
|
||||
|
||||
val expectedDependencies = project.dependencies
|
||||
.map { Triple(it.groupId, it.artifactId, "\${" + it.version.name + ".version}") }
|
||||
|
||||
assertThat(expectedDependencies).containsExactlyInAnyOrderElementsOf(deps)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun versions() {
|
||||
val xml = pomTemplate.render(project)
|
||||
|
||||
val versions = xml.extractAll("/project/properties") {
|
||||
it.childNodes.asList()
|
||||
.filter { it.nodeType == Node.ELEMENT_NODE }
|
||||
.filter { it.nodeName.endsWith(".version") }
|
||||
.filterNot { it.nodeName in listOf("java.version", "kotlin.version") }
|
||||
.associate { it.nodeName.substringBefore(".version") to it.firstChild.nodeValue }
|
||||
}.first()
|
||||
|
||||
val expected = project.dependencies.associate { it.version.name to it.version.value }
|
||||
|
||||
assertThat(versions).containsExactlyInAnyOrderEntriesOf(expected)
|
||||
|
||||
println(versions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun kotlinxSerialization() {
|
||||
val xml = pomTemplate.render(project)
|
||||
|
||||
@Language("XPath")
|
||||
val kotlinMavenPlugin = "/project/build/plugins/plugin[artifactId='kotlin-maven-plugin']"
|
||||
|
||||
val kotlinxPlugin = "$kotlinMavenPlugin/configuration/compilerPlugins/plugin"
|
||||
assertThat(xml.extract(kotlinxPlugin))
|
||||
.isEqualTo("kotlinx-serialization")
|
||||
|
||||
val kotlinxPluginDep = "$kotlinMavenPlugin/dependencies/dependency/artifactId"
|
||||
assertThat(xml.extract(kotlinxPluginDep))
|
||||
.isEqualTo("kotlin-maven-serialization")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package starter.utils
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.http4k.core.Method
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.body.Form
|
||||
import org.http4k.core.body.toBody
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import org.koin.dsl.koinApplication
|
||||
import org.koin.dsl.module
|
||||
import starter.Feature
|
||||
import starter.Input
|
||||
import starter.Project
|
||||
import starter.modules.routesModule
|
||||
import starter.testConfig
|
||||
import java.util.stream.Stream
|
||||
|
||||
internal class ProjectExtractorImplTest {
|
||||
|
||||
private val fakeModule = module {
|
||||
single { testConfig }
|
||||
}
|
||||
|
||||
private val koin = koinApplication {
|
||||
modules(routesModule, fakeModule)
|
||||
}.koin
|
||||
|
||||
private val projectExtractor = koin.get<ProjectExtractor>()
|
||||
|
||||
@Suppress("unused")
|
||||
fun invalidProjectsSource(): Stream<Form> = Stream.of(
|
||||
emptyList(),
|
||||
listOf(
|
||||
"basePackage" to "org.example",
|
||||
),
|
||||
listOf(
|
||||
"basePackage" to "org.example/a",
|
||||
"name" to "test"
|
||||
),
|
||||
)
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("invalidProjectsSource")
|
||||
fun invalidProjects(form: Form) {
|
||||
val request = Request(Method.POST, "")
|
||||
.body(form.toBody())
|
||||
|
||||
val project = projectExtractor(request)
|
||||
|
||||
assertThat(project).isNull()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun validProjectsSource(): Stream<Pair<Form, Project>> = Stream.of(
|
||||
listOf(
|
||||
"name" to "Test",
|
||||
"basePackage" to "org.example",
|
||||
"ktlint" to "",
|
||||
) to Project(
|
||||
name = "Test",
|
||||
basePackage = "org.example",
|
||||
inputs = listOf(
|
||||
Input(name = "name", display = "name", value = "Test"),
|
||||
Input(name = "basePackage", display = "Base package", value = "org.example"),
|
||||
),
|
||||
features = listOf(
|
||||
Feature(name = "ktlint", value = true)
|
||||
),
|
||||
dependencies = listOf(),
|
||||
repositories = setOf(),
|
||||
),
|
||||
listOf(
|
||||
"name" to "Test",
|
||||
"basePackage" to "org.example",
|
||||
"ktlint" to "",
|
||||
"koin" to "",
|
||||
) to Project(
|
||||
name = "Test",
|
||||
basePackage = "org.example",
|
||||
inputs = listOf(
|
||||
Input(name = "name", display = "name", value = "Test"),
|
||||
Input(name = "basePackage", display = "Base package", value = "org.example"),
|
||||
),
|
||||
features = listOf(
|
||||
Feature(name = "ktlint", value = true)
|
||||
),
|
||||
dependencies = listOf(
|
||||
testConfig.dependencies.find { it.name == "koin" }!!
|
||||
),
|
||||
repositories = setOf(
|
||||
testConfig.dependencies.find { it.name == "koin" }!!.repository!!
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("validProjectsSource")
|
||||
fun validProjects(pair: Pair<Form, Project>) {
|
||||
val (form, expected) = pair
|
||||
|
||||
val request = Request(Method.POST, "")
|
||||
.body(form.toBody())
|
||||
|
||||
val project = projectExtractor(request)
|
||||
|
||||
assertThat(project).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
junit.jupiter.testinstance.lifecycle.default=per_class
|
||||
junit.jupiter.execution.parallel.enabled=true
|
||||
junit.jupiter.execution.parallel.mode.default=same_thread
|
||||
junit.jupiter.execution.parallel.mode.classes.default=concurrent
|
||||
Reference in New Issue
Block a user