Skip to content

Commit

Permalink
Merge pull request #602 from hexagonkt/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jaguililla committed Feb 1, 2023
2 parents 463c76e + a0cac87 commit 7b8d202
Show file tree
Hide file tree
Showing 70 changed files with 653 additions and 587 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Nightly

on:
schedule:
- cron: "59 23 * * *"
- cron: "59 23 * * 0,2,4,6"

jobs:
stale:
Expand Down
15 changes: 2 additions & 13 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,12 @@ plugins {

apply(from = "gradle/certificates.gradle")

defaultTasks("build")

repositories {
mavenCentral()
}

tasks.register<Delete>("clean") {
group = "build"
description = "Delete root project's generated artifacts, logs and error dumps."

delete("build", "log", "out", ".vertx", "file-uploads", "config")
delete(
fileTree(rootDir) { include("**/*.log") },
fileTree(rootDir) { include("**/*.hprof") },
fileTree(rootDir) { include("**/.attach_pid*") },
fileTree(rootDir) { include("**/hs_err_pid*") }
)
}

task("setUp") {
group = "build setup"
description = "Set up project for development. Creates the Git pre push hook (run build task)."
Expand Down
20 changes: 1 addition & 19 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,7 @@ apply(from = "../gradle/detekt.gradle")
description = "Hexagon core utilities. Includes serialization and logging helpers."

dependencies {
"api"("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
"api"("org.jetbrains.kotlin:kotlin-stdlib")

"testImplementation"("org.jetbrains.kotlin:kotlin-reflect")
}

task("hexagonInfo") {
group = "build"
description = "Add `META-INF/hexagon.properties` file (with toolkit variables) to the package."

doLast {
file("$buildDir/resources/main/META-INF").mkdirs()
file("$buildDir/resources/main/META-INF/hexagon.properties").writeText("""
project=${rootProject.name}
module=${project.name}
version=${project.version}
group=${project.group}
description=${project.description}
""".trimIndent ())
}
}

tasks.getByName("classes").dependsOn("hexagonInfo")
6 changes: 1 addition & 5 deletions core/src/main/kotlin/com/hexagonkt/core/ClasspathHandler.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package com.hexagonkt.core

import com.hexagonkt.core.logging.Logger
import java.net.URL
import java.net.URLConnection
import java.net.URLStreamHandler

object ClasspathHandler : URLStreamHandler() {

// Logger needs to be lazy due to GraalVM native image generation constraints
private val logger: Logger by lazy { Logger(this::class) }
private val classLoader: ClassLoader = Thread.currentThread().contextClassLoader
private val protocolHandlers: Map<String, URLStreamHandler> = mapOf("classpath" to this)

Expand All @@ -23,7 +19,7 @@ object ClasspathHandler : URLStreamHandler() {
}
}
catch (e: Error) {
logger.debug { "Classpath URL handler already registered" }
e.printStackTrace()
}
}

Expand Down
34 changes: 22 additions & 12 deletions core/src/main/kotlin/com/hexagonkt/core/Helpers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import java.util.*
import java.util.concurrent.TimeUnit.SECONDS

/**
* Disable heavy and optional checks in runtime. This flag can be enabled to get a small
* performance boost. Do *NOT* do this on development (it could mask problems) and enable it on
* production only if you have tested your application extensively.
*
* It's initial value is taken from the `DISABLE_CHECKS` flag. See [Jvm.systemFlag] for details on
* how flags are checked on a JVM.
*
* This variable can be changed on code to affect only certain parts of the code, however this is
* not advised and should be done carefully.
* This flag is true when assertions are enabled in the JVM (`-ea` flag). Assertions are disabled by
* default in the JVM, but they are enabled (and should be that way) on the tests.
*/
var disableChecks: Boolean = Jvm.systemFlag("DISABLE_CHECKS")
val assertEnabled: Boolean by lazy {
try {
assert(false)
false
} catch (_: AssertionError) {
true
}
}

/**
* Print receiver to stdout. Convenient utility to debug variables quickly.
Expand All @@ -27,8 +27,18 @@ var disableChecks: Boolean = Jvm.systemFlag("DISABLE_CHECKS")
* @param prefix String to print before the actual object information. Empty string by default.
* @return Receiver's reference. Returned to allow method call chaining.
*/
fun <T> T.println(prefix: String = ""): T =
apply { kotlin.io.println("$prefix$this") }
fun <T> T.out(prefix: String = ""): T =
apply { println("$prefix$this") }

/**
* Print receiver to stderr. Convenient utility to debug variables quickly.
*
* @receiver Reference to the object to print. Can be `null`.
* @param prefix String to print before the actual object information. Empty string by default.
* @return Receiver's reference. Returned to allow method call chaining.
*/
fun <T> T.err(prefix: String = ""): T =
apply { System.err.println("$prefix$this") }

/**
* Load a '*.properties' file from a URL transforming the content into a plain map. If the resource
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/kotlin/com/hexagonkt/core/I18n.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ fun localeOf(language: String = "", country: String = ""): Locale {
}
require(language.isEmpty() || language in languageCodes) { "Language: '$language' not allowed" }
require(country.isEmpty() || country in countryCodes) { "Country: '$country' not allowed" }
return Locale(language, country)
return Locale.Builder().setLanguage(language).setRegion(country).build()
}

/**
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/kotlin/com/hexagonkt/core/Jvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ object Jvm {
* found on the JVM system properties and in OS environment variables.
*/
fun <T: Any> systemSettingOrNull(type: KClass<T>, name: String): T? =
systemSettingRaw(name).let { it.toOrNull(type) }
systemSettingRaw(name).let { it.parseOrNull(type) }

fun <T: Any> systemSetting(type: KClass<T>, name: String): T =
systemSettingOrNull(type, name)
Expand Down
61 changes: 49 additions & 12 deletions core/src/main/kotlin/com/hexagonkt/core/Strings.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hexagonkt.core

import java.io.ByteArrayInputStream
import java.io.File
import java.io.InputStream
import kotlin.IllegalArgumentException
import java.lang.System.getProperty
Expand All @@ -9,6 +10,9 @@ import java.net.URI
import java.net.URL
import java.text.Normalizer.Form.NFD
import java.text.Normalizer.normalize
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.util.*
import kotlin.reflect.KClass

Expand All @@ -21,6 +25,23 @@ private val base64Decoder: Base64.Decoder = Base64.getDecoder()
/** Runtime specific end of line. */
val eol: String by lazy { getProperty("line.separator") }

/** Supported types for the [parseOrNull] function. */
val parsedClasses: Set<KClass<*>> = setOf(
Boolean::class,
Int::class,
Long::class,
Float::class,
Double::class,
String::class,
InetAddress::class,
URL::class,
URI::class,
File::class,
LocalDate::class,
LocalTime::class,
LocalDateTime::class,
)

/**
* Encode the content of this byteArray to base64.
*
Expand Down Expand Up @@ -133,7 +154,7 @@ fun <T : Enum<*>> String.toEnumOrNull(converter: (String) -> T): T? =
* @return .
*/
@Suppress("UNCHECKED_CAST") // All allowed types are checked at runtime
fun <T : Any> String?.toOrNull(type: KClass<T>): T? =
fun <T : Any> String?.parseOrNull(type: KClass<T>): T? =
this?.let {
when (type) {
Boolean::class -> this.toBooleanStrictOrNull()
Expand All @@ -145,6 +166,10 @@ fun <T : Any> String?.toOrNull(type: KClass<T>): T? =
InetAddress::class -> this.let(InetAddress::getByName)
URL::class -> this.let(::URL)
URI::class -> this.let(::URI)
File::class -> this.let(::File)
LocalDate::class -> LocalDate.parse(this)
LocalTime::class -> LocalTime.parse(this)
LocalDateTime::class -> LocalDateTime.parse(this)
else -> error("Unsupported type: ${type.qualifiedName}")
}
} as? T
Expand All @@ -161,30 +186,42 @@ fun Regex.findGroups(text: String): List<MatchGroup> =
.filterNotNull()
.drop(1)

/**
* Transform the target string from snake case to camel case.
*/
fun String.snakeToCamel(): String =
snakeToWords().wordsToCamel()
fun String.camelToWords(): List<String> =
split("(?=\\p{Upper}\\p{Lower})".toRegex()).toWords()

fun String.snakeToWords(): List<String> =
this.split("_").filter(String::isNotEmpty).map(String::lowercase)
split("_").toWords()

fun List<String>.wordsToSnake(): String =
joinToString("_").replaceFirstChar(Char::lowercase)
fun String.kebabToWords(): List<String> =
split("-").toWords()

fun String.camelToWords(): List<String> =
split("(?=\\p{Upper}\\p{Lower})".toRegex()).map(String::lowercase)
fun List<String>.toWords(): List<String> =
filter(String::isNotEmpty).map(String::lowercase)

fun List<String>.wordsToCamel(): String =
joinToString("") { it.replaceFirstChar(Char::uppercase) }.replaceFirstChar(Char::lowercase)
wordsToPascal().replaceFirstChar(Char::lowercase)

fun List<String>.wordsToPascal(): String =
joinToString("") { it.replaceFirstChar(Char::uppercase) }

fun List<String>.wordsToSnake(): String =
joinToString("_")

fun List<String>.wordsToKebab(): String =
joinToString("-")

fun List<String>.wordsToTitle(): String =
joinToString(" ") { it.replaceFirstChar(Char::uppercase) }

fun List<String>.wordsToSentence(): String =
joinToString(" ").replaceFirstChar(Char::uppercase)

/**
* Transform the target string from snake case to camel case.
*/
fun String.snakeToCamel(): String =
snakeToWords().wordsToCamel()

fun Enum<*>.toWords(): String =
toString().lowercase().replace("_", " ")

Expand Down
32 changes: 0 additions & 32 deletions core/src/main/kotlin/com/hexagonkt/core/logging/Logger.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.hexagonkt.core.logging

import java.lang.System.nanoTime
import kotlin.reflect.KClass
import com.hexagonkt.core.Ansi.BLINK
import com.hexagonkt.core.Ansi.BOLD
Expand Down Expand Up @@ -123,35 +122,6 @@ class Logger(val name: String) {
log.log(TRACE) { "$BOLD$BLINK$FLARE_PREFIX$RESET ${message()}" }
}

/**
* Log a message using [TRACE] level with the logging time.
*
* @param startNanos The start logging time in nanoseconds.
* @param message The required message to log.
*/
fun time(startNanos: Long, message: () -> Any? = { "" }) {
log.log(TRACE) { "${message() ?: "TIME"} : ${formatNanos(nanoTime() - startNanos)}" }
}

/**
* Execute a lambda block and log a message using [TRACE] level with the logging time.
*
* @param message The required message to log.
* @param block The lambda block to execute.
*/
fun <T> time(message: () -> Any? = { null }, block: () -> T): T {
val start = nanoTime()
return block().also { time(start, message) }
}

/**
* Execute a lambda block and log a message using [TRACE] level with the logging time.
*
* @param message The required message to log.
* @param block The lambda block to execute.
*/
fun <T> time(message: Any?, block: () -> T): T = this.time({ message }, block)

/**
* Set a logging level for this logger.
*
Expand Down Expand Up @@ -209,6 +179,4 @@ class Logger(val name: String) {
*/
fun isErrorEnabled(): Boolean =
isLoggerLevelEnabled(ERROR)

private fun formatNanos (nanoseconds: Long): String = "%1.3f ms".format (nanoseconds / 1e6)
}
9 changes: 3 additions & 6 deletions core/src/main/kotlin/com/hexagonkt/core/media/CustomMedia.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.hexagonkt.core.media

import com.hexagonkt.core.disableChecks
import com.hexagonkt.core.assertEnabled
import com.hexagonkt.core.media.MediaTypeGroup.ANY

/**
Expand All @@ -14,10 +14,7 @@ data class CustomMedia(
override val fullType: String = if (group == ANY) "*/$type" else "${group.text}/$type"

init {
if (!disableChecks) {
require(type.matches(mediaTypeFormat)) {
"Type must match '$mediaTypeFormat': $type"
}
}
if (assertEnabled)
require(type.matches(mediaTypeFormat)) { "Type must match '$mediaTypeFormat': $type" }
}
}
5 changes: 5 additions & 0 deletions core/src/main/resources/hexagon.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
project=${rootProject.name}
module=${project.name}
version=${project.version}
group=${project.group}
description=${project.description}

0 comments on commit 7b8d202

Please sign in to comment.