1
0

Clean + lint

This commit is contained in:
Hubert Van De Walle 2020-09-30 02:39:58 +02:00
parent ce92e1fae9
commit 66878900f5
19 changed files with 220 additions and 139 deletions

18
.editorconfig Normal file
View File

@ -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

View File

@ -10,6 +10,7 @@
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.4.10</kotlin.version> <kotlin.version>1.4.10</kotlin.version>
<kotlin.code.style>official</kotlin.code.style>
</properties> </properties>
<repositories> <repositories>
@ -51,6 +52,11 @@
<artifactId>koin-core</artifactId> <artifactId>koin-core</artifactId>
<version>2.1.6</version> <version>2.1.6</version>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -4,9 +4,9 @@ import com.electronwill.nightconfig.core.file.FileConfig
import com.electronwill.nightconfig.core.Config as NightConfig import com.electronwill.nightconfig.core.Config as NightConfig
data class StarterConfig( data class StarterConfig(
val dependencies: List<Dependency>, val dependencies: List<Dependency>,
val inputs: List<Input>, val inputs: List<Input>,
val repositories: List<Repository>, val repositories: List<Repository>,
) )
class Config { class Config {
@ -19,31 +19,30 @@ class Config {
cfg.load() cfg.load()
val dependencies = cfg.configMap("dependencies") val dependencies = cfg.configMap("dependencies")
.map { (name, values) -> .map { (name, values) ->
Dependency( Dependency(
name, name,
values["groupId"], values["groupId"],
values["artifactId"], values["artifactId"],
values["version"], values["version"],
values.getOrElse("default", false), values.getOrElse("default", false),
values.getEnumOrElse("category", Category.Other), values.getEnumOrElse("category", Category.Other),
values.getEnumOrElse("scope", Scope.Compile), values.getEnumOrElse("scope", Scope.Compile),
values["logger"], values["logger"],
values["repository"], values["repository"],
) )
} }
val inputs = cfg.configMap("inputs") val inputs = cfg.configMap("inputs")
.map { (name, values) -> .map { (name, values) ->
Input(name, values["display"], values["default"]) Input(name, values["display"], values["default"])
} }
val repositories = cfg.configMap("repositories") val repositories = cfg.configMap("repositories")
.map { (name, values) -> .map { (name, values) ->
Repository(name, values["url"]) Repository(name, values["url"])
} }
return StarterConfig(dependencies, inputs, repositories) return StarterConfig(dependencies, inputs, repositories)
} }
} }

View File

@ -1,14 +1,32 @@
package starter 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.core.context.startKoin
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module 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.* import starter.templates.*
val mainModule = module { val mainModule = module {
single(createdAtStart = true) {
get<Logger>().info("Starting on http://localhost:7000")
get<RoutingHttpHandler>().asServer(SunHttp(7000)).start()
} onClose { it?.stop() }
single { Config().load() } single { Config().load() }
single { PebbleModule().engine() } single { PebbleModule().engine() }
single { Server(get(), get(), get()) } single { LoggerFactory.getLogger("Starter") }
single { Views(get()) } single { Views(get()) }
single { ProjectZip(getAll()) } single { ProjectZip(getAll()) }
} }
@ -20,10 +38,19 @@ val templateModule = module {
single { GitignoreTemplate(get()) } bind Template::class single { GitignoreTemplate(get()) } bind Template::class
} }
fun main() { val routesModule = module {
val koin = startKoin { single { IndexRouteSupplier(get(), get()) } bind RouteSupplier::class
modules(mainModule, templateModule) single { ZipRouteSupplier(get(), get()) } bind RouteSupplier::class
}.koin single {
val server = koin.get<Server>() routes(
server.run() static(ResourceLoader.Classpath("/assets")),
getAll<RouteSupplier>().toRouter()
)
}
}
fun main() {
startKoin {
modules(mainModule, templateModule, routesModule)
}
} }

View File

@ -9,15 +9,15 @@ enum class Scope {
} }
data class Dependency( data class Dependency(
val name: String, val name: String,
val groupId: String, val groupId: String,
val artifactId: String, val artifactId: String,
val version: String, val version: String,
val default: Boolean, val default: Boolean,
val category: Category, val category: Category,
val scope: Scope, val scope: Scope,
val logger: String?, val logger: String?,
val repository: String?, val repository: String?,
) )
data class Repository(val name: String, val url: 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 Input(val name: String, val display: String, val value: String? = null)
data class Project( data class Project(
val name: String, val name: String,
val basePackage: String, val basePackage: String,
val inputs: List<Input>, val inputs: List<Input>,
val dependencies: List<Dependency>, val dependencies: List<Dependency>,
val repositories: List<Repository>, val repositories: List<Repository>,
) )

View File

@ -8,8 +8,8 @@ class PebbleModule {
val loader = ClasspathLoader() val loader = ClasspathLoader()
loader.suffix = ".twig" loader.suffix = ".twig"
return PebbleEngine.Builder() return PebbleEngine.Builder()
.loader(loader) .loader(loader)
.cacheActive(true) .cacheActive(true)
.build() .build()
} }
} }

View File

@ -17,5 +17,4 @@ class ProjectZip(private val templates: List<Template>) {
} }
return zipOutput.outputStream return zipOutput.outputStream
} }
} }

View File

@ -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")
}
}

View File

@ -1,17 +1,14 @@
package starter package starter
import com.mitchellbosecke.pebble.PebbleEngine import com.mitchellbosecke.pebble.PebbleEngine
import org.slf4j.LoggerFactory
import starter.utils.render import starter.utils.render
class Views(private val engine: PebbleEngine) { class Views(private val engine: PebbleEngine) {
private val logger = LoggerFactory.getLogger(javaClass)
fun index(dependencies: List<Dependency>, inputs: List<Input>): String { fun index(dependencies: List<Dependency>, inputs: List<Input>): String {
val dependenciesByCategory = dependencies.groupBy { it.category }.toSortedMap() val dependenciesByCategory = dependencies.groupBy { it.category }.toSortedMap()
return engine.render("views/index", return engine.render(
mapOf("dependencies" to dependenciesByCategory, "inputs" to inputs) "views/index",
mapOf("dependencies" to dependenciesByCategory, "inputs" to inputs)
) )
} }
} }

View File

@ -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)
}

View File

@ -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")
}
}

View File

@ -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())

View File

@ -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"
)
)
}
}
}

View File

@ -6,8 +6,8 @@ import starter.utils.render
class GitignoreTemplate(private val engine: PebbleEngine) : Template { class GitignoreTemplate(private val engine: PebbleEngine) : Template {
override fun path(project: Project) = override fun path(project: Project) =
".gitignore" ".gitignore"
override fun render(project: Project) = override fun render(project: Project) =
engine.render("starter/gitignore/index") engine.render("starter/gitignore/index")
} }

View File

@ -12,7 +12,7 @@ class LogbackTemplate(private val engine: PebbleEngine) : Template {
override fun render(project: Project): String { override fun render(project: Project): String {
val args = mapOf( 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) val rendered = engine.render("starter/logback/index", args)
return prettyPrintXml(rendered) return prettyPrintXml(rendered)

View File

@ -6,8 +6,8 @@ import starter.utils.render
class MainTemplate(private val engine: PebbleEngine) : Template { class MainTemplate(private val engine: PebbleEngine) : Template {
override fun path(project: Project) = 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) = 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))
} }

View File

@ -10,9 +10,9 @@ class PomTemplate(private val engine: PebbleEngine) : Template {
override fun render(project: Project): String { override fun render(project: Project): String {
val args: MutableMap<String, Any?> = mutableMapOf( val args: MutableMap<String, Any?> = mutableMapOf(
"dependencies" to project.dependencies.sortedBy { it.scope }, "dependencies" to project.dependencies.sortedBy { it.scope },
"repositories" to project.repositories, "repositories" to project.repositories,
"kotlinxSerialization" to project.dependencies.any { it.name == "Kotlinx-serialization" }, "kotlinxSerialization" to project.dependencies.any { it.name == "Kotlinx-serialization" },
) )
project.inputs.forEach { project.inputs.forEach {

View File

@ -13,22 +13,23 @@ import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPathConstants import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory import javax.xml.xpath.XPathFactory
/* /*
@see https://stackoverflow.com/a/33541820 @see https://stackoverflow.com/a/33541820
*/ */
fun prettyPrintXml(xml: String, indent: Int = 4): String { fun prettyPrintXml(xml: String, indent: Int = 4): String {
// Turn xml string into a document // Turn xml string into a document
val document: Document = DocumentBuilderFactory.newInstance() val document: Document = DocumentBuilderFactory.newInstance()
.newDocumentBuilder() .newDocumentBuilder()
.parse(InputSource(ByteArrayInputStream(xml.encodeToByteArray()))) .parse(InputSource(ByteArrayInputStream(xml.encodeToByteArray())))
// Remove whitespaces outside tags // Remove whitespaces outside tags
document.normalize() document.normalize()
val xPath = XPathFactory.newInstance().newXPath() val xPath = XPathFactory.newInstance().newXPath()
val nodeList = xPath.evaluate("//text()[normalize-space()='']", val nodeList = xPath.evaluate(
document, "//text()[normalize-space()='']",
XPathConstants.NODESET) as NodeList document,
XPathConstants.NODESET
) as NodeList
for (i in 0 until nodeList.length) { for (i in 0 until nodeList.length) {
val node = nodeList.item(i) val node = nodeList.item(i)
node.parentNode.removeChild(node) node.parentNode.removeChild(node)