Compare commits

...

151 Commits

Author SHA1 Message Date
30440b4ed1
Merge pull request #457 from InsanusMokrassar/0.20.52
0.20.52
2024-06-14 23:57:26 +06:00
09bb90604d weaks rework 2024-06-14 17:09:04 +06:00
4d55ec6f36 add separated WeakScope 2024-06-14 16:58:40 +06:00
f373524f34 a little improve of weak extensions and add tests for weak scopes 2024-06-14 16:53:56 +06:00
0398a7bebd start 0.20.52 2024-06-14 16:36:52 +06:00
fa8a5bcd97
Merge pull request #453 from InsanusMokrassar/0.20.51
0.20.51
2024-05-26 23:53:46 +06:00
758a92410b more fixes to god of fixes in paginations 2024-05-26 23:51:46 +06:00
77f56c5dda make improvements in pagination 2024-05-26 23:35:24 +06:00
72f2fe3cc3 add "indices" cache in pagination with reversed functionality 2024-05-26 23:01:23 +06:00
7e0e520f03 add note about android fragment update 2024-05-26 23:00:11 +06:00
92c4784e42 paginations improvements 2024-05-26 22:59:19 +06:00
a634229dc0 start 0.20.51 2024-05-26 19:46:53 +06:00
de3f36ef2a
Merge pull request #445 from InsanusMokrassar/0.20.50
0.20.50
2024-05-12 21:58:07 +06:00
c623a265ee fill changelog and remove redundant parts in coroutines compose build.gradle 2024-05-12 21:51:20 +06:00
e75125f6df now SmartMutex and SmartSemaphore (and SmartRWLocker as a result) works on SpecialMutableStateFlow 2024-05-12 21:44:33 +06:00
4901a8844c complete rework of states 2024-05-12 21:34:17 +06:00
a1854b68d8 almost updated dependencies and modules hierarchy 2024-05-12 19:17:16 +06:00
aee4a6243b start 0.20.50 2024-05-12 17:01:22 +06:00
4c0cb73d69
Merge pull request #438 from InsanusMokrassar/0.20.49
0.20.49
2024-05-04 23:04:10 +06:00
c6eab182f6 new crud repo extensions on* 2024-05-02 18:20:12 +06:00
3e3fbd97eb diff/applyDiff 2024-05-02 17:18:43 +06:00
feb695caa7 start 0.20.49 2024-05-02 15:08:53 +06:00
1bd46d9651
Merge pull request #437 from InsanusMokrassar/0.20.48
0.20.48
2024-05-02 14:09:47 +06:00
32eabb6b36 update dependencies 2024-05-02 14:06:46 +06:00
9bfe6dc6d8 start 0.20.48 2024-05-02 13:58:12 +06:00
3f366aeea4
Merge pull request #434 from InsanusMokrassar/0.20.47
0.20.47
2024-05-01 18:16:58 +06:00
4338fd46f2 update android gradle plugin 2024-05-01 18:14:28 +06:00
36974f5b49 update dependencies 2024-05-01 17:47:47 +06:00
d48f767408 start 0.20.47 2024-05-01 17:47:02 +06:00
bd2558e852
Merge pull request #430 from InsanusMokrassar/0.20.46
0.20.46
2024-04-27 15:41:22 +06:00
9f7c963cd5 add klock in common and DateTimeSerializer 2024-04-27 15:34:52 +06:00
2db0eadbfe start 0.20.46 2024-04-27 15:17:40 +06:00
580c7b8842
Merge pull request #429 from InsanusMokrassar/0.20.45
0.20.45
2024-04-22 11:34:25 +06:00
88fba347ea update android core ktx 2024-04-22 11:27:07 +06:00
c8c5255e62 start 0.20.45 2024-04-22 11:25:02 +06:00
01a1a2760a
Merge pull request #427 from InsanusMokrassar/0.20.44
0.20.44
2024-04-15 00:30:41 +06:00
6d34df8d48 update dependencies 2024-04-15 00:29:22 +06:00
b124ea65e1 start 0.20.44 2024-04-15 00:19:11 +06:00
f3214347a9
Merge pull request #424 from InsanusMokrassar/0.20.43
0.20.43
2024-04-08 22:13:09 +06:00
6b8724b59f update dependencies 2024-04-08 21:56:02 +06:00
ac3e378edf start 0.20.43 2024-04-08 21:53:43 +06:00
5d31fd1c91
Merge pull request #420 from InsanusMokrassar/0.20.42
0.20.42
2024-04-05 15:09:27 +06:00
046fe1ec08
Update Processor.kt 2024-04-04 23:36:59 +06:00
721873c843 repos models generator improvements 2024-04-04 19:28:59 +06:00
f6ffbfc10a start 0.20.42 2024-04-04 19:28:11 +06:00
4e91649e0a
Merge pull request #419 from InsanusMokrassar/0.20.41
0.20.41
2024-04-01 19:02:13 +06:00
4f7f8abec4 improve exposed repos 2024-04-01 18:20:05 +06:00
6370562dbc start 0.20.41 2024-04-01 18:12:25 +06:00
94e2e67522
Merge pull request #418 from InsanusMokrassar/0.20.40
0.20.40
2024-03-31 12:49:35 +06:00
1d8330015d update changelog 2024-03-31 12:48:55 +06:00
e5017b0258 update dependencies 2024-03-29 20:10:32 +06:00
cd412ca31b start 0.20.40 2024-03-29 20:03:36 +06:00
d2b6473095
Merge pull request #412 from InsanusMokrassar/0.20.39
0.20.39
2024-03-14 21:25:28 +06:00
ab42507275
Update CHANGELOG.md 2024-03-14 21:05:23 +06:00
dcef844e20
Update libs.versions.toml 2024-03-14 10:58:08 +06:00
3244708c1b
Update gradle.properties 2024-03-14 10:44:57 +06:00
73ac1f1741
Merge pull request #405 from InsanusMokrassar/0.20.38
0.20.38
2024-03-06 02:05:30 +06:00
f104e9f352 update ktor dependency 2024-03-06 01:51:48 +06:00
393c9a7d06 start 0.20.38 2024-03-06 01:50:34 +06:00
ea497ea488
Merge pull request #404 from InsanusMokrassar/renovate/jb.dokka
Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.20
2024-03-05 11:07:09 +06:00
renovate[bot]
5add89cad0
Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.20 2024-03-04 15:11:31 +00:00
2db447d2ef
Merge pull request #402 from InsanusMokrassar/0.20.37
0.20.37
2024-03-01 03:25:36 +06:00
31c83813e6 update dependencies 2024-03-01 03:15:25 +06:00
27483a282d start 0.20.37 2024-03-01 03:06:26 +06:00
48b816aa22
Merge pull request #396 from InsanusMokrassar/0.20.36
0.20.36
2024-02-26 17:10:05 +06:00
0065f94f52 update dependencies 2024-02-26 16:55:18 +06:00
ccc0002eb2 improve of actualize all 2024-02-21 00:05:57 +06:00
15a2eee141 start 0.20.36 2024-02-20 22:52:02 +06:00
b9faac71e5
Merge pull request #394 from InsanusMokrassar/0.20.35
0.20.35
2024-02-16 19:11:27 +06:00
0a4465de33 remove API dumps 2024-02-16 19:09:55 +06:00
f9dfd09628 update dependencies 2024-02-16 19:01:46 +06:00
8638d7afce start 0.20.35 2024-02-16 18:50:35 +06:00
8311793a43
Merge pull request #392 from InsanusMokrassar/0.20.34
0.20.34
2024-02-15 16:32:41 +06:00
0d552cfcd2 fix build 2024-02-15 16:18:31 +06:00
4b0f20dbd1 improve default 'set' of KeyValuesRepo 2024-02-15 15:36:32 +06:00
cf531c949d start 0.20.34 2024-02-15 15:14:38 +06:00
ba5c5f17d5
Merge pull request #391 from InsanusMokrassar/0.20.33
0.20.33
2024-02-12 14:42:31 +06:00
35825ad9b7 improve HEXAColorTests 2024-02-12 14:41:55 +06:00
b1eb26a89e fixes and testsAdditions in HEXAColor 2024-02-12 14:25:45 +06:00
c9d04b6698 add ahex to color 2024-02-12 13:58:06 +06:00
496133e014 start 0.20.33 2024-02-12 13:32:30 +06:00
f2857ee2be
Merge pull request #388 from InsanusMokrassar/0.20.32
0.20.32
2024-02-10 17:14:04 +06:00
22541f2d5e
Update CHANGELOG.md 2024-02-10 15:40:59 +06:00
e235c52b6c
Update libs.versions.toml 2024-02-10 15:39:48 +06:00
e89b8c72dd
Update gradle-wrapper.properties 2024-02-08 10:40:43 +06:00
a374e53a3a update StringResource 2024-02-06 19:56:44 +06:00
afb066c4ee start 0.20.32 2024-02-06 19:55:34 +06:00
f05761d4a5
Merge pull request #386 from InsanusMokrassar/0.20.31
0.20.31
2024-01-31 18:10:53 +06:00
76adc9ea33 update ktor 2024-01-31 17:37:27 +06:00
99dd291413 start 0.20.31 2024-01-31 17:36:30 +06:00
f904eb27e1
Merge pull request #384 from InsanusMokrassar/0.20.30
0.20.30
2024-01-31 17:27:17 +06:00
eeb8214812 update exposed 2024-01-31 17:05:43 +06:00
f7215b039e start 0.20.30 2024-01-31 17:04:38 +06:00
c07fe5a0f9
Merge pull request #382 from InsanusMokrassar/0.20.29
0.20.29
2024-01-30 23:03:30 +06:00
0d28cb6e20 update dependencies 2024-01-30 21:18:49 +06:00
a1a17bfd1f start 0.20.29 2024-01-30 20:21:45 +06:00
f386f09592
Merge pull request #377 from InsanusMokrassar/0.20.28
0.20.28
2024-01-19 12:59:11 +06:00
a47e17fe6e downgrade kotlin and compose 2024-01-18 23:19:17 +06:00
01dc3b63ff start 0.20.28 2024-01-18 23:12:36 +06:00
2d97e0699e
Merge pull request #372 from InsanusMokrassar/0.20.27
0.20.27
2024-01-16 12:43:08 +06:00
75f514d99b update github release 2024-01-16 12:38:53 +06:00
9a687cfc1c update dependencies 2024-01-16 12:19:30 +06:00
14edf8b6b7 start 0.20.27 2024-01-16 12:13:07 +06:00
23aa2d8917
Merge pull request #371 from InsanusMokrassar/0.20.26
0.20.26
2024-01-11 23:29:05 +06:00
7651388b5c update exposed version 2024-01-11 23:09:17 +06:00
805ab32b24 improvements in resources 2024-01-11 23:01:10 +06:00
cc623b1097 start 0.20.26 2024-01-11 23:00:14 +06:00
1420416b3e
Merge pull request #369 from InsanusMokrassar/0.20.25
0.20.25
2024-01-04 20:34:15 +06:00
9a0b67f938 fix of tests 2024-01-04 20:30:09 +06:00
303e1e6281 add rgb and rgba representations of HEXAColor 2024-01-04 20:20:55 +06:00
ff59b0cc9c add colors module 2024-01-04 19:53:43 +06:00
be5d2ee715 start 0.20.25 2024-01-04 18:14:09 +06:00
8dd2e3f6f9
Merge pull request #366 from InsanusMokrassar/0.20.24
0.20.24
2024-01-04 10:26:00 +06:00
2eedd196d2 update changelog 2024-01-04 10:25:50 +06:00
759a3f2784 make it possible to use encoder inside of serialize callback 2024-01-04 10:24:39 +06:00
386fa830c3 update soywiz dependencies and downgrade ksp 2024-01-04 09:58:03 +06:00
c382423d77 upgrade MapperSerializer 2023-12-31 13:44:50 +06:00
bb466ce66c start 0.20.24 2023-12-31 13:38:57 +06:00
72cd3dd8a1
Merge pull request #365 from InsanusMokrassar/renovate/ksp
Update ksp to v1.9.22-1.0.16
2023-12-25 21:53:05 +06:00
renovate[bot]
595cedaaf1
Update ksp to v1.9.22-1.0.16 2023-12-25 10:15:44 +00:00
eeaceb6cf5
Merge pull request #362 from InsanusMokrassar/0.20.23
0.20.23
2023-12-22 02:43:14 +06:00
1bd671685b update dependencies and remove redundant usages of old IetfLanguageCode 2023-12-21 23:25:36 +06:00
48d3fe41f2 a lot of improvements in language codes 2023-12-21 15:41:11 +06:00
7ab21871cd Revert "add klock module"
This reverts commit 65d01b1fb3.
2023-12-21 14:05:27 +06:00
65d01b1fb3 add klock module 2023-12-17 23:37:45 +06:00
6230accb68 start 0.20.23 2023-12-17 22:30:25 +06:00
c6ed821934
Merge pull request #360 from InsanusMokrassar/0.20.22
0.20.22
2023-12-14 23:57:51 +06:00
10e03bb951 allow to create own Diff with constructor 2023-12-14 23:56:56 +06:00
aa4f392948 start 0.20.22 2023-12-14 23:52:28 +06:00
f51b59ec02
Update libs.versions.toml 2023-12-14 13:47:34 +06:00
8c76834ae4
Merge pull request #359 from InsanusMokrassar/0.20.21
0.20.21
2023-12-12 23:49:26 +06:00
4a454f3d67 add opportunity to use translation with default locale 2023-12-12 23:45:22 +06:00
151aa1863d update gradle wrapper 2023-12-12 21:41:53 +06:00
3bf6896296 get back translations with android and java resources packages 2023-12-12 21:41:09 +06:00
0d01561476 optimizations and improvements in resources 2023-12-12 21:39:58 +06:00
f6ded92251 init resources module 2023-12-12 20:58:08 +06:00
d01b735cc6 start 0.20.21 2023-12-12 20:21:37 +06:00
6c12001080
Merge pull request #358 from InsanusMokrassar/0.20.20
0.20.20
2023-12-12 00:12:34 +06:00
1afbf03606 improvements in AbstractExposedCRUDRepo 2023-12-12 00:01:58 +06:00
f6ef5c61c5 start 0.20.20 2023-12-11 23:54:25 +06:00
c18fee8107 update gradle publishing scripts
one more potential ix of publishing scripts

drop new configs of publish scripts

revert publish scripts and update gradle properties
2023-12-09 20:38:44 +06:00
d9df7a4384
Revert gradle wrapper version 2023-12-09 01:38:45 +06:00
87c2230e8e revert xmx 2500 2023-12-08 23:42:26 +06:00
da7eb6de0a change xmx 2023-12-08 21:34:05 +06:00
ed3815118f update jvmargs of gradle 2023-12-08 19:34:47 +06:00
be726f42bd
Merge pull request #356 from InsanusMokrassar/0.20.19
update publishing scripts
2023-12-08 16:06:22 +06:00
a91006132f update publishing scripts 2023-12-08 16:06:07 +06:00
9a9f741a0b
Merge pull request #355 from InsanusMokrassar/0.20.19
0.20.19
2023-12-08 16:03:05 +06:00
5028f130e9 update dependencies 2023-12-08 15:58:16 +06:00
77fa019651 start 0.20.19 2023-12-08 15:56:44 +06:00
9715da9384
Merge pull request #353 from InsanusMokrassar/0.20.18
0.20.18
2023-12-04 16:13:33 +06:00
99 changed files with 4759 additions and 3066 deletions

3
.gitignore vendored
View File

@ -14,3 +14,6 @@ local.properties
kotlin-js-store
publishing.sh
local.*
local/

View File

@ -1,5 +1,237 @@
# Changelog
## 0.20.52
* `Coroutines`:
* Small rework of weak jobs: add `WeakScope` factory, rename old weal extensions and add kdocs
## 0.20.51
* `Versions`:
* `Android Fragment`: `1.7.0` -> `1.7.1`
* `Pagination`:
* Add `Pagination.nextPageIfTrue` and `Pagination.thisPageIftrue` extensions to get the page according to condition
pass status
* Add `PaginationResult.nextPageIfNotEmptyOrLastPage` and `PaginationResult.thisPageIfNotEmptyOrLastPage`
* Change all `doForAll` and `getAll` extensions fo pagination to work basing on `nextPageIfNotEmptyOrLastPage` and
`thisPageIfNotEmptyOrLastPage`
## 0.20.50
* `Versions`:
* `Coroutines`: `1.8.0` -> `1.8.1`
* `KSLog`: `1.3.3` -> `1.3.4`
* `Exposed`: `0.50.0` -> `0.50.1`
* `Ktor`: `2.3.10` -> `2.3.11`
* A lot of inline functions became common functions due to inline with only noinline callbacks in arguments leads to
low performance
* `Coroutines`:
* `SmartMutex`, `SmartSemaphore` and `SmartRWLocker` as their user changed their state flow to `SpecialMutableStateFlow`
## 0.20.49
* `Repos`:
* `Common`:
* All `Repo`s get `diff` extensions
* `KeyValueRepo` and `KeyValuesRepo` get `applyDiff` extension
* Add new extensions `on*` flows for crud repos
## 0.20.48
* `Versions`:
* `Android Core KTX`: `1.13.0` -> `1.13.1`
* `AndroidX Fragment`: `1.6.2` -> `1.7.0`
## 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`:

View File

@ -7,9 +7,6 @@ import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ContentAlpha
import androidx.compose.material.IconButton
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
@ -75,7 +72,7 @@ fun NumberPicker(
}
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
val animatedStateValue = animatedStateValue(animatedOffset.value)
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
val inputFieldShown = if (allowUseManualInput) {
remember { mutableStateOf(false) }

View File

@ -5,7 +5,6 @@ import androidx.compose.animation.core.exponentialDecay
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
@ -62,9 +61,7 @@ fun <T> SetPicker(
(index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
}
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
val boxOffset = (indexAnimatedOffset * halvedNumbersColumnHeightPx) - coercedAnimatedOffset
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
val scrollState = rememberScrollState()
val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
Column(
modifier = modifier

View File

@ -27,7 +27,7 @@ allprojects {
mavenCentral()
google()
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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,11 @@ apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api libs.klock
}
}
jvmMain {
dependencies {
api project(":micro_utils.coroutines")
@ -15,23 +20,11 @@ kotlin {
}
androidMain {
dependencies {
api project(":micro_utils.coroutines")
api libs.android.fragment
}
dependsOn jvmMain
}
linuxX64Main {
dependencies {
api libs.okio
}
}
mingwX64Main {
dependencies {
api libs.okio
}
}
linuxArm64Main {
nativeMain {
dependencies {
api libs.okio
}

View File

@ -1 +0,0 @@
<manifest/>

View File

@ -0,0 +1,25 @@
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
/**
* Serializes [DateTime] as its raw [DateTime.unixMillis] and deserializes in the same way
*/
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)
}
}

View File

@ -27,7 +27,7 @@ private inline fun <T> getObject(
* @see calculateDiff
*/
@Serializable
data class Diff<T> internal constructor(
data class Diff<T> @Warning(warning) constructor(
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
/**
* Old-New values pairs
@ -36,6 +36,10 @@ data class Diff<T> internal constructor(
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
) {
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())

View File

@ -1,36 +0,0 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@ -1,25 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.*
import platform.posix.snprintf
import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@ -1,36 +0,0 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@ -1,25 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.*
import platform.posix.snprintf
import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@ -22,7 +22,6 @@ kotlin {
dependencies {
api libs.kt.coroutines.android
}
dependsOn(jvmMain)
}
}
}

View File

@ -1 +0,0 @@
<manifest/>

View File

@ -1,46 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow].
* Based on [SpecialMutableStateFlow]
*/
@Deprecated("Will be removed soon")
class FlowState<T>(
initial: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : MutableState<T>,
SpecialMutableStateFlow<T>(initial, internalScope) {
private var internalValue: T = initial
override var value: T
get() = internalValue
set(value) {
internalValue = value
tryEmit(value)
}
override fun onChangeWithoutSync(value: T) {
internalValue = value
super.onChangeWithoutSync(value)
}
override fun component1(): T = value
override fun component2(): (T) -> Unit = { tryEmit(it) }
override fun tryEmit(value: T): Boolean {
internalValue = value
return super.tryEmit(value)
}
override suspend fun emit(value: T) {
internalValue = value
super.emit(value)
}
}
//fun <T> MutableState<T>.asFlowState(scope: CoroutineScope = CoroutineScope(Dispatchers.Main)) = FlowState(this, scope)

View File

@ -0,0 +1,24 @@
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.test.*
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
class FlowStateTests {
@OptIn(ExperimentalTestApi::class)
@Test
@TestOnly
fun simpleTest() = runComposeUiTest {
val flowState = SpecialMutableStateFlow(0)
setContent {
Button({ flowState.value++ }) { Text("Click") }
Text(flowState.collectAsState().value.toString())
}
onNodeWithText(0.toString()).assertExists()
onNodeWithText("Click").performClick()
onNodeWithText(1.toString()).assertExists()
}
}

View File

@ -16,8 +16,8 @@ class DoWithFirstBuilder<T>(
operator fun plus(block: suspend CoroutineScope.() -> T) {
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
}
inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block)
inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block)
fun add(block: suspend CoroutineScope.() -> T) = plus(block)
fun include(block: suspend CoroutineScope.() -> T) = plus(block)
fun build() = deferreds.toList()
}

View File

@ -85,32 +85,32 @@ fun <T, M> Flow<T>.subscribeAsync(
return job
}
inline fun <T, M> Flow<T>.subscribeSafelyAsync(
fun <T, M> Flow<T>.subscribeSafelyAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend (T) -> Unit
markerFactory: suspend (T) -> M,
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safely(onException) {
block(it)
}
}
inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend (T) -> Unit
markerFactory: suspend (T) -> M,
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions(onException) {
block(it)
}
}
inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline block: suspend (T) -> Unit
markerFactory: suspend (T) -> M,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions({ /* do nothing */}) {
block(it)

View File

@ -51,7 +51,7 @@ class ContextSafelyExceptionHandler(
* @see ContextSafelyExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
/**
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
@ -96,9 +96,9 @@ suspend fun <T> safelyWithContextExceptionHandler(
* @see safelyWithoutExceptions
* @see safelyWithContextExceptionHandler
*/
suspend inline fun <T> safely(
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
suspend fun <T> safely(
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T
): T {
return try {
supervisorScope(block)
@ -108,26 +108,26 @@ suspend inline fun <T> safely(
}
}
suspend inline fun <T> runCatchingSafely(
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
suspend fun <T> runCatchingSafely(
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T
): Result<T> = runCatching {
safely(onException, block)
}
suspend inline fun <T, R> T.runCatchingSafely(
noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
noinline block: suspend T.() -> R
suspend fun <T, R> T.runCatchingSafely(
onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
block: suspend T.() -> R
): Result<R> = runCatching {
safely(onException) { block() }
}
suspend inline fun <T> safelyWithResult(
noinline block: suspend CoroutineScope.() -> T
suspend fun <T> safelyWithResult(
block: suspend CoroutineScope.() -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
suspend inline fun <T, R> T.safelyWithResult(
noinline block: suspend T.() -> R
suspend fun <T, R> T.safelyWithResult(
block: suspend T.() -> R
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
/**
@ -147,21 +147,21 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
* result from exception (instead of throwing it, by default always returns null)
*/
suspend inline fun <T> safelyWithoutExceptions(
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
suspend fun <T> safelyWithoutExceptions(
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend CoroutineScope.() -> T
): T? = safely(onException, block)
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
suspend fun <T> runCatchingSafelyWithoutExceptions(
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend CoroutineScope.() -> T
): Result<T?> = runCatching {
safelyWithoutExceptions(onException, block)
}
inline fun CoroutineScope(
fun CoroutineScope(
context: CoroutineContext,
noinline defaultExceptionsHandler: ExceptionHandler<Unit>
defaultExceptionsHandler: ExceptionHandler<Unit>
) = CoroutineScope(
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
)

View File

@ -4,38 +4,38 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
inline fun CoroutineScope.launchSafely(
fun CoroutineScope.launchSafely(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> Unit
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> Unit
) = launch(context, start) {
safely(onException, block)
}
inline fun CoroutineScope.launchSafelyWithoutExceptions(
fun CoroutineScope.launchSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> Unit
onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend CoroutineScope.() -> Unit
) = launch(context, start) {
safelyWithoutExceptions(onException, block)
}
inline fun <T> CoroutineScope.asyncSafely(
fun <T> CoroutineScope.asyncSafely(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T
) = async(context, start) {
safely(onException, block)
}
inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend CoroutineScope.() -> T
) = async(context, start) {
safelyWithoutExceptions(onException, block)
}

View File

@ -44,7 +44,7 @@ sealed interface SmartMutex {
* @param locked Preset state of [isLocked] and its internal [_lockStateFlow]
*/
class Mutable(locked: Boolean = false) : SmartMutex {
private val _lockStateFlow = MutableStateFlow<Boolean>(locked)
private val _lockStateFlow = SpecialMutableStateFlow<Boolean>(locked)
override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow()
private val internalChangesMutex = Mutex()

View File

@ -45,7 +45,7 @@ sealed interface SmartSemaphore {
* @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow]
*/
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
private val _freePermitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits)
private val _freePermitsStateFlow = SpecialMutableStateFlow<Int>(permits - acquiredPermits)
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
private val internalChangesMutex = Mutex(false)

View File

@ -11,76 +11,60 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.internal.SynchronizedObject
import kotlinx.coroutines.internal.synchronized
import kotlin.coroutines.CoroutineContext
/**
* Works like [StateFlow], but guarantee that latest value update will always be delivered to
* each active subscriber
*/
open class SpecialMutableStateFlow<T>(
initialValue: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
initialValue: T
) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> {
@OptIn(InternalCoroutinesApi::class)
private val syncObject = SynchronizedObject()
protected val internalSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 2,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
protected val publicSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
protected val sharingFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 1,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
protected var _value: T = initialValue
override var value: T
get() = _value
@OptIn(InternalCoroutinesApi::class)
override var value: T = initialValue
set(value) {
doOnChangeAction(value)
synchronized(syncObject) {
if (field != value) {
field = value
sharingFlow.tryEmit(value)
}
}
}
protected val job = internalSharedFlow.subscribe(internalScope) {
doOnChangeAction(it)
}
override val replayCache: List<T>
get() = publicSharedFlow.replayCache
get() = sharingFlow.replayCache
override val subscriptionCount: StateFlow<Int>
get() = publicSharedFlow.subscriptionCount
get() = sharingFlow.subscriptionCount
init {
sharingFlow.tryEmit(initialValue)
}
@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)
}
if (expect == value) {
value = update
}
return expect == value
}
@ExperimentalCoroutinesApi
override fun resetReplayCache() = publicSharedFlow.resetReplayCache()
override fun resetReplayCache() = sharingFlow.resetReplayCache()
override fun tryEmit(value: T): Boolean {
return internalSharedFlow.tryEmit(value)
return compareAndSet(this.value, value)
}
override suspend fun emit(value: T) {
internalSharedFlow.emit(value)
compareAndSet(this.value, value)
}
override suspend fun collect(collector: FlowCollector<T>) = publicSharedFlow.collect(collector)
override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector)
}

View File

@ -4,28 +4,71 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
private fun CoroutineScope.createWeakSubScope() = CoroutineScope(coroutineContext.minusKey(Job)).also { newScope ->
coroutineContext.job.invokeOnCompletion { newScope.cancel() }
/**
* Created [CoroutineScope] which will [launch] listening of [context] job completing and drop itself. Current weak
* scope **will not** be attached to [context] directly. So, this [CoroutineScope] will not prevent parent one from
* cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status
* of parent [Job]
*/
fun WeakScope(
context: CoroutineContext
) = CoroutineScope(context.minusKey(Job) + Job()).also { newScope ->
newScope.launch {
context.job.join()
newScope.cancel()
}
}
fun CoroutineScope.weakLaunch(
/**
* Created [CoroutineScope] which will [launch] listening of [scope] [CoroutineContext] job completing and drop itself. Current weak
* scope **will not** be attached to [scope] [CoroutineContext] directly. So, this [CoroutineScope] will not prevent parent one from
* cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status
* of parent [Job]
*/
fun WeakScope(
scope: CoroutineScope
) = WeakScope(scope.coroutineContext)
/**
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
* will be used to [launch] [Job]
*/
fun CoroutineScope.launchWeak(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val scope = createWeakSubScope()
val scope = WeakScope(this)
val job = scope.launch(context, start, block)
job.invokeOnCompletion { scope.cancel() }
return job
}
fun <T> CoroutineScope.weakAsync(
/**
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
* will be used to create [async] [Deferred]
*/
fun <T> CoroutineScope.asyncWeak(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val scope = createWeakSubScope()
val scope = WeakScope(this)
val deferred = scope.async(context, start, block)
deferred.invokeOnCompletion { scope.cancel() }
return deferred
}
@Deprecated("Renamed", ReplaceWith("launchWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.launchWeak"))
fun CoroutineScope.weakLaunch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = launchWeak(context, start, block)
@Deprecated("Renamed", ReplaceWith("asyncWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.asyncWeak"))
fun <T> CoroutineScope.weakAsync(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> = asyncWeak(context, start, block)

View File

@ -0,0 +1,33 @@
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.asDeferred
import dev.inmo.micro_utils.coroutines.subscribe
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SpecialMutableStateFlowTests {
@Test
fun simpleTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0)
specialMutableStateFlow.value = 1
specialMutableStateFlow.first { it == 1 }
assertEquals(1, specialMutableStateFlow.value)
}
@Test
fun specialTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0)
lateinit var subscriberJob: Job
subscriberJob = specialMutableStateFlow.subscribe(this) {
when (it) {
1 -> specialMutableStateFlow.value = 2
2 -> subscriberJob.cancel()
}
}
specialMutableStateFlow.value = 1
subscriberJob.join()
assertEquals(2, specialMutableStateFlow.value)
}
}

View File

@ -0,0 +1,64 @@
import dev.inmo.micro_utils.coroutines.asyncWeak
import dev.inmo.micro_utils.coroutines.launchWeak
import kotlinx.coroutines.*
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertTrue
class WeakJobTests {
@Test
fun testWeakJob() = runTest {
var commonJobDone = false
var weakJobStarted = false
try {
coroutineScope {
launch {
delay(1000)
commonJobDone = true
}
asyncWeak {
weakJobStarted = true
delay(100500L)
error("This must never happen")
}
}.await()
} catch (error: Throwable) {
assertTrue(error is CancellationException)
assertTrue(commonJobDone)
assertTrue(weakJobStarted)
return@runTest
}
error("Cancellation exception has not been thrown")
}
@Test
fun testThatWeakJobsWorksCorrectly() = runTest {
val scope = CoroutineScope(Dispatchers.Default)
lateinit var weakLaunchJob: Job
lateinit var weakAsyncJob: Job
val completeDeferred = Job()
coroutineScope {
weakLaunchJob = launchWeak {
while (isActive) {
delay(100L)
}
}
weakAsyncJob = asyncWeak {
while (isActive) {
delay(100L)
}
}
coroutineContext.job.invokeOnCompletion {
scope.launch {
delay(1000L)
completeDeferred.complete()
}
}
launch { delay(1000L); cancel() }
}
completeDeferred.join()
assertTrue(!weakLaunchJob.isActive)
assertTrue(!weakAsyncJob.isActive)
}
}

View File

@ -1,40 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import org.junit.Test
class WeakJob {
@Test
fun `test that weak jobs works correctly`() {
val scope = CoroutineScope(Dispatchers.Default)
lateinit var weakLaunchJob: Job
lateinit var weakAsyncJob: Job
scope.launchSynchronously {
val completeDeferred = Job()
coroutineScope {
weakLaunchJob = weakLaunch {
while (isActive) {
delay(100L)
}
}
weakAsyncJob = weakAsync {
while (isActive) {
delay(100L)
}
}
coroutineContext.job.invokeOnCompletion {
scope.launch {
delay(1000L)
completeDeferred.complete()
}
}
launch { delay(1000L); cancel() }
}
completeDeferred.join()
}
assert(!weakLaunchJob.isActive)
assert(!weakAsyncJob.isActive)
}
}

View File

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

View File

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

View File

@ -1,41 +1,41 @@
[versions]
kt = "1.9.21"
kt-serialization = "1.6.2"
kt-coroutines = "1.7.3"
kt = "1.9.23"
kt-serialization = "1.6.3"
kt-coroutines = "1.8.1"
kslog = "1.3.1"
kslog = "1.3.4"
jb-compose = "1.5.11"
jb-exposed = "0.45.0"
jb-dokka = "1.9.10"
jb-compose = "1.6.2"
jb-exposed = "0.50.1"
jb-dokka = "1.9.20"
korlibs = "4.0.10"
uuid = "0.8.2"
korlibs = "5.4.0"
uuid = "0.8.4"
ktor = "2.3.6"
ktor = "2.3.11"
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"
kotlin-poet = "1.15.2"
ksp = "1.9.23-1.0.20"
kotlin-poet = "1.16.0"
versions = "0.50.0"
versions = "0.51.0"
android-gradle = "8.2.0"
dexcount = "4.0.0"
android-coreKtx = "1.12.0"
android-coreKtx = "1.13.1"
android-recyclerView = "1.3.2"
android-appCompat = "1.6.1"
android-fragment = "1.6.2"
android-fragment = "1.7.1"
android-espresso = "3.5.1"
android-test = "1.1.5"
android-compose-material3 = "1.1.2"
android-compose-material3 = "1.2.1"
android-props-minSdk = "21"
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" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" }
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" }
klock = { module = "com.soywiz.korge:korlibs-time", version.ref = "korlibs" }
krypto = { module = "com.soywiz.korge:korlibs-crypto", version.ref = "korlibs" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

View File

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

View File

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

View File

@ -15,9 +15,6 @@ kotlin {
api libs.ktor.client
}
}
androidMain {
dependsOn jvmMain
}
linuxX64Main {
dependencies {

View File

@ -12,13 +12,9 @@ kotlin {
dependencies {
api internalProject("micro_utils.common")
api libs.kt.serialization.cbor
api libs.klock
api libs.uuid
api libs.ktor.io
}
}
androidMain {
dependsOn jvmMain
}
}
}

View File

@ -5,11 +5,3 @@ plugins {
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin {
sourceSets {
androidMain {
dependsOn jvmMain
}
}
}

View File

@ -12,9 +12,12 @@ private val json = Json {
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 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)"
@Serializable
@ -78,14 +81,12 @@ private fun printLanguageCodeAndTags(
indents: String = " "
): String = if (tag.subtags.isEmpty()) {
"""${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 {
"""
${indents}${baseClassSerializerAnnotationName}
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
${indents} override val code: String = "${tag.tag}"
${indents} override val withoutDialect: String
${indents} get() = code
${indents} override val code: String = "${tag.tag}"${parent ?.let { parent -> "\n${indents} override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""}
${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
/**
@ -106,20 +107,27 @@ import kotlinx.serialization.Serializable
${baseClassSerializerAnnotationName}
sealed class $baseClassName {
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 = " ") } }
$baseClassSerializerAnnotationName
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
}
@Deprecated("Renamed", ReplaceWith("$baseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName"))
typealias $oldBaseClassName = $baseClassName
""".trimIndent()
fun createStringConverterCode(tags: List<Tag>): String {
fun createStringConverterCode(tags: List<Tag>, prePackage: String): String {
fun createDeserializeVariantForTag(
tag: Tag,
pretitle: String = baseClassName,
@ -128,19 +136,70 @@ fun createStringConverterCode(tags: List<Tag>): String {
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 """fun String.as$baseClassName(): $baseClassName {
return when (this) {
${tags.joinToString("\n") { createDeserializeVariantForTag(it) }}
else -> $baseClassName.${unknownBaseClassName}(this)
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 """val knownLanguageCodesMap: Map<String, $baseClassName> by lazy {
mapOf(
${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = " ") }}
)
}
val knownLanguageCodesMapByLowerCasedKeys: Map<String, $baseClassName> by lazy {
mapOf(
${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = " ", codeSuffix = ".lowercase()") }}
)
}
val knownLanguageCodes: List<$baseClassName> by lazy {
knownLanguageCodesMap.values.toList()
}
fun String.as$baseClassName(): $baseClassName {
return knownLanguageCodesMap[this] ?: $baseClassName.${unknownBaseClassName}(this)
}
fun String.as${baseClassName}CaseInsensitive(): $baseClassName {
return knownLanguageCodesMapByLowerCasedKeys[this.lowercase()] ?: $baseClassName.${unknownBaseClassName}(this)
}
@Deprecated("Renamed", ReplaceWith("this.as$baseClassName()", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}as$baseClassName"))
fun String.as$oldBaseClassName(): $baseClassName = as$baseClassName()
fun convertTo$baseClassName(code: String) = code.as$baseClassName()
fun convertTo${baseClassName}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}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
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
@ -153,16 +212,20 @@ object $baseClassSerializerName : KSerializer<$baseClassName> {
return $baseClassName(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLanguageCode) {
override fun serialize(encoder: Encoder, value: $baseClassName) {
encoder.encodeString(value.code)
}
}
@Deprecated("Renamed", ReplaceWith("$baseClassSerializerName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassSerializerName"))
typealias $oldBaseClassSerializerName = $baseClassSerializerName
"""
}
suspend fun main(vararg args: String) {
val outputFolder = args.firstOrNull() ?.let { File(it) }
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 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 {
delete()
createNewFile()
writeText(buildKtFileContent(tags))
writeText(targetPackagePrefix + buildKtFileContent(tags, targetPackage ?: ""))
}
File(outputFolder, "StringToLanguageCodes.kt").apply {
delete()
createNewFile()
writeText(createStringConverterCode(tags))
writeText(targetPackagePrefix + createStringConverterCode(tags, targetPackage ?: ""))
}
File(outputFolder, "$baseClassSerializerName.kt").apply {
delete()
createNewFile()
writeText(createSerializerCode(tags))
writeText(targetPackagePrefix + createSerializerCode(tags, targetPackage ?: ""))
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -60,6 +60,12 @@ kotlin {
implementation kotlin('test-junit')
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}

View File

@ -45,6 +45,13 @@ kotlin {
implementation kotlin('test-junit')
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}

View File

@ -55,6 +55,10 @@ kotlin {
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}

View File

@ -28,8 +28,8 @@ kotlin {
commonMain {
dependencies {
implementation kotlin('stdlib')
api libs.kt.serialization
implementation compose.runtime
api libs.kt.serialization
}
}
commonTest {
@ -47,6 +47,7 @@ kotlin {
jvmTest {
dependencies {
implementation kotlin('test-junit')
implementation compose.uiTest
}
}
jsMain {
@ -61,14 +62,20 @@ kotlin {
}
}
androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
implementation compose.uiTest
}
}
androidInstrumentedTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
androidMain.dependsOn jvmMain
}
}

View File

@ -20,7 +20,13 @@ data class PaginationResult<T>(
* Amount of pages for current pagination
*/
@EncodeDefault
val pagesNumber: Int = ceil(objectsNumber / size.toFloat()).toInt()
@SerialName("pagesNumber")
val pagesNumberLong: Long = ceil(objectsNumber / size.toFloat()).toLong()
/**
* Amount of pages for current pagination
*/
@Transient
val pagesNumber: Int = pagesNumberLong.toInt()
constructor(
page: Int,
@ -35,31 +41,58 @@ data class PaginationResult<T>(
)
}
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L)
val PaginationResult<*>.lastPageLong
get() = pagesNumberLong - 1
val PaginationResult<*>.lastPage
get() = lastPageLong.toInt()
val PaginationResult<*>.isLastPage
get() = page.toLong() == lastPageLong
fun <T> emptyPaginationResult(
basePagination: Pagination
basePagination: Pagination,
objectsNumber: Number
) = PaginationResult<T>(
basePagination.page,
basePagination.size,
emptyList(),
0L
objectsNumber.toLong()
)
fun <T> emptyPaginationResult(
basePagination: Pagination,
) = emptyPaginationResult<T>(basePagination, 0)
fun <T> emptyPaginationResult() = emptyPaginationResult<T>(FirstPagePagination(0))
/**
* @return New [PaginationResult] with [data] without checking of data sizes equality
*/
inline fun <I, O> PaginationResult<I>.changeResultsUnchecked(
block: PaginationResult<I>.() -> List<O>
): PaginationResult<O> = PaginationResult(page, size, block(), objectsNumber)
/**
* @return New [PaginationResult] with [data] without checking of data sizes equality
*/
fun <I, O> PaginationResult<I>.changeResultsUnchecked(
data: List<O>
): PaginationResult<O> = PaginationResult(page, size, data, objectsNumber)
): PaginationResult<O> = changeResultsUnchecked { data }
/**
* @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality
*/
inline fun <I, O> PaginationResult<I>.changeResults(
block: PaginationResult<I>.() -> List<O>
): PaginationResult<O> {
val data = block()
require(data.size == results.size)
return changeResultsUnchecked(data)
}
/**
* @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality
*/
fun <I, O> PaginationResult<I>.changeResults(
data: List<O>
): PaginationResult<O> {
require(data.size == results.size)
return changeResultsUnchecked(data)
}
): PaginationResult<O> = changeResults { data }
fun <T> List<T>.createPaginationResult(
pagination: Pagination,

View File

@ -10,8 +10,7 @@ inline fun doWithPagination(
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun PaginationResult<*>.nextPageIfNotEmpty() = if (results.isNotEmpty()) {
inline fun <T, PR: PaginationResult<T>> PR.nextPageIfTrue(condition: PR.() -> Boolean) = if (condition()) {
SimplePagination(
page + 1,
size
@ -20,12 +19,28 @@ inline fun PaginationResult<*>.nextPageIfNotEmpty() = if (results.isNotEmpty())
null
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? = if (results.isNotEmpty()) {
inline fun <T, PR: PaginationResult<T>> PR.thisPageIfTrue(condition: PR.() -> Boolean): PR? = if (condition()) {
this
} else {
null
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()
fun PaginationResult<*>.nextPageIfNotEmpty() = nextPageIfTrue { results.isNotEmpty() }
fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? = thisPageIfTrue { results.isNotEmpty() }
fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()
fun PaginationResult<*>.nextPageIfNotEmptyOrLastPage() = nextPageIfTrue { results.isNotEmpty() && !this.isLastPage }
fun <T> PaginationResult<T>.thisPageIfNotEmptyOrLastPage(): PaginationResult<T>? = thisPageIfTrue { results.isNotEmpty() && !this.isLastPage }
fun <T> PaginationResult<T>.currentPageIfNotEmptyOrLastPage() = thisPageIfNotEmptyOrLastPage()
fun PaginationResult<*>.nextPageIfNotLastPage() = nextPageIfTrue { !this.isLastPage }
fun <T> PaginationResult<T>.thisPageIfNotLastPage(): PaginationResult<T>? = thisPageIfTrue { !this.isLastPage }
fun <T> PaginationResult<T>.currentPageIfNotLastPage() = thisPageIfNotLastPage()

View File

@ -18,7 +18,7 @@ inline fun <T> doForAllWithNextPaging(
) {
doForAll(
initialPagination,
{ it.nextPageIfNotEmpty() },
{ it.nextPageIfNotEmptyOrLastPage() },
block
)
}
@ -29,7 +29,7 @@ inline fun <T> doAllWithCurrentPaging(
) {
doForAll(
initialPagination,
{ it.currentPageIfNotEmpty() },
{ it.nextPageIfNotEmptyOrLastPage() },
block
)
}

View File

@ -31,7 +31,7 @@ inline fun <T> getAllWithNextPaging(
block: (Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ it.nextPageIfNotEmpty() },
{ it.nextPageIfNotEmptyOrLastPage() },
block
)
@ -48,7 +48,7 @@ inline fun <T> getAllWithCurrentPaging(
block: (Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ it.currentPageIfNotEmpty() },
{ it.thisPageIfNotEmptyOrLastPage() },
block
)

View File

@ -21,33 +21,25 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
}
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
val firstIndex = maxOf(with.firstIndex, 0)
val lastIndex = minOf(with.lastIndexExclusive, size)
if (firstIndex > lastIndex) {
return emptyPaginationResult()
if (with.firstIndex >= size || with.lastIndex < 0) {
return emptyPaginationResult(with, size.toLong())
}
return subList(firstIndex, lastIndex).createPaginationResult(
return asSequence().drop(with.firstIndex).take(with.size).toList().createPaginationResult(
with,
size.toLong()
)
}
fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
val actualPagination = with.optionallyReverse(
size,
reversed
)
val firstIndex = maxOf(actualPagination.firstIndex, 0)
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
if (firstIndex > lastIndex) {
return emptyPaginationResult()
return if (reversed) {
val actualPagination = with.optionallyReverse(
size,
reversed
)
paginate(actualPagination).changeResultsUnchecked { results.reversed() }
} else {
paginate(with)
}
return subList(firstIndex, lastIndex).optionallyReverse(reversed).createPaginationResult(
with,
size.toLong()
)
}
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {

View File

@ -0,0 +1,72 @@
package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class PaginationPaging {
@Test
fun testPaginateOnList() {
val list = (0 until 7).toList()
val startPagination = FirstPagePagination(2)
var lastPageHappened = false
doForAllWithNextPaging(startPagination) {
val result = list.paginate(it)
if (result.isLastPage) {
lastPageHappened = true
assertTrue(result.results.size == 1)
}
val testSublist = list.subList(it.firstIndex, minOf(it.lastIndexExclusive, list.size))
assertEquals(result.results, testSublist)
result
}
assertTrue(lastPageHappened)
}
@Test
fun testEmptyPaginateOnList() {
val list = listOf<Int>()
val startPagination = FirstPagePagination(2)
var paginationHappend = false
doForAllWithNextPaging(startPagination) {
val resultPagination = list.paginate(it)
assertEquals(resultPagination, emptyPaginationResult(it, list.size))
assertFalse(paginationHappend)
paginationHappend = true
resultPagination
}
assertTrue(paginationHappend)
}
@Test
fun testRightOutPaginateOnList() {
val list = (0 until 7).toList()
val startPagination = SimplePagination(page = 4, size = 2)
var paginationHappend = false
doForAllWithNextPaging(startPagination) {
val resultPagination = list.paginate(it)
assertEquals(resultPagination, emptyPaginationResult(it, list.size))
assertFalse(paginationHappend)
paginationHappend = true
resultPagination
}
assertTrue(paginationHappend)
}
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,11 @@
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()
}
typealias CacheRepo = InvalidatableRepo

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,8 +8,8 @@ import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
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.pagination.getAll
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
@ -41,10 +41,7 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
actualize = { locker.withWriteLock { actualize(it) } }
)
protected open suspend fun actualizeAll() {
locker.withWriteLock {
kvCache.clear()
kvCache.set(parentRepo.getAll())
}
kvCache.actualizeAll(parentRepo, locker)
}
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(
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
{ getAll() },
{ kvCache.actualizeAll(clear = true) { it } }
{ kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } }
)
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 invalidate() {
locker.withWriteLock {
kvCache.actualizeAll(parentRepo)
}
kvCache.actualizeAll(parentRepo, locker)
}
override suspend fun clear() {

View File

@ -8,6 +8,7 @@ import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.*
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -41,15 +42,13 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
)
protected open suspend fun actualizeKey(k: Key) {
locker.withWriteLock {
kvCache.set(k, parentRepo.getAll(k))
kvCache.actualizeAll(locker = locker, clearMode = ActualizeAllClearMode.Never) {
mapOf(k to parentRepo.getAll(k))
}
}
protected open suspend fun actualizeAll() {
locker.withWriteLock {
kvCache.actualizeAll(parentRepo)
}
kvCache.actualizeAll(parentRepo, locker = locker)
}
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() {
locker.withWriteLock {
kvCache.actualizeAll(parentRepo)
}
kvCache.actualizeAll(parentRepo, locker = locker)
}
override suspend fun set(toSet: Map<Key, List<Value>>) {
super<KeyValuesRepo>.set(toSet)
}
override suspend fun removeWithValue(v: Value) {

View File

@ -1,43 +1,169 @@
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 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>
) {
set(
getAll().also {
if (clear) {
clear()
}
}
)
clear()
val newData = getAll()
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()
}
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(
repo: ReadKeyValueRepo<K, V>,
clear: Boolean = true,
locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
getAll: () -> Map<K, V>
) {
actualizeAll(clear) {
repo.getAll()
when (clearMode) {
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(
repo: ReadKeyValuesRepo<K, V>,
clear: Boolean = true,
parentRepo: ReadKeyValuesRepo<K, V>,
locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
) {
actualizeAll(clear) {
repo.getAll()
actualizeAll(locker, clearMode) {
parentRepo.getAll()
}
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
repo: ReadCRUDRepo<V, K>,
clear: Boolean = true,
parentRepo: ReadCRUDRepo<V, K>,
locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
) {
actualizeAll(clear) {
repo.getAll()
actualizeAll(locker, clearMode) {
parentRepo.getAll()
}
}

View File

@ -28,7 +28,6 @@ kotlin {
api internalProject("micro_utils.common")
api internalProject("micro_utils.coroutines")
}
dependsOn jvmMain
}
}
}

View File

@ -1,5 +1,6 @@
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.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
@ -130,6 +131,14 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
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>

View File

@ -42,6 +42,22 @@ interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
}
typealias WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType>
/**
* Just mirroring [WriteCRUDRepo.newObjectsFlow] to be same as in KV repos
*/
val <ObjectType> WriteCRUDRepo<ObjectType, *, *>.onNewObjects: Flow<ObjectType>
get() = newObjectsFlow
/**
* Just mirroring [WriteCRUDRepo.updatedObjectsFlow] to be same as in KV repos
*/
val <ObjectType> WriteCRUDRepo<ObjectType, *, *>.onUpdatedObjects: Flow<ObjectType>
get() = updatedObjectsFlow
/**
* Just mirroring [WriteCRUDRepo.deletedObjectsIdsFlow] to be same as in KV repos
*/
val <IdType> WriteCRUDRepo<*, IdType, *>.onDeletedObjectsIds: Flow<IdType>
get() = deletedObjectsIdsFlow
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create(
vararg values: InputValueType
): List<ObjectType> = create(values.toList())

View File

@ -0,0 +1,20 @@
package dev.inmo.micro_utils.repos.diff
import dev.inmo.micro_utils.common.MapDiff
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.repos.CRUDRepo
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.unset
suspend fun <Id, Registered> ReadCRUDRepo<Registered, Id>.diff(other: Map<Id, Registered>): MapDiff<Id, Registered> {
return getAll().diff(other)
}
suspend fun <Id, Registered> Map<Id, Registered>.diff(other: ReadCRUDRepo<Registered, Id>): MapDiff<Id, Registered> {
return diff(other.getAll())
}
suspend fun <Id, Registered> MutableMap<Id, Registered>.applyDiff(other: ReadCRUDRepo<Registered, Id>) {
applyDiff(diff(other))
}

View File

@ -0,0 +1,33 @@
package dev.inmo.micro_utils.repos.diff
import dev.inmo.micro_utils.common.MapDiff
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.unset
suspend fun <Id, Registered> ReadKeyValueRepo<Id, Registered>.diff(other: Map<Id, Registered>): MapDiff<Id, Registered> {
return getAll().diff(other)
}
suspend fun <Id, Registered> Map<Id, Registered>.diff(other: ReadKeyValueRepo<Id, Registered>): MapDiff<Id, Registered> {
return diff(other.getAll())
}
suspend fun <Id, Registered> KeyValueRepo<Id, Registered>.applyDiff(diff: MapDiff<Id, Registered>) {
unset(diff.removed.map { it.key })
set(
diff.changed.map { (k, oldNew) ->
k to oldNew.second
}.toMap() + diff.added
)
}
suspend fun <Id, Registered> KeyValueRepo<Id, Registered>.applyDiff(other: Map<Id, Registered>) {
applyDiff(diff(other))
}
suspend fun <Id, Registered> MutableMap<Id, Registered>.applyDiff(other: ReadKeyValueRepo<Id, Registered>) {
applyDiff(diff(other))
}

View File

@ -0,0 +1,33 @@
package dev.inmo.micro_utils.repos.diff
import dev.inmo.micro_utils.common.MapDiff
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.repos.*
suspend fun <Id, Registered> ReadKeyValuesRepo<Id, Registered>.diff(other: Map<Id, List<Registered>>): MapDiff<Id, List<Registered>> {
return getAll().diff(other)
}
suspend fun <Id, Registered> Map<Id, List<Registered>>.diff(other: ReadKeyValuesRepo<Id, Registered>): MapDiff<Id, List<Registered>> {
return diff(other.getAll())
}
suspend fun <Id, Registered> KeyValuesRepo<Id, Registered>.applyDiff(diff: MapDiff<Id, List<Registered>>) {
diff.removed.forEach {
clear(it.key)
}
set(
diff.changed.map { (k, oldNew) ->
k to oldNew.second
}.toMap() + diff.added
)
}
suspend fun <Id, Registered> KeyValuesRepo<Id, Registered>.applyDiff(other: Map<Id, List<Registered>>) {
applyDiff(diff(other))
}
suspend fun <Id, Registered> MutableMap<Id, List<Registered>>.applyDiff(other: ReadKeyValuesRepo<Id, Registered>) {
applyDiff(diff(other))
}

View File

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

View File

@ -35,14 +35,14 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
}
override suspend fun getById(id: IdType): ObjectType? {
return transaction(db = database) {
select {
selectAll().where {
selectById(id)
}.limit(1).firstOrNull() ?.asObject
}
}
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) {

View File

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

View File

@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.KeyValueRepo
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.*
@ -8,13 +9,15 @@ import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedKeyValueRepo<Key, Value>(
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>(
database,
tableName
) {
protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
protected val _onValueRemoved = MutableSharedFlow<Key>()
protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
protected val _onValueRemoved = MutableSharedFlow<Key>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
@ -65,7 +68,7 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
override suspend fun unsetWithValues(toUnset: List<Value>) {
transaction(database) {
toUnset.flatMap {
val keys = select { selectByValue(it) }.mapNotNull { it.asKey }
val keys = selectAll().where { selectByValue(it) }.mapNotNull { it.asKey }
deleteWhere { selectByIds(it, keys) }
keys
}

View File

@ -24,11 +24,11 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
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) {
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 } }
@ -46,7 +46,7 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
}
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
select { selectByValue(v) }.selectPaginated(
selectAll().where { selectByValue(v) }.selectPaginated(
pagination,
keyColumn,
reversed

View File

@ -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.exposed.ColumnAllocator
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
@ -13,15 +14,17 @@ open class ExposedKeyValueRepo<Key, Value>(
database: Database,
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
tableName: String? = null,
flowsExtraBufferCapacity: Int = Int.MAX_VALUE,
flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
database,
keyColumnAllocator,
valueColumnAllocator,
tableName
) {
protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
protected val _onValueRemoved = MutableSharedFlow<Key>()
protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
protected val _onValueRemoved = MutableSharedFlow<Key>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
@ -65,7 +68,7 @@ open class ExposedKeyValueRepo<Key, Value>(
override suspend fun unsetWithValues(toUnset: List<Value>) {
transaction(database) {
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) }
keys
}

View File

@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos.exposed.onetomany
import dev.inmo.micro_utils.repos.KeyValuesRepo
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
@ -8,18 +9,20 @@ import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedKeyValuesRepo<Key, Value>(
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>(
database,
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>>
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>>
get() = _onValueRemoved.asSharedFlow()
protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow()
protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
override val onDataCleared: Flow<Key>
get() = _onDataCleared.asSharedFlow()
@ -29,7 +32,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>(
transaction(database) {
toAdd.keys.flatMap { k ->
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
}
val insertResult = insert {

View File

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

View File

@ -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.exposed.ColumnAllocator
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
@ -13,20 +14,22 @@ open class ExposedKeyValuesRepo<Key, Value>(
database: Database,
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
tableName: String? = null,
flowsExtraBufferCapacity: Int = Int.MAX_VALUE,
flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : KeyValuesRepo<Key, Value>, ExposedReadKeyValuesRepo<Key, Value>(
database,
keyColumnAllocator,
valueColumnAllocator,
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>>
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>>
get() = _onValueRemoved
protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow()
protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
override val onDataCleared: Flow<Key>
get() = _onDataCleared
@ -34,7 +37,7 @@ open class ExposedKeyValuesRepo<Key, Value>(
transaction(database) {
toAdd.keys.flatMap { k ->
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
}
val insertResult = insert {
@ -69,7 +72,7 @@ open class ExposedKeyValuesRepo<Key, Value>(
override suspend fun removeWithValue(v: Value) {
transaction(database) {
val keys = select { selectByValue(v) }.map { it.asKey }
val keys = selectAll().where { selectByValue(v) }.map { it.asKey }
deleteWhere { SqlExpressionBuilder.selectByValue(v) }
keys
}.forEach {
@ -85,7 +88,7 @@ open class ExposedKeyValuesRepo<Key, Value>(
override suspend fun clearWithValue(v: Value) {
transaction(database) {
val toClear = select { selectByValue(v) }
val toClear = selectAll().where { selectByValue(v) }
.asSequence()
.map { it.asKey to it.asObject }
.groupBy { it.first }

View File

@ -26,7 +26,7 @@ class ExposedStandardVersionsRepoProxy(
}
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) {

View File

@ -1,8 +1,6 @@
package dev.inmo.micro_utils.repos.generator
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.isAnnotationPresent
import com.google.devtools.ksp.*
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
@ -36,6 +34,7 @@ import kotlinx.serialization.Serializable
import java.io.File
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.jvmName
private fun KSClassifierReference.quilifiedName(): String = "${qualifier ?.let { "${it.quilifiedName()}." } ?: ""}${referencedName()}"
@ -75,16 +74,29 @@ class Processor(
ORIGINAL FILE: ${ksFile.fileName}
""".trimIndent()
)
val newName = "New${ksClassDeclaration.simpleName.getShortName()}"
val registeredName = "Registered${ksClassDeclaration.simpleName.getShortName()}"
val allKSClassProperties = ksClassDeclaration.getAllProperties()
val excludedKSClassProperties = allKSClassProperties.filter {
it.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class)
val excludedKSClassProperties = allKSClassProperties.filter { property ->
property.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) || (property.findOverridee() ?.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) == true)
}
val excludedKSClassPropertiesNames = excludedKSClassProperties.map { it.simpleName.asString() }
val ksClassProperties = allKSClassProperties.filter {
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 newNewType = TypeSpec.classBuilder(newName).apply {
@ -99,17 +111,20 @@ class Processor(
}
primaryConstructor(
FunSpec.constructorBuilder().apply {
val withoutDefaults = mutableListOf<Pair<ParameterSpec.Builder, PropertySpec.Builder>>()
ksClassProperties.forEach {
addParameter(
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())
}.build()
)
val property = PropertySpec.builder(it.simpleName.getShortName(), it.type.toTypeName(), KModifier.OVERRIDE).apply {
initializer(it.simpleName.getShortName())
}
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()
)
@ -125,14 +140,25 @@ class Processor(
(it.arguments.first().value as List<KSType>).map { it.declaration as KSClassDeclaration }
}.toList()
val registeredTypesProperties: List<KSPropertyDeclaration> = registeredSupertypes.flatMap { registeredType ->
registeredType.getAllProperties()
}.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 propertiesToOverrideInRegistered = allProperties.distinctBy { it.simpleName.asString() }.sortedBy { property ->
val allProperties: List<KSPropertyDeclaration> = registeredTypesProperties + ksClassProperties.toList()
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()
ksClassPropertiesNames.indexOf(name).takeIf { it > -1 } ?.let {
@ -156,17 +182,20 @@ class Processor(
addModifiers(KModifier.DATA)
primaryConstructor(
FunSpec.constructorBuilder().apply {
val withoutDefaults = mutableListOf<Pair<ParameterSpec.Builder, PropertySpec.Builder>>()
propertiesToOverrideInRegistered.forEach {
addParameter(
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())
}.build()
)
val property = PropertySpec.builder(it.simpleName.getShortName(), it.type.toTypeName(), KModifier.OVERRIDE).apply {
initializer(it.simpleName.getShortName())
}
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()
)
@ -191,7 +220,7 @@ class Processor(
FunSpec.builder("asRegistered").apply {
receiver(ksClassDeclaration.toClassName())
(registeredTypesProperties.filter { it.simpleName.asString() !in ksClassPropertiesNames }).forEach {
addParameter(it.simpleName.asString(), it.typeName)
addParameter(it.simpleName.asString(), it.type.toTypeName())
}
addCode(
CodeBlock.of(

View File

@ -14,6 +14,7 @@ public data class NewTest(
override val property1: String,
override val property2: Int,
@Serializable
@SerialName(`value` = "custom_parent_name")
override val parent: ParentTypeId?,
) : Test
@ -24,6 +25,7 @@ public data class RegisteredTest(
override val property1: String,
override val property2: Int,
@Serializable
@SerialName(`value` = "custom_parent_name")
override val parent: ParentTypeId?,
) : Test, IRegisteredTest

View File

@ -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.GenerateCRUDModelExcludeOverride
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
@ -18,6 +19,7 @@ sealed interface Test {
val property1: String
val property2: Int
@Serializable
@SerialName("custom_parent_name")
val parent: ParentTypeId?
@GenerateCRUDModelExcludeOverride

17
resources/build.gradle Normal file
View File

@ -0,0 +1,17 @@
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")
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,7 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
@ -11,15 +9,20 @@ import kotlinx.serialization.encoding.Encoder
* serialization
*
* @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>(
private val base: SerializationStrategy<I>,
private val serialize: (O) -> I
private val internalSerialize: (Encoder, O) -> I
) : SerializationStrategy<O> {
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) {
base.serialize(encoder, serialize(value))
base.serialize(encoder, internalSerialize(encoder, value))
}
}

View File

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

View File

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