diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..237ae17
--- /dev/null
+++ b/.editorconfig
@@ -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
diff --git a/pom.xml b/pom.xml
index 6c404a2..fe74bfb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,6 +10,7 @@
11
UTF-8
1.4.10
+ official
@@ -51,6 +52,11 @@
koin-core
2.1.6
+
+ org.slf4j
+ slf4j-simple
+ 1.7.30
+
diff --git a/src/main/kotlin/starter/Config.kt b/src/main/kotlin/starter/Config.kt
index c625fc7..43989ea 100644
--- a/src/main/kotlin/starter/Config.kt
+++ b/src/main/kotlin/starter/Config.kt
@@ -4,9 +4,9 @@ import com.electronwill.nightconfig.core.file.FileConfig
import com.electronwill.nightconfig.core.Config as NightConfig
data class StarterConfig(
- val dependencies: List,
- val inputs: List,
- val repositories: List,
+ val dependencies: List,
+ val inputs: List,
+ val repositories: List,
)
class Config {
@@ -19,31 +19,30 @@ class Config {
cfg.load()
val dependencies = cfg.configMap("dependencies")
- .map { (name, values) ->
- Dependency(
- name,
- values["groupId"],
- values["artifactId"],
- values["version"],
- values.getOrElse("default", false),
- values.getEnumOrElse("category", Category.Other),
- values.getEnumOrElse("scope", Scope.Compile),
- values["logger"],
- values["repository"],
- )
- }
+ .map { (name, values) ->
+ Dependency(
+ name,
+ values["groupId"],
+ values["artifactId"],
+ values["version"],
+ values.getOrElse("default", false),
+ values.getEnumOrElse("category", Category.Other),
+ values.getEnumOrElse("scope", Scope.Compile),
+ values["logger"],
+ values["repository"],
+ )
+ }
val inputs = cfg.configMap("inputs")
- .map { (name, values) ->
- Input(name, values["display"], values["default"])
- }
+ .map { (name, values) ->
+ Input(name, values["display"], values["default"])
+ }
val repositories = cfg.configMap("repositories")
- .map { (name, values) ->
- Repository(name, values["url"])
- }
+ .map { (name, values) ->
+ Repository(name, values["url"])
+ }
return StarterConfig(dependencies, inputs, repositories)
}
-
}
diff --git a/src/main/kotlin/starter/KotlinStarter.kt b/src/main/kotlin/starter/KotlinStarter.kt
index 584d29a..4885bd2 100644
--- a/src/main/kotlin/starter/KotlinStarter.kt
+++ b/src/main/kotlin/starter/KotlinStarter.kt
@@ -1,14 +1,32 @@
package starter
+import org.http4k.routing.ResourceLoader
+import org.http4k.routing.RoutingHttpHandler
+import org.http4k.routing.routes
+import org.http4k.routing.static
+import org.http4k.server.SunHttp
+import org.http4k.server.asServer
import org.koin.core.context.startKoin
import org.koin.dsl.bind
import org.koin.dsl.module
+import org.koin.dsl.onClose
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import starter.routes.IndexRouteSupplier
+import starter.routes.RouteSupplier
+import starter.routes.ZipRouteSupplier
+import starter.routes.toRouter
import starter.templates.*
val mainModule = module {
+ single(createdAtStart = true) {
+ get().info("Starting on http://localhost:7000")
+ get().asServer(SunHttp(7000)).start()
+ } onClose { it?.stop() }
+
single { Config().load() }
single { PebbleModule().engine() }
- single { Server(get(), get(), get()) }
+ single { LoggerFactory.getLogger("Starter") }
single { Views(get()) }
single { ProjectZip(getAll()) }
}
@@ -20,10 +38,19 @@ val templateModule = module {
single { GitignoreTemplate(get()) } bind Template::class
}
-fun main() {
- val koin = startKoin {
- modules(mainModule, templateModule)
- }.koin
- val server = koin.get()
- server.run()
+val routesModule = module {
+ single { IndexRouteSupplier(get(), get()) } bind RouteSupplier::class
+ single { ZipRouteSupplier(get(), get()) } bind RouteSupplier::class
+ single {
+ routes(
+ static(ResourceLoader.Classpath("/assets")),
+ getAll().toRouter()
+ )
+ }
+}
+
+fun main() {
+ startKoin {
+ modules(mainModule, templateModule, routesModule)
+ }
}
diff --git a/src/main/kotlin/starter/Models.kt b/src/main/kotlin/starter/Models.kt
index f0a0886..eb27e2b 100644
--- a/src/main/kotlin/starter/Models.kt
+++ b/src/main/kotlin/starter/Models.kt
@@ -9,15 +9,15 @@ enum class Scope {
}
data class Dependency(
- val name: String,
- val groupId: String,
- val artifactId: String,
- val version: String,
- val default: Boolean,
- val category: Category,
- val scope: Scope,
- val logger: String?,
- val repository: String?,
+ val name: String,
+ val groupId: String,
+ val artifactId: String,
+ val version: String,
+ val default: Boolean,
+ val category: Category,
+ val scope: Scope,
+ val logger: String?,
+ val repository: String?,
)
data class Repository(val name: String, val url: String)
@@ -25,9 +25,9 @@ data class Repository(val name: String, val url: String)
data class Input(val name: String, val display: String, val value: String? = null)
data class Project(
- val name: String,
- val basePackage: String,
- val inputs: List,
- val dependencies: List,
- val repositories: List,
+ val name: String,
+ val basePackage: String,
+ val inputs: List,
+ val dependencies: List,
+ val repositories: List,
)
diff --git a/src/main/kotlin/starter/PebbleModule.kt b/src/main/kotlin/starter/PebbleModule.kt
index 48c1bd4..5c3bc98 100644
--- a/src/main/kotlin/starter/PebbleModule.kt
+++ b/src/main/kotlin/starter/PebbleModule.kt
@@ -8,8 +8,8 @@ class PebbleModule {
val loader = ClasspathLoader()
loader.suffix = ".twig"
return PebbleEngine.Builder()
- .loader(loader)
- .cacheActive(true)
- .build()
+ .loader(loader)
+ .cacheActive(true)
+ .build()
}
}
diff --git a/src/main/kotlin/starter/ProjectZip.kt b/src/main/kotlin/starter/ProjectZip.kt
index 5f20a0f..7613dba 100644
--- a/src/main/kotlin/starter/ProjectZip.kt
+++ b/src/main/kotlin/starter/ProjectZip.kt
@@ -17,5 +17,4 @@ class ProjectZip(private val templates: List) {
}
return zipOutput.outputStream
}
-
}
diff --git a/src/main/kotlin/starter/Server.kt b/src/main/kotlin/starter/Server.kt
deleted file mode 100644
index cb21919..0000000
--- a/src/main/kotlin/starter/Server.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package starter
-
-import org.http4k.core.HttpHandler
-import org.http4k.core.Method
-import org.http4k.core.Response
-import org.http4k.core.Status
-import org.http4k.core.body.form
-import org.http4k.core.body.formAsMap
-import org.http4k.routing.ResourceLoader
-import org.http4k.routing.bind
-import org.http4k.routing.routes
-import org.http4k.routing.static
-import org.http4k.server.SunHttp
-import org.http4k.server.asServer
-import starter.utils.sanitizeFilename
-import java.io.ByteArrayInputStream
-
-class Server(
- private val views: Views,
- private val conf: StarterConfig,
- private val projectZip: ProjectZip,
-) {
-
- fun run() {
- val indexHandler: HttpHandler = {
- Response(Status.OK).body(views.index(conf.dependencies, conf.inputs)).header("Content-Type", "text/html")
- }
- val zipHandler: HttpHandler = { req ->
- val deps = conf.dependencies.filter {
- req.form(it.name) != null
- }
-
- val inputKeys = conf.inputs.map { it.name }
- val inputs = req.formAsMap()
- .filter { it.key in inputKeys }
- .map { (name, value) ->
- conf.inputs.find { it.name == name }!!.copy(value = value.first())
- }
-
- val projectName = inputs.find { it.name == "name" }!!.value!!
- val basePackage = inputs.find { it.name == "basePackage" }!!.value!!
-
- if (basePackage.contains("/") || basePackage.contains("..")) {
- Response(Status.BAD_REQUEST).body("Invalid Base Package")
- } else {
- val repositories = conf.repositories
- .filter { repo -> repo.name in deps.mapNotNull { it.repository } }
-
- val project = Project(projectName, basePackage, inputs, deps, repositories)
- val outputStream = projectZip.createZip(project)
-
- Response(Status.OK).header("Content-Type", "application/zip")
- .header("Content-Disposition", "attachment; filename=\"${sanitizeFilename(projectName)}.zip\"")
- .body(ByteArrayInputStream(outputStream.toByteArray()))
- }
- }
-
- val app = routes(
- "/" bind Method.GET to indexHandler,
- "/" bind Method.POST to zipHandler,
- static(ResourceLoader.Classpath("/assets"))
- )
-
- app.asServer(SunHttp(7000)).start()
- println("Started on http://localhost:7000")
- }
-
-}
diff --git a/src/main/kotlin/starter/Views.kt b/src/main/kotlin/starter/Views.kt
index 2999ad9..ff45b1b 100644
--- a/src/main/kotlin/starter/Views.kt
+++ b/src/main/kotlin/starter/Views.kt
@@ -1,17 +1,14 @@
package starter
import com.mitchellbosecke.pebble.PebbleEngine
-import org.slf4j.LoggerFactory
import starter.utils.render
class Views(private val engine: PebbleEngine) {
- private val logger = LoggerFactory.getLogger(javaClass)
-
fun index(dependencies: List, inputs: List): String {
val dependenciesByCategory = dependencies.groupBy { it.category }.toSortedMap()
- return engine.render("views/index",
- mapOf("dependencies" to dependenciesByCategory, "inputs" to inputs)
+ return engine.render(
+ "views/index",
+ mapOf("dependencies" to dependenciesByCategory, "inputs" to inputs)
)
}
-
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/starter/extensions/Http4kExtensions.kt b/src/main/kotlin/starter/extensions/Http4kExtensions.kt
new file mode 100644
index 0000000..04b3eef
--- /dev/null
+++ b/src/main/kotlin/starter/extensions/Http4kExtensions.kt
@@ -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)
+}
diff --git a/src/main/kotlin/starter/routes/IndexRouteSupplier.kt b/src/main/kotlin/starter/routes/IndexRouteSupplier.kt
new file mode 100644
index 0000000..2f3a504
--- /dev/null
+++ b/src/main/kotlin/starter/routes/IndexRouteSupplier.kt
@@ -0,0 +1,19 @@
+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.StarterConfig
+import starter.Views
+
+class IndexRouteSupplier(
+ private val views: Views,
+ private val conf: StarterConfig,
+) : RouteSupplier {
+ override fun get() = "/" bind Method.GET to {
+ Response(Status.OK)
+ .body(views.index(conf.dependencies, conf.inputs))
+ .header("Content-Type", "text/html")
+ }
+}
diff --git a/src/main/kotlin/starter/routes/RouteSupplier.kt b/src/main/kotlin/starter/routes/RouteSupplier.kt
new file mode 100644
index 0000000..70a91dd
--- /dev/null
+++ b/src/main/kotlin/starter/routes/RouteSupplier.kt
@@ -0,0 +1,10 @@
+package starter.routes
+
+import org.http4k.routing.RoutingHttpHandler
+import org.http4k.routing.routes
+
+interface RouteSupplier {
+ fun get(): RoutingHttpHandler
+}
+
+fun List.toRouter() = routes(*map { it.get() }.toTypedArray())
diff --git a/src/main/kotlin/starter/routes/ZipRouteSupplier.kt b/src/main/kotlin/starter/routes/ZipRouteSupplier.kt
new file mode 100644
index 0000000..3e7444d
--- /dev/null
+++ b/src/main/kotlin/starter/routes/ZipRouteSupplier.kt
@@ -0,0 +1,56 @@
+package starter.routes
+
+import org.http4k.core.Method
+import org.http4k.core.Response
+import org.http4k.core.body.form
+import org.http4k.core.body.formAsMap
+import org.http4k.core.with
+import org.http4k.routing.bind
+import starter.Project
+import starter.ProjectZip
+import starter.StarterConfig
+import starter.extensions.attachment
+import starter.extensions.badRequest
+import starter.extensions.ok
+import java.io.ByteArrayInputStream
+
+class ZipRouteSupplier(
+ private val conf: StarterConfig,
+ private val projectZip: ProjectZip,
+) : RouteSupplier {
+
+ override fun get() = "/" bind Method.POST to { req ->
+
+ val deps = conf.dependencies.filter {
+ req.form(it.name) != null
+ }
+
+ val inputKeys = conf.inputs.map { it.name }
+ val inputs = req.formAsMap()
+ .filter { it.key in inputKeys }
+ .map { (name, value) ->
+ conf.inputs.find { it.name == name }!!.copy(value = value.first())
+ }
+
+ val projectName = inputs.find { it.name == "name" }!!.value!!
+ val basePackage = inputs.find { it.name == "basePackage" }!!.value!!
+
+ if (basePackage.contains("/") || basePackage.contains("..")) {
+ Response.badRequest()
+ } else {
+ val repositories = conf.repositories
+ .filter { repo -> repo.name in deps.mapNotNull { it.repository } }
+
+ val project = Project(projectName, basePackage, inputs, deps, repositories)
+ val outputStream = projectZip.createZip(project)
+
+ Response.ok().with(
+ attachment(
+ value = ByteArrayInputStream(outputStream.toByteArray()),
+ name = "$projectName.zip",
+ contentType = "application/zip"
+ )
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/starter/templates/GitignoreTemplate.kt b/src/main/kotlin/starter/templates/GitignoreTemplate.kt
index 21ea929..67e1696 100644
--- a/src/main/kotlin/starter/templates/GitignoreTemplate.kt
+++ b/src/main/kotlin/starter/templates/GitignoreTemplate.kt
@@ -6,8 +6,8 @@ import starter.utils.render
class GitignoreTemplate(private val engine: PebbleEngine) : Template {
override fun path(project: Project) =
- ".gitignore"
+ ".gitignore"
override fun render(project: Project) =
- engine.render("starter/gitignore/index")
+ engine.render("starter/gitignore/index")
}
diff --git a/src/main/kotlin/starter/templates/LogbackTemplate.kt b/src/main/kotlin/starter/templates/LogbackTemplate.kt
index 73340a9..b7bbf36 100644
--- a/src/main/kotlin/starter/templates/LogbackTemplate.kt
+++ b/src/main/kotlin/starter/templates/LogbackTemplate.kt
@@ -12,7 +12,7 @@ class LogbackTemplate(private val engine: PebbleEngine) : Template {
override fun render(project: Project): String {
val args = mapOf(
- "loggers" to project.dependencies.mapNotNull { it.logger }.toSet()
+ "loggers" to project.dependencies.mapNotNull { it.logger }.toSet()
)
val rendered = engine.render("starter/logback/index", args)
return prettyPrintXml(rendered)
diff --git a/src/main/kotlin/starter/templates/MainTemplate.kt b/src/main/kotlin/starter/templates/MainTemplate.kt
index 9aed9c6..99d29ea 100644
--- a/src/main/kotlin/starter/templates/MainTemplate.kt
+++ b/src/main/kotlin/starter/templates/MainTemplate.kt
@@ -6,8 +6,8 @@ import starter.utils.render
class MainTemplate(private val engine: PebbleEngine) : Template {
override fun path(project: Project) =
- "src/main/kotlin/" + project.basePackage.replace('.', '/') + "/" + project.name.toLowerCase().capitalize() + ".kt"
+ "src/main/kotlin/" + project.basePackage.replace('.', '/') + "/" + project.name.toLowerCase().capitalize() + ".kt"
override fun render(project: Project) =
- engine.render("starter/main/index", mapOf("basePackage" to project.basePackage))
+ engine.render("starter/main/index", mapOf("basePackage" to project.basePackage))
}
diff --git a/src/main/kotlin/starter/templates/PomTemplate.kt b/src/main/kotlin/starter/templates/PomTemplate.kt
index 64fc55c..54e390b 100644
--- a/src/main/kotlin/starter/templates/PomTemplate.kt
+++ b/src/main/kotlin/starter/templates/PomTemplate.kt
@@ -10,9 +10,9 @@ class PomTemplate(private val engine: PebbleEngine) : Template {
override fun render(project: Project): String {
val args: MutableMap = mutableMapOf(
- "dependencies" to project.dependencies.sortedBy { it.scope },
- "repositories" to project.repositories,
- "kotlinxSerialization" to project.dependencies.any { it.name == "Kotlinx-serialization" },
+ "dependencies" to project.dependencies.sortedBy { it.scope },
+ "repositories" to project.repositories,
+ "kotlinxSerialization" to project.dependencies.any { it.name == "Kotlinx-serialization" },
)
project.inputs.forEach {
diff --git a/src/main/kotlin/starter/utils/PebbleUtils.kt b/src/main/kotlin/starter/utils/PebbleUtils.kt
index 6a52f56..e24ae0a 100644
--- a/src/main/kotlin/starter/utils/PebbleUtils.kt
+++ b/src/main/kotlin/starter/utils/PebbleUtils.kt
@@ -8,4 +8,4 @@ fun PebbleEngine.render(name: String, args: Map = mapOf()): String
val writer = StringWriter()
template.evaluate(writer, args)
return writer.toString()
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/starter/utils/XmlUtils.kt b/src/main/kotlin/starter/utils/XmlUtils.kt
index 7e1a7de..04bf6a2 100644
--- a/src/main/kotlin/starter/utils/XmlUtils.kt
+++ b/src/main/kotlin/starter/utils/XmlUtils.kt
@@ -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()
-}
\ No newline at end of file
+}