mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
176 Commits
Author | SHA1 | Date | |
---|---|---|---|
1c0b8cf842 | |||
a1624ea2a9 | |||
23a050cf1e | |||
916f2f96f4 | |||
00cc214754 | |||
b2e38f72b9 | |||
e7107d238d | |||
ed9ebdbd1a | |||
e80676d3d2 | |||
02d02fa8f2 | |||
bd783fb74f | |||
50386adf70 | |||
f4ee6c2890 | |||
d45aef9fe5 | |||
a56cd3dddd | |||
419e7070ee | |||
612cf40b5f | |||
8b39882e83 | |||
e639ae172b | |||
d0446850ae | |||
c48465b90b | |||
f419fd03d2 | |||
494812a660 | |||
eb78f21eec | |||
4bda70268b | |||
f037ce4371 | |||
3d2196e35d | |||
a74f061b02 | |||
11ade14676 | |||
eb562d8784 | |||
1ee5b4bfd4 | |||
d97892080b | |||
6f37125724 | |||
ed1baaade7 | |||
bb9669f8fd | |||
bdac715d48 | |||
acf4971298 | |||
249bc83a8c | |||
0fbb92f03f | |||
ca27cb3f82 | |||
3a5771a0cc | |||
527a2a91ac | |||
6763e5c4c6 | |||
06918d8310 | |||
89ccaa1b57 | |||
5d0bdb9bcf | |||
31fdcf74a5 | |||
afca09cc1d | |||
531d89d9db | |||
6bbbea0bc3 | |||
e337cd98c8 | |||
bcbab3b380 | |||
fb63de7568 | |||
aa45a4ab13 | |||
2af7e2f681 | |||
34fd9edce0 | |||
2a4cb8c5f9 | |||
50ea40bc3a | |||
a77654052d | |||
88aafce552 | |||
4e95d6bfff | |||
38d0e34fb5 | |||
8fbc6b9041 | |||
e8219d6cf4 | |||
6c20fc4ca6 | |||
85cd975492 | |||
1171a717fe | |||
bbe5320312 | |||
00acb9fddd | |||
de3d14dc41 | |||
67ff9cc9b3 | |||
af132103a0 | |||
3b1124a804 | |||
f226c2dfd6 | |||
69d6e63846 | |||
02c3d397ad | |||
67a1050646 | |||
8cd0775a6c | |||
162294d6c6 | |||
c4dd19dd00 | |||
d2314422f1 | |||
6fedd6f859 | |||
e52b59665f | |||
cda9d09689 | |||
c9237b3f00 | |||
18bba66c4a | |||
63418c4a8a | |||
2e66c6f4e3 | |||
e9c5df4c13 | |||
bc7789ad2c | |||
e3da761249 | |||
4082f65afa | |||
5d1cab075d | |||
bcf67f7e59 | |||
7d3b1f8e75 | |||
119a0588cc | |||
fab789d9c0 | |||
ceba81c08f | |||
a061af0558 | |||
c7a53846ad | |||
a683cccf0c | |||
50d41e35c1 | |||
aa0e831cea | |||
44e26ccb4f | |||
2a783f6e2b | |||
6058d6a724 | |||
2e9c7eb5fa | |||
e75465ad10 | |||
de01ad54e9 | |||
eeea7ddbe3 | |||
e0b18bec05 | |||
410e89bba9 | |||
9ef19dc42b | |||
0337d1b82d | |||
f5bd4c5ccb | |||
630f9bc0d4 | |||
18b4ffece1 | |||
f64e1effa3 | |||
847fcbb488 | |||
88002ec8e7 | |||
7f8db6a29d | |||
b183b82443 | |||
5dad27de72 | |||
6b66084d0e | |||
50b56a7c39 | |||
7ab7d14471 | |||
bdcc179b7b | |||
55ffd4b46f | |||
7fc5ee70e1 | |||
a24a335743 | |||
ef9af71960 | |||
925702d315 | |||
d50dffec8c | |||
cef2081a13 | |||
06c8bde7c9 | |||
c9bbfa3820 | |||
eed7cfdc42 | |||
bd9b0d16ab | |||
ea6c33b497 | |||
dc80ade2fb | |||
f6a06ee8ea | |||
2644f27975 | |||
3dc68a7b8b | |||
97fc1d6239 | |||
662f4d22a3 | |||
b70aa12be9 | |||
71f12f5f19 | |||
e10504eeeb | |||
2dea9f3bc0 | |||
35c9dda5bc | |||
e831f3949a | |||
b0b39cc693 | |||
fc03be3f73 | |||
b61f6b81f1 | |||
f5bc1c1fce | |||
a729f9568c | |||
5749e00377 | |||
ef73c24a0c | |||
94717ee351 | |||
9a18ded65b | |||
b23220f491 | |||
6e6bb03246 | |||
1ae6bae3b8 | |||
1239ca3256 | |||
57b7797ea4 | |||
5ee5bfd1d5 | |||
7229a3e198 | |||
bee083582f | |||
9d7f99f286 | |||
6ef403853c | |||
6ae7ccb9a1 | |||
dafc50c463 | |||
e89e2c931d | |||
43a67b99e4 | |||
46c48f4f31 | |||
bf0fe85aa6 |
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -1,12 +0,0 @@
|
|||||||
name: Regular build
|
|
||||||
on: [push]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-java@v1
|
|
||||||
with:
|
|
||||||
java-version: 1.8
|
|
||||||
- name: Build
|
|
||||||
run: ./gradlew build
|
|
5
.github/workflows/dokka_push.yml
vendored
5
.github/workflows/dokka_push.yml
vendored
@@ -10,7 +10,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 11
|
||||||
|
- name: Fix android 32.0.0 dx
|
||||||
|
continue-on-error: true
|
||||||
|
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew dokkaHtml
|
run: ./gradlew dokkaHtml
|
||||||
- name: Publish KDocs
|
- name: Publish KDocs
|
||||||
|
6
.github/workflows/packages_push.yml
vendored
6
.github/workflows/packages_push.yml
vendored
@@ -8,7 +8,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 11
|
||||||
|
- name: Fix android 32.0.0 dx
|
||||||
|
continue-on-error: true
|
||||||
|
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
|
||||||
- name: Rewrite version
|
- name: Rewrite version
|
||||||
run: |
|
run: |
|
||||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
@@ -18,6 +21,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
|
continue-on-error: true
|
||||||
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
|
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
|
||||||
env:
|
env:
|
||||||
GITHUBPACKAGES_USER: ${{ github.actor }}
|
GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,5 +11,6 @@ out/
|
|||||||
|
|
||||||
secret.gradle
|
secret.gradle
|
||||||
local.properties
|
local.properties
|
||||||
|
kotlin-js-store
|
||||||
|
|
||||||
publishing.sh
|
publishing.sh
|
||||||
|
268
CHANGELOG.md
268
CHANGELOG.md
@@ -1,5 +1,273 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9.2
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.10` -> `2.4.12`
|
||||||
|
|
||||||
|
## 0.9.1
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Exposed`:
|
||||||
|
* Default realizations of standard interfaces for exposed DB are using public fields for now:
|
||||||
|
* `ExposedReadKeyValueRepo`
|
||||||
|
* `ExposedReadOneToManyKeyValueRepo`
|
||||||
|
* `ExposedStandardVersionsRepoProxy`
|
||||||
|
* New typealiases for one to many exposed realizations:
|
||||||
|
* `ExposedReadKeyValuesRepo`
|
||||||
|
* `ExposedKeyValuesRepo`
|
||||||
|
|
||||||
|
## 0.9.0
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.5.31` -> `1.6.10`
|
||||||
|
* `Coroutines`: `1.5.2` -> `1.6.0`
|
||||||
|
* `Serialization`: `1.3.1` -> `1.3.2`
|
||||||
|
* `Exposed`: `0.36.2` -> `0.37.2`
|
||||||
|
* `Ktor`: `1.6.5` -> `1.6.7`
|
||||||
|
* `Klock`: `2.4.8` -> `2.4.10`
|
||||||
|
|
||||||
|
## 0.8.9
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* `Server`:
|
||||||
|
* Fixes in `uniloadMultipart`
|
||||||
|
* `Client`:
|
||||||
|
* Fixes in `unimultipart`
|
||||||
|
* `FSM`:
|
||||||
|
* Fixes in `DefaultUpdatableStatesMachine`
|
||||||
|
|
||||||
|
## 0.8.8
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `AppCompat`: `1.3.1` -> `1.4.0`
|
||||||
|
* Android Compile SDK: `31.0.0` -> `32.0.0`
|
||||||
|
* `FSM`:
|
||||||
|
* `DefaultStatesMachine` now is extendable
|
||||||
|
* New type `UpdatableStatesMachine` with default realization`DefaultUpdatableStatesMachine`
|
||||||
|
|
||||||
|
## 0.8.7
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* `Client`:
|
||||||
|
* `UnifiedRequester` now have no private fields
|
||||||
|
* Add preview work with multipart
|
||||||
|
* `Server`
|
||||||
|
* `UnifiedRouter` now have no private fields
|
||||||
|
* Add preview work with multipart
|
||||||
|
|
||||||
|
## 0.8.6
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* `Either` extensions `onFirst` and `onSecond` now accept not `crossinline` callbacks
|
||||||
|
* All `joinTo` now accept not `crossinline` callbacks
|
||||||
|
|
||||||
|
## 0.8.5
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* `repeatOnFailure`
|
||||||
|
|
||||||
|
## 0.8.4
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* `Server`:
|
||||||
|
* Several new `createKtorServer`
|
||||||
|
|
||||||
|
## 0.8.3
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* Ranges intersection functionality
|
||||||
|
* New type `Optional`
|
||||||
|
* `Pagination`:
|
||||||
|
* `Pagination` now extends `ClosedRange<Int>`
|
||||||
|
* `Pagination` intersection functionality
|
||||||
|
|
||||||
|
## 0.8.2
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.7` -> `2.4.8`
|
||||||
|
* `Serialization`: `1.3.0` -> `1.3.1`
|
||||||
|
* `FSM`:
|
||||||
|
* Now it is possible to pass any `CheckableHandlerHolder` in `FSMBuilder`
|
||||||
|
* Now `StatesMachine` works with `CheckableHandlerHolder` instead of `CustomizableHandlerHolder`
|
||||||
|
|
||||||
|
## 0.8.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.36.1` -> `0.36.2`
|
||||||
|
* `Core KTX`: `1.6.0` -> `1.7.0`
|
||||||
|
|
||||||
|
## 0.8.0
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.6` -> `2.4.7`
|
||||||
|
* `Ktor`: `1.6.4` -> `1.6.5`
|
||||||
|
* `Exposed`: `0.35.3` -> `0.36.1`
|
||||||
|
* `Common`:
|
||||||
|
* Type `Either` got its own serializer
|
||||||
|
* `FSM`:
|
||||||
|
* `Common`:
|
||||||
|
* Full rework of FSM:
|
||||||
|
* Now it is more flexible for checking of handler opportunity to handle state
|
||||||
|
* Now machine and states managers are type-oriented
|
||||||
|
* `StateHandlerHolder` has been renamed to `CheckableHandlerHolder`
|
||||||
|
* Add opportunity for comfortable adding default state handler
|
||||||
|
|
||||||
|
## 0.7.4
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* New type `Either`
|
||||||
|
* `Serialization`:
|
||||||
|
* `TypedSerializer`
|
||||||
|
* New factory fun which accept vararg pairs of type and its serializer
|
||||||
|
* `Repos`:
|
||||||
|
* `Common` (`Android`):
|
||||||
|
* `AbstractMutableAndroidCRUDRepo` flows now will have extra buffer capacity instead of reply. It means that
|
||||||
|
android crud repo _WILL NOT_ send previous events to the
|
||||||
|
* `Exposed`:
|
||||||
|
* New parameter `AbstractExposedWriteCRUDRepo#replyCacheInFlows`
|
||||||
|
* KeyValue realization `ExposedKeyValueRepo` properties `_onNewValue` and `_onValueRemoved` now are available in
|
||||||
|
inheritors
|
||||||
|
* `Pagination`:
|
||||||
|
* `Common`:
|
||||||
|
* New types `getAllBy*` for current, next and custom paging
|
||||||
|
|
||||||
|
## 0.7.3
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.35.2` -> `0.35.3`
|
||||||
|
|
||||||
|
## 0.7.2
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.5` -> `2.4.6`
|
||||||
|
|
||||||
|
## 0.7.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.3` -> `2.4.5`
|
||||||
|
* `Exposed`: `0.35.1` -> `0.35.2`
|
||||||
|
* `Coroutines`:
|
||||||
|
* `Common`:
|
||||||
|
* New `Flow` - `AccumulatorFlow`
|
||||||
|
* `FSM`:
|
||||||
|
* `Common`:
|
||||||
|
* `InMemoryStatesManager` has been replaced
|
||||||
|
* `StatesMachine` became an interface
|
||||||
|
* New manager `DefaultStatesManager` with `DefaultStatesManagerRepo` for abstraction of manager and storing of
|
||||||
|
data info
|
||||||
|
|
||||||
|
## 0.7.0
|
||||||
|
|
||||||
|
**THIS VERSION HAS MIGRATED FROM KOTLINX DATETIME TO KORLIBS KLOCK. CAREFUL**
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `kotlinx.datetime` -> `Klock`
|
||||||
|
|
||||||
|
## 0.6.0 DO NOT RECOMMENDED
|
||||||
|
|
||||||
|
**THIS VERSION HAS MIGRATED FROM KORLIBS KLOCK TO KOTLINX DATETIME. CAREFUL**
|
||||||
|
**ALL DEPRECATION HAVE BEEN REMOVED**
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Klock` -> `kotlinx.datetime`
|
||||||
|
|
||||||
|
## 0.5.31
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.4.2` -> `2.4.3`
|
||||||
|
* `Ktor`: `1.6.3` -> `1.6.4`
|
||||||
|
|
||||||
|
## 0.5.30
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Serialization`: `1.2.2` -> `1.3.0`
|
||||||
|
|
||||||
|
## 0.5.29
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.34.2` -> `0.35.1`
|
||||||
|
|
||||||
|
## 0.5.28
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.5.30` -> `1.5.31`
|
||||||
|
* `Klock`: `2.4.1` -> `2.4.2`
|
||||||
|
|
||||||
|
## 0.5.27
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.34.1` -> `0.34.2`
|
||||||
|
|
||||||
|
## 0.5.26
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `InMemory`:
|
||||||
|
* `MapCRUDRepo`s and `MapKeyValueRepo`s got `protected` methods and properties instead of private
|
||||||
|
|
||||||
|
## 0.5.25
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `UUID`: `0.3.0` -> `0.3.1`
|
||||||
|
* `Common`:
|
||||||
|
* New property `MPPFile#withoutSlashAtTheEnd`
|
||||||
|
* Extension `clamp` has been deprecated
|
||||||
|
* New extension `Iterable#diff`
|
||||||
|
* `Serialization`:
|
||||||
|
* New operators `TypedSerializer#plusAssign` and `TypedSerializer#minusAssign`
|
||||||
|
|
||||||
|
## 0.5.24
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Coroutines`: `1.5.1` -> `1.5.2`
|
||||||
|
* `Klock`: `2.3.4` -> `2.4.1`
|
||||||
|
* `Coroutines`:
|
||||||
|
* New function `CoroutineScope` with safely exceptions handler as second parameter
|
||||||
|
|
||||||
|
## 0.5.23
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Exposed`: `0.33.1` -> `0.34.1`
|
||||||
|
* `Common`:
|
||||||
|
* New extensions `Iterable#joinTo` and `Array#joinTo`
|
||||||
|
|
||||||
|
## 0.5.22
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Kotlin`: `1.5.21` -> `1.5.30`
|
||||||
|
* `Klock`: `2.3.2` -> `2.3.4`
|
||||||
|
* `AppCompat`: `1.3.0` -> `1.3.1`
|
||||||
|
* `Ktor`: `1.6.2` -> `1.6.3`
|
||||||
|
|
||||||
|
## 0.5.21
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Klock`: `2.3.1` -> `2.3.2`
|
||||||
|
* `Serialization`
|
||||||
|
* `Typed Serializer`:
|
||||||
|
* `TypedSerializer` Descriptor serial name has been fixed
|
||||||
|
|
||||||
|
## 0.5.20
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Common`
|
||||||
|
* `Android`:
|
||||||
|
* `*OrNull` analogs of `Cursor.get*(String)` extensions have been added
|
||||||
|
* Extensions `Cursor.getFloat` and `Cursor.getFloatOrNull` have been added
|
||||||
|
|
||||||
|
## 0.5.19
|
||||||
|
|
||||||
|
* `LanguageCode`:
|
||||||
|
* `IetfLanguageCode` became as sealed class
|
||||||
|
* `IetfLanguageCode` now override `toString` and returns its code
|
||||||
|
|
||||||
|
## 0.5.18
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Kotlin Exposed`: `0.32.1` -> `0.33.1`
|
||||||
|
* `LanguageCode`:
|
||||||
|
* Module has been created
|
||||||
|
|
||||||
## 0.5.17
|
## 0.5.17
|
||||||
|
|
||||||
**SINCE THIS UPDATE JS PARTS WILL BE COMPILED WITH IR COMPILER ONLY**
|
**SINCE THIS UPDATE JS PARTS WILL BE COMPILED WITH IR COMPILER ONLY**
|
||||||
|
@@ -7,7 +7,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
||||||
|
@@ -12,9 +12,7 @@ package dev.inmo.micro_utils.common
|
|||||||
AnnotationTarget.PROPERTY_GETTER,
|
AnnotationTarget.PROPERTY_GETTER,
|
||||||
AnnotationTarget.PROPERTY_SETTER,
|
AnnotationTarget.PROPERTY_SETTER,
|
||||||
AnnotationTarget.FUNCTION,
|
AnnotationTarget.FUNCTION,
|
||||||
AnnotationTarget.TYPE,
|
AnnotationTarget.TYPEALIAS
|
||||||
AnnotationTarget.TYPEALIAS,
|
|
||||||
AnnotationTarget.TYPE_PARAMETER
|
|
||||||
)
|
)
|
||||||
annotation class PreviewFeature(val message: String = "It is possible, that behaviour of this thing will be changed or removed in future releases")
|
annotation class PreviewFeature(val message: String = "It is possible, that behaviour of this thing will be changed or removed in future releases")
|
||||||
|
|
||||||
@@ -30,8 +28,6 @@ annotation class PreviewFeature(val message: String = "It is possible, that beha
|
|||||||
AnnotationTarget.PROPERTY_GETTER,
|
AnnotationTarget.PROPERTY_GETTER,
|
||||||
AnnotationTarget.PROPERTY_SETTER,
|
AnnotationTarget.PROPERTY_SETTER,
|
||||||
AnnotationTarget.FUNCTION,
|
AnnotationTarget.FUNCTION,
|
||||||
AnnotationTarget.TYPE,
|
AnnotationTarget.TYPEALIAS
|
||||||
AnnotationTarget.TYPEALIAS,
|
|
||||||
AnnotationTarget.TYPE_PARAMETER
|
|
||||||
)
|
)
|
||||||
annotation class Warning(val message: String)
|
annotation class Warning(val message: String)
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.common
|
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
inline fun <T : Comparable<T>> T.clamp(min: T, max: T): T {
|
|
||||||
return when {
|
|
||||||
this < min -> min
|
|
||||||
this > max -> max
|
|
||||||
else -> this
|
|
||||||
}
|
|
||||||
}
|
|
@@ -27,8 +27,8 @@ data class Diff<T> internal constructor(
|
|||||||
|
|
||||||
private inline fun <T> performChanges(
|
private inline fun <T> performChanges(
|
||||||
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
||||||
additionalsInOld: MutableList<T>,
|
additionsInOld: MutableList<T>,
|
||||||
additionalsInNew: MutableList<T>,
|
additionsInNew: MutableList<T>,
|
||||||
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
removedList: MutableList<IndexedValue<T>>,
|
removedList: MutableList<IndexedValue<T>>,
|
||||||
addedList: MutableList<IndexedValue<T>>,
|
addedList: MutableList<IndexedValue<T>>,
|
||||||
@@ -52,20 +52,20 @@ private inline fun <T> performChanges(
|
|||||||
newPotentials.first().second ?.let { addedList.add(it) }
|
newPotentials.first().second ?.let { addedList.add(it) }
|
||||||
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||||
addedList.add(newOne!!)
|
addedList.add(newOne!!)
|
||||||
oldOne ?.let { additionalsInOld.add(oldOne.value) }
|
oldOne ?.let { additionsInOld.add(oldOne.value) }
|
||||||
}
|
}
|
||||||
if (newPotentials.size > 1) {
|
if (newPotentials.size > 1) {
|
||||||
newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) }
|
newPotentials.last().first ?.value ?.let { additionsInOld.add(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newOneEqualToOldObject -> {
|
newOneEqualToOldObject -> {
|
||||||
newPotentials.first().first ?.let { removedList.add(it) }
|
newPotentials.first().first ?.let { removedList.add(it) }
|
||||||
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) ->
|
||||||
removedList.add(oldOne!!)
|
removedList.add(oldOne!!)
|
||||||
newOne ?.let { additionalsInNew.add(newOne.value) }
|
newOne ?.let { additionsInNew.add(newOne.value) }
|
||||||
}
|
}
|
||||||
if (newPotentials.size > 1) {
|
if (newPotentials.size > 1) {
|
||||||
newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) }
|
newPotentials.last().second ?.value ?.let { additionsInNew.add(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,6 +139,10 @@ fun <T> Iterable<T>.calculateDiff(
|
|||||||
|
|
||||||
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
||||||
}
|
}
|
||||||
|
inline fun <T> Iterable<T>.diff(
|
||||||
|
other: Iterable<T>,
|
||||||
|
strictComparison: Boolean = false
|
||||||
|
): Diff<T> = calculateDiff(other, strictComparison)
|
||||||
|
|
||||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
|
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
|
||||||
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
||||||
|
@@ -0,0 +1,148 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.descriptors.*
|
||||||
|
import kotlinx.serialization.encoding.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realization of this interface will contains at least one not null - [t1] or [t2]
|
||||||
|
*
|
||||||
|
* @see EitherFirst
|
||||||
|
* @see EitherSecond
|
||||||
|
* @see Either.Companion.first
|
||||||
|
* @see Either.Companion.second
|
||||||
|
* @see Either.onFirst
|
||||||
|
* @see Either.onSecond
|
||||||
|
*/
|
||||||
|
@Serializable(EitherSerializer::class)
|
||||||
|
sealed interface Either<T1, T2> {
|
||||||
|
val t1: T1?
|
||||||
|
val t2: T2?
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <T1, T2> serializer(
|
||||||
|
t1Serializer: KSerializer<T1>,
|
||||||
|
t2Serializer: KSerializer<T2>,
|
||||||
|
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EitherSerializer<T1, T2>(
|
||||||
|
t1Serializer: KSerializer<T1>,
|
||||||
|
t2Serializer: KSerializer<T2>,
|
||||||
|
) : KSerializer<Either<T1, T2>> {
|
||||||
|
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
||||||
|
override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
||||||
|
"TypedSerializer",
|
||||||
|
SerialKind.CONTEXTUAL
|
||||||
|
) {
|
||||||
|
element("type", String.serializer().descriptor)
|
||||||
|
element("value", ContextualSerializer(Either::class).descriptor)
|
||||||
|
}
|
||||||
|
private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer)
|
||||||
|
private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
||||||
|
override fun deserialize(decoder: Decoder): Either<T1, T2> {
|
||||||
|
return decoder.decodeStructure(descriptor) {
|
||||||
|
var type: String? = null
|
||||||
|
lateinit var result: Either<T1, T2>
|
||||||
|
while (true) {
|
||||||
|
when (val index = decodeElementIndex(descriptor)) {
|
||||||
|
0 -> type = decodeStringElement(descriptor, 0)
|
||||||
|
1 -> {
|
||||||
|
result = when (type) {
|
||||||
|
"t1" -> decodeSerializableElement(
|
||||||
|
descriptor,
|
||||||
|
1,
|
||||||
|
t1EitherSerializer
|
||||||
|
)
|
||||||
|
"t2" -> decodeSerializableElement(
|
||||||
|
descriptor,
|
||||||
|
1,
|
||||||
|
t2EitherSerializer
|
||||||
|
)
|
||||||
|
else -> error("Unknown type of either: $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompositeDecoder.DECODE_DONE -> break
|
||||||
|
else -> error("Unexpected index: $index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
||||||
|
override fun serialize(encoder: Encoder, value: Either<T1, T2>) {
|
||||||
|
encoder.encodeStructure(descriptor) {
|
||||||
|
when (value) {
|
||||||
|
is EitherFirst -> {
|
||||||
|
encodeStringElement(descriptor, 0, "t1")
|
||||||
|
encodeSerializableElement(descriptor, 1, t1EitherSerializer, value)
|
||||||
|
}
|
||||||
|
is EitherSecond -> {
|
||||||
|
encodeStringElement(descriptor, 0, "t2")
|
||||||
|
encodeSerializableElement(descriptor, 1, t2EitherSerializer, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type [Either] will always have not nullable [t1]
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class EitherFirst<T1, T2>(
|
||||||
|
override val t1: T1
|
||||||
|
) : Either<T1, T2> {
|
||||||
|
override val t2: T2?
|
||||||
|
get() = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type [Either] will always have not nullable [t2]
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class EitherSecond<T1, T2>(
|
||||||
|
override val t2: T2
|
||||||
|
) : Either<T1, T2> {
|
||||||
|
override val t1: T1?
|
||||||
|
get() = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return New instance of [EitherFirst]
|
||||||
|
*/
|
||||||
|
inline fun <T1, T2> Either.Companion.first(t1: T1): Either<T1, T2> = EitherFirst(t1)
|
||||||
|
/**
|
||||||
|
* @return New instance of [EitherSecond]
|
||||||
|
*/
|
||||||
|
inline fun <T1, T2> Either.Companion.second(t2: T2): Either<T1, T2> = EitherSecond(t2)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] in case when [Either.t1] of [this] is not null
|
||||||
|
*/
|
||||||
|
inline fun <T1, T2, E : Either<T1, T2>> E.onFirst(block: (T1) -> Unit): E {
|
||||||
|
val t1 = t1
|
||||||
|
t1 ?.let(block)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] in case when [Either.t2] of [this] is not null
|
||||||
|
*/
|
||||||
|
inline fun <T1, T2, E : Either<T1, T2>> E.onSecond(block: (T2) -> Unit): E {
|
||||||
|
val t2 = t2
|
||||||
|
t2 ?.let(block)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T1, reified T2> Any.either() = when (this) {
|
||||||
|
is T1 -> Either.first<T1, T2>(this)
|
||||||
|
is T2 -> Either.second<T1, T2>(this)
|
||||||
|
else -> error("Incorrect type of either argument $this")
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
inline fun <I, R> Iterable<I>.joinTo(
|
||||||
|
separatorFun: (I) -> R?,
|
||||||
|
prefix: R? = null,
|
||||||
|
postfix: R? = null,
|
||||||
|
transform: (I) -> R?
|
||||||
|
): List<R> {
|
||||||
|
val result = mutableListOf<R>()
|
||||||
|
val iterator = iterator()
|
||||||
|
|
||||||
|
prefix ?.let(result::add)
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val element = iterator.next()
|
||||||
|
result.add(transform(element) ?: continue)
|
||||||
|
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
result.add(separatorFun(element) ?: continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postfix ?.let(result::add)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <I, R> Iterable<I>.joinTo(
|
||||||
|
separator: R? = null,
|
||||||
|
prefix: R? = null,
|
||||||
|
postfix: R? = null,
|
||||||
|
transform: (I) -> R?
|
||||||
|
): List<R> = joinTo({ separator }, prefix, postfix, transform)
|
||||||
|
|
||||||
|
inline fun <I> Iterable<I>.joinTo(
|
||||||
|
separatorFun: (I) -> I?,
|
||||||
|
prefix: I? = null,
|
||||||
|
postfix: I? = null
|
||||||
|
): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it }
|
||||||
|
|
||||||
|
inline fun <I> Iterable<I>.joinTo(
|
||||||
|
separator: I? = null,
|
||||||
|
prefix: I? = null,
|
||||||
|
postfix: I? = null
|
||||||
|
): List<I> = joinTo<I>({ separator }, prefix, postfix)
|
||||||
|
|
||||||
|
inline fun <I, reified R> Array<I>.joinTo(
|
||||||
|
separatorFun: (I) -> R?,
|
||||||
|
prefix: R? = null,
|
||||||
|
postfix: R? = null,
|
||||||
|
transform: (I) -> R?
|
||||||
|
): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray()
|
||||||
|
|
||||||
|
inline fun <I, reified R> Array<I>.joinTo(
|
||||||
|
separator: R? = null,
|
||||||
|
prefix: R? = null,
|
||||||
|
postfix: R? = null,
|
||||||
|
transform: (I) -> R?
|
||||||
|
): Array<R> = asIterable().joinTo(separator, prefix, postfix, transform).toTypedArray()
|
@@ -7,7 +7,7 @@ import kotlin.jvm.JvmInline
|
|||||||
@JvmInline
|
@JvmInline
|
||||||
value class FileName(val string: String) {
|
value class FileName(val string: String) {
|
||||||
val name: String
|
val name: String
|
||||||
get() = string.takeLastWhile { it != '/' }
|
get() = withoutSlashAtTheEnd.takeLastWhile { it != '/' }
|
||||||
val extension: String
|
val extension: String
|
||||||
get() = name.takeLastWhile { it != '.' }
|
get() = name.takeLastWhile { it != '.' }
|
||||||
val nameWithoutExtension: String
|
val nameWithoutExtension: String
|
||||||
@@ -17,15 +17,18 @@ value class FileName(val string: String) {
|
|||||||
filename.substring(0, it)
|
filename.substring(0, it)
|
||||||
} ?: filename
|
} ?: filename
|
||||||
}
|
}
|
||||||
|
val withoutSlashAtTheEnd: String
|
||||||
|
get() = string.dropLastWhile { it == '/' }
|
||||||
override fun toString(): String = string
|
override fun toString(): String = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@PreviewFeature
|
|
||||||
expect class MPPFile
|
expect class MPPFile
|
||||||
|
|
||||||
expect val MPPFile.filename: FileName
|
expect val MPPFile.filename: FileName
|
||||||
expect val MPPFile.filesize: Long
|
expect val MPPFile.filesize: Long
|
||||||
|
expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||||
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||||
|
fun MPPFile.bytesSync() = bytesAllocatorSync()
|
||||||
suspend fun MPPFile.bytes() = bytesAllocator()
|
suspend fun MPPFile.bytes() = bytesAllocator()
|
||||||
|
|
||||||
|
@@ -0,0 +1,92 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type represents [T] as not only potentially nullable data, but also as a data which can not be presented. This
|
||||||
|
* type will be useful in cases when [T] is nullable and null as valuable data too in time of data absence should be
|
||||||
|
* presented by some third type.
|
||||||
|
*
|
||||||
|
* Let's imagine, you have nullable name in some database. In case when name is not nullable everything is clear - null
|
||||||
|
* will represent absence of row in the database. In case when name is nullable null will be a little bit dual-meaning,
|
||||||
|
* cause this null will say nothing about availability of the row (of course, it is exaggerated example)
|
||||||
|
*
|
||||||
|
* @see Optional.presented
|
||||||
|
* @see Optional.absent
|
||||||
|
* @see Optional.optional
|
||||||
|
* @see Optional.onPresented
|
||||||
|
* @see Optional.onAbsent
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class Optional<T> internal constructor(
|
||||||
|
@Warning("It is unsafe to use this data directly")
|
||||||
|
val data: T?,
|
||||||
|
@Warning("It is unsafe to use this data directly")
|
||||||
|
val dataPresented: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Will create [Optional] with presented data
|
||||||
|
*/
|
||||||
|
fun <T> presented(data: T) = Optional(data, true)
|
||||||
|
/**
|
||||||
|
* Will create [Optional] without data
|
||||||
|
*/
|
||||||
|
fun <T> absent() = Optional<T>(null, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline val <T> T.optional
|
||||||
|
get() = Optional.presented(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||||
|
*/
|
||||||
|
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
|
||||||
|
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||||
|
*/
|
||||||
|
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
|
||||||
|
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] when data absent ([Optional.dataPresented] == false)
|
||||||
|
*/
|
||||||
|
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
|
||||||
|
if (!dataPresented) { block() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||||
|
*/
|
||||||
|
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
|
||||||
|
if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
|
||||||
|
*/
|
||||||
|
fun <T> Optional<T>.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
|
||||||
|
*/
|
||||||
|
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
||||||
|
*/
|
||||||
|
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
||||||
|
*/
|
||||||
|
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
|
||||||
|
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
@@ -0,0 +1,19 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
fun <T : Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): Pair<T, T>? = when {
|
||||||
|
start == other.start && endInclusive == other.endInclusive -> start to endInclusive
|
||||||
|
start > other.endInclusive || other.start > endInclusive -> null
|
||||||
|
else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IntRange.intersect(
|
||||||
|
other: IntRange
|
||||||
|
): IntRange? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
|
||||||
|
it.first .. it.second
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LongRange.intersect(
|
||||||
|
other: LongRange
|
||||||
|
): LongRange? = (this as ClosedRange<Long>).intersect(other as ClosedRange<Long>) ?.let {
|
||||||
|
it.first .. it.second
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given [action] until getting of successful result specified number of [times].
|
||||||
|
*
|
||||||
|
* A zero-based index of current iteration is passed as a parameter to [action].
|
||||||
|
*/
|
||||||
|
inline fun <R> repeatOnFailure(
|
||||||
|
times: Int,
|
||||||
|
onEachFailure: (Throwable) -> Unit = {},
|
||||||
|
action: (Int) -> R
|
||||||
|
): Optional<R> {
|
||||||
|
repeat(times) {
|
||||||
|
runCatching {
|
||||||
|
action(it)
|
||||||
|
}.onFailure(onEachFailure).onSuccess {
|
||||||
|
return Optional.presented(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.absent()
|
||||||
|
}
|
@@ -11,7 +11,7 @@ class DiffUtilsTests {
|
|||||||
val withIndex = oldList.withIndex()
|
val withIndex = oldList.withIndex()
|
||||||
|
|
||||||
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||||
for ((i, v) in withIndex) {
|
for ((i, _) in withIndex) {
|
||||||
if (i + count > oldList.lastIndex) {
|
if (i + count > oldList.lastIndex) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,12 @@ package dev.inmo.micro_utils.common
|
|||||||
|
|
||||||
import org.khronos.webgl.ArrayBuffer
|
import org.khronos.webgl.ArrayBuffer
|
||||||
import org.w3c.dom.ErrorEvent
|
import org.w3c.dom.ErrorEvent
|
||||||
import org.w3c.files.File
|
import org.w3c.files.*
|
||||||
import org.w3c.files.FileReader
|
|
||||||
import kotlin.js.Promise
|
import kotlin.js.Promise
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual typealias MPPFile = File
|
actual typealias MPPFile = File
|
||||||
|
|
||||||
fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
|
fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
|
||||||
@@ -21,12 +23,32 @@ fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
|
|||||||
reader.readAsArrayBuffer(this)
|
reader.readAsArrayBuffer(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun MPPFile.readBytes(): ByteArray {
|
||||||
|
val reader = FileReaderSync()
|
||||||
|
return reader.readAsArrayBuffer(this).toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await()
|
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual val MPPFile.filename: FileName
|
actual val MPPFile.filename: FileName
|
||||||
get() = FileName(name)
|
get() = FileName(name)
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual val MPPFile.filesize: Long
|
actual val MPPFile.filesize: Long
|
||||||
get() = size.toLong()
|
get() = size.toLong()
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
|
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
|
||||||
|
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||||
|
get() = ::readBytes
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
|
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
|
||||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||||
get() = ::dirtyReadBytes
|
get() = ::dirtyReadBytes
|
||||||
|
@@ -4,12 +4,29 @@ import dev.inmo.micro_utils.coroutines.doInIO
|
|||||||
import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine
|
import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual typealias MPPFile = File
|
actual typealias MPPFile = File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual val MPPFile.filename: FileName
|
actual val MPPFile.filename: FileName
|
||||||
get() = FileName(name)
|
get() = FileName(name)
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual val MPPFile.filesize: Long
|
actual val MPPFile.filesize: Long
|
||||||
get() = length()
|
get() = length()
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
|
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||||
|
get() = ::readBytes
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||||
get() = {
|
get() = {
|
||||||
doInIO {
|
doInIO {
|
||||||
|
@@ -0,0 +1,94 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
private sealed interface AccumulatorFlowStep
|
||||||
|
private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
|
||||||
|
private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
|
||||||
|
private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
|
||||||
|
*
|
||||||
|
* * All unhandled by [FlowCollector] data will not be removed from [AccumulatorFlow] and will be sent to new
|
||||||
|
* [FlowCollector]s until anybody will handle it
|
||||||
|
* * Here there are an [activeData] where data [T] will be stored until somebody will handle it
|
||||||
|
*/
|
||||||
|
class AccumulatorFlow<T>(
|
||||||
|
sourceDataFlow: Flow<T>,
|
||||||
|
scope: CoroutineScope
|
||||||
|
) : AbstractFlow<T>() {
|
||||||
|
private val subscope = scope.LinkedSupervisorScope()
|
||||||
|
private val activeData = ArrayDeque<T>()
|
||||||
|
private val dataMutex = Mutex()
|
||||||
|
private val channelsForBroadcast = mutableListOf<Channel<Any>>()
|
||||||
|
private val channelsMutex = Mutex()
|
||||||
|
private val steps = subscope.actor<AccumulatorFlowStep> { step ->
|
||||||
|
when (step) {
|
||||||
|
is DataRetrievedAccumulatorFlowStep -> {
|
||||||
|
if (activeData.first() === step.data) {
|
||||||
|
dataMutex.withLock {
|
||||||
|
activeData.removeFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is SubscribeAccumulatorFlowStep -> channelsMutex.withLock {
|
||||||
|
channelsForBroadcast.add(step.channel)
|
||||||
|
dataMutex.withLock {
|
||||||
|
val dataToSend = activeData.toList()
|
||||||
|
safelyWithoutExceptions {
|
||||||
|
dataToSend.forEach { step.channel.send(it as Any) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is UnsubscribeAccumulatorFlowStep -> channelsMutex.withLock {
|
||||||
|
channelsForBroadcast.remove(step.channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val subscriptionJob = sourceDataFlow.subscribeSafelyWithoutExceptions(subscope) {
|
||||||
|
dataMutex.withLock {
|
||||||
|
activeData.addLast(it)
|
||||||
|
}
|
||||||
|
channelsMutex.withLock {
|
||||||
|
channelsForBroadcast.forEach { channel ->
|
||||||
|
safelyWithResult {
|
||||||
|
channel.send(it as Any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun collectSafely(collector: FlowCollector<T>) {
|
||||||
|
val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
|
||||||
|
steps.send(SubscribeAccumulatorFlowStep(channel))
|
||||||
|
for (data in channel) {
|
||||||
|
try {
|
||||||
|
collector.emit(data as T)
|
||||||
|
steps.send(DataRetrievedAccumulatorFlowStep(data))
|
||||||
|
} finally {
|
||||||
|
channel.cancel()
|
||||||
|
steps.send(UnsubscribeAccumulatorFlowStep(channel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates [AccumulatorFlow] using [this] as base [Flow]
|
||||||
|
*/
|
||||||
|
fun <T> Flow<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> {
|
||||||
|
return AccumulatorFlow(this, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates [AccumulatorFlow] using [this] with [receiveAsFlow] to get
|
||||||
|
*/
|
||||||
|
fun <T> Channel<T>.accumulatorFlow(scope: CoroutineScope): Flow<T> {
|
||||||
|
return receiveAsFlow().accumulatorFlow(scope)
|
||||||
|
}
|
@@ -147,3 +147,10 @@ suspend inline fun <T> runCatchingSafelyWithoutExceptions(
|
|||||||
): Result<T?> = runCatching {
|
): Result<T?> = runCatching {
|
||||||
safelyWithoutExceptions(onException, block)
|
safelyWithoutExceptions(onException, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun CoroutineScope(
|
||||||
|
context: CoroutineContext,
|
||||||
|
noinline defaultExceptionsHandler: ExceptionHandler<Unit>
|
||||||
|
) = CoroutineScope(
|
||||||
|
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
|
||||||
|
)
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.crypto
|
package dev.inmo.micro_utils.crypto
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())
|
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())
|
||||||
|
@@ -3,6 +3,9 @@ package dev.inmo.micro_utils.crypto
|
|||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual fun SourceBytes.md5(): MD5 = BigInteger(
|
actual fun SourceBytes.md5(): MD5 = BigInteger(
|
||||||
1,
|
1,
|
||||||
MessageDigest.getInstance("MD5").digest(this)
|
MessageDigest.getInstance("MD5").digest(this)
|
||||||
|
@@ -13,10 +13,10 @@ repositories {
|
|||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
js(IR) {
|
// js(IR) {
|
||||||
browser()
|
// browser()
|
||||||
nodejs()
|
// nodejs()
|
||||||
}
|
// }
|
||||||
android {}
|
android {}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -29,7 +29,7 @@ kotlin {
|
|||||||
it != project
|
it != project
|
||||||
&& it.hasProperty("kotlin")
|
&& it.hasProperty("kotlin")
|
||||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||||
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||||
) {
|
) {
|
||||||
@@ -38,22 +38,22 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsMain {
|
// jsMain {
|
||||||
dependencies {
|
// dependencies {
|
||||||
implementation kotlin('stdlib')
|
// implementation kotlin('stdlib')
|
||||||
|
|
||||||
project.parent.subprojects.forEach {
|
// project.parent.subprojects.forEach {
|
||||||
if (
|
// if (
|
||||||
it != project
|
// it != project
|
||||||
&& it.hasProperty("kotlin")
|
// && it.hasProperty("kotlin")
|
||||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
// && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||||
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||||
) {
|
// ) {
|
||||||
api it
|
// api it
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('stdlib')
|
implementation kotlin('stdlib')
|
||||||
@@ -116,9 +116,9 @@ tasks.dokkaHtml {
|
|||||||
sourceRoots.setFrom(findSourcesWithName("commonMain"))
|
sourceRoots.setFrom(findSourcesWithName("commonMain"))
|
||||||
}
|
}
|
||||||
|
|
||||||
named("jsMain") {
|
// named("jsMain") {
|
||||||
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
|
// sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
|
||||||
}
|
// }
|
||||||
|
|
||||||
named("jvmMain") {
|
named("jvmMain") {
|
||||||
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
|
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
|
||||||
|
@@ -10,6 +10,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api project(":micro_utils.common")
|
||||||
api project(":micro_utils.coroutines")
|
api project(":micro_utils.coroutines")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,81 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define checkable holder which can be used to precheck that this handler may handle incoming [State]
|
||||||
|
*/
|
||||||
|
interface CheckableHandlerHolder<I : State, O : State> : StatesHandler<I, O> {
|
||||||
|
suspend fun checkHandleable(state: O): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class
|
||||||
|
* casting in [handleState]
|
||||||
|
*/
|
||||||
|
class CustomizableHandlerHolder<I : O, O : State>(
|
||||||
|
private val delegateTo: StatesHandler<I, O>,
|
||||||
|
private val filter: suspend (state: O) -> Boolean
|
||||||
|
) : CheckableHandlerHolder<I, O> {
|
||||||
|
/**
|
||||||
|
* Checks that [state] can be handled by [delegateTo]. Under the hood it will check exact equality of [state]
|
||||||
|
* [KClass] and use [KClass.isInstance] of [inputKlass] if [strict] == false
|
||||||
|
*/
|
||||||
|
override suspend fun checkHandleable(state: O) = filter(state)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable]
|
||||||
|
* to be sure that this [StatesHandlerHolder] will be able to handle [state]
|
||||||
|
*/
|
||||||
|
override suspend fun StatesMachine<in O>.handleState(state: I): O? {
|
||||||
|
return delegateTo.run { handleState(state) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <I : O, O : State> CheckableHandlerHolder(
|
||||||
|
inputKlass: KClass<I>,
|
||||||
|
strict: Boolean = false,
|
||||||
|
delegateTo: StatesHandler<I, O>
|
||||||
|
) = CustomizableHandlerHolder(
|
||||||
|
StatesHandler<O, O> {
|
||||||
|
delegateTo.run { handleState(it as I) }
|
||||||
|
},
|
||||||
|
if (strict) {
|
||||||
|
{ it::class == inputKlass }
|
||||||
|
} else {
|
||||||
|
{ inputKlass.isInstance(it) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
|
||||||
|
fun <I : O, O : State> StateHandlerHolder(
|
||||||
|
inputKlass: KClass<I>,
|
||||||
|
strict: Boolean = false,
|
||||||
|
delegateTo: StatesHandler<I, O>
|
||||||
|
) = CheckableHandlerHolder(inputKlass, strict, delegateTo)
|
||||||
|
|
||||||
|
inline fun <reified I : O, O : State> CheckableHandlerHolder(
|
||||||
|
strict: Boolean = false,
|
||||||
|
delegateTo: StatesHandler<I, O>
|
||||||
|
) = CheckableHandlerHolder(I::class, strict, delegateTo)
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
|
||||||
|
inline fun <reified I : O, O : State> StateHandlerHolder(
|
||||||
|
strict: Boolean = false,
|
||||||
|
delegateTo: StatesHandler<I, O>
|
||||||
|
) = CheckableHandlerHolder(strict, delegateTo)
|
||||||
|
|
||||||
|
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
|
||||||
|
strict: Boolean = true
|
||||||
|
) = CheckableHandlerHolder<I, O>(
|
||||||
|
I::class,
|
||||||
|
strict,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <I : O, O: State> StatesHandler<I, O>.holder(
|
||||||
|
noinline filter: suspend (state: State) -> Boolean
|
||||||
|
) = CustomizableHandlerHolder<O, O>(
|
||||||
|
{ this@holder.run { handleState(it as I) } },
|
||||||
|
filter
|
||||||
|
)
|
@@ -1,15 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
|
||||||
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
class StateHandlerHolder<I : State>(
|
|
||||||
private val inputKlass: KClass<I>,
|
|
||||||
private val strict: Boolean = false,
|
|
||||||
private val delegateTo: StatesHandler<I>
|
|
||||||
) : StatesHandler<State> {
|
|
||||||
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
|
|
||||||
|
|
||||||
override suspend fun StatesMachine.handleState(state: State): State? {
|
|
||||||
return delegateTo.run { handleState(state as I) }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +1,12 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
fun interface StatesHandler<I : State> {
|
/**
|
||||||
suspend fun StatesMachine.handleState(state: I): State?
|
* Default realization of states handler
|
||||||
|
*/
|
||||||
|
fun interface StatesHandler<I : State, O: State> {
|
||||||
|
/**
|
||||||
|
* Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null
|
||||||
|
* [State] it is assumed that chain is not completed.
|
||||||
|
*/
|
||||||
|
suspend fun StatesMachine<in O>.handleState(state: I): O?
|
||||||
}
|
}
|
||||||
|
@@ -1,46 +1,120 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.Optional
|
||||||
|
import dev.inmo.micro_utils.common.onPresented
|
||||||
import dev.inmo.micro_utils.coroutines.*
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
private suspend fun <I : State> StatesMachine.launchStateHandling(
|
/**
|
||||||
state: State,
|
* Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
|
||||||
handlers: List<StateHandlerHolder<out I>>
|
* [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start
|
||||||
): State? {
|
* handling until [start] method will be called
|
||||||
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
*/
|
||||||
handleState(state)
|
interface StatesMachine<T : State> : StatesHandler<T, T> {
|
||||||
|
suspend fun launchStateHandling(
|
||||||
|
state: T,
|
||||||
|
handlers: List<CheckableHandlerHolder<in T, T>>
|
||||||
|
): T? {
|
||||||
|
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
||||||
|
handleState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts handling of [State]s
|
||||||
|
*/
|
||||||
|
fun start(scope: CoroutineScope): Job
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start chain of [State]s witn [state]
|
||||||
|
*/
|
||||||
|
suspend fun startChain(state: T)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Creates [DefaultStatesMachine]
|
||||||
|
*/
|
||||||
|
operator fun <T: State> invoke(
|
||||||
|
statesManager: StatesManager<T>,
|
||||||
|
handlers: List<CheckableHandlerHolder<in T, T>>
|
||||||
|
) = DefaultStatesMachine(statesManager, handlers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatesMachine (
|
/**
|
||||||
private val statesManager: StatesManager,
|
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
|
||||||
private val handlers: List<StateHandlerHolder<*>>
|
* resolving, and uses [launchStateHandling] for [State] handling.
|
||||||
) : StatesHandler<State> {
|
*
|
||||||
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
|
* This class suppose to be extended in case you wish some custom behaviour inside of [launchStateHandling], for example
|
||||||
|
*/
|
||||||
|
open class DefaultStatesMachine <T: State>(
|
||||||
|
protected val statesManager: StatesManager<T>,
|
||||||
|
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||||
|
) : StatesMachine<T> {
|
||||||
|
/**
|
||||||
|
* Will call [launchStateHandling] for state handling
|
||||||
|
*/
|
||||||
|
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
|
||||||
|
|
||||||
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
/**
|
||||||
val statePerformer: suspend (State) -> Unit = { state: State ->
|
* This
|
||||||
val newState = launchStateHandling(state, handlers)
|
*/
|
||||||
if (newState != null) {
|
protected val statesJobs = mutableMapOf<T, Job>()
|
||||||
statesManager.update(state, newState)
|
protected val statesJobsMutex = Mutex()
|
||||||
} else {
|
|
||||||
statesManager.endChain(state)
|
protected open suspend fun performUpdate(state: T) {
|
||||||
|
val newState = launchStateHandling(state, handlers)
|
||||||
|
if (newState != null) {
|
||||||
|
statesManager.update(state, newState)
|
||||||
|
} else {
|
||||||
|
statesManager.endChain(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
|
||||||
|
statesJobsMutex.withLock {
|
||||||
|
statesJobs[actualState] ?.cancel()
|
||||||
|
statesJobs[actualState] = scope.launch {
|
||||||
|
performUpdate(actualState)
|
||||||
|
}.also { job ->
|
||||||
|
job.invokeOnCompletion { _ ->
|
||||||
|
scope.launch {
|
||||||
|
statesJobsMutex.withLock {
|
||||||
|
if (statesJobs[actualState] == job) {
|
||||||
|
statesJobs.remove(actualState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch handling of states. On [statesManager] [StatesManager.onStartChain],
|
||||||
|
* [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
|
||||||
|
* [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise
|
||||||
|
* [StatesManager.endChain].
|
||||||
|
*/
|
||||||
|
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||||
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
||||||
launch { statePerformer(it) }
|
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||||
}
|
}
|
||||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||||
launch { statePerformer(it.second) }
|
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
statesManager.getActiveStates().forEach {
|
statesManager.getActiveStates().forEach {
|
||||||
launch { statePerformer(it) }
|
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startChain(state: State) {
|
/**
|
||||||
|
* Just calls [StatesManager.startChain] of [statesManager]
|
||||||
|
*/
|
||||||
|
override suspend fun startChain(state: T) {
|
||||||
statesManager.startChain(state)
|
statesManager.startChain(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,92 +1,30 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
interface StatesManager {
|
interface StatesManager<T : State> {
|
||||||
val onChainStateUpdated: Flow<Pair<State, State>>
|
val onChainStateUpdated: Flow<Pair<T, T>>
|
||||||
val onStartChain: Flow<State>
|
val onStartChain: Flow<T>
|
||||||
val onEndChain: Flow<State>
|
val onEndChain: Flow<T>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must set current set using [State.context]
|
* Must set current set using [State.context]
|
||||||
*/
|
*/
|
||||||
suspend fun update(old: State, new: State)
|
suspend fun update(old: T, new: T)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
|
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
|
||||||
* busy by the other [State]
|
* busy by the other [State]
|
||||||
*/
|
*/
|
||||||
suspend fun startChain(state: State)
|
suspend fun startChain(state: T)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
|
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
|
||||||
* ignored
|
* ignored
|
||||||
*/
|
*/
|
||||||
suspend fun endChain(state: State)
|
suspend fun endChain(state: T)
|
||||||
|
|
||||||
suspend fun getActiveStates(): List<State>
|
suspend fun getActiveStates(): List<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
|
||||||
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
|
||||||
* new state by using [endChain] with that state
|
|
||||||
*/
|
|
||||||
class InMemoryStatesManager(
|
|
||||||
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
|
||||||
) : StatesManager {
|
|
||||||
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
|
|
||||||
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
|
|
||||||
private val _onStartChain = MutableSharedFlow<State>(0)
|
|
||||||
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
|
|
||||||
private val _onEndChain = MutableSharedFlow<State>(0)
|
|
||||||
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
|
|
||||||
|
|
||||||
private val contextsToStates = mutableMapOf<Any, State>()
|
|
||||||
private val mapMutex = Mutex()
|
|
||||||
|
|
||||||
override suspend fun update(old: State, new: State) = mapMutex.withLock {
|
|
||||||
when {
|
|
||||||
contextsToStates[old.context] != old -> return@withLock
|
|
||||||
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
|
|
||||||
contextsToStates[old.context] = new
|
|
||||||
_onChainStateUpdated.emit(old to new)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val stateOnNewOneContext = contextsToStates.getValue(new.context)
|
|
||||||
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
|
||||||
endChainWithoutLock(stateOnNewOneContext)
|
|
||||||
contextsToStates.remove(old.context)
|
|
||||||
contextsToStates[new.context] = new
|
|
||||||
_onChainStateUpdated.emit(old to new)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun startChain(state: State) = mapMutex.withLock {
|
|
||||||
if (!contextsToStates.containsKey(state.context)) {
|
|
||||||
contextsToStates[state.context] = state
|
|
||||||
_onStartChain.emit(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun endChainWithoutLock(state: State) {
|
|
||||||
if (contextsToStates[state.context] == state) {
|
|
||||||
contextsToStates.remove(state.context)
|
|
||||||
_onEndChain.emit(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun endChain(state: State) {
|
|
||||||
mapMutex.withLock {
|
|
||||||
endChainWithoutLock(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -0,0 +1,60 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extender of [StatesMachine] interface declare one new function [updateChain]. Realizations of this interface
|
||||||
|
* must be able to perform update of chain in internal [StatesManager]
|
||||||
|
*/
|
||||||
|
interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
|
||||||
|
/**
|
||||||
|
* Update chain with current state equal to [currentState] with [newState]. Behaviour of this update preforming
|
||||||
|
* in cases when [currentState] does not exist in [StatesManager] must be declared inside of realization of
|
||||||
|
* [StatesManager.update] function
|
||||||
|
*/
|
||||||
|
suspend fun updateChain(currentState: T, newState: T)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class DefaultUpdatableStatesMachine<T : State>(
|
||||||
|
statesManager: StatesManager<T>,
|
||||||
|
handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||||
|
) : DefaultStatesMachine<T>(
|
||||||
|
statesManager,
|
||||||
|
handlers
|
||||||
|
), UpdatableStatesMachine<T> {
|
||||||
|
protected val jobsStates = mutableMapOf<Job, T>()
|
||||||
|
|
||||||
|
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
|
||||||
|
statesJobsMutex.withLock {
|
||||||
|
if (compare(previousState, actualState)) {
|
||||||
|
statesJobs[actualState] ?.cancel()
|
||||||
|
}
|
||||||
|
val job = previousState.mapOnPresented {
|
||||||
|
statesJobs.remove(it)
|
||||||
|
} ?.takeIf { it.isActive } ?: scope.launch {
|
||||||
|
performUpdate(actualState)
|
||||||
|
}.also { job ->
|
||||||
|
job.invokeOnCompletion { _ ->
|
||||||
|
scope.launch {
|
||||||
|
statesJobsMutex.withLock {
|
||||||
|
statesJobs.remove(
|
||||||
|
jobsStates[job] ?: return@withLock
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jobsStates.remove(job)
|
||||||
|
statesJobs[actualState] = job
|
||||||
|
jobsStates[job] = actualState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
|
||||||
|
|
||||||
|
override suspend fun updateChain(currentState: T, newState: T) {
|
||||||
|
statesManager.update(currentState, newState)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,35 +1,61 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common.dsl
|
package dev.inmo.micro_utils.fsm.common.dsl
|
||||||
|
|
||||||
import dev.inmo.micro_utils.fsm.common.*
|
import dev.inmo.micro_utils.fsm.common.*
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.InMemoryDefaultStatesManagerRepo
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class FSMBuilder(
|
class FSMBuilder<T : State>(
|
||||||
var statesManager: StatesManager = InMemoryStatesManager()
|
var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
|
val fsmBuilder: (statesManager: StatesManager<T>, states: List<CheckableHandlerHolder<T, T>>) -> StatesMachine<T> = { statesManager, states ->
|
||||||
|
StatesMachine(
|
||||||
|
statesManager,
|
||||||
|
states
|
||||||
|
)
|
||||||
|
},
|
||||||
|
var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
|
||||||
) {
|
) {
|
||||||
private var states = mutableListOf<StateHandlerHolder<*>>()
|
private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
|
||||||
|
|
||||||
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) {
|
fun add(handler: CheckableHandlerHolder<T, T>) {
|
||||||
states.add(StateHandlerHolder(kClass, false, handler))
|
states.add(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) {
|
fun <I : T> add(kClass: KClass<I>, handler: StatesHandler<I, T>) {
|
||||||
states.add(StateHandlerHolder(kClass, true, handler))
|
add(CheckableHandlerHolder(kClass, false, handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build() = StatesMachine(
|
fun <I : T> add(filter: suspend (state: State) -> Boolean, handler: StatesHandler<I, T>) {
|
||||||
|
add(handler.holder(filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <I : T> addStrict(kClass: KClass<I>, handler: StatesHandler<I, T>) {
|
||||||
|
states.add(CheckableHandlerHolder(kClass, true, handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified I : T> onStateOrSubstate(handler: StatesHandler<I, T>) {
|
||||||
|
add(I::class, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified I : T> strictlyOn(handler: StatesHandler<I, T>) {
|
||||||
|
addStrict(I::class, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified I : T> doWhen(
|
||||||
|
noinline filter: suspend (state: State) -> Boolean,
|
||||||
|
handler: StatesHandler<I, T>
|
||||||
|
) {
|
||||||
|
add(filter, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build() = fsmBuilder(
|
||||||
statesManager,
|
statesManager,
|
||||||
states.toList()
|
states.toList().let { list ->
|
||||||
|
defaultStateHandler ?.let { list + it.holder { true } } ?: list
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) {
|
fun <T : State> buildFSM(
|
||||||
add(I::class, handler)
|
block: FSMBuilder<T>.() -> Unit
|
||||||
}
|
): StatesMachine<T> = FSMBuilder<T>().apply(block).build()
|
||||||
|
|
||||||
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
|
|
||||||
addStrict(I::class, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildFSM(
|
|
||||||
block: FSMBuilder.() -> Unit
|
|
||||||
): StatesMachine = FSMBuilder().apply(block).build()
|
|
||||||
|
@@ -0,0 +1,101 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common.managers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
|
import dev.inmo.micro_utils.fsm.common.StatesManager
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement this repo if you want to use some custom repo for [DefaultStatesManager]
|
||||||
|
*/
|
||||||
|
interface DefaultStatesManagerRepo<T : State> {
|
||||||
|
/**
|
||||||
|
* Must save [state] as current state of chain with [State.context] of [state]
|
||||||
|
*/
|
||||||
|
suspend fun set(state: T)
|
||||||
|
/**
|
||||||
|
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
|
||||||
|
* NOT be removed
|
||||||
|
*/
|
||||||
|
suspend fun removeState(state: T)
|
||||||
|
/**
|
||||||
|
* @return Current list of available and saved states
|
||||||
|
*/
|
||||||
|
suspend fun getStates(): List<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Current state by [context]
|
||||||
|
*/
|
||||||
|
suspend fun getContextState(context: Any): T?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Current state by [context]
|
||||||
|
*/
|
||||||
|
suspend fun contains(context: Any): Boolean = getContextState(context) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE
|
||||||
|
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default will be used
|
||||||
|
* [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter
|
||||||
|
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||||
|
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
||||||
|
* new state by using [endChain] with that state
|
||||||
|
*/
|
||||||
|
class DefaultStatesManager<T : State>(
|
||||||
|
private val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
||||||
|
private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||||
|
) : StatesManager<T> {
|
||||||
|
private val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
||||||
|
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
||||||
|
private val _onStartChain = MutableSharedFlow<T>(0)
|
||||||
|
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
|
||||||
|
private val _onEndChain = MutableSharedFlow<T>(0)
|
||||||
|
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
|
||||||
|
|
||||||
|
private val mapMutex = Mutex()
|
||||||
|
|
||||||
|
override suspend fun update(old: T, new: T) = mapMutex.withLock {
|
||||||
|
val stateByOldContext: T? = repo.getContextState(old.context)
|
||||||
|
when {
|
||||||
|
stateByOldContext != old -> return@withLock
|
||||||
|
stateByOldContext == null || old.context == new.context -> {
|
||||||
|
repo.set(new)
|
||||||
|
_onChainStateUpdated.emit(old to new)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val stateOnNewOneContext = repo.getContextState(new.context)
|
||||||
|
if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
||||||
|
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
|
||||||
|
repo.removeState(old)
|
||||||
|
repo.set(new)
|
||||||
|
_onChainStateUpdated.emit(old to new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun startChain(state: T) = mapMutex.withLock {
|
||||||
|
if (!repo.contains(state.context)) {
|
||||||
|
repo.set(state)
|
||||||
|
_onStartChain.emit(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun endChainWithoutLock(state: T) {
|
||||||
|
if (repo.getContextState(state.context) == state) {
|
||||||
|
repo.removeState(state)
|
||||||
|
_onEndChain.emit(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun endChain(state: T) {
|
||||||
|
mapMutex.withLock {
|
||||||
|
endChainWithoutLock(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getActiveStates(): List<T> = repo.getStates()
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common.managers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive
|
||||||
|
* functionality
|
||||||
|
*/
|
||||||
|
class InMemoryDefaultStatesManagerRepo<T : State>(
|
||||||
|
private val map: MutableMap<Any, T> = mutableMapOf()
|
||||||
|
) : DefaultStatesManagerRepo<T> {
|
||||||
|
override suspend fun set(state: T) {
|
||||||
|
map[state.context] = state
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeState(state: T) {
|
||||||
|
map.remove(state.context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStates(): List<T> = map.values.toList()
|
||||||
|
|
||||||
|
override suspend fun getContextState(context: Any): T? = map[context]
|
||||||
|
|
||||||
|
override suspend fun contains(context: Any): Boolean = map.contains(context)
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common.managers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
|
||||||
|
*
|
||||||
|
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||||
|
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
||||||
|
* new state by using [endChain] with that state
|
||||||
|
*/
|
||||||
|
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
|
||||||
|
fun <T: State> InMemoryStatesManager(
|
||||||
|
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||||
|
) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver)
|
@@ -1,6 +1,7 @@
|
|||||||
import dev.inmo.micro_utils.fsm.common.*
|
import dev.inmo.micro_utils.fsm.common.*
|
||||||
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
|
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
|
||||||
import dev.inmo.micro_utils.fsm.common.dsl.strictlyOn
|
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
sealed interface TrafficLightState : State {
|
sealed interface TrafficLightState : State {
|
||||||
@@ -25,9 +26,9 @@ class PlayableMain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val statesManager = InMemoryStatesManager()
|
val statesManager = DefaultStatesManager<TrafficLightState>()
|
||||||
|
|
||||||
val machine = buildFSM {
|
val machine = buildFSM<TrafficLightState> {
|
||||||
strictlyOn<GreenCommon> {
|
strictlyOn<GreenCommon> {
|
||||||
delay(1000L)
|
delay(1000L)
|
||||||
YellowCommon(it.context).also(::println)
|
YellowCommon(it.context).also(::println)
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.repos.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
|
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||||
|
|
||||||
|
class KeyValueBasedDefaultStatesManagerRepo<T : State>(
|
||||||
|
private val keyValueRepo: KeyValueRepo<Any, T>
|
||||||
|
) : DefaultStatesManagerRepo<T> {
|
||||||
|
override suspend fun set(state: T) {
|
||||||
|
keyValueRepo.set(state.context, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeState(state: T) {
|
||||||
|
if (keyValueRepo.get(state.context) == state) {
|
||||||
|
keyValueRepo.unset(state.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second }
|
||||||
|
override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context)
|
||||||
|
|
||||||
|
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
|
||||||
|
}
|
@@ -1,83 +0,0 @@
|
|||||||
package dev.inmo.micro_utils.fsm.repos.common
|
|
||||||
|
|
||||||
import dev.inmo.micro_utils.fsm.common.State
|
|
||||||
import dev.inmo.micro_utils.fsm.common.StatesManager
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
|
||||||
import dev.inmo.micro_utils.repos.mappers.withMapper
|
|
||||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
class KeyValueBasedStatesManager(
|
|
||||||
private val keyValueRepo: KeyValueRepo<Any, State>,
|
|
||||||
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
|
|
||||||
) : StatesManager {
|
|
||||||
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
|
|
||||||
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
|
|
||||||
private val _onEndChain = MutableSharedFlow<State>(0)
|
|
||||||
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
|
|
||||||
|
|
||||||
override val onStartChain: Flow<State> = keyValueRepo.onNewValue.map { it.second }
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
override suspend fun update(old: State, new: State) {
|
|
||||||
mutex.withLock {
|
|
||||||
when {
|
|
||||||
keyValueRepo.get(old.context) != old -> return@withLock
|
|
||||||
old.context == new.context || !keyValueRepo.contains(new.context) -> {
|
|
||||||
keyValueRepo.set(old.context, new)
|
|
||||||
_onChainStateUpdated.emit(old to new)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val stateOnNewOneContext = keyValueRepo.get(new.context)!!
|
|
||||||
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
|
||||||
endChainWithoutLock(stateOnNewOneContext)
|
|
||||||
keyValueRepo.unset(old.context)
|
|
||||||
keyValueRepo.set(new.context, new)
|
|
||||||
_onChainStateUpdated.emit(old to new)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun startChain(state: State) {
|
|
||||||
if (!keyValueRepo.contains(state.context)) {
|
|
||||||
keyValueRepo.set(state.context, state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun endChainWithoutLock(state: State) {
|
|
||||||
if (keyValueRepo.get(state.context) == state) {
|
|
||||||
keyValueRepo.unset(state.context)
|
|
||||||
_onEndChain.emit(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun endChain(state: State) {
|
|
||||||
mutex.withLock { endChainWithoutLock(state) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getActiveStates(): List<State> {
|
|
||||||
return keyValueRepo.getAll { keys(it) }.map { it.second }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified TargetContextType, reified TargetStateType> createStatesManager(
|
|
||||||
targetKeyValueRepo: KeyValueRepo<TargetContextType, TargetStateType>,
|
|
||||||
noinline contextToOutTransformer: suspend Any.() -> TargetContextType,
|
|
||||||
noinline stateToOutTransformer: suspend State.() -> TargetStateType,
|
|
||||||
noinline outToContextTransformer: suspend TargetContextType.() -> Any,
|
|
||||||
noinline outToStateTransformer: suspend TargetStateType.() -> State,
|
|
||||||
) = KeyValueBasedStatesManager(
|
|
||||||
targetKeyValueRepo.withMapper<Any, State, TargetContextType, TargetStateType>(
|
|
||||||
contextToOutTransformer,
|
|
||||||
stateToOutTransformer,
|
|
||||||
outToContextTransformer,
|
|
||||||
outToStateTransformer
|
|
||||||
)
|
|
||||||
)
|
|
@@ -7,29 +7,29 @@ android.useAndroidX=true
|
|||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
org.gradle.jvmargs=-Xmx2g
|
org.gradle.jvmargs=-Xmx2g
|
||||||
|
|
||||||
kotlin_version=1.5.21
|
kotlin_version=1.6.10
|
||||||
kotlin_coroutines_version=1.5.1
|
kotlin_coroutines_version=1.6.0
|
||||||
kotlin_serialisation_core_version=1.2.2
|
kotlin_serialisation_core_version=1.3.2
|
||||||
kotlin_exposed_version=0.32.1
|
kotlin_exposed_version=0.37.2
|
||||||
|
|
||||||
ktor_version=1.6.2
|
ktor_version=1.6.7
|
||||||
|
|
||||||
klockVersion=2.3.1
|
klockVersion=2.4.12
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
|
||||||
uuidVersion=0.3.0
|
uuidVersion=0.3.1
|
||||||
|
|
||||||
# ANDROID
|
# ANDROID
|
||||||
|
|
||||||
core_ktx_version=1.6.0
|
core_ktx_version=1.7.0
|
||||||
androidx_recycler_version=1.2.1
|
androidx_recycler_version=1.2.1
|
||||||
appcompat_version=1.3.0
|
appcompat_version=1.4.0
|
||||||
|
|
||||||
android_minSdkVersion=19
|
android_minSdkVersion=19
|
||||||
android_compileSdkVersion=30
|
android_compileSdkVersion=32
|
||||||
android_buildToolsVersion=30.0.3
|
android_buildToolsVersion=32.0.0
|
||||||
dexcount_version=2.1.0-RC01
|
dexcount_version=3.0.1
|
||||||
junit_version=4.12
|
junit_version=4.12
|
||||||
test_ext_junit_version=1.1.2
|
test_ext_junit_version=1.1.2
|
||||||
espresso_core=3.3.0
|
espresso_core=3.3.0
|
||||||
@@ -40,10 +40,10 @@ crypto_js_version=4.1.1
|
|||||||
|
|
||||||
# Dokka
|
# Dokka
|
||||||
|
|
||||||
dokka_version=1.4.32
|
dokka_version=1.6.0
|
||||||
|
|
||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.5.17
|
version=0.9.2
|
||||||
android_code_version=58
|
android_code_version=92
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import io.ktor.client.request.forms.InputProvider
|
||||||
|
|
||||||
|
expect suspend fun MPPFile.inputProvider(): InputProvider
|
@@ -1,16 +1,20 @@
|
|||||||
package dev.inmo.micro_utils.ktor.client
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import dev.inmo.micro_utils.common.filename
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.forms.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.utils.io.core.ByteReadPacket
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
||||||
|
|
||||||
class UnifiedRequester(
|
class UnifiedRequester(
|
||||||
private val client: HttpClient = HttpClient(),
|
val client: HttpClient = HttpClient(),
|
||||||
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
) {
|
) {
|
||||||
suspend fun <ResultType> uniget(
|
suspend fun <ResultType> uniget(
|
||||||
url: String,
|
url: String,
|
||||||
@@ -31,6 +35,54 @@ class UnifiedRequester(
|
|||||||
resultDeserializer: DeserializationStrategy<ResultType>
|
resultDeserializer: DeserializationStrategy<ResultType>
|
||||||
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
||||||
|
|
||||||
|
suspend fun <ResultType> unimultipart(
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
inputProvider: InputProvider,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
||||||
|
|
||||||
|
suspend fun <BodyType, ResultType> unimultipart(
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
inputProvider: InputProvider,
|
||||||
|
otherData: BodyPair<BodyType>,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
||||||
|
|
||||||
|
suspend fun <ResultType> unimultipart(
|
||||||
|
url: String,
|
||||||
|
mppFile: MPPFile,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||||
|
): ResultType = client.unimultipart(
|
||||||
|
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun <BodyType, ResultType> unimultipart(
|
||||||
|
url: String,
|
||||||
|
mppFile: MPPFile,
|
||||||
|
otherData: BodyPair<BodyType>,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||||
|
): ResultType = client.unimultipart(
|
||||||
|
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
||||||
|
)
|
||||||
|
|
||||||
fun <T> createStandardWebsocketFlow(
|
fun <T> createStandardWebsocketFlow(
|
||||||
url: String,
|
url: String,
|
||||||
checkReconnection: (Throwable?) -> Boolean = { true },
|
checkReconnection: (Throwable?) -> Boolean = { true },
|
||||||
@@ -69,3 +121,124 @@ suspend fun <BodyType, ResultType> HttpClient.unipost(
|
|||||||
}.let {
|
}.let {
|
||||||
serialFormat.decodeDefault(resultDeserializer, it)
|
serialFormat.decodeDefault(resultDeserializer, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun <ResultType> HttpClient.unimultipart(
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
inputProvider: InputProvider,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
|
): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
|
||||||
|
url,
|
||||||
|
formData = formData {
|
||||||
|
append(
|
||||||
|
"bytes",
|
||||||
|
inputProvider,
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, mimetype)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
|
||||||
|
dataHeadersBuilder()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
additionalParametersBuilder()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
requestBuilder()
|
||||||
|
}.let { serialFormat.decodeDefault(resultDeserializer, it) }
|
||||||
|
|
||||||
|
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
otherData: BodyPair<BodyType>,
|
||||||
|
inputProvider: InputProvider,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
|
): ResultType = unimultipart(
|
||||||
|
url,
|
||||||
|
filename,
|
||||||
|
inputProvider,
|
||||||
|
resultDeserializer,
|
||||||
|
mimetype,
|
||||||
|
additionalParametersBuilder = {
|
||||||
|
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
||||||
|
append(
|
||||||
|
"data",
|
||||||
|
InputProvider(serialized.size.toLong()) {
|
||||||
|
ByteReadPacket(serialized)
|
||||||
|
},
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
||||||
|
dataHeadersBuilder()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
additionalParametersBuilder()
|
||||||
|
},
|
||||||
|
dataHeadersBuilder,
|
||||||
|
requestBuilder,
|
||||||
|
serialFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun <ResultType> HttpClient.unimultipart(
|
||||||
|
url: String,
|
||||||
|
mppFile: MPPFile,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
|
): ResultType = unimultipart(
|
||||||
|
url,
|
||||||
|
mppFile.filename.string,
|
||||||
|
mppFile.inputProvider(),
|
||||||
|
resultDeserializer,
|
||||||
|
mimetype,
|
||||||
|
additionalParametersBuilder,
|
||||||
|
dataHeadersBuilder,
|
||||||
|
requestBuilder,
|
||||||
|
serialFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||||
|
url: String,
|
||||||
|
mppFile: MPPFile,
|
||||||
|
otherData: BodyPair<BodyType>,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
|
): ResultType = unimultipart(
|
||||||
|
url,
|
||||||
|
mppFile,
|
||||||
|
resultDeserializer,
|
||||||
|
mimetype,
|
||||||
|
additionalParametersBuilder = {
|
||||||
|
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
||||||
|
append(
|
||||||
|
"data",
|
||||||
|
InputProvider(serialized.size.toLong()) {
|
||||||
|
ByteReadPacket(serialized)
|
||||||
|
},
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
||||||
|
dataHeadersBuilder()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
additionalParametersBuilder()
|
||||||
|
},
|
||||||
|
dataHeadersBuilder,
|
||||||
|
requestBuilder,
|
||||||
|
serialFormat
|
||||||
|
)
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import io.ktor.client.request.forms.InputProvider
|
||||||
|
import io.ktor.utils.io.core.ByteReadPacket
|
||||||
|
|
||||||
|
actual suspend fun MPPFile.inputProvider(): InputProvider = bytes().let {
|
||||||
|
InputProvider(it.size.toLong()) {
|
||||||
|
ByteReadPacket(it)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import io.ktor.client.request.forms.InputProvider
|
||||||
|
import io.ktor.utils.io.streams.asInput
|
||||||
|
|
||||||
|
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
|
||||||
|
inputStream().asInput()
|
||||||
|
}
|
@@ -10,6 +10,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api internalProject("micro_utils.common")
|
||||||
api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version"
|
api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version"
|
||||||
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,31 @@
|
|||||||
package dev.inmo.micro_utils.ktor.server
|
package dev.inmo.micro_utils.ktor.server
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
import dev.inmo.micro_utils.coroutines.safely
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.content.PartData
|
||||||
|
import io.ktor.http.content.forEachPart
|
||||||
import io.ktor.request.receive
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.request.receiveMultipart
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.response.respondBytes
|
import io.ktor.response.respondBytes
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.util.asStream
|
||||||
|
import io.ktor.util.cio.writeChannel
|
||||||
import io.ktor.util.pipeline.PipelineContext
|
import io.ktor.util.pipeline.PipelineContext
|
||||||
|
import io.ktor.utils.io.core.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
import java.io.File
|
||||||
|
import java.io.File.createTempFile
|
||||||
|
|
||||||
class UnifiedRouter(
|
class UnifiedRouter(
|
||||||
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||||
private val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||||
) {
|
) {
|
||||||
fun <T> Route.includeWebsocketHandling(
|
fun <T> Route.includeWebsocketHandling(
|
||||||
suburl: String,
|
suburl: String,
|
||||||
@@ -104,6 +113,139 @@ suspend fun <T> ApplicationCall.uniload(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun ApplicationCall.uniloadMultipart(
|
||||||
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||||
|
) = safely {
|
||||||
|
val multipartData = receiveMultipart()
|
||||||
|
|
||||||
|
var resultInput: Input? = null
|
||||||
|
|
||||||
|
multipartData.forEachPart {
|
||||||
|
when (it) {
|
||||||
|
is PartData.FormItem -> onFormItem(it)
|
||||||
|
is PartData.FileItem -> {
|
||||||
|
when (it.name) {
|
||||||
|
"bytes" -> resultInput = it.provider()
|
||||||
|
else -> onCustomFileItem(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PartData.BinaryItem -> onBinaryContent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultInput ?: error("Bytes has not been received")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> ApplicationCall.uniloadMultipart(
|
||||||
|
deserializer: DeserializationStrategy<T>,
|
||||||
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||||
|
): Pair<Input, T> {
|
||||||
|
var data: Optional<T>? = null
|
||||||
|
val resultInput = uniloadMultipart(
|
||||||
|
onFormItem,
|
||||||
|
{
|
||||||
|
if (it.name == "data") {
|
||||||
|
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
||||||
|
} else {
|
||||||
|
onCustomFileItem(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBinaryContent
|
||||||
|
)
|
||||||
|
|
||||||
|
val completeData = data ?: error("Data has not been received")
|
||||||
|
return resultInput to (completeData.dataOrNull().let { it as T })
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
||||||
|
deserializer: DeserializationStrategy<T>,
|
||||||
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||||
|
) = safely {
|
||||||
|
val multipartData = receiveMultipart()
|
||||||
|
|
||||||
|
var resultInput: MPPFile? = null
|
||||||
|
var data: Optional<T>? = null
|
||||||
|
|
||||||
|
multipartData.forEachPart {
|
||||||
|
when (it) {
|
||||||
|
is PartData.FormItem -> onFormItem(it)
|
||||||
|
is PartData.FileItem -> {
|
||||||
|
when (it.name) {
|
||||||
|
"bytes" -> {
|
||||||
|
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
||||||
|
resultInput = MPPFile.createTempFile(
|
||||||
|
name.nameWithoutExtension.let {
|
||||||
|
var resultName = it
|
||||||
|
while (resultName.length < 3) {
|
||||||
|
resultName += "_"
|
||||||
|
}
|
||||||
|
resultName
|
||||||
|
},
|
||||||
|
".${name.extension}"
|
||||||
|
).apply {
|
||||||
|
outputStream().use { fileStream ->
|
||||||
|
it.provider().asStream().copyTo(fileStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
||||||
|
else -> onCustomFileItem(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PartData.BinaryItem -> onBinaryContent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val completeData = data ?: error("Data has not been received")
|
||||||
|
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun ApplicationCall.uniloadMultipartFile(
|
||||||
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||||
|
) = safely {
|
||||||
|
val multipartData = receiveMultipart()
|
||||||
|
|
||||||
|
var resultInput: MPPFile? = null
|
||||||
|
|
||||||
|
multipartData.forEachPart {
|
||||||
|
when (it) {
|
||||||
|
is PartData.FormItem -> onFormItem(it)
|
||||||
|
is PartData.FileItem -> {
|
||||||
|
if (it.name == "bytes") {
|
||||||
|
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
||||||
|
resultInput = MPPFile.createTempFile(
|
||||||
|
name.nameWithoutExtension.let {
|
||||||
|
var resultName = it
|
||||||
|
while (resultName.length < 3) {
|
||||||
|
resultName += "_"
|
||||||
|
}
|
||||||
|
resultName
|
||||||
|
},
|
||||||
|
".${name.extension}"
|
||||||
|
).apply {
|
||||||
|
outputStream().use { fileStream ->
|
||||||
|
it.provider().asStream().copyTo(fileStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onCustomFileItem(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PartData.BinaryItem -> onBinaryContent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultInput ?: error("Bytes has not been received")
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun ApplicationCall.getParameterOrSendError(
|
suspend fun ApplicationCall.getParameterOrSendError(
|
||||||
field: String
|
field: String
|
||||||
) = parameters[field].also {
|
) = parameters[field].also {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.ktor.server
|
package dev.inmo.micro_utils.ktor.server
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
@@ -31,3 +32,27 @@ fun createKtorServer(
|
|||||||
port: Int = Random.nextInt(1024, 65535),
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
block: Application.() -> Unit
|
block: Application.() -> Unit
|
||||||
): ApplicationEngine = createKtorServer(CIO, host, port, block)
|
): ApplicationEngine = createKtorServer(CIO, host, port, block)
|
||||||
|
|
||||||
|
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> createKtorServer(
|
||||||
|
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
|
||||||
|
host: String = "localhost",
|
||||||
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
|
configurators: List<KtorApplicationConfigurator>
|
||||||
|
): TEngine = createKtorServer(
|
||||||
|
engine,
|
||||||
|
host,
|
||||||
|
port
|
||||||
|
) {
|
||||||
|
configurators.forEach { it.apply { configure() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create server with [CIO] server engine without starting of it
|
||||||
|
*
|
||||||
|
* @see ApplicationEngine.start
|
||||||
|
*/
|
||||||
|
fun createKtorServer(
|
||||||
|
host: String = "localhost",
|
||||||
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
|
configurators: List<KtorApplicationConfigurator>
|
||||||
|
): ApplicationEngine = createKtorServer(CIO, host, port, configurators)
|
||||||
|
7
language_codes/build.gradle
Normal file
7
language_codes/build.gradle
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationPresetPath"
|
31
language_codes/generator/build.gradle
Normal file
31
language_codes/generator/build.gradle
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "application"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version"
|
||||||
|
|
||||||
|
implementation "io.ktor:ktor-client-core:$ktor_version"
|
||||||
|
implementation "io.ktor:ktor-client-java:$ktor_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
mainClassName="MainKt"
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
214
language_codes/generator/src/main/kotlin/Main.kt
Normal file
214
language_codes/generator/src/main/kotlin/Main.kt
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.File
|
||||||
|
import java.text.Normalizer
|
||||||
|
|
||||||
|
private val json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val baseClassName = "IetfLanguageCode"
|
||||||
|
private const val unknownBaseClassName = "Unknown$baseClassName"
|
||||||
|
private const val baseClassSerializerName = "IetfLanguageCodeSerializer"
|
||||||
|
private const val baseClassSerializerAnnotationName = "@Serializable(${baseClassSerializerName}::class)"
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private data class LanguageCode(
|
||||||
|
@SerialName("alpha2")
|
||||||
|
val tag: String,
|
||||||
|
@SerialName("English")
|
||||||
|
val title: String
|
||||||
|
)
|
||||||
|
|
||||||
|
fun String.adaptAsTitle() = if (first().isDigit()) {
|
||||||
|
"L$this"
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.normalized() = Normalizer.normalize(this, Normalizer.Form.NFD).replace(Regex("[^\\p{ASCII}]"), "")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private data class LanguageCodeWithTag(
|
||||||
|
@SerialName("langType")
|
||||||
|
val tag: String,
|
||||||
|
@SerialName("lang")
|
||||||
|
val withSubtag: String
|
||||||
|
) {
|
||||||
|
val partWithoutTag: String
|
||||||
|
get() {
|
||||||
|
return withSubtag.substring(
|
||||||
|
withSubtag.indexOf("-") + 1, withSubtag.length
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val middleTag
|
||||||
|
get() = if (partWithoutTag.contains("-")) {
|
||||||
|
partWithoutTag.substring(0, partWithoutTag.indexOf("-"))
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val middleTagTitle
|
||||||
|
get() = middleTag ?.adaptAsTitle() ?: partWithoutTag.adaptAsTitle()
|
||||||
|
val subtag: String
|
||||||
|
get() = middleTag ?: partWithoutTag
|
||||||
|
val endTag
|
||||||
|
get() = if (partWithoutTag.contains("-")) {
|
||||||
|
partWithoutTag.substring(partWithoutTag.indexOf("-") + 1, partWithoutTag.length)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val endTagAsTitle
|
||||||
|
get() = endTag ?.adaptAsTitle()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Tag(
|
||||||
|
val title: String,
|
||||||
|
val tag: String,
|
||||||
|
val subtags: List<Tag>
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun printLanguageCodeAndTags(
|
||||||
|
tag: Tag,
|
||||||
|
parent: Tag? = null,
|
||||||
|
indents: String = " "
|
||||||
|
): String = if (tag.subtags.isEmpty()) {
|
||||||
|
"""${indents}${baseClassSerializerAnnotationName}
|
||||||
|
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}" }"""
|
||||||
|
} else {
|
||||||
|
"""
|
||||||
|
${indents}${baseClassSerializerAnnotationName}
|
||||||
|
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
|
||||||
|
${indents} override val code: String = "${tag.tag}"
|
||||||
|
|
||||||
|
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
|
||||||
|
|
||||||
|
${indents} ${baseClassSerializerAnnotationName}
|
||||||
|
${indents} companion object : ${tag.title}()
|
||||||
|
${indents}}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildKtFileContent(tags: List<Tag>): String = """
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class has been automatically generated using
|
||||||
|
* https://github.com/InsanusMokrassar/MicroUtils/tree/master/language_codes/generator . This generator uses
|
||||||
|
* https://datahub.io/core/language-codes/ files (base and tags) and create the whole hierarchy using it.
|
||||||
|
*/
|
||||||
|
${baseClassSerializerAnnotationName}
|
||||||
|
sealed class $baseClassName {
|
||||||
|
abstract val code: String
|
||||||
|
|
||||||
|
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
|
||||||
|
|
||||||
|
$baseClassSerializerAnnotationName
|
||||||
|
data class $unknownBaseClassName (override val code: String) : $baseClassName()
|
||||||
|
|
||||||
|
override fun toString() = code
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
fun createStringConverterCode(tags: List<Tag>): String {
|
||||||
|
fun createDeserializeVariantForTag(
|
||||||
|
tag: Tag,
|
||||||
|
pretitle: String = baseClassName,
|
||||||
|
indents: String = " "
|
||||||
|
): 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 convertTo$baseClassName(code: String) = code.as$baseClassName()
|
||||||
|
fun $baseClassName(code: String) = code.as$baseClassName()
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createSerializerCode(tags: List<Tag>): String {
|
||||||
|
return """import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
object $baseClassSerializerName : KSerializer<$baseClassName> {
|
||||||
|
override val descriptor = String.serializer().descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): $baseClassName {
|
||||||
|
return $baseClassName(decoder.decodeString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: IetfLanguageCode) {
|
||||||
|
encoder.encodeString(value.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun main(vararg args: String) {
|
||||||
|
val outputFolder = args.firstOrNull() ?.let { File(it) }
|
||||||
|
outputFolder ?.mkdirs()
|
||||||
|
val ietfLanguageCodesLink = "https://datahub.io/core/language-codes/r/language-codes.json"
|
||||||
|
val ietfLanguageCodesAdditionalTagsLink = "https://datahub.io/core/language-codes/r/ietf-language-tags.json"
|
||||||
|
|
||||||
|
val client = HttpClient()
|
||||||
|
|
||||||
|
val ietfLanguageCodes = json.decodeFromString(
|
||||||
|
ListSerializer(LanguageCode.serializer()),
|
||||||
|
client.get(ietfLanguageCodesLink)
|
||||||
|
).map {
|
||||||
|
it.copy(
|
||||||
|
title = it.title
|
||||||
|
.replace(Regex("[;,()-]"), "")
|
||||||
|
.split(" ")
|
||||||
|
.joinToString("") { "${it.first().uppercase()}${it.substring(1)}" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val ietfLanguageCodesWithTagsMap = json.decodeFromString(
|
||||||
|
ListSerializer(LanguageCodeWithTag.serializer()),
|
||||||
|
client.get(ietfLanguageCodesAdditionalTagsLink)
|
||||||
|
).filter { it.withSubtag != it.tag }.groupBy { it.tag }
|
||||||
|
|
||||||
|
val tags = ietfLanguageCodes.map {
|
||||||
|
val unformattedSubtags = ietfLanguageCodesWithTagsMap[it.tag] ?: emptyList()
|
||||||
|
val threeLevelTags = unformattedSubtags.filter { it.endTag != null }.groupBy { it.middleTag }
|
||||||
|
val subtags = unformattedSubtags.mapNotNull {
|
||||||
|
if (it.endTag == null) {
|
||||||
|
val currentSubtags = (threeLevelTags[it.subtag] ?: emptyList()).map {
|
||||||
|
Tag(it.endTagAsTitle!!.normalized(), it.withSubtag, emptyList())
|
||||||
|
}
|
||||||
|
Tag(it.middleTagTitle.normalized(), it.withSubtag, currentSubtags)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag(it.title.normalized(), it.tag, subtags)
|
||||||
|
}
|
||||||
|
|
||||||
|
File(outputFolder, "LanguageCodes.kt").apply {
|
||||||
|
delete()
|
||||||
|
createNewFile()
|
||||||
|
writeText(buildKtFileContent(tags))
|
||||||
|
}
|
||||||
|
|
||||||
|
File(outputFolder, "StringToLanguageCodes.kt").apply {
|
||||||
|
delete()
|
||||||
|
createNewFile()
|
||||||
|
writeText(createStringConverterCode(tags))
|
||||||
|
}
|
||||||
|
|
||||||
|
File(outputFolder, "$baseClassSerializerName.kt").apply {
|
||||||
|
delete()
|
||||||
|
createNewFile()
|
||||||
|
writeText(createSerializerCode(tags))
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package dev.inmo.micro_utils.language_codes
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
object IetfLanguageCodeSerializer : KSerializer<IetfLanguageCode> {
|
||||||
|
override val descriptor = String.serializer().descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): IetfLanguageCode {
|
||||||
|
return IetfLanguageCode(decoder.decodeString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: IetfLanguageCode) {
|
||||||
|
encoder.encodeString(value.code)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,671 @@
|
|||||||
|
package dev.inmo.micro_utils.language_codes
|
||||||
|
|
||||||
|
fun String.asIetfLanguageCode(): IetfLanguageCode {
|
||||||
|
return when (this) {
|
||||||
|
IetfLanguageCode.Afar.code -> IetfLanguageCode.Afar
|
||||||
|
IetfLanguageCode.Abkhazian.code -> IetfLanguageCode.Abkhazian
|
||||||
|
IetfLanguageCode.Avestan.code -> IetfLanguageCode.Avestan
|
||||||
|
IetfLanguageCode.Afrikaans.code -> IetfLanguageCode.Afrikaans
|
||||||
|
IetfLanguageCode.Afrikaans.NA.code -> IetfLanguageCode.Afrikaans.NA
|
||||||
|
IetfLanguageCode.Afrikaans.ZA.code -> IetfLanguageCode.Afrikaans.ZA
|
||||||
|
IetfLanguageCode.Akan.code -> IetfLanguageCode.Akan
|
||||||
|
IetfLanguageCode.Akan.GH.code -> IetfLanguageCode.Akan.GH
|
||||||
|
IetfLanguageCode.Amharic.code -> IetfLanguageCode.Amharic
|
||||||
|
IetfLanguageCode.Amharic.ET.code -> IetfLanguageCode.Amharic.ET
|
||||||
|
IetfLanguageCode.Aragonese.code -> IetfLanguageCode.Aragonese
|
||||||
|
IetfLanguageCode.Arabic.code -> IetfLanguageCode.Arabic
|
||||||
|
IetfLanguageCode.Arabic.L001.code -> IetfLanguageCode.Arabic.L001
|
||||||
|
IetfLanguageCode.Arabic.AE.code -> IetfLanguageCode.Arabic.AE
|
||||||
|
IetfLanguageCode.Arabic.BH.code -> IetfLanguageCode.Arabic.BH
|
||||||
|
IetfLanguageCode.Arabic.DJ.code -> IetfLanguageCode.Arabic.DJ
|
||||||
|
IetfLanguageCode.Arabic.DZ.code -> IetfLanguageCode.Arabic.DZ
|
||||||
|
IetfLanguageCode.Arabic.EG.code -> IetfLanguageCode.Arabic.EG
|
||||||
|
IetfLanguageCode.Arabic.EH.code -> IetfLanguageCode.Arabic.EH
|
||||||
|
IetfLanguageCode.Arabic.ER.code -> IetfLanguageCode.Arabic.ER
|
||||||
|
IetfLanguageCode.Arabic.IL.code -> IetfLanguageCode.Arabic.IL
|
||||||
|
IetfLanguageCode.Arabic.IQ.code -> IetfLanguageCode.Arabic.IQ
|
||||||
|
IetfLanguageCode.Arabic.JO.code -> IetfLanguageCode.Arabic.JO
|
||||||
|
IetfLanguageCode.Arabic.KM.code -> IetfLanguageCode.Arabic.KM
|
||||||
|
IetfLanguageCode.Arabic.KW.code -> IetfLanguageCode.Arabic.KW
|
||||||
|
IetfLanguageCode.Arabic.LB.code -> IetfLanguageCode.Arabic.LB
|
||||||
|
IetfLanguageCode.Arabic.LY.code -> IetfLanguageCode.Arabic.LY
|
||||||
|
IetfLanguageCode.Arabic.MA.code -> IetfLanguageCode.Arabic.MA
|
||||||
|
IetfLanguageCode.Arabic.MR.code -> IetfLanguageCode.Arabic.MR
|
||||||
|
IetfLanguageCode.Arabic.OM.code -> IetfLanguageCode.Arabic.OM
|
||||||
|
IetfLanguageCode.Arabic.PS.code -> IetfLanguageCode.Arabic.PS
|
||||||
|
IetfLanguageCode.Arabic.QA.code -> IetfLanguageCode.Arabic.QA
|
||||||
|
IetfLanguageCode.Arabic.SA.code -> IetfLanguageCode.Arabic.SA
|
||||||
|
IetfLanguageCode.Arabic.SD.code -> IetfLanguageCode.Arabic.SD
|
||||||
|
IetfLanguageCode.Arabic.SO.code -> IetfLanguageCode.Arabic.SO
|
||||||
|
IetfLanguageCode.Arabic.SS.code -> IetfLanguageCode.Arabic.SS
|
||||||
|
IetfLanguageCode.Arabic.SY.code -> IetfLanguageCode.Arabic.SY
|
||||||
|
IetfLanguageCode.Arabic.TD.code -> IetfLanguageCode.Arabic.TD
|
||||||
|
IetfLanguageCode.Arabic.TN.code -> IetfLanguageCode.Arabic.TN
|
||||||
|
IetfLanguageCode.Arabic.YE.code -> IetfLanguageCode.Arabic.YE
|
||||||
|
IetfLanguageCode.Assamese.code -> IetfLanguageCode.Assamese
|
||||||
|
IetfLanguageCode.Assamese.IN.code -> IetfLanguageCode.Assamese.IN
|
||||||
|
IetfLanguageCode.Avaric.code -> IetfLanguageCode.Avaric
|
||||||
|
IetfLanguageCode.Aymara.code -> IetfLanguageCode.Aymara
|
||||||
|
IetfLanguageCode.Azerbaijani.code -> IetfLanguageCode.Azerbaijani
|
||||||
|
IetfLanguageCode.Azerbaijani.Cyrl.code -> IetfLanguageCode.Azerbaijani.Cyrl
|
||||||
|
IetfLanguageCode.Azerbaijani.Cyrl.AZ.code -> IetfLanguageCode.Azerbaijani.Cyrl.AZ
|
||||||
|
IetfLanguageCode.Azerbaijani.Latn.code -> IetfLanguageCode.Azerbaijani.Latn
|
||||||
|
IetfLanguageCode.Azerbaijani.Latn.AZ.code -> IetfLanguageCode.Azerbaijani.Latn.AZ
|
||||||
|
IetfLanguageCode.Bashkir.code -> IetfLanguageCode.Bashkir
|
||||||
|
IetfLanguageCode.Belarusian.code -> IetfLanguageCode.Belarusian
|
||||||
|
IetfLanguageCode.Belarusian.BY.code -> IetfLanguageCode.Belarusian.BY
|
||||||
|
IetfLanguageCode.Bulgarian.code -> IetfLanguageCode.Bulgarian
|
||||||
|
IetfLanguageCode.Bulgarian.BG.code -> IetfLanguageCode.Bulgarian.BG
|
||||||
|
IetfLanguageCode.BihariLanguages.code -> IetfLanguageCode.BihariLanguages
|
||||||
|
IetfLanguageCode.Bislama.code -> IetfLanguageCode.Bislama
|
||||||
|
IetfLanguageCode.Bambara.code -> IetfLanguageCode.Bambara
|
||||||
|
IetfLanguageCode.Bambara.ML.code -> IetfLanguageCode.Bambara.ML
|
||||||
|
IetfLanguageCode.Bengali.code -> IetfLanguageCode.Bengali
|
||||||
|
IetfLanguageCode.Bengali.BD.code -> IetfLanguageCode.Bengali.BD
|
||||||
|
IetfLanguageCode.Bengali.IN.code -> IetfLanguageCode.Bengali.IN
|
||||||
|
IetfLanguageCode.Tibetan.code -> IetfLanguageCode.Tibetan
|
||||||
|
IetfLanguageCode.Tibetan.CN.code -> IetfLanguageCode.Tibetan.CN
|
||||||
|
IetfLanguageCode.Tibetan.IN.code -> IetfLanguageCode.Tibetan.IN
|
||||||
|
IetfLanguageCode.Breton.code -> IetfLanguageCode.Breton
|
||||||
|
IetfLanguageCode.Breton.FR.code -> IetfLanguageCode.Breton.FR
|
||||||
|
IetfLanguageCode.Bosnian.code -> IetfLanguageCode.Bosnian
|
||||||
|
IetfLanguageCode.Bosnian.Cyrl.code -> IetfLanguageCode.Bosnian.Cyrl
|
||||||
|
IetfLanguageCode.Bosnian.Cyrl.BA.code -> IetfLanguageCode.Bosnian.Cyrl.BA
|
||||||
|
IetfLanguageCode.Bosnian.Latn.code -> IetfLanguageCode.Bosnian.Latn
|
||||||
|
IetfLanguageCode.Bosnian.Latn.BA.code -> IetfLanguageCode.Bosnian.Latn.BA
|
||||||
|
IetfLanguageCode.CatalanValencian.code -> IetfLanguageCode.CatalanValencian
|
||||||
|
IetfLanguageCode.CatalanValencian.AD.code -> IetfLanguageCode.CatalanValencian.AD
|
||||||
|
IetfLanguageCode.CatalanValencian.ES.code -> IetfLanguageCode.CatalanValencian.ES
|
||||||
|
IetfLanguageCode.CatalanValencian.ES.VALENCIA.code -> IetfLanguageCode.CatalanValencian.ES.VALENCIA
|
||||||
|
IetfLanguageCode.CatalanValencian.FR.code -> IetfLanguageCode.CatalanValencian.FR
|
||||||
|
IetfLanguageCode.CatalanValencian.IT.code -> IetfLanguageCode.CatalanValencian.IT
|
||||||
|
IetfLanguageCode.Chechen.code -> IetfLanguageCode.Chechen
|
||||||
|
IetfLanguageCode.Chechen.RU.code -> IetfLanguageCode.Chechen.RU
|
||||||
|
IetfLanguageCode.Chamorro.code -> IetfLanguageCode.Chamorro
|
||||||
|
IetfLanguageCode.Corsican.code -> IetfLanguageCode.Corsican
|
||||||
|
IetfLanguageCode.Cree.code -> IetfLanguageCode.Cree
|
||||||
|
IetfLanguageCode.Czech.code -> IetfLanguageCode.Czech
|
||||||
|
IetfLanguageCode.Czech.CZ.code -> IetfLanguageCode.Czech.CZ
|
||||||
|
IetfLanguageCode.ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic.code -> IetfLanguageCode.ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic
|
||||||
|
IetfLanguageCode.ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic.RU.code -> IetfLanguageCode.ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic.RU
|
||||||
|
IetfLanguageCode.Chuvash.code -> IetfLanguageCode.Chuvash
|
||||||
|
IetfLanguageCode.Welsh.code -> IetfLanguageCode.Welsh
|
||||||
|
IetfLanguageCode.Welsh.GB.code -> IetfLanguageCode.Welsh.GB
|
||||||
|
IetfLanguageCode.Danish.code -> IetfLanguageCode.Danish
|
||||||
|
IetfLanguageCode.Danish.DK.code -> IetfLanguageCode.Danish.DK
|
||||||
|
IetfLanguageCode.Danish.GL.code -> IetfLanguageCode.Danish.GL
|
||||||
|
IetfLanguageCode.German.code -> IetfLanguageCode.German
|
||||||
|
IetfLanguageCode.German.AT.code -> IetfLanguageCode.German.AT
|
||||||
|
IetfLanguageCode.German.BE.code -> IetfLanguageCode.German.BE
|
||||||
|
IetfLanguageCode.German.CH.code -> IetfLanguageCode.German.CH
|
||||||
|
IetfLanguageCode.German.DE.code -> IetfLanguageCode.German.DE
|
||||||
|
IetfLanguageCode.German.IT.code -> IetfLanguageCode.German.IT
|
||||||
|
IetfLanguageCode.German.LI.code -> IetfLanguageCode.German.LI
|
||||||
|
IetfLanguageCode.German.LU.code -> IetfLanguageCode.German.LU
|
||||||
|
IetfLanguageCode.DivehiDhivehiMaldivian.code -> IetfLanguageCode.DivehiDhivehiMaldivian
|
||||||
|
IetfLanguageCode.Dzongkha.code -> IetfLanguageCode.Dzongkha
|
||||||
|
IetfLanguageCode.Dzongkha.BT.code -> IetfLanguageCode.Dzongkha.BT
|
||||||
|
IetfLanguageCode.Ewe.code -> IetfLanguageCode.Ewe
|
||||||
|
IetfLanguageCode.Ewe.GH.code -> IetfLanguageCode.Ewe.GH
|
||||||
|
IetfLanguageCode.Ewe.TG.code -> IetfLanguageCode.Ewe.TG
|
||||||
|
IetfLanguageCode.GreekModern1453.code -> IetfLanguageCode.GreekModern1453
|
||||||
|
IetfLanguageCode.GreekModern1453.CY.code -> IetfLanguageCode.GreekModern1453.CY
|
||||||
|
IetfLanguageCode.GreekModern1453.GR.code -> IetfLanguageCode.GreekModern1453.GR
|
||||||
|
IetfLanguageCode.English.code -> IetfLanguageCode.English
|
||||||
|
IetfLanguageCode.English.L001.code -> IetfLanguageCode.English.L001
|
||||||
|
IetfLanguageCode.English.L150.code -> IetfLanguageCode.English.L150
|
||||||
|
IetfLanguageCode.English.AE.code -> IetfLanguageCode.English.AE
|
||||||
|
IetfLanguageCode.English.AG.code -> IetfLanguageCode.English.AG
|
||||||
|
IetfLanguageCode.English.AI.code -> IetfLanguageCode.English.AI
|
||||||
|
IetfLanguageCode.English.AS.code -> IetfLanguageCode.English.AS
|
||||||
|
IetfLanguageCode.English.AT.code -> IetfLanguageCode.English.AT
|
||||||
|
IetfLanguageCode.English.AU.code -> IetfLanguageCode.English.AU
|
||||||
|
IetfLanguageCode.English.BB.code -> IetfLanguageCode.English.BB
|
||||||
|
IetfLanguageCode.English.BE.code -> IetfLanguageCode.English.BE
|
||||||
|
IetfLanguageCode.English.BI.code -> IetfLanguageCode.English.BI
|
||||||
|
IetfLanguageCode.English.BM.code -> IetfLanguageCode.English.BM
|
||||||
|
IetfLanguageCode.English.BS.code -> IetfLanguageCode.English.BS
|
||||||
|
IetfLanguageCode.English.BW.code -> IetfLanguageCode.English.BW
|
||||||
|
IetfLanguageCode.English.BZ.code -> IetfLanguageCode.English.BZ
|
||||||
|
IetfLanguageCode.English.CA.code -> IetfLanguageCode.English.CA
|
||||||
|
IetfLanguageCode.English.CC.code -> IetfLanguageCode.English.CC
|
||||||
|
IetfLanguageCode.English.CH.code -> IetfLanguageCode.English.CH
|
||||||
|
IetfLanguageCode.English.CK.code -> IetfLanguageCode.English.CK
|
||||||
|
IetfLanguageCode.English.CM.code -> IetfLanguageCode.English.CM
|
||||||
|
IetfLanguageCode.English.CX.code -> IetfLanguageCode.English.CX
|
||||||
|
IetfLanguageCode.English.CY.code -> IetfLanguageCode.English.CY
|
||||||
|
IetfLanguageCode.English.DE.code -> IetfLanguageCode.English.DE
|
||||||
|
IetfLanguageCode.English.DG.code -> IetfLanguageCode.English.DG
|
||||||
|
IetfLanguageCode.English.DK.code -> IetfLanguageCode.English.DK
|
||||||
|
IetfLanguageCode.English.DM.code -> IetfLanguageCode.English.DM
|
||||||
|
IetfLanguageCode.English.ER.code -> IetfLanguageCode.English.ER
|
||||||
|
IetfLanguageCode.English.FI.code -> IetfLanguageCode.English.FI
|
||||||
|
IetfLanguageCode.English.FJ.code -> IetfLanguageCode.English.FJ
|
||||||
|
IetfLanguageCode.English.FK.code -> IetfLanguageCode.English.FK
|
||||||
|
IetfLanguageCode.English.FM.code -> IetfLanguageCode.English.FM
|
||||||
|
IetfLanguageCode.English.GB.code -> IetfLanguageCode.English.GB
|
||||||
|
IetfLanguageCode.English.GD.code -> IetfLanguageCode.English.GD
|
||||||
|
IetfLanguageCode.English.GG.code -> IetfLanguageCode.English.GG
|
||||||
|
IetfLanguageCode.English.GH.code -> IetfLanguageCode.English.GH
|
||||||
|
IetfLanguageCode.English.GI.code -> IetfLanguageCode.English.GI
|
||||||
|
IetfLanguageCode.English.GM.code -> IetfLanguageCode.English.GM
|
||||||
|
IetfLanguageCode.English.GU.code -> IetfLanguageCode.English.GU
|
||||||
|
IetfLanguageCode.English.GY.code -> IetfLanguageCode.English.GY
|
||||||
|
IetfLanguageCode.English.HK.code -> IetfLanguageCode.English.HK
|
||||||
|
IetfLanguageCode.English.IE.code -> IetfLanguageCode.English.IE
|
||||||
|
IetfLanguageCode.English.IL.code -> IetfLanguageCode.English.IL
|
||||||
|
IetfLanguageCode.English.IM.code -> IetfLanguageCode.English.IM
|
||||||
|
IetfLanguageCode.English.IN.code -> IetfLanguageCode.English.IN
|
||||||
|
IetfLanguageCode.English.IO.code -> IetfLanguageCode.English.IO
|
||||||
|
IetfLanguageCode.English.JE.code -> IetfLanguageCode.English.JE
|
||||||
|
IetfLanguageCode.English.JM.code -> IetfLanguageCode.English.JM
|
||||||
|
IetfLanguageCode.English.KE.code -> IetfLanguageCode.English.KE
|
||||||
|
IetfLanguageCode.English.KI.code -> IetfLanguageCode.English.KI
|
||||||
|
IetfLanguageCode.English.KN.code -> IetfLanguageCode.English.KN
|
||||||
|
IetfLanguageCode.English.KY.code -> IetfLanguageCode.English.KY
|
||||||
|
IetfLanguageCode.English.LC.code -> IetfLanguageCode.English.LC
|
||||||
|
IetfLanguageCode.English.LR.code -> IetfLanguageCode.English.LR
|
||||||
|
IetfLanguageCode.English.LS.code -> IetfLanguageCode.English.LS
|
||||||
|
IetfLanguageCode.English.MG.code -> IetfLanguageCode.English.MG
|
||||||
|
IetfLanguageCode.English.MH.code -> IetfLanguageCode.English.MH
|
||||||
|
IetfLanguageCode.English.MO.code -> IetfLanguageCode.English.MO
|
||||||
|
IetfLanguageCode.English.MP.code -> IetfLanguageCode.English.MP
|
||||||
|
IetfLanguageCode.English.MS.code -> IetfLanguageCode.English.MS
|
||||||
|
IetfLanguageCode.English.MT.code -> IetfLanguageCode.English.MT
|
||||||
|
IetfLanguageCode.English.MU.code -> IetfLanguageCode.English.MU
|
||||||
|
IetfLanguageCode.English.MW.code -> IetfLanguageCode.English.MW
|
||||||
|
IetfLanguageCode.English.MY.code -> IetfLanguageCode.English.MY
|
||||||
|
IetfLanguageCode.English.NA.code -> IetfLanguageCode.English.NA
|
||||||
|
IetfLanguageCode.English.NF.code -> IetfLanguageCode.English.NF
|
||||||
|
IetfLanguageCode.English.NG.code -> IetfLanguageCode.English.NG
|
||||||
|
IetfLanguageCode.English.NL.code -> IetfLanguageCode.English.NL
|
||||||
|
IetfLanguageCode.English.NR.code -> IetfLanguageCode.English.NR
|
||||||
|
IetfLanguageCode.English.NU.code -> IetfLanguageCode.English.NU
|
||||||
|
IetfLanguageCode.English.NZ.code -> IetfLanguageCode.English.NZ
|
||||||
|
IetfLanguageCode.English.PG.code -> IetfLanguageCode.English.PG
|
||||||
|
IetfLanguageCode.English.PH.code -> IetfLanguageCode.English.PH
|
||||||
|
IetfLanguageCode.English.PK.code -> IetfLanguageCode.English.PK
|
||||||
|
IetfLanguageCode.English.PN.code -> IetfLanguageCode.English.PN
|
||||||
|
IetfLanguageCode.English.PR.code -> IetfLanguageCode.English.PR
|
||||||
|
IetfLanguageCode.English.PW.code -> IetfLanguageCode.English.PW
|
||||||
|
IetfLanguageCode.English.RW.code -> IetfLanguageCode.English.RW
|
||||||
|
IetfLanguageCode.English.SB.code -> IetfLanguageCode.English.SB
|
||||||
|
IetfLanguageCode.English.SC.code -> IetfLanguageCode.English.SC
|
||||||
|
IetfLanguageCode.English.SD.code -> IetfLanguageCode.English.SD
|
||||||
|
IetfLanguageCode.English.SE.code -> IetfLanguageCode.English.SE
|
||||||
|
IetfLanguageCode.English.SG.code -> IetfLanguageCode.English.SG
|
||||||
|
IetfLanguageCode.English.SH.code -> IetfLanguageCode.English.SH
|
||||||
|
IetfLanguageCode.English.SI.code -> IetfLanguageCode.English.SI
|
||||||
|
IetfLanguageCode.English.SL.code -> IetfLanguageCode.English.SL
|
||||||
|
IetfLanguageCode.English.SS.code -> IetfLanguageCode.English.SS
|
||||||
|
IetfLanguageCode.English.SX.code -> IetfLanguageCode.English.SX
|
||||||
|
IetfLanguageCode.English.SZ.code -> IetfLanguageCode.English.SZ
|
||||||
|
IetfLanguageCode.English.TC.code -> IetfLanguageCode.English.TC
|
||||||
|
IetfLanguageCode.English.TK.code -> IetfLanguageCode.English.TK
|
||||||
|
IetfLanguageCode.English.TO.code -> IetfLanguageCode.English.TO
|
||||||
|
IetfLanguageCode.English.TT.code -> IetfLanguageCode.English.TT
|
||||||
|
IetfLanguageCode.English.TV.code -> IetfLanguageCode.English.TV
|
||||||
|
IetfLanguageCode.English.TZ.code -> IetfLanguageCode.English.TZ
|
||||||
|
IetfLanguageCode.English.UG.code -> IetfLanguageCode.English.UG
|
||||||
|
IetfLanguageCode.English.UM.code -> IetfLanguageCode.English.UM
|
||||||
|
IetfLanguageCode.English.US.code -> IetfLanguageCode.English.US
|
||||||
|
IetfLanguageCode.English.US.POSIX.code -> IetfLanguageCode.English.US.POSIX
|
||||||
|
IetfLanguageCode.English.VC.code -> IetfLanguageCode.English.VC
|
||||||
|
IetfLanguageCode.English.VG.code -> IetfLanguageCode.English.VG
|
||||||
|
IetfLanguageCode.English.VI.code -> IetfLanguageCode.English.VI
|
||||||
|
IetfLanguageCode.English.VU.code -> IetfLanguageCode.English.VU
|
||||||
|
IetfLanguageCode.English.WS.code -> IetfLanguageCode.English.WS
|
||||||
|
IetfLanguageCode.English.ZA.code -> IetfLanguageCode.English.ZA
|
||||||
|
IetfLanguageCode.English.ZM.code -> IetfLanguageCode.English.ZM
|
||||||
|
IetfLanguageCode.English.ZW.code -> IetfLanguageCode.English.ZW
|
||||||
|
IetfLanguageCode.Esperanto.code -> IetfLanguageCode.Esperanto
|
||||||
|
IetfLanguageCode.Esperanto.L001.code -> IetfLanguageCode.Esperanto.L001
|
||||||
|
IetfLanguageCode.SpanishCastilian.code -> IetfLanguageCode.SpanishCastilian
|
||||||
|
IetfLanguageCode.SpanishCastilian.L419.code -> IetfLanguageCode.SpanishCastilian.L419
|
||||||
|
IetfLanguageCode.SpanishCastilian.AR.code -> IetfLanguageCode.SpanishCastilian.AR
|
||||||
|
IetfLanguageCode.SpanishCastilian.BO.code -> IetfLanguageCode.SpanishCastilian.BO
|
||||||
|
IetfLanguageCode.SpanishCastilian.BR.code -> IetfLanguageCode.SpanishCastilian.BR
|
||||||
|
IetfLanguageCode.SpanishCastilian.BZ.code -> IetfLanguageCode.SpanishCastilian.BZ
|
||||||
|
IetfLanguageCode.SpanishCastilian.CL.code -> IetfLanguageCode.SpanishCastilian.CL
|
||||||
|
IetfLanguageCode.SpanishCastilian.CO.code -> IetfLanguageCode.SpanishCastilian.CO
|
||||||
|
IetfLanguageCode.SpanishCastilian.CR.code -> IetfLanguageCode.SpanishCastilian.CR
|
||||||
|
IetfLanguageCode.SpanishCastilian.CU.code -> IetfLanguageCode.SpanishCastilian.CU
|
||||||
|
IetfLanguageCode.SpanishCastilian.DO.code -> IetfLanguageCode.SpanishCastilian.DO
|
||||||
|
IetfLanguageCode.SpanishCastilian.EA.code -> IetfLanguageCode.SpanishCastilian.EA
|
||||||
|
IetfLanguageCode.SpanishCastilian.EC.code -> IetfLanguageCode.SpanishCastilian.EC
|
||||||
|
IetfLanguageCode.SpanishCastilian.ES.code -> IetfLanguageCode.SpanishCastilian.ES
|
||||||
|
IetfLanguageCode.SpanishCastilian.GQ.code -> IetfLanguageCode.SpanishCastilian.GQ
|
||||||
|
IetfLanguageCode.SpanishCastilian.GT.code -> IetfLanguageCode.SpanishCastilian.GT
|
||||||
|
IetfLanguageCode.SpanishCastilian.HN.code -> IetfLanguageCode.SpanishCastilian.HN
|
||||||
|
IetfLanguageCode.SpanishCastilian.IC.code -> IetfLanguageCode.SpanishCastilian.IC
|
||||||
|
IetfLanguageCode.SpanishCastilian.MX.code -> IetfLanguageCode.SpanishCastilian.MX
|
||||||
|
IetfLanguageCode.SpanishCastilian.NI.code -> IetfLanguageCode.SpanishCastilian.NI
|
||||||
|
IetfLanguageCode.SpanishCastilian.PA.code -> IetfLanguageCode.SpanishCastilian.PA
|
||||||
|
IetfLanguageCode.SpanishCastilian.PE.code -> IetfLanguageCode.SpanishCastilian.PE
|
||||||
|
IetfLanguageCode.SpanishCastilian.PH.code -> IetfLanguageCode.SpanishCastilian.PH
|
||||||
|
IetfLanguageCode.SpanishCastilian.PR.code -> IetfLanguageCode.SpanishCastilian.PR
|
||||||
|
IetfLanguageCode.SpanishCastilian.PY.code -> IetfLanguageCode.SpanishCastilian.PY
|
||||||
|
IetfLanguageCode.SpanishCastilian.SV.code -> IetfLanguageCode.SpanishCastilian.SV
|
||||||
|
IetfLanguageCode.SpanishCastilian.US.code -> IetfLanguageCode.SpanishCastilian.US
|
||||||
|
IetfLanguageCode.SpanishCastilian.UY.code -> IetfLanguageCode.SpanishCastilian.UY
|
||||||
|
IetfLanguageCode.SpanishCastilian.VE.code -> IetfLanguageCode.SpanishCastilian.VE
|
||||||
|
IetfLanguageCode.Estonian.code -> IetfLanguageCode.Estonian
|
||||||
|
IetfLanguageCode.Estonian.EE.code -> IetfLanguageCode.Estonian.EE
|
||||||
|
IetfLanguageCode.Basque.code -> IetfLanguageCode.Basque
|
||||||
|
IetfLanguageCode.Basque.ES.code -> IetfLanguageCode.Basque.ES
|
||||||
|
IetfLanguageCode.Persian.code -> IetfLanguageCode.Persian
|
||||||
|
IetfLanguageCode.Persian.AF.code -> IetfLanguageCode.Persian.AF
|
||||||
|
IetfLanguageCode.Persian.IR.code -> IetfLanguageCode.Persian.IR
|
||||||
|
IetfLanguageCode.Fulah.code -> IetfLanguageCode.Fulah
|
||||||
|
IetfLanguageCode.Fulah.Adlm.code -> IetfLanguageCode.Fulah.Adlm
|
||||||
|
IetfLanguageCode.Fulah.Adlm.BF.code -> IetfLanguageCode.Fulah.Adlm.BF
|
||||||
|
IetfLanguageCode.Fulah.Adlm.CM.code -> IetfLanguageCode.Fulah.Adlm.CM
|
||||||
|
IetfLanguageCode.Fulah.Adlm.GH.code -> IetfLanguageCode.Fulah.Adlm.GH
|
||||||
|
IetfLanguageCode.Fulah.Adlm.GM.code -> IetfLanguageCode.Fulah.Adlm.GM
|
||||||
|
IetfLanguageCode.Fulah.Adlm.GN.code -> IetfLanguageCode.Fulah.Adlm.GN
|
||||||
|
IetfLanguageCode.Fulah.Adlm.GW.code -> IetfLanguageCode.Fulah.Adlm.GW
|
||||||
|
IetfLanguageCode.Fulah.Adlm.LR.code -> IetfLanguageCode.Fulah.Adlm.LR
|
||||||
|
IetfLanguageCode.Fulah.Adlm.MR.code -> IetfLanguageCode.Fulah.Adlm.MR
|
||||||
|
IetfLanguageCode.Fulah.Adlm.NE.code -> IetfLanguageCode.Fulah.Adlm.NE
|
||||||
|
IetfLanguageCode.Fulah.Adlm.NG.code -> IetfLanguageCode.Fulah.Adlm.NG
|
||||||
|
IetfLanguageCode.Fulah.Adlm.SL.code -> IetfLanguageCode.Fulah.Adlm.SL
|
||||||
|
IetfLanguageCode.Fulah.Adlm.SN.code -> IetfLanguageCode.Fulah.Adlm.SN
|
||||||
|
IetfLanguageCode.Fulah.Latn.code -> IetfLanguageCode.Fulah.Latn
|
||||||
|
IetfLanguageCode.Fulah.Latn.BF.code -> IetfLanguageCode.Fulah.Latn.BF
|
||||||
|
IetfLanguageCode.Fulah.Latn.CM.code -> IetfLanguageCode.Fulah.Latn.CM
|
||||||
|
IetfLanguageCode.Fulah.Latn.GH.code -> IetfLanguageCode.Fulah.Latn.GH
|
||||||
|
IetfLanguageCode.Fulah.Latn.GM.code -> IetfLanguageCode.Fulah.Latn.GM
|
||||||
|
IetfLanguageCode.Fulah.Latn.GN.code -> IetfLanguageCode.Fulah.Latn.GN
|
||||||
|
IetfLanguageCode.Fulah.Latn.GW.code -> IetfLanguageCode.Fulah.Latn.GW
|
||||||
|
IetfLanguageCode.Fulah.Latn.LR.code -> IetfLanguageCode.Fulah.Latn.LR
|
||||||
|
IetfLanguageCode.Fulah.Latn.MR.code -> IetfLanguageCode.Fulah.Latn.MR
|
||||||
|
IetfLanguageCode.Fulah.Latn.NE.code -> IetfLanguageCode.Fulah.Latn.NE
|
||||||
|
IetfLanguageCode.Fulah.Latn.NG.code -> IetfLanguageCode.Fulah.Latn.NG
|
||||||
|
IetfLanguageCode.Fulah.Latn.SL.code -> IetfLanguageCode.Fulah.Latn.SL
|
||||||
|
IetfLanguageCode.Fulah.Latn.SN.code -> IetfLanguageCode.Fulah.Latn.SN
|
||||||
|
IetfLanguageCode.Finnish.code -> IetfLanguageCode.Finnish
|
||||||
|
IetfLanguageCode.Finnish.FI.code -> IetfLanguageCode.Finnish.FI
|
||||||
|
IetfLanguageCode.Fijian.code -> IetfLanguageCode.Fijian
|
||||||
|
IetfLanguageCode.Faroese.code -> IetfLanguageCode.Faroese
|
||||||
|
IetfLanguageCode.Faroese.DK.code -> IetfLanguageCode.Faroese.DK
|
||||||
|
IetfLanguageCode.Faroese.FO.code -> IetfLanguageCode.Faroese.FO
|
||||||
|
IetfLanguageCode.French.code -> IetfLanguageCode.French
|
||||||
|
IetfLanguageCode.French.BE.code -> IetfLanguageCode.French.BE
|
||||||
|
IetfLanguageCode.French.BF.code -> IetfLanguageCode.French.BF
|
||||||
|
IetfLanguageCode.French.BI.code -> IetfLanguageCode.French.BI
|
||||||
|
IetfLanguageCode.French.BJ.code -> IetfLanguageCode.French.BJ
|
||||||
|
IetfLanguageCode.French.BL.code -> IetfLanguageCode.French.BL
|
||||||
|
IetfLanguageCode.French.CA.code -> IetfLanguageCode.French.CA
|
||||||
|
IetfLanguageCode.French.CD.code -> IetfLanguageCode.French.CD
|
||||||
|
IetfLanguageCode.French.CF.code -> IetfLanguageCode.French.CF
|
||||||
|
IetfLanguageCode.French.CG.code -> IetfLanguageCode.French.CG
|
||||||
|
IetfLanguageCode.French.CH.code -> IetfLanguageCode.French.CH
|
||||||
|
IetfLanguageCode.French.CI.code -> IetfLanguageCode.French.CI
|
||||||
|
IetfLanguageCode.French.CM.code -> IetfLanguageCode.French.CM
|
||||||
|
IetfLanguageCode.French.DJ.code -> IetfLanguageCode.French.DJ
|
||||||
|
IetfLanguageCode.French.DZ.code -> IetfLanguageCode.French.DZ
|
||||||
|
IetfLanguageCode.French.FR.code -> IetfLanguageCode.French.FR
|
||||||
|
IetfLanguageCode.French.GA.code -> IetfLanguageCode.French.GA
|
||||||
|
IetfLanguageCode.French.GF.code -> IetfLanguageCode.French.GF
|
||||||
|
IetfLanguageCode.French.GN.code -> IetfLanguageCode.French.GN
|
||||||
|
IetfLanguageCode.French.GP.code -> IetfLanguageCode.French.GP
|
||||||
|
IetfLanguageCode.French.GQ.code -> IetfLanguageCode.French.GQ
|
||||||
|
IetfLanguageCode.French.HT.code -> IetfLanguageCode.French.HT
|
||||||
|
IetfLanguageCode.French.KM.code -> IetfLanguageCode.French.KM
|
||||||
|
IetfLanguageCode.French.LU.code -> IetfLanguageCode.French.LU
|
||||||
|
IetfLanguageCode.French.MA.code -> IetfLanguageCode.French.MA
|
||||||
|
IetfLanguageCode.French.MC.code -> IetfLanguageCode.French.MC
|
||||||
|
IetfLanguageCode.French.MF.code -> IetfLanguageCode.French.MF
|
||||||
|
IetfLanguageCode.French.MG.code -> IetfLanguageCode.French.MG
|
||||||
|
IetfLanguageCode.French.ML.code -> IetfLanguageCode.French.ML
|
||||||
|
IetfLanguageCode.French.MQ.code -> IetfLanguageCode.French.MQ
|
||||||
|
IetfLanguageCode.French.MR.code -> IetfLanguageCode.French.MR
|
||||||
|
IetfLanguageCode.French.MU.code -> IetfLanguageCode.French.MU
|
||||||
|
IetfLanguageCode.French.NC.code -> IetfLanguageCode.French.NC
|
||||||
|
IetfLanguageCode.French.NE.code -> IetfLanguageCode.French.NE
|
||||||
|
IetfLanguageCode.French.PF.code -> IetfLanguageCode.French.PF
|
||||||
|
IetfLanguageCode.French.PM.code -> IetfLanguageCode.French.PM
|
||||||
|
IetfLanguageCode.French.RE.code -> IetfLanguageCode.French.RE
|
||||||
|
IetfLanguageCode.French.RW.code -> IetfLanguageCode.French.RW
|
||||||
|
IetfLanguageCode.French.SC.code -> IetfLanguageCode.French.SC
|
||||||
|
IetfLanguageCode.French.SN.code -> IetfLanguageCode.French.SN
|
||||||
|
IetfLanguageCode.French.SY.code -> IetfLanguageCode.French.SY
|
||||||
|
IetfLanguageCode.French.TD.code -> IetfLanguageCode.French.TD
|
||||||
|
IetfLanguageCode.French.TG.code -> IetfLanguageCode.French.TG
|
||||||
|
IetfLanguageCode.French.TN.code -> IetfLanguageCode.French.TN
|
||||||
|
IetfLanguageCode.French.VU.code -> IetfLanguageCode.French.VU
|
||||||
|
IetfLanguageCode.French.WF.code -> IetfLanguageCode.French.WF
|
||||||
|
IetfLanguageCode.French.YT.code -> IetfLanguageCode.French.YT
|
||||||
|
IetfLanguageCode.WesternFrisian.code -> IetfLanguageCode.WesternFrisian
|
||||||
|
IetfLanguageCode.WesternFrisian.NL.code -> IetfLanguageCode.WesternFrisian.NL
|
||||||
|
IetfLanguageCode.Irish.code -> IetfLanguageCode.Irish
|
||||||
|
IetfLanguageCode.Irish.GB.code -> IetfLanguageCode.Irish.GB
|
||||||
|
IetfLanguageCode.Irish.IE.code -> IetfLanguageCode.Irish.IE
|
||||||
|
IetfLanguageCode.GaelicScottishGaelic.code -> IetfLanguageCode.GaelicScottishGaelic
|
||||||
|
IetfLanguageCode.GaelicScottishGaelic.GB.code -> IetfLanguageCode.GaelicScottishGaelic.GB
|
||||||
|
IetfLanguageCode.Galician.code -> IetfLanguageCode.Galician
|
||||||
|
IetfLanguageCode.Galician.ES.code -> IetfLanguageCode.Galician.ES
|
||||||
|
IetfLanguageCode.Guarani.code -> IetfLanguageCode.Guarani
|
||||||
|
IetfLanguageCode.Gujarati.code -> IetfLanguageCode.Gujarati
|
||||||
|
IetfLanguageCode.Gujarati.IN.code -> IetfLanguageCode.Gujarati.IN
|
||||||
|
IetfLanguageCode.Manx.code -> IetfLanguageCode.Manx
|
||||||
|
IetfLanguageCode.Manx.IM.code -> IetfLanguageCode.Manx.IM
|
||||||
|
IetfLanguageCode.Hausa.code -> IetfLanguageCode.Hausa
|
||||||
|
IetfLanguageCode.Hausa.GH.code -> IetfLanguageCode.Hausa.GH
|
||||||
|
IetfLanguageCode.Hausa.NE.code -> IetfLanguageCode.Hausa.NE
|
||||||
|
IetfLanguageCode.Hausa.NG.code -> IetfLanguageCode.Hausa.NG
|
||||||
|
IetfLanguageCode.Hebrew.code -> IetfLanguageCode.Hebrew
|
||||||
|
IetfLanguageCode.Hebrew.IL.code -> IetfLanguageCode.Hebrew.IL
|
||||||
|
IetfLanguageCode.Hindi.code -> IetfLanguageCode.Hindi
|
||||||
|
IetfLanguageCode.Hindi.IN.code -> IetfLanguageCode.Hindi.IN
|
||||||
|
IetfLanguageCode.HiriMotu.code -> IetfLanguageCode.HiriMotu
|
||||||
|
IetfLanguageCode.Croatian.code -> IetfLanguageCode.Croatian
|
||||||
|
IetfLanguageCode.Croatian.BA.code -> IetfLanguageCode.Croatian.BA
|
||||||
|
IetfLanguageCode.Croatian.HR.code -> IetfLanguageCode.Croatian.HR
|
||||||
|
IetfLanguageCode.HaitianHaitianCreole.code -> IetfLanguageCode.HaitianHaitianCreole
|
||||||
|
IetfLanguageCode.Hungarian.code -> IetfLanguageCode.Hungarian
|
||||||
|
IetfLanguageCode.Hungarian.HU.code -> IetfLanguageCode.Hungarian.HU
|
||||||
|
IetfLanguageCode.Armenian.code -> IetfLanguageCode.Armenian
|
||||||
|
IetfLanguageCode.Armenian.AM.code -> IetfLanguageCode.Armenian.AM
|
||||||
|
IetfLanguageCode.Herero.code -> IetfLanguageCode.Herero
|
||||||
|
IetfLanguageCode.InterlinguaInternationalAuxiliaryLanguageAssociation.code -> IetfLanguageCode.InterlinguaInternationalAuxiliaryLanguageAssociation
|
||||||
|
IetfLanguageCode.InterlinguaInternationalAuxiliaryLanguageAssociation.L001.code -> IetfLanguageCode.InterlinguaInternationalAuxiliaryLanguageAssociation.L001
|
||||||
|
IetfLanguageCode.Indonesian.code -> IetfLanguageCode.Indonesian
|
||||||
|
IetfLanguageCode.Indonesian.ID.code -> IetfLanguageCode.Indonesian.ID
|
||||||
|
IetfLanguageCode.InterlingueOccidental.code -> IetfLanguageCode.InterlingueOccidental
|
||||||
|
IetfLanguageCode.Igbo.code -> IetfLanguageCode.Igbo
|
||||||
|
IetfLanguageCode.Igbo.NG.code -> IetfLanguageCode.Igbo.NG
|
||||||
|
IetfLanguageCode.SichuanYiNuosu.code -> IetfLanguageCode.SichuanYiNuosu
|
||||||
|
IetfLanguageCode.SichuanYiNuosu.CN.code -> IetfLanguageCode.SichuanYiNuosu.CN
|
||||||
|
IetfLanguageCode.Inupiaq.code -> IetfLanguageCode.Inupiaq
|
||||||
|
IetfLanguageCode.Ido.code -> IetfLanguageCode.Ido
|
||||||
|
IetfLanguageCode.Icelandic.code -> IetfLanguageCode.Icelandic
|
||||||
|
IetfLanguageCode.Icelandic.IS.code -> IetfLanguageCode.Icelandic.IS
|
||||||
|
IetfLanguageCode.Italian.code -> IetfLanguageCode.Italian
|
||||||
|
IetfLanguageCode.Italian.CH.code -> IetfLanguageCode.Italian.CH
|
||||||
|
IetfLanguageCode.Italian.IT.code -> IetfLanguageCode.Italian.IT
|
||||||
|
IetfLanguageCode.Italian.SM.code -> IetfLanguageCode.Italian.SM
|
||||||
|
IetfLanguageCode.Italian.VA.code -> IetfLanguageCode.Italian.VA
|
||||||
|
IetfLanguageCode.Inuktitut.code -> IetfLanguageCode.Inuktitut
|
||||||
|
IetfLanguageCode.Japanese.code -> IetfLanguageCode.Japanese
|
||||||
|
IetfLanguageCode.Japanese.JP.code -> IetfLanguageCode.Japanese.JP
|
||||||
|
IetfLanguageCode.Javanese.code -> IetfLanguageCode.Javanese
|
||||||
|
IetfLanguageCode.Javanese.ID.code -> IetfLanguageCode.Javanese.ID
|
||||||
|
IetfLanguageCode.Georgian.code -> IetfLanguageCode.Georgian
|
||||||
|
IetfLanguageCode.Georgian.GE.code -> IetfLanguageCode.Georgian.GE
|
||||||
|
IetfLanguageCode.Kongo.code -> IetfLanguageCode.Kongo
|
||||||
|
IetfLanguageCode.KikuyuGikuyu.code -> IetfLanguageCode.KikuyuGikuyu
|
||||||
|
IetfLanguageCode.KikuyuGikuyu.KE.code -> IetfLanguageCode.KikuyuGikuyu.KE
|
||||||
|
IetfLanguageCode.KuanyamaKwanyama.code -> IetfLanguageCode.KuanyamaKwanyama
|
||||||
|
IetfLanguageCode.Kazakh.code -> IetfLanguageCode.Kazakh
|
||||||
|
IetfLanguageCode.Kazakh.KZ.code -> IetfLanguageCode.Kazakh.KZ
|
||||||
|
IetfLanguageCode.KalaallisutGreenlandic.code -> IetfLanguageCode.KalaallisutGreenlandic
|
||||||
|
IetfLanguageCode.KalaallisutGreenlandic.GL.code -> IetfLanguageCode.KalaallisutGreenlandic.GL
|
||||||
|
IetfLanguageCode.CentralKhmer.code -> IetfLanguageCode.CentralKhmer
|
||||||
|
IetfLanguageCode.CentralKhmer.KH.code -> IetfLanguageCode.CentralKhmer.KH
|
||||||
|
IetfLanguageCode.Kannada.code -> IetfLanguageCode.Kannada
|
||||||
|
IetfLanguageCode.Kannada.IN.code -> IetfLanguageCode.Kannada.IN
|
||||||
|
IetfLanguageCode.Korean.code -> IetfLanguageCode.Korean
|
||||||
|
IetfLanguageCode.Korean.KP.code -> IetfLanguageCode.Korean.KP
|
||||||
|
IetfLanguageCode.Korean.KR.code -> IetfLanguageCode.Korean.KR
|
||||||
|
IetfLanguageCode.Kanuri.code -> IetfLanguageCode.Kanuri
|
||||||
|
IetfLanguageCode.Kashmiri.code -> IetfLanguageCode.Kashmiri
|
||||||
|
IetfLanguageCode.Kashmiri.Arab.code -> IetfLanguageCode.Kashmiri.Arab
|
||||||
|
IetfLanguageCode.Kashmiri.Arab.IN.code -> IetfLanguageCode.Kashmiri.Arab.IN
|
||||||
|
IetfLanguageCode.Kurdish.code -> IetfLanguageCode.Kurdish
|
||||||
|
IetfLanguageCode.Kurdish.TR.code -> IetfLanguageCode.Kurdish.TR
|
||||||
|
IetfLanguageCode.Komi.code -> IetfLanguageCode.Komi
|
||||||
|
IetfLanguageCode.Cornish.code -> IetfLanguageCode.Cornish
|
||||||
|
IetfLanguageCode.Cornish.GB.code -> IetfLanguageCode.Cornish.GB
|
||||||
|
IetfLanguageCode.KirghizKyrgyz.code -> IetfLanguageCode.KirghizKyrgyz
|
||||||
|
IetfLanguageCode.KirghizKyrgyz.KG.code -> IetfLanguageCode.KirghizKyrgyz.KG
|
||||||
|
IetfLanguageCode.Latin.code -> IetfLanguageCode.Latin
|
||||||
|
IetfLanguageCode.LuxembourgishLetzeburgesch.code -> IetfLanguageCode.LuxembourgishLetzeburgesch
|
||||||
|
IetfLanguageCode.LuxembourgishLetzeburgesch.LU.code -> IetfLanguageCode.LuxembourgishLetzeburgesch.LU
|
||||||
|
IetfLanguageCode.Ganda.code -> IetfLanguageCode.Ganda
|
||||||
|
IetfLanguageCode.Ganda.UG.code -> IetfLanguageCode.Ganda.UG
|
||||||
|
IetfLanguageCode.LimburganLimburgerLimburgish.code -> IetfLanguageCode.LimburganLimburgerLimburgish
|
||||||
|
IetfLanguageCode.Lingala.code -> IetfLanguageCode.Lingala
|
||||||
|
IetfLanguageCode.Lingala.AO.code -> IetfLanguageCode.Lingala.AO
|
||||||
|
IetfLanguageCode.Lingala.CD.code -> IetfLanguageCode.Lingala.CD
|
||||||
|
IetfLanguageCode.Lingala.CF.code -> IetfLanguageCode.Lingala.CF
|
||||||
|
IetfLanguageCode.Lingala.CG.code -> IetfLanguageCode.Lingala.CG
|
||||||
|
IetfLanguageCode.Lao.code -> IetfLanguageCode.Lao
|
||||||
|
IetfLanguageCode.Lao.LA.code -> IetfLanguageCode.Lao.LA
|
||||||
|
IetfLanguageCode.Lithuanian.code -> IetfLanguageCode.Lithuanian
|
||||||
|
IetfLanguageCode.Lithuanian.LT.code -> IetfLanguageCode.Lithuanian.LT
|
||||||
|
IetfLanguageCode.LubaKatanga.code -> IetfLanguageCode.LubaKatanga
|
||||||
|
IetfLanguageCode.LubaKatanga.CD.code -> IetfLanguageCode.LubaKatanga.CD
|
||||||
|
IetfLanguageCode.Latvian.code -> IetfLanguageCode.Latvian
|
||||||
|
IetfLanguageCode.Latvian.LV.code -> IetfLanguageCode.Latvian.LV
|
||||||
|
IetfLanguageCode.Malagasy.code -> IetfLanguageCode.Malagasy
|
||||||
|
IetfLanguageCode.Malagasy.MG.code -> IetfLanguageCode.Malagasy.MG
|
||||||
|
IetfLanguageCode.Marshallese.code -> IetfLanguageCode.Marshallese
|
||||||
|
IetfLanguageCode.Maori.code -> IetfLanguageCode.Maori
|
||||||
|
IetfLanguageCode.Maori.NZ.code -> IetfLanguageCode.Maori.NZ
|
||||||
|
IetfLanguageCode.Macedonian.code -> IetfLanguageCode.Macedonian
|
||||||
|
IetfLanguageCode.Macedonian.MK.code -> IetfLanguageCode.Macedonian.MK
|
||||||
|
IetfLanguageCode.Malayalam.code -> IetfLanguageCode.Malayalam
|
||||||
|
IetfLanguageCode.Malayalam.IN.code -> IetfLanguageCode.Malayalam.IN
|
||||||
|
IetfLanguageCode.Mongolian.code -> IetfLanguageCode.Mongolian
|
||||||
|
IetfLanguageCode.Mongolian.MN.code -> IetfLanguageCode.Mongolian.MN
|
||||||
|
IetfLanguageCode.Marathi.code -> IetfLanguageCode.Marathi
|
||||||
|
IetfLanguageCode.Marathi.IN.code -> IetfLanguageCode.Marathi.IN
|
||||||
|
IetfLanguageCode.Malay.code -> IetfLanguageCode.Malay
|
||||||
|
IetfLanguageCode.Malay.BN.code -> IetfLanguageCode.Malay.BN
|
||||||
|
IetfLanguageCode.Malay.ID.code -> IetfLanguageCode.Malay.ID
|
||||||
|
IetfLanguageCode.Malay.MY.code -> IetfLanguageCode.Malay.MY
|
||||||
|
IetfLanguageCode.Malay.SG.code -> IetfLanguageCode.Malay.SG
|
||||||
|
IetfLanguageCode.Maltese.code -> IetfLanguageCode.Maltese
|
||||||
|
IetfLanguageCode.Maltese.MT.code -> IetfLanguageCode.Maltese.MT
|
||||||
|
IetfLanguageCode.Burmese.code -> IetfLanguageCode.Burmese
|
||||||
|
IetfLanguageCode.Burmese.MM.code -> IetfLanguageCode.Burmese.MM
|
||||||
|
IetfLanguageCode.Nauru.code -> IetfLanguageCode.Nauru
|
||||||
|
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal
|
||||||
|
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.NO.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal.NO
|
||||||
|
IetfLanguageCode.BokmalNorwegianNorwegianBokmal.SJ.code -> IetfLanguageCode.BokmalNorwegianNorwegianBokmal.SJ
|
||||||
|
IetfLanguageCode.NdebeleNorthNorthNdebele.code -> IetfLanguageCode.NdebeleNorthNorthNdebele
|
||||||
|
IetfLanguageCode.NdebeleNorthNorthNdebele.ZW.code -> IetfLanguageCode.NdebeleNorthNorthNdebele.ZW
|
||||||
|
IetfLanguageCode.Nepali.code -> IetfLanguageCode.Nepali
|
||||||
|
IetfLanguageCode.Nepali.IN.code -> IetfLanguageCode.Nepali.IN
|
||||||
|
IetfLanguageCode.Nepali.NP.code -> IetfLanguageCode.Nepali.NP
|
||||||
|
IetfLanguageCode.Ndonga.code -> IetfLanguageCode.Ndonga
|
||||||
|
IetfLanguageCode.DutchFlemish.code -> IetfLanguageCode.DutchFlemish
|
||||||
|
IetfLanguageCode.DutchFlemish.AW.code -> IetfLanguageCode.DutchFlemish.AW
|
||||||
|
IetfLanguageCode.DutchFlemish.BE.code -> IetfLanguageCode.DutchFlemish.BE
|
||||||
|
IetfLanguageCode.DutchFlemish.BQ.code -> IetfLanguageCode.DutchFlemish.BQ
|
||||||
|
IetfLanguageCode.DutchFlemish.CW.code -> IetfLanguageCode.DutchFlemish.CW
|
||||||
|
IetfLanguageCode.DutchFlemish.NL.code -> IetfLanguageCode.DutchFlemish.NL
|
||||||
|
IetfLanguageCode.DutchFlemish.SR.code -> IetfLanguageCode.DutchFlemish.SR
|
||||||
|
IetfLanguageCode.DutchFlemish.SX.code -> IetfLanguageCode.DutchFlemish.SX
|
||||||
|
IetfLanguageCode.NorwegianNynorskNynorskNorwegian.code -> IetfLanguageCode.NorwegianNynorskNynorskNorwegian
|
||||||
|
IetfLanguageCode.NorwegianNynorskNynorskNorwegian.NO.code -> IetfLanguageCode.NorwegianNynorskNynorskNorwegian.NO
|
||||||
|
IetfLanguageCode.Norwegian.code -> IetfLanguageCode.Norwegian
|
||||||
|
IetfLanguageCode.NdebeleSouthSouthNdebele.code -> IetfLanguageCode.NdebeleSouthSouthNdebele
|
||||||
|
IetfLanguageCode.NavajoNavaho.code -> IetfLanguageCode.NavajoNavaho
|
||||||
|
IetfLanguageCode.ChichewaChewaNyanja.code -> IetfLanguageCode.ChichewaChewaNyanja
|
||||||
|
IetfLanguageCode.OccitanPost1500.code -> IetfLanguageCode.OccitanPost1500
|
||||||
|
IetfLanguageCode.Ojibwa.code -> IetfLanguageCode.Ojibwa
|
||||||
|
IetfLanguageCode.Oromo.code -> IetfLanguageCode.Oromo
|
||||||
|
IetfLanguageCode.Oromo.ET.code -> IetfLanguageCode.Oromo.ET
|
||||||
|
IetfLanguageCode.Oromo.KE.code -> IetfLanguageCode.Oromo.KE
|
||||||
|
IetfLanguageCode.Oriya.code -> IetfLanguageCode.Oriya
|
||||||
|
IetfLanguageCode.Oriya.IN.code -> IetfLanguageCode.Oriya.IN
|
||||||
|
IetfLanguageCode.OssetianOssetic.code -> IetfLanguageCode.OssetianOssetic
|
||||||
|
IetfLanguageCode.OssetianOssetic.GE.code -> IetfLanguageCode.OssetianOssetic.GE
|
||||||
|
IetfLanguageCode.OssetianOssetic.RU.code -> IetfLanguageCode.OssetianOssetic.RU
|
||||||
|
IetfLanguageCode.PanjabiPunjabi.code -> IetfLanguageCode.PanjabiPunjabi
|
||||||
|
IetfLanguageCode.PanjabiPunjabi.Arab.code -> IetfLanguageCode.PanjabiPunjabi.Arab
|
||||||
|
IetfLanguageCode.PanjabiPunjabi.Arab.PK.code -> IetfLanguageCode.PanjabiPunjabi.Arab.PK
|
||||||
|
IetfLanguageCode.PanjabiPunjabi.Guru.code -> IetfLanguageCode.PanjabiPunjabi.Guru
|
||||||
|
IetfLanguageCode.PanjabiPunjabi.Guru.IN.code -> IetfLanguageCode.PanjabiPunjabi.Guru.IN
|
||||||
|
IetfLanguageCode.Pali.code -> IetfLanguageCode.Pali
|
||||||
|
IetfLanguageCode.Polish.code -> IetfLanguageCode.Polish
|
||||||
|
IetfLanguageCode.Polish.PL.code -> IetfLanguageCode.Polish.PL
|
||||||
|
IetfLanguageCode.PushtoPashto.code -> IetfLanguageCode.PushtoPashto
|
||||||
|
IetfLanguageCode.PushtoPashto.AF.code -> IetfLanguageCode.PushtoPashto.AF
|
||||||
|
IetfLanguageCode.PushtoPashto.PK.code -> IetfLanguageCode.PushtoPashto.PK
|
||||||
|
IetfLanguageCode.Portuguese.code -> IetfLanguageCode.Portuguese
|
||||||
|
IetfLanguageCode.Portuguese.AO.code -> IetfLanguageCode.Portuguese.AO
|
||||||
|
IetfLanguageCode.Portuguese.BR.code -> IetfLanguageCode.Portuguese.BR
|
||||||
|
IetfLanguageCode.Portuguese.CH.code -> IetfLanguageCode.Portuguese.CH
|
||||||
|
IetfLanguageCode.Portuguese.CV.code -> IetfLanguageCode.Portuguese.CV
|
||||||
|
IetfLanguageCode.Portuguese.GQ.code -> IetfLanguageCode.Portuguese.GQ
|
||||||
|
IetfLanguageCode.Portuguese.GW.code -> IetfLanguageCode.Portuguese.GW
|
||||||
|
IetfLanguageCode.Portuguese.LU.code -> IetfLanguageCode.Portuguese.LU
|
||||||
|
IetfLanguageCode.Portuguese.MO.code -> IetfLanguageCode.Portuguese.MO
|
||||||
|
IetfLanguageCode.Portuguese.MZ.code -> IetfLanguageCode.Portuguese.MZ
|
||||||
|
IetfLanguageCode.Portuguese.PT.code -> IetfLanguageCode.Portuguese.PT
|
||||||
|
IetfLanguageCode.Portuguese.ST.code -> IetfLanguageCode.Portuguese.ST
|
||||||
|
IetfLanguageCode.Portuguese.TL.code -> IetfLanguageCode.Portuguese.TL
|
||||||
|
IetfLanguageCode.Quechua.code -> IetfLanguageCode.Quechua
|
||||||
|
IetfLanguageCode.Quechua.BO.code -> IetfLanguageCode.Quechua.BO
|
||||||
|
IetfLanguageCode.Quechua.EC.code -> IetfLanguageCode.Quechua.EC
|
||||||
|
IetfLanguageCode.Quechua.PE.code -> IetfLanguageCode.Quechua.PE
|
||||||
|
IetfLanguageCode.Romansh.code -> IetfLanguageCode.Romansh
|
||||||
|
IetfLanguageCode.Romansh.CH.code -> IetfLanguageCode.Romansh.CH
|
||||||
|
IetfLanguageCode.Rundi.code -> IetfLanguageCode.Rundi
|
||||||
|
IetfLanguageCode.Rundi.BI.code -> IetfLanguageCode.Rundi.BI
|
||||||
|
IetfLanguageCode.RomanianMoldavianMoldovan.code -> IetfLanguageCode.RomanianMoldavianMoldovan
|
||||||
|
IetfLanguageCode.RomanianMoldavianMoldovan.MD.code -> IetfLanguageCode.RomanianMoldavianMoldovan.MD
|
||||||
|
IetfLanguageCode.RomanianMoldavianMoldovan.RO.code -> IetfLanguageCode.RomanianMoldavianMoldovan.RO
|
||||||
|
IetfLanguageCode.Russian.code -> IetfLanguageCode.Russian
|
||||||
|
IetfLanguageCode.Russian.BY.code -> IetfLanguageCode.Russian.BY
|
||||||
|
IetfLanguageCode.Russian.KG.code -> IetfLanguageCode.Russian.KG
|
||||||
|
IetfLanguageCode.Russian.KZ.code -> IetfLanguageCode.Russian.KZ
|
||||||
|
IetfLanguageCode.Russian.MD.code -> IetfLanguageCode.Russian.MD
|
||||||
|
IetfLanguageCode.Russian.RU.code -> IetfLanguageCode.Russian.RU
|
||||||
|
IetfLanguageCode.Russian.UA.code -> IetfLanguageCode.Russian.UA
|
||||||
|
IetfLanguageCode.Kinyarwanda.code -> IetfLanguageCode.Kinyarwanda
|
||||||
|
IetfLanguageCode.Kinyarwanda.RW.code -> IetfLanguageCode.Kinyarwanda.RW
|
||||||
|
IetfLanguageCode.Sanskrit.code -> IetfLanguageCode.Sanskrit
|
||||||
|
IetfLanguageCode.Sardinian.code -> IetfLanguageCode.Sardinian
|
||||||
|
IetfLanguageCode.Sindhi.code -> IetfLanguageCode.Sindhi
|
||||||
|
IetfLanguageCode.Sindhi.Arab.code -> IetfLanguageCode.Sindhi.Arab
|
||||||
|
IetfLanguageCode.Sindhi.Arab.PK.code -> IetfLanguageCode.Sindhi.Arab.PK
|
||||||
|
IetfLanguageCode.Sindhi.Deva.code -> IetfLanguageCode.Sindhi.Deva
|
||||||
|
IetfLanguageCode.Sindhi.Deva.IN.code -> IetfLanguageCode.Sindhi.Deva.IN
|
||||||
|
IetfLanguageCode.NorthernSami.code -> IetfLanguageCode.NorthernSami
|
||||||
|
IetfLanguageCode.NorthernSami.FI.code -> IetfLanguageCode.NorthernSami.FI
|
||||||
|
IetfLanguageCode.NorthernSami.NO.code -> IetfLanguageCode.NorthernSami.NO
|
||||||
|
IetfLanguageCode.NorthernSami.SE.code -> IetfLanguageCode.NorthernSami.SE
|
||||||
|
IetfLanguageCode.Sango.code -> IetfLanguageCode.Sango
|
||||||
|
IetfLanguageCode.Sango.CF.code -> IetfLanguageCode.Sango.CF
|
||||||
|
IetfLanguageCode.SinhalaSinhalese.code -> IetfLanguageCode.SinhalaSinhalese
|
||||||
|
IetfLanguageCode.SinhalaSinhalese.LK.code -> IetfLanguageCode.SinhalaSinhalese.LK
|
||||||
|
IetfLanguageCode.Slovak.code -> IetfLanguageCode.Slovak
|
||||||
|
IetfLanguageCode.Slovak.SK.code -> IetfLanguageCode.Slovak.SK
|
||||||
|
IetfLanguageCode.Slovenian.code -> IetfLanguageCode.Slovenian
|
||||||
|
IetfLanguageCode.Slovenian.SI.code -> IetfLanguageCode.Slovenian.SI
|
||||||
|
IetfLanguageCode.Samoan.code -> IetfLanguageCode.Samoan
|
||||||
|
IetfLanguageCode.Shona.code -> IetfLanguageCode.Shona
|
||||||
|
IetfLanguageCode.Shona.ZW.code -> IetfLanguageCode.Shona.ZW
|
||||||
|
IetfLanguageCode.Somali.code -> IetfLanguageCode.Somali
|
||||||
|
IetfLanguageCode.Somali.DJ.code -> IetfLanguageCode.Somali.DJ
|
||||||
|
IetfLanguageCode.Somali.ET.code -> IetfLanguageCode.Somali.ET
|
||||||
|
IetfLanguageCode.Somali.KE.code -> IetfLanguageCode.Somali.KE
|
||||||
|
IetfLanguageCode.Somali.SO.code -> IetfLanguageCode.Somali.SO
|
||||||
|
IetfLanguageCode.Albanian.code -> IetfLanguageCode.Albanian
|
||||||
|
IetfLanguageCode.Albanian.AL.code -> IetfLanguageCode.Albanian.AL
|
||||||
|
IetfLanguageCode.Albanian.MK.code -> IetfLanguageCode.Albanian.MK
|
||||||
|
IetfLanguageCode.Albanian.XK.code -> IetfLanguageCode.Albanian.XK
|
||||||
|
IetfLanguageCode.Serbian.code -> IetfLanguageCode.Serbian
|
||||||
|
IetfLanguageCode.Serbian.Cyrl.code -> IetfLanguageCode.Serbian.Cyrl
|
||||||
|
IetfLanguageCode.Serbian.Cyrl.BA.code -> IetfLanguageCode.Serbian.Cyrl.BA
|
||||||
|
IetfLanguageCode.Serbian.Cyrl.ME.code -> IetfLanguageCode.Serbian.Cyrl.ME
|
||||||
|
IetfLanguageCode.Serbian.Cyrl.RS.code -> IetfLanguageCode.Serbian.Cyrl.RS
|
||||||
|
IetfLanguageCode.Serbian.Cyrl.XK.code -> IetfLanguageCode.Serbian.Cyrl.XK
|
||||||
|
IetfLanguageCode.Serbian.Latn.code -> IetfLanguageCode.Serbian.Latn
|
||||||
|
IetfLanguageCode.Serbian.Latn.BA.code -> IetfLanguageCode.Serbian.Latn.BA
|
||||||
|
IetfLanguageCode.Serbian.Latn.ME.code -> IetfLanguageCode.Serbian.Latn.ME
|
||||||
|
IetfLanguageCode.Serbian.Latn.RS.code -> IetfLanguageCode.Serbian.Latn.RS
|
||||||
|
IetfLanguageCode.Serbian.Latn.XK.code -> IetfLanguageCode.Serbian.Latn.XK
|
||||||
|
IetfLanguageCode.Swati.code -> IetfLanguageCode.Swati
|
||||||
|
IetfLanguageCode.SothoSouthern.code -> IetfLanguageCode.SothoSouthern
|
||||||
|
IetfLanguageCode.Sundanese.code -> IetfLanguageCode.Sundanese
|
||||||
|
IetfLanguageCode.Sundanese.Latn.code -> IetfLanguageCode.Sundanese.Latn
|
||||||
|
IetfLanguageCode.Sundanese.Latn.ID.code -> IetfLanguageCode.Sundanese.Latn.ID
|
||||||
|
IetfLanguageCode.Swedish.code -> IetfLanguageCode.Swedish
|
||||||
|
IetfLanguageCode.Swedish.AX.code -> IetfLanguageCode.Swedish.AX
|
||||||
|
IetfLanguageCode.Swedish.FI.code -> IetfLanguageCode.Swedish.FI
|
||||||
|
IetfLanguageCode.Swedish.SE.code -> IetfLanguageCode.Swedish.SE
|
||||||
|
IetfLanguageCode.Swahili.code -> IetfLanguageCode.Swahili
|
||||||
|
IetfLanguageCode.Swahili.CD.code -> IetfLanguageCode.Swahili.CD
|
||||||
|
IetfLanguageCode.Swahili.KE.code -> IetfLanguageCode.Swahili.KE
|
||||||
|
IetfLanguageCode.Swahili.TZ.code -> IetfLanguageCode.Swahili.TZ
|
||||||
|
IetfLanguageCode.Swahili.UG.code -> IetfLanguageCode.Swahili.UG
|
||||||
|
IetfLanguageCode.Tamil.code -> IetfLanguageCode.Tamil
|
||||||
|
IetfLanguageCode.Tamil.IN.code -> IetfLanguageCode.Tamil.IN
|
||||||
|
IetfLanguageCode.Tamil.LK.code -> IetfLanguageCode.Tamil.LK
|
||||||
|
IetfLanguageCode.Tamil.MY.code -> IetfLanguageCode.Tamil.MY
|
||||||
|
IetfLanguageCode.Tamil.SG.code -> IetfLanguageCode.Tamil.SG
|
||||||
|
IetfLanguageCode.Telugu.code -> IetfLanguageCode.Telugu
|
||||||
|
IetfLanguageCode.Telugu.IN.code -> IetfLanguageCode.Telugu.IN
|
||||||
|
IetfLanguageCode.Tajik.code -> IetfLanguageCode.Tajik
|
||||||
|
IetfLanguageCode.Tajik.TJ.code -> IetfLanguageCode.Tajik.TJ
|
||||||
|
IetfLanguageCode.Thai.code -> IetfLanguageCode.Thai
|
||||||
|
IetfLanguageCode.Thai.TH.code -> IetfLanguageCode.Thai.TH
|
||||||
|
IetfLanguageCode.Tigrinya.code -> IetfLanguageCode.Tigrinya
|
||||||
|
IetfLanguageCode.Tigrinya.ER.code -> IetfLanguageCode.Tigrinya.ER
|
||||||
|
IetfLanguageCode.Tigrinya.ET.code -> IetfLanguageCode.Tigrinya.ET
|
||||||
|
IetfLanguageCode.Turkmen.code -> IetfLanguageCode.Turkmen
|
||||||
|
IetfLanguageCode.Turkmen.TM.code -> IetfLanguageCode.Turkmen.TM
|
||||||
|
IetfLanguageCode.Tagalog.code -> IetfLanguageCode.Tagalog
|
||||||
|
IetfLanguageCode.Tswana.code -> IetfLanguageCode.Tswana
|
||||||
|
IetfLanguageCode.TongaTongaIslands.code -> IetfLanguageCode.TongaTongaIslands
|
||||||
|
IetfLanguageCode.TongaTongaIslands.TO.code -> IetfLanguageCode.TongaTongaIslands.TO
|
||||||
|
IetfLanguageCode.Turkish.code -> IetfLanguageCode.Turkish
|
||||||
|
IetfLanguageCode.Turkish.CY.code -> IetfLanguageCode.Turkish.CY
|
||||||
|
IetfLanguageCode.Turkish.TR.code -> IetfLanguageCode.Turkish.TR
|
||||||
|
IetfLanguageCode.Tsonga.code -> IetfLanguageCode.Tsonga
|
||||||
|
IetfLanguageCode.Tatar.code -> IetfLanguageCode.Tatar
|
||||||
|
IetfLanguageCode.Tatar.RU.code -> IetfLanguageCode.Tatar.RU
|
||||||
|
IetfLanguageCode.Twi.code -> IetfLanguageCode.Twi
|
||||||
|
IetfLanguageCode.Tahitian.code -> IetfLanguageCode.Tahitian
|
||||||
|
IetfLanguageCode.UighurUyghur.code -> IetfLanguageCode.UighurUyghur
|
||||||
|
IetfLanguageCode.UighurUyghur.CN.code -> IetfLanguageCode.UighurUyghur.CN
|
||||||
|
IetfLanguageCode.Ukrainian.code -> IetfLanguageCode.Ukrainian
|
||||||
|
IetfLanguageCode.Ukrainian.UA.code -> IetfLanguageCode.Ukrainian.UA
|
||||||
|
IetfLanguageCode.Urdu.code -> IetfLanguageCode.Urdu
|
||||||
|
IetfLanguageCode.Urdu.IN.code -> IetfLanguageCode.Urdu.IN
|
||||||
|
IetfLanguageCode.Urdu.PK.code -> IetfLanguageCode.Urdu.PK
|
||||||
|
IetfLanguageCode.Uzbek.code -> IetfLanguageCode.Uzbek
|
||||||
|
IetfLanguageCode.Uzbek.Arab.code -> IetfLanguageCode.Uzbek.Arab
|
||||||
|
IetfLanguageCode.Uzbek.Arab.AF.code -> IetfLanguageCode.Uzbek.Arab.AF
|
||||||
|
IetfLanguageCode.Uzbek.Cyrl.code -> IetfLanguageCode.Uzbek.Cyrl
|
||||||
|
IetfLanguageCode.Uzbek.Cyrl.UZ.code -> IetfLanguageCode.Uzbek.Cyrl.UZ
|
||||||
|
IetfLanguageCode.Uzbek.Latn.code -> IetfLanguageCode.Uzbek.Latn
|
||||||
|
IetfLanguageCode.Uzbek.Latn.UZ.code -> IetfLanguageCode.Uzbek.Latn.UZ
|
||||||
|
IetfLanguageCode.Venda.code -> IetfLanguageCode.Venda
|
||||||
|
IetfLanguageCode.Vietnamese.code -> IetfLanguageCode.Vietnamese
|
||||||
|
IetfLanguageCode.Vietnamese.VN.code -> IetfLanguageCode.Vietnamese.VN
|
||||||
|
IetfLanguageCode.Volapuk.code -> IetfLanguageCode.Volapuk
|
||||||
|
IetfLanguageCode.Volapuk.L001.code -> IetfLanguageCode.Volapuk.L001
|
||||||
|
IetfLanguageCode.Walloon.code -> IetfLanguageCode.Walloon
|
||||||
|
IetfLanguageCode.Wolof.code -> IetfLanguageCode.Wolof
|
||||||
|
IetfLanguageCode.Wolof.SN.code -> IetfLanguageCode.Wolof.SN
|
||||||
|
IetfLanguageCode.Xhosa.code -> IetfLanguageCode.Xhosa
|
||||||
|
IetfLanguageCode.Xhosa.ZA.code -> IetfLanguageCode.Xhosa.ZA
|
||||||
|
IetfLanguageCode.Yiddish.code -> IetfLanguageCode.Yiddish
|
||||||
|
IetfLanguageCode.Yiddish.L001.code -> IetfLanguageCode.Yiddish.L001
|
||||||
|
IetfLanguageCode.Yoruba.code -> IetfLanguageCode.Yoruba
|
||||||
|
IetfLanguageCode.Yoruba.BJ.code -> IetfLanguageCode.Yoruba.BJ
|
||||||
|
IetfLanguageCode.Yoruba.NG.code -> IetfLanguageCode.Yoruba.NG
|
||||||
|
IetfLanguageCode.ZhuangChuang.code -> IetfLanguageCode.ZhuangChuang
|
||||||
|
IetfLanguageCode.Chinese.code -> IetfLanguageCode.Chinese
|
||||||
|
IetfLanguageCode.Chinese.Hans.code -> IetfLanguageCode.Chinese.Hans
|
||||||
|
IetfLanguageCode.Chinese.Hans.CN.code -> IetfLanguageCode.Chinese.Hans.CN
|
||||||
|
IetfLanguageCode.Chinese.Hans.HK.code -> IetfLanguageCode.Chinese.Hans.HK
|
||||||
|
IetfLanguageCode.Chinese.Hans.MO.code -> IetfLanguageCode.Chinese.Hans.MO
|
||||||
|
IetfLanguageCode.Chinese.Hans.SG.code -> IetfLanguageCode.Chinese.Hans.SG
|
||||||
|
IetfLanguageCode.Chinese.Hant.code -> IetfLanguageCode.Chinese.Hant
|
||||||
|
IetfLanguageCode.Chinese.Hant.HK.code -> IetfLanguageCode.Chinese.Hant.HK
|
||||||
|
IetfLanguageCode.Chinese.Hant.MO.code -> IetfLanguageCode.Chinese.Hant.MO
|
||||||
|
IetfLanguageCode.Chinese.Hant.TW.code -> IetfLanguageCode.Chinese.Hant.TW
|
||||||
|
IetfLanguageCode.Zulu.code -> IetfLanguageCode.Zulu
|
||||||
|
IetfLanguageCode.Zulu.ZA.code -> IetfLanguageCode.Zulu.ZA
|
||||||
|
else -> IetfLanguageCode.UnknownIetfLanguageCode(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun convertToIetfLanguageCode(code: String) = code.asIetfLanguageCode()
|
||||||
|
fun IetfLanguageCode(code: String) = code.asIetfLanguageCode()
|
1
language_codes/src/main/AndroidManifest.xml
Normal file
1
language_codes/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.language_codes"/>
|
52
mime_types/mimes_generator/mime_generator.py
Normal file
52
mime_types/mimes_generator/mime_generator.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import pandas as pd
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
def fix_name(category, raw_name):
|
||||||
|
splitted = raw_name.replace('-', '+').replace('.', '+').replace(',', '+').split('+')
|
||||||
|
out1 = ""
|
||||||
|
for s in splitted:
|
||||||
|
out1 += s.capitalize()
|
||||||
|
|
||||||
|
result = ""
|
||||||
|
if out1[0].isdigit():
|
||||||
|
result += category[0].capitalize()
|
||||||
|
result += out1
|
||||||
|
else:
|
||||||
|
result += out1
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
df = pd.read_html(open('table.html', 'r'))
|
||||||
|
mimes = []
|
||||||
|
for row in df[0].iterrows():
|
||||||
|
mime = row[1][1]
|
||||||
|
mime_category = mime.split('/', 1)[0]
|
||||||
|
mime_name = mime.split('/', 1)[1]
|
||||||
|
mimes.append({
|
||||||
|
'mime_category': mime_category,
|
||||||
|
'mime_name': mime_name,
|
||||||
|
})
|
||||||
|
|
||||||
|
# codegen
|
||||||
|
|
||||||
|
mimes.sort(key=lambda x: x['mime_category'])
|
||||||
|
grouped = itertools.groupby(mimes, lambda x: x['mime_category'])
|
||||||
|
code = ''
|
||||||
|
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
|
||||||
|
code2 += ' KnownMimeTypes.Any,\n'
|
||||||
|
for key, group in grouped:
|
||||||
|
group_name = key.capitalize()
|
||||||
|
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
|
||||||
|
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
|
||||||
|
for mime in group:
|
||||||
|
name = fix_name(mime['mime_category'], mime['mime_name'])
|
||||||
|
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name'])
|
||||||
|
code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name)
|
||||||
|
code += '}\n\n'
|
||||||
|
code2 += ')\n'
|
||||||
|
with open('out1.txt', 'w') as file:
|
||||||
|
file.write(code)
|
||||||
|
with open('out2.txt', 'w') as file:
|
||||||
|
file.write(code2)
|
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
|
||||||
|
|
||||||
package dev.inmo.micro_utils.mime_types
|
package dev.inmo.micro_utils.mime_types
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@@ -24,3 +24,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
@@ -4,7 +4,13 @@ project.group = "$group"
|
|||||||
apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm {
|
||||||
|
compilations.main {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
@@ -26,3 +32,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
@@ -4,7 +4,13 @@ project.group = "$group"
|
|||||||
apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm {
|
||||||
|
compilations.main {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
js (IR) {
|
js (IR) {
|
||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
@@ -48,3 +54,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
@@ -5,3 +5,13 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":micro_utils.common")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.pagination
|
package dev.inmo.micro_utils.pagination
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.intersect
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ import kotlin.math.floor
|
|||||||
* If you want to request something, you should use [SimplePagination]. If you need to return some result including
|
* If you want to request something, you should use [SimplePagination]. If you need to return some result including
|
||||||
* pagination - [PaginationResult]
|
* pagination - [PaginationResult]
|
||||||
*/
|
*/
|
||||||
interface Pagination {
|
interface Pagination : ClosedRange<Int> {
|
||||||
/**
|
/**
|
||||||
* Started with 0.
|
* Started with 0.
|
||||||
* Number of page inside of pagination. Offset can be calculated as [page] * [size]
|
* Number of page inside of pagination. Offset can be calculated as [page] * [size]
|
||||||
@@ -20,6 +21,17 @@ interface Pagination {
|
|||||||
* Size of current page. Offset can be calculated as [page] * [size]
|
* Size of current page. Offset can be calculated as [page] * [size]
|
||||||
*/
|
*/
|
||||||
val size: Int
|
val size: Int
|
||||||
|
|
||||||
|
override val start: Int
|
||||||
|
get() = page * size
|
||||||
|
override val endInclusive: Int
|
||||||
|
get() = start + size - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Pagination.intersect(
|
||||||
|
other: Pagination
|
||||||
|
): Pagination? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
|
||||||
|
PaginationByIndexes(it.first, it.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +44,7 @@ inline val Pagination.isFirstPage
|
|||||||
* First number in index of objects. It can be used as offset for databases or other data sources
|
* First number in index of objects. It can be used as offset for databases or other data sources
|
||||||
*/
|
*/
|
||||||
val Pagination.firstIndex: Int
|
val Pagination.firstIndex: Int
|
||||||
get() = page * size
|
get() = start
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
||||||
@@ -41,7 +53,7 @@ val Pagination.firstIndex: Int
|
|||||||
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
|
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
|
||||||
*/
|
*/
|
||||||
val Pagination.lastIndexExclusive: Int
|
val Pagination.lastIndexExclusive: Int
|
||||||
get() = firstIndex + size
|
get() = endInclusive + 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
* Last number in index of objects. In fact, one [Pagination] object represent data in next range:
|
||||||
@@ -50,7 +62,7 @@ val Pagination.lastIndexExclusive: Int
|
|||||||
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
|
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
|
||||||
*/
|
*/
|
||||||
val Pagination.lastIndex: Int
|
val Pagination.lastIndex: Int
|
||||||
get() = lastIndexExclusive - 1
|
get() = endInclusive
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates pages count for given [datasetSize]
|
* Calculates pages count for given [datasetSize]
|
||||||
|
@@ -16,6 +16,16 @@ suspend fun <T> getAll(
|
|||||||
return results.toList()
|
return results.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun <T, R> R.getAllBy(
|
||||||
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
|
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
|
||||||
|
block: suspend R.(Pagination) -> PaginationResult<T>
|
||||||
|
): List<T> = getAll(
|
||||||
|
initialPagination,
|
||||||
|
{ paginationMapper(it) },
|
||||||
|
{ block(it) }
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun <T> getAllWithNextPaging(
|
suspend fun <T> getAllWithNextPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: suspend (Pagination) -> PaginationResult<T>
|
||||||
@@ -25,6 +35,14 @@ suspend fun <T> getAllWithNextPaging(
|
|||||||
block
|
block
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun <T, R> R.getAllByWithNextPaging(
|
||||||
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
|
block: suspend R.(Pagination) -> PaginationResult<T>
|
||||||
|
): List<T> = getAllWithNextPaging(
|
||||||
|
initialPagination,
|
||||||
|
{ block(it) }
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun <T> getAllWithCurrentPaging(
|
suspend fun <T> getAllWithCurrentPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: suspend (Pagination) -> PaginationResult<T>
|
||||||
@@ -33,3 +51,11 @@ suspend fun <T> getAllWithCurrentPaging(
|
|||||||
{ it.currentPageIfNotEmpty() },
|
{ it.currentPageIfNotEmpty() },
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun <T, R> R.getAllByWithCurrentPaging(
|
||||||
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
|
block: suspend R.(Pagination) -> PaginationResult<T>
|
||||||
|
): List<T> = getAllWithCurrentPaging(
|
||||||
|
initialPagination,
|
||||||
|
{ block(it) }
|
||||||
|
)
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
apply plugin: 'signing'
|
|
||||||
|
|
||||||
task javadocsJar(type: Jar) {
|
task javadocsJar(type: Jar) {
|
||||||
classifier = 'javadoc'
|
classifier = 'javadoc'
|
||||||
@@ -69,8 +68,19 @@ publishing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signing {
|
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||||
useGpgCmd()
|
apply plugin: 'signing'
|
||||||
sign publishing.publications
|
|
||||||
|
signing {
|
||||||
|
useGpgCmd()
|
||||||
|
|
||||||
|
sign publishing.publications
|
||||||
|
}
|
||||||
|
|
||||||
|
task signAll {
|
||||||
|
tasks.withType(Sign).forEach {
|
||||||
|
dependsOn(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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","includeGpgSigning":true,"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":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}]}}
|
{"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":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
|
@@ -2,6 +2,8 @@ package dev.inmo.micro_utils.repos
|
|||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import androidx.core.database.*
|
||||||
|
import dev.inmo.micro_utils.repos.getLongOrNull
|
||||||
|
|
||||||
fun createTableQuery(
|
fun createTableQuery(
|
||||||
tableName: String,
|
tableName: String,
|
||||||
@@ -32,6 +34,11 @@ fun SQLiteDatabase.createTable(
|
|||||||
fun Cursor.getString(columnName: String): String = getString(
|
fun Cursor.getString(columnName: String): String = getString(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getStringOrNull(columnName: String): String? {
|
||||||
|
return getStringOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
@@ -39,6 +46,11 @@ fun Cursor.getString(columnName: String): String = getString(
|
|||||||
fun Cursor.getShort(columnName: String): Short = getShort(
|
fun Cursor.getShort(columnName: String): Short = getShort(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getShortOrNull(columnName: String): Short? {
|
||||||
|
return getShortOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
@@ -46,6 +58,11 @@ fun Cursor.getShort(columnName: String): Short = getShort(
|
|||||||
fun Cursor.getLong(columnName: String): Long = getLong(
|
fun Cursor.getLong(columnName: String): Long = getLong(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getLongOrNull(columnName: String): Long? {
|
||||||
|
return getLongOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
@@ -53,6 +70,23 @@ fun Cursor.getLong(columnName: String): Long = getLong(
|
|||||||
fun Cursor.getInt(columnName: String): Int = getInt(
|
fun Cursor.getInt(columnName: String): Int = getInt(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getIntOrNull(columnName: String): Int? {
|
||||||
|
return getIntOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
*/
|
||||||
|
fun Cursor.getFloat(columnName: String): Float = getFloat(
|
||||||
|
getColumnIndexOrThrow(columnName)
|
||||||
|
)
|
||||||
|
fun Cursor.getFloatOrNull(columnName: String): Float? {
|
||||||
|
return getFloatOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
@@ -60,6 +94,11 @@ fun Cursor.getInt(columnName: String): Int = getInt(
|
|||||||
fun Cursor.getDouble(columnName: String): Double = getDouble(
|
fun Cursor.getDouble(columnName: String): Double = getDouble(
|
||||||
getColumnIndexOrThrow(columnName)
|
getColumnIndexOrThrow(columnName)
|
||||||
)
|
)
|
||||||
|
fun Cursor.getDoubleOrNull(columnName: String): Double? {
|
||||||
|
return getDoubleOrNull(
|
||||||
|
getColumnIndex(columnName).takeIf { it != -1 } ?: return null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun SQLiteDatabase.select(
|
fun SQLiteDatabase.select(
|
||||||
table: String,
|
table: String,
|
||||||
|
@@ -6,13 +6,15 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
helper: StandardSQLHelper
|
helper: StandardSQLHelper,
|
||||||
|
replyInFlows: Int = 0,
|
||||||
|
extraBufferCapacityInFlows: Int = 64
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
|
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
|
||||||
StandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
StandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
|
||||||
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
|
||||||
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(64)
|
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyInFlows, extraBufferCapacityInFlows)
|
||||||
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
|
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
||||||
@@ -102,4 +104,4 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun count(): Long = helper.blockingReadableTransaction { select(tableName).use { it.count.toLong() } }
|
override suspend fun count(): Long = helper.blockingReadableTransaction { select(tableName).use { it.count.toLong() } }
|
||||||
}
|
}
|
||||||
|
@@ -10,15 +10,16 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
|
|
||||||
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
flowsChannelsSize: Int = 0,
|
flowsChannelsSize: Int = 0,
|
||||||
tableName: String = ""
|
tableName: String = "",
|
||||||
|
replyCacheInFlows: Int = 0
|
||||||
) :
|
) :
|
||||||
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
|
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
|
||||||
ExposedCRUDRepo<ObjectType, IdType>,
|
ExposedCRUDRepo<ObjectType, IdType>,
|
||||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
{
|
{
|
||||||
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize)
|
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
||||||
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize)
|
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
||||||
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(flowsChannelsSize)
|
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize)
|
||||||
|
|
||||||
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
|
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
||||||
|
@@ -19,8 +19,8 @@ open class ExposedKeyValueRepo<Key, Value>(
|
|||||||
valueColumnAllocator,
|
valueColumnAllocator,
|
||||||
tableName
|
tableName
|
||||||
) {
|
) {
|
||||||
private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
|
protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
|
||||||
private val _onValueRemoved = MutableSharedFlow<Key>()
|
protected val _onValueRemoved = MutableSharedFlow<Key>()
|
||||||
|
|
||||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
|
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
|
||||||
|
@@ -12,8 +12,8 @@ open class ExposedReadKeyValueRepo<Key, Value>(
|
|||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
tableName: String? = null
|
tableName: String? = null
|
||||||
) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||||
protected val keyColumn: Column<Key> = keyColumnAllocator()
|
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||||
protected val valueColumn: Column<Value> = valueColumnAllocator()
|
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
||||||
|
|
||||||
init { initTable() }
|
init { initTable() }
|
||||||
|
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.*
|
|||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
|
typealias ExposedKeyValuesRepo<Key, Value> = ExposedOneToManyKeyValueRepo<Key, Value>
|
||||||
open class ExposedOneToManyKeyValueRepo<Key, Value>(
|
open class ExposedOneToManyKeyValueRepo<Key, Value>(
|
||||||
database: Database,
|
database: Database,
|
||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
|
@@ -3,17 +3,20 @@ package dev.inmo.micro_utils.repos.exposed.onetomany
|
|||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.*
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
|
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedReadKeyValueRepo
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
|
typealias ExposedReadKeyValuesRepo<Key, Value> = ExposedReadOneToManyKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
||||||
override val database: Database,
|
override val database: Database,
|
||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
tableName: String? = null
|
tableName: String? = null
|
||||||
) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||||
protected val keyColumn: Column<Key> = keyColumnAllocator()
|
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||||
protected val valueColumn: Column<Value> = valueColumnAllocator()
|
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||||
|
|
||||||
init { initTable() }
|
init { initTable() }
|
||||||
|
|
||||||
|
@@ -18,8 +18,8 @@ inline fun versionsRepo(database: Database): VersionsRepo<Database> = StandardVe
|
|||||||
class ExposedStandardVersionsRepoProxy(
|
class ExposedStandardVersionsRepoProxy(
|
||||||
override val database: Database
|
override val database: Database
|
||||||
) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo {
|
) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo {
|
||||||
private val tableNameColumn = text("tableName")
|
val tableNameColumn = text("tableName")
|
||||||
private val tableVersionColumn = integer("tableName")
|
val tableVersionColumn = integer("tableName")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initTable()
|
initTable()
|
||||||
|
@@ -23,13 +23,13 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
private val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
private val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||||
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
||||||
private val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
||||||
private val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MutableSharedFlow()
|
protected val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MutableSharedFlow()
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
||||||
|
|
||||||
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
||||||
|
@@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
class ReadMapKeyValueRepo<Key, Value>(
|
class ReadMapKeyValueRepo<Key, Value>(
|
||||||
private val map: Map<Key, Value> = emptyMap()
|
protected val map: Map<Key, Value> = emptyMap()
|
||||||
) : ReadStandardKeyValueRepo<Key, Value> {
|
) : ReadStandardKeyValueRepo<Key, Value> {
|
||||||
override suspend fun get(k: Key): Value? = map[k]
|
override suspend fun get(k: Key): Value? = map[k]
|
||||||
|
|
||||||
|
@@ -8,20 +8,20 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
open class TypedSerializer<T : Any>(
|
open class TypedSerializer<T : Any>(
|
||||||
kClass: KClass<T>,
|
kClass: KClass<T>,
|
||||||
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
|
presetSerializers: Map<String, KSerializer<out T>> = emptyMap(),
|
||||||
) : KSerializer<T> {
|
) : KSerializer<T> {
|
||||||
protected val serializers = presetSerializers.toMutableMap()
|
protected val serializers = presetSerializers.toMutableMap()
|
||||||
@InternalSerializationApi
|
@OptIn(InternalSerializationApi::class)
|
||||||
open override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
||||||
"TextSourceSerializer",
|
"TypedSerializer",
|
||||||
SerialKind.CONTEXTUAL
|
SerialKind.CONTEXTUAL
|
||||||
) {
|
) {
|
||||||
element("type", String.serializer().descriptor)
|
element("type", String.serializer().descriptor)
|
||||||
element("value", ContextualSerializer(kClass).descriptor)
|
element("value", ContextualSerializer(kClass).descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InternalSerializationApi
|
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
||||||
open override fun deserialize(decoder: Decoder): T {
|
override fun deserialize(decoder: Decoder): T {
|
||||||
return decoder.decodeStructure(descriptor) {
|
return decoder.decodeStructure(descriptor) {
|
||||||
var type: String? = null
|
var type: String? = null
|
||||||
lateinit var result: T
|
lateinit var result: T
|
||||||
@@ -44,13 +44,13 @@ open class TypedSerializer<T : Any>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@InternalSerializationApi
|
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
||||||
protected open fun <O: T> CompositeEncoder.encode(value: O) {
|
protected open fun <O: T> CompositeEncoder.encode(value: O) {
|
||||||
encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value)
|
encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InternalSerializationApi
|
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
||||||
open override fun serialize(encoder: Encoder, value: T) {
|
override fun serialize(encoder: Encoder, value: T) {
|
||||||
encoder.encodeStructure(descriptor) {
|
encoder.encodeStructure(descriptor) {
|
||||||
val valueSerializer = value::class.serializer()
|
val valueSerializer = value::class.serializer()
|
||||||
val type = serializers.keys.first { serializers[it] == valueSerializer }
|
val type = serializers.keys.first { serializers[it] == valueSerializer }
|
||||||
@@ -69,6 +69,20 @@ open class TypedSerializer<T : Any>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InternalSerializationApi
|
||||||
|
operator fun <T : Any> TypedSerializer<T>.plusAssign(kClass: KClass<T>) {
|
||||||
|
include(kClass.simpleName!!, kClass.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
@InternalSerializationApi
|
||||||
|
operator fun <T : Any> TypedSerializer<T>.minusAssign(kClass: KClass<T>) {
|
||||||
|
exclude(kClass.simpleName!!)
|
||||||
|
}
|
||||||
|
|
||||||
inline fun <reified T : Any> TypedSerializer(
|
inline fun <reified T : Any> TypedSerializer(
|
||||||
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
|
presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
|
||||||
) = TypedSerializer(T::class, presetSerializers)
|
) = TypedSerializer(T::class, presetSerializers)
|
||||||
|
|
||||||
|
inline fun <reified T : Any> TypedSerializer(
|
||||||
|
vararg presetSerializers: Pair<String, KSerializer<out T>>
|
||||||
|
) = TypedSerializer(presetSerializers.toMap())
|
||||||
|
@@ -10,6 +10,8 @@ String[] includes = [
|
|||||||
":pagination:ktor:common",
|
":pagination:ktor:common",
|
||||||
":pagination:ktor:server",
|
":pagination:ktor:server",
|
||||||
":mime_types",
|
":mime_types",
|
||||||
|
":language_codes",
|
||||||
|
":language_codes:generator",
|
||||||
":repos:common",
|
":repos:common",
|
||||||
":repos:cache",
|
":repos:cache",
|
||||||
":repos:exposed",
|
":repos:exposed",
|
||||||
|
Reference in New Issue
Block a user