mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-31 20:22:03 +00:00 
			
		
		
		
	Compare commits
	
		
			130 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4338fd46f2 | |||
| 36974f5b49 | |||
| d48f767408 | |||
| bd2558e852 | |||
| 9f7c963cd5 | |||
| 2db0eadbfe | |||
| 580c7b8842 | |||
| 88fba347ea | |||
| c8c5255e62 | |||
| 01a1a2760a | |||
| 6d34df8d48 | |||
| b124ea65e1 | |||
| f3214347a9 | |||
| 6b8724b59f | |||
| ac3e378edf | |||
| 5d31fd1c91 | |||
| 046fe1ec08 | |||
| 721873c843 | |||
| f6ffbfc10a | |||
| 4e91649e0a | |||
| 4f7f8abec4 | |||
| 6370562dbc | |||
| 94e2e67522 | |||
| 1d8330015d | |||
| e5017b0258 | |||
| cd412ca31b | |||
| d2b6473095 | |||
| ab42507275 | |||
| dcef844e20 | |||
| 3244708c1b | |||
| 73ac1f1741 | |||
| f104e9f352 | |||
| 393c9a7d06 | |||
| ea497ea488 | |||
|  | 5add89cad0 | ||
| 2db447d2ef | |||
| 31c83813e6 | |||
| 27483a282d | |||
| 48b816aa22 | |||
| 0065f94f52 | |||
| ccc0002eb2 | |||
| 15a2eee141 | |||
| b9faac71e5 | |||
| 0a4465de33 | |||
| f9dfd09628 | |||
| 8638d7afce | |||
| 8311793a43 | |||
| 0d552cfcd2 | |||
| 4b0f20dbd1 | |||
| cf531c949d | |||
| ba5c5f17d5 | |||
| 35825ad9b7 | |||
| b1eb26a89e | |||
| c9d04b6698 | |||
| 496133e014 | |||
| f2857ee2be | |||
| 22541f2d5e | |||
| e235c52b6c | |||
| e89b8c72dd | |||
| a374e53a3a | |||
| afb066c4ee | |||
| f05761d4a5 | |||
| 76adc9ea33 | |||
| 99dd291413 | |||
| f904eb27e1 | |||
| eeb8214812 | |||
| f7215b039e | |||
| c07fe5a0f9 | |||
| 0d28cb6e20 | |||
| a1a17bfd1f | |||
| f386f09592 | |||
| a47e17fe6e | |||
| 01dc3b63ff | |||
| 2d97e0699e | |||
| 75f514d99b | |||
| 9a687cfc1c | |||
| 14edf8b6b7 | |||
| 23aa2d8917 | |||
| 7651388b5c | |||
| 805ab32b24 | |||
| cc623b1097 | |||
| 1420416b3e | |||
| 9a0b67f938 | |||
| 303e1e6281 | |||
| ff59b0cc9c | |||
| be5d2ee715 | |||
| 8dd2e3f6f9 | |||
| 2eedd196d2 | |||
| 759a3f2784 | |||
| 386fa830c3 | |||
| c382423d77 | |||
| bb466ce66c | |||
| 72cd3dd8a1 | |||
|  | 595cedaaf1 | ||
| eeaceb6cf5 | |||
| 1bd671685b | |||
| 48d3fe41f2 | |||
| 7ab21871cd | |||
| 65d01b1fb3 | |||
| 6230accb68 | |||
| c6ed821934 | |||
| 10e03bb951 | |||
| aa4f392948 | |||
| f51b59ec02 | |||
| 8c76834ae4 | |||
| 4a454f3d67 | |||
| 151aa1863d | |||
| 3bf6896296 | |||
| 0d01561476 | |||
| f6ded92251 | |||
| d01b735cc6 | |||
| 6c12001080 | |||
| 1afbf03606 | |||
| f6ef5c61c5 | |||
| c18fee8107 | |||
| d9df7a4384 | |||
| 87c2230e8e | |||
| da7eb6de0a | |||
| ed3815118f | |||
| be726f42bd | |||
| a91006132f | |||
| 9a9f741a0b | |||
| 5028f130e9 | |||
| 77fa019651 | |||
| 9715da9384 | |||
| f6d5035c1a | |||
| 43e782ab6f | |||
| f3f9920bfb | |||
| 2bfd615812 | |||
| ebfacb3659 | 
							
								
								
									
										197
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										197
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,202 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 0.20.47 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Exposed`: `0.49.0` -> `0.50.0` | ||||||
|  |  | ||||||
|  | ## 0.20.46 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * Now this repo depends on `klock` | ||||||
|  |     * Add new object-serializer `DateTimeSerializer` for `klock` serializer | ||||||
|  |  | ||||||
|  | ## 0.20.45 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Android Core KTX`: `1.12.0` -> `1.13.0` | ||||||
|  |  | ||||||
|  | ## 0.20.44 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Compose`: `1.6.1` -> `1.6.2` | ||||||
|  |     * `Koin`: `3.5.4` -> `3.5.6` | ||||||
|  |  | ||||||
|  | ## 0.20.43 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.3.9` -> `2.3.10` | ||||||
|  |     * `Koin`: `3.5.3` -> `3.5.4` | ||||||
|  |  | ||||||
|  | ## 0.20.42 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Generator`: | ||||||
|  |         * Improvements | ||||||
|  |  | ||||||
|  | ## 0.20.41 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Exposed`: | ||||||
|  |         * `AbstractExposedKeyValueRepo`, `ExposedKeyValueRepo`, `AbstractExposedKeyValuesRepo`, `ExposedKeyValuesRepo` got opportunity to setup some part of their flows | ||||||
|  |  | ||||||
|  | ## 0.20.40 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `KSLog`: `1.3.2` -> `1.3.3` | ||||||
|  |     * `Exposed`: `0.48.0` -> `0.49.0` | ||||||
|  |     * `UUID`: `0.8.2` -> `0.8.4` | ||||||
|  |  | ||||||
|  | ## 0.20.39 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Kotlin`: `1.9.22` -> `1.9.23` | ||||||
|  |     * `Korlibs`: `5.3.2` -> `5.4.0` | ||||||
|  |     * `Okio`: `3.8.0` -> `3.9.0` | ||||||
|  |     * `Compose`: `1.6.0` -> `1.6.1` | ||||||
|  |     * `ComposeMaterial3`: `1.2.0` -> `1.2.1` | ||||||
|  |  | ||||||
|  | ## 0.20.38 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.3.8` -> `2.3.9` | ||||||
|  |  | ||||||
|  | ## 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`: | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								colors/common/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								colors/common/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | plugins { | ||||||
|  |     id "org.jetbrains.kotlin.multiplatform" | ||||||
|  |     id "org.jetbrains.kotlin.plugin.serialization" | ||||||
|  |     id "com.android.library" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | ||||||
							
								
								
									
										174
									
								
								colors/common/src/commonMain/kotlin/HEXAColor.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								colors/common/src/commonMain/kotlin/HEXAColor.kt
									
									
									
									
									
										Normal 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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										209
									
								
								colors/common/src/commonTest/kotlin/HexColorTests.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								colors/common/src/commonTest/kotlin/HexColorTests.kt
									
									
									
									
									
										Normal 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)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,6 +8,11 @@ apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | |||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     sourceSets { |     sourceSets { | ||||||
|  |         commonMain { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.klock | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         jvmMain { |         jvmMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 api project(":micro_utils.coroutines") |                 api project(":micro_utils.coroutines") | ||||||
|   | |||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import korlibs.time.DateTime | ||||||
|  | import kotlinx.serialization.KSerializer | ||||||
|  | import kotlinx.serialization.Serializer | ||||||
|  | import kotlinx.serialization.builtins.serializer | ||||||
|  | import kotlinx.serialization.descriptors.SerialDescriptor | ||||||
|  | import kotlinx.serialization.encoding.Decoder | ||||||
|  | import kotlinx.serialization.encoding.Encoder | ||||||
|  |  | ||||||
|  | @Serializer(DateTime::class) | ||||||
|  | object DateTimeSerializer : KSerializer<DateTime> { | ||||||
|  |     override val descriptor: SerialDescriptor | ||||||
|  |         get() = Double.serializer().descriptor | ||||||
|  |  | ||||||
|  |     override fun deserialize(decoder: Decoder): DateTime { | ||||||
|  |         return DateTime(decoder.decodeDouble()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun serialize(encoder: Encoder, value: DateTime) { | ||||||
|  |         encoder.encodeDouble(value.unixMillis) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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()) | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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) { |     protected val job = internalSharedFlow.subscribe(internalScope) { | ||||||
|         if (_value != it) { |         doOnChangeAction(it) | ||||||
|             onChange(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() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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.47 | ||||||
| android_code_version=223 | android_code_version=253 | ||||||
|   | |||||||
| @@ -1,41 +1,41 @@ | |||||||
| [versions] | [versions] | ||||||
|  |  | ||||||
| kt = "1.9.21" | kt = "1.9.23" | ||||||
| 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.3" | ||||||
|  |  | ||||||
| jb-compose = "1.5.11" | jb-compose = "1.6.2" | ||||||
| jb-exposed = "0.45.0" | jb-exposed = "0.50.0" | ||||||
| jb-dokka = "1.9.10" | jb-dokka = "1.9.20" | ||||||
|  |  | ||||||
| korlibs = "4.0.10" | korlibs = "5.4.0" | ||||||
| uuid = "0.8.2" | uuid = "0.8.4" | ||||||
|  |  | ||||||
| ktor = "2.3.6" | ktor = "2.3.10" | ||||||
|  |  | ||||||
| gh-release = "2.4.1" | gh-release = "2.5.2" | ||||||
|  |  | ||||||
| koin = "3.5.0" | koin = "3.5.6" | ||||||
|  |  | ||||||
| okio = "3.6.0" | okio = "3.9.0" | ||||||
|  |  | ||||||
| ksp = "1.9.21-1.0.15" | ksp = "1.9.23-1.0.20" | ||||||
| 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.4.0" | ||||||
| dexcount = "4.0.0" | dexcount = "4.0.0" | ||||||
|  |  | ||||||
| android-coreKtx = "1.12.0" | android-coreKtx = "1.13.0" | ||||||
| android-recyclerView = "1.3.2" | android-recyclerView = "1.3.2" | ||||||
| android-appCompat = "1.6.1" | 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.1" | ||||||
|  |  | ||||||
| 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" } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -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.7-bin.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|   | |||||||
| @@ -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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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"} | ||||||
| @@ -12,7 +12,6 @@ kotlin { | |||||||
|             dependencies { |             dependencies { | ||||||
|                 api internalProject("micro_utils.common") |                 api internalProject("micro_utils.common") | ||||||
|                 api libs.kt.serialization.cbor |                 api libs.kt.serialization.cbor | ||||||
|                 api libs.klock |  | ||||||
|                 api libs.uuid |                 api libs.uuid | ||||||
|                 api libs.ktor.io |                 api libs.ktor.io | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -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( | ||||||
|  |         tag: Tag, | ||||||
|  |         pretitle: String = baseClassName, | ||||||
|  |         indents: String = "        " | ||||||
|  |     ): 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 """fun String.as$baseClassName(): $baseClassName { |     return """val knownLanguageCodesMap: Map<String, $baseClassName> by lazy { | ||||||
|     return when (this) { |     mapOf( | ||||||
| ${tags.joinToString("\n") { createDeserializeVariantForTag(it) }} | ${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = "        ") }} | ||||||
|         else -> $baseClassName.${unknownBaseClassName}(this) |     ) | ||||||
| } | } | ||||||
|  | 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 ?: "")) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -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 | ||||||
| @@ -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) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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() | ||||||
|   | |||||||
| @@ -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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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"}}} | ||||||
| @@ -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>, | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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() | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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() | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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() { | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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() |         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() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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> | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.repos.exposed.keyvalue | package dev.inmo.micro_utils.repos.exposed.keyvalue | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.repos.KeyValueRepo | import dev.inmo.micro_utils.repos.KeyValueRepo | ||||||
|  | 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.* | ||||||
| @@ -8,13 +9,15 @@ import org.jetbrains.exposed.sql.transactions.transaction | |||||||
|  |  | ||||||
| abstract class AbstractExposedKeyValueRepo<Key, Value>( | abstract class AbstractExposedKeyValueRepo<Key, Value>( | ||||||
|     override val database: Database, |     override val database: Database, | ||||||
|     tableName: String? = null |     tableName: String? = null, | ||||||
|  |     flowsExtraBufferCapacity: Int = Int.MAX_VALUE, | ||||||
|  |     flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND | ||||||
| ) : KeyValueRepo<Key, Value>, AbstractExposedReadKeyValueRepo<Key, Value>( | ) : KeyValueRepo<Key, Value>, AbstractExposedReadKeyValueRepo<Key, Value>( | ||||||
|     database, |     database, | ||||||
|     tableName |     tableName | ||||||
| ) { | ) { | ||||||
|     protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>() |     protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|     protected val _onValueRemoved = MutableSharedFlow<Key>() |     protected val _onValueRemoved = MutableSharedFlow<Key>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|  |  | ||||||
|     override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() |     override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() | ||||||
|     override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() |     override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() | ||||||
| @@ -65,7 +68,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 | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue | |||||||
|  |  | ||||||
| import dev.inmo.micro_utils.repos.KeyValueRepo | import dev.inmo.micro_utils.repos.KeyValueRepo | ||||||
| import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | ||||||
|  | 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.SqlExpressionBuilder.eq | import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq | ||||||
| @@ -13,15 +14,17 @@ open class ExposedKeyValueRepo<Key, Value>( | |||||||
|     database: Database, |     database: Database, | ||||||
|     keyColumnAllocator: ColumnAllocator<Key>, |     keyColumnAllocator: ColumnAllocator<Key>, | ||||||
|     valueColumnAllocator: ColumnAllocator<Value>, |     valueColumnAllocator: ColumnAllocator<Value>, | ||||||
|     tableName: String? = null |     tableName: String? = null, | ||||||
|  |     flowsExtraBufferCapacity: Int = Int.MAX_VALUE, | ||||||
|  |     flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND | ||||||
| ) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>( | ) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>( | ||||||
|     database, |     database, | ||||||
|     keyColumnAllocator, |     keyColumnAllocator, | ||||||
|     valueColumnAllocator, |     valueColumnAllocator, | ||||||
|     tableName |     tableName | ||||||
| ) { | ) { | ||||||
|     protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>() |     protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|     protected val _onValueRemoved = MutableSharedFlow<Key>() |     protected val _onValueRemoved = MutableSharedFlow<Key>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|  |  | ||||||
|     override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() |     override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() | ||||||
|     override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() |     override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() | ||||||
| @@ -65,7 +68,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 | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.repos.exposed.onetomany | package dev.inmo.micro_utils.repos.exposed.onetomany | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.repos.KeyValuesRepo | import dev.inmo.micro_utils.repos.KeyValuesRepo | ||||||
|  | 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.InsertStatement | import org.jetbrains.exposed.sql.statements.InsertStatement | ||||||
| @@ -8,18 +9,20 @@ import org.jetbrains.exposed.sql.transactions.transaction | |||||||
|  |  | ||||||
| abstract class AbstractExposedKeyValuesRepo<Key, Value>( | abstract class AbstractExposedKeyValuesRepo<Key, Value>( | ||||||
|     override val database: Database, |     override val database: Database, | ||||||
|     tableName: String? = null |     tableName: String? = null, | ||||||
|  |     flowsExtraBufferCapacity: Int = Int.MAX_VALUE, | ||||||
|  |     flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND | ||||||
| ) : KeyValuesRepo<Key, Value>, AbstractExposedReadKeyValuesRepo<Key, Value>( | ) : KeyValuesRepo<Key, Value>, AbstractExposedReadKeyValuesRepo<Key, Value>( | ||||||
|     database, |     database, | ||||||
|     tableName |     tableName | ||||||
| ) { | ) { | ||||||
|     protected val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow() |     protected val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|     override val onNewValue: Flow<Pair<Key, Value>> |     override val onNewValue: Flow<Pair<Key, Value>> | ||||||
|         get() = _onNewValue.asSharedFlow() |         get() = _onNewValue.asSharedFlow() | ||||||
|     protected val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow() |     protected val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|     override val onValueRemoved: Flow<Pair<Key, Value>> |     override val onValueRemoved: Flow<Pair<Key, Value>> | ||||||
|         get() = _onValueRemoved.asSharedFlow() |         get() = _onValueRemoved.asSharedFlow() | ||||||
|     protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow() |     protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|     override val onDataCleared: Flow<Key> |     override val onDataCleared: Flow<Key> | ||||||
|         get() = _onDataCleared.asSharedFlow() |         get() = _onDataCleared.asSharedFlow() | ||||||
|  |  | ||||||
| @@ -29,7 +32,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 { | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.exposed.onetomany | |||||||
|  |  | ||||||
| import dev.inmo.micro_utils.repos.KeyValuesRepo | import dev.inmo.micro_utils.repos.KeyValuesRepo | ||||||
| import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | ||||||
|  | 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.SqlExpressionBuilder.eq | import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq | ||||||
| @@ -13,20 +14,22 @@ open class ExposedKeyValuesRepo<Key, Value>( | |||||||
|     database: Database, |     database: Database, | ||||||
|     keyColumnAllocator: ColumnAllocator<Key>, |     keyColumnAllocator: ColumnAllocator<Key>, | ||||||
|     valueColumnAllocator: ColumnAllocator<Value>, |     valueColumnAllocator: ColumnAllocator<Value>, | ||||||
|     tableName: String? = null |     tableName: String? = null, | ||||||
|  |     flowsExtraBufferCapacity: Int = Int.MAX_VALUE, | ||||||
|  |     flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND | ||||||
| ) : KeyValuesRepo<Key, Value>, ExposedReadKeyValuesRepo<Key, Value>( | ) : KeyValuesRepo<Key, Value>, ExposedReadKeyValuesRepo<Key, Value>( | ||||||
|     database, |     database, | ||||||
|     keyColumnAllocator, |     keyColumnAllocator, | ||||||
|     valueColumnAllocator, |     valueColumnAllocator, | ||||||
|     tableName |     tableName | ||||||
| ) { | ) { | ||||||
|     protected val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow() |     protected val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|     override val onNewValue: Flow<Pair<Key, Value>> |     override val onNewValue: Flow<Pair<Key, Value>> | ||||||
|         get() = _onNewValue |         get() = _onNewValue | ||||||
|     protected val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow() |     protected val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|     override val onValueRemoved: Flow<Pair<Key, Value>> |     override val onValueRemoved: Flow<Pair<Key, Value>> | ||||||
|         get() = _onValueRemoved |         get() = _onValueRemoved | ||||||
|     protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow() |     protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||||
|     override val onDataCleared: Flow<Key> |     override val onDataCleared: Flow<Key> | ||||||
|         get() = _onDataCleared |         get() = _onDataCleared | ||||||
|  |  | ||||||
| @@ -34,7 +37,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 +72,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 +88,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 } | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -1,8 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.repos.generator | package dev.inmo.micro_utils.repos.generator | ||||||
|  |  | ||||||
| import com.google.devtools.ksp.KspExperimental | import com.google.devtools.ksp.* | ||||||
| import com.google.devtools.ksp.getAnnotationsByType |  | ||||||
| import com.google.devtools.ksp.isAnnotationPresent |  | ||||||
| import com.google.devtools.ksp.processing.CodeGenerator | import com.google.devtools.ksp.processing.CodeGenerator | ||||||
| import com.google.devtools.ksp.processing.Resolver | import com.google.devtools.ksp.processing.Resolver | ||||||
| import com.google.devtools.ksp.processing.SymbolProcessor | import com.google.devtools.ksp.processing.SymbolProcessor | ||||||
| @@ -36,6 +34,7 @@ import kotlinx.serialization.Serializable | |||||||
| import java.io.File | import java.io.File | ||||||
| import kotlin.reflect.KProperty1 | import kotlin.reflect.KProperty1 | ||||||
| import kotlin.reflect.full.memberProperties | import kotlin.reflect.full.memberProperties | ||||||
|  | import kotlin.reflect.jvm.jvmName | ||||||
|  |  | ||||||
| private fun KSClassifierReference.quilifiedName(): String = "${qualifier ?.let { "${it.quilifiedName()}." } ?: ""}${referencedName()}" | private fun KSClassifierReference.quilifiedName(): String = "${qualifier ?.let { "${it.quilifiedName()}." } ?: ""}${referencedName()}" | ||||||
|  |  | ||||||
| @@ -75,16 +74,29 @@ class Processor( | |||||||
|                         ORIGINAL FILE: ${ksFile.fileName} |                         ORIGINAL FILE: ${ksFile.fileName} | ||||||
|                     """.trimIndent() |                     """.trimIndent() | ||||||
|                     ) |                     ) | ||||||
|  |  | ||||||
|                     val newName = "New${ksClassDeclaration.simpleName.getShortName()}" |                     val newName = "New${ksClassDeclaration.simpleName.getShortName()}" | ||||||
|                     val registeredName = "Registered${ksClassDeclaration.simpleName.getShortName()}" |                     val registeredName = "Registered${ksClassDeclaration.simpleName.getShortName()}" | ||||||
|  |  | ||||||
|                     val allKSClassProperties = ksClassDeclaration.getAllProperties() |                     val allKSClassProperties = ksClassDeclaration.getAllProperties() | ||||||
|                     val excludedKSClassProperties = allKSClassProperties.filter { |                     val excludedKSClassProperties = allKSClassProperties.filter { property -> | ||||||
|                         it.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) |                         property.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) || (property.findOverridee() ?.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) == true) | ||||||
|                     } |                     } | ||||||
|                     val excludedKSClassPropertiesNames = excludedKSClassProperties.map { it.simpleName.asString() } |                     val excludedKSClassPropertiesNames = excludedKSClassProperties.map { it.simpleName.asString() } | ||||||
|                     val ksClassProperties = allKSClassProperties.filter { |                     val ksClassProperties = allKSClassProperties.filter { | ||||||
|                         it !in excludedKSClassProperties |                         it !in excludedKSClassProperties | ||||||
|  |                     }.groupBy { it.simpleName.asString() }.map { | ||||||
|  |                         var current = it.value.first() | ||||||
|  |                         var currentType = current.type.resolve() | ||||||
|  |                         it.value.forEach { | ||||||
|  |                             val type = it.type.resolve() | ||||||
|  |  | ||||||
|  |                             if (currentType.isAssignableFrom(type) && !type.isAssignableFrom(currentType)) { | ||||||
|  |                                 current = it | ||||||
|  |                                 currentType = type | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         current | ||||||
|                     } |                     } | ||||||
|                     val ksClassPropertiesNames = ksClassProperties.map { it.simpleName.asString() } |                     val ksClassPropertiesNames = ksClassProperties.map { it.simpleName.asString() } | ||||||
|                     val newNewType = TypeSpec.classBuilder(newName).apply { |                     val newNewType = TypeSpec.classBuilder(newName).apply { | ||||||
| @@ -99,17 +111,20 @@ class Processor( | |||||||
|                         } |                         } | ||||||
|                         primaryConstructor( |                         primaryConstructor( | ||||||
|                             FunSpec.constructorBuilder().apply { |                             FunSpec.constructorBuilder().apply { | ||||||
|  |                                 val withoutDefaults = mutableListOf<Pair<ParameterSpec.Builder, PropertySpec.Builder>>() | ||||||
|                                 ksClassProperties.forEach { |                                 ksClassProperties.forEach { | ||||||
|                                     addParameter( |                                     val property = PropertySpec.builder(it.simpleName.getShortName(), it.type.toTypeName(), KModifier.OVERRIDE).apply { | ||||||
|                                         ParameterSpec.builder(it.simpleName.getShortName(), it.typeName).apply { |  | ||||||
|                                             annotations += it.annotations.map { it.toAnnotationSpec() } |  | ||||||
|                                         }.build() |  | ||||||
|                                     ) |  | ||||||
|                                     typeBuilder.addProperty( |  | ||||||
|                                         PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply { |  | ||||||
|                                         initializer(it.simpleName.getShortName()) |                                         initializer(it.simpleName.getShortName()) | ||||||
|                                         }.build() |                                     } | ||||||
|                                     ) |                                     ParameterSpec.builder(it.simpleName.getShortName(), it.type.toTypeName()).apply { | ||||||
|  |                                         withoutDefaults.add(this to property) | ||||||
|  |                                         annotations += it.annotations.map { it.toAnnotationSpec() } | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 withoutDefaults.forEach { | ||||||
|  |                                     addParameter(it.first.build()) | ||||||
|  |                                     addProperty(it.second.build()) | ||||||
|                                 } |                                 } | ||||||
|                             }.build() |                             }.build() | ||||||
|                         ) |                         ) | ||||||
| @@ -125,14 +140,25 @@ class Processor( | |||||||
|                         (it.arguments.first().value as List<KSType>).map { it.declaration as KSClassDeclaration } |                         (it.arguments.first().value as List<KSType>).map { it.declaration as KSClassDeclaration } | ||||||
|                     }.toList() |                     }.toList() | ||||||
|  |  | ||||||
|  |  | ||||||
|                     val registeredTypesProperties: List<KSPropertyDeclaration> = registeredSupertypes.flatMap { registeredType -> |                     val registeredTypesProperties: List<KSPropertyDeclaration> = registeredSupertypes.flatMap { registeredType -> | ||||||
|                         registeredType.getAllProperties() |                         registeredType.getAllProperties() | ||||||
|                     }.filter { |                     }.filter { | ||||||
|                         it.simpleName.asString() !in excludedKSClassPropertiesNames && it.getAnnotationsByType(GenerateCRUDModelExcludeOverride::class).none() |                         it.simpleName.asString() !in excludedKSClassPropertiesNames && !it.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) | ||||||
|                     } |                     } | ||||||
|                     val allProperties: List<KSPropertyDeclaration> = ksClassProperties.toList() + registeredTypesProperties |                     val allProperties: List<KSPropertyDeclaration> = registeredTypesProperties + ksClassProperties.toList() | ||||||
|                     val propertiesToOverrideInRegistered = allProperties.distinctBy { it.simpleName.asString() }.sortedBy { property -> |                     val propertiesToOverrideInRegistered = allProperties.groupBy { it.simpleName.asString() }.map { | ||||||
|  |                         var current = it.value.first() | ||||||
|  |                         var currentType = current.type.resolve() | ||||||
|  |                         it.value.forEach { | ||||||
|  |                             val type = it.type.resolve() | ||||||
|  |  | ||||||
|  |                             if (currentType.isAssignableFrom(type) && !type.isAssignableFrom(currentType)) { | ||||||
|  |                                 current = it | ||||||
|  |                                 currentType = type | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         current | ||||||
|  |                     }.sortedBy { property -> | ||||||
|                         val name = property.simpleName.asString() |                         val name = property.simpleName.asString() | ||||||
|  |  | ||||||
|                         ksClassPropertiesNames.indexOf(name).takeIf { it > -1 } ?.let { |                         ksClassPropertiesNames.indexOf(name).takeIf { it > -1 } ?.let { | ||||||
| @@ -156,17 +182,20 @@ class Processor( | |||||||
|                         addModifiers(KModifier.DATA) |                         addModifiers(KModifier.DATA) | ||||||
|                         primaryConstructor( |                         primaryConstructor( | ||||||
|                             FunSpec.constructorBuilder().apply { |                             FunSpec.constructorBuilder().apply { | ||||||
|  |                                 val withoutDefaults = mutableListOf<Pair<ParameterSpec.Builder, PropertySpec.Builder>>() | ||||||
|                                 propertiesToOverrideInRegistered.forEach { |                                 propertiesToOverrideInRegistered.forEach { | ||||||
|                                     addParameter( |                                     val property = PropertySpec.builder(it.simpleName.getShortName(), it.type.toTypeName(), KModifier.OVERRIDE).apply { | ||||||
|                                         ParameterSpec.builder(it.simpleName.getShortName(), it.typeName).apply { |  | ||||||
|                                             annotations += it.annotations.map { it.toAnnotationSpec() } |  | ||||||
|                                         }.build() |  | ||||||
|                                     ) |  | ||||||
|                                     typeBuilder.addProperty( |  | ||||||
|                                         PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply { |  | ||||||
|                                         initializer(it.simpleName.getShortName()) |                                         initializer(it.simpleName.getShortName()) | ||||||
|                                         }.build() |                                     } | ||||||
|                                     ) |                                     ParameterSpec.builder(it.simpleName.getShortName(), it.type.toTypeName()).apply { | ||||||
|  |                                         withoutDefaults.add(this to property) | ||||||
|  |                                         annotations += it.annotations.map { it.toAnnotationSpec() } | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 withoutDefaults.forEach { | ||||||
|  |                                     addParameter(it.first.build()) | ||||||
|  |                                     addProperty(it.second.build()) | ||||||
|                                 } |                                 } | ||||||
|                             }.build() |                             }.build() | ||||||
|                         ) |                         ) | ||||||
| @@ -191,7 +220,7 @@ class Processor( | |||||||
|                         FunSpec.builder("asRegistered").apply { |                         FunSpec.builder("asRegistered").apply { | ||||||
|                             receiver(ksClassDeclaration.toClassName()) |                             receiver(ksClassDeclaration.toClassName()) | ||||||
|                             (registeredTypesProperties.filter { it.simpleName.asString() !in ksClassPropertiesNames }).forEach { |                             (registeredTypesProperties.filter { it.simpleName.asString() !in ksClassPropertiesNames }).forEach { | ||||||
|                                 addParameter(it.simpleName.asString(), it.typeName) |                                 addParameter(it.simpleName.asString(), it.type.toTypeName()) | ||||||
|                             } |                             } | ||||||
|                             addCode( |                             addCode( | ||||||
|                                 CodeBlock.of( |                                 CodeBlock.of( | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ public data class NewTest( | |||||||
|   override val property1: String, |   override val property1: String, | ||||||
|   override val property2: Int, |   override val property2: Int, | ||||||
|   @Serializable |   @Serializable | ||||||
|  |   @SerialName(`value` = "custom_parent_name") | ||||||
|   override val parent: ParentTypeId?, |   override val parent: ParentTypeId?, | ||||||
| ) : Test | ) : Test | ||||||
|  |  | ||||||
| @@ -24,6 +25,7 @@ public data class RegisteredTest( | |||||||
|   override val property1: String, |   override val property1: String, | ||||||
|   override val property2: Int, |   override val property2: Int, | ||||||
|   @Serializable |   @Serializable | ||||||
|  |   @SerialName(`value` = "custom_parent_name") | ||||||
|   override val parent: ParentTypeId?, |   override val parent: ParentTypeId?, | ||||||
| ) : Test, IRegisteredTest | ) : Test, IRegisteredTest | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.generator.test | |||||||
|  |  | ||||||
| import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModel | import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModel | ||||||
| import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModelExcludeOverride | import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModelExcludeOverride | ||||||
|  | import kotlinx.serialization.SerialName | ||||||
| import kotlinx.serialization.Serializable | import kotlinx.serialization.Serializable | ||||||
| import kotlin.jvm.JvmInline | import kotlin.jvm.JvmInline | ||||||
|  |  | ||||||
| @@ -18,6 +19,7 @@ sealed interface Test { | |||||||
|     val property1: String |     val property1: String | ||||||
|     val property2: Int |     val property2: Int | ||||||
|     @Serializable |     @Serializable | ||||||
|  |     @SerialName("custom_parent_name") | ||||||
|     val parent: ParentTypeId? |     val parent: ParentTypeId? | ||||||
|  |  | ||||||
|     @GenerateCRUDModelExcludeOverride |     @GenerateCRUDModelExcludeOverride | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								resources/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								resources/build.gradle
									
									
									
									
									
										Normal 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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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) | ||||||
							
								
								
									
										103
									
								
								resources/src/commonMain/kotlin/StringResource.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								resources/src/commonMain/kotlin/StringResource.kt
									
									
									
									
									
										Normal 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() | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								resources/src/jsMain/kotlin/StringResourceExtensions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								resources/src/jsMain/kotlin/StringResourceExtensions.kt
									
									
									
									
									
										Normal 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) | ||||||
							
								
								
									
										10
									
								
								resources/src/jvmMain/kotlin/StringResourceLocaleGetter.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								resources/src/jvmMain/kotlin/StringResourceLocaleGetter.kt
									
									
									
									
									
										Normal 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) | ||||||
| @@ -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)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user