mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
122 Commits
Author | SHA1 | Date | |
---|---|---|---|
1239ca3256 | |||
5ee5bfd1d5 | |||
7229a3e198 | |||
bee083582f | |||
9d7f99f286 | |||
6ef403853c | |||
6ae7ccb9a1 | |||
dafc50c463 | |||
e89e2c931d | |||
43a67b99e4 | |||
46c48f4f31 | |||
bf0fe85aa6 | |||
42c5bd3a7f | |||
d170e86c8a | |||
e3078169b1 | |||
a33ad123f6 | |||
7e14fa2f5c | |||
ba698b41e1 | |||
e76215987e | |||
d1a247af8c | |||
2b7e9534f3 | |||
38521558a1 | |||
100f3d214b | |||
1309867611 | |||
611f64f2e1 | |||
f118ebce6e | |||
59fc90e556 | |||
fb9e4d57fb | |||
960c38b696 | |||
39895e58a6 | |||
b420d85be5 | |||
19ea2f340a | |||
11b0d059bf | |||
c8a25ce544 | |||
509583ea2e | |||
1c86f3f4bf | |||
6d999be590 | |||
e715772dbf | |||
63eb7b7ea8 | |||
b07683b815 | |||
96e97d1691 | |||
261d8827e3 | |||
c3156f2e41 | |||
8c08801460 | |||
aaf1299da7 | |||
a411355b4f | |||
eba41066b4 | |||
f295dff8a2 | |||
a16815143c | |||
6ff3f6ae42 | |||
84071881af | |||
7cccf7e56e | |||
2516d5e381 | |||
cdec8bac75 | |||
fa30aae194 | |||
eb959a3135 | |||
24033e0cac | |||
71f9a505e0 | |||
979b8f017b | |||
af78f01682 | |||
0b16d5c826 | |||
597e14bc7e | |||
04a95867e2 | |||
e0d5eb45b7 | |||
b90cab318e | |||
3252b61abe | |||
2a2da21ff3 | |||
04ef371337 | |||
623e0cd369 | |||
1f466747f0 | |||
2215462f99 | |||
ac4c0a2e4c | |||
f7496db5ac | |||
3028fe975d | |||
23a5034493 | |||
65e339f811 | |||
2020e48659 | |||
9566d6f81f | |||
a00d734712 | |||
27a3e8706a | |||
e601efcfc0 | |||
2bfad9f885 | |||
e78e984943 | |||
242f4b02d0 | |||
041be5a1d1 | |||
976ce056c1 | |||
00c23c73a8 | |||
9dd1848337 | |||
9b30efd9a2 | |||
5853f7cc49 | |||
7b00a06f3e | |||
9ef9be0f37 | |||
13ca419473 | |||
b80f1a0773 | |||
e85101c74e | |||
90668bdf63 | |||
e1a00079a5 | |||
e3add4df42 | |||
ced1a3bccb | |||
8d13a14343 | |||
7238e1ea8a | |||
bd0423f243 | |||
b0441c134c | |||
5860901c30 | |||
df4eaea4b9 | |||
e9223d5502 | |||
702c5a3e5d | |||
ccbed95cdc | |||
c4e2c06cf5 | |||
a6135738a3 | |||
ad1ea985b8 | |||
2058950d07 | |||
4dc27f4489 | |||
122daa3220 | |||
e30928e23d | |||
30292306bb | |||
2f9aa585f1 | |||
9e02c3e5ff | |||
7f813a519b | |||
b5072486b4 | |||
50c1cd8215 | |||
ed4812e6d8 |
12
.github/workflows/build.yml
vendored
Normal file
12
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
2
.github/workflows/packages_push.yml
vendored
2
.github/workflows/packages_push.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Build
|
||||
run: ./gradlew build
|
||||
- name: Publish
|
||||
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signMetadataPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
|
||||
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
|
||||
env:
|
||||
GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
8
.space.kts
Normal file
8
.space.kts
Normal file
@@ -0,0 +1,8 @@
|
||||
job("Build and run tests") {
|
||||
container(displayName = "Run gradle build", image = "openjdk:11") {
|
||||
kotlinScript { api ->
|
||||
// here can be your complex logic
|
||||
api.gradlew("build")
|
||||
}
|
||||
}
|
||||
}
|
27
.travis.yml
27
.travis.yml
@@ -1,27 +0,0 @@
|
||||
language: android
|
||||
install: true
|
||||
|
||||
os: linux
|
||||
dist: trusty
|
||||
jdk: oraclejdk8
|
||||
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- build-tools-30.0.2
|
||||
- android-30
|
||||
- add-on
|
||||
- extra
|
||||
|
||||
before_script:
|
||||
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "build-tools;30.0.2"
|
||||
- yes | /usr/local/android-sdk/tools/bin/sdkmanager "platforms;android-30"
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: build
|
||||
script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest
|
||||
# Tests are temporarily disabled on public travis due to the problems of launching
|
||||
# - state: test
|
||||
# script: ./gradlew allTests
|
187
CHANGELOG.md
187
CHANGELOG.md
@@ -1,5 +1,192 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
**SINCE THIS UPDATE JS PARTS WILL BE COMPILED WITH IR COMPILER ONLY**
|
||||
|
||||
* `Versions`
|
||||
* `Kotlin`: `1.5.20` -> `1.5.21`
|
||||
* `Ktor`: `1.6.1` -> `1.6.2`
|
||||
* `Klock`: `2.2.0` -> `2.3.1`
|
||||
* `CryptoJS`: `4.0.0` -> `4.1.1`
|
||||
|
||||
## 0.5.16
|
||||
|
||||
* `Versions`
|
||||
* `Coroutines`: `1.5.0` -> `1.5.1`
|
||||
* `Serialization`: `1.2.1` -> `1.2.2`
|
||||
* `Ktor`: `1.6.0` -> `1.6.1`
|
||||
* `Klock`: `2.1.2` -> `2.2.0`
|
||||
* `Core KTX`: `1.5.0` -> `1.6.0`
|
||||
|
||||
## 0.5.15 HOTFIX FOR 0.5.14
|
||||
|
||||
* `Coroutines`
|
||||
* Fixes in `subscribeAsync`
|
||||
|
||||
## 0.5.14 NOT RECOMMENDED
|
||||
|
||||
* `Versions`
|
||||
* `Kotlin`: `1.5.10` -> `1.5.20`
|
||||
* `Coroutines`
|
||||
* `subscribeSafelyWithoutExceptions` got new parameter `onException` by analogue with `safelyWithoutExceptions`
|
||||
* New extensions `Flow#subscribeAsync` and subsequent analogs of `subscribe` with opportunity to set up custom marker
|
||||
|
||||
## 0.5.13
|
||||
|
||||
* `Common`:
|
||||
* Add functionality for multiplatform working with files:
|
||||
* Main class for files `MPPFile`
|
||||
* Inline class for filenames work encapsulation `FileName`
|
||||
* `FSM`
|
||||
* Module inited and in preview state
|
||||
|
||||
## 0.5.12
|
||||
|
||||
* `Common`:
|
||||
* `Android`
|
||||
* Extension `View#changeVisibility` has been fixed
|
||||
* `Android`
|
||||
* `RecyclerView`
|
||||
* Default adapter got `dataCountFlow` property
|
||||
* New subtype of adapter based on `StateFlow`: `StateFlowBasedRecyclerViewAdapter`
|
||||
|
||||
## 0.5.11
|
||||
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* Fixes in `WriteOneToManyRepo#add`
|
||||
* `Exposed`:
|
||||
* Fixes in `ExposedOneToManyKeyValueRepo#add`
|
||||
|
||||
## 0.5.10
|
||||
|
||||
* `Versions`
|
||||
* `Core KTX`: `1.3.2` -> `1.5.0`
|
||||
* `AndroidX Recycler`: `1.2.0` -> `1.2.1`
|
||||
* `AppCompat`: `1.2.0` -> `1.3.0`
|
||||
* `Android`
|
||||
* `RecyclerView`:
|
||||
* `data` of `RecyclerViewAdapter` became an abstract field
|
||||
* New function `RecyclerViewAdapter`
|
||||
* `Common`:
|
||||
* New extension `View#changeVisibility`
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* `WriteOneToManyRepo` got new function `clearWithValue`
|
||||
* `Android`:
|
||||
* New extension `SQLiteDatabase#selectDistinct`
|
||||
* Fixes in `OneToManyAndroidRepo`
|
||||
* `Ktor`
|
||||
* `Server`
|
||||
* All elements in configurators became a `fun interface`
|
||||
* `Pagination`
|
||||
* New function `doForAllWithCurrentPaging`
|
||||
|
||||
## 0.5.9
|
||||
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* `OneToManyAndroidRepo` got new primary constructor
|
||||
|
||||
## 0.5.8
|
||||
|
||||
* `Common`:
|
||||
* New extension `Iterable#firstNotNull`
|
||||
* `Coroutines`
|
||||
* New extension `Flow#firstNotNull`
|
||||
* New extensions `CoroutineContext#LinkedSupervisorJob`, `CoroutineScope#LinkedSupervisorJob` and
|
||||
`CoroutineScope#LinkedSupervisorScope`
|
||||
|
||||
## 0.5.7
|
||||
|
||||
* `Pagination`
|
||||
* `Ktor`
|
||||
* `Server`
|
||||
* Fixes in extension `extractPagination`
|
||||
* `Repos`
|
||||
* `Cache`
|
||||
* All standard cache repos have been separated to read and read/write repos
|
||||
|
||||
## 0.5.6
|
||||
|
||||
* `Versions`
|
||||
* `Exposed`: `0.31.1` -> `0.32.1`
|
||||
* `Coroutines`
|
||||
* `JVM`
|
||||
* `launchSynchronously` and subsequent functions got improved mechanism
|
||||
* New method `safelyWithResult`
|
||||
|
||||
## 0.5.5
|
||||
|
||||
* `Versions`
|
||||
* `Ktor`: `1.5.4` -> `1.6.0`
|
||||
|
||||
## 0.5.4
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `2.1.0` -> `2.1.2`
|
||||
|
||||
## 0.5.3
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.5.0` -> `1.5.10`
|
||||
* `Coroutines`:
|
||||
* Extensions `doInUI` and `doInDefault` were replaced in common and available on any supported platform
|
||||
* Extension `doInIO` replaced into `jvm` and available on any `JVM` platform
|
||||
* Old extension `safelyWithouException` without `onException` has been replaced by its copy with `onException` and
|
||||
default value
|
||||
* New value `defaultSafelyWithoutExceptionHandlerWithNull` which is used in all `*WithoutExceptions` by default
|
||||
* Analogs of `launch` and `async` for `safely` and `safelyWithoutExceptions` were added
|
||||
* Analogs of `runCatching` for `safely` and `safelyWithoutExceptions` were added
|
||||
|
||||
## 0.5.2
|
||||
|
||||
* `Ktor`:
|
||||
* `Client`:
|
||||
* Fixes in `UnifiedRequester`
|
||||
|
||||
## 0.5.1
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin Serialization`: `1.2.0` -> `1.2.1`
|
||||
|
||||
## 0.5.0
|
||||
|
||||
**Notice**: This version is still depend on Kotlin
|
||||
Exposed 0.31.1. That means that this version
|
||||
may work improperly in modules based on Kotlin
|
||||
Exposed
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin Exposed`: `0.30.2` -> `0.31.1`
|
||||
* `Kotlin Coroutines`: `1.4.3` -> `1.5.0`
|
||||
* `RecyclerView`: `1.1.0` -> `1.2.0`
|
||||
* `Ktor`: `1.5.3` -> `1.5.4`
|
||||
* `Klock`: `2.0.7` -> `2.1.0`
|
||||
* `UUID`: `0.2.4` -> `0.3.0`
|
||||
* **ALL DEPRECATIONS WERE REMOVED**
|
||||
* `Android`:
|
||||
* `Alerts`:
|
||||
* `RecyclerView`:
|
||||
* Classes `ActionViewHolder` and `ActionsRecyclerViewAdapter` became public
|
||||
* `Coroutines`:
|
||||
* New extension and function `doSynchronously` which are the same as `launchSynchronously`
|
||||
* New extensions `doInDefault` and `doInIO`
|
||||
|
||||
## 0.4.36
|
||||
|
||||
* All `Android` targets inside common mpp modules now includes JVM code
|
||||
|
@@ -15,7 +15,7 @@ data class AlertAction(
|
||||
val callback: (DialogInterface) -> Unit
|
||||
)
|
||||
|
||||
private class ActionViewHolder(
|
||||
class ActionViewHolder(
|
||||
container: ViewGroup, dialogInterfaceGetter: () -> DialogInterface
|
||||
) : AbstractStandardViewHolder<AlertAction>(container, android.R.layout.simple_list_item_1) {
|
||||
private lateinit var action: AlertAction
|
||||
@@ -34,10 +34,10 @@ private class ActionViewHolder(
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionsRecyclerViewAdapter(
|
||||
data: List<AlertAction>,
|
||||
class ActionsRecyclerViewAdapter(
|
||||
override val data: List<AlertAction>,
|
||||
private val dialogInterfaceGetter: () -> DialogInterface
|
||||
) : RecyclerViewAdapter<AlertAction>(data) {
|
||||
) : RecyclerViewAdapter<AlertAction>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<AlertAction> = ActionViewHolder(
|
||||
parent, dialogInterfaceGetter
|
||||
)
|
||||
|
@@ -11,6 +11,7 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
api project(":micro_utils.common")
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
|
@@ -1,12 +1,21 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
|
||||
abstract class RecyclerViewAdapter<T>(
|
||||
val data: List<T>
|
||||
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||
abstract class RecyclerViewAdapter<T>: RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||
protected abstract val data: List<T>
|
||||
|
||||
private val _dataCountState by lazy {
|
||||
MutableStateFlow<Int>(data.size)
|
||||
}
|
||||
val dataCountState: StateFlow<Int> by lazy {
|
||||
_dataCountState.asStateFlow()
|
||||
}
|
||||
|
||||
var emptyView: View? = null
|
||||
set(value) {
|
||||
field = value
|
||||
@@ -18,31 +27,37 @@ abstract class RecyclerViewAdapter<T>(
|
||||
object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeChanged(positionStart, itemCount)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
|
||||
super.onItemRangeChanged(positionStart, itemCount, payload)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeRemoved(positionStart, itemCount)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
|
||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
super.onItemRangeInserted(positionStart, itemCount)
|
||||
_dataCountState.value = data.size
|
||||
checkEmpty()
|
||||
}
|
||||
}
|
||||
@@ -58,7 +73,7 @@ abstract class RecyclerViewAdapter<T>(
|
||||
|
||||
private fun checkEmpty() {
|
||||
emptyView ?. let {
|
||||
if (data.isEmpty()) {
|
||||
if (dataCountState.value == 0) {
|
||||
it.visibility = View.VISIBLE
|
||||
} else {
|
||||
it.visibility = View.GONE
|
||||
@@ -66,3 +81,11 @@ abstract class RecyclerViewAdapter<T>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> RecyclerViewAdapter(
|
||||
data: List<T>,
|
||||
onCreateViewHolder: (parent: ViewGroup, viewType: Int) -> AbstractViewHolder<T>
|
||||
) = object : RecyclerViewAdapter<T>() {
|
||||
override val data: List<T> = data
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<T> = onCreateViewHolder(parent, viewType)
|
||||
}
|
||||
|
@@ -0,0 +1,50 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import dev.inmo.micro_utils.common.Diff
|
||||
import dev.inmo.micro_utils.common.PreviewFeature
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@PreviewFeature("This feature in preview state and may contains different bugs. " +
|
||||
"Besides, this feature can be changed in future in non-compatible way")
|
||||
abstract class StateFlowBasedRecyclerViewAdapter<T>(
|
||||
listeningScope: CoroutineScope,
|
||||
dataState: StateFlow<List<T>>
|
||||
) : RecyclerViewAdapter<T>() {
|
||||
override var data: List<T> = emptyList()
|
||||
|
||||
init {
|
||||
dataState.onEach {
|
||||
try {
|
||||
val diffForRemoves = Diff(data, it)
|
||||
val removedIndexes = diffForRemoves.removed.map { it.index }
|
||||
val leftRemove = removedIndexes.toMutableList()
|
||||
data = data.filterIndexed { i, _ ->
|
||||
if (i in leftRemove) {
|
||||
leftRemove.remove(i)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
removedIndexes.sortedDescending().forEach {
|
||||
notifyItemRemoved(it)
|
||||
}
|
||||
}
|
||||
val diffAddsAndReplaces = Diff(data, it)
|
||||
data = it
|
||||
withContext(Dispatchers.Main) {
|
||||
diffAddsAndReplaces.replaced.forEach { (from, to) ->
|
||||
notifyItemMoved(from.index, to.index)
|
||||
}
|
||||
diffAddsAndReplaces.added.forEach {
|
||||
notifyItemInserted(it.index)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// currently do nothing
|
||||
}
|
||||
}.launchIn(listeningScope)
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
@@ -8,7 +7,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.2'
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
||||
@@ -20,10 +19,8 @@ buildscript {
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
}
|
||||
|
||||
// temporal crutch until legacy tests will be stabled or legacy target will be removed
|
||||
|
@@ -5,3 +5,18 @@ plugins {
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.coroutines")
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.coroutines")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ package dev.inmo.micro_utils.common
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
AnnotationTarget.TYPE_PARAMETER
|
||||
)
|
||||
annotation class PreviewFeature
|
||||
annotation class PreviewFeature(val message: String = "It is possible, that behaviour of this thing will be changed or removed in future releases")
|
||||
|
||||
@RequiresOptIn(
|
||||
"This thing is marked as warned. See message of warn to get more info",
|
||||
|
@@ -1,89 +0,0 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlin.experimental.and
|
||||
|
||||
private const val BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
private const val BASE64_MASK: Byte = 0x3f
|
||||
private const val BASE64_PAD = '='
|
||||
private val BASE64_INVERSE_ALPHABET = IntArray(256) {
|
||||
BASE64_ALPHABET.indexOf(it.toChar())
|
||||
}
|
||||
|
||||
internal fun Int.toBase64(): Char = BASE64_ALPHABET[this]
|
||||
internal fun Byte.fromBase64(): Byte = BASE64_INVERSE_ALPHABET[toInt() and 0xff].toByte() and BASE64_MASK
|
||||
|
||||
const val replacedInCrypto = "Replaced in crypto subproject"
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("EncodedBase64String", "dev.inmo.micro_utils.crypto"))
|
||||
typealias EncodedBase64String = String
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("EncodedByteArray", "dev.inmo.micro_utils.crypto"))
|
||||
typealias EncodedByteArray = ByteArray
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64String()", "dev.inmo.micro_utils.crypto"))
|
||||
fun String.encodeBase64String(): EncodedBase64String = encodeToByteArray().encodeBase64String()
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64()", "dev.inmo.micro_utils.crypto"))
|
||||
fun String.encodeBase64(): EncodedByteArray = encodeToByteArray().encodeBase64()
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64String()", "dev.inmo.micro_utils.crypto"))
|
||||
fun ByteArray.encodeBase64String(): EncodedBase64String = buildString {
|
||||
var i = 0
|
||||
while (this@encodeBase64String.size > i) {
|
||||
val read = kotlin.math.min(3, this@encodeBase64String.size - i)
|
||||
val data = ByteArray(3) {
|
||||
if (it < read) {
|
||||
this@encodeBase64String[it + i]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
val padSize = (data.size - read) * 8 / 6
|
||||
val chunk = ((data[0].toInt() and 0xFF) shl 16) or
|
||||
((data[1].toInt() and 0xFF) shl 8) or
|
||||
(data[2].toInt() and 0xFF)
|
||||
|
||||
for (index in data.size downTo padSize) {
|
||||
val char = (chunk shr (6 * index)) and BASE64_MASK.toInt()
|
||||
append(char.toBase64())
|
||||
}
|
||||
|
||||
repeat(padSize) { append(BASE64_PAD) }
|
||||
|
||||
i += read
|
||||
}
|
||||
}
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64()", "dev.inmo.micro_utils.crypto"))
|
||||
fun ByteArray.encodeBase64(): EncodedByteArray = encodeBase64String().encodeToByteArray()
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64()", "dev.inmo.micro_utils.crypto"))
|
||||
fun EncodedBase64String.decodeBase64() = dropLastWhile { it == BASE64_PAD }.encodeToByteArray().decodeBase64()
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64String()", "dev.inmo.micro_utils.crypto"))
|
||||
fun EncodedBase64String.decodeBase64String() = decodeBase64().decodeToString()
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64()", "dev.inmo.micro_utils.crypto"))
|
||||
fun EncodedByteArray.decodeBase64(): ByteArray {
|
||||
val result = mutableListOf<Byte>()
|
||||
val data = ByteArray(4)
|
||||
(0 until size step 4).forEach { i ->
|
||||
var read = 0
|
||||
for (j in 0 until 4) {
|
||||
if (j + i < size) {
|
||||
data[j] = get(j + i)
|
||||
read++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
val chunk = data.foldIndexed(0) { index, result, current ->
|
||||
result or (current.fromBase64().toInt() shl ((3 - index) * 6))
|
||||
}
|
||||
for (index in data.size - 2 downTo (data.size - read)) {
|
||||
val origin = (chunk shr (8 * index)) and 0xff
|
||||
result.add(origin.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
return result.toByteArray()
|
||||
}
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64String()", "dev.inmo.micro_utils.crypto"))
|
||||
fun EncodedByteArray.decodeBase64String() = decodeBase64().decodeToString()
|
@@ -7,9 +7,17 @@ import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
typealias ByteArrayAllocator = () -> ByteArray
|
||||
typealias SuspendByteArrayAllocator = suspend () -> ByteArray
|
||||
|
||||
val ByteArray.asAllocator: ByteArrayAllocator
|
||||
get() = { this }
|
||||
val ByteArray.asSuspendAllocator: SuspendByteArrayAllocator
|
||||
get() = { this }
|
||||
val ByteArrayAllocator.asSuspendAllocator: SuspendByteArrayAllocator
|
||||
get() = { this() }
|
||||
suspend fun SuspendByteArrayAllocator.asAllocator(): ByteArrayAllocator {
|
||||
return invoke().asAllocator
|
||||
}
|
||||
|
||||
object ByteArrayAllocatorSerializer : KSerializer<ByteArrayAllocator> {
|
||||
private val realSerializer = ByteArraySerializer()
|
||||
@@ -17,7 +25,7 @@ object ByteArrayAllocatorSerializer : KSerializer<ByteArrayAllocator> {
|
||||
|
||||
override fun deserialize(decoder: Decoder): ByteArrayAllocator {
|
||||
val bytes = realSerializer.deserialize(decoder)
|
||||
return { bytes }
|
||||
return bytes.asAllocator
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ByteArrayAllocator) {
|
||||
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
fun <T> Iterable<T?>.firstNotNull() = first { it != null }!!
|
@@ -0,0 +1,31 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class FileName(val string: String) {
|
||||
val name: String
|
||||
get() = string.takeLastWhile { it != '/' }
|
||||
val extension: String
|
||||
get() = name.takeLastWhile { it != '.' }
|
||||
val nameWithoutExtension: String
|
||||
get() {
|
||||
val filename = name
|
||||
return filename.indexOfLast { it == '.' }.takeIf { it > -1 } ?.let {
|
||||
filename.substring(0, it)
|
||||
} ?: filename
|
||||
}
|
||||
override fun toString(): String = string
|
||||
}
|
||||
|
||||
|
||||
@PreviewFeature
|
||||
expect class MPPFile
|
||||
|
||||
expect val MPPFile.filename: FileName
|
||||
expect val MPPFile.filesize: Long
|
||||
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||
suspend fun MPPFile.bytes() = bytesAllocator()
|
||||
|
@@ -1,33 +0,0 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
|
||||
class Base64Text {
|
||||
|
||||
val testText = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"""
|
||||
val base64Text = """TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgpTZWQgdXQgcGVyc3BpY2lhdGlzIHVuZGUgb21uaXMgaXN0ZSBuYXR1cyBlcnJvciBzaXQgdm9sdXB0YXRlbSBhY2N1c2FudGl1bSBkb2xvcmVtcXVlIGxhdWRhbnRpdW0sIHRvdGFtIHJlbSBhcGVyaWFtLCBlYXF1ZSBpcHNhIHF1YWUgYWIgaWxsbyBpbnZlbnRvcmUgdmVyaXRhdGlzIGV0IHF1YXNpIGFyY2hpdGVjdG8gYmVhdGFlIHZpdGFlIGRpY3RhIHN1bnQgZXhwbGljYWJvLiBOZW1vIGVuaW0gaXBzYW0gdm9sdXB0YXRlbSBxdWlhIHZvbHVwdGFzIHNpdCBhc3Blcm5hdHVyIGF1dCBvZGl0IGF1dCBmdWdpdCwgc2VkIHF1aWEgY29uc2VxdXVudHVyIG1hZ25pIGRvbG9yZXMgZW9zIHF1aSByYXRpb25lIHZvbHVwdGF0ZW0gc2VxdWkgbmVzY2l1bnQuIE5lcXVlIHBvcnJvIHF1aXNxdWFtIGVzdCwgcXVpIGRvbG9yZW0gaXBzdW0gcXVpYSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIsIGFkaXBpc2NpIHZlbGl0LCBzZWQgcXVpYSBub24gbnVtcXVhbSBlaXVzIG1vZGkgdGVtcG9yYSBpbmNpZHVudCB1dCBsYWJvcmUgZXQgZG9sb3JlIG1hZ25hbSBhbGlxdWFtIHF1YWVyYXQgdm9sdXB0YXRlbS4gVXQgZW5pbSBhZCBtaW5pbWEgdmVuaWFtLCBxdWlzIG5vc3RydW0gZXhlcmNpdGF0aW9uZW0gdWxsYW0gY29ycG9yaXMgc3VzY2lwaXQgbGFib3Jpb3NhbSwgbmlzaSB1dCBhbGlxdWlkIGV4IGVhIGNvbW1vZGkgY29uc2VxdWF0dXI/IFF1aXMgYXV0ZW0gdmVsIGV1bSBpdXJlIHJlcHJlaGVuZGVyaXQgcXVpIGluIGVhIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIHF1YW0gbmloaWwgbW9sZXN0aWFlIGNvbnNlcXVhdHVyLCB2ZWwgaWxsdW0gcXVpIGRvbG9yZW0gZXVtIGZ1Z2lhdCBxdW8gdm9sdXB0YXMgbnVsbGEgcGFyaWF0dXI/"""
|
||||
|
||||
@Test
|
||||
fun decodeEncode() {
|
||||
val encoded = testText.encodeBase64String()
|
||||
assertEquals(base64Text, encoded)
|
||||
val decoded = encoded.decodeBase64String()
|
||||
assertEquals(testText, decoded)
|
||||
}
|
||||
|
||||
val urlTestText = "https://example.com?isitexample=1"
|
||||
val urlTestBase64Text = "aHR0cHM6Ly9leGFtcGxlLmNvbT9pc2l0ZXhhbXBsZT0x"
|
||||
|
||||
@Test
|
||||
fun decodeEncodeUrl() {
|
||||
val encoded = urlTestText.encodeBase64String()
|
||||
assertEquals(urlTestBase64Text, encoded)
|
||||
val decoded = encoded.decodeBase64String()
|
||||
assertEquals(urlTestText, decoded)
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import org.khronos.webgl.ArrayBuffer
|
||||
import org.w3c.dom.ErrorEvent
|
||||
import org.w3c.files.File
|
||||
import org.w3c.files.FileReader
|
||||
import kotlin.js.Promise
|
||||
|
||||
actual typealias MPPFile = File
|
||||
|
||||
fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
|
||||
val reader = FileReader()
|
||||
reader.onload = {
|
||||
success((reader.result as ArrayBuffer).toByteArray())
|
||||
Unit
|
||||
}
|
||||
reader.onerror = {
|
||||
failure(Exception((it as ErrorEvent).message))
|
||||
Unit
|
||||
}
|
||||
reader.readAsArrayBuffer(this)
|
||||
}
|
||||
|
||||
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await()
|
||||
|
||||
actual val MPPFile.filename: FileName
|
||||
get() = FileName(name)
|
||||
actual val MPPFile.filesize: Long
|
||||
get() = size.toLong()
|
||||
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
|
||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||
get() = ::dirtyReadBytes
|
@@ -0,0 +1,8 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.js.Promise
|
||||
|
||||
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
|
||||
then({ cont.resume(it) }, { cont.resumeWithException(it) })
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.doInIO
|
||||
import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine
|
||||
import java.io.File
|
||||
|
||||
actual typealias MPPFile = File
|
||||
|
||||
actual val MPPFile.filename: FileName
|
||||
get() = FileName(name)
|
||||
actual val MPPFile.filesize: Long
|
||||
get() = length()
|
||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||
get() = {
|
||||
doInIO {
|
||||
doOutsideOfCoroutine {
|
||||
readBytes()
|
||||
}
|
||||
}
|
||||
}
|
@@ -33,3 +33,15 @@ fun View.toggleVisibility(goneOnHide: Boolean = true) {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
fun View.changeVisibility(show: Boolean = !isShown, goneOnHide: Boolean = true) {
|
||||
if (show) {
|
||||
show()
|
||||
} else {
|
||||
if (goneOnHide) {
|
||||
gone()
|
||||
} else {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,4 +19,4 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ inline fun CoroutineScope.createSafeActionsActor(
|
||||
suspend fun <T> Channel<suspend () -> Unit>.doWithSuspending(
|
||||
action: ActorAction<T>
|
||||
) = suspendCoroutine<T> {
|
||||
offer {
|
||||
trySend {
|
||||
safely({ e -> it.resumeWithException(e) }) {
|
||||
it.resume(action())
|
||||
}
|
||||
|
@@ -0,0 +1,23 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
inline val UI
|
||||
get() = Dispatchers.Main
|
||||
inline val Default
|
||||
get() = Dispatchers.Default
|
||||
|
||||
suspend inline fun <T> doIn(context: CoroutineContext, noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||
context,
|
||||
block
|
||||
)
|
||||
|
||||
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = doIn(
|
||||
UI,
|
||||
block
|
||||
)
|
||||
suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = doIn(
|
||||
Default,
|
||||
block
|
||||
)
|
@@ -0,0 +1,6 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!!
|
@@ -4,6 +4,8 @@ package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
/**
|
||||
* Shortcut for chain if [Flow.onEach] and [Flow.launchIn]
|
||||
@@ -29,9 +31,10 @@ inline fun <T> Flow<T>.subscribeSafely(
|
||||
*/
|
||||
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
||||
scope: CoroutineScope,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribe(scope) {
|
||||
safelyWithoutExceptions {
|
||||
safelyWithoutExceptions(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,118 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
private class SubscribeAsyncReceiver<T>(
|
||||
val scope: CoroutineScope,
|
||||
output: suspend SubscribeAsyncReceiver<T>.(T) -> Unit
|
||||
) {
|
||||
private val dataChannel: Channel<T> = Channel(Channel.UNLIMITED)
|
||||
val channel: SendChannel<T>
|
||||
get() = dataChannel
|
||||
|
||||
init {
|
||||
scope.launchSafelyWithoutExceptions {
|
||||
for (data in dataChannel) {
|
||||
output(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isEmpty(): Boolean = dataChannel.isEmpty
|
||||
}
|
||||
|
||||
private sealed interface AsyncSubscriptionCommand<T, M> {
|
||||
suspend operator fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>)
|
||||
}
|
||||
private data class AsyncSubscriptionCommandData<T, M>(
|
||||
val data: T,
|
||||
val scope: CoroutineScope,
|
||||
val markerFactory: suspend (T) -> M,
|
||||
val block: suspend (T) -> Unit,
|
||||
val onEmpty: suspend (M) -> Unit
|
||||
) : AsyncSubscriptionCommand<T, M> {
|
||||
override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) {
|
||||
val marker = markerFactory(data)
|
||||
markersMap.getOrPut(marker) {
|
||||
SubscribeAsyncReceiver(scope.LinkedSupervisorScope()) {
|
||||
safelyWithoutExceptions { block(it) }
|
||||
if (isEmpty()) {
|
||||
onEmpty(marker)
|
||||
}
|
||||
}
|
||||
}.channel.send(data)
|
||||
}
|
||||
}
|
||||
|
||||
private data class AsyncSubscriptionCommandClearReceiver<T, M>(
|
||||
val marker: M
|
||||
) : AsyncSubscriptionCommand<T, M> {
|
||||
override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) {
|
||||
val receiver = markersMap[marker]
|
||||
if (receiver ?.isEmpty() == true) {
|
||||
markersMap.remove(marker)
|
||||
receiver.scope.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, M> Flow<T>.subscribeAsync(
|
||||
scope: CoroutineScope,
|
||||
markerFactory: suspend (T) -> M,
|
||||
block: suspend (T) -> Unit
|
||||
): Job {
|
||||
val subscope = scope.LinkedSupervisorScope()
|
||||
val markersMap = mutableMapOf<M, SubscribeAsyncReceiver<T>>()
|
||||
val actor = subscope.actor<AsyncSubscriptionCommand<T, M>>(Channel.UNLIMITED) {
|
||||
it.invoke(markersMap)
|
||||
}
|
||||
|
||||
val job = subscribeSafelyWithoutExceptions(subscope) { data ->
|
||||
val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker ->
|
||||
actor.send(
|
||||
AsyncSubscriptionCommandClearReceiver(marker)
|
||||
)
|
||||
}
|
||||
actor.send(dataCommand)
|
||||
}
|
||||
|
||||
job.invokeOnCompletion { if (subscope.isActive) subscope.cancel() }
|
||||
|
||||
return job
|
||||
}
|
||||
|
||||
inline fun <T, M> Flow<T>.subscribeSafelyAsync(
|
||||
scope: CoroutineScope,
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safely(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
|
||||
scope: CoroutineScope,
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safelyWithoutExceptions(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
|
||||
scope: CoroutineScope,
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safelyWithoutExceptions({ /* do nothing */}) {
|
||||
block(it)
|
||||
}
|
||||
}
|
@@ -22,34 +22,6 @@ var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for [SafelyExceptionHandler] which can be used in [CoroutineContext.get] to get current default
|
||||
* [SafelyExceptionHandler]
|
||||
*/
|
||||
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||
class SafelyExceptionHandlerKey<T> : CoroutineContext.Key<SafelyExceptionHandler<T>>
|
||||
|
||||
/**
|
||||
* Shortcut for creating instance of [SafelyExceptionHandlerKey]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||
inline fun <T> safelyExceptionHandlerKey() = SafelyExceptionHandlerKey<T>()
|
||||
|
||||
/**
|
||||
* Wrapper for [ExceptionHandler] which can be used in [CoroutineContext] to set local (for [CoroutineContext]) default
|
||||
* [ExceptionHandler]. To get it use [CoroutineContext.get] with key [SafelyExceptionHandlerKey]
|
||||
*
|
||||
* @see SafelyExceptionHandlerKey
|
||||
* @see ExceptionHandler
|
||||
*/
|
||||
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandler", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||
class SafelyExceptionHandler<T>(
|
||||
val handler: ExceptionHandler<T>
|
||||
) : CoroutineContext.Element {
|
||||
override val key: CoroutineContext.Key<*> = safelyExceptionHandlerKey<T>()
|
||||
}
|
||||
|
||||
/**
|
||||
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
|
||||
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
|
||||
@@ -114,6 +86,9 @@ suspend fun <T> safelyWithContextExceptionHandler(
|
||||
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
|
||||
* * [defaultSafelyExceptionHandler]
|
||||
*
|
||||
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
|
||||
* be called [onException]
|
||||
*
|
||||
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
||||
* exception will be available for catching
|
||||
*
|
||||
@@ -133,24 +108,42 @@ suspend inline fun <T> safely(
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <T> runCatchingSafely(
|
||||
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): Result<T> = runCatching {
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
suspend inline fun <T> safelyWithResult(
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||
|
||||
/**
|
||||
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
|
||||
* returning null at one time
|
||||
*
|
||||
* @see safelyWithoutExceptions
|
||||
* @see launchSafelyWithoutExceptions
|
||||
* @see asyncSafelyWithoutExceptions
|
||||
*/
|
||||
val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
|
||||
defaultSafelyWithoutExceptionHandler.invoke(it)
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
|
||||
* result from exception (instead of throwing it)
|
||||
* result from exception (instead of throwing it, by default always returns null)
|
||||
*/
|
||||
suspend inline fun <T> safelyWithoutExceptions(
|
||||
noinline onException: ExceptionHandler<T?>,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T? = safely(onException, block)
|
||||
|
||||
/**
|
||||
* Shortcut for [safely] without exception handler (instead of this you will always receive null as a result)
|
||||
*/
|
||||
suspend inline fun <T> safelyWithoutExceptions(
|
||||
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T? = safelyWithoutExceptions(
|
||||
{
|
||||
defaultSafelyWithoutExceptionHandler.invoke(it)
|
||||
null
|
||||
},
|
||||
block
|
||||
)
|
||||
): Result<T?> = runCatching {
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
||||
|
@@ -0,0 +1,41 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
inline fun CoroutineScope.launchSafely(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> Unit
|
||||
) = launch(context, start) {
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
inline fun CoroutineScope.launchSafelyWithoutExceptions(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> Unit
|
||||
) = launch(context, start) {
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
||||
|
||||
inline fun <T> CoroutineScope.asyncSafely(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
) = async(context, start) {
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
) = async(context, start) {
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun CoroutineContext.LinkedSupervisorJob(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = SupervisorJob(job).let { if (additionalContext != null) it + additionalContext else it }
|
||||
fun CoroutineScope.LinkedSupervisorJob(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = coroutineContext.LinkedSupervisorJob(additionalContext)
|
||||
|
||||
fun CoroutineScope.LinkedSupervisorScope(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = CoroutineScope(
|
||||
coroutineContext + LinkedSupervisorJob(additionalContext)
|
||||
)
|
@@ -0,0 +1,11 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
val IO
|
||||
get() = Dispatchers.IO
|
||||
|
||||
suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = doIn(
|
||||
IO,
|
||||
block
|
||||
)
|
@@ -3,28 +3,24 @@ package dev.inmo.micro_utils.coroutines
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
|
||||
var throwable: Throwable? = null
|
||||
var result: T? = null
|
||||
val objectToSynchronize = java.lang.Object()
|
||||
val launchCallback = {
|
||||
var result: Result<T>? = null
|
||||
val objectToSynchronize = Object()
|
||||
synchronized(objectToSynchronize) {
|
||||
launch {
|
||||
safely(
|
||||
{
|
||||
throwable = it
|
||||
}
|
||||
) {
|
||||
result = block()
|
||||
}
|
||||
result = safelyWithResult(block)
|
||||
}.invokeOnCompletion {
|
||||
synchronized(objectToSynchronize) {
|
||||
objectToSynchronize.notifyAll()
|
||||
}
|
||||
}
|
||||
while (result == null) {
|
||||
objectToSynchronize.wait()
|
||||
}
|
||||
}
|
||||
synchronized(objectToSynchronize) {
|
||||
launchCallback()
|
||||
objectToSynchronize.wait()
|
||||
}
|
||||
throw throwable ?: return result!!
|
||||
return result!!.getOrThrow()
|
||||
}
|
||||
|
||||
fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block)
|
||||
|
||||
fun <T> CoroutineScope.doSynchronously(block: suspend CoroutineScope.() -> T): T = launchSynchronously(block)
|
||||
fun <T> doSynchronously(block: suspend CoroutineScope.() -> T): T = launchSynchronously(block)
|
||||
|
@@ -1,9 +1,8 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.asDeferred
|
||||
import dev.inmo.micro_utils.coroutines.launchSynchronously
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.test.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DoWithFirstTests {
|
||||
@Test
|
||||
|
@@ -1,10 +0,0 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||
Dispatchers.Main,
|
||||
block
|
||||
)
|
@@ -1,5 +1,30 @@
|
||||
apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
||||
ext {
|
||||
jvmKotlinFolderFile = {
|
||||
String sep = File.separator
|
||||
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
|
||||
}
|
||||
|
||||
enableIncludingJvmCodeInAndroidPart = {
|
||||
File jvmKotlinFolder = jvmKotlinFolderFile()
|
||||
if (jvmKotlinFolder.exists()) {
|
||||
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
|
||||
}
|
||||
}
|
||||
|
||||
disableIncludingJvmCodeInAndroidPart = {
|
||||
File jvmKotlinFolder = jvmKotlinFolderFile()
|
||||
String[] oldDirs = android.sourceSets.main.java.srcDirs
|
||||
android.sourceSets.main.java.srcDirs = []
|
||||
for (oldDir in oldDirs) {
|
||||
if (oldDir != jvmKotlinFolder.path) {
|
||||
android.sourceSets.main.java.srcDirs += oldDir
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
||||
buildToolsVersion "$android_buildToolsVersion"
|
||||
@@ -35,6 +60,8 @@ android {
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
String sep = File.separator
|
||||
main.java.srcDirs += "src${sep}main${sep}kotlin"
|
||||
enableIncludingJvmCodeInAndroidPart()
|
||||
}
|
||||
}
|
||||
|
@@ -7,14 +7,13 @@ plugins {
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js(BOTH) {
|
||||
js(IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
17
fsm/common/build.gradle
Normal file
17
fsm/common/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.coroutines")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.fsm.common
|
||||
|
||||
interface State {
|
||||
val context: Any
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
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) }
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.fsm.common
|
||||
|
||||
fun interface StatesHandler<I : State> {
|
||||
suspend fun StatesMachine.handleState(state: I): State?
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package dev.inmo.micro_utils.fsm.common
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
|
||||
private suspend fun <I : State> StatesMachine.launchStateHandling(
|
||||
state: State,
|
||||
handlers: List<StateHandlerHolder<out I>>
|
||||
): State? {
|
||||
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
||||
handleState(state)
|
||||
}
|
||||
}
|
||||
|
||||
class StatesMachine (
|
||||
private val statesManager: StatesManager,
|
||||
private val handlers: List<StateHandlerHolder<*>>
|
||||
) : StatesHandler<State> {
|
||||
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
|
||||
|
||||
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||
val statePerformer: suspend (State) -> Unit = { state: State ->
|
||||
val newState = launchStateHandling(state, handlers)
|
||||
if (newState != null) {
|
||||
statesManager.update(state, newState)
|
||||
} else {
|
||||
statesManager.endChain(state)
|
||||
}
|
||||
}
|
||||
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
||||
launch { statePerformer(it) }
|
||||
}
|
||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||
launch { statePerformer(it.second) }
|
||||
}
|
||||
|
||||
statesManager.getActiveStates().forEach {
|
||||
launch { statePerformer(it) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun startChain(state: State) {
|
||||
statesManager.startChain(state)
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
package dev.inmo.micro_utils.fsm.common
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
interface StatesManager {
|
||||
val onChainStateUpdated: Flow<Pair<State, State>>
|
||||
val onStartChain: Flow<State>
|
||||
val onEndChain: Flow<State>
|
||||
|
||||
|
||||
/**
|
||||
* Must set current set using [State.context]
|
||||
*/
|
||||
suspend fun update(old: State, new: State)
|
||||
|
||||
/**
|
||||
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
|
||||
* busy by the other [State]
|
||||
*/
|
||||
suspend fun startChain(state: State)
|
||||
|
||||
/**
|
||||
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
|
||||
* ignored
|
||||
*/
|
||||
suspend fun endChain(state: State)
|
||||
|
||||
suspend fun getActiveStates(): List<State>
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,35 @@
|
||||
package dev.inmo.micro_utils.fsm.common.dsl
|
||||
|
||||
import dev.inmo.micro_utils.fsm.common.*
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FSMBuilder(
|
||||
var statesManager: StatesManager = InMemoryStatesManager()
|
||||
) {
|
||||
private var states = mutableListOf<StateHandlerHolder<*>>()
|
||||
|
||||
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) {
|
||||
states.add(StateHandlerHolder(kClass, false, handler))
|
||||
}
|
||||
|
||||
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) {
|
||||
states.add(StateHandlerHolder(kClass, true, handler))
|
||||
}
|
||||
|
||||
fun build() = StatesMachine(
|
||||
statesManager,
|
||||
states.toList()
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) {
|
||||
add(I::class, handler)
|
||||
}
|
||||
|
||||
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
|
||||
addStrict(I::class, handler)
|
||||
}
|
||||
|
||||
fun buildFSM(
|
||||
block: FSMBuilder.() -> Unit
|
||||
): StatesMachine = FSMBuilder().apply(block).build()
|
53
fsm/common/src/jvmTest/kotlin/PlayableMain.kt
Normal file
53
fsm/common/src/jvmTest/kotlin/PlayableMain.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
import dev.inmo.micro_utils.fsm.common.*
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.strictlyOn
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
sealed interface TrafficLightState : State {
|
||||
val trafficLightNumber: Int
|
||||
override val context: Int
|
||||
get() = trafficLightNumber
|
||||
}
|
||||
data class GreenCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||
data class YellowCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||
data class RedCommon(override val trafficLightNumber: Int) : TrafficLightState
|
||||
|
||||
class PlayableMain {
|
||||
// @Test
|
||||
fun test() {
|
||||
runBlocking {
|
||||
val countOfTrafficLights = 10
|
||||
val initialStates = (0 until countOfTrafficLights).map {
|
||||
when (0/*Random.nextInt(3)*/) {
|
||||
0 -> GreenCommon(it)
|
||||
1 -> YellowCommon(it)
|
||||
else -> RedCommon(it)
|
||||
}
|
||||
}
|
||||
|
||||
val statesManager = InMemoryStatesManager()
|
||||
|
||||
val machine = buildFSM {
|
||||
strictlyOn<GreenCommon> {
|
||||
delay(1000L)
|
||||
YellowCommon(it.context).also(::println)
|
||||
}
|
||||
strictlyOn<YellowCommon> {
|
||||
delay(1000L)
|
||||
RedCommon(it.context).also(::println)
|
||||
}
|
||||
strictlyOn<RedCommon> {
|
||||
delay(1000L)
|
||||
GreenCommon(it.context).also(::println)
|
||||
}
|
||||
this.statesManager = statesManager
|
||||
}
|
||||
|
||||
initialStates.forEach { machine.startChain(it) }
|
||||
|
||||
val scope = CoroutineScope(Dispatchers.Default)
|
||||
machine.start(scope).join()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
1
fsm/common/src/main/AndroidManifest.xml
Normal file
1
fsm/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.fsm.common"/>
|
18
fsm/repos/common/build.gradle
Normal file
18
fsm/repos/common/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.fsm.common")
|
||||
api project(":micro_utils.repos.common")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
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
|
||||
)
|
||||
)
|
1
fsm/repos/common/src/main/AndroidManifest.xml
Normal file
1
fsm/repos/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.fsm.repos.common"/>
|
@@ -5,44 +5,45 @@ kotlin.incremental=true
|
||||
kotlin.incremental.js=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
org.gradle.jvmargs=-Xmx2g
|
||||
|
||||
kotlin_version=1.4.32
|
||||
kotlin_coroutines_version=1.4.3
|
||||
kotlin_serialisation_core_version=1.1.0
|
||||
kotlin_exposed_version=0.30.2
|
||||
kotlin_version=1.5.21
|
||||
kotlin_coroutines_version=1.5.1
|
||||
kotlin_serialisation_core_version=1.2.2
|
||||
kotlin_exposed_version=0.33.1
|
||||
|
||||
ktor_version=1.5.3
|
||||
ktor_version=1.6.2
|
||||
|
||||
klockVersion=2.0.7
|
||||
klockVersion=2.3.1
|
||||
|
||||
github_release_plugin_version=2.2.12
|
||||
|
||||
uuidVersion=0.2.4
|
||||
uuidVersion=0.3.0
|
||||
|
||||
# ANDROID
|
||||
|
||||
core_ktx_version=1.3.2
|
||||
androidx_recycler_version=1.1.0
|
||||
appcompat_version=1.2.0
|
||||
core_ktx_version=1.6.0
|
||||
androidx_recycler_version=1.2.1
|
||||
appcompat_version=1.3.0
|
||||
|
||||
android_minSdkVersion=19
|
||||
android_compileSdkVersion=30
|
||||
android_buildToolsVersion=30.0.2
|
||||
dexcount_version=2.0.0
|
||||
android_buildToolsVersion=30.0.3
|
||||
dexcount_version=2.1.0-RC01
|
||||
junit_version=4.12
|
||||
test_ext_junit_version=1.1.2
|
||||
espresso_core=3.3.0
|
||||
|
||||
# JS NPM
|
||||
|
||||
crypto_js_version=4.0.0
|
||||
crypto_js_version=4.1.1
|
||||
|
||||
# Dokka
|
||||
|
||||
dokka_version=1.4.30
|
||||
dokka_version=1.5.0
|
||||
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.4.36
|
||||
android_code_version=40
|
||||
version=0.5.19
|
||||
android_code_version=60
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@@ -15,30 +15,21 @@ class UnifiedRequester(
|
||||
suspend fun <ResultType> uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
): ResultType = client.get<StandardKtorSerialInputData>(
|
||||
url
|
||||
).let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
||||
): ResultType = client.uniget(url, resultDeserializer, serialFormat)
|
||||
|
||||
fun <T> encodeUrlQueryValue(
|
||||
serializationStrategy: SerializationStrategy<T>,
|
||||
value: T
|
||||
) = serialFormat.encodeHex(
|
||||
serializationStrategy,
|
||||
value
|
||||
) = serializationStrategy.encodeUrlQueryValue(
|
||||
value,
|
||||
serialFormat
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = client.post<StandardKtorSerialInputData>(url) {
|
||||
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
}.let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
@@ -51,14 +42,30 @@ val defaultRequester = UnifiedRequester()
|
||||
|
||||
suspend fun <ResultType> HttpClient.uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = defaultRequester.uniget(url, resultDeserializer)
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = get<StandardKtorSerialInputData>(
|
||||
url
|
||||
).let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
||||
|
||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = defaultRequester.encodeUrlQueryValue(this, value)
|
||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
|
||||
value: T,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = serialFormat.encodeHex(
|
||||
this,
|
||||
value
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> HttpClient.unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = defaultRequester.unipost(url, bodyInfo, resultDeserializer)
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = post<StandardKtorSerialInputData>(url) {
|
||||
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
}.let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
private suspend fun DefaultWebSocketSession.checkReceivedAndCloseIfExists() {
|
||||
if (incoming.poll() != null) {
|
||||
if (incoming.tryReceive() != null) {
|
||||
close()
|
||||
throw CorrectCloseException
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
data class ApplicationCachingHeadersConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun CachingHeaders.Configuration.invoke() }
|
||||
fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(CachingHeaders) {
|
||||
|
@@ -10,17 +10,18 @@ import kotlinx.serialization.Serializable
|
||||
class ApplicationRoutingConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun Route.invoke() }
|
||||
fun interface Element { operator fun Route.invoke() }
|
||||
private val rootInstaller = Element {
|
||||
elements.forEach {
|
||||
it.apply { invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun Application.configure() {
|
||||
try {
|
||||
feature(Routing)
|
||||
} catch (e: IllegalStateException) {
|
||||
install(Routing) {
|
||||
elements.forEach {
|
||||
it.apply { invoke() }
|
||||
}
|
||||
}
|
||||
featureOrNull(Routing) ?.apply {
|
||||
rootInstaller.apply { invoke() }
|
||||
} ?: install(Routing) {
|
||||
rootInstaller.apply { invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
class ApplicationSessionsConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun Sessions.Configuration.invoke() }
|
||||
fun interface Element { operator fun Sessions.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(Sessions) {
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
class StatusPagesConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun StatusPages.Configuration.invoke() }
|
||||
fun interface Element { operator fun StatusPages.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(StatusPages) {
|
||||
|
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"
|
26
language_codes/generator/build.gradle
Normal file
26
language_codes/generator/build.gradle
Normal file
@@ -0,0 +1,26 @@
|
||||
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"
|
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"/>
|
@@ -4,9 +4,7 @@ project.group = "$group"
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.main.kotlinOptions.useIR = true
|
||||
}
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
|
@@ -4,10 +4,8 @@ project.group = "$group"
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.main.kotlinOptions.useIR = true
|
||||
}
|
||||
js (BOTH) {
|
||||
jvm()
|
||||
js (IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
@@ -33,3 +33,8 @@ suspend fun <T> doAllWithCurrentPaging(
|
||||
block
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> doForAllWithCurrentPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
) = doAllWithCurrentPaging(initialPagination, block)
|
||||
|
@@ -5,8 +5,8 @@ import io.ktor.http.Parameters
|
||||
|
||||
val Parameters.extractPagination: Pagination
|
||||
get() = SimplePagination(
|
||||
get("page") ?.toIntOrNull() ?: 0,
|
||||
get("size") ?.toIntOrNull() ?: defaultPaginationPageSize
|
||||
get(paginationPageKey) ?.toIntOrNull() ?: 0,
|
||||
get(paginationSizeKey) ?.toIntOrNull() ?: defaultPaginationPageSize
|
||||
)
|
||||
|
||||
val ApplicationCall.extractPagination: Pagination
|
||||
|
@@ -6,19 +6,29 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
protected val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
protected val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
protected val kvCache: KVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
protected val idGetter: (ObjectType) -> IdType
|
||||
) : CRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
|
||||
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
|
||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo {
|
||||
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
|
||||
kvCache.set(id, it)
|
||||
})
|
||||
|
||||
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
||||
}
|
||||
|
||||
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
kvCache: KVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) : ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
parentRepo,
|
||||
kvCache,
|
||||
idGetter
|
||||
), CRUDRepo<ObjectType, IdType, InputValueType>, WriteCRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
|
||||
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
|
@@ -7,14 +7,19 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class KeyValueCacheRepo<Key,Value>(
|
||||
protected val parentRepo: KeyValueRepo<Key, Value>,
|
||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||
protected val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||
protected val kvCache: KVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : KeyValueRepo<Key,Value> by parentRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
|
||||
) : ReadKeyValueRepo<Key,Value> by parentRepo {
|
||||
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
|
||||
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
|
||||
}
|
||||
|
||||
open class KeyValueCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValueRepo<Key, Value>,
|
||||
kvCache: KVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
|
@@ -11,15 +11,10 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class KeyValuesCacheRepo<Key,Value>(
|
||||
protected val parentRepo: KeyValuesRepo<Key, Value>,
|
||||
protected val kvCache: KVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : KeyValuesRepo<Key,Value> by parentRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
|
||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
|
||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||
protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||
protected val kvCache: KVCache<Key, List<Value>>
|
||||
) : ReadKeyValuesRepo<Key,Value> by parentRepo {
|
||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||
return kvCache.get(k) ?.paginate(
|
||||
pagination.let { if (reversed) it.reverse(count(k)) else it }
|
||||
@@ -35,3 +30,13 @@ open class KeyValuesCacheRepo<Key,Value>(
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v)
|
||||
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
|
||||
}
|
||||
|
||||
open class KeyValuesCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValuesRepo<Key, Value>,
|
||||
kvCache: KVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
|
||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
|
@@ -30,4 +30,6 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disableIncludingJvmCodeInAndroidPart()
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -47,6 +48,7 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun remove(toRemove: Map<Key, List<Value>>)
|
||||
|
||||
suspend fun clear(k: Key)
|
||||
suspend fun clearWithValue(v: Value)
|
||||
|
||||
suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||
toSet.keys.forEach { key -> clear(key) }
|
||||
@@ -87,7 +89,19 @@ suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||
k: Key, vararg v: Value
|
||||
) = set(k, v.toList())
|
||||
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
doWithPagination {
|
||||
val keysResult = keys(v, it)
|
||||
|
||||
if (keysResult.results.isNotEmpty()) {
|
||||
remove(keysResult.results.map { it to listOf(v) })
|
||||
}
|
||||
|
||||
keysResult.currentPageIfNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
|
@@ -114,6 +114,7 @@ open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
|
||||
override suspend fun clearWithValue(v: FromValue) = to.clearWithValue(v.toOutValue())
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
|
@@ -5,26 +5,6 @@ import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
|
||||
|
||||
@Deprecated("Will be removed soon due to redundancy. Can be replaced with other doForAll extensions")
|
||||
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>,
|
||||
crossinline block: (List<T>) -> Unit
|
||||
) {
|
||||
doForAllWithNextPaging {
|
||||
methodCaller(it).also {
|
||||
block(it.results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Will be removed soon due to redundancy. Can be replaced with other doForAll extensions")
|
||||
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.doForAll(
|
||||
crossinline block: (List<T>) -> Unit
|
||||
) = doForAllWithNextPaging {
|
||||
getByPagination(it).also { block(it.results) }
|
||||
}
|
||||
|
||||
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
||||
|
@@ -4,24 +4,6 @@ import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||
|
||||
@Deprecated("Will be removed soon due to redundancy")
|
||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>,
|
||||
block: (List<Pair<Key, Value>>) -> Unit
|
||||
) {
|
||||
doWithPagination {
|
||||
methodCaller(it).also { keys ->
|
||||
block(keys.results.mapNotNull { key -> get(key) ?.let { value -> key to value } })
|
||||
}.nextPageIfNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Will be removed soon due to redundancy")
|
||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||
block: (List<Pair<Key, Value>>) -> Unit
|
||||
) = doForAll({ keys(it, false) }, block)
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
|
@@ -4,34 +4,6 @@ import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||
|
||||
@Deprecated("Will be removed soon due to redundancy")
|
||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>,
|
||||
block: (List<Pair<Key, List<Value>>>) -> Unit
|
||||
) {
|
||||
doWithPagination {
|
||||
methodCaller(it).also { keys ->
|
||||
block(
|
||||
keys.results.mapNotNull { key ->
|
||||
val values = mutableListOf<Value>()
|
||||
doWithPagination {
|
||||
get(key, it).also {
|
||||
values.addAll(it.results)
|
||||
}.nextPageIfNotEmpty()
|
||||
}
|
||||
key to values
|
||||
}
|
||||
)
|
||||
}.nextPageIfNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Will be removed soon due to redundancy")
|
||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||
block: (List<Pair<Key, List<Value>>>) -> Unit
|
||||
) = doForAll({ keys(it, false) }, block)
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
|
@@ -18,19 +18,16 @@ class StandardVersionsRepo<T>(
|
||||
onCreate: suspend T.() -> Unit,
|
||||
onUpdate: suspend T.(from: Int, to: Int) -> Unit
|
||||
) {
|
||||
var savedVersion = proxy.getTableVersion(tableName)
|
||||
if (savedVersion == null) {
|
||||
var currentVersion = proxy.getTableVersion(tableName)
|
||||
if (currentVersion == null) {
|
||||
proxy.database.onCreate()
|
||||
proxy.updateTableVersion(tableName, version)
|
||||
} else {
|
||||
while (savedVersion != null && savedVersion < version) {
|
||||
val newVersion = savedVersion + 1
|
||||
}
|
||||
while (currentVersion == null || currentVersion < version) {
|
||||
val oldVersion = currentVersion ?: 0
|
||||
currentVersion = oldVersion + 1
|
||||
proxy.database.onUpdate(oldVersion, currentVersion)
|
||||
|
||||
proxy.database.onUpdate(savedVersion, newVersion)
|
||||
|
||||
proxy.updateTableVersion(tableName, newVersion)
|
||||
savedVersion = newVersion
|
||||
}
|
||||
proxy.updateTableVersion(tableName, currentVersion)
|
||||
}
|
||||
}
|
||||
}
|
@@ -74,6 +74,19 @@ fun SQLiteDatabase.select(
|
||||
table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
|
||||
)
|
||||
|
||||
fun SQLiteDatabase.selectDistinct(
|
||||
table: String,
|
||||
columns: Array<String>? = null,
|
||||
selection: String? = null,
|
||||
selectionArgs: Array<String>? = null,
|
||||
groupBy: String? = null,
|
||||
having: String? = null,
|
||||
orderBy: String? = null,
|
||||
limit: String? = null
|
||||
) = query(
|
||||
true, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
|
||||
)
|
||||
|
||||
fun makePlaceholders(count: Int): String {
|
||||
return (0 until count).joinToString { "?" }
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import android.database.DatabaseErrorHandler
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.repos.keyvalue.keyValueStore
|
||||
import dev.inmo.micro_utils.repos.versions.*
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
@@ -15,7 +16,8 @@ class StandardSQLHelper(
|
||||
name: String,
|
||||
factory: SQLiteDatabase.CursorFactory? = null,
|
||||
version: Int = 1,
|
||||
errorHandler: DatabaseErrorHandler? = null
|
||||
errorHandler: DatabaseErrorHandler? = null,
|
||||
useSharedPreferencesForVersions: Boolean = false
|
||||
) {
|
||||
val sqlOpenHelper = object : SQLiteOpenHelper(context, name, factory, version, errorHandler) {
|
||||
override fun onCreate(db: SQLiteDatabase?) {}
|
||||
@@ -23,7 +25,16 @@ class StandardSQLHelper(
|
||||
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {}
|
||||
}
|
||||
val versionsRepo: VersionsRepo<SQLiteOpenHelper> by lazy {
|
||||
StandardVersionsRepo(AndroidSQLStandardVersionsRepoProxy(sqlOpenHelper))
|
||||
StandardVersionsRepo(
|
||||
if (useSharedPreferencesForVersions) {
|
||||
KeyValueBasedVersionsRepoProxy(
|
||||
context.keyValueStore("AndroidSPStandardVersionsRepo"),
|
||||
sqlOpenHelper
|
||||
)
|
||||
} else {
|
||||
AndroidSQLStandardVersionsRepoProxy(sqlOpenHelper)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block)
|
||||
|
@@ -50,7 +50,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
||||
override suspend fun deleteById(ids: List<IdType>) {
|
||||
val deleted = mutableListOf<IdType>()
|
||||
helper.blockingWritableTransaction {
|
||||
ids.forEach { id ->
|
||||
for (id in ids) {
|
||||
delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
|
||||
if (it > 0) {
|
||||
deleted.add(id)
|
||||
@@ -58,8 +58,8 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
||||
}
|
||||
}
|
||||
}
|
||||
deleted.forEach {
|
||||
deleteObjectsIdsChannel.emit(it)
|
||||
for (deletedItem in deleted) {
|
||||
deleteObjectsIdsChannel.emit(deletedItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -42,9 +42,9 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
|
||||
init {
|
||||
cachedData ?.let {
|
||||
sharedPreferences.all.forEach {
|
||||
if (it.value != null) {
|
||||
cachedData[it.key] = it.value as Any
|
||||
for ((key, value) in sharedPreferences.all) {
|
||||
if (value != null) {
|
||||
cachedData[key] = value
|
||||
}
|
||||
}
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
@@ -113,7 +113,7 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
|
||||
override suspend fun set(toSet: Map<String, T>) {
|
||||
sharedPreferences.edit {
|
||||
toSet.forEach { (k, v) ->
|
||||
for ((k, v) in toSet) {
|
||||
when(v) {
|
||||
is Int -> putInt(k, v)
|
||||
is Long -> putLong(k, v)
|
||||
@@ -127,16 +127,20 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
}
|
||||
}
|
||||
}
|
||||
toSet.forEach { (k, v) ->
|
||||
for ((k, v) in toSet) {
|
||||
onNewValueChannel.emit(k to v)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unset(toUnset: List<String>) {
|
||||
sharedPreferences.edit {
|
||||
toUnset.forEach { remove(it) }
|
||||
for (item in toUnset) {
|
||||
remove(item)
|
||||
}
|
||||
}
|
||||
for (it in toUnset) {
|
||||
_onValueRemovedFlow.emit(it)
|
||||
}
|
||||
toUnset.forEach { _onValueRemovedFlow.emit(it) }
|
||||
}
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<T>) {
|
||||
|
@@ -3,10 +3,7 @@ package dev.inmo.micro_utils.repos.onetomany
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.content.contentValuesOf
|
||||
import dev.inmo.micro_utils.common.mapNotNullA
|
||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.createPaginationResult
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -20,10 +17,14 @@ private val internalSerialFormat = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
typealias KeyValuesAndroidRepo<Key, Value> = OneToManyAndroidRepo<Key, Value>
|
||||
|
||||
class OneToManyAndroidRepo<Key, Value>(
|
||||
private val tableName: String,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>,
|
||||
private val keyAsString: Key.() -> String,
|
||||
private val valueAsString: Value.() -> String,
|
||||
private val keyFromString: String.() -> Key,
|
||||
private val valueFromString: String.() -> Value,
|
||||
private val helper: SQLiteOpenHelper
|
||||
) : OneToManyKeyValueRepo<Key, Value> {
|
||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
@@ -34,12 +35,9 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
|
||||
|
||||
private val idColumnName = "id"
|
||||
private val idColumnArray = arrayOf(idColumnName)
|
||||
private val valueColumnName = "value"
|
||||
|
||||
private fun Key.asId() = internalSerialFormat.encodeToString(keySerializer, this)
|
||||
private fun Value.asValue() = internalSerialFormat.encodeToString(valueSerializer, this)
|
||||
private fun String.asValue(): Value = internalSerialFormat.decodeFromString(valueSerializer, this)
|
||||
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
|
||||
private val valueColumnArray = arrayOf(valueColumnName)
|
||||
|
||||
init {
|
||||
helper.blockingWritableTransaction {
|
||||
@@ -55,14 +53,25 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||
val added = mutableListOf<Pair<Key, Value>>()
|
||||
helper.blockingWritableTransaction {
|
||||
toAdd.forEach { (k, values) ->
|
||||
for ((k, values) in toAdd) {
|
||||
values.forEach { v ->
|
||||
val kAsString = k.keyAsString()
|
||||
val vAsString = v.valueAsString()
|
||||
val isThere = select(tableName,
|
||||
null,
|
||||
"$idColumnName=? AND $valueColumnName=?",
|
||||
arrayOf(kAsString, vAsString),
|
||||
limit = limitClause(1)
|
||||
).use { it.moveToFirst() }
|
||||
if (isThere) {
|
||||
return@forEach
|
||||
}
|
||||
insert(
|
||||
tableName,
|
||||
null,
|
||||
contentValuesOf(
|
||||
idColumnName to k.asId(),
|
||||
valueColumnName to v.asValue()
|
||||
idColumnName to k.keyAsString(),
|
||||
valueColumnName to v.valueAsString()
|
||||
)
|
||||
).also {
|
||||
if (it != -1L) {
|
||||
@@ -77,7 +86,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
|
||||
override suspend fun clear(k: Key) {
|
||||
helper.blockingWritableTransaction {
|
||||
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
|
||||
delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString()))
|
||||
}.also {
|
||||
if (it > 0) {
|
||||
_onDataCleared.emit(k)
|
||||
@@ -88,7 +97,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||
val (clearedKeys, inserted) = helper.blockingWritableTransaction {
|
||||
toSet.mapNotNull { (k, _) ->
|
||||
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
|
||||
if (delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString())) > 0) {
|
||||
k
|
||||
} else {
|
||||
null
|
||||
@@ -98,7 +107,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
insert(
|
||||
tableName,
|
||||
null,
|
||||
contentValuesOf(idColumnName to k.asId(), valueColumnName to v.asValue())
|
||||
contentValuesOf(idColumnName to k.keyAsString(), valueColumnName to v.valueAsString())
|
||||
)
|
||||
k to v
|
||||
}
|
||||
@@ -109,7 +118,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = helper.blockingReadableTransaction {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = firstPageWithOneElementPagination.limitClause()).use {
|
||||
it.count > 0
|
||||
}
|
||||
}
|
||||
@@ -118,14 +127,14 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
select(
|
||||
tableName,
|
||||
selection = "$idColumnName=? AND $valueColumnName=?",
|
||||
selectionArgs = arrayOf(k.asId(), v.asValue()),
|
||||
selectionArgs = arrayOf(k.keyAsString(), v.valueAsString()),
|
||||
limit = FirstPagePagination(1).limitClause()
|
||||
).use {
|
||||
it.count > 0
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun count(): Long =helper.blockingReadableTransaction {
|
||||
override suspend fun count(): Long = helper.blockingReadableTransaction {
|
||||
select(
|
||||
tableName
|
||||
).use {
|
||||
@@ -134,7 +143,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}.toLong()
|
||||
|
||||
override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
selectDistinct(tableName, columns = valueColumnArray, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
it.count
|
||||
}
|
||||
}.toLong()
|
||||
@@ -144,18 +153,25 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Value> = count(k).let { count ->
|
||||
if (pagination.firstIndex >= count) {
|
||||
return@let emptyList<Value>().createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
valueColumnArray,
|
||||
selection = "$idColumnName=?",
|
||||
selectionArgs = arrayOf(k.asId()),
|
||||
selectionArgs = arrayOf(k.keyAsString()),
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Value>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(valueColumnName).asValue())
|
||||
it.add(c.getString(valueColumnName).valueFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -170,16 +186,23 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = count().let { count ->
|
||||
if (pagination.firstIndex >= count) {
|
||||
return@let emptyList<Key>().createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
selectDistinct(
|
||||
tableName,
|
||||
idColumnArray,
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Key>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(idColumnName).asKey())
|
||||
it.add(c.getString(idColumnName).keyFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -197,16 +220,17 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
): PaginationResult<Key> = count().let { count ->
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
selectDistinct(
|
||||
tableName,
|
||||
idColumnArray,
|
||||
selection = "$valueColumnName=?",
|
||||
selectionArgs = arrayOf(v.asValue()),
|
||||
selectionArgs = arrayOf(v.valueAsString()),
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Key>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(idColumnName).asKey())
|
||||
it.add(c.getString(idColumnName).keyFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -221,7 +245,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
helper.blockingWritableTransaction {
|
||||
toRemove.flatMap { (k, vs) ->
|
||||
vs.mapNotNullA { v ->
|
||||
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
|
||||
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.keyAsString(), v.valueAsString())) > 0) {
|
||||
k to v
|
||||
} else {
|
||||
null
|
||||
@@ -233,3 +257,24 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> OneToManyAndroidRepo(
|
||||
tableName: String,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
helper: SQLiteOpenHelper
|
||||
) = OneToManyAndroidRepo(
|
||||
tableName,
|
||||
{ internalSerialFormat.encodeToString(keySerializer, this) },
|
||||
{ internalSerialFormat.encodeToString(valueSerializer, this) },
|
||||
{ internalSerialFormat.decodeFromString(keySerializer, this) },
|
||||
{ internalSerialFormat.decodeFromString(valueSerializer, this) },
|
||||
helper
|
||||
)
|
||||
|
||||
fun <Key, Value> KeyValuesAndroidRepo(
|
||||
tableName: String,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
helper: SQLiteOpenHelper
|
||||
) = OneToManyAndroidRepo(tableName, keySerializer, valueSerializer, helper)
|
||||
|
@@ -21,14 +21,12 @@ class AndroidSQLStandardVersionsRepoProxy(
|
||||
private val tableVersionColumnName = "version"
|
||||
|
||||
init {
|
||||
runBlocking(DatabaseCoroutineContext) {
|
||||
database.writableTransaction {
|
||||
createTable(
|
||||
tableName,
|
||||
tableNameColumnName to ColumnType.Text.NOT_NULLABLE,
|
||||
tableVersionColumnName to ColumnType.Numeric.INTEGER()
|
||||
)
|
||||
}
|
||||
database.blockingWritableTransaction {
|
||||
createTable(
|
||||
tableName,
|
||||
tableNameColumnName to ColumnType.Text.NOT_NULLABLE,
|
||||
tableVersionColumnName to ColumnType.Numeric.INTEGER()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -24,9 +24,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
||||
|
||||
@Deprecated("Will be removed in near major update. Override open fun with the same name instead")
|
||||
abstract val InsertStatement<Number>.asObject: ObjectType
|
||||
protected open fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType = asObject
|
||||
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
|
||||
abstract val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||
|
||||
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
|
||||
|
@@ -31,6 +31,9 @@ open class ExposedOneToManyKeyValueRepo<Key, Value>(
|
||||
transaction(database) {
|
||||
toAdd.keys.flatMap { k ->
|
||||
toAdd[k] ?.mapNotNull { v ->
|
||||
if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
insertIgnore {
|
||||
it[keyColumn] = k
|
||||
it[valueColumn] = v
|
||||
|
@@ -86,6 +86,12 @@ class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
||||
override suspend fun clear(k: Key) {
|
||||
map.remove(k) ?.also { _onDataCleared.emit(k) }
|
||||
}
|
||||
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
map.forEach { (k, values) ->
|
||||
if (values.remove(v)) _onValueRemoved.emit(k to v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MapOneToManyKeyValueRepo<Key, Value>(
|
||||
|
@@ -67,6 +67,15 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun clearWithValue(v: Value) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
clearWithValueRoute,
|
||||
),
|
||||
BodyPair(valueSerializer, v),
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
@@ -75,4 +84,4 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
BodyPair(keyValueMapSerializer, toSet),
|
||||
Unit.serializer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.common.one_to_many
|
||||
|
||||
@Deprecated("Replaced", ReplaceWith("keyParameterName", "dev.inmo.micro_utils.repos.ktor.common.keyParameterName"))
|
||||
const val keyParameterName = dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||
@Deprecated("Replaced", ReplaceWith("valueParameterName", "dev.inmo.micro_utils.repos.ktor.common.valueParameterName"))
|
||||
const val valueParameterName = dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
@Deprecated("Replaced", ReplaceWith("reversedParameterName", "dev.inmo.micro_utils.repos.ktor.common.reversedParameterName"))
|
||||
const val reversedParameterName = dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
@@ -14,4 +14,5 @@ const val onDataClearedRoute = "onDataCleared"
|
||||
const val addRoute = "add"
|
||||
const val removeRoute = "remove"
|
||||
const val clearRoute = "clear"
|
||||
const val setRoute = "set"
|
||||
const val clearWithValueRoute = "clearWithValue"
|
||||
const val setRoute = "set"
|
||||
|
@@ -72,6 +72,17 @@ fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
||||
}
|
||||
}
|
||||
|
||||
post(clearWithValueRoute) {
|
||||
unifiedRouter.apply {
|
||||
val v = uniload(valueSerializer)
|
||||
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.clearWithValue(v),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(setRoute) {
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
@@ -12,7 +12,7 @@ open class TypedSerializer<T : Any>(
|
||||
) : KSerializer<T> {
|
||||
protected val serializers = presetSerializers.toMutableMap()
|
||||
@InternalSerializationApi
|
||||
override open val descriptor: SerialDescriptor = buildSerialDescriptor(
|
||||
open override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
||||
"TextSourceSerializer",
|
||||
SerialKind.CONTEXTUAL
|
||||
) {
|
||||
@@ -21,7 +21,7 @@ open class TypedSerializer<T : Any>(
|
||||
}
|
||||
|
||||
@InternalSerializationApi
|
||||
override open fun deserialize(decoder: Decoder): T {
|
||||
open override fun deserialize(decoder: Decoder): T {
|
||||
return decoder.decodeStructure(descriptor) {
|
||||
var type: String? = null
|
||||
lateinit var result: T
|
||||
@@ -50,7 +50,7 @@ open class TypedSerializer<T : Any>(
|
||||
}
|
||||
|
||||
@InternalSerializationApi
|
||||
override open fun serialize(encoder: Encoder, value: T) {
|
||||
open override fun serialize(encoder: Encoder, value: T) {
|
||||
encoder.encodeStructure(descriptor) {
|
||||
val valueSerializer = value::class.serializer()
|
||||
val type = serializers.keys.first { serializers[it] == valueSerializer }
|
||||
|
@@ -10,6 +10,8 @@ String[] includes = [
|
||||
":pagination:ktor:common",
|
||||
":pagination:ktor:server",
|
||||
":mime_types",
|
||||
":language_codes",
|
||||
":language_codes:generator",
|
||||
":repos:common",
|
||||
":repos:cache",
|
||||
":repos:exposed",
|
||||
@@ -28,6 +30,9 @@ String[] includes = [
|
||||
":serialization:encapsulator",
|
||||
":serialization:typed_serializer",
|
||||
|
||||
":fsm:common",
|
||||
":fsm:repos:common",
|
||||
|
||||
":dokka"
|
||||
]
|
||||
|
||||
|
Reference in New Issue
Block a user