Compare commits

...

94 Commits

Author SHA1 Message Date
31c83813e6 update dependencies 2024-03-01 03:15:25 +06:00
27483a282d start 0.20.37 2024-03-01 03:06:26 +06:00
48b816aa22 Merge pull request #396 from InsanusMokrassar/0.20.36
0.20.36
2024-02-26 17:10:05 +06:00
0065f94f52 update dependencies 2024-02-26 16:55:18 +06:00
ccc0002eb2 improve of actualize all 2024-02-21 00:05:57 +06:00
15a2eee141 start 0.20.36 2024-02-20 22:52:02 +06:00
b9faac71e5 Merge pull request #394 from InsanusMokrassar/0.20.35
0.20.35
2024-02-16 19:11:27 +06:00
0a4465de33 remove API dumps 2024-02-16 19:09:55 +06:00
f9dfd09628 update dependencies 2024-02-16 19:01:46 +06:00
8638d7afce start 0.20.35 2024-02-16 18:50:35 +06:00
8311793a43 Merge pull request #392 from InsanusMokrassar/0.20.34
0.20.34
2024-02-15 16:32:41 +06:00
0d552cfcd2 fix build 2024-02-15 16:18:31 +06:00
4b0f20dbd1 improve default 'set' of KeyValuesRepo 2024-02-15 15:36:32 +06:00
cf531c949d start 0.20.34 2024-02-15 15:14:38 +06:00
ba5c5f17d5 Merge pull request #391 from InsanusMokrassar/0.20.33
0.20.33
2024-02-12 14:42:31 +06:00
35825ad9b7 improve HEXAColorTests 2024-02-12 14:41:55 +06:00
b1eb26a89e fixes and testsAdditions in HEXAColor 2024-02-12 14:25:45 +06:00
c9d04b6698 add ahex to color 2024-02-12 13:58:06 +06:00
496133e014 start 0.20.33 2024-02-12 13:32:30 +06:00
f2857ee2be Merge pull request #388 from InsanusMokrassar/0.20.32
0.20.32
2024-02-10 17:14:04 +06:00
22541f2d5e Update CHANGELOG.md 2024-02-10 15:40:59 +06:00
e235c52b6c Update libs.versions.toml 2024-02-10 15:39:48 +06:00
e89b8c72dd Update gradle-wrapper.properties 2024-02-08 10:40:43 +06:00
a374e53a3a update StringResource 2024-02-06 19:56:44 +06:00
afb066c4ee start 0.20.32 2024-02-06 19:55:34 +06:00
f05761d4a5 Merge pull request #386 from InsanusMokrassar/0.20.31
0.20.31
2024-01-31 18:10:53 +06:00
76adc9ea33 update ktor 2024-01-31 17:37:27 +06:00
99dd291413 start 0.20.31 2024-01-31 17:36:30 +06:00
f904eb27e1 Merge pull request #384 from InsanusMokrassar/0.20.30
0.20.30
2024-01-31 17:27:17 +06:00
eeb8214812 update exposed 2024-01-31 17:05:43 +06:00
f7215b039e start 0.20.30 2024-01-31 17:04:38 +06:00
c07fe5a0f9 Merge pull request #382 from InsanusMokrassar/0.20.29
0.20.29
2024-01-30 23:03:30 +06:00
0d28cb6e20 update dependencies 2024-01-30 21:18:49 +06:00
a1a17bfd1f start 0.20.29 2024-01-30 20:21:45 +06:00
f386f09592 Merge pull request #377 from InsanusMokrassar/0.20.28
0.20.28
2024-01-19 12:59:11 +06:00
a47e17fe6e downgrade kotlin and compose 2024-01-18 23:19:17 +06:00
01dc3b63ff start 0.20.28 2024-01-18 23:12:36 +06:00
2d97e0699e Merge pull request #372 from InsanusMokrassar/0.20.27
0.20.27
2024-01-16 12:43:08 +06:00
75f514d99b update github release 2024-01-16 12:38:53 +06:00
9a687cfc1c update dependencies 2024-01-16 12:19:30 +06:00
14edf8b6b7 start 0.20.27 2024-01-16 12:13:07 +06:00
23aa2d8917 Merge pull request #371 from InsanusMokrassar/0.20.26
0.20.26
2024-01-11 23:29:05 +06:00
7651388b5c update exposed version 2024-01-11 23:09:17 +06:00
805ab32b24 improvements in resources 2024-01-11 23:01:10 +06:00
cc623b1097 start 0.20.26 2024-01-11 23:00:14 +06:00
1420416b3e Merge pull request #369 from InsanusMokrassar/0.20.25
0.20.25
2024-01-04 20:34:15 +06:00
9a0b67f938 fix of tests 2024-01-04 20:30:09 +06:00
303e1e6281 add rgb and rgba representations of HEXAColor 2024-01-04 20:20:55 +06:00
ff59b0cc9c add colors module 2024-01-04 19:53:43 +06:00
be5d2ee715 start 0.20.25 2024-01-04 18:14:09 +06:00
8dd2e3f6f9 Merge pull request #366 from InsanusMokrassar/0.20.24
0.20.24
2024-01-04 10:26:00 +06:00
2eedd196d2 update changelog 2024-01-04 10:25:50 +06:00
759a3f2784 make it possible to use encoder inside of serialize callback 2024-01-04 10:24:39 +06:00
386fa830c3 update soywiz dependencies and downgrade ksp 2024-01-04 09:58:03 +06:00
c382423d77 upgrade MapperSerializer 2023-12-31 13:44:50 +06:00
bb466ce66c start 0.20.24 2023-12-31 13:38:57 +06:00
72cd3dd8a1 Merge pull request #365 from InsanusMokrassar/renovate/ksp
Update ksp to v1.9.22-1.0.16
2023-12-25 21:53:05 +06:00
renovate[bot]
595cedaaf1 Update ksp to v1.9.22-1.0.16 2023-12-25 10:15:44 +00:00
eeaceb6cf5 Merge pull request #362 from InsanusMokrassar/0.20.23
0.20.23
2023-12-22 02:43:14 +06:00
1bd671685b update dependencies and remove redundant usages of old IetfLanguageCode 2023-12-21 23:25:36 +06:00
48d3fe41f2 a lot of improvements in language codes 2023-12-21 15:41:11 +06:00
7ab21871cd Revert "add klock module"
This reverts commit 65d01b1fb3.
2023-12-21 14:05:27 +06:00
65d01b1fb3 add klock module 2023-12-17 23:37:45 +06:00
6230accb68 start 0.20.23 2023-12-17 22:30:25 +06:00
c6ed821934 Merge pull request #360 from InsanusMokrassar/0.20.22
0.20.22
2023-12-14 23:57:51 +06:00
10e03bb951 allow to create own Diff with constructor 2023-12-14 23:56:56 +06:00
aa4f392948 start 0.20.22 2023-12-14 23:52:28 +06:00
f51b59ec02 Update libs.versions.toml 2023-12-14 13:47:34 +06:00
8c76834ae4 Merge pull request #359 from InsanusMokrassar/0.20.21
0.20.21
2023-12-12 23:49:26 +06:00
4a454f3d67 add opportunity to use translation with default locale 2023-12-12 23:45:22 +06:00
151aa1863d update gradle wrapper 2023-12-12 21:41:53 +06:00
3bf6896296 get back translations with android and java resources packages 2023-12-12 21:41:09 +06:00
0d01561476 optimizations and improvements in resources 2023-12-12 21:39:58 +06:00
f6ded92251 init resources module 2023-12-12 20:58:08 +06:00
d01b735cc6 start 0.20.21 2023-12-12 20:21:37 +06:00
6c12001080 Merge pull request #358 from InsanusMokrassar/0.20.20
0.20.20
2023-12-12 00:12:34 +06:00
1afbf03606 improvements in AbstractExposedCRUDRepo 2023-12-12 00:01:58 +06:00
f6ef5c61c5 start 0.20.20 2023-12-11 23:54:25 +06:00
c18fee8107 update gradle publishing scripts
one more potential ix of publishing scripts

drop new configs of publish scripts

revert publish scripts and update gradle properties
2023-12-09 20:38:44 +06:00
d9df7a4384 Revert gradle wrapper version 2023-12-09 01:38:45 +06:00
87c2230e8e revert xmx 2500 2023-12-08 23:42:26 +06:00
da7eb6de0a change xmx 2023-12-08 21:34:05 +06:00
ed3815118f update jvmargs of gradle 2023-12-08 19:34:47 +06:00
be726f42bd Merge pull request #356 from InsanusMokrassar/0.20.19
update publishing scripts
2023-12-08 16:06:22 +06:00
a91006132f update publishing scripts 2023-12-08 16:06:07 +06:00
9a9f741a0b Merge pull request #355 from InsanusMokrassar/0.20.19
0.20.19
2023-12-08 16:03:05 +06:00
5028f130e9 update dependencies 2023-12-08 15:58:16 +06:00
77fa019651 start 0.20.19 2023-12-08 15:56:44 +06:00
9715da9384 Merge pull request #353 from InsanusMokrassar/0.20.18
0.20.18
2023-12-04 16:13:33 +06:00
f6d5035c1a small refactor in SpecialMutableStateFlow 2023-12-04 15:44:06 +06:00
43e782ab6f SpecialMutableStateFlow : MutableStateFlow 2023-12-04 15:37:02 +06:00
f3f9920bfb deprecate FlowState 2023-12-04 15:08:52 +06:00
2bfd615812 start 0.20.18 2023-12-04 15:01:42 +06:00
ebfacb3659 Merge pull request #352 from InsanusMokrassar/0.20.17
0.20.17
2023-12-01 02:51:00 +06:00
53 changed files with 4054 additions and 2650 deletions

View File

@@ -1,5 +1,141 @@
# Changelog # Changelog
## 0.20.37
* `Versions`:
* `Compose`: `1.5.12` -> `1.6.0`
* `Exposed`: `0.47.0` -> `0.48.0`
## 0.20.36
* `Versions`:
* `Serialization`: `1.6.2` -> `1.6.3`
* `Korlibs`: `5.3.1` -> `5.3.2`
* `Repos`:
* `Cache`:
* Improve work and functionality of `actualizeAll` and subsequent functions
* All internal repos `invalidate`/`actualizeAll` now use common `actualizeAll` functions
## 0.20.35
* `Versions`:
* `Coroutines`: `1.7.3` -> `1.8.0`
* `Material3`: `1.1.2` -> `1.2.0`
## 0.20.34
* `Repos`:
* `Common`:
* Improve default `set` realization of `KeyValuesRepo`
## 0.20.33
* `Colors`
* `Common`:
* Add opportunity to use `HEXAColor` with `ahex` colors
## 0.20.32
* `Versions`:
* `Okio`: `3.7.0` -> `3.8.0`
* `Resources`:
* Make `StringResource` serializable
* Add several variants of builder usages
## 0.20.31
* `Versions`:
* `Ktor`: `2.3.7` -> `2.3.8`
## 0.20.30
* `Versions`:
* `Exposed`: `0.46.0` -> `0.47.0`
## 0.20.29
* `Versions`:
* `Kotlin`: `1.9.21` -> `1.9.22`
* `Compose`: `1.5.11` -> `1.5.12`
* `Korlibs`: `5.3.0` -> `5.3.1`
## 0.20.28
* `Versions`:
* `Kotlin`: `1.9.22` -> `1.9.21` (downgrade)
* `Compose`: `1.6.0-dev13691` -> `1.5.11` (downgrade)
## 0.20.27
* `Versions`:
* `Kotlin`: `1.9.21` -> `1.9.22`
* `Compose`: `1.5.11` -> `1.6.0-dev13691`
## 0.20.26
* `Versions`:
* `Exposed`: `0.45.0` -> `0.46.0`. **This update brinds new api deprecations in exposed**
* `Resources`:
* Add opportunity to get default translation by passing `null` as `IetfLang` argument
* Add several useful extensions to get translations in `JS` target
## 0.20.25
* `Colors`:
* `Common`:
* Module inited
## 0.20.24
**Since this version depdendencies of klock and krypto replaced with `com.soywiz.korge:korlibs-time` and `com.soywiz.korge:korlibs-crypto`**
* `Versions`:
* `Klock` (since now `KorlibsTime`): `4.0.10` -> `5.3.0`
* `Krypto` (since now `KorlibsCrypto`): `4.0.10` -> `5.3.0`
* `Serialization`:
* `Mapper`:
* `Mapper` pass decoder into callback of deserialization strategy
* `Mapper` pass encoder into callback of serialization strategy
## 0.20.23
* `Versions`:
* `Koin`: `3.5.0` -> `3.5.3`
* `Okio`: `3.6.0` -> `3.7.0`
* `LanguageCodes`:
* Fixes in intermediate language codes (like `Chinese.Hans`)
* Rename `IetfLanguageCode` to `IetfLang`
* Rename all subsequent functions (including serializer)
* New lazy properties `knownLanguageCodesMap`, `knownLanguageCodesMapByLowerCasedKeys` and several others
## 0.20.22
* `Common`:
* Add opportunity to create own `Diff` with base constructor
## 0.20.21
* `Resources`:
* Inited
## 0.20.20
* `Repos`:
* `Exposed`:
* Add opportunity for setup flows in `AbstractExposedCRUDRepo`
## 0.20.19
* `Versions`:
* `Ktor`: `2.3.6` -> `2.3.7`
## 0.20.18
* `Coroutines`:
* `SpecialMutableStateFlow` now extends `MutableStateFlow`
* `Compose`:
* Deprecate `FlowState` due to its complexity in fixes
## 0.20.17 ## 0.20.17
* `Versions`: * `Versions`:

View File

@@ -27,7 +27,7 @@ allprojects {
mavenCentral() mavenCentral()
google() google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" } maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
} }
// temporal crutch until legacy tests will be stabled or legacy target will be removed // temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@@ -0,0 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"

View File

@@ -0,0 +1,174 @@
package dev.inmo.micro_utils.colors.common
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
import kotlin.math.floor
/**
* Wrapper for RGBA colors. Receiving [UInt] in main constructor. Each part in main constructor
* configured with `0x00 - 0xff` range. Examples:
*
* * Red: `0xff0000ffu`
* * Red (0.5 capacity): `0xff000088u`
*
* Anyway it is recommended to use
*
* @param hexaUInt rgba [UInt] in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
*/
@Serializable
@JvmInline
value class HEXAColor (
val hexaUInt: UInt
) : Comparable<HEXAColor> {
/**
* @returns [hexaUInt] as a string with format `#FFEEBBAA` where FF - red, EE - green, BB - blue and AA - alpha
*/
val hexa: String
get() = "#${hexaUInt.toString(16).padStart(8, '0')}"
/**
* @returns [hexaUInt] as a string with format `#FFEEBB` where FF - red, EE - green and BB - blue
*/
val hex: String
get() = hexa.take(7)
/**
* @returns [hexaUInt] as a string with format `#AAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue
*/
val ahex: String
get() = "#${a.toString(16).padStart(2, '2')}${hex.drop(1)}"
val rgba: String
get() = "rgba($r,$g,$b,${aOfOne.toString().take(5)})"
val rgb: String
get() = "rgb($r,$g,$b)"
val shortHex: String
get() = "#${r.shortPart()}${g.shortPart()}${b.shortPart()}"
val shortHexa: String
get() = "$shortHex${a.shortPart()}"
val rgbUInt: UInt
get() = (hexaUInt / 256u)
val rgbInt: Int
get() = rgbUInt.toInt()
val ahexUInt
get() = (a * 0x1000000).toUInt() + rgbUInt
val r: Int
get() = ((hexaUInt and 0xff000000u) / 0x1000000u).toInt()
val g: Int
get() = ((hexaUInt and 0x00ff0000u) / 0x10000u).toInt()
val b: Int
get() = ((hexaUInt and 0x0000ff00u) / 0x100u).toInt()
val a: Int
get() = ((hexaUInt and 0x000000ffu)).toInt()
val aOfOne: Float
get() = a.toFloat() / (0xff)
init {
require(hexaUInt in 0u ..0xffffffffu)
}
constructor(r: Int, g: Int, b: Int, a: Int) : this(
((r * 0x1000000).toLong() + g * 0x10000 + b * 0x100 + a).toUInt()
) {
require(r in 0 ..0xff)
require(g in 0 ..0xff)
require(b in 0 ..0xff)
require(a in 0 ..0xff)
}
constructor(r: Int, g: Int, b: Int, aOfOne: Float = 1f) : this(
r = r, g = g, b = b, a = (aOfOne * 0xff).toInt()
)
override fun toString(): String {
return hexa
}
override fun compareTo(other: HEXAColor): Int = (hexaUInt - other.hexaUInt).coerceIn(Int.MIN_VALUE.toUInt(), Int.MAX_VALUE.toLong().toUInt()).toInt()
fun copy(
r: Int = this.r,
g: Int = this.g,
b: Int = this.b,
aOfOne: Float = this.aOfOne
) = HEXAColor(r = r, g = g, b = b, aOfOne = aOfOne)
fun copy(
r: Int = this.r,
g: Int = this.g,
b: Int = this.b,
a: Int
) = HEXAColor(r = r, g = g, b = b, a = a)
companion object {
/**
* Parsing color from [color]
*
* Supported formats samples (on Red color based):
*
* * `#f00`
* * `#f00f`
* * `#ff0000`
* * `#ff0000ff`
* * `rgb(255, 0, 0)`
* * `rgba(255, 0, 0, 1)`
*/
fun parseStringColor(color: String): HEXAColor = when {
color.startsWith("#") -> color.removePrefix("#").let { color ->
when (color.length) {
3 -> color.map { "$it$it" }.joinToString(separator = "", postfix = "ff")
4 -> color.take(3).map { "$it$it" }.joinToString(separator = "", postfix = color.takeLast(1).let { "${it}0" })
6 -> "${color}ff"
8 -> color
else -> error("Malfurmed color string: $color. It is expected that color started with # will contains 3, 6 or 8 valuable parts")
}
}
color.startsWith("rgb(") -> color
.removePrefix("rgb(")
.removeSuffix(")")
.replace(Regex("\\s"), "")
.split(",")
.joinToString("", postfix = "ff") {
it.toInt().toString(16).padStart(2, '0')
}
color.startsWith("rgba(") -> color
.removePrefix("rgba(")
.removeSuffix(")")
.replace(Regex("\\s"), "")
.split(",").let {
it.take(3).map { it.toInt().toString(16).padStart(2, '0') } + (it.last().toFloat() * 0xff).toInt().toString(16).padStart(2, '0')
}
.joinToString("")
else -> color
}.lowercase().toUInt(16).let(::HEXAColor)
/**
* Creates [HEXAColor] from [uint] presume it is in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
*/
fun fromHexa(uint: UInt) = HEXAColor(uint)
/**
* Creates [HEXAColor] from [uint] presume it is in format `0xAAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue`
*/
fun fromAhex(uint: UInt) = HEXAColor(
a = ((uint and 0xff000000u) / 0x1000000u).toInt(),
r = ((uint and 0x00ff0000u) / 0x10000u).toInt(),
g = ((uint and 0x0000ff00u) / 0x100u).toInt(),
b = ((uint and 0x000000ffu)).toInt()
)
/**
* Parsing color from [color]
*
* Supported formats samples (on Red color based):
*
* * `#f00`
* * `#ff0000`
* * `#ff0000ff`
* * `rgb(255, 0, 0)`
* * `rgba(255, 0, 0, 1)`
*/
operator fun invoke(color: String) = parseStringColor(color)
private fun Int.shortPart(): String {
return (floor(toFloat() / 16)).toInt().toString(16)
}
}
}

View File

@@ -0,0 +1,209 @@
package dev.inmo.micro_utils.colors.common
import kotlin.math.floor
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class HexColorTests {
val alphaRgbaPrecision = 5
class TestColor(
val color: HEXAColor,
val shortHex: String,
val shortHexa: String,
val hex: String,
val hexa: String,
val ahex: String,
val ahexUInt: UInt,
val rgbUInt: UInt,
val rgb: String,
val rgba: String,
val r: Int,
val g: Int,
val b: Int,
val a: Int,
vararg val additionalRGBAVariants: String
)
val testColors: List<TestColor>
get() = listOf(
TestColor(
color = HEXAColor(hexaUInt = 0xff0000ffu),
shortHex = "#f00",
shortHexa = "#f00f",
hex = "#ff0000",
hexa = "#ff0000ff",
ahex = "#ffff0000",
ahexUInt = 0xffff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,1.0)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0xff,
"rgba(255,0,0,1)",
),
TestColor(
color = HEXAColor(hexaUInt = 0x00ff00ffu),
shortHex = "#0f0",
shortHexa = "#0f0f",
hex = "#00ff00",
hexa = "#00ff00ff",
ahex = "#ff00ff00",
ahexUInt = 0xff00ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,1.0)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0xff,
"rgba(0,255,0,1)"
),
TestColor(
color = HEXAColor(0x0000ffffu),
shortHex = "#00f",
shortHexa = "#00ff",
hex = "#0000ff",
hexa = "#0000ffff",
ahex = "#ff0000ff",
ahexUInt = 0xff0000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,1.0)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0xff,
"rgba(0,0,255,1)"
),
TestColor(
color = HEXAColor(0xff000088u),
shortHex = "#f00",
shortHexa = "#f008",
hex = "#ff0000",
hexa = "#ff000088",
ahex = "#88ff0000",
ahexUInt = 0x88ff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,0.533)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0x88,
),
TestColor(
color = HEXAColor(0x00ff0088u),
shortHex = "#0f0",
shortHexa = "#0f08",
hex = "#00ff00",
hexa = "#00ff0088",
ahex = "#8800ff00",
ahexUInt = 0x8800ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,0.533)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0x88,
),
TestColor(
color = HEXAColor(0x0000ff88u),
shortHex = "#00f",
shortHexa = "#00f8",
hex = "#0000ff",
hexa = "#0000ff88",
ahex = "#880000ff",
ahexUInt = 0x880000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,0.533)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0x88,
),
TestColor(
color = HEXAColor(0xff000022u),
shortHex = "#f00",
shortHexa = "#f002",
hex = "#ff0000",
hexa = "#ff000022",
ahex = "#22ff0000",
ahexUInt = 0x22ff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,0.133)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0x22,
),
TestColor(
color = HEXAColor(0x00ff0022u),
shortHex = "#0f0",
shortHexa = "#0f02",
hex = "#00ff00",
hexa = "#00ff0022",
ahex = "#2200ff00",
ahexUInt = 0x2200ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,0.133)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0x22,
),
TestColor(
color = HEXAColor(0x0000ff22u),
shortHex = "#00f",
shortHexa = "#00f2",
hex = "#0000ff",
hexa = "#0000ff22",
ahex = "#220000ff",
ahexUInt = 0x220000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,0.133)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0x22,
),
)
@Test
fun baseTest() {
testColors.forEach {
assertEquals(it.hex, it.color.hex)
assertEquals(it.hexa, it.color.hexa)
assertEquals(it.ahex, it.color.ahex)
assertEquals(it.rgbUInt, it.color.rgbUInt)
assertEquals(it.ahexUInt, it.color.ahexUInt)
assertEquals(it.shortHex, it.color.shortHex)
assertEquals(it.shortHexa, it.color.shortHexa)
assertEquals(it.rgb, it.color.rgb)
assertTrue(it.rgba == it.color.rgba || it.color.rgba in it.additionalRGBAVariants)
assertEquals(it.r, it.color.r)
assertEquals(it.g, it.color.g)
assertEquals(it.b, it.color.b)
assertEquals(it.a, it.color.a)
assertEquals(it.color, HEXAColor.fromAhex(it.ahexUInt))
}
}
@Test
fun testHexParseColor() {
testColors.forEach {
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.hex))
assertEquals(it.color, HEXAColor.parseStringColor(it.hexa))
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.rgb))
assertTrue(it.color.hexaUInt.toInt() - HEXAColor.parseStringColor(it.rgba).hexaUInt.toInt() in -0x1 .. 0x1, )
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex))
assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa))
}
}
}

View File

@@ -27,7 +27,7 @@ private inline fun <T> getObject(
* @see calculateDiff * @see calculateDiff
*/ */
@Serializable @Serializable
data class Diff<T> internal constructor( data class Diff<T> @Warning(warning) constructor(
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>, val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
/** /**
* Old-New values pairs * Old-New values pairs
@@ -36,6 +36,10 @@ data class Diff<T> internal constructor(
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>> val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
) { ) {
fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty() fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty()
companion object {
private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing"
}
} }
fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList()) fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList())

View File

@@ -1,10 +1,7 @@
package dev.inmo.micro_utils.coroutines.compose package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.doInUI
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -12,6 +9,7 @@ import kotlinx.coroutines.Dispatchers
* This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow]. * This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow].
* Based on [SpecialMutableStateFlow] * Based on [SpecialMutableStateFlow]
*/ */
@Deprecated("Will be removed soon")
class FlowState<T>( class FlowState<T>(
initial: T, initial: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default) internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
@@ -25,9 +23,9 @@ class FlowState<T>(
tryEmit(value) tryEmit(value)
} }
override suspend fun onChange(value: T) { override fun onChangeWithoutSync(value: T) {
internalValue = value internalValue = value
super.onChange(value) super.onChangeWithoutSync(value)
} }
override fun component1(): T = value override fun component1(): T = value

View File

@@ -3,10 +3,14 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.internal.SynchronizedObject
import kotlinx.coroutines.internal.synchronized
/** /**
* Works like [StateFlow], but guarantee that latest value update will always be delivered to * Works like [StateFlow], but guarantee that latest value update will always be delivered to
@@ -15,7 +19,9 @@ import kotlinx.coroutines.flow.StateFlow
open class SpecialMutableStateFlow<T>( open class SpecialMutableStateFlow<T>(
initialValue: T, initialValue: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default) internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : StateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> { ) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> {
@OptIn(InternalCoroutinesApi::class)
private val syncObject = SynchronizedObject()
protected val internalSharedFlow: MutableSharedFlow<T> = MutableSharedFlow( protected val internalSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 0, replay = 0,
extraBufferCapacity = 2, extraBufferCapacity = 2,
@@ -28,16 +34,13 @@ open class SpecialMutableStateFlow<T>(
) )
protected var _value: T = initialValue protected var _value: T = initialValue
override val value: T override var value: T
get() = _value get() = _value
protected open suspend fun onChange(value: T) { set(value) {
_value = value doOnChangeAction(value)
publicSharedFlow.emit(value)
}
protected val job = internalSharedFlow.subscribe(internalScope) {
if (_value != it) {
onChange(it)
} }
protected val job = internalSharedFlow.subscribe(internalScope) {
doOnChangeAction(it)
} }
override val replayCache: List<T> override val replayCache: List<T>
@@ -45,6 +48,29 @@ open class SpecialMutableStateFlow<T>(
override val subscriptionCount: StateFlow<Int> override val subscriptionCount: StateFlow<Int>
get() = publicSharedFlow.subscriptionCount get() = publicSharedFlow.subscriptionCount
@OptIn(InternalCoroutinesApi::class)
override fun compareAndSet(expect: T, update: T): Boolean {
return synchronized(syncObject) {
if (expect == _value && update != _value) {
doOnChangeAction(update)
}
expect == _value
}
}
protected open fun onChangeWithoutSync(value: T) {
_value = value
publicSharedFlow.tryEmit(value)
}
@OptIn(InternalCoroutinesApi::class)
protected open fun doOnChangeAction(value: T) {
synchronized(syncObject) {
if (_value != value) {
onChangeWithoutSync(value)
}
}
}
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
override fun resetReplayCache() = publicSharedFlow.resetReplayCache() override fun resetReplayCache() = publicSharedFlow.resetReplayCache()

View File

@@ -18,13 +18,13 @@ if (new File(projectDir, "secret.gradle").exists()) {
githubRelease { githubRelease {
token "${project.property('GITHUB_RELEASE_TOKEN')}" token "${project.property('GITHUB_RELEASE_TOKEN')}"
owner "InsanusMokrassar" owner = "InsanusMokrassar"
repo "MicroUtils" repo = "MicroUtils"
tagName "v${project.version}" tagName = "v${project.version}"
releaseName "${project.version}" releaseName = "${project.version}"
targetCommitish "${project.version}" targetCommitish = "${project.version}"
body getCurrentVersionChangelog() body = getCurrentVersionChangelog()
} }
} }

View File

@@ -6,7 +6,7 @@ kotlin.incremental.js=true
#kotlin.experimental.tryK2=true #kotlin.experimental.tryK2=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2500m org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g
# JS NPM # JS NPM
@@ -15,5 +15,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.20.17 version=0.20.37
android_code_version=223 android_code_version=243

View File

@@ -1,32 +1,32 @@
[versions] [versions]
kt = "1.9.21" kt = "1.9.22"
kt-serialization = "1.6.2" kt-serialization = "1.6.3"
kt-coroutines = "1.7.3" kt-coroutines = "1.8.0"
kslog = "1.3.1" kslog = "1.3.2"
jb-compose = "1.5.11" jb-compose = "1.6.0"
jb-exposed = "0.45.0" jb-exposed = "0.48.0"
jb-dokka = "1.9.10" jb-dokka = "1.9.10"
korlibs = "4.0.10" korlibs = "5.3.2"
uuid = "0.8.2" uuid = "0.8.2"
ktor = "2.3.6" ktor = "2.3.8"
gh-release = "2.4.1" gh-release = "2.5.2"
koin = "3.5.0" koin = "3.5.3"
okio = "3.6.0" okio = "3.8.0"
ksp = "1.9.21-1.0.15" ksp = "1.9.22-1.0.18"
kotlin-poet = "1.15.2" kotlin-poet = "1.16.0"
versions = "0.50.0" versions = "0.51.0"
android-gradle = "8.2.0" android-gradle = "8.3.0"
dexcount = "4.0.0" dexcount = "4.0.0"
android-coreKtx = "1.12.0" android-coreKtx = "1.12.0"
@@ -35,7 +35,7 @@ android-appCompat = "1.6.1"
android-fragment = "1.6.2" android-fragment = "1.6.2"
android-espresso = "3.5.1" android-espresso = "3.5.1"
android-test = "1.1.5" android-test = "1.1.5"
android-compose-material3 = "1.1.2" android-compose-material3 = "1.2.0"
android-props-minSdk = "21" android-props-minSdk = "21"
android-props-compileSdk = "34" android-props-compileSdk = "34"
@@ -72,8 +72,8 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" } klock = { module = "com.soywiz.korge:korlibs-time", version.ref = "korlibs" }
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" } krypto = { module = "com.soywiz.korge:korlibs-crypto", version.ref = "korlibs" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -68,18 +68,14 @@ publishing {
} }
} }
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven { maven {
name = "Gitea" name = "InmoNexus"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials(HttpHeaderCredentials) { credentials {
name = "Authorization" username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
authentication {
header(HttpHeaderAuthentication)
} }
} }
@@ -121,4 +117,21 @@ if (project.hasProperty("signing.gnupg.keyName")) {
def signingTasks = project.getTasks().withType(Sign.class) def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks) mustRunAfter(signingTasks)
} }
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
} }

View File

@@ -1 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"} {"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

View File

@@ -12,9 +12,12 @@ private val json = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
} }
private const val baseClassName = "IetfLanguageCode" private const val baseClassName = "IetfLang"
private const val oldBaseClassName = "IetfLanguageCode"
private const val unknownBaseClassName = "Unknown$baseClassName" private const val unknownBaseClassName = "Unknown$baseClassName"
private const val baseClassSerializerName = "IetfLanguageCodeSerializer" private const val oldUnknownBaseClassName = "Unknown$oldBaseClassName"
private const val baseClassSerializerName = "${baseClassName}Serializer"
private const val oldBaseClassSerializerName = "IetfLanguageCodeSerializer"
private const val baseClassSerializerAnnotationName = "@Serializable(${baseClassSerializerName}::class)" private const val baseClassSerializerAnnotationName = "@Serializable(${baseClassSerializerName}::class)"
@Serializable @Serializable
@@ -78,14 +81,12 @@ private fun printLanguageCodeAndTags(
indents: String = " " indents: String = " "
): String = if (tag.subtags.isEmpty()) { ): String = if (tag.subtags.isEmpty()) {
"""${indents}${baseClassSerializerAnnotationName} """${indents}${baseClassSerializerAnnotationName}
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"; override val withoutDialect: String get() = ${parent ?.title ?.let { "$it.code" } ?: "code"} }""" ${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"${parent ?.let { parent -> "; override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""} }"""
} else { } else {
""" """
${indents}${baseClassSerializerAnnotationName} ${indents}${baseClassSerializerAnnotationName}
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() { ${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
${indents} override val code: String = "${tag.tag}" ${indents} override val code: String = "${tag.tag}"${parent ?.let { parent -> "\n${indents} override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""}
${indents} override val withoutDialect: String
${indents} get() = code
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }} ${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
@@ -95,7 +96,7 @@ ${indents}}
""" """
} }
fun buildKtFileContent(tags: List<Tag>): String = """ fun buildKtFileContent(tags: List<Tag>, prePackage: String): String = """
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@@ -106,20 +107,27 @@ import kotlinx.serialization.Serializable
${baseClassSerializerAnnotationName} ${baseClassSerializerAnnotationName}
sealed class $baseClassName { sealed class $baseClassName {
abstract val code: String abstract val code: String
abstract val withoutDialect: String open val parentLang: $baseClassName?
get() = null
open val withoutDialect: String
get() = parentLang ?.code ?: code
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } } ${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
$baseClassSerializerAnnotationName $baseClassSerializerAnnotationName
data class $unknownBaseClassName (override val code: String) : $baseClassName() { data class $unknownBaseClassName (override val code: String) : $baseClassName() {
override val withoutDialect: String = code.takeWhile { it != '-' } override val parentLang = code.dropLastWhile { it != '-' }.removeSuffix("-").takeIf { it.length > 0 } ?.let(::$unknownBaseClassName)
} }
@Deprecated("Renamed", ReplaceWith("$baseClassName.$unknownBaseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName.$unknownBaseClassName"))
val $oldUnknownBaseClassName = $unknownBaseClassName
override fun toString() = code override fun toString() = code
} }
@Deprecated("Renamed", ReplaceWith("$baseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName"))
typealias $oldBaseClassName = $baseClassName
""".trimIndent() """.trimIndent()
fun createStringConverterCode(tags: List<Tag>): String { fun createStringConverterCode(tags: List<Tag>, prePackage: String): String {
fun createDeserializeVariantForTag( fun createDeserializeVariantForTag(
tag: Tag, tag: Tag,
pretitle: String = baseClassName, pretitle: String = baseClassName,
@@ -128,19 +136,70 @@ fun createStringConverterCode(tags: List<Tag>): String {
val currentTitle = "$pretitle.${tag.title}" val currentTitle = "$pretitle.${tag.title}"
return """${indents}$currentTitle.code -> $currentTitle${if (tag.subtags.isNotEmpty()) tag.subtags.joinToString("\n", "\n") { createDeserializeVariantForTag(it, currentTitle, indents) } else ""}""" return """${indents}$currentTitle.code -> $currentTitle${if (tag.subtags.isNotEmpty()) tag.subtags.joinToString("\n", "\n") { createDeserializeVariantForTag(it, currentTitle, indents) } else ""}"""
} }
fun createInheritorVariantForTag(
return """fun String.as$baseClassName(): $baseClassName { tag: Tag,
return when (this) { pretitle: String = baseClassName,
${tags.joinToString("\n") { createDeserializeVariantForTag(it) }} indents: String = " "
else -> $baseClassName.${unknownBaseClassName}(this) ): String {
val currentTitle = "$pretitle.${tag.title}"
val subtags = if (tag.subtags.isNotEmpty()) {
tag.subtags.joinToString(",\n", ",\n") { createInheritorVariantForTag(it, currentTitle, "$indents ") }
} else {
""
}
return "${indents}$currentTitle$subtags"
} }
fun createInheritorVariantForMapForTag(
tag: Tag,
pretitle: String = baseClassName,
indents: String = " ",
codeSuffix: String = ""
): String {
val currentTitle = "$pretitle.${tag.title}"
val subtags = if (tag.subtags.isNotEmpty()) {
tag.subtags.joinToString(",\n", ",\n") { createInheritorVariantForMapForTag(it, currentTitle, "$indents ", codeSuffix) }
} else {
""
}
return "${indents}$currentTitle.code${codeSuffix} to $currentTitle$subtags"
}
return """val knownLanguageCodesMap: Map<String, $baseClassName> by lazy {
mapOf(
${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = " ") }}
)
} }
val knownLanguageCodesMapByLowerCasedKeys: Map<String, $baseClassName> by lazy {
mapOf(
${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = " ", codeSuffix = ".lowercase()") }}
)
}
val knownLanguageCodes: List<$baseClassName> by lazy {
knownLanguageCodesMap.values.toList()
}
fun String.as$baseClassName(): $baseClassName {
return knownLanguageCodesMap[this] ?: $baseClassName.${unknownBaseClassName}(this)
}
fun String.as${baseClassName}CaseInsensitive(): $baseClassName {
return knownLanguageCodesMapByLowerCasedKeys[this.lowercase()] ?: $baseClassName.${unknownBaseClassName}(this)
}
@Deprecated("Renamed", ReplaceWith("this.as$baseClassName()", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}as$baseClassName"))
fun String.as$oldBaseClassName(): $baseClassName = as$baseClassName()
fun convertTo$baseClassName(code: String) = code.as$baseClassName() fun convertTo$baseClassName(code: String) = code.as$baseClassName()
fun convertTo${baseClassName}CaseInsensitive(code: String) = code.as${baseClassName}CaseInsensitive()
@Deprecated("Renamed", ReplaceWith("convertTo$baseClassName(code)", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}convertTo$baseClassName"))
fun convertTo$oldBaseClassName(code: String) = convertTo$baseClassName(code)
fun $baseClassName(code: String) = code.as$baseClassName() fun $baseClassName(code: String) = code.as$baseClassName()
fun ${baseClassName}CaseInsensitive(code: String) = code.as${baseClassName}CaseInsensitive()
@Deprecated("Renamed", ReplaceWith("$baseClassName(code)", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName"))
fun $oldBaseClassName(code: String) = $baseClassName(code)
""" """
} }
fun createSerializerCode(tags: List<Tag>): String { fun createSerializerCode(tags: List<Tag>, prePackage: String): String {
return """import kotlinx.serialization.KSerializer return """import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
@@ -153,16 +212,20 @@ object $baseClassSerializerName : KSerializer<$baseClassName> {
return $baseClassName(decoder.decodeString()) return $baseClassName(decoder.decodeString())
} }
override fun serialize(encoder: Encoder, value: IetfLanguageCode) { override fun serialize(encoder: Encoder, value: $baseClassName) {
encoder.encodeString(value.code) encoder.encodeString(value.code)
} }
} }
@Deprecated("Renamed", ReplaceWith("$baseClassSerializerName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassSerializerName"))
typealias $oldBaseClassSerializerName = $baseClassSerializerName
""" """
} }
suspend fun main(vararg args: String) { suspend fun main(vararg args: String) {
val outputFolder = args.firstOrNull() ?.let { File(it) } val outputFolder = args.firstOrNull() ?.let { File(it) }
outputFolder ?.mkdirs() outputFolder ?.mkdirs()
val targetPackage = args.getOrNull(1)
val targetPackagePrefix = targetPackage ?.let { "package $it\n\n" } ?: ""
val ietfLanguageCodesLink = "https://datahub.io/core/language-codes/r/language-codes.json" val ietfLanguageCodesLink = "https://datahub.io/core/language-codes/r/language-codes.json"
val ietfLanguageCodesAdditionalTagsLink = "https://datahub.io/core/language-codes/r/ietf-language-tags.json" val ietfLanguageCodesAdditionalTagsLink = "https://datahub.io/core/language-codes/r/ietf-language-tags.json"
@@ -203,18 +266,18 @@ suspend fun main(vararg args: String) {
File(outputFolder, "LanguageCodes.kt").apply { File(outputFolder, "LanguageCodes.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(buildKtFileContent(tags)) writeText(targetPackagePrefix + buildKtFileContent(tags, targetPackage ?: ""))
} }
File(outputFolder, "StringToLanguageCodes.kt").apply { File(outputFolder, "StringToLanguageCodes.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(createStringConverterCode(tags)) writeText(targetPackagePrefix + createStringConverterCode(tags, targetPackage ?: ""))
} }
File(outputFolder, "$baseClassSerializerName.kt").apply { File(outputFolder, "$baseClassSerializerName.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(createSerializerCode(tags)) writeText(targetPackagePrefix + createSerializerCode(tags, targetPackage ?: ""))
} }
} }

View File

@@ -0,0 +1,20 @@
package dev.inmo.micro_utils.language_codes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object IetfLangSerializer : KSerializer<IetfLang> {
override val descriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): IetfLang {
return IetfLang(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLang) {
encoder.encodeString(value.code)
}
}
@Deprecated("Renamed", ReplaceWith("IetfLangSerializer", "dev.inmo.micro_utils.language_codes.IetfLangSerializer"))
typealias IetfLanguageCodeSerializer = IetfLangSerializer

View File

@@ -1,18 +0,0 @@
package dev.inmo.micro_utils.language_codes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object IetfLanguageCodeSerializer : KSerializer<IetfLanguageCode> {
override val descriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): IetfLanguageCode {
return IetfLanguageCode(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLanguageCode) {
encoder.encodeString(value.code)
}
}

View File

@@ -0,0 +1,16 @@
package dev.inmo.micro_utils.language_codes
import kotlin.test.Test
import kotlin.test.assertEquals
class UnknownIetfLanguageTests {
@Test
fun commonTestOfParentCode() {
knownLanguageCodes.forEach {
val language = IetfLang.UnknownIetfLang(it.code)
assertEquals(it.code, language.code)
assertEquals(it.withoutDialect, language.withoutDialect)
assertEquals(it.parentLang ?.code, language.parentLang ?.code)
}
}
}

View File

@@ -2,7 +2,9 @@ package dev.inmo.micro_utils.language_codes
import java.util.Locale import java.util.Locale
fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code) fun IetfLang.toJavaLocale(): Locale = Locale.forLanguageTag(code)
fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault() fun IetfLang?.toJavaLocaleOrDefault(): Locale = this?.toJavaLocale() ?: Locale.getDefault()
fun Locale.toIetfLanguageCode(): IetfLanguageCode = IetfLanguageCode(toLanguageTag()) fun Locale.toIetfLang(): IetfLang = IetfLang(toLanguageTag())
@Deprecated("Renamed", ReplaceWith("this.toIetfLang()", "dev.inmo.micro_utils.language_codes.toIetfLang"))
fun Locale.toIetfLanguageCode(): IetfLang = toIetfLang()

View File

@@ -57,18 +57,14 @@ publishing {
} }
} }
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven { maven {
name = "Gitea" name = "InmoNexus"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials(HttpHeaderCredentials) { credentials {
name = "Authorization" username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
authentication {
header(HttpHeaderAuthentication)
} }
} }
@@ -109,4 +105,21 @@ if (project.hasProperty("signing.gnupg.keyName")) {
def signingTasks = project.getTasks().withType(Sign.class) def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks) mustRunAfter(signingTasks)
} }
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
} }

View File

@@ -1 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}} {"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}

View File

@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.coroutines.withReadAcquire
import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -29,7 +30,7 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
kvCache.getAll() kvCache.getAll()
}.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also { }.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also {
locker.withWriteLock { locker.withWriteLock {
kvCache.actualizeAll(true) { it } kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it }
} }
} }
} }
@@ -148,7 +149,9 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
locker, locker,
idGetter idGetter
), ),
CRUDRepo<ObjectType, IdType, InputValueType> CRUDRepo<ObjectType, IdType, InputValueType> {
override suspend fun invalidate() = kvCache.actualizeAll(parentRepo, locker = locker)
}
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached( fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
kvCache: KVCache<IdType, ObjectType>, kvCache: KVCache<IdType, ObjectType>,

View File

@@ -1,5 +1,11 @@
package dev.inmo.micro_utils.repos.cache package dev.inmo.micro_utils.repos.cache
interface CacheRepo { interface InvalidatableRepo {
/**
* Invalidates its internal data. It __may__ lead to autoreload of data. In case when repo makes autoreload,
* it must do loading of data __before__ clear
*/
suspend fun invalidate() suspend fun invalidate()
} }
typealias CacheRepo = InvalidatableRepo

View File

@@ -6,6 +6,7 @@ import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@@ -48,9 +49,7 @@ open class ReadKeyValueCacheRepo<Key,Value>(
} }
} }
override suspend fun invalidate() = locker.withWriteLock { override suspend fun invalidate() = kvCache.actualizeAll(parentRepo, locker = locker)
kvCache.clear()
}
} }
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
@@ -75,10 +74,6 @@ open class KeyValueCacheRepo<Key,Value>(
} }
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
override suspend fun clear() { override suspend fun clear() {
parentRepo.clear() parentRepo.clear()
locker.withWriteLock { locker.withWriteLock {

View File

@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.* import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@@ -47,9 +48,7 @@ open class ReadKeyValuesCacheRepo<Key,Value>(
kvCache.contains(k) kvCache.contains(k)
} || parentRepo.contains(k) } || parentRepo.contains(k)
override suspend fun invalidate() = locker.withWriteLock { override suspend fun invalidate() = kvCache.actualizeAll(parentRepo, locker = locker)
kvCache.clear()
}
} }
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
@@ -84,10 +83,6 @@ open class KeyValuesCacheRepo<Key,Value>(
kvCache.unset(it) kvCache.unset(it)
} }
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.cached( fun <Key, Value> KeyValuesRepo<Key, Value>.cached(

View File

@@ -9,6 +9,7 @@ import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -56,7 +57,7 @@ open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap { override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
originalRepo.getAll() originalRepo.getAll()
}.onSuccess { }.onSuccess {
kvCache.actualizeAll(clear = true) { it } kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it }
}.getOrElse { }.getOrElse {
kvCache.getAll() kvCache.getAll()
} }

View File

@@ -9,6 +9,7 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -56,7 +57,7 @@ open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap { override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
originalRepo.getAll() originalRepo.getAll()
}.onSuccess { }.onSuccess {
kvCache.actualizeAll(clear = true) { it } kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it }
}.getOrElse { }.getOrElse {
kvCache.getAll() kvCache.getAll()
} }

View File

@@ -9,6 +9,7 @@ import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.* import dev.inmo.micro_utils.repos.cache.*
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -42,7 +43,7 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
) )
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo) } kvCache.actualizeAll(parentRepo, locker = locker)
} }
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize( override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
@@ -72,7 +73,7 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualizeWithWriteLock( override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualizeWithWriteLock(
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
{ getAll() }, { getAll() },
{ kvCache.actualizeAll(clear = true) { it } } { kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } }
) )
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualizeWithWriteLock( override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualizeWithWriteLock(

View File

@@ -8,8 +8,8 @@ import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.Pagination import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.pagination.getAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@@ -41,10 +41,7 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
actualize = { locker.withWriteLock { actualize(it) } } actualize = { locker.withWriteLock { actualize(it) } }
) )
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo, locker)
kvCache.clear()
kvCache.set(parentRepo.getAll())
}
} }
override suspend fun get(k: Key): Value? = doOrTakeAndActualizeWithWriteLock( override suspend fun get(k: Key): Value? = doOrTakeAndActualizeWithWriteLock(
@@ -74,7 +71,7 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualizeWithWriteLock( override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualizeWithWriteLock(
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
{ getAll() }, { getAll() },
{ kvCache.actualizeAll(clear = true) { it } } { kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } }
) )
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize( override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
@@ -150,9 +147,7 @@ open class FullKeyValueCacheRepo<Key,Value>(
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset) override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
override suspend fun invalidate() { override suspend fun invalidate() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo, locker)
kvCache.actualizeAll(parentRepo)
}
} }
override suspend fun clear() { override suspend fun clear() {

View File

@@ -8,6 +8,7 @@ import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.* import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -41,15 +42,13 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
) )
protected open suspend fun actualizeKey(k: Key) { protected open suspend fun actualizeKey(k: Key) {
locker.withWriteLock { kvCache.actualizeAll(locker = locker, clearMode = ActualizeAllClearMode.Never) {
kvCache.set(k, parentRepo.getAll(k)) mapOf(k to parentRepo.getAll(k))
} }
} }
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo, locker = locker)
kvCache.actualizeAll(parentRepo)
}
} }
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
@@ -187,9 +186,11 @@ open class FullKeyValuesCacheRepo<Key,Value>(
} }
override suspend fun invalidate() { override suspend fun invalidate() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo, locker = locker)
kvCache.actualizeAll(parentRepo) }
}
override suspend fun set(toSet: Map<Key, List<Value>>) {
super<KeyValuesRepo>.set(toSet)
} }
override suspend fun removeWithValue(v: Value) { override suspend fun removeWithValue(v: Value) {

View File

@@ -1,43 +1,169 @@
package dev.inmo.micro_utils.repos.cache.util package dev.inmo.micro_utils.repos.cache.util
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import kotlinx.serialization.Serializable
import kotlin.js.JsName
import kotlin.jvm.JvmName
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll( /**
clear: Boolean = true, * `invalidate`/`actualizeAll` clearing mode. Declare when data in original repo will be cleared
*/
@Serializable
sealed interface ActualizeAllClearMode {
/**
* Instruct user of this mode to clear internal data __before load__ of external data
*/
@Serializable
data object BeforeLoad : ActualizeAllClearMode
/**
* Instruct user of this mode to clear internal data __after load__ of external data and __before set__ of internal data
*/
@Serializable
data object BeforeSet : ActualizeAllClearMode
/**
* Instruct user of this mode to never clear internal data
*/
@Serializable
data object Never : ActualizeAllClearMode
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeLoad(
getAll: () -> Map<K, V> getAll: () -> Map<K, V>
) { ) {
set( clear()
getAll().also { val newData = getAll()
if (clear) { set(newData)
clear() }
}
} suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeSet(
) getAll: () -> Map<K, V>
) {
val newData = getAll()
clear()
set(newData)
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithoutClear(
getAll: () -> Map<K, V>
) {
val newData = getAll()
set(newData)
}
@JvmName("actualizeAllWithClearBeforeLoadWithLocker")
@JsName("actualizeAllWithClearBeforeLoadWithLocker")
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeLoad(
locker: SmartRWLocker,
getAll: () -> Map<K, V>
) {
locker.withWriteLock {
clear()
}
val newData = getAll()
locker.withWriteLock {
set(newData)
}
}
@JvmName("actualizeAllWithClearBeforeSetWithLocker")
@JsName("actualizeAllWithClearBeforeSetWithLocker")
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeSet(
locker: SmartRWLocker,
getAll: () -> Map<K, V>
) {
val newData = getAll()
locker.withWriteLock {
clear()
set(newData)
}
}
@JvmName("actualizeAllWithoutClearWithLocker")
@JsName("actualizeAllWithoutClearWithLocker")
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithoutClear(
locker: SmartRWLocker,
getAll: () -> Map<K, V>
) {
val newData = getAll()
locker.withWriteLock {
set(newData)
}
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeLoad(
locker: SmartRWLocker? = null,
getAll: () -> Map<K, V>
) {
locker ?.let {
actualizeAllWithClearBeforeLoad(locker = locker, getAll)
} ?: actualizeAllWithClearBeforeLoad(getAll)
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeSet(
locker: SmartRWLocker? = null,
getAll: () -> Map<K, V>
) {
locker ?.let {
actualizeAllWithClearBeforeSet(locker = locker, getAll)
} ?: actualizeAllWithClearBeforeSet(getAll)
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithoutClear(
locker: SmartRWLocker? = null,
getAll: () -> Map<K, V>
) {
locker ?.let {
actualizeAllWithoutClear(locker = locker, getAll)
} ?: actualizeAllWithoutClear(getAll)
} }
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll( suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
repo: ReadKeyValueRepo<K, V>, locker: SmartRWLocker? = null,
clear: Boolean = true, clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
getAll: () -> Map<K, V>
) { ) {
actualizeAll(clear) { when (clearMode) {
repo.getAll() ActualizeAllClearMode.BeforeLoad -> locker ?.let {
actualizeAllWithClearBeforeLoad(locker = locker, getAll)
} ?: actualizeAllWithClearBeforeLoad(getAll)
ActualizeAllClearMode.BeforeSet -> locker ?.let {
actualizeAllWithClearBeforeSet(locker = locker, getAll)
} ?: actualizeAllWithClearBeforeSet(getAll)
ActualizeAllClearMode.Never -> locker ?.let {
actualizeAllWithoutClear(locker = locker, getAll)
} ?: actualizeAllWithoutClear(getAll)
}
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
parentRepo: ReadKeyValueRepo<K, V>,
locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
) {
actualizeAll(locker, clearMode) {
parentRepo.getAll()
} }
} }
suspend inline fun <K, V> KeyValueRepo<K, List<V>>.actualizeAll( suspend inline fun <K, V> KeyValueRepo<K, List<V>>.actualizeAll(
repo: ReadKeyValuesRepo<K, V>, parentRepo: ReadKeyValuesRepo<K, V>,
clear: Boolean = true, locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
) { ) {
actualizeAll(clear) { actualizeAll(locker, clearMode) {
repo.getAll() parentRepo.getAll()
} }
} }
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll( suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
repo: ReadCRUDRepo<V, K>, parentRepo: ReadCRUDRepo<V, K>,
clear: Boolean = true, locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
) { ) {
actualizeAll(clear) { actualizeAll(locker, clearMode) {
repo.getAll() parentRepo.getAll()
} }
} }

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.repos package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
@@ -130,6 +131,14 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
remove(toRemove) remove(toRemove)
} }
override suspend fun set(toSet: Map<Key, List<Value>>) {
toSet.forEach { (k, v) ->
val diff = getAll(k).diff(v)
remove(k, diff.removed.map { it.value })
add(k, diff.added.map { it.value })
}
}
} }
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value> typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>

View File

@@ -1,14 +1,19 @@
package dev.inmo.micro_utils.repos.exposed package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.CRUDRepo import dev.inmo.micro_utils.repos.CRUDRepo
import kotlinx.coroutines.channels.BufferOverflow
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>( abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0, flowsChannelsSize: Int = 0,
tableName: String = "" tableName: String = "",
replyCacheInFlows: Int = 0,
onBufferOverflowBehaviour: BufferOverflow = BufferOverflow.SUSPEND
) : ) :
AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize, flowsChannelsSize,
tableName tableName,
replyCacheInFlows,
onBufferOverflowBehaviour
), ),
ExposedCRUDRepo<ObjectType, IdType>, ExposedCRUDRepo<ObjectType, IdType>,
CRUDRepo<ObjectType, IdType, InputValueType> CRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -35,14 +35,14 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
} }
override suspend fun getById(id: IdType): ObjectType? { override suspend fun getById(id: IdType): ObjectType? {
return transaction(db = database) { return transaction(db = database) {
select { selectAll().where {
selectById(id) selectById(id)
}.limit(1).firstOrNull() ?.asObject }.limit(1).firstOrNull() ?.asObject
} }
} }
override suspend fun contains(id: IdType): Boolean = transaction(db = database) { override suspend fun contains(id: IdType): Boolean = transaction(db = database) {
select { selectById(id) }.limit(1).any() selectAll().where { selectById(id) }.limit(1).any()
} }
override suspend fun getAll(): Map<IdType, ObjectType> = transaction(database) { override suspend fun getAll(): Map<IdType, ObjectType> = transaction(database) {

View File

@@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.UpdatedValuePair import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteCRUDRepo import dev.inmo.micro_utils.repos.WriteCRUDRepo
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.* import org.jetbrains.exposed.sql.statements.*
@@ -11,19 +12,26 @@ import java.util.Objects
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0, flowsChannelsSize: Int = 0,
tableName: String = "", tableName: String = "",
replyCacheInFlows: Int = 0 replyCacheInFlows: Int = 0,
onBufferOverflowBehaviour: BufferOverflow = BufferOverflow.SUSPEND
) : ) :
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName), AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
ExposedCRUDRepo<ObjectType, IdType>, ExposedCRUDRepo<ObjectType, IdType>,
WriteCRUDRepo<ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>
{ {
protected val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize) protected open val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour)
protected val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize) protected open val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour)
protected val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize) protected open val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour)
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow() override val newObjectsFlow: Flow<ObjectType> by lazy {
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow() _newObjectsFlow.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow() }
override val updatedObjectsFlow: Flow<ObjectType> by lazy {
_updatedObjectsFlow.asSharedFlow()
}
override val deletedObjectsIdsFlow: Flow<IdType> by lazy {
_deletedObjectsIdsFlow.asSharedFlow()
}
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
@@ -93,7 +101,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
}.let { }.let {
if (it > 0) { if (it > 0) {
transaction(db = database) { transaction(db = database) {
select { selectAll().where {
selectById(this, id) selectById(this, id)
}.limit(1).firstOrNull() ?.asObject }.limit(1).firstOrNull() ?.asObject
} }
@@ -134,7 +142,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
ids ids
} else { } else {
ids.filter { ids.filter {
select { selectById(it) }.limit(1).none() selectAll().where { selectById(it) }.limit(1).none()
} }
} }
}.forEach { }.forEach {

View File

@@ -65,7 +65,7 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
override suspend fun unsetWithValues(toUnset: List<Value>) { override suspend fun unsetWithValues(toUnset: List<Value>) {
transaction(database) { transaction(database) {
toUnset.flatMap { toUnset.flatMap {
val keys = select { selectByValue(it) }.mapNotNull { it.asKey } val keys = selectAll().where { selectByValue(it) }.mapNotNull { it.asKey }
deleteWhere { selectByIds(it, keys) } deleteWhere { selectByIds(it, keys) }
keys keys
} }

View File

@@ -24,11 +24,11 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
override suspend fun get(k: Key): Value? = transaction(database) { override suspend fun get(k: Key): Value? = transaction(database) {
select { selectById(k) }.limit(1).firstOrNull() ?.asObject selectAll().where { selectById(k) }.limit(1).firstOrNull() ?.asObject
} }
override suspend fun contains(key: Key): Boolean = transaction(database) { override suspend fun contains(key: Key): Boolean = transaction(database) {
select { selectById(key) }.limit(1).any() selectAll().where { selectById(key) }.limit(1).any()
} }
override suspend fun getAll(): Map<Key, Value> = transaction(database) { selectAll().associate { it.asKey to it.asObject } } override suspend fun getAll(): Map<Key, Value> = transaction(database) { selectAll().associate { it.asKey to it.asObject } }
@@ -46,7 +46,7 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
} }
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) { override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
select { selectByValue(v) }.selectPaginated( selectAll().where { selectByValue(v) }.selectPaginated(
pagination, pagination,
keyColumn, keyColumn,
reversed reversed

View File

@@ -65,7 +65,7 @@ open class ExposedKeyValueRepo<Key, Value>(
override suspend fun unsetWithValues(toUnset: List<Value>) { override suspend fun unsetWithValues(toUnset: List<Value>) {
transaction(database) { transaction(database) {
toUnset.flatMap { toUnset.flatMap {
val keys = select { valueColumn.eq(it) }.mapNotNull { it[keyColumn] } val keys = selectAll().where { valueColumn.eq(it) }.mapNotNull { it[keyColumn] }
deleteWhere { keyColumn.inList(keys) } deleteWhere { keyColumn.inList(keys) }
keys keys
} }

View File

@@ -29,7 +29,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>(
transaction(database) { transaction(database) {
toAdd.keys.flatMap { k -> toAdd.keys.flatMap { k ->
toAdd[k] ?.mapNotNull { v -> toAdd[k] ?.mapNotNull { v ->
if (select { selectById(k).and(selectByValue(v)) }.limit(1).any()) { if (selectAll().where { selectById(k).and(selectByValue(v)) }.limit(1).any()) {
return@mapNotNull null return@mapNotNull null
} }
val insertResult = insert { val insertResult = insert {

View File

@@ -19,7 +19,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
get() = asKey get() = asKey
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
override suspend fun count(k: Key): Long = transaction(database) { select { selectById(k) }.count() } override suspend fun count(k: Key): Long = transaction(database) { selectAll().where { selectById(k) }.count() }
override suspend fun count(): Long = transaction(database) { selectAll().count() } override suspend fun count(): Long = transaction(database) { selectAll().count() }
@@ -28,7 +28,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
pagination: Pagination, pagination: Pagination,
reversed: Boolean reversed: Boolean
): PaginationResult<Value> = transaction(database) { ): PaginationResult<Value> = transaction(database) {
select { selectById(k) }.selectPaginated( selectAll().where { selectById(k) }.selectPaginated(
pagination, pagination,
keyColumn, keyColumn,
reversed reversed
@@ -55,7 +55,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
pagination: Pagination, pagination: Pagination,
reversed: Boolean reversed: Boolean
): PaginationResult<Key> = transaction(database) { ): PaginationResult<Key> = transaction(database) {
select { selectByValue(v) }.selectPaginated( selectAll().where { selectByValue(v) }.selectPaginated(
pagination, pagination,
keyColumn, keyColumn,
reversed reversed
@@ -65,11 +65,11 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
} }
override suspend fun contains(k: Key): Boolean = transaction(database) { override suspend fun contains(k: Key): Boolean = transaction(database) {
select { selectById(k) }.limit(1).any() selectAll().where { selectById(k) }.limit(1).any()
} }
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) { override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
select { selectById(k).and(selectByValue(v)) }.limit(1).any() selectAll().where { selectById(k).and(selectByValue(v)) }.limit(1).any()
} }
override suspend fun getAll(reverseLists: Boolean): Map<Key, List<Value>> = transaction(database) { override suspend fun getAll(reverseLists: Boolean): Map<Key, List<Value>> = transaction(database) {
@@ -85,9 +85,9 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
override suspend fun getAll(k: Key, reverseLists: Boolean): List<Value> = transaction(database) { override suspend fun getAll(k: Key, reverseLists: Boolean): List<Value> = transaction(database) {
val query = if (reverseLists) { val query = if (reverseLists) {
select { selectById(k) }.orderBy(keyColumn, SortOrder.DESC) selectAll().where { selectById(k) }.orderBy(keyColumn, SortOrder.DESC)
} else { } else {
select { selectById(k) } selectAll().where { selectById(k) }
} }
query.map { query.map {
it.asObject it.asObject

View File

@@ -34,7 +34,7 @@ open class ExposedKeyValuesRepo<Key, Value>(
transaction(database) { transaction(database) {
toAdd.keys.flatMap { k -> toAdd.keys.flatMap { k ->
toAdd[k] ?.mapNotNull { v -> toAdd[k] ?.mapNotNull { v ->
if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) { if (selectAll().where { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) {
return@mapNotNull null return@mapNotNull null
} }
val insertResult = insert { val insertResult = insert {
@@ -69,7 +69,7 @@ open class ExposedKeyValuesRepo<Key, Value>(
override suspend fun removeWithValue(v: Value) { override suspend fun removeWithValue(v: Value) {
transaction(database) { transaction(database) {
val keys = select { selectByValue(v) }.map { it.asKey } val keys = selectAll().where { selectByValue(v) }.map { it.asKey }
deleteWhere { SqlExpressionBuilder.selectByValue(v) } deleteWhere { SqlExpressionBuilder.selectByValue(v) }
keys keys
}.forEach { }.forEach {
@@ -85,7 +85,7 @@ open class ExposedKeyValuesRepo<Key, Value>(
override suspend fun clearWithValue(v: Value) { override suspend fun clearWithValue(v: Value) {
transaction(database) { transaction(database) {
val toClear = select { selectByValue(v) } val toClear = selectAll().where { selectByValue(v) }
.asSequence() .asSequence()
.map { it.asKey to it.asObject } .map { it.asKey to it.asObject }
.groupBy { it.first } .groupBy { it.first }

View File

@@ -26,7 +26,7 @@ class ExposedStandardVersionsRepoProxy(
} }
override suspend fun getTableVersion(tableName: String): Int? = transaction(database) { override suspend fun getTableVersion(tableName: String): Int? = transaction(database) {
select { tableNameColumn.eq(tableName) }.limit(1).firstOrNull() ?.getOrNull(tableVersionColumn) selectAll().where { tableNameColumn.eq(tableName) }.limit(1).firstOrNull() ?.getOrNull(tableVersionColumn)
} }
override suspend fun updateTableVersion(tableName: String, version: Int) { override suspend fun updateTableVersion(tableName: String, version: Int) {

21
resources/build.gradle Normal file
View File

@@ -0,0 +1,21 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.language_codes")
}
}
androidMain {
dependsOn(jvmMain)
}
}
}

View File

@@ -0,0 +1,20 @@
package dev.inmo.micro_utils.strings
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
fun StringResource.translation(configuration: Configuration): String = translation(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.locales[0]
} else {
configuration.locale
}
)
fun StringResource.translation(resources: Resources): String = translation(resources.configuration)
fun StringResource.translation(context: Context): String = translation(context.resources)
fun Configuration.translation(resource: StringResource): String = resource.translation(this)
fun Resources.translation(resource: StringResource): String = configuration.translation(resource)
fun Context.translation(resource: StringResource): String = resources.translation(resource)

View File

@@ -0,0 +1,103 @@
package dev.inmo.micro_utils.strings
import dev.inmo.micro_utils.language_codes.IetfLang
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Use this class as a type of your strings object fields. For example:
*
* ```kotlin
* object Strings {
* val someResource: StringResource
* }
* ```
*
* Use [buildStringResource] for useful creation of string resource
*
* @see buildStringResource
*/
@Serializable(StringResource.Companion::class)
data class StringResource(
val default: String,
val translations: Map<IetfLang, Lazy<String>>
) {
class Builder(
var default: String
) {
private val map = mutableMapOf<IetfLang, Lazy<String>>()
infix fun IetfLang.variant(value: Lazy<String>) {
map[this] = value
}
infix fun IetfLang.variant(value: () -> String) = this variant lazy(value)
infix fun IetfLang.variant(value: String) = this variant lazyOf(value)
operator fun IetfLang.invoke(value: () -> String) = this variant value
operator fun IetfLang.invoke(value: String) = this variant value
infix fun String.variant(value: Lazy<String>) = IetfLang(this) variant value
infix fun String.variant(value: () -> String) = IetfLang(this) variant lazy(value)
infix fun String.variant(value: String) = this variant lazyOf(value)
operator fun String.invoke(value: () -> String) = this variant value
operator fun String.invoke(value: String) = this variant value
fun build() = StringResource(default, map.toMap())
}
fun translation(languageCode: IetfLang?): String {
if (languageCode == null) {
return default
}
translations[languageCode] ?.let { return it.value }
return languageCode.parentLang ?.let {
translations[it] ?.value
} ?: default
}
companion object : KSerializer<StringResource> {
@Serializable
private class Surrogate(
val default: String,
val translations: Map<String, String>
)
override val descriptor: SerialDescriptor
get() = Surrogate.serializer().descriptor
override fun deserialize(decoder: Decoder): StringResource {
val surrogate = Surrogate.serializer().deserialize(decoder)
return StringResource(
surrogate.default,
surrogate.translations.map { IetfLang(it.key) to lazyOf(it.value) }.toMap()
)
}
override fun serialize(encoder: Encoder, value: StringResource) {
Surrogate.serializer().serialize(
encoder,
Surrogate(
value.default,
value.translations.map {
it.key.code to it.value.value
}.toMap()
)
)
}
}
}
inline fun buildStringResource(
default: String,
builder: StringResource.Builder.() -> Unit = {}
): StringResource {
return StringResource.Builder(default).apply(builder).build()
}

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.strings
import dev.inmo.micro_utils.language_codes.IetfLang
import kotlinx.browser.window
import org.w3c.dom.NavigatorLanguage
fun StringResource.translation(language: NavigatorLanguage) = translation(
language.language.unsafeCast<String?>() ?.let { IetfLang(it) }
)
fun StringResource.translation() = translation(window.navigator)

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.strings
import dev.inmo.micro_utils.language_codes.toIetfLang
import java.util.Locale
fun StringResource.translation(locale: Locale = Locale.getDefault()): String {
return translation(locale.toIetfLang())
}
fun Locale.translation(resource: StringResource): String = resource.translation(this)

View File

@@ -16,11 +16,16 @@ import kotlinx.serialization.encoding.Encoder
*/ */
open class MapperDeserializationStrategy<I, O>( open class MapperDeserializationStrategy<I, O>(
private val base: DeserializationStrategy<I>, private val base: DeserializationStrategy<I>,
private val deserialize: (I) -> O private val deserialize: (Decoder, I) -> O
) : DeserializationStrategy<O> { ) : DeserializationStrategy<O> {
override val descriptor: SerialDescriptor = base.descriptor override val descriptor: SerialDescriptor = base.descriptor
constructor(
base: DeserializationStrategy<I>,
deserialize: (I) -> O
) : this(base, { _, i -> deserialize(i) })
override fun deserialize(decoder: Decoder): O { override fun deserialize(decoder: Decoder): O {
return deserialize(base.deserialize(decoder)) return deserialize(decoder, base.deserialize(decoder))
} }
} }

View File

@@ -1,9 +1,7 @@
package dev.inmo.micro_utils.serialization.mapper package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
/** /**
@@ -11,15 +9,20 @@ import kotlinx.serialization.encoding.Encoder
* serialization * serialization
* *
* @param base Serializer for [I] * @param base Serializer for [I]
* @param serialize Will be used in [serialize] method to convert incoming [O] to [I] and serialize with [base] * @param internalSerialize Will be used in [internalSerialize] method to convert incoming [O] to [I] and serialize with [base]
*/ */
open class MapperSerializationStrategy<I, O>( open class MapperSerializationStrategy<I, O>(
private val base: SerializationStrategy<I>, private val base: SerializationStrategy<I>,
private val serialize: (O) -> I private val internalSerialize: (Encoder, O) -> I
) : SerializationStrategy<O> { ) : SerializationStrategy<O> {
override val descriptor: SerialDescriptor = base.descriptor override val descriptor: SerialDescriptor = base.descriptor
constructor(
base: SerializationStrategy<I>,
serialize: (O) -> I
) : this(base, { _, o -> serialize(o) })
override fun serialize(encoder: Encoder, value: O) { override fun serialize(encoder: Encoder, value: O) {
base.serialize(encoder, serialize(value)) base.serialize(encoder, internalSerialize(encoder, value))
} }
} }

View File

@@ -1,6 +1,8 @@
package dev.inmo.micro_utils.serialization.mapper package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
@@ -15,16 +17,28 @@ import kotlinx.serialization.encoding.Encoder
*/ */
open class MapperSerializer<I, O>( open class MapperSerializer<I, O>(
private val base: KSerializer<I>, private val base: KSerializer<I>,
private val serialize: (O) -> I, private val serialize: (Encoder, O) -> I,
private val deserialize: (I) -> O private val deserialize: (Decoder, I) -> O
) : KSerializer<O> { ) : KSerializer<O>,
DeserializationStrategy<O> by MapperDeserializationStrategy<I, O>(base, deserialize),
SerializationStrategy<O> by MapperSerializationStrategy<I, O>(base, serialize) {
override val descriptor: SerialDescriptor = base.descriptor override val descriptor: SerialDescriptor = base.descriptor
override fun deserialize(decoder: Decoder): O { constructor(
return deserialize(base.deserialize(decoder)) base: KSerializer<I>,
} serialize: (O) -> I,
deserialize: (I) -> O
) : this(base, { _, o -> serialize(o) }, { _, i -> deserialize(i) })
override fun serialize(encoder: Encoder, value: O) { constructor(
base.serialize(encoder, serialize(value)) base: KSerializer<I>,
} serialize: (Encoder, O) -> I,
deserialize: (I) -> O
) : this(base, serialize, { _, i -> deserialize(i) })
constructor(
base: KSerializer<I>,
serialize: (O) -> I,
deserialize: (Decoder, I) -> O
) : this(base, { _, o -> serialize(o) }, deserialize)
} }

View File

@@ -42,6 +42,9 @@ String[] includes = [
":serialization:mapper", ":serialization:mapper",
":startup:plugin", ":startup:plugin",
":startup:launcher", ":startup:launcher",
":colors:common",
":resources",
":fsm:common", ":fsm:common",
":fsm:repos:common", ":fsm:repos:common",