Skip to content

Commit

Permalink
Merge pull request #14 from respawn-app/1.3.2
Browse files Browse the repository at this point in the history
1.3.2
  • Loading branch information
Nek-12 authored Apr 15, 2024
2 parents 27209b7 + d86d045 commit 2436739
Show file tree
Hide file tree
Showing 22 changed files with 334 additions and 466 deletions.
30 changes: 7 additions & 23 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import nl.littlerobots.vcu.plugin.versionCatalogUpdate
import nl.littlerobots.vcu.plugin.versionSelector
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.detekt)
alias(libs.plugins.gradleDoctor)
alias(libs.plugins.versions)
alias(libs.plugins.version.catalog.update)
alias(libs.plugins.dokka)
alias(libs.plugins.dependencyAnalysis)
Expand All @@ -19,7 +18,6 @@ buildscript {
dependencies {
classpath(libs.android.gradle)
classpath(libs.kotlin.gradle)
classpath(libs.version.gradle)
classpath(libs.detekt.gradle)
}
}
Expand Down Expand Up @@ -87,12 +85,14 @@ dependencies {
}

versionCatalogUpdate {
sortByKey.set(true)
sortByKey = true

versionSelector { stabilityLevel(it.candidate.version) >= Config.minStabilityLevel }

keep {
keepUnusedVersions.set(true)
keepUnusedLibraries.set(true)
keepUnusedPlugins.set(true)
keepUnusedVersions = true
keepUnusedLibraries = true
keepUnusedPlugins = true
}
}

Expand Down Expand Up @@ -129,22 +129,6 @@ tasks {
autoCorrect = false
}

withType<com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask>().configureEach {
outputFormatter = "json"

fun stabilityLevel(version: String): Int {
Config.stabilityLevels.forEachIndexed { index, postfix ->
val regex = """.*[.\-]$postfix[.\-\d]*""".toRegex(RegexOption.IGNORE_CASE)
if (version.matches(regex)) return index
}
return Config.stabilityLevels.size
}

rejectVersionIf {
stabilityLevel(currentVersion) > stabilityLevel(candidate.version)
}
}

wrapper {
distributionType = Wrapper.DistributionType.BIN
}
Expand Down
3 changes: 2 additions & 1 deletion buildSrc/src/main/kotlin/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object Config {

const val majorRelease = 1
const val minorRelease = 3
const val patch = 1
const val patch = 2
const val postfix = ""
const val versionName = "$majorRelease.$minorRelease.$patch$postfix"

Expand Down Expand Up @@ -67,6 +67,7 @@ object Config {

// build scripts
val stabilityLevels = listOf("preview", "eap", "dev", "alpha", "beta", "m", "cr", "rc")
val minStabilityLevel = stabilityLevels.indexOf("beta")

object Detekt {

Expand Down
13 changes: 12 additions & 1 deletion buildSrc/src/main/kotlin/ConfigureMultiplatform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import org.gradle.api.Project
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.getting
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

@OptIn(ExperimentalWasmDsl::class)
@Suppress("LongParameterList", "CyclomaticComplexMethod")
fun Project.configureMultiplatform(
ext: KotlinMultiplatformExtension,
jvm: Boolean = true,
Expand All @@ -14,7 +17,8 @@ fun Project.configureMultiplatform(
js: Boolean = true,
tvOs: Boolean = true,
macOs: Boolean = true,
watchOs: Boolean = true
watchOs: Boolean = true,
wasmJs: Boolean = true,
) = ext.apply {
val libs by versionCatalog
explicitApi()
Expand All @@ -41,6 +45,13 @@ fun Project.configureMultiplatform(

if (jvm) jvm()

if (wasmJs) wasmJs {
moduleName = this@configureMultiplatform.name
nodejs()
browser()
binaries.library()
}

sequence {
if (iOs) {
yield(iosX64())
Expand Down
8 changes: 8 additions & 0 deletions buildSrc/src/main/kotlin/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,11 @@ val Project.localProperties
load(FileInputStream(File(rootProject.rootDir, "local.properties")))
}
}

fun stabilityLevel(version: String): Int {
Config.stabilityLevels.forEachIndexed { index, postfix ->
val regex = """.*[.\-]$postfix[.\-\d]*""".toRegex(RegexOption.IGNORE_CASE)
if (version.matches(regex)) return index
}
return Config.stabilityLevels.size
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@file:OptIn(ExperimentalContracts::class)
@file:Suppress("TooManyFunctions")

package pro.respawn.kmmutils.common

Expand Down Expand Up @@ -88,6 +89,7 @@ public inline fun <T, R> Iterable<T>.reorderBy(order: List<R>, crossinline selec

/**
* Swaps values [index1] with [index2] in place.
* Throws [IndexOutOfBoundsException] when one or both indices are not present in the collection
*/
public fun <T> MutableList<T>.swap(index1: Int, index2: Int): MutableList<T> {
val tmp = this[index1]
Expand All @@ -96,15 +98,36 @@ public fun <T> MutableList<T>.swap(index1: Int, index2: Int): MutableList<T> {
return this
}

/**
* Swaps values [index1] with [index2] in place.
* Returns the original collection if either [index1] or [index2] are not resent
*/
public fun <T : Any> MutableList<T>.trySwap(index1: Int, index2: Int): MutableList<T> {
val tmp = getOrNull(index1) ?: return this
this[index1] = getOrNull(index2) ?: return this
this[index2] = tmp
return this
}

/**
*
* Returns a shallow copy of this list with the items at [index1] and [index2] swapped.
* Throws [IndexOutOfBoundsException] when one or both indices are not present in the collection
*/
public fun <T> List<T>.swapped(index1: Int, index2: Int): List<T> {
val list = toMutableList()
return list.swap(index1, index2)
}

/**
* Returns a shallow copy of this list with the items at [index1] and [index2] swapped.
* Returns the original collection if either [index1] or [index2] are not resent
*/
public fun <T : Any> List<T>.swappedOrDefault(index1: Int, index2: Int): List<T> {
val list = toMutableList()
return list.trySwap(index1, index2)
}

/**
* Returns a list of pairs, where each value corresponds to all possible pairings with values from [other].
* this: A, B, C
Expand Down Expand Up @@ -157,6 +180,83 @@ public fun Collection<Float>.chunkedAverage(chunkSize: Int): List<Double> = ifEm
public fun Iterable<String>.filterBySubstring(
substring: String?,
ignoreCase: Boolean = false
): List<String> = if (!substring.isValid) toList() else asSequence()
.filter { it.contains(substring!!, ignoreCase) }
): List<String> = if (!substring.isValid()) toList() else asSequence()
.filter { it.contains(substring, ignoreCase) }
.toList()

/**
* A [sumOf] variation that returns a float instead of Double
*/
public inline fun <T> Collection<T>.sumOf(selector: (T) -> Float): Float {
var sum = 0f
for (element in this) {
sum += selector(element)
}
return sum
}

/**
* Consume this iterator by taking the first [count] elements
*/
public fun <T> Iterator<T>.take(count: Int): List<T> = List(count) { next() }

/**
* Returns a [Map] containing key-value pairs provided by [key] and [value] functions
* applied to elements of the given sequence.
*
* If any of two pairs would have the same key the first one gets added to the map.
*
* The returned map preserves the entry iteration order of the original sequence.
*
* The operation is _terminal_.
*/
public inline fun <T, K, V> Sequence<T>.associateFirst(
key: (T) -> K,
value: (T) -> V,
): Map<K, V> {
val dst = LinkedHashMap<K, V>()
for (element in this) {
val newKey = key(element)
if (!dst.containsKey(newKey)) {
dst[newKey] = value(element)
}
}
return dst
}

/**
* Returns a [Map] containing key-value pairs provided by [key] and [value] functions
* applied to elements of the given collection.
*
* If any of two pairs would have the same key the first one gets added to the map.
*
* The returned map preserves the entry iteration order of the original collection.
*/
public inline fun <T, K, V> Iterable<T>.associateFirst(
key: (T) -> K,
value: (T) -> V,
): Map<K, V> {
val dst = LinkedHashMap<K, V>()
for (element in this) {
val newKey = key(element)
if (!dst.containsKey(newKey)) {
dst[newKey] = value(element)
}
}
return dst
}

/**
* Filters the values in this map, leaving only non-null entries
*/
@Suppress("UNCHECKED_CAST")
public fun <K, V> Map<K, V?>.filterNotNullValues(): Map<K, V & Any> = filterValues { it != null } as Map<K, V & Any>

/**
* The same as a regular [mapNotNull], but [transform] block contains the previous value of the list.
*/
public fun <T, R> List<T>.mapNotNull(transform: (prev: R?, value: T) -> R?): List<R> {
val dst = ArrayList<R>()
forEach { value -> transform(dst.lastOrNull(), value)?.let { dst.add(it) } }
return dst
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
@file:Suppress("unused")
@file:Suppress("unused", "NOTHING_TO_INLINE")

package pro.respawn.kmmutils.common

import kotlin.contracts.contract
import kotlin.enums.enumEntries
import kotlin.jvm.JvmName

/**
* @return Whether this string is valid
Expand All @@ -14,14 +16,33 @@ import kotlin.enums.enumEntries
* - "NULL" -> false
* - " " -> false
*/
@Deprecated("Use the function version as it allows for smart-casting", ReplaceWith("this.isValid()"))
@get:JvmName("getIsValid")
public val String?.isValid: Boolean
get() = !isNullOrBlank() && !equals("null", true)

/**
* @return Whether this string is valid
*
* Examples:
* - null -> false
* - "null" -> false
* - "" -> false
* - "NULL" -> false
* - " " -> false
*/
public inline fun String?.isValid(): Boolean {
contract {
returns(true) implies (this@isValid != null)
}
return !isNullOrBlank() && !equals("null", true)
}

/**
* Takes this string only if it [isValid]
* @see isValid
*/
public fun String?.takeIfValid(): String? = if (isValid) this else null
public fun String?.takeIfValid(): String? = if (isValid()) this else null

/**
* Check if this String has length in [range]
Expand Down Expand Up @@ -78,3 +99,24 @@ public inline val <reified T : Enum<T>> Enum<T>.previous: T
*/
@ExperimentalStdlibApi
public inline val <reified T : Enum<T>> Enum<T>.previousOrNull: T? get() = enumEntries<T>().getOrNull(ordinal - 1)

/**
* Calls [requireNotNull] on this value and returns it
*/
public fun <T> T?.requireNotNull(): T & Any = requireNotNull(this)

/**
* Calls [requireNotNull] on this value and returns it
*/
public inline fun <T> T?.requireNotNull(lazyMessage: () -> Unit): T & Any = requireNotNull(this, lazyMessage)

/**
* If this is an [Error], throws it, otherwise returns [this] as an [Exception]
*/
public fun Throwable?.rethrowErrors(): Exception? = this?.let { it as? Exception? ?: throw it }

/**
* If this is an [Error], throws it, otherwise returns [this] as an [Exception]
*/
@JvmName("rethrowErrorsNotNull")
public fun Throwable.rethrowErrors(): Exception = this as? Exception? ?: throw this
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ public val Int.length: Int
else -> log10(abs(this).toDouble()).toInt() + 1
}

/**
* @return The number of digits in this [Long]
*/
public val Long.length: Int
get() = when (this) {
0L -> 1
else -> log10(abs(this).toDouble()).toInt() + 1
}

/**
* @return 1 if this is `true`, and 0 otherwise.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package pro.respawn.kmmutils.coroutines

import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.receiveAsFlow

/**
* A [Flow] instance that can be retried by calling [retry].
* Whatever block or flow emission was before will be re-evaluated (re-created) again.
*/
public interface RetryFlow<T> : Flow<T> {

/**
* Retry the invocation of this flow. The flow that originally was used with this wrapper will be recreated
*/
public fun retry()
}

/**
* Creates a new [RetryFlow] from [flow] builder
*/
public fun <T> retryFlow(flow: suspend () -> Flow<T>): RetryFlow<T> = RetryFlowImpl(flow)

/**
* Creates a new [RetryFlow] from this [call] function, evaluated as a cold flow
*/
public inline fun <T> retry(crossinline call: suspend () -> T): RetryFlow<T> = RetryFlowImpl({ flow { emit(call()) } })

@PublishedApi
internal class RetryFlowImpl<T>(
producer: suspend () -> Flow<T>,
private val delegate: Channel<Unit> = Channel(Channel.CONFLATED),
) : RetryFlow<T>, Flow<T> by delegate.receiveAsFlow().flatMapLatest(transform = { producer() }) {

init {
retry()
}

override fun retry() {
delegate.trySend(Unit)
}
}
Loading

0 comments on commit 2436739

Please sign in to comment.