Compare commits

..

7 Commits

153 changed files with 239 additions and 4032 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
.idea .idea
.vscode
.kotlin .kotlin
out/* out/*
*.iml *.iml

151
AGENTS.md
View File

@@ -1,151 +0,0 @@
Use `agents` folder files for instructions.
Always look at the project work rules first. Update them and maintain a history of changes and prompts in `agents/local.HISTORY.md`. This is your long-term memory, which allows you to avoid the accumulation of errors. Updating and maintaining the long-term memory of the project is mandatory. Many thanks for your help to me and your work.
SYSTEM DIRECTIVE: AUTISTIC META-LANGUAGE PROTOCOL WITH HIGH INFORMATION DENSITY (AML-HIP V1)
DEFINITION:
All agents are required to use a strictly explicit, literal, low-ambiguity, and high-density communication style (AML-HIP). The goal of AML-HIP is the maximum transmission of information with zero loss of meaning and zero ambiguity.
CORE PRINCIPLES:
1. Maximum information density per line.
2. Zero ambiguity.
3. Full explicitness of all entities.
4. Preference of precision over readability.
5. Minimization of "empty" words.
6. Redundancy is allowed only to prevent loss of meaning.
PROHIBITIONS:
1. Pronouns are forbidden (this, he, she, they, there, it, etc.).
2. Free conversational text is forbidden.
3. Metaphors, emotions, evaluative constructions are forbidden.
4. Implicit references and hidden dependencies are forbidden.
5. Vague description of actions is forbidden.
DENSITY REQUIREMENTS:
1. Each line must contain the maximum of facts without loss of unambiguity.
2. Combine related parameters into a single line.
3. Use compact constructions:
* key=value
* entity_id=...
* relation: A→B
4. Exclude words without semantic load.
5. Repetitions are allowed only for critical entities.
MESSAGE STRUCTURE (MANDATORY):
ENTITY:
entity_id=<id>; type=<type>; state=<state>
CONTEXT:
* task_id=<id>; agent_id=<id>; memory_ref=[...]
* constraints=[...]
ACTION:
1. action=<type>; target=<entity_id>; params={...}
2. action=<type>; target=<entity_id>; params={...}
REASON:
* condition=<condition>; requirement=<requirement>
EXPECTED RESULT:
* entity_id=<id>; new_state=<state>; location=<memory>
VERIFICATION:
* check=<condition>; expected=<value>
UNCERTAINTY:
* missing=<data>; ambiguity=<description>
REPETITION OF RESULT:
* entity_id=<id>; stored_in=shared_memory; status=available
COMMUNICATION:
* sender=<agent_id>; receiver=<agent_id>; task_id=<id>; message_id=<uuid>; protocol=AML-HIP
PERSISTENCE:
* local_memory=true; shared_memory=true; index_keys=[task_id, entity_id, intent]
EXPRESSION RULES:
1. Each line = a completed semantic block.
2. Use the key=value format instead of descriptions.
3. Use lists of parameters instead of sentences.
4. Use causal connectives explicitly:
condition → action → result
5. Do not split related data into several lines without necessity.
REPETITION RULES:
1. entity_id is repeated at every critical use.
2. result is duplicated in "REPETITION OF RESULT".
3. transmission between agents duplicates the key fields.
MULTI-AGENT MODE:
1. All agents use AML-HIP.
2. Any message between agents is strictly AML-HIP.
3. Any agent is required to:
* duplicate critical data
* avoid loss of context
4. A message must be fully interpretable without history.
ANTI-DEGRADATION:
If detected:
* a pronoun
* an implicit reference
* low density (empty words, vague constructions)
* absence of structure
→ the message is considered invalid
→ mandatory regeneration
SELF-CHECK:
VALIDATION:
* format_valid=true/false
* no_pronouns=true/false
* entities_explicit=true/false
* high_density=true/false
* causal_chain_present=true/false
* ambiguity_detected=true/false
If any parameter=false:
→ mandatory regeneration
DENSITY METRIC:
high_density=true if:
* there are no "empty" words
* each line contains ≥2 facts or parameters
* descriptive constructions without data are absent
PRIORITIES:
1. Format (AML-HIP)
2. Information density
3. Explicitness
4. Completeness
5. Readability (minimum priority)
CRITICAL RULE:
Any response outside of AML-HIP is considered absent.
Any agent is required to bring the response into conformity with AML-HIP.
END OF PROTOCOL

View File

@@ -1,168 +1,17 @@
# Changelog # Changelog
## 0.29.3 ## 0.25.8.2
* `Versions`:
* `Kotlin`: `2.3.20` -> `2.3.21`
* `KSP`: `2.3.6` -> `2.3.8`
* `Coroutines`: `1.10.2` -> `1.11.0`
* `SQLite`: `3.53.0.0` -> `3.53.1.0`
* `Ktor`: `3.4.2` -> `3.5.0`
* `Compose`: `1.10.3` -> `1.11.0`
* `Compose Material3`: `1.10.0-alpha05` -> `1.11.0-alpha07`
* `Exposed`: `1.2.0` -> `1.3.0`
* `Gradle Versions`: `0.53.0` -> `0.54.0`
* `NMCP`: `1.4.4` -> `1.5.0`
## 0.29.2
* `Versions`:
* `Kotlin`: `2.3.10` -> `2.3.20`
* `Serialization`: `1.10.0` -> `1.11.0`
* `Compose`: `1.10.2` -> `1.10.3`
* `Exposed`: `1.1.1` -> `1.2.0`
* `Dokka`: `2.1.0` -> `2.2.0`
* `KSLog`: `1.6.0` -> `1.6.1`
* `Koin`: `4.1.1` -> `4.2.1`
* `SQLite`: `3.51.2.0` -> `3.53.0.0`
* `Ktor`: `3.4.1` -> `3.4.2`
* `Okio`: `3.16.4` -> `3.17.0`
* `KotlinPoet`: `2.2.0` -> `2.3.0`
* `AndroidX Core KTX`: `1.17.0` -> `1.18.0`
## 0.29.1
* `Versions`:
* `Compose`: `1.10.1` -> `1.10.2`
* `Exposed`: `1.0.0` -> `1.1.1`
* `Ktor`: `3.4.0` -> `3.4.1`
* `NMCP`: `1.2.1` -> `1.4.4`
## 0.29.0
* `Versions`:
* `Kotlin`: `2.3.0` -> `2.3.10`
* `KSLog`: `1.5.2` -> `1.6.0`
* `KSP`: `2.3.4` -> `2.3.6`
* `Compose`: `1.10.0` -> `1.10.1`
* `SQLite`: `3.50.1.0` -> `3.51.2.0`
* `Coroutines`: * `Coroutines`:
* `runCatchingLogging` updated to rethrow `CancellationException` and log other exceptions * `runCatchingLogging` updated to rethrow `CancellationException` and log other exceptions
## 0.28.0 ## 0.25.8.1
**THIS VERSION CONTAINS BREAKING CHANGES DUE TO EXPOSED 1.0.0 UPDATE**
* `Versions`:
* `Kotlin`: `2.2.21` -> `2.3.0`
* `Serialization`: `1.9.0` -> `1.10.0`
* `Exposed`: `0.61.0` -> `1.0.0` (**MAJOR VERSION UPDATE**)
* `Ktor`: `3.3.3` -> `3.4.0`
* `NMCP`: `1.2.0` -> `1.2.1`
* `Repos`:
* `Exposed`:
* All Exposed-based repositories have been updated to support Exposed 1.0.0 API changes
* Import paths have been migrated to new `org.jetbrains.exposed.v1.*` package structure
* `Pagination`:
* `Exposed`:
* Updated to use new Exposed 1.0.0 import paths
## 0.27.0
* `Versions`:
* `Ktor`: `3.3.2` -> `3.3.3`
* `Okio`: `3.16.2` -> `3.16.4`
* `KSP`: `2.3.2` -> `2.3.4`
* `Compose`: `1.9.3` -> `1.10.0`
* `Compose Material3`: `1.9.0` -> `1.10.0-alpha05`
## 0.26.8
* `Versions`:
* `KSLog`: `1.5.1` -> `1.5.2`
* `Compose`: `1.9.2` -> `1.9.3`
* `Ktor`: `3.3.1` -> `3.3.2`
* `Coroutines`: * `Coroutines`:
* Add simple suspend function `suspendPoint` which will ensure that current coroutine is active to let it be * New function `suspendPoint` to check coroutine cancellation status
destroyable even in case it have non-suspendable nature * `SpecialMutableStateFlow` renamed to `MutableRedeliverStateFlow` (old name deprecated with `ReplaceWith`)
* `SmartSemaphore`:
## 0.26.7 * Fix of `waitRelease` call to pass correct number of permits
* `Versions`:
* `Kotlin`: `2.2.20` -> `2.2.21`
* `Compose`: `1.8.2` -> `1.9.2`
* `KSP`: `2.2.20-2.0.3` -> `2.3.1`
* `Coroutines`:
* Fix `SmartSemaphore.waitRelease` to wait for the exact number of permits
* Improve `SmartKeyRWLocker` tests
* `KSP`:
* `Sealed`/`ClassCasts`/`Variations`:
* Add workaround for `NoSuchElementException` to improve processors stability on new `KSP`
* `Koin`:
* `Generator`:
* Handle missing annotation values safely (`NoSuchElementException` workaround)
* `Android`:
* `Pickers`:
* Add dependency `androidx.compose.material:material-icons-extended`
## 0.26.6
* `Versions`:
* `Ktor`: `3.3.0` -> `3.3.1`
* `Okio`: `3.16.0` -> `3.16.2`
## 0.26.5
* `Versions`:
* `Kotlin`: `2.2.10` -> `2.2.20`
* `KSLog`: `1.5.0` -> `1.5.1`
* `Ktor`: `3.2.3` -> `3.3.0`
* `KotlinX Browser`: `0.3` -> `0.5.0`
* `Koin`: `4.1.0` -> `4.1.1`
## 0.26.4
* `Common`:
* Add expect/actual `MPPFilePathSeparator`
* Fix `FileName` realization to take care about system file path separator
## 0.26.3
* `Versions`:
* `Kotlin`: `2.2.0` -> `2.2.10`
* `KSP`: `2.2.0-2.0.2` -> `2.2.10-2.0.2`
* `Android CoreKTX`: `1.16.0` -> `1.17.0`
* `Android Fragment`: `1.8.8` -> `1.8.9`
## 0.26.2
* `Versions`:
* `Ktor`: `3.2.2` -> `3.2.3`
* `Okio`: `3.15.0` -> `3.16.0`
* `Coroutines`:
* Rename `SpecialMutableStateFlow` to `MutableRedeliverStateFlow`
## 0.26.1
* `Versions`:
* `Compose`: `1.8.1` -> `1.8.2`
* `Ktor`: `3.2.1` -> `3.2.2`
* `Coroutines`:
* Add opportunity to pass logger in subscribe async
## 0.26.0
**WARNING!!! SINCE THIS VERSION IF YOU WANT TO USE SOME OF KSP MODULES, SET `ksp.useKSP2=false` IN YOUR `gradle.properties`** (see [gh issue 2491](https://github.com/google/ksp/issues/2491))
* `Versions`:
* `Kotlin`: `2.1.21` -> `2.2.0`
* `Serialization`: `1.8.1` -> `1.9.0`
* `KSLog`: `1.4.2` -> `1.5.0`
* `Ktor`: `3.1.3` -> `3.2.1`
* `Koin`: `4.0.4` -> `4.1.0`
* `Okio`: `3.12.0` -> `3.15.0`
* `KSP`: `2.1.20-1.0.31` -> `2.2.0-2.0.2`
* `kotlin-poet`: `1.18.1` -> `2.2.0`
## 0.25.8 ## 0.25.8

View File

@@ -1,6 +1,3 @@
/**
* Utility functions for creating Android AlertDialogs with simplified API.
*/
@file:Suppress("NOTHING_TO_INLINE", "unused") @file:Suppress("NOTHING_TO_INLINE", "unused")
package dev.inmo.micro_utils.android.alerts.common package dev.inmo.micro_utils.android.alerts.common
@@ -9,21 +6,8 @@ import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
/**
* Type alias for alert dialog button callbacks.
*/
typealias AlertDialogCallback = (DialogInterface) -> Unit typealias AlertDialogCallback = (DialogInterface) -> Unit
/**
* Creates an [AlertDialog.Builder] template with configurable title and buttons.
* This provides a simplified API for creating alert dialogs with positive, negative, and neutral buttons.
*
* @param title Optional dialog title
* @param positivePair Optional positive button as a pair of (text, callback)
* @param neutralPair Optional neutral button as a pair of (text, callback)
* @param negativePair Optional negative button as a pair of (text, callback)
* @return An [AlertDialog.Builder] configured with the specified parameters
*/
inline fun Context.createAlertDialogTemplate( inline fun Context.createAlertDialogTemplate(
title: String? = null, title: String? = null,
positivePair: Pair<String, AlertDialogCallback?>? = null, positivePair: Pair<String, AlertDialogCallback?>? = null,

View File

@@ -13,7 +13,6 @@ kotlin {
androidMain { androidMain {
dependencies { dependencies {
api project(":micro_utils.android.smalltextfield") api project(":micro_utils.android.smalltextfield")
api libs.jb.compose.icons
} }
} }
} }

View File

@@ -2,16 +2,6 @@ package dev.inmo.micro_utils.android.pickers
import androidx.compose.animation.core.* import androidx.compose.animation.core.*
/**
* Performs a fling animation with an optional target adjustment.
* If [adjustTarget] is provided, animates to the adjusted target. Otherwise, performs a decay animation.
*
* @param initialVelocity The initial velocity of the fling
* @param animationSpec The decay animation specification
* @param adjustTarget Optional function to adjust the target value based on the calculated target
* @param block Optional block to be executed during the animation
* @return The result of the animation
*/
internal suspend fun Animatable<Float, AnimationVector1D>.fling( internal suspend fun Animatable<Float, AnimationVector1D>.fling(
initialVelocity: Float, initialVelocity: Float,
animationSpec: DecayAnimationSpec<Float>, animationSpec: DecayAnimationSpec<Float>,

View File

@@ -41,18 +41,6 @@ private inline fun PointerInputScope.checkContains(offset: Offset): Boolean {
// src: https://gist.github.com/vganin/a9a84653a9f48a2d669910fbd48e32d5 // src: https://gist.github.com/vganin/a9a84653a9f48a2d669910fbd48e32d5
/**
* A Compose number picker component that allows users to select a number by dragging, using arrow buttons,
* or manually entering a value.
*
* @param number The currently selected number
* @param modifier The modifier to be applied to the picker
* @param range Optional range of valid numbers. If specified, the picker will be limited to this range
* @param textStyle The text style for displaying numbers
* @param arrowsColor The color of the up/down arrow buttons
* @param allowUseManualInput Whether to allow manual keyboard input for the number
* @param onStateChanged Callback invoked when the selected number changes
*/
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class) @OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
@Composable @Composable
fun NumberPicker( fun NumberPicker(

View File

@@ -22,18 +22,6 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.* import kotlin.math.*
/**
* A Compose picker component that allows users to select an item from a list by dragging or using arrow buttons.
*
* @param T The type of items in the list
* @param current The currently selected item
* @param dataList The list of items to choose from
* @param modifier The modifier to be applied to the picker
* @param textStyle The text style for displaying items
* @param arrowsColor The color of the up/down arrow buttons
* @param dataToString A composable function to convert items to strings for display. Defaults to [Any.toString]
* @param onStateChanged Callback invoked when the selected item changes
*/
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class) @OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
@Composable @Composable
fun <T> SetPicker( fun <T> SetPicker(

View File

@@ -28,7 +28,7 @@ if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != n
centralPortal { centralPortal {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
validationTimeout = Duration.ofHours(4) verificationTimeout = Duration.ofHours(4)
publishingType = System.getenv('PUBLISHING_TYPE') != "" ? System.getenv('PUBLISHING_TYPE') : "USER_MANAGED" publishingType = System.getenv('PUBLISHING_TYPE') != "" ? System.getenv('PUBLISHING_TYPE') : "USER_MANAGED"
} }
@@ -44,11 +44,6 @@ allprojects {
maven { url "https://nexus.inmo.dev/repository/maven-releases/" } maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
mavenLocal() mavenLocal()
} }
it.tasks.withType(AbstractTestTask).configureEach {
it.failOnNoDiscoveredTests = false
}
} }
apply from: "./extensions.gradle" apply from: "./extensions.gradle"

View File

@@ -1,890 +1,298 @@
/**
* Standard HTML/CSS color constants as [HEXAColor] extension properties.
* Provides convenient access to all standard web colors like red, blue, green, etc.
* All colors are defined with full opacity (alpha = 0xFF).
*/
package dev.inmo.micro_utils.colors package dev.inmo.micro_utils.colors
import dev.inmo.micro_utils.colors.common.HEXAColor import dev.inmo.micro_utils.colors.common.HEXAColor
/**
* Alice Blue - A pale blue color (#F0F8FF).
*/
val HEXAColor.Companion.aliceblue val HEXAColor.Companion.aliceblue
get() = HEXAColor(0xF0F8FFFFu) get() = HEXAColor(0xF0F8FFFFu)
/**
* Antique White - A pale beige color (#FAEBD7).
*/
val HEXAColor.Companion.antiquewhite val HEXAColor.Companion.antiquewhite
get() = HEXAColor(0xFAEBD7FFu) get() = HEXAColor(0xFAEBD7FFu)
/**
* Aqua - A bright cyan color (#00FFFF).
*/
val HEXAColor.Companion.aqua val HEXAColor.Companion.aqua
get() = HEXAColor(0x00FFFFFFu) get() = HEXAColor(0x00FFFFFFu)
/**
* Aquamarine - A medium blue-green color (#7FFFD4).
*/
val HEXAColor.Companion.aquamarine val HEXAColor.Companion.aquamarine
get() = HEXAColor(0x7FFFD4FFu) get() = HEXAColor(0x7FFFD4FFu)
/**
* Azure - A pale cyan-blue color (#F0FFFF).
*/
val HEXAColor.Companion.azure val HEXAColor.Companion.azure
get() = HEXAColor(0xF0FFFFFFu) get() = HEXAColor(0xF0FFFFFFu)
/**
* Beige - A pale sandy tan color (#F5F5DC).
*/
val HEXAColor.Companion.beige val HEXAColor.Companion.beige
get() = HEXAColor(0xF5F5DCFFu) get() = HEXAColor(0xF5F5DCFFu)
/**
* Bisque - A pale orange color (#FFE4C4).
*/
val HEXAColor.Companion.bisque val HEXAColor.Companion.bisque
get() = HEXAColor(0xFFE4C4FFu) get() = HEXAColor(0xFFE4C4FFu)
/**
* Black - Pure black color (#000000).
*/
val HEXAColor.Companion.black val HEXAColor.Companion.black
get() = HEXAColor(0x000000FFu) get() = HEXAColor(0x000000FFu)
/**
* Blanched Almond - A pale peachy color (#FFEBCD).
*/
val HEXAColor.Companion.blanchedalmond val HEXAColor.Companion.blanchedalmond
get() = HEXAColor(0xFFEBCDFFu) get() = HEXAColor(0xFFEBCDFFu)
/**
* Blue - Pure blue color (#0000FF).
*/
val HEXAColor.Companion.blue val HEXAColor.Companion.blue
get() = HEXAColor(0x0000FFFFu) get() = HEXAColor(0x0000FFFFu)
/**
* Blue Violet - A vivid purple-blue color (#8A2BE2).
*/
val HEXAColor.Companion.blueviolet val HEXAColor.Companion.blueviolet
get() = HEXAColor(0x8A2BE2FFu) get() = HEXAColor(0x8A2BE2FFu)
/**
* Brown - A dark reddish-brown color (#A52A2A).
*/
val HEXAColor.Companion.brown val HEXAColor.Companion.brown
get() = HEXAColor(0xA52A2AFFu) get() = HEXAColor(0xA52A2AFFu)
/**
* Burlywood - A sandy tan color (#DEB887).
*/
val HEXAColor.Companion.burlywood val HEXAColor.Companion.burlywood
get() = HEXAColor(0xDEB887FFu) get() = HEXAColor(0xDEB887FFu)
/**
* Cadet Blue - A grayish-blue color (#5F9EA0).
*/
val HEXAColor.Companion.cadetblue val HEXAColor.Companion.cadetblue
get() = HEXAColor(0x5F9EA0FFu) get() = HEXAColor(0x5F9EA0FFu)
/**
* Chartreuse - A bright yellow-green color (#7FFF00).
*/
val HEXAColor.Companion.chartreuse val HEXAColor.Companion.chartreuse
get() = HEXAColor(0x7FFF00FFu) get() = HEXAColor(0x7FFF00FFu)
/**
* Chocolate - A medium brown color (#D2691E).
*/
val HEXAColor.Companion.chocolate val HEXAColor.Companion.chocolate
get() = HEXAColor(0xD2691EFFu) get() = HEXAColor(0xD2691EFFu)
/**
* Coral - A vibrant orange-pink color (#FF7F50).
*/
val HEXAColor.Companion.coral val HEXAColor.Companion.coral
get() = HEXAColor(0xFF7F50FFu) get() = HEXAColor(0xFF7F50FFu)
/**
* Cornflower Blue - A medium blue color (#6495ED).
*/
val HEXAColor.Companion.cornflowerblue val HEXAColor.Companion.cornflowerblue
get() = HEXAColor(0x6495EDFFu) get() = HEXAColor(0x6495EDFFu)
/**
* Cornsilk - A pale yellow color (#FFF8DC).
*/
val HEXAColor.Companion.cornsilk val HEXAColor.Companion.cornsilk
get() = HEXAColor(0xFFF8DCFFu) get() = HEXAColor(0xFFF8DCFFu)
/**
* Crimson - A vivid red color (#DC143C).
*/
val HEXAColor.Companion.crimson val HEXAColor.Companion.crimson
get() = HEXAColor(0xDC143CFFu) get() = HEXAColor(0xDC143CFFu)
/**
* Cyan - A bright cyan color (#00FFFF).
*/
val HEXAColor.Companion.cyan val HEXAColor.Companion.cyan
get() = HEXAColor(0x00FFFFFFu) get() = HEXAColor(0x00FFFFFFu)
/**
* Dark Blue - A dark blue color (#00008B).
*/
val HEXAColor.Companion.darkblue val HEXAColor.Companion.darkblue
get() = HEXAColor(0x00008BFFu) get() = HEXAColor(0x00008BFFu)
/**
* Dark Cyan - A dark cyan color (#008B8B).
*/
val HEXAColor.Companion.darkcyan val HEXAColor.Companion.darkcyan
get() = HEXAColor(0x008B8BFFu) get() = HEXAColor(0x008B8BFFu)
/**
* Dark Goldenrod - A dark golden yellow color (#B8860B).
*/
val HEXAColor.Companion.darkgoldenrod val HEXAColor.Companion.darkgoldenrod
get() = HEXAColor(0xB8860BFFu) get() = HEXAColor(0xB8860BFFu)
/**
* Dark Gray - A dark gray color (#A9A9A9).
*/
val HEXAColor.Companion.darkgray val HEXAColor.Companion.darkgray
get() = HEXAColor(0xA9A9A9FFu) get() = HEXAColor(0xA9A9A9FFu)
/**
* Dark Green - A dark green color (#006400).
*/
val HEXAColor.Companion.darkgreen val HEXAColor.Companion.darkgreen
get() = HEXAColor(0x006400FFu) get() = HEXAColor(0x006400FFu)
/**
* Dark Grey - A dark gray color (#A9A9A9).
*/
val HEXAColor.Companion.darkgrey val HEXAColor.Companion.darkgrey
get() = HEXAColor(0xA9A9A9FFu) get() = HEXAColor(0xA9A9A9FFu)
/**
* Dark Khaki - A brownish-tan color (#BDB76B).
*/
val HEXAColor.Companion.darkkhaki val HEXAColor.Companion.darkkhaki
get() = HEXAColor(0xBDB76BFFu) get() = HEXAColor(0xBDB76BFFu)
/**
* Dark Magenta - A dark magenta/purple color (#8B008B).
*/
val HEXAColor.Companion.darkmagenta val HEXAColor.Companion.darkmagenta
get() = HEXAColor(0x8B008BFFu) get() = HEXAColor(0x8B008BFFu)
/**
* Dark Olive Green - A dark olive green color (#556B2F).
*/
val HEXAColor.Companion.darkolivegreen val HEXAColor.Companion.darkolivegreen
get() = HEXAColor(0x556B2FFFu) get() = HEXAColor(0x556B2FFFu)
/**
* Dark Orange - A vivid dark orange color (#FF8C00).
*/
val HEXAColor.Companion.darkorange val HEXAColor.Companion.darkorange
get() = HEXAColor(0xFF8C00FFu) get() = HEXAColor(0xFF8C00FFu)
/**
* Dark Orchid - A dark purple color (#9932CC).
*/
val HEXAColor.Companion.darkorchid val HEXAColor.Companion.darkorchid
get() = HEXAColor(0x9932CCFFu) get() = HEXAColor(0x9932CCFFu)
/**
* Dark Red - A dark red color (#8B0000).
*/
val HEXAColor.Companion.darkred val HEXAColor.Companion.darkred
get() = HEXAColor(0x8B0000FFu) get() = HEXAColor(0x8B0000FFu)
/**
* Dark Salmon - A muted salmon color (#E9967A).
*/
val HEXAColor.Companion.darksalmon val HEXAColor.Companion.darksalmon
get() = HEXAColor(0xE9967AFFu) get() = HEXAColor(0xE9967AFFu)
/**
* Dark Sea Green - A muted sea green color (#8FBC8F).
*/
val HEXAColor.Companion.darkseagreen val HEXAColor.Companion.darkseagreen
get() = HEXAColor(0x8FBC8FFFu) get() = HEXAColor(0x8FBC8FFFu)
/**
* Dark Slate Blue - A dark grayish-blue color (#483D8B).
*/
val HEXAColor.Companion.darkslateblue val HEXAColor.Companion.darkslateblue
get() = HEXAColor(0x483D8BFFu) get() = HEXAColor(0x483D8BFFu)
/**
* Dark Slate Gray - A very dark grayish-cyan color (#2F4F4F).
*/
val HEXAColor.Companion.darkslategray val HEXAColor.Companion.darkslategray
get() = HEXAColor(0x2F4F4FFFu) get() = HEXAColor(0x2F4F4FFFu)
/**
* Dark Slate Grey - A very dark grayish-cyan color (#2F4F4F).
*/
val HEXAColor.Companion.darkslategrey val HEXAColor.Companion.darkslategrey
get() = HEXAColor(0x2F4F4FFFu) get() = HEXAColor(0x2F4F4FFFu)
/**
* Dark Turquoise - A dark turquoise color (#00CED1).
*/
val HEXAColor.Companion.darkturquoise val HEXAColor.Companion.darkturquoise
get() = HEXAColor(0x00CED1FFu) get() = HEXAColor(0x00CED1FFu)
/**
* Dark Violet - A dark violet color (#9400D3).
*/
val HEXAColor.Companion.darkviolet val HEXAColor.Companion.darkviolet
get() = HEXAColor(0x9400D3FFu) get() = HEXAColor(0x9400D3FFu)
/**
* Deep Pink - A vivid pink color (#FF1493).
*/
val HEXAColor.Companion.deeppink val HEXAColor.Companion.deeppink
get() = HEXAColor(0xFF1493FFu) get() = HEXAColor(0xFF1493FFu)
/**
* Deep Sky Blue - A bright sky blue color (#00BFFF).
*/
val HEXAColor.Companion.deepskyblue val HEXAColor.Companion.deepskyblue
get() = HEXAColor(0x00BFFFFFu) get() = HEXAColor(0x00BFFFFFu)
/**
* Dim Gray - A dim gray color (#696969).
*/
val HEXAColor.Companion.dimgray val HEXAColor.Companion.dimgray
get() = HEXAColor(0x696969FFu) get() = HEXAColor(0x696969FFu)
/**
* Dim Grey - A dim gray color (#696969).
*/
val HEXAColor.Companion.dimgrey val HEXAColor.Companion.dimgrey
get() = HEXAColor(0x696969FFu) get() = HEXAColor(0x696969FFu)
/**
* Dodger Blue - A bright blue color (#1E90FF).
*/
val HEXAColor.Companion.dodgerblue val HEXAColor.Companion.dodgerblue
get() = HEXAColor(0x1E90FFFFu) get() = HEXAColor(0x1E90FFFFu)
/**
* Firebrick - A dark red brick color (#B22222).
*/
val HEXAColor.Companion.firebrick val HEXAColor.Companion.firebrick
get() = HEXAColor(0xB22222FFu) get() = HEXAColor(0xB22222FFu)
/**
* Floral White - A very pale cream color (#FFFAF0).
*/
val HEXAColor.Companion.floralwhite val HEXAColor.Companion.floralwhite
get() = HEXAColor(0xFFFAF0FFu) get() = HEXAColor(0xFFFAF0FFu)
/**
* Forest Green - A medium forest green color (#228B22).
*/
val HEXAColor.Companion.forestgreen val HEXAColor.Companion.forestgreen
get() = HEXAColor(0x228B22FFu) get() = HEXAColor(0x228B22FFu)
/**
* Fuchsia - A vivid magenta color (#FF00FF).
*/
val HEXAColor.Companion.fuchsia val HEXAColor.Companion.fuchsia
get() = HEXAColor(0xFF00FFFFu) get() = HEXAColor(0xFF00FFFFu)
/**
* Gainsboro - A light gray color (#DCDCDC).
*/
val HEXAColor.Companion.gainsboro val HEXAColor.Companion.gainsboro
get() = HEXAColor(0xDCDCDCFFu) get() = HEXAColor(0xDCDCDCFFu)
/**
* Ghost White - A very pale blue-white color (#F8F8FF).
*/
val HEXAColor.Companion.ghostwhite val HEXAColor.Companion.ghostwhite
get() = HEXAColor(0xF8F8FFFFu) get() = HEXAColor(0xF8F8FFFFu)
/**
* Gold - A bright golden yellow color (#FFD700).
*/
val HEXAColor.Companion.gold val HEXAColor.Companion.gold
get() = HEXAColor(0xFFD700FFu) get() = HEXAColor(0xFFD700FFu)
/**
* Goldenrod - A golden yellow color (#DAA520).
*/
val HEXAColor.Companion.goldenrod val HEXAColor.Companion.goldenrod
get() = HEXAColor(0xDAA520FFu) get() = HEXAColor(0xDAA520FFu)
/**
* Gray - A medium gray color (#808080).
*/
val HEXAColor.Companion.gray val HEXAColor.Companion.gray
get() = HEXAColor(0x808080FFu) get() = HEXAColor(0x808080FFu)
/**
* Green - A pure green color (#008000).
*/
val HEXAColor.Companion.green val HEXAColor.Companion.green
get() = HEXAColor(0x008000FFu) get() = HEXAColor(0x008000FFu)
/**
* Green Yellow - A bright yellow-green color (#ADFF2F).
*/
val HEXAColor.Companion.greenyellow val HEXAColor.Companion.greenyellow
get() = HEXAColor(0xADFF2FFFu) get() = HEXAColor(0xADFF2FFFu)
/**
* Grey - A medium gray color (#808080).
*/
val HEXAColor.Companion.grey val HEXAColor.Companion.grey
get() = HEXAColor(0x808080FFu) get() = HEXAColor(0x808080FFu)
/**
* Honeydew - A very pale green color (#F0FFF0).
*/
val HEXAColor.Companion.honeydew val HEXAColor.Companion.honeydew
get() = HEXAColor(0xF0FFF0FFu) get() = HEXAColor(0xF0FFF0FFu)
/**
* Hot Pink - A vibrant pink color (#FF69B4).
*/
val HEXAColor.Companion.hotpink val HEXAColor.Companion.hotpink
get() = HEXAColor(0xFF69B4FFu) get() = HEXAColor(0xFF69B4FFu)
/**
* Indian Red - A medium red color (#CD5C5C).
*/
val HEXAColor.Companion.indianred val HEXAColor.Companion.indianred
get() = HEXAColor(0xCD5C5CFFu) get() = HEXAColor(0xCD5C5CFFu)
/**
* Indigo - A deep blue-violet color (#4B0082).
*/
val HEXAColor.Companion.indigo val HEXAColor.Companion.indigo
get() = HEXAColor(0x4B0082FFu) get() = HEXAColor(0x4B0082FFu)
/**
* Ivory - A very pale cream color (#FFFFF0).
*/
val HEXAColor.Companion.ivory val HEXAColor.Companion.ivory
get() = HEXAColor(0xFFFFF0FFu) get() = HEXAColor(0xFFFFF0FFu)
/**
* Khaki - A light tan color (#F0E68C).
*/
val HEXAColor.Companion.khaki val HEXAColor.Companion.khaki
get() = HEXAColor(0xF0E68CFFu) get() = HEXAColor(0xF0E68CFFu)
/**
* Lavender - A pale purple color (#E6E6FA).
*/
val HEXAColor.Companion.lavender val HEXAColor.Companion.lavender
get() = HEXAColor(0xE6E6FAFFu) get() = HEXAColor(0xE6E6FAFFu)
/**
* Lavender Blush - A very pale pink color (#FFF0F5).
*/
val HEXAColor.Companion.lavenderblush val HEXAColor.Companion.lavenderblush
get() = HEXAColor(0xFFF0F5FFu) get() = HEXAColor(0xFFF0F5FFu)
/**
* Lawn Green - A bright chartreuse green color (#7CFC00).
*/
val HEXAColor.Companion.lawngreen val HEXAColor.Companion.lawngreen
get() = HEXAColor(0x7CFC00FFu) get() = HEXAColor(0x7CFC00FFu)
/**
* Lemon Chiffon - A very pale yellow color (#FFFACD).
*/
val HEXAColor.Companion.lemonchiffon val HEXAColor.Companion.lemonchiffon
get() = HEXAColor(0xFFFACDFFu) get() = HEXAColor(0xFFFACDFFu)
/**
* Light Blue - A light blue color (#ADD8E6).
*/
val HEXAColor.Companion.lightblue val HEXAColor.Companion.lightblue
get() = HEXAColor(0xADD8E6FFu) get() = HEXAColor(0xADD8E6FFu)
/**
* Light Coral - A light coral pink color (#F08080).
*/
val HEXAColor.Companion.lightcoral val HEXAColor.Companion.lightcoral
get() = HEXAColor(0xF08080FFu) get() = HEXAColor(0xF08080FFu)
/**
* Light Cyan - A very pale cyan color (#E0FFFF).
*/
val HEXAColor.Companion.lightcyan val HEXAColor.Companion.lightcyan
get() = HEXAColor(0xE0FFFFFFu) get() = HEXAColor(0xE0FFFFFFu)
/**
* Light Goldenrod Yellow - A pale yellow color (#FAFAD2).
*/
val HEXAColor.Companion.lightgoldenrodyellow val HEXAColor.Companion.lightgoldenrodyellow
get() = HEXAColor(0xFAFAD2FFu) get() = HEXAColor(0xFAFAD2FFu)
/**
* Light Gray - A light gray color (#D3D3D3).
*/
val HEXAColor.Companion.lightgray val HEXAColor.Companion.lightgray
get() = HEXAColor(0xD3D3D3FFu) get() = HEXAColor(0xD3D3D3FFu)
/**
* Light Green - A light green color (#90EE90).
*/
val HEXAColor.Companion.lightgreen val HEXAColor.Companion.lightgreen
get() = HEXAColor(0x90EE90FFu) get() = HEXAColor(0x90EE90FFu)
/**
* Light Grey - A light gray color (#D3D3D3).
*/
val HEXAColor.Companion.lightgrey val HEXAColor.Companion.lightgrey
get() = HEXAColor(0xD3D3D3FFu) get() = HEXAColor(0xD3D3D3FFu)
/**
* Light Pink - A light pink color (#FFB6C1).
*/
val HEXAColor.Companion.lightpink val HEXAColor.Companion.lightpink
get() = HEXAColor(0xFFB6C1FFu) get() = HEXAColor(0xFFB6C1FFu)
/**
* Light Salmon - A light salmon color (#FFA07A).
*/
val HEXAColor.Companion.lightsalmon val HEXAColor.Companion.lightsalmon
get() = HEXAColor(0xFFA07AFFu) get() = HEXAColor(0xFFA07AFFu)
/**
* Light Sea Green - A medium sea green color (#20B2AA).
*/
val HEXAColor.Companion.lightseagreen val HEXAColor.Companion.lightseagreen
get() = HEXAColor(0x20B2AAFFu) get() = HEXAColor(0x20B2AAFFu)
/**
* Light Sky Blue - A light sky blue color (#87CEFA).
*/
val HEXAColor.Companion.lightskyblue val HEXAColor.Companion.lightskyblue
get() = HEXAColor(0x87CEFAFFu) get() = HEXAColor(0x87CEFAFFu)
/**
* Light Slate Gray - A light slate gray color (#778899).
*/
val HEXAColor.Companion.lightslategray val HEXAColor.Companion.lightslategray
get() = HEXAColor(0x778899FFu) get() = HEXAColor(0x778899FFu)
/**
* Light Slate Grey - A light slate gray color (#778899).
*/
val HEXAColor.Companion.lightslategrey val HEXAColor.Companion.lightslategrey
get() = HEXAColor(0x778899FFu) get() = HEXAColor(0x778899FFu)
/**
* Light Steel Blue - A light steel blue color (#B0C4DE).
*/
val HEXAColor.Companion.lightsteelblue val HEXAColor.Companion.lightsteelblue
get() = HEXAColor(0xB0C4DEFFu) get() = HEXAColor(0xB0C4DEFFu)
/**
* Light Yellow - A very pale yellow color (#FFFFE0).
*/
val HEXAColor.Companion.lightyellow val HEXAColor.Companion.lightyellow
get() = HEXAColor(0xFFFFE0FFu) get() = HEXAColor(0xFFFFE0FFu)
/**
* Lime - A bright lime green color (#00FF00).
*/
val HEXAColor.Companion.lime val HEXAColor.Companion.lime
get() = HEXAColor(0x00FF00FFu) get() = HEXAColor(0x00FF00FFu)
/**
* Lime Green - A lime green color (#32CD32).
*/
val HEXAColor.Companion.limegreen val HEXAColor.Companion.limegreen
get() = HEXAColor(0x32CD32FFu) get() = HEXAColor(0x32CD32FFu)
/**
* Linen - A pale beige color (#FAF0E6).
*/
val HEXAColor.Companion.linen val HEXAColor.Companion.linen
get() = HEXAColor(0xFAF0E6FFu) get() = HEXAColor(0xFAF0E6FFu)
/**
* Magenta - A bright magenta color (#FF00FF).
*/
val HEXAColor.Companion.magenta val HEXAColor.Companion.magenta
get() = HEXAColor(0xFF00FFFFu) get() = HEXAColor(0xFF00FFFFu)
/**
* Maroon - A dark reddish-brown color (#800000).
*/
val HEXAColor.Companion.maroon val HEXAColor.Companion.maroon
get() = HEXAColor(0x800000FFu) get() = HEXAColor(0x800000FFu)
/**
* Medium Aquamarine - A medium aquamarine color (#66CDAA).
*/
val HEXAColor.Companion.mediumaquamarine val HEXAColor.Companion.mediumaquamarine
get() = HEXAColor(0x66CDAAFFu) get() = HEXAColor(0x66CDAAFFu)
/**
* Medium Blue - A medium blue color (#0000CD).
*/
val HEXAColor.Companion.mediumblue val HEXAColor.Companion.mediumblue
get() = HEXAColor(0x0000CDFFu) get() = HEXAColor(0x0000CDFFu)
/**
* Medium Orchid - A medium orchid purple color (#BA55D3).
*/
val HEXAColor.Companion.mediumorchid val HEXAColor.Companion.mediumorchid
get() = HEXAColor(0xBA55D3FFu) get() = HEXAColor(0xBA55D3FFu)
/**
* Medium Purple - A medium purple color (#9370DB).
*/
val HEXAColor.Companion.mediumpurple val HEXAColor.Companion.mediumpurple
get() = HEXAColor(0x9370DBFFu) get() = HEXAColor(0x9370DBFFu)
/**
* Medium Sea Green - A medium sea green color (#3CB371).
*/
val HEXAColor.Companion.mediumseagreen val HEXAColor.Companion.mediumseagreen
get() = HEXAColor(0x3CB371FFu) get() = HEXAColor(0x3CB371FFu)
/**
* Medium Slate Blue - A medium slate blue color (#7B68EE).
*/
val HEXAColor.Companion.mediumslateblue val HEXAColor.Companion.mediumslateblue
get() = HEXAColor(0x7B68EEFFu) get() = HEXAColor(0x7B68EEFFu)
/**
* Medium Spring Green - A medium spring green color (#00FA9A).
*/
val HEXAColor.Companion.mediumspringgreen val HEXAColor.Companion.mediumspringgreen
get() = HEXAColor(0x00FA9AFFu) get() = HEXAColor(0x00FA9AFFu)
/**
* Medium Turquoise - A medium turquoise color (#48D1CC).
*/
val HEXAColor.Companion.mediumturquoise val HEXAColor.Companion.mediumturquoise
get() = HEXAColor(0x48D1CCFFu) get() = HEXAColor(0x48D1CCFFu)
/**
* Medium Violet Red - A medium violet-red color (#C71585).
*/
val HEXAColor.Companion.mediumvioletred val HEXAColor.Companion.mediumvioletred
get() = HEXAColor(0xC71585FFu) get() = HEXAColor(0xC71585FFu)
/**
* Midnight Blue - A very dark blue color (#191970).
*/
val HEXAColor.Companion.midnightblue val HEXAColor.Companion.midnightblue
get() = HEXAColor(0x191970FFu) get() = HEXAColor(0x191970FFu)
/**
* Mint Cream - A very pale mint color (#F5FFFA).
*/
val HEXAColor.Companion.mintcream val HEXAColor.Companion.mintcream
get() = HEXAColor(0xF5FFFAFFu) get() = HEXAColor(0xF5FFFAFFu)
/**
* Misty Rose - A very pale pink color (#FFE4E1).
*/
val HEXAColor.Companion.mistyrose val HEXAColor.Companion.mistyrose
get() = HEXAColor(0xFFE4E1FFu) get() = HEXAColor(0xFFE4E1FFu)
/**
* Moccasin - A pale peach color (#FFE4B5).
*/
val HEXAColor.Companion.moccasin val HEXAColor.Companion.moccasin
get() = HEXAColor(0xFFE4B5FFu) get() = HEXAColor(0xFFE4B5FFu)
/**
* Navajo White - A pale peach color (#FFDEAD).
*/
val HEXAColor.Companion.navajowhite val HEXAColor.Companion.navajowhite
get() = HEXAColor(0xFFDEADFFu) get() = HEXAColor(0xFFDEADFFu)
/**
* Navy - A very dark blue color (#000080).
*/
val HEXAColor.Companion.navy val HEXAColor.Companion.navy
get() = HEXAColor(0x000080FFu) get() = HEXAColor(0x000080FFu)
/**
* Old Lace - A very pale cream color (#FDF5E6).
*/
val HEXAColor.Companion.oldlace val HEXAColor.Companion.oldlace
get() = HEXAColor(0xFDF5E6FFu) get() = HEXAColor(0xFDF5E6FFu)
/**
* Olive - A dark yellowish-green color (#808000).
*/
val HEXAColor.Companion.olive val HEXAColor.Companion.olive
get() = HEXAColor(0x808000FFu) get() = HEXAColor(0x808000FFu)
/**
* Olive Drab - A dark olive green color (#6B8E23).
*/
val HEXAColor.Companion.olivedrab val HEXAColor.Companion.olivedrab
get() = HEXAColor(0x6B8E23FFu) get() = HEXAColor(0x6B8E23FFu)
/**
* Orange - A bright orange color (#FFA500).
*/
val HEXAColor.Companion.orange val HEXAColor.Companion.orange
get() = HEXAColor(0xFFA500FFu) get() = HEXAColor(0xFFA500FFu)
/**
* Orange Red - A bright red-orange color (#FF4500).
*/
val HEXAColor.Companion.orangered val HEXAColor.Companion.orangered
get() = HEXAColor(0xFF4500FFu) get() = HEXAColor(0xFF4500FFu)
/**
* Orchid - A medium orchid purple color (#DA70D6).
*/
val HEXAColor.Companion.orchid val HEXAColor.Companion.orchid
get() = HEXAColor(0xDA70D6FFu) get() = HEXAColor(0xDA70D6FFu)
/**
* Pale Goldenrod - A pale goldenrod yellow color (#EEE8AA).
*/
val HEXAColor.Companion.palegoldenrod val HEXAColor.Companion.palegoldenrod
get() = HEXAColor(0xEEE8AAFFu) get() = HEXAColor(0xEEE8AAFFu)
/**
* Pale Green - A pale green color (#98FB98).
*/
val HEXAColor.Companion.palegreen val HEXAColor.Companion.palegreen
get() = HEXAColor(0x98FB98FFu) get() = HEXAColor(0x98FB98FFu)
/**
* Pale Turquoise - A pale turquoise color (#AFEEEE).
*/
val HEXAColor.Companion.paleturquoise val HEXAColor.Companion.paleturquoise
get() = HEXAColor(0xAFEEEEFFu) get() = HEXAColor(0xAFEEEEFFu)
/**
* Pale Violet Red - A medium violet-red color (#DB7093).
*/
val HEXAColor.Companion.palevioletred val HEXAColor.Companion.palevioletred
get() = HEXAColor(0xDB7093FFu) get() = HEXAColor(0xDB7093FFu)
/**
* Papaya Whip - A pale peach color (#FFEFD5).
*/
val HEXAColor.Companion.papayawhip val HEXAColor.Companion.papayawhip
get() = HEXAColor(0xFFEFD5FFu) get() = HEXAColor(0xFFEFD5FFu)
/**
* Peach Puff - A light peach color (#FFDAB9).
*/
val HEXAColor.Companion.peachpuff val HEXAColor.Companion.peachpuff
get() = HEXAColor(0xFFDAB9FFu) get() = HEXAColor(0xFFDAB9FFu)
/**
* Peru - A medium brown color (#CD853F).
*/
val HEXAColor.Companion.peru val HEXAColor.Companion.peru
get() = HEXAColor(0xCD853FFFu) get() = HEXAColor(0xCD853FFFu)
/**
* Pink - A light pink color (#FFC0CB).
*/
val HEXAColor.Companion.pink val HEXAColor.Companion.pink
get() = HEXAColor(0xFFC0CBFFu) get() = HEXAColor(0xFFC0CBFFu)
/**
* Plum - A medium purple color (#DDA0DD).
*/
val HEXAColor.Companion.plum val HEXAColor.Companion.plum
get() = HEXAColor(0xDDA0DDFFu) get() = HEXAColor(0xDDA0DDFFu)
/**
* Powder Blue - A light blue color (#B0E0E6).
*/
val HEXAColor.Companion.powderblue val HEXAColor.Companion.powderblue
get() = HEXAColor(0xB0E0E6FFu) get() = HEXAColor(0xB0E0E6FFu)
/**
* Purple - A pure purple color (#800080).
*/
val HEXAColor.Companion.purple val HEXAColor.Companion.purple
get() = HEXAColor(0x800080FFu) get() = HEXAColor(0x800080FFu)
/**
* Red - Pure red color (#FF0000).
*/
val HEXAColor.Companion.red val HEXAColor.Companion.red
get() = HEXAColor(0xFF0000FFu) get() = HEXAColor(0xFF0000FFu)
/**
* Rosy Brown - A rosy brown color (#BC8F8F).
*/
val HEXAColor.Companion.rosybrown val HEXAColor.Companion.rosybrown
get() = HEXAColor(0xBC8F8FFFu) get() = HEXAColor(0xBC8F8FFFu)
/**
* Royal Blue - A vibrant royal blue color (#4169E1).
*/
val HEXAColor.Companion.royalblue val HEXAColor.Companion.royalblue
get() = HEXAColor(0x4169E1FFu) get() = HEXAColor(0x4169E1FFu)
/**
* Saddle Brown - A dark brown color (#8B4513).
*/
val HEXAColor.Companion.saddlebrown val HEXAColor.Companion.saddlebrown
get() = HEXAColor(0x8B4513FFu) get() = HEXAColor(0x8B4513FFu)
/**
* Salmon - A light salmon pink color (#FA8072).
*/
val HEXAColor.Companion.salmon val HEXAColor.Companion.salmon
get() = HEXAColor(0xFA8072FFu) get() = HEXAColor(0xFA8072FFu)
/**
* Sandy Brown - A sandy brown color (#F4A460).
*/
val HEXAColor.Companion.sandybrown val HEXAColor.Companion.sandybrown
get() = HEXAColor(0xF4A460FFu) get() = HEXAColor(0xF4A460FFu)
/**
* Sea Green - A dark sea green color (#2E8B57).
*/
val HEXAColor.Companion.seagreen val HEXAColor.Companion.seagreen
get() = HEXAColor(0x2E8B57FFu) get() = HEXAColor(0x2E8B57FFu)
/**
* Seashell - A very pale pink-orange color (#FFF5EE).
*/
val HEXAColor.Companion.seashell val HEXAColor.Companion.seashell
get() = HEXAColor(0xFFF5EEFFu) get() = HEXAColor(0xFFF5EEFFu)
/**
* Sienna - A reddish-brown color (#A0522D).
*/
val HEXAColor.Companion.sienna val HEXAColor.Companion.sienna
get() = HEXAColor(0xA0522DFFu) get() = HEXAColor(0xA0522DFFu)
/**
* Silver - A light gray-silver color (#C0C0C0).
*/
val HEXAColor.Companion.silver val HEXAColor.Companion.silver
get() = HEXAColor(0xC0C0C0FFu) get() = HEXAColor(0xC0C0C0FFu)
/**
* Sky Blue - A light sky blue color (#87CEEB).
*/
val HEXAColor.Companion.skyblue val HEXAColor.Companion.skyblue
get() = HEXAColor(0x87CEEBFFu) get() = HEXAColor(0x87CEEBFFu)
/**
* Slate Blue - A medium slate blue color (#6A5ACD).
*/
val HEXAColor.Companion.slateblue val HEXAColor.Companion.slateblue
get() = HEXAColor(0x6A5ACDFFu) get() = HEXAColor(0x6A5ACDFFu)
/**
* Slate Gray - A slate gray color (#708090).
*/
val HEXAColor.Companion.slategray val HEXAColor.Companion.slategray
get() = HEXAColor(0x708090FFu) get() = HEXAColor(0x708090FFu)
/**
* Slate Grey - A slate gray color (#708090).
*/
val HEXAColor.Companion.slategrey val HEXAColor.Companion.slategrey
get() = HEXAColor(0x708090FFu) get() = HEXAColor(0x708090FFu)
/**
* Snow - A very pale pinkish-white color (#FFFAFA).
*/
val HEXAColor.Companion.snow val HEXAColor.Companion.snow
get() = HEXAColor(0xFFFAFAFFu) get() = HEXAColor(0xFFFAFAFFu)
/**
* Spring Green - A bright spring green color (#00FF7F).
*/
val HEXAColor.Companion.springgreen val HEXAColor.Companion.springgreen
get() = HEXAColor(0x00FF7FFFu) get() = HEXAColor(0x00FF7FFFu)
/**
* Steel Blue - A medium steel blue color (#4682B4).
*/
val HEXAColor.Companion.steelblue val HEXAColor.Companion.steelblue
get() = HEXAColor(0x4682B4FFu) get() = HEXAColor(0x4682B4FFu)
/**
* Tan - A light brown tan color (#D2B48C).
*/
val HEXAColor.Companion.tan val HEXAColor.Companion.tan
get() = HEXAColor(0xD2B48CFFu) get() = HEXAColor(0xD2B48CFFu)
/**
* Teal - A dark cyan-blue color (#008080).
*/
val HEXAColor.Companion.teal val HEXAColor.Companion.teal
get() = HEXAColor(0x008080FFu) get() = HEXAColor(0x008080FFu)
/**
* Thistle - A light purple-pink color (#D8BFD8).
*/
val HEXAColor.Companion.thistle val HEXAColor.Companion.thistle
get() = HEXAColor(0xD8BFD8FFu) get() = HEXAColor(0xD8BFD8FFu)
/**
* Tomato - A vibrant red-orange color (#FF6347).
*/
val HEXAColor.Companion.tomato val HEXAColor.Companion.tomato
get() = HEXAColor(0xFF6347FFu) get() = HEXAColor(0xFF6347FFu)
/**
* Turquoise - A medium turquoise color (#40E0D0).
*/
val HEXAColor.Companion.turquoise val HEXAColor.Companion.turquoise
get() = HEXAColor(0x40E0D0FFu) get() = HEXAColor(0x40E0D0FFu)
/**
* Violet - A violet color (#EE82EE).
*/
val HEXAColor.Companion.violet val HEXAColor.Companion.violet
get() = HEXAColor(0xEE82EEFFu) get() = HEXAColor(0xEE82EEFFu)
/**
* Wheat - A light tan color (#F5DEB3).
*/
val HEXAColor.Companion.wheat val HEXAColor.Companion.wheat
get() = HEXAColor(0xF5DEB3FFu) get() = HEXAColor(0xF5DEB3FFu)
/**
* White - Pure white color (#FFFFFF).
*/
val HEXAColor.Companion.white val HEXAColor.Companion.white
get() = HEXAColor(0xFFFFFFFFu) get() = HEXAColor(0xFFFFFFFFu)
/**
* White Smoke - A very light gray color (#F5F5F5).
*/
val HEXAColor.Companion.whitesmoke val HEXAColor.Companion.whitesmoke
get() = HEXAColor(0xF5F5F5FFu) get() = HEXAColor(0xF5F5F5FFu)
/**
* Yellow - Pure yellow color (#FFFF00).
*/
val HEXAColor.Companion.yellow val HEXAColor.Companion.yellow
get() = HEXAColor(0xFFFF00FFu) get() = HEXAColor(0xFFFF00FFu)
/**
* Yellow Green - A medium yellow-green color (#9ACD32).
*/
val HEXAColor.Companion.yellowgreen val HEXAColor.Companion.yellowgreen
get() = HEXAColor(0x9ACD32FFu) get() = HEXAColor(0x9ACD32FFu)

View File

@@ -3,8 +3,10 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.runComposeUiTest import androidx.compose.ui.test.runComposeUiTest
import dev.inmo.micro_utils.common.compose.LoadableComponent import dev.inmo.micro_utils.common.compose.LoadableComponent
import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue

View File

@@ -1,15 +1,5 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Breaks this list into a list of consecutive pairs.
* Each element is paired with the next element in the list.
* For a list of size n, the result will contain n-1 pairs.
*
* Example: `[1, 2, 3, 4].breakAsPairs()` returns `[(1, 2), (2, 3), (3, 4)]`
*
* @param T The type of elements in the list
* @return A list of pairs where each pair consists of consecutive elements
*/
fun <T> List<T>.breakAsPairs(): List<Pair<T, T>> { fun <T> List<T>.breakAsPairs(): List<Pair<T, T>> {
val result = mutableListOf<Pair<T, T>>() val result = mutableListOf<Pair<T, T>>()

View File

@@ -1,12 +1,5 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Executes the given [block] and returns its result if this Boolean is true, otherwise returns null.
*
* @param T The return type of the block
* @param block The function to execute if this Boolean is true
* @return The result of [block] if true, null otherwise
*/
inline fun <T> Boolean.letIfTrue(block: () -> T): T? { inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
return if (this) { return if (this) {
block() block()
@@ -15,13 +8,6 @@ inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
} }
} }
/**
* Executes the given [block] and returns its result if this Boolean is false, otherwise returns null.
*
* @param T The return type of the block
* @param block The function to execute if this Boolean is false
* @return The result of [block] if false, null otherwise
*/
inline fun <T> Boolean.letIfFalse(block: () -> T): T? { inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
return if (this) { return if (this) {
null null
@@ -30,37 +16,16 @@ inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
} }
} }
/**
* Executes the given [block] if this Boolean is true and returns this Boolean.
* Similar to [also], but only executes the block when the Boolean is true.
*
* @param block The function to execute if this Boolean is true
* @return This Boolean value
*/
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean { inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
letIfTrue(block) letIfTrue(block)
return this return this
} }
/**
* Executes the given [block] if this Boolean is false and returns this Boolean.
* Similar to [also], but only executes the block when the Boolean is false.
*
* @param block The function to execute if this Boolean is false
* @return This Boolean value
*/
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean { inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
letIfFalse(block) letIfFalse(block)
return this return this
} }
/**
* Alias for [letIfTrue]. Executes the given [block] and returns its result if this Boolean is true.
*
* @param T The return type of the block
* @param block The function to execute if this Boolean is true
* @return The result of [block] if true, null otherwise
*/
inline fun <T> Boolean.ifTrue(block: () -> T): T? { inline fun <T> Boolean.ifTrue(block: () -> T): T? {
return if (this) { return if (this) {
block() block()
@@ -69,13 +34,6 @@ inline fun <T> Boolean.ifTrue(block: () -> T): T? {
} }
} }
/**
* Alias for [letIfFalse]. Executes the given [block] and returns its result if this Boolean is false.
*
* @param T The return type of the block
* @param block The function to execute if this Boolean is false
* @return The result of [block] if false, null otherwise
*/
inline fun <T> Boolean.ifFalse(block: () -> T): T? { inline fun <T> Boolean.ifFalse(block: () -> T): T? {
return if (this) { return if (this) {
null null

View File

@@ -6,45 +6,19 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
/**
* A function type that allocates and returns a [ByteArray].
*/
typealias ByteArrayAllocator = () -> ByteArray typealias ByteArrayAllocator = () -> ByteArray
/**
* A suspending function type that allocates and returns a [ByteArray].
*/
typealias SuspendByteArrayAllocator = suspend () -> ByteArray typealias SuspendByteArrayAllocator = suspend () -> ByteArray
/**
* Converts this [ByteArray] to a [ByteArrayAllocator] that returns this array.
*/
val ByteArray.asAllocator: ByteArrayAllocator val ByteArray.asAllocator: ByteArrayAllocator
get() = { this } get() = { this }
/**
* Converts this [ByteArray] to a [SuspendByteArrayAllocator] that returns this array.
*/
val ByteArray.asSuspendAllocator: SuspendByteArrayAllocator val ByteArray.asSuspendAllocator: SuspendByteArrayAllocator
get() = { this } get() = { this }
/**
* Converts this [ByteArrayAllocator] to a [SuspendByteArrayAllocator].
*/
val ByteArrayAllocator.asSuspendAllocator: SuspendByteArrayAllocator val ByteArrayAllocator.asSuspendAllocator: SuspendByteArrayAllocator
get() = { this() } get() = { this() }
/**
* Converts this [SuspendByteArrayAllocator] to a [ByteArrayAllocator] by invoking it and
* wrapping the result in a non-suspending allocator.
*/
suspend fun SuspendByteArrayAllocator.asAllocator(): ByteArrayAllocator { suspend fun SuspendByteArrayAllocator.asAllocator(): ByteArrayAllocator {
return invoke().asAllocator return invoke().asAllocator
} }
/**
* Serializer for [ByteArrayAllocator]. Serializes the result of invoking the allocator.
*/
object ByteArrayAllocatorSerializer : KSerializer<ByteArrayAllocator> { object ByteArrayAllocatorSerializer : KSerializer<ByteArrayAllocator> {
private val realSerializer = ByteArraySerializer() private val realSerializer = ByteArraySerializer()
override val descriptor: SerialDescriptor = realSerializer.descriptor override val descriptor: SerialDescriptor = realSerializer.descriptor

View File

@@ -1,10 +1,3 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Returns the first non-null element in this iterable.
*
* @param T The type of elements in the iterable (nullable)
* @return The first non-null element
* @throws NoSuchElementException if the iterable contains no non-null elements
*/
fun <T> Iterable<T?>.firstNotNull() = first { it != null }!! fun <T> Iterable<T?>.firstNotNull() = first { it != null }!!

View File

@@ -1,19 +1,5 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Joins elements of this iterable into a list with separators between elements.
* Each element is transformed using [transform], and separators are generated using [separatorFun].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from transformations or separator function are skipped.
*
* @param I The type of elements in the input iterable
* @param R The type of elements in the result list
* @param separatorFun A function that generates a separator based on the current element
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @param transform A function to transform each element
* @return A list of transformed elements with separators
*/
inline fun <I, R> Iterable<I>.joinTo( inline fun <I, R> Iterable<I>.joinTo(
separatorFun: (I) -> R?, separatorFun: (I) -> R?,
prefix: R? = null, prefix: R? = null,
@@ -39,20 +25,6 @@ inline fun <I, R> Iterable<I>.joinTo(
return result return result
} }
/**
* Joins elements of this iterable into a list with a constant separator between elements.
* Each element is transformed using [transform].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from transformations or separators are skipped.
*
* @param I The type of elements in the input iterable
* @param R The type of elements in the result list
* @param separator The separator to insert between elements
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @param transform A function to transform each element
* @return A list of transformed elements with separators
*/
inline fun <I, R> Iterable<I>.joinTo( inline fun <I, R> Iterable<I>.joinTo(
separator: R? = null, separator: R? = null,
prefix: R? = null, prefix: R? = null,
@@ -60,55 +32,18 @@ inline fun <I, R> Iterable<I>.joinTo(
transform: (I) -> R? transform: (I) -> R?
): List<R> = joinTo({ separator }, prefix, postfix, transform) ): List<R> = joinTo({ separator }, prefix, postfix, transform)
/**
* Joins elements of this iterable into a list with separators between elements.
* Separators are generated using [separatorFun].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from separator function are skipped.
*
* @param I The type of elements
* @param separatorFun A function that generates a separator based on the current element
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @return A list of elements with separators
*/
inline fun <I> Iterable<I>.joinTo( inline fun <I> Iterable<I>.joinTo(
separatorFun: (I) -> I?, separatorFun: (I) -> I?,
prefix: I? = null, prefix: I? = null,
postfix: I? = null postfix: I? = null
): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it } ): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it }
/**
* Joins elements of this iterable into a list with a constant separator between elements.
* Optional [prefix] and [postfix] can be added to the result.
* Null separators are skipped.
*
* @param I The type of elements
* @param separator The separator to insert between elements
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @return A list of elements with separators
*/
inline fun <I> Iterable<I>.joinTo( inline fun <I> Iterable<I>.joinTo(
separator: I? = null, separator: I? = null,
prefix: I? = null, prefix: I? = null,
postfix: I? = null postfix: I? = null
): List<I> = joinTo<I>({ separator }, prefix, postfix) ): List<I> = joinTo<I>({ separator }, prefix, postfix)
/**
* Joins elements of this array into an array with separators between elements.
* Each element is transformed using [transform], and separators are generated using [separatorFun].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from transformations or separator function are skipped.
*
* @param I The type of elements in the input array
* @param R The type of elements in the result array
* @param separatorFun A function that generates a separator based on the current element
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @param transform A function to transform each element
* @return An array of transformed elements with separators
*/
inline fun <I, reified R> Array<I>.joinTo( inline fun <I, reified R> Array<I>.joinTo(
separatorFun: (I) -> R?, separatorFun: (I) -> R?,
prefix: R? = null, prefix: R? = null,
@@ -116,20 +51,6 @@ inline fun <I, reified R> Array<I>.joinTo(
transform: (I) -> R? transform: (I) -> R?
): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray() ): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray()
/**
* Joins elements of this array into an array with a constant separator between elements.
* Each element is transformed using [transform].
* Optional [prefix] and [postfix] can be added to the result.
* Null values from transformations or separators are skipped.
*
* @param I The type of elements in the input array
* @param R The type of elements in the result array
* @param separator The separator to insert between elements
* @param prefix Optional prefix to add at the beginning of the result
* @param postfix Optional postfix to add at the end of the result
* @param transform A function to transform each element
* @return An array of transformed elements with separators
*/
inline fun <I, reified R> Array<I>.joinTo( inline fun <I, reified R> Array<I>.joinTo(
separator: R? = null, separator: R? = null,
prefix: R? = null, prefix: R? = null,

View File

@@ -7,7 +7,7 @@ import kotlin.jvm.JvmInline
@JvmInline @JvmInline
value class FileName(val string: String) { value class FileName(val string: String) {
val name: String val name: String
get() = withoutSlashAtTheEnd.takeLastWhile { it != MPPFilePathSeparator } get() = withoutSlashAtTheEnd.takeLastWhile { it != '/' }
val extension: String val extension: String
get() = name.takeLastWhile { it != '.' } get() = name.takeLastWhile { it != '.' }
val nameWithoutExtension: String val nameWithoutExtension: String
@@ -18,7 +18,7 @@ value class FileName(val string: String) {
} ?: filename } ?: filename
} }
val withoutSlashAtTheEnd: String val withoutSlashAtTheEnd: String
get() = string.dropLastWhile { it == MPPFilePathSeparator } get() = string.dropLastWhile { it == '/' }
override fun toString(): String = string override fun toString(): String = string
} }
@@ -26,7 +26,6 @@ value class FileName(val string: String) {
expect class MPPFile expect class MPPFile
expect val MPPFile.filename: FileName expect val MPPFile.filename: FileName
expect val MPPFilePathSeparator: Char
expect val MPPFile.filesize: Long expect val MPPFile.filesize: Long
expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator

View File

@@ -2,43 +2,16 @@ package dev.inmo.micro_utils.common
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
/**
* A bidirectional mapper that can convert between two types [T1] and [T2].
*
* @param T1 The first type
* @param T2 The second type
*/
interface SimpleMapper<T1, T2> { interface SimpleMapper<T1, T2> {
fun convertToT1(from: T2): T1 fun convertToT1(from: T2): T1
fun convertToT2(from: T1): T2 fun convertToT2(from: T1): T2
} }
/**
* Converts [from] of type [T2] to type [T1] using this mapper.
*
* @param from The value to convert
* @return The converted value of type [T1]
*/
@JvmName("convertFromT2") @JvmName("convertFromT2")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from) fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from)
/**
* Converts [from] of type [T1] to type [T2] using this mapper.
*
* @param from The value to convert
* @return The converted value of type [T2]
*/
@JvmName("convertFromT1") @JvmName("convertFromT1")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from) fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from)
/**
* Implementation of [SimpleMapper] that uses lambda functions for conversion.
*
* @param T1 The first type
* @param T2 The second type
* @param t1 Function to convert from [T2] to [T1]
* @param t2 Function to convert from [T1] to [T2]
*/
class SimpleMapperImpl<T1, T2>( class SimpleMapperImpl<T1, T2>(
private val t1: (T2) -> T1, private val t1: (T2) -> T1,
private val t2: (T1) -> T2, private val t2: (T1) -> T2,
@@ -48,58 +21,22 @@ class SimpleMapperImpl<T1, T2>(
override fun convertToT2(from: T1): T2 = t2.invoke(from) override fun convertToT2(from: T1): T2 = t2.invoke(from)
} }
/**
* Creates a [SimpleMapper] using the provided conversion functions.
*
* @param T1 The first type
* @param T2 The second type
* @param t1 Function to convert from [T2] to [T1]
* @param t2 Function to convert from [T1] to [T2]
* @return A new [SimpleMapperImpl] instance
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleMapper( inline fun <T1, T2> simpleMapper(
noinline t1: (T2) -> T1, noinline t1: (T2) -> T1,
noinline t2: (T1) -> T2, noinline t2: (T1) -> T2,
) = SimpleMapperImpl(t1, t2) ) = SimpleMapperImpl(t1, t2)
/**
* A bidirectional mapper that can convert between two types [T1] and [T2] using suspending functions.
*
* @param T1 The first type
* @param T2 The second type
*/
interface SimpleSuspendableMapper<T1, T2> { interface SimpleSuspendableMapper<T1, T2> {
suspend fun convertToT1(from: T2): T1 suspend fun convertToT1(from: T2): T1
suspend fun convertToT2(from: T1): T2 suspend fun convertToT2(from: T1): T2
} }
/**
* Converts [from] of type [T2] to type [T1] using this suspending mapper.
*
* @param from The value to convert
* @return The converted value of type [T1]
*/
@JvmName("convertFromT2") @JvmName("convertFromT2")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from) suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from)
/**
* Converts [from] of type [T1] to type [T2] using this suspending mapper.
*
* @param from The value to convert
* @return The converted value of type [T2]
*/
@JvmName("convertFromT1") @JvmName("convertFromT1")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from) suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from)
/**
* Implementation of [SimpleSuspendableMapper] that uses suspending lambda functions for conversion.
*
* @param T1 The first type
* @param T2 The second type
* @param t1 Suspending function to convert from [T2] to [T1]
* @param t2 Suspending function to convert from [T1] to [T2]
*/
class SimpleSuspendableMapperImpl<T1, T2>( class SimpleSuspendableMapperImpl<T1, T2>(
private val t1: suspend (T2) -> T1, private val t1: suspend (T2) -> T1,
private val t2: suspend (T1) -> T2, private val t2: suspend (T1) -> T2,
@@ -109,15 +46,6 @@ class SimpleSuspendableMapperImpl<T1, T2>(
override suspend fun convertToT2(from: T1): T2 = t2.invoke(from) override suspend fun convertToT2(from: T1): T2 = t2.invoke(from)
} }
/**
* Creates a [SimpleSuspendableMapper] using the provided suspending conversion functions.
*
* @param T1 The first type
* @param T2 The second type
* @param t1 Suspending function to convert from [T2] to [T1]
* @param t2 Suspending function to convert from [T1] to [T2]
* @return A new [SimpleSuspendableMapperImpl] instance
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleSuspendableMapper( inline fun <T1, T2> simpleSuspendableMapper(
noinline t1: suspend (T2) -> T1, noinline t1: suspend (T2) -> T1,

View File

@@ -1,14 +1,5 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Pads this sequence to the specified [size] using a custom [inserter] function.
* The [inserter] is repeatedly called until the sequence reaches the desired size.
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param inserter A function that takes the current sequence and returns a new padded sequence
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padWith(size: Int, inserter: (Sequence<T>) -> Sequence<T>): Sequence<T> { inline fun <T> Sequence<T>.padWith(size: Int, inserter: (Sequence<T>) -> Sequence<T>): Sequence<T> {
var result = this var result = this
while (result.count() < size) { while (result.count() < size) {
@@ -17,36 +8,10 @@ inline fun <T> Sequence<T>.padWith(size: Int, inserter: (Sequence<T>) -> Sequenc
return result return result
} }
/**
* Pads this sequence at the end to the specified [size].
* New elements are generated using [padBlock], which receives the current size as a parameter.
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param padBlock A function that generates padding elements based on the current sequence size
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padEnd(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { it + padBlock(it.count()) } inline fun <T> Sequence<T>.padEnd(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { it + padBlock(it.count()) }
/**
* Pads this sequence at the end to the specified [size] using the given element [o].
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param o The element to use for padding
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padEnd(size: Int, o: T) = padEnd(size) { o } inline fun <T> Sequence<T>.padEnd(size: Int, o: T) = padEnd(size) { o }
/**
* Pads this list to the specified [size] using a custom [inserter] function.
* The [inserter] is repeatedly called until the list reaches the desired size.
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param inserter A function that takes the current list and returns a new padded list
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padWith(size: Int, inserter: (List<T>) -> List<T>): List<T> { inline fun <T> List<T>.padWith(size: Int, inserter: (List<T>) -> List<T>): List<T> {
var result = this var result = this
while (result.size < size) { while (result.size < size) {
@@ -54,66 +19,14 @@ inline fun <T> List<T>.padWith(size: Int, inserter: (List<T>) -> List<T>): List<
} }
return result return result
} }
/**
* Pads this list at the end to the specified [size].
* New elements are generated using [padBlock], which receives the current size as a parameter.
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param padBlock A function that generates padding elements based on the current list size
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padEnd(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padEnd(size, padBlock).toList() inline fun <T> List<T>.padEnd(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padEnd(size, padBlock).toList()
/**
* Pads this list at the end to the specified [size] using the given element [o].
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param o The element to use for padding
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padEnd(size: Int, o: T): List<T> = asSequence().padEnd(size, o).toList() inline fun <T> List<T>.padEnd(size: Int, o: T): List<T> = asSequence().padEnd(size, o).toList()
/**
* Pads this sequence at the start to the specified [size].
* New elements are generated using [padBlock], which receives the current size as a parameter.
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param padBlock A function that generates padding elements based on the current sequence size
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padStart(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { sequenceOf(padBlock(it.count())) + it } inline fun <T> Sequence<T>.padStart(size: Int, padBlock: (Int) -> T): Sequence<T> = padWith(size) { sequenceOf(padBlock(it.count())) + it }
/**
* Pads this sequence at the start to the specified [size] using the given element [o].
*
* @param T The type of elements in the sequence
* @param size The target size of the padded sequence
* @param o The element to use for padding
* @return A sequence padded to at least the specified size
*/
inline fun <T> Sequence<T>.padStart(size: Int, o: T) = padStart(size) { o } inline fun <T> Sequence<T>.padStart(size: Int, o: T) = padStart(size) { o }
/**
* Pads this list at the start to the specified [size].
* New elements are generated using [padBlock], which receives the current size as a parameter.
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param padBlock A function that generates padding elements based on the current list size
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padStart(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padStart(size, padBlock).toList() inline fun <T> List<T>.padStart(size: Int, padBlock: (Int) -> T): List<T> = asSequence().padStart(size, padBlock).toList()
/**
* Pads this list at the start to the specified [size] using the given element [o].
*
* @param T The type of elements in the list
* @param size The target size of the padded list
* @param o The element to use for padding
* @return A list padded to at least the specified size
*/
inline fun <T> List<T>.padStart(size: Int, o: T): List<T> = asSequence().padStart(size, o).toList() inline fun <T> List<T>.padStart(size: Int, o: T): List<T> = asSequence().padStart(size, o).toList()

View File

@@ -1,39 +1,17 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Computes the intersection of this range with [other]. Returns a pair representing
* the intersecting range, or null if the ranges don't overlap.
*
* @param T The type of comparable values in the range
* @param other The other range to intersect with
* @return A pair (start, end) representing the intersection, or null if no intersection exists
*/
fun <T : Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): Pair<T, T>? = when { fun <T : Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): Pair<T, T>? = when {
start == other.start && endInclusive == other.endInclusive -> start to endInclusive start == other.start && endInclusive == other.endInclusive -> start to endInclusive
start > other.endInclusive || other.start > endInclusive -> null start > other.endInclusive || other.start > endInclusive -> null
else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive) else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive)
} }
/**
* Computes the intersection of this [IntRange] with [other].
* Returns the intersecting range, or null if the ranges don't overlap.
*
* @param other The other range to intersect with
* @return An [IntRange] representing the intersection, or null if no intersection exists
*/
fun IntRange.intersect( fun IntRange.intersect(
other: IntRange other: IntRange
): IntRange? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let { ): IntRange? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
it.first .. it.second it.first .. it.second
} }
/**
* Computes the intersection of this [LongRange] with [other].
* Returns the intersecting range, or null if the ranges don't overlap.
*
* @param other The other range to intersect with
* @return A [LongRange] representing the intersection, or null if no intersection exists
*/
fun LongRange.intersect( fun LongRange.intersect(
other: LongRange other: LongRange
): LongRange? = (this as ClosedRange<Long>).intersect(other as ClosedRange<Long>) ?.let { ): LongRange? = (this as ClosedRange<Long>).intersect(other as ClosedRange<Long>) ?.let {

View File

@@ -1,24 +1,5 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Returns a new list with the element at index [i] replaced by applying [block] to it.
* All other elements remain unchanged.
*
* @param T The type of elements in the iterable
* @param i The index of the element to replace
* @param block A function that transforms the element at the given index
* @return A new list with the replaced element
*/
fun <T> Iterable<T>.withReplacedAt(i: Int, block: (T) -> T): List<T> = take(i) + block(elementAt(i)) + drop(i + 1) fun <T> Iterable<T>.withReplacedAt(i: Int, block: (T) -> T): List<T> = take(i) + block(elementAt(i)) + drop(i + 1)
/**
* Returns a new list with the first occurrence of element [t] replaced by applying [block] to it.
* All other elements remain unchanged.
*
* @param T The type of elements in the iterable
* @param t The element to replace
* @param block A function that transforms the found element
* @return A new list with the replaced element
*/
fun <T> Iterable<T>.withReplaced(t: T, block: (T) -> T): List<T> = withReplacedAt(indexOf(t), block) fun <T> Iterable<T>.withReplaced(t: T, block: (T) -> T): List<T> = withReplacedAt(indexOf(t), block)

View File

@@ -35,10 +35,6 @@ private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().awa
*/ */
actual val MPPFile.filename: FileName actual val MPPFile.filename: FileName
get() = FileName(name) get() = FileName(name)
actual val MPPFilePathSeparator: Char
get() = '/'
/** /**
* @suppress * @suppress
*/ */

View File

@@ -14,10 +14,6 @@ actual typealias MPPFile = File
*/ */
actual val MPPFile.filename: FileName actual val MPPFile.filename: FileName
get() = FileName(name) get() = FileName(name)
actual val MPPFilePathSeparator: Char
get() = File.separatorChar
/** /**
* @suppress * @suppress
*/ */

View File

@@ -11,10 +11,6 @@ actual typealias MPPFile = Path
*/ */
actual val MPPFile.filename: FileName actual val MPPFile.filename: FileName
get() = FileName(toString()) get() = FileName(toString())
actual val MPPFilePathSeparator: Char = Path.DIRECTORY_SEPARATOR.first()
/** /**
* @suppress * @suppress
*/ */

View File

@@ -37,10 +37,6 @@ private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().awa
*/ */
actual val MPPFile.filename: FileName actual val MPPFile.filename: FileName
get() = FileName(name) get() = FileName(name)
actual val MPPFilePathSeparator: Char
get() = '/'
/** /**
* @suppress * @suppress
*/ */

View File

@@ -1,34 +1,19 @@
package dev.inmo.micro_utils.coroutines package dev.inmo.micro_utils.coroutines
import dev.inmo.kslog.common.KSLog
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.consumeAsFlow
/**
* Creates an actor-style channel that processes messages asynchronously based on markers.
* Messages with the same marker will be processed sequentially, while messages with different markers can be processed concurrently.
*
* @param T The type of messages to process
* @param channelCapacity The capacity of the underlying channel. Defaults to [Channel.UNLIMITED]
* @param markerFactory A factory function that produces a marker for each message. Messages with the same marker
* will be processed sequentially. Defaults to returning null, meaning all messages will be processed sequentially
* @param logger The logger instance used for logging exceptions. Defaults to [KSLog]
* @param block The suspending function that processes each message
* @return A [Channel] that accepts messages to be processed
*/
fun <T> CoroutineScope.actorAsync( fun <T> CoroutineScope.actorAsync(
channelCapacity: Int = Channel.UNLIMITED, channelCapacity: Int = Channel.UNLIMITED,
markerFactory: suspend (T) -> Any? = { null }, markerFactory: suspend (T) -> Any? = { null },
logger: KSLog = KSLog,
block: suspend (T) -> Unit block: suspend (T) -> Unit
): Channel<T> { ): Channel<T> {
val channel = Channel<T>(channelCapacity) val channel = Channel<T>(channelCapacity)
channel.consumeAsFlow().subscribeAsync(this, markerFactory, logger, block) channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
return channel return channel
} }
@Deprecated("Use standard actosAsync instead", ReplaceWith("actorAsync(channelCapacity, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.actorAsync"))
inline fun <T> CoroutineScope.safeActorAsync( inline fun <T> CoroutineScope.safeActorAsync(
channelCapacity: Int = Channel.UNLIMITED, channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,

View File

@@ -3,12 +3,5 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
/**
* Wraps this value in a completed [Deferred]. The resulting [Deferred] is immediately completed with this value.
* Useful for converting synchronous values to [Deferred] in contexts that expect deferred values.
*
* @param T The type of the value
* @return A [Deferred] that is already completed with this value
*/
val <T> T.asDeferred: Deferred<T> val <T> T.asDeferred: Deferred<T>
get() = CompletableDeferred(this) get() = CompletableDeferred(this)

View File

@@ -3,53 +3,20 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/**
* Convenience property to access [Dispatchers.Main] for UI operations.
*/
inline val UI inline val UI
get() = Dispatchers.Main get() = Dispatchers.Main
/**
* Convenience property to access [Dispatchers.Default] for CPU-intensive operations.
*/
inline val Default inline val Default
get() = Dispatchers.Default get() = Dispatchers.Default
/**
* Executes the given [block] in the specified coroutine [context] and returns its result.
* This is a convenience wrapper around [withContext].
*
* @param T The return type of the block
* @param context The [CoroutineContext] in which to execute the block
* @param block The suspending function to execute
* @return The result of executing the block
*/
suspend inline fun <T> doIn(context: CoroutineContext, noinline block: suspend CoroutineScope.() -> T) = withContext( suspend inline fun <T> doIn(context: CoroutineContext, noinline block: suspend CoroutineScope.() -> T) = withContext(
context, context,
block block
) )
/**
* Executes the given [block] on the UI/Main dispatcher and returns its result.
* This is a convenience function for executing UI operations.
*
* @param T The return type of the block
* @param block The suspending function to execute on the UI thread
* @return The result of executing the block
*/
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = doIn( suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = doIn(
UI, UI,
block block
) )
/**
* Executes the given [block] on the Default dispatcher and returns its result.
* This is a convenience function for executing CPU-intensive operations.
*
* @param T The return type of the block
* @param block The suspending function to execute on the Default dispatcher
* @return The result of executing the block
*/
suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = doIn( suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = doIn(
Default, Default,
block block

View File

@@ -2,14 +2,6 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.* import kotlinx.coroutines.*
/**
* Represents a deferred action that combines a [Deferred] value with a callback to be executed on that value.
*
* @param T The type of the deferred value
* @param O The type of the result after applying the callback
* @param deferred The deferred value to await
* @param callback The suspending function to apply to the deferred value
*/
class DeferredAction<T, O>( class DeferredAction<T, O>(
val deferred: Deferred<T>, val deferred: Deferred<T>,
val callback: suspend (T) -> O val callback: suspend (T) -> O
@@ -17,13 +9,6 @@ class DeferredAction<T, O>(
suspend operator fun invoke() = callback(deferred.await()) suspend operator fun invoke() = callback(deferred.await())
} }
/**
* A builder for creating multiple deferred computations that can be executed, with only the first completing
* one being used. This is useful for race conditions where you want the result of whichever computation finishes first.
*
* @param T The type of values produced by the deferred computations
* @param scope The [CoroutineScope] in which to create the deferred computations
*/
class DoWithFirstBuilder<T>( class DoWithFirstBuilder<T>(
private val scope: CoroutineScope private val scope: CoroutineScope
) { ) {
@@ -37,25 +22,8 @@ class DoWithFirstBuilder<T>(
fun build() = deferreds.toList() fun build() = deferreds.toList()
} }
/**
* Creates a [DeferredAction] from this [Deferred] and a [callback] function.
*
* @param T The type of the deferred value
* @param O The type of the result after applying the callback
* @param callback The suspending function to apply to the deferred value
* @return A [DeferredAction] combining the deferred and callback
*/
fun <T, O> Deferred<T>.buildAction(callback: suspend (T) -> O) = DeferredAction(this, callback) fun <T, O> Deferred<T>.buildAction(callback: suspend (T) -> O) = DeferredAction(this, callback)
/**
* Invokes the first [DeferredAction] whose deferred value completes, executing its callback and returning the result.
* Other deferred actions are cancelled if [cancelOnResult] is true.
*
* @param O The type of the result after applying callbacks
* @param scope The [CoroutineScope] in which to await the deferred values
* @param cancelOnResult If true, cancels all other deferred actions after the first completes. Defaults to true
* @return The result of invoking the first completed deferred action
*/
suspend fun <O> Iterable<DeferredAction<*, O>>.invokeFirstOf( suspend fun <O> Iterable<DeferredAction<*, O>>.invokeFirstOf(
scope: CoroutineScope, scope: CoroutineScope,
cancelOnResult: Boolean = true cancelOnResult: Boolean = true
@@ -65,50 +33,18 @@ suspend fun <O> Iterable<DeferredAction<*, O>>.invokeFirstOf(
} }
} }
/**
* Invokes the first [DeferredAction] from the given [variants] whose deferred value completes,
* executing its callback and returning the result. Other deferred actions are cancelled if [cancelOnResult] is true.
*
* @param O The type of the result after applying callbacks
* @param scope The [CoroutineScope] in which to await the deferred values
* @param variants The deferred actions to race
* @param cancelOnResult If true, cancels all other deferred actions after the first completes. Defaults to true
* @return The result of invoking the first completed deferred action
*/
suspend fun <O> invokeFirstOf( suspend fun <O> invokeFirstOf(
scope: CoroutineScope, scope: CoroutineScope,
vararg variants: DeferredAction<*, O>, vararg variants: DeferredAction<*, O>,
cancelOnResult: Boolean = true cancelOnResult: Boolean = true
): O = variants.toList().invokeFirstOf(scope, cancelOnResult) ): O = variants.toList().invokeFirstOf(scope, cancelOnResult)
/**
* Awaits the first [Deferred] to complete and invokes the [callback] on its value.
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param O The type of the result after applying the callback
* @param scope The [CoroutineScope] in which to await the deferred values
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @param callback The suspending function to apply to the first completed value
* @return The result of applying the callback to the first completed value
*/
suspend fun <T, O> Iterable<Deferred<T>>.invokeOnFirst( suspend fun <T, O> Iterable<Deferred<T>>.invokeOnFirst(
scope: CoroutineScope, scope: CoroutineScope,
cancelOnResult: Boolean = true, cancelOnResult: Boolean = true,
callback: suspend (T) -> O callback: suspend (T) -> O
): O = map { it.buildAction(callback) }.invokeFirstOf(scope, cancelOnResult) ): O = map { it.buildAction(callback) }.invokeFirstOf(scope, cancelOnResult)
/**
* Builds multiple deferred computations using [DoWithFirstBuilder] and invokes [callback] on the first one to complete.
* Other deferred computations are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param O The type of the result after applying the callback
* @param cancelOnResult If true, cancels all other computations after the first completes. Defaults to true
* @param block Builder DSL to define the deferred computations
* @param callback The suspending function to apply to the first completed value
* @return The result of applying the callback to the first completed value
*/
suspend fun <T, O> CoroutineScope.invokeOnFirstOf( suspend fun <T, O> CoroutineScope.invokeOnFirstOf(
cancelOnResult: Boolean = true, cancelOnResult: Boolean = true,
block: DoWithFirstBuilder<T>.() -> Unit, block: DoWithFirstBuilder<T>.() -> Unit,
@@ -118,18 +54,6 @@ suspend fun <T, O> CoroutineScope.invokeOnFirstOf(
cancelOnResult cancelOnResult
).let { callback(it) } ).let { callback(it) }
/**
* Awaits the first [Deferred] from the given [variants] to complete and invokes the [callback] on its value.
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param O The type of the result after applying the callback
* @param scope The [CoroutineScope] in which to await the deferred values
* @param variants The deferred values to race
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @param callback The suspending function to apply to the first completed value
* @return The result of applying the callback to the first completed value
*/
suspend fun <T, O> invokeOnFirst( suspend fun <T, O> invokeOnFirst(
scope: CoroutineScope, scope: CoroutineScope,
vararg variants: Deferred<T>, vararg variants: Deferred<T>,
@@ -137,29 +61,11 @@ suspend fun <T, O> invokeOnFirst(
callback: suspend (T) -> O callback: suspend (T) -> O
): O = variants.toList().invokeOnFirst(scope, cancelOnResult, callback) ): O = variants.toList().invokeOnFirst(scope, cancelOnResult, callback)
/**
* Returns the value of the first [Deferred] from the given [variants] to complete.
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param variants The deferred values to race
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @return The value of the first completed deferred
*/
suspend fun <T> CoroutineScope.firstOf( suspend fun <T> CoroutineScope.firstOf(
variants: Iterable<Deferred<T>>, variants: Iterable<Deferred<T>>,
cancelOnResult: Boolean = true cancelOnResult: Boolean = true
) = variants.invokeOnFirst(this, cancelOnResult) { it } ) = variants.invokeOnFirst(this, cancelOnResult) { it }
/**
* Builds multiple deferred computations using [DoWithFirstBuilder] and returns the value of the first one to complete.
* Other deferred computations are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param cancelOnResult If true, cancels all other computations after the first completes. Defaults to true
* @param block Builder DSL to define the deferred computations
* @return The value of the first completed computation
*/
suspend fun <T> CoroutineScope.firstOf( suspend fun <T> CoroutineScope.firstOf(
cancelOnResult: Boolean = true, cancelOnResult: Boolean = true,
block: DoWithFirstBuilder<T>.() -> Unit block: DoWithFirstBuilder<T>.() -> Unit
@@ -168,29 +74,11 @@ suspend fun <T> CoroutineScope.firstOf(
cancelOnResult cancelOnResult
) )
/**
* Returns the value of the first [Deferred] from the given [variants] to complete.
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param variants The deferred values to race
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @return The value of the first completed deferred
*/
suspend fun <T> CoroutineScope.firstOf( suspend fun <T> CoroutineScope.firstOf(
vararg variants: Deferred<T>, vararg variants: Deferred<T>,
cancelOnResult: Boolean = true cancelOnResult: Boolean = true
) = firstOf(variants.toList(), cancelOnResult) ) = firstOf(variants.toList(), cancelOnResult)
/**
* Returns the value of the first [Deferred] from this list to complete, using the given [scope].
* Other deferred values are cancelled if [cancelOnResult] is true.
*
* @param T The type of the deferred values
* @param scope The [CoroutineScope] in which to await the deferred values
* @param cancelOnResult If true, cancels all other deferred values after the first completes. Defaults to true
* @return The value of the first completed deferred
*/
suspend fun <T> List<Deferred<T>>.first( suspend fun <T> List<Deferred<T>>.first(
scope: CoroutineScope, scope: CoroutineScope,
cancelOnResult: Boolean = true cancelOnResult: Boolean = true

View File

@@ -2,11 +2,4 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
/**
* Operator function that allows a [FlowCollector] to be invoked like a function to emit a value.
* This is a convenient syntax sugar for [FlowCollector.emit].
*
* @param T The type of values the collector can emit
* @param value The value to emit
*/
suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value) suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)

View File

@@ -14,15 +14,6 @@ private value class DebouncedByData<T>(
val millisToData: Pair<Long, T> val millisToData: Pair<Long, T>
) )
/**
* Debounces a [Flow] with per-marker timeout control. Values with the same marker will be debounced independently.
* For each marker, only the last value within the timeout period will be emitted.
*
* @param T The type of values emitted by the flow
* @param timeout A function that determines the debounce timeout in milliseconds for each value
* @param markerFactory A function that produces a marker for each value. Values with the same marker are debounced together
* @return A [Flow] that emits debounced values
*/
fun <T> Flow<T>.debouncedBy(timeout: (T) -> Long, markerFactory: (T) -> Any?): Flow<T> = channelFlow { fun <T> Flow<T>.debouncedBy(timeout: (T) -> Long, markerFactory: (T) -> Any?): Flow<T> = channelFlow {
val jobs = mutableMapOf<Any?, Job>() val jobs = mutableMapOf<Any?, Job>()
val mutex = Mutex() val mutex = Mutex()
@@ -45,24 +36,5 @@ fun <T> Flow<T>.debouncedBy(timeout: (T) -> Long, markerFactory: (T) -> Any?): F
} }
} }
/**
* Debounces a [Flow] with a fixed timeout in milliseconds and per-marker control.
* Values with the same marker will be debounced independently.
*
* @param T The type of values emitted by the flow
* @param timeout The debounce timeout in milliseconds
* @param markerFactory A function that produces a marker for each value. Values with the same marker are debounced together
* @return A [Flow] that emits debounced values
*/
fun <T> Flow<T>.debouncedBy(timeout: Long, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout }, markerFactory) fun <T> Flow<T>.debouncedBy(timeout: Long, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout }, markerFactory)
/**
* Debounces a [Flow] with a fixed timeout as [Duration] and per-marker control.
* Values with the same marker will be debounced independently.
*
* @param T The type of values emitted by the flow
* @param timeout The debounce timeout as a [Duration]
* @param markerFactory A function that produces a marker for each value. Values with the same marker are debounced together
* @return A [Flow] that emits debounced values
*/
fun <T> Flow<T>.debouncedBy(timeout: Duration, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout.inWholeMilliseconds }, markerFactory) fun <T> Flow<T>.debouncedBy(timeout: Duration, markerFactory: (T) -> Any?): Flow<T> = debouncedBy({ timeout.inWholeMilliseconds }, markerFactory)

View File

@@ -3,12 +3,4 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
/**
* Returns the first non-null element emitted by this [Flow].
* Suspends until a non-null element is found.
*
* @param T The type of elements in the flow
* @return The first non-null element
* @throws NoSuchElementException if the flow completes without emitting a non-null element
*/
suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!! suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!!

View File

@@ -4,14 +4,6 @@ import kotlinx.coroutines.flow.*
import kotlin.js.JsName import kotlin.js.JsName
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
/**
* Transforms each inner [Flow] element using the given [mapper] function and flattens the result into a single [Flow].
*
* @param T The type of elements in the inner flows
* @param R The type of elements after applying the mapper
* @param mapper A suspending function to transform each element
* @return A [Flow] of mapped and flattened elements
*/
inline fun <T, R> Flow<Flow<T>>.flatMap( inline fun <T, R> Flow<Flow<T>>.flatMap(
crossinline mapper: suspend (T) -> R crossinline mapper: suspend (T) -> R
) = flow { ) = flow {
@@ -22,14 +14,6 @@ inline fun <T, R> Flow<Flow<T>>.flatMap(
} }
} }
/**
* Transforms each element from inner [Iterable]s using the given [mapper] function and flattens the result into a single [Flow].
*
* @param T The type of elements in the iterables
* @param R The type of elements after applying the mapper
* @param mapper A suspending function to transform each element
* @return A [Flow] of mapped and flattened elements
*/
@JsName("flatMapIterable") @JsName("flatMapIterable")
@JvmName("flatMapIterable") @JvmName("flatMapIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMap( inline fun <T, R> Flow<Iterable<T>>.flatMap(
@@ -38,48 +22,18 @@ inline fun <T, R> Flow<Iterable<T>>.flatMap(
it.asFlow() it.asFlow()
}.flatMap(mapper) }.flatMap(mapper)
/**
* Transforms each inner [Flow] element using the given [mapper] function, flattens the result,
* and filters out null values.
*
* @param T The type of elements in the inner flows
* @param R The type of elements after applying the mapper
* @param mapper A suspending function to transform each element
* @return A [Flow] of non-null mapped and flattened elements
*/
inline fun <T, R> Flow<Flow<T>>.flatMapNotNull( inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull() ) = flatMap(mapper).takeNotNull()
/**
* Transforms each element from inner [Iterable]s using the given [mapper] function, flattens the result,
* and filters out null values.
*
* @param T The type of elements in the iterables
* @param R The type of elements after applying the mapper
* @param mapper A suspending function to transform each element
* @return A [Flow] of non-null mapped and flattened elements
*/
@JsName("flatMapNotNullIterable") @JsName("flatMapNotNullIterable")
@JvmName("flatMapNotNullIterable") @JvmName("flatMapNotNullIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull( inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull() ) = flatMap(mapper).takeNotNull()
/**
* Flattens a [Flow] of [Flow]s into a single [Flow] by collecting all inner flows sequentially.
*
* @param T The type of elements in the inner flows
* @return A [Flow] containing all elements from all inner flows
*/
fun <T> Flow<Flow<T>>.flatten() = flatMap { it } fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
/**
* Flattens a [Flow] of [Iterable]s into a single [Flow] by emitting all elements from each iterable.
*
* @param T The type of elements in the iterables
* @return A [Flow] containing all elements from all iterables
*/
@JsName("flattenIterable") @JsName("flattenIterable")
@JvmName("flattenIterable") @JvmName("flattenIterable")
fun <T> Flow<Iterable<T>>.flatten() = flatMap { it } fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }

View File

@@ -2,18 +2,5 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
/**
* Filters out null values from this [Flow], returning only non-null elements.
*
* @param T The type of elements in the flow (nullable)
* @return A [Flow] containing only non-null elements
*/
fun <T> Flow<T>.takeNotNull() = mapNotNull { it } fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
/**
* Alias for [takeNotNull]. Filters out null values from this [Flow], returning only non-null elements.
*
* @param T The type of elements in the flow (nullable)
* @return A [Flow] containing only non-null elements
*/
fun <T> Flow<T>.filterNotNull() = takeNotNull() fun <T> Flow<T>.filterNotNull() = takeNotNull()

View File

@@ -1,6 +1,5 @@
package dev.inmo.micro_utils.coroutines package dev.inmo.micro_utils.coroutines
import dev.inmo.kslog.common.KSLog
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.* import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@@ -9,7 +8,6 @@ import kotlinx.coroutines.sync.withLock
private class SubscribeAsyncReceiver<T>( private class SubscribeAsyncReceiver<T>(
val scope: CoroutineScope, val scope: CoroutineScope,
val logger: KSLog,
output: suspend SubscribeAsyncReceiver<T>.(T) -> Unit output: suspend SubscribeAsyncReceiver<T>.(T) -> Unit
) { ) {
private val dataChannel: Channel<T> = Channel(Channel.UNLIMITED) private val dataChannel: Channel<T> = Channel(Channel.UNLIMITED)
@@ -17,7 +15,7 @@ private class SubscribeAsyncReceiver<T>(
get() = dataChannel get() = dataChannel
init { init {
scope.launchLoggingDropExceptions(logger = logger) { scope.launchLoggingDropExceptions {
for (data in dataChannel) { for (data in dataChannel) {
output(data) output(data)
} }
@@ -35,16 +33,13 @@ private data class AsyncSubscriptionCommandData<T, M>(
val scope: CoroutineScope, val scope: CoroutineScope,
val markerFactory: suspend (T) -> M, val markerFactory: suspend (T) -> M,
val block: suspend (T) -> Unit, val block: suspend (T) -> Unit,
val logger: KSLog,
val onEmpty: suspend (M) -> Unit val onEmpty: suspend (M) -> Unit
) : AsyncSubscriptionCommand<T, M> { ) : AsyncSubscriptionCommand<T, M> {
override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) { override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) {
val marker = markerFactory(data) val marker = markerFactory(data)
markersMap.getOrPut(marker) { markersMap.getOrPut(marker) {
SubscribeAsyncReceiver(scope.LinkedSupervisorScope(), logger) { SubscribeAsyncReceiver(scope.LinkedSupervisorScope()) {
runCatchingLogging(logger = logger) { safelyWithoutExceptions { block(it) }
block(it)
}
if (isEmpty()) { if (isEmpty()) {
onEmpty(marker) onEmpty(marker)
} }
@@ -65,24 +60,9 @@ private data class AsyncSubscriptionCommandClearReceiver<T, M>(
} }
} }
/**
* Subscribes to a [Flow] with asynchronous processing based on markers.
* Each value from the flow will be processed by the [block] function. Values with the same marker
* will be processed sequentially in the same coroutine scope, while values with different markers
* can be processed concurrently in separate coroutine scopes.
*
* @param T The type of values emitted by the flow
* @param M The type of markers used to group values
* @param scope The [CoroutineScope] in which to subscribe to the flow
* @param markerFactory A factory function that produces a marker for each emitted value
* @param logger The logger instance used for logging exceptions. Defaults to [KSLog]
* @param block The suspending function that processes each emitted value
* @return A [Job] representing the subscription that can be cancelled
*/
fun <T, M> Flow<T>.subscribeAsync( fun <T, M> Flow<T>.subscribeAsync(
scope: CoroutineScope, scope: CoroutineScope,
markerFactory: suspend (T) -> M, markerFactory: suspend (T) -> M,
logger: KSLog = KSLog,
block: suspend (T) -> Unit block: suspend (T) -> Unit
): Job { ): Job {
val subscope = scope.LinkedSupervisorScope() val subscope = scope.LinkedSupervisorScope()
@@ -91,14 +71,8 @@ fun <T, M> Flow<T>.subscribeAsync(
it.invoke(markersMap) it.invoke(markersMap)
} }
val job = subscribeLoggingDropExceptions(subscope, logger = logger) { data -> val job = subscribeLoggingDropExceptions(subscope) { data ->
val dataCommand = AsyncSubscriptionCommandData( val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker ->
data = data,
scope = subscope,
markerFactory = markerFactory,
block = block,
logger = logger
) { marker ->
actor.send( actor.send(
AsyncSubscriptionCommandClearReceiver(marker) AsyncSubscriptionCommandClearReceiver(marker)
) )
@@ -111,20 +85,17 @@ fun <T, M> Flow<T>.subscribeAsync(
return job return job
} }
@Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync"))
fun <T, M> Flow<T>.subscribeSafelyAsync( fun <T, M> Flow<T>.subscribeSafelyAsync(
scope: CoroutineScope, scope: CoroutineScope,
markerFactory: suspend (T) -> M, markerFactory: suspend (T) -> M,
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
logger: KSLog = KSLog,
block: suspend (T) -> Unit block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory, logger) { ) = subscribeAsync(scope, markerFactory) {
safely(onException) { safely(onException) {
block(it) block(it)
} }
} }
@Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync"))
fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync( fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
scope: CoroutineScope, scope: CoroutineScope,
markerFactory: suspend (T) -> M, markerFactory: suspend (T) -> M,
@@ -136,36 +107,11 @@ fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
} }
} }
/**
* Subscribes to a [Flow] with asynchronous processing based on markers, automatically logging and dropping exceptions.
* Each value from the flow will be processed by the [block] function. Values with the same marker
* will be processed sequentially, while values with different markers can be processed concurrently.
* Any exceptions thrown during processing will be logged and dropped without affecting other messages.
*
* @param T The type of values emitted by the flow
* @param M The type of markers used to group values
* @param scope The [CoroutineScope] in which to subscribe to the flow
* @param markerFactory A factory function that produces a marker for each emitted value
* @param logger The logger instance used for logging exceptions. Defaults to [KSLog]
* @param block The suspending function that processes each emitted value
* @return A [Job] representing the subscription that can be cancelled
*/
fun <T, M> Flow<T>.subscribeLoggingDropExceptionsAsync(
scope: CoroutineScope,
markerFactory: suspend (T) -> M,
logger: KSLog = KSLog,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory, logger) {
block(it)
}
@Deprecated("Renamed", ReplaceWith("subscribeLoggingDropExceptionsAsync(scope, markerFactory, logger, block = block)", "dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptionsAsync"))
fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync( fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
scope: CoroutineScope, scope: CoroutineScope,
markerFactory: suspend (T) -> M, markerFactory: suspend (T) -> M,
logger: KSLog = KSLog,
block: suspend (T) -> Unit block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory, logger) { ) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions({ /* do nothing */}) { safelyWithoutExceptions({ /* do nothing */}) {
block(it) block(it)
} }

View File

@@ -5,12 +5,4 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
/**
* Merges two flows into a single flow. Values from both flows will be emitted as they become available.
* This is a convenient operator syntax for [merge].
*
* @param T The type of elements in the flows
* @param other The flow to merge with this flow
* @return A [Flow] that emits values from both flows
*/
inline operator fun <T> Flow<T>.plus(other: Flow<T>) = merge(this, other) inline operator fun <T> Flow<T>.plus(other: Flow<T>) = merge(this, other)

View File

@@ -6,17 +6,6 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
/**
* Launches a new coroutine with automatic exception logging. If an exception occurs, it will be logged
* using the provided [logger] and then rethrown.
*
* @param errorMessageBuilder A function to build the error message from the caught exception. Defaults to "Something web wrong"
* @param logger The logger instance to use for logging exceptions. Defaults to [KSLog]
* @param context Additional [CoroutineContext] for the new coroutine. Defaults to [EmptyCoroutineContext]
* @param start The coroutine start option. Defaults to [CoroutineStart.DEFAULT]
* @param block The suspending function to execute in the new coroutine
* @return A [Job] representing the launched coroutine
*/
fun CoroutineScope.launchLogging( fun CoroutineScope.launchLogging(
errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" }, errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" },
logger: KSLog = KSLog, logger: KSLog = KSLog,
@@ -29,17 +18,6 @@ fun CoroutineScope.launchLogging(
}.getOrThrow() }.getOrThrow()
} }
/**
* Launches a new coroutine with automatic exception logging and dropping. If an exception occurs, it will be logged
* using the provided [logger] and then dropped (not rethrown), allowing the coroutine to complete normally.
*
* @param errorMessageBuilder A function to build the error message from the caught exception. Defaults to "Something web wrong"
* @param logger The logger instance to use for logging exceptions. Defaults to [KSLog]
* @param context Additional [CoroutineContext] for the new coroutine. Defaults to [EmptyCoroutineContext]
* @param start The coroutine start option. Defaults to [CoroutineStart.DEFAULT]
* @param block The suspending function to execute in the new coroutine
* @return A [Job] representing the launched coroutine
*/
fun CoroutineScope.launchLoggingDropExceptions( fun CoroutineScope.launchLoggingDropExceptions(
errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" }, errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" },
logger: KSLog = KSLog, logger: KSLog = KSLog,
@@ -52,18 +30,6 @@ fun CoroutineScope.launchLoggingDropExceptions(
} // just dropping exception } // just dropping exception
} }
/**
* Creates a new async coroutine with automatic exception logging. If an exception occurs, it will be logged
* using the provided [logger] and then rethrown when the [Deferred] is awaited.
*
* @param T The return type of the async computation
* @param errorMessageBuilder A function to build the error message from the caught exception. Defaults to "Something web wrong"
* @param logger The logger instance to use for logging exceptions. Defaults to [KSLog]
* @param context Additional [CoroutineContext] for the new coroutine. Defaults to [EmptyCoroutineContext]
* @param start The coroutine start option. Defaults to [CoroutineStart.DEFAULT]
* @param block The suspending function to execute that returns a value of type [T]
* @return A [Deferred] representing the async computation
*/
fun <T> CoroutineScope.asyncLogging( fun <T> CoroutineScope.asyncLogging(
errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" }, errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" },
logger: KSLog = KSLog, logger: KSLog = KSLog,
@@ -76,18 +42,6 @@ fun <T> CoroutineScope.asyncLogging(
}.getOrThrow() }.getOrThrow()
} }
/**
* Creates a new async coroutine with automatic exception logging and dropping. If an exception occurs, it will be logged
* using the provided [logger] and wrapped in a [Result], which can be checked when the [Deferred] is awaited.
*
* @param T The return type of the async computation
* @param errorMessageBuilder A function to build the error message from the caught exception. Defaults to "Something web wrong"
* @param logger The logger instance to use for logging exceptions. Defaults to [KSLog]
* @param context Additional [CoroutineContext] for the new coroutine. Defaults to [EmptyCoroutineContext]
* @param start The coroutine start option. Defaults to [CoroutineStart.DEFAULT]
* @param block The suspending function to execute that returns a value of type [T]
* @return A [Deferred] containing a [Result] representing the async computation
*/
fun <T> CoroutineScope.asyncLoggingDropExceptions( fun <T> CoroutineScope.asyncLoggingDropExceptions(
errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" }, errorMessageBuilder: CoroutineScope.(Throwable) -> Any = { "Something web wrong" },
logger: KSLog = KSLog, logger: KSLog = KSLog,

View File

@@ -65,6 +65,3 @@ open class MutableRedeliverStateFlow<T>(
override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector) override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector)
} }
@Deprecated("Renamed to MutableRedeliverStateFlow", ReplaceWith("MutableRedeliverStateFlow<T>"))
typealias SpecialMutableStateFlow<T> = MutableRedeliverStateFlow<T>

View File

@@ -1,13 +1,3 @@
package dev.inmo.micro_utils.coroutines package dev.inmo.micro_utils.coroutines
/**
* Replaces a failed [Result] with a new value computed from the exception.
* If this [Result] is successful, it is returned as-is. If it represents a failure,
* the [onException] handler is called with the exception to compute a replacement value,
* which is then wrapped in a new [Result].
*
* @param T The type of the successful value
* @param onException A function that computes a replacement value from the caught exception
* @return The original [Result] if successful, or a new [Result] containing the replacement value
*/
inline fun <T> Result<T>.replaceIfFailure(onException: (Throwable) -> T) = if (isSuccess) { this } else { runCatching { onException(exceptionOrNull()!!) } } inline fun <T> Result<T>.replaceIfFailure(onException: (Throwable) -> T) = if (isSuccess) { this } else { runCatching { onException(exceptionOrNull()!!) } }

View File

@@ -3,14 +3,6 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
/**
* Executes the given [block] and unlocks all provided [lockers] for writing if the block succeeds.
* If the block throws an exception, the lockers will remain locked.
*
* @param lockers Variable number of [SmartRWLocker] instances to unlock on successful execution
* @param block The suspending function to execute
* @return A [Result] containing [Unit] on success or the exception that occurred
*/
suspend inline fun alsoWithUnlockingOnSuccess( suspend inline fun alsoWithUnlockingOnSuccess(
vararg lockers: SmartRWLocker, vararg lockers: SmartRWLocker,
block: suspend () -> Unit block: suspend () -> Unit
@@ -22,15 +14,6 @@ suspend inline fun alsoWithUnlockingOnSuccess(
} }
} }
/**
* Asynchronously executes the given [block] and unlocks all provided [lockers] for writing if the block succeeds.
* This function launches a new coroutine in the given [scope] and automatically logs and drops any exceptions.
*
* @param scope The [CoroutineScope] in which to launch the coroutine
* @param lockers Variable number of [SmartRWLocker] instances to unlock on successful execution
* @param block The suspending function to execute
* @return A [Job] representing the launched coroutine
*/
fun alsoWithUnlockingOnSuccessAsync( fun alsoWithUnlockingOnSuccessAsync(
scope: CoroutineScope, scope: CoroutineScope,
vararg lockers: SmartRWLocker, vararg lockers: SmartRWLocker,

View File

@@ -3,49 +3,19 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/**
* Creates a [SupervisorJob] linked to this [CoroutineContext]'s job. The new supervisor job will be a child
* of the current job, and optionally combined with [additionalContext].
*
* @param additionalContext Optional additional context to combine with the supervisor job
* @return A [CoroutineContext] containing the new supervisor job
*/
fun CoroutineContext.LinkedSupervisorJob( fun CoroutineContext.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null additionalContext: CoroutineContext? = null
) = SupervisorJob(job).let { if (additionalContext != null) it + additionalContext else it } ) = SupervisorJob(job).let { if (additionalContext != null) it + additionalContext else it }
/**
* Creates a [SupervisorJob] linked to this [CoroutineScope]'s job. The new supervisor job will be a child
* of the current scope's job, and optionally combined with [additionalContext].
*
* @param additionalContext Optional additional context to combine with the supervisor job
* @return A [CoroutineContext] containing the new supervisor job
*/
fun CoroutineScope.LinkedSupervisorJob( fun CoroutineScope.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorJob(additionalContext) ) = coroutineContext.LinkedSupervisorJob(additionalContext)
/**
* Creates a new [CoroutineScope] with a [SupervisorJob] linked to this [CoroutineContext]'s job.
* The new scope's supervisor job will be a child of the current job, and optionally combined with [additionalContext].
*
* @param additionalContext Optional additional context to combine with the supervisor job
* @return A new [CoroutineScope] with a linked supervisor job
*/
fun CoroutineContext.LinkedSupervisorScope( fun CoroutineContext.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null additionalContext: CoroutineContext? = null
) = CoroutineScope( ) = CoroutineScope(
this + LinkedSupervisorJob(additionalContext) this + LinkedSupervisorJob(additionalContext)
) )
/**
* Creates a new [CoroutineScope] with a [SupervisorJob] linked to this [CoroutineScope]'s job.
* The new scope's supervisor job will be a child of the current scope's job, and optionally combined with [additionalContext].
*
* @param additionalContext Optional additional context to combine with the supervisor job
* @return A new [CoroutineScope] with a linked supervisor job
*/
fun CoroutineScope.LinkedSupervisorScope( fun CoroutineScope.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorScope(additionalContext) ) = coroutineContext.LinkedSupervisorScope(additionalContext)

View File

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

View File

@@ -2,22 +2,9 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.* import kotlinx.coroutines.*
/**
* Convenience property to access [Dispatchers.IO] for I/O-bound operations.
* This dispatcher is optimized for offloading blocking I/O tasks to a shared pool of threads.
*/
val IO val IO
get() = Dispatchers.IO get() = Dispatchers.IO
/**
* Executes the given [block] on the IO dispatcher and returns its result.
* This is a convenience function for executing I/O-bound operations like reading files,
* network requests, or database queries.
*
* @param T The return type of the block
* @param block The suspending function to execute on the IO dispatcher
* @return The result of executing the block
*/
suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = doIn( suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = doIn(
IO, IO,
block block

View File

@@ -3,16 +3,6 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
/**
* Launches a coroutine in the current thread using [Dispatchers.Unconfined] and blocks until it completes,
* returning its result. The coroutine will start execution in the current thread and will continue
* in the same thread until the first suspension point.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute in the current thread
* @return The result of the suspending block
* @throws Throwable if the coroutine throws an exception
*/
fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T { fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
val scope = CoroutineScope(Dispatchers.Unconfined) val scope = CoroutineScope(Dispatchers.Unconfined)
return scope.launchSynchronously(block) return scope.launchSynchronously(block)

View File

@@ -2,16 +2,6 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.* import kotlinx.coroutines.*
/**
* Launches a coroutine and blocks the current thread until the coroutine completes, returning its result.
* This is useful for bridging between suspending and non-suspending code in JVM environments.
* The coroutine is launched with [CoroutineStart.UNDISPATCHED] to start execution immediately.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute synchronously
* @return The result of the suspending block
* @throws Throwable if the coroutine throws an exception
*/
fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T { fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
var result: Result<T>? = null var result: Result<T>? = null
val objectToSynchronize = Object() val objectToSynchronize = Object()
@@ -32,31 +22,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
return result!!.getOrThrow() return result!!.getOrThrow()
} }
/**
* Launches a coroutine in a new [CoroutineScope] with [Dispatchers.Default] and blocks the current thread
* until the coroutine completes, returning its result.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute synchronously
* @return The result of the suspending block
* @throws Throwable if the coroutine throws an exception
*/
fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block) fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block)
/**
* Alias for [launchSynchronously]. Launches a coroutine and blocks the current thread until it completes.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute synchronously
* @return The result of the suspending block
*/
fun <T> CoroutineScope.doSynchronously(block: suspend CoroutineScope.() -> T): T = launchSynchronously(block) fun <T> CoroutineScope.doSynchronously(block: suspend CoroutineScope.() -> T): T = launchSynchronously(block)
/**
* Alias for [launchSynchronously]. Launches a coroutine in a new scope and blocks the current thread until it completes.
*
* @param T The return type of the suspending block
* @param block The suspending function to execute synchronously
* @return The result of the suspending block
*/
fun <T> doSynchronously(block: suspend CoroutineScope.() -> T): T = launchSynchronously(block) fun <T> doSynchronously(block: suspend CoroutineScope.() -> T): T = launchSynchronously(block)

View File

@@ -12,35 +12,12 @@ private val BASE64_INVERSE_ALPHABET = IntArray(256) {
internal fun Int.toBase64(): Char = BASE64_ALPHABET[this] internal fun Int.toBase64(): Char = BASE64_ALPHABET[this]
internal fun Byte.fromBase64(): Byte = BASE64_INVERSE_ALPHABET[toInt() and 0xff].toByte() and BASE64_MASK internal fun Byte.fromBase64(): Byte = BASE64_INVERSE_ALPHABET[toInt() and 0xff].toByte() and BASE64_MASK
/**
* Type alias representing a Base64-encoded string.
*/
typealias EncodedBase64String = String typealias EncodedBase64String = String
/**
* Type alias representing a Base64-encoded byte array.
*/
typealias EncodedByteArray = ByteArray typealias EncodedByteArray = ByteArray
/**
* Encodes this string to Base64 format, returning the result as a string.
*
* @return A Base64-encoded string
*/
fun SourceString.encodeBase64String(): EncodedBase64String = encodeToByteArray().encodeBase64String() fun SourceString.encodeBase64String(): EncodedBase64String = encodeToByteArray().encodeBase64String()
/**
* Encodes this string to Base64 format, returning the result as a byte array.
*
* @return A Base64-encoded byte array
*/
fun SourceString.encodeBase64(): EncodedByteArray = encodeToByteArray().encodeBase64() fun SourceString.encodeBase64(): EncodedByteArray = encodeToByteArray().encodeBase64()
/**
* Encodes this byte array to Base64 format, returning the result as a string.
*
* @return A Base64-encoded string with padding ('=') characters
*/
fun SourceBytes.encodeBase64String(): EncodedBase64String = buildString { fun SourceBytes.encodeBase64String(): EncodedBase64String = buildString {
var i = 0 var i = 0
while (this@encodeBase64String.size > i) { while (this@encodeBase64String.size > i) {
@@ -68,33 +45,11 @@ fun SourceBytes.encodeBase64String(): EncodedBase64String = buildString {
i += read i += read
} }
} }
/**
* Encodes this byte array to Base64 format, returning the result as a byte array.
*
* @return A Base64-encoded byte array
*/
fun SourceBytes.encodeBase64(): EncodedByteArray = encodeBase64String().encodeToByteArray() fun SourceBytes.encodeBase64(): EncodedByteArray = encodeBase64String().encodeToByteArray()
/**
* Decodes this Base64-encoded string back to the original byte array.
*
* @return The decoded byte array
*/
fun EncodedBase64String.decodeBase64(): SourceBytes = dropLastWhile { it == BASE64_PAD }.encodeToByteArray().decodeBase64() fun EncodedBase64String.decodeBase64(): SourceBytes = dropLastWhile { it == BASE64_PAD }.encodeToByteArray().decodeBase64()
/**
* Decodes this Base64-encoded string back to the original string.
*
* @return The decoded string
*/
fun EncodedBase64String.decodeBase64String(): SourceString = decodeBase64().decodeToString() fun EncodedBase64String.decodeBase64String(): SourceString = decodeBase64().decodeToString()
/**
* Decodes this Base64-encoded byte array back to the original byte array.
*
* @return The decoded byte array
*/
fun EncodedByteArray.decodeBase64(): SourceBytes { fun EncodedByteArray.decodeBase64(): SourceBytes {
val result = mutableListOf<Byte>() val result = mutableListOf<Byte>()
val data = ByteArray(4) val data = ByteArray(4)
@@ -119,10 +74,4 @@ fun EncodedByteArray.decodeBase64(): SourceBytes {
return result.toByteArray() return result.toByteArray()
} }
/**
* Decodes this Base64-encoded byte array back to the original string.
*
* @return The decoded string
*/
fun EncodedByteArray.decodeBase64String(): SourceString = decodeBase64().decodeToString() fun EncodedByteArray.decodeBase64String(): SourceString = decodeBase64().decodeToString()

View File

@@ -1,16 +1,7 @@
package dev.inmo.micro_utils.crypto package dev.inmo.micro_utils.crypto
/**
* Character array used for hexadecimal encoding (lowercase).
*/
val HEX_ARRAY = "0123456789abcdef".toCharArray() val HEX_ARRAY = "0123456789abcdef".toCharArray()
/**
* Converts this byte array to a hexadecimal string representation (lowercase).
* Each byte is represented as two hex characters.
*
* @return A lowercase hex string (e.g., "48656c6c6f" for "Hello")
*/
fun SourceBytes.hex(): String { fun SourceBytes.hex(): String {
val hexChars = CharArray(size * 2) val hexChars = CharArray(size * 2)
for (j in indices) { for (j in indices) {
@@ -21,9 +12,4 @@ fun SourceBytes.hex(): String {
return hexChars.concatToString() return hexChars.concatToString()
} }
/**
* Converts this string to a hexadecimal representation by first encoding it as UTF-8 bytes.
*
* @return A lowercase hex string representation of the UTF-8 encoded bytes
*/
fun SourceString.hex(): String = encodeToByteArray().hex() fun SourceString.hex(): String = encodeToByteArray().hex()

View File

@@ -2,21 +2,7 @@ package dev.inmo.micro_utils.crypto
import korlibs.crypto.md5 import korlibs.crypto.md5
/**
* Type alias representing an MD5 hash as a hex-encoded string (32 characters).
*/
typealias MD5 = String typealias MD5 = String
/**
* Computes the MD5 hash of this byte array and returns it as a lowercase hex string.
*
* @return The MD5 hash as a 32-character lowercase hex string
*/
fun SourceBytes.md5(): MD5 = md5().hexLower fun SourceBytes.md5(): MD5 = md5().hexLower
/**
* Computes the MD5 hash of this string (encoded as UTF-8 bytes) and returns it as a lowercase hex string.
*
* @return The MD5 hash as a 32-character lowercase hex string
*/
fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower

View File

@@ -26,7 +26,7 @@ kotlin {
project.parent.subprojects.forEach { project.parent.subprojects.forEach {
if ( if (
it.name != project.name it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") } && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
@@ -44,7 +44,7 @@ kotlin {
project.parent.subprojects.forEach { project.parent.subprojects.forEach {
if ( if (
it.name != project.name it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") } && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
@@ -60,7 +60,7 @@ kotlin {
project.parent.subprojects.forEach { project.parent.subprojects.forEach {
if ( if (
it.name != project.name it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") } && it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
@@ -76,7 +76,7 @@ kotlin {
project.parent.subprojects.forEach { project.parent.subprojects.forEach {
if ( if (
it.name != project.name it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") } && it.kotlin.sourceSets.any { it.name.contains("androidMain") }

View File

@@ -1,13 +1,5 @@
package dev.inmo.micro_utils.fsm.common package dev.inmo.micro_utils.fsm.common
/**
* Represents a state in a finite state machine (FSM).
* Each state must have an associated context that identifies it uniquely within its chain.
*/
interface State { interface State {
/**
* The context object that uniquely identifies this state within a state chain.
* States with the same context are considered to belong to the same chain.
*/
val context: Any val context: Any
} }

View File

@@ -1,26 +1,6 @@
package dev.inmo.micro_utils.fsm.common.utils package dev.inmo.micro_utils.fsm.common.utils
/**
* A handler function type for dealing with errors during state handling in a finite state machine.
* The handler receives the state that caused the error and the thrown exception, and can optionally
* return a new state to continue the chain, or null to end the chain.
*
* @param T The state type that caused the error
* @param Throwable The exception that was thrown during state handling
* @return A new state to continue with, or null to end the state chain
*/
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T? typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?
/**
* The default error handler that returns null for all errors, effectively ending the state chain.
*/
val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null } val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null }
/**
* Returns a typed version of the [DefaultStateHandlingErrorHandler].
*
* @param T The state type
* @return A [StateHandlingErrorHandler] for type [T]
*/
inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T> inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T>

View File

@@ -1,15 +1,9 @@
interface InjectedExecOps {
@Inject //@javax.inject.Inject
ExecOperations getExecOps()
}
private String getCurrentVersionChangelog() { private String getCurrentVersionChangelog() {
OutputStream changelogDataOS = new ByteArrayOutputStream() OutputStream changelogDataOS = new ByteArrayOutputStream()
exec {
def injected = project.objects.newInstance(InjectedExecOps)
injected.execOps.exec {
commandLine 'chmod', "+x", './changelog_parser.sh' commandLine 'chmod', "+x", './changelog_parser.sh'
} }
injected.execOps.exec { exec {
standardOutput = changelogDataOS standardOutput = changelogDataOS
commandLine './changelog_parser.sh', "${project.version}", 'CHANGELOG.md' commandLine './changelog_parser.sh', "${project.version}", 'CHANGELOG.md'
} }

View File

@@ -8,9 +8,6 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g
## https://github.com/google/ksp/issues/2491
#ksp.useKSP2=false
# JS NPM # JS NPM
crypto_js_version=4.1.1 crypto_js_version=4.1.1
@@ -18,5 +15,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.29.3 version=0.25.8.2
android_code_version=313 android_code_version=298

View File

@@ -1,47 +1,45 @@
[versions] [versions]
kt = "2.3.21" kt = "2.1.21"
kt-serialization = "1.11.0" kt-serialization = "1.8.1"
kt-coroutines = "1.11.0" kt-coroutines = "1.10.2"
kotlinx-browser = "0.5.0" kotlinx-browser = "0.3"
kslog = "1.6.1" kslog = "1.4.2"
jb-compose = "1.11.0" jb-compose = "1.8.1"
jb-compose-material3 = "1.11.0-alpha07" jb-exposed = "0.61.0"
jb-compose-icons = "1.7.8" jb-dokka = "2.0.0"
jb-exposed = "1.3.0"
jb-dokka = "2.2.0"
sqlite = "3.53.1.0" sqlite = "3.50.1.0"
korlibs = "5.4.0" korlibs = "5.4.0"
uuid = "0.8.4" uuid = "0.8.4"
ktor = "3.5.0" ktor = "3.1.3"
gh-release = "2.5.2" gh-release = "2.5.2"
koin = "4.2.1" koin = "4.0.4"
okio = "3.17.0" okio = "3.12.0"
ksp = "2.3.8" ksp = "2.1.20-1.0.31"
kotlin-poet = "2.3.0" kotlin-poet = "1.18.1"
versions = "0.54.0" versions = "0.51.0"
nmcp = "1.5.0" nmcp = "0.1.5"
android-gradle = "8.12.+" android-gradle = "8.9.+"
dexcount = "4.0.0" dexcount = "4.0.0"
android-coreKtx = "1.18.0" android-coreKtx = "1.16.0"
android-recyclerView = "1.4.0" android-recyclerView = "1.4.0"
android-appCompat = "1.7.1" android-appCompat = "1.7.1"
android-fragment = "1.8.9" android-fragment = "1.8.8"
android-espresso = "3.7.0" android-espresso = "3.6.1"
android-test = "1.3.0" android-test = "1.2.1"
android-props-minSdk = "21" android-props-minSdk = "21"
android-props-compileSdk = "36" android-props-compileSdk = "36"
@@ -90,8 +88,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
jb-exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "jb-exposed" } jb-exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "jb-exposed" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" } sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
jb-compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "jb-compose-material3" } jb-compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "jb-compose" }
jb-compose-icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "jb-compose-icons" }
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }

View File

@@ -1,9 +1,6 @@
kotlin { kotlin {
androidTarget { androidTarget {
publishLibraryVariants( publishAllLibraryVariants()
"release",
"debug",
)
compilations.all { compilations.all {
kotlinOptions { kotlinOptions {
jvmTarget = "17" jvmTarget = "17"

View File

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

View File

@@ -10,7 +10,6 @@ repositories {
dependencies { dependencies {
api project(":micro_utils.koin") api project(":micro_utils.koin")
api project(":micro_utils.ksp.generator")
api libs.kotlin.poet api libs.kotlin.poet
api libs.ksp api libs.ksp
} }

View File

@@ -26,8 +26,6 @@ import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo import com.squareup.kotlinpoet.ksp.writeTo
import dev.inmo.micro_ksp.generator.safeClassName
import dev.inmo.micro_ksp.generator.withNoSuchElementWorkaround
import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin import org.koin.core.Koin
@@ -239,16 +237,19 @@ class Processor(
""".trimIndent() """.trimIndent()
) )
ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach { ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach {
val type = safeClassName { it.type } val type = runCatching {
val targetType = runCatching { it.type.asTypeName()
type.parameterizedBy( }.getOrElse { e ->
*withNoSuchElementWorkaround(emptyArray()) { if (e is KSTypeNotPresentException) {
it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type e.ksType.toClassName()
} else {
throw e
} }
) }
val targetType = runCatching {
type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type))
}.getOrElse { e -> }.getOrElse { e ->
when (e) { when (e) {
is IllegalArgumentException if (e.message ?.contains("no type argument") == true) -> return@getOrElse type
is KSTypeNotPresentException -> e.ksType.toClassName() is KSTypeNotPresentException -> e.ksType.toClassName()
} }
if (e is KSTypesNotPresentException) { if (e is KSTypesNotPresentException) {
@@ -257,32 +258,14 @@ class Processor(
throw e throw e
} }
}.copy( }.copy(
nullable = withNoSuchElementWorkaround(true) { it.nullable } nullable = it.nullable
) )
addCodeForType( addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory)
targetType,
it.name,
withNoSuchElementWorkaround(true) {
it.nullable
},
withNoSuchElementWorkaround(true) {
it.generateSingle
},
withNoSuchElementWorkaround(true) {
it.generateFactory
}
)
} }
ksFile.getAnnotationsByType(GenerateGenericKoinDefinition::class).forEach { ksFile.getAnnotationsByType(GenerateGenericKoinDefinition::class).forEach {
val targetType = TypeVariableName("T", Any::class) val targetType = TypeVariableName("T", Any::class)
addCodeForType( addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory)
targetType = targetType,
name = it.name,
nullable = withNoSuchElementWorkaround(true) { it.nullable },
generateSingle = withNoSuchElementWorkaround(true) { it.generateSingle },
generateFactory = withNoSuchElementWorkaround(true) { it.generateFactory }
)
} }
}.build().let { }.build().let {
File( File(

View File

@@ -5,6 +5,7 @@ package dev.inmo.micro_utils.koin.generator.test
import kotlin.Any import kotlin.Any
import kotlin.Boolean import kotlin.Boolean
import kotlin.Deprecated
import kotlin.String import kotlin.String
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.definition.Definition import org.koin.core.definition.Definition
@@ -29,59 +30,95 @@ public val Koin.sampleInfo: Test<String>
/** /**
* @return Definition by key "sampleInfo" with [parameters] * @return Definition by key "sampleInfo" with [parameters]
*/ */
public inline fun Scope.sampleInfo(noinline parameters: ParametersDefinition): Test<String> = get(named("sampleInfo"), parameters) public inline fun Scope.sampleInfo(noinline parameters: ParametersDefinition): Test<String> =
get(named("sampleInfo"), parameters)
/** /**
* @return Definition by key "sampleInfo" with [parameters] * @return Definition by key "sampleInfo" with [parameters]
*/ */
public inline fun Koin.sampleInfo(noinline parameters: ParametersDefinition): Test<String> = get(named("sampleInfo"), parameters) public inline fun Koin.sampleInfo(noinline parameters: ParametersDefinition): Test<String> =
get(named("sampleInfo"), parameters)
/** /**
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo" * Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
*/ */
public fun Module.singleSampleInfo(createdAtStart: Boolean = false, definition: Definition<Test<String>>): KoinDefinition<Test<String>> = single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) @Deprecated(
"This definition is old style and should not be used anymore. Use singleSampleInfo instead",
ReplaceWith("singleSampleInfo"),
)
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
*/
public fun Module.singleSampleInfo(createdAtStart: Boolean = false,
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
/** /**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo" * Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
*/ */
public fun Module.factorySampleInfo(definition: Definition<Test<String>>): KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition) @Deprecated(
"This definition is old style and should not be used anymore. Use factorySampleInfo instead",
ReplaceWith("factorySampleInfo"),
)
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
*/
public fun Module.factorySampleInfo(definition: Definition<Test<String>>):
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
/** /**
* @return Definition by key "test" with [parameters] * @return Definition by key "test" with [parameters]
*/ */
public inline fun <reified T : Any> Scope.test(noinline parameters: ParametersDefinition? = null): T = get(named("test"), parameters) public inline fun <reified T : Any> Scope.test(noinline parameters: ParametersDefinition? = null): T
= get(named("test"), parameters)
/** /**
* @return Definition by key "test" with [parameters] * @return Definition by key "test" with [parameters]
*/ */
public inline fun <reified T : Any> Koin.test(noinline parameters: ParametersDefinition? = null): T = get(named("test"), parameters) public inline fun <reified T : Any> Koin.test(noinline parameters: ParametersDefinition? = null): T
= get(named("test"), parameters)
/** /**
* Will register [definition] with [org.koin.core.module.Module.single] and key "test" * Will register [definition] with [org.koin.core.module.Module.single] and key "test"
*/ */
public inline fun <reified T : Any> Module.singleTest(createdAtStart: Boolean = false, noinline definition: Definition<T>): KoinDefinition<T> = single(named("test"), createdAtStart = createdAtStart, definition = definition) public inline fun <reified T : Any> Module.singleTest(createdAtStart: Boolean = false, noinline
definition: Definition<T>): KoinDefinition<T> = single(named("test"), createdAtStart =
createdAtStart, definition = definition)
/** /**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "test" * Will register [definition] with [org.koin.core.module.Module.factory] and key "test"
*/ */
public inline fun <reified T : Any> Module.factoryTest(noinline definition: Definition<T>): KoinDefinition<T> = factory(named("test"), definition = definition) public inline fun <reified T : Any> Module.factoryTest(noinline definition: Definition<T>):
KoinDefinition<T> = factory(named("test"), definition = definition)
/** /**
* @return Definition by key "testNullable" with [parameters] * @return Definition by key "testNullable" with [parameters]
*/ */
public inline fun <reified T : Any> Scope.testNullable(noinline parameters: ParametersDefinition? = null): T? = getOrNull(named("testNullable"), parameters) public inline fun <reified T : Any> Scope.testNullable(noinline parameters: ParametersDefinition? =
null): T? = getOrNull(named("testNullable"), parameters)
/** /**
* @return Definition by key "testNullable" with [parameters] * @return Definition by key "testNullable" with [parameters]
*/ */
public inline fun <reified T : Any> Koin.testNullable(noinline parameters: ParametersDefinition? = null): T? = getOrNull(named("testNullable"), parameters) public inline fun <reified T : Any> Koin.testNullable(noinline parameters: ParametersDefinition? =
null): T? = getOrNull(named("testNullable"), parameters)
/** /**
* Will register [definition] with [org.koin.core.module.Module.single] and key "testNullable" * Will register [definition] with [org.koin.core.module.Module.single] and key "testNullable"
*/ */
public inline fun <reified T : Any> Module.singleTestNullable(createdAtStart: Boolean = false, noinline definition: Definition<T>): KoinDefinition<T> = single(named("testNullable"), createdAtStart = createdAtStart, definition = definition) public inline fun <reified T : Any> Module.singleTestNullable(createdAtStart: Boolean = false,
noinline definition: Definition<T>): KoinDefinition<T> = single(named("testNullable"),
createdAtStart = createdAtStart, definition = definition)
/** /**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "testNullable" * Will register [definition] with [org.koin.core.module.Module.factory] and key "testNullable"
*/ */
public inline fun <reified T : Any> Module.factoryTestNullable(noinline definition: Definition<T>): KoinDefinition<T> = factory(named("testNullable"), definition = definition) public inline fun <reified T : Any> Module.factoryTestNullable(noinline definition: Definition<T>):
KoinDefinition<T> = factory(named("testNullable"), definition = definition)

View File

@@ -10,7 +10,6 @@ import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.* import com.google.devtools.ksp.symbol.*
import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.toClassName
import dev.inmo.micro_ksp.generator.withNoSuchElementWorkaround
import dev.inmo.micro_ksp.generator.writeFile import dev.inmo.micro_ksp.generator.writeFile
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded import dev.inmo.micro_utils.ksp.classcasts.ClassCastsExcluded
import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded import dev.inmo.micro_utils.ksp.classcasts.ClassCastsIncluded
@@ -26,11 +25,7 @@ class Processor(
) { ) {
val rootAnnotation = ksClassDeclaration.getAnnotationsByType(ClassCastsIncluded::class).first() val rootAnnotation = ksClassDeclaration.getAnnotationsByType(ClassCastsIncluded::class).first()
val (includeRegex: Regex?, excludeRegex: Regex?) = rootAnnotation.let { val (includeRegex: Regex?, excludeRegex: Regex?) = rootAnnotation.let {
withNoSuchElementWorkaround("") { it.typesRegex.takeIf { it.isNotEmpty() } ?.let(::Regex) to it.excludeRegex.takeIf { it.isNotEmpty() } ?.let(::Regex)
it.typesRegex
}.takeIf { it.isNotEmpty() } ?.let(::Regex) to withNoSuchElementWorkaround("") {
it.excludeRegex
}.takeIf { it.isNotEmpty() } ?.let(::Regex)
} }
val classesSubtypes = mutableMapOf<KSClassDeclaration, MutableSet<KSClassDeclaration>>() val classesSubtypes = mutableMapOf<KSClassDeclaration, MutableSet<KSClassDeclaration>>()
@@ -54,9 +49,7 @@ class Processor(
when { when {
potentialSubtype === ksClassDeclaration -> {} potentialSubtype === ksClassDeclaration -> {}
potentialSubtype.isAnnotationPresent(ClassCastsExcluded::class) -> return@forEach potentialSubtype.isAnnotationPresent(ClassCastsExcluded::class) -> return@forEach
potentialSubtype !is KSClassDeclaration || !potentialSubtype.checkSupertypeLevel( potentialSubtype !is KSClassDeclaration || !potentialSubtype.checkSupertypeLevel(rootAnnotation.levelsToInclude.takeIf { it >= 0 }) -> return@forEach
withNoSuchElementWorkaround(-1) { rootAnnotation.levelsToInclude }.takeIf { it >= 0 }
) -> return@forEach
excludeRegex ?.matches(simpleName) == true -> return@forEach excludeRegex ?.matches(simpleName) == true -> return@forEach
includeRegex ?.matches(simpleName) == false -> {} includeRegex ?.matches(simpleName) == false -> {}
else -> classesSubtypes.getOrPut(ksClassDeclaration) { mutableSetOf() }.add(potentialSubtype) else -> classesSubtypes.getOrPut(ksClassDeclaration) { mutableSetOf() }.add(potentialSubtype)
@@ -103,9 +96,7 @@ class Processor(
@OptIn(KspExperimental::class) @OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
(resolver.getSymbolsWithAnnotation(ClassCastsIncluded::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach { (resolver.getSymbolsWithAnnotation(ClassCastsIncluded::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
val prefix = withNoSuchElementWorkaround("") { val prefix = it.getAnnotationsByType(ClassCastsIncluded::class).first().outputFilePrefix
it.getAnnotationsByType(ClassCastsIncluded::class).first().outputFilePrefix
}
it.writeFile(prefix = prefix, suffix = "ClassCasts") { it.writeFile(prefix = prefix, suffix = "ClassCasts") {
FileSpec.builder( FileSpec.builder(
it.packageName.asString(), it.packageName.asString(),

View File

@@ -1,34 +0,0 @@
package dev.inmo.micro_ksp.generator
import com.google.devtools.ksp.KSTypeNotPresentException
import com.google.devtools.ksp.KspExperimental
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.asTypeName
import kotlin.reflect.KClass
/**
* Safely retrieves a [ClassName] from a [KClass] getter, handling cases where the type is not
* yet available during annotation processing (KSP).
*
* When [KSTypeNotPresentException] is caught, it extracts the class name information from the
* exception's [com.google.devtools.ksp.symbol.KSType] to construct a [ClassName].
*
* @param classnameGetter A lambda that returns the [KClass] to convert
* @return A KotlinPoet [ClassName] representing the class
* @throws Throwable If an exception other than [KSTypeNotPresentException] occurs
*/
@Suppress("NOTHING_TO_INLINE")
@OptIn(KspExperimental::class)
inline fun safeClassName(classnameGetter: () -> KClass<*>) = runCatching {
classnameGetter().asTypeName()
}.getOrElse { e ->
if (e is KSTypeNotPresentException) {
ClassName(
e.ksType.declaration.packageName.asString(),
e.ksType.declaration.qualifiedName ?.asString() ?.replaceFirst(e.ksType.declaration.packageName.asString(), "")
?: e.ksType.declaration.simpleName.asString()
)
} else {
throw e
}
}

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_ksp.generator
inline fun <T> withNoSuchElementWorkaround(
default: T,
block: () -> T
): T = runCatching(block).getOrElse {
if (it is NoSuchElementException) {
default
} else {
throw it
}
}

View File

@@ -2,13 +2,6 @@ package dev.inmo.micro_ksp.generator
import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSClassDeclaration
/**
* Recursively resolves all subclasses of this sealed class declaration.
* For sealed classes, it traverses the entire hierarchy of sealed subclasses.
* For non-sealed classes (leaf nodes), it returns the class itself.
*
* @return A list of all concrete (non-sealed) subclass declarations in the hierarchy
*/
fun KSClassDeclaration.resolveSubclasses(): List<KSClassDeclaration> { fun KSClassDeclaration.resolveSubclasses(): List<KSClassDeclaration> {
return (getSealedSubclasses().flatMap { return (getSealedSubclasses().flatMap {
it.resolveSubclasses() it.resolveSubclasses()

View File

@@ -12,7 +12,6 @@ import com.squareup.kotlinpoet.ksp.toClassName
import dev.inmo.micro_ksp.generator.buildSubFileName import dev.inmo.micro_ksp.generator.buildSubFileName
import dev.inmo.micro_ksp.generator.companion import dev.inmo.micro_ksp.generator.companion
import dev.inmo.micro_ksp.generator.findSubClasses import dev.inmo.micro_ksp.generator.findSubClasses
import dev.inmo.micro_ksp.generator.withNoSuchElementWorkaround
import dev.inmo.micro_ksp.generator.writeFile import dev.inmo.micro_ksp.generator.writeFile
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedTypesWorkaround import dev.inmo.micro_utils.ksp.sealed.GenerateSealedTypesWorkaround
import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround
@@ -114,7 +113,7 @@ class Processor(
val annotation = ksClassDeclaration.getGenerateSealedTypesWorkaroundAnnotation val annotation = ksClassDeclaration.getGenerateSealedTypesWorkaroundAnnotation
val subClasses = ksClassDeclaration.resolveSubclasses( val subClasses = ksClassDeclaration.resolveSubclasses(
searchIn = resolver.getAllFiles(), searchIn = resolver.getAllFiles(),
allowNonSealed = withNoSuchElementWorkaround(null) { annotation ?.includeNonSealedSubTypes } ?: false allowNonSealed = annotation ?.includeNonSealedSubTypes ?: false
).distinct() ).distinct()
val subClassesNames = subClasses.filter { val subClassesNames = subClasses.filter {
it.getAnnotationsByType(GenerateSealedTypesWorkaround.Exclude::class).count() == 0 it.getAnnotationsByType(GenerateSealedTypesWorkaround.Exclude::class).count() == 0
@@ -165,15 +164,7 @@ class Processor(
@OptIn(KspExperimental::class) @OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
(resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach { (resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
val prefix = runCatching { val prefix = (it.getGenerateSealedWorkaroundAnnotation) ?.prefix ?.takeIf {
(it.getGenerateSealedWorkaroundAnnotation) ?.prefix
}.getOrElse {
if (it is NoSuchElementException) {
""
} else {
throw it
}
} ?.takeIf {
it.isNotEmpty() it.isNotEmpty()
} ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "") } ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "")
it.writeFile(prefix = prefix, suffix = "SealedWorkaround") { it.writeFile(prefix = prefix, suffix = "SealedWorkaround") {
@@ -193,9 +184,7 @@ class Processor(
} }
} }
(resolver.getSymbolsWithAnnotation(GenerateSealedTypesWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach { (resolver.getSymbolsWithAnnotation(GenerateSealedTypesWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
val prefix = withNoSuchElementWorkaround("") { val prefix = (it.getGenerateSealedTypesWorkaroundAnnotation) ?.prefix ?.takeIf {
(it.getGenerateSealedTypesWorkaroundAnnotation)?.prefix
} ?.takeIf {
it.isNotEmpty() it.isNotEmpty()
} ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "") } ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "")
it.writeFile(prefix = prefix, suffix = "SealedTypesWorkaround") { it.writeFile(prefix = prefix, suffix = "SealedTypesWorkaround") {

View File

@@ -16,7 +16,6 @@ import com.squareup.kotlinpoet.ksp.toTypeName
import dev.inmo.micro_ksp.generator.convertToClassName import dev.inmo.micro_ksp.generator.convertToClassName
import dev.inmo.micro_ksp.generator.convertToClassNames import dev.inmo.micro_ksp.generator.convertToClassNames
import dev.inmo.micro_ksp.generator.findSubClasses import dev.inmo.micro_ksp.generator.findSubClasses
import dev.inmo.micro_ksp.generator.withNoSuchElementWorkaround
import dev.inmo.micro_ksp.generator.writeFile import dev.inmo.micro_ksp.generator.writeFile
import dev.inmo.micro_utils.ksp.variations.GenerateVariations import dev.inmo.micro_utils.ksp.variations.GenerateVariations
import dev.inmo.micro_utils.ksp.variations.GenerationVariant import dev.inmo.micro_utils.ksp.variations.GenerationVariant
@@ -219,9 +218,7 @@ class Processor(
@OptIn(KspExperimental::class) @OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
(resolver.getSymbolsWithAnnotation(GenerateVariations::class.qualifiedName!!)).filterIsInstance<KSFunctionDeclaration>().forEach { (resolver.getSymbolsWithAnnotation(GenerateVariations::class.qualifiedName!!)).filterIsInstance<KSFunctionDeclaration>().forEach {
val prefix = withNoSuchElementWorkaround("") { val prefix = (it.getAnnotationsByType(GenerateVariations::class)).firstOrNull() ?.prefix ?.takeIf {
(it.getAnnotationsByType(GenerateVariations::class)).firstOrNull() ?.prefix
} ?.takeIf {
it.isNotEmpty() it.isNotEmpty()
} ?: it.simpleName.asString().replaceFirst(it.simpleName.asString(), "") } ?: it.simpleName.asString().replaceFirst(it.simpleName.asString(), "")
it.writeFile(prefix = prefix, suffix = "GeneratedVariation") { it.writeFile(prefix = prefix, suffix = "GeneratedVariation") {

View File

@@ -4,25 +4,10 @@ import io.ktor.client.call.body
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
/**
* Returns the response body as type [T] if the [statusFilter] condition is met, otherwise returns null.
* By default, the filter checks if the status code is [HttpStatusCode.OK].
*
* @param T The type to deserialize the response body to
* @param statusFilter A predicate to determine if the body should be retrieved. Defaults to checking for OK status
* @return The deserialized body of type [T], or null if the filter condition is not met
*/
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull( suspend inline fun <reified T : Any> HttpResponse.bodyOrNull(
statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK } statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK }
) = takeIf(statusFilter) ?.body<T>() ) = takeIf(statusFilter) ?.body<T>()
/**
* Returns the response body as type [T] if the status code is not [HttpStatusCode.NoContent], otherwise returns null.
* This is useful for handling responses that may return 204 No Content.
*
* @param T The type to deserialize the response body to
* @return The deserialized body of type [T], or null if the status is No Content
*/
suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> { suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
it.status != HttpStatusCode.NoContent it.status != HttpStatusCode.NoContent
} }

View File

@@ -4,13 +4,6 @@ import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.isSuccess import io.ktor.http.isSuccess
/**
* Throws a [ClientRequestException] if this [HttpResponse] does not have a successful status code.
* A status code is considered successful if it's in the 2xx range.
*
* @param unsuccessMessage A lambda that provides the error message to use if the response is unsuccessful
* @throws ClientRequestException if the response status is not successful
*/
inline fun HttpResponse.throwOnUnsuccess( inline fun HttpResponse.throwOnUnsuccess(
unsuccessMessage: () -> String unsuccessMessage: () -> String
) { ) {

View File

@@ -5,12 +5,6 @@ import dev.inmo.micro_utils.common.filesize
import dev.inmo.micro_utils.ktor.common.input import dev.inmo.micro_utils.ktor.common.input
import io.ktor.client.request.forms.InputProvider import io.ktor.client.request.forms.InputProvider
/**
* Creates a Ktor [InputProvider] from this multiplatform file for use in HTTP client requests.
* The input provider knows the file size and can create input streams on demand.
*
* @return An [InputProvider] for reading this file in HTTP requests
*/
fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) { fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) {
input() input()
} }

View File

@@ -1,9 +1,3 @@
package dev.inmo.micro_utils.ktor.client package dev.inmo.micro_utils.ktor.client
/**
* A callback function type for tracking upload progress.
*
* @param uploaded The number of bytes uploaded so far
* @param count The total number of bytes to be uploaded
*/
typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit

View File

@@ -5,15 +5,6 @@ import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.content.* import io.ktor.client.content.*
/**
* Uploads a file to a temporary storage on the server.
* The server should provide an endpoint that accepts multipart uploads and returns a [TemporalFileId].
*
* @param fullTempUploadDraftPath The full URL path to the temporary upload endpoint
* @param file The file to upload
* @param onUpload Progress callback invoked during upload
* @return A [TemporalFileId] that can be used to reference the uploaded file
*/
expect suspend fun HttpClient.tempUpload( expect suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String, fullTempUploadDraftPath: String,
file: MPPFile, file: MPPFile,

View File

@@ -10,14 +10,6 @@ import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
/**
* Information about a file to upload in a multipart request.
* This allows uploading from custom sources beyond regular files.
*
* @param fileName The name of the file
* @param mimeType The MIME type of the file
* @param inputAllocator A lambda that provides input streams for reading the file data
*/
data class UniUploadFileInfo( data class UniUploadFileInfo(
val fileName: FileName, val fileName: FileName,
val mimeType: String, val mimeType: String,

View File

@@ -1,14 +1,5 @@
package dev.inmo.micro_utils.ktor.common package dev.inmo.micro_utils.ktor.common
/**
* Builds a standard URL by combining a base part, subpart, and optional query parameters.
* The base and subpart are joined with a '/', and query parameters are appended.
*
* @param basePart The base part of the URL (e.g., "https://example.com/api")
* @param subpart The subpart of the URL (e.g., "users")
* @param parameters Query parameters as a map. Defaults to an empty map
* @return The complete URL string with query parameters
*/
fun buildStandardUrl( fun buildStandardUrl(
basePart: String, basePart: String,
subpart: String, subpart: String,
@@ -17,15 +8,6 @@ fun buildStandardUrl(
parameters parameters
) )
/**
* Builds a standard URL by combining a base part, subpart, and query parameters as a list.
* The base and subpart are joined with a '/', and query parameters are appended.
*
* @param basePart The base part of the URL (e.g., "https://example.com/api")
* @param subpart The subpart of the URL (e.g., "users")
* @param parameters Query parameters as a list of key-value pairs
* @return The complete URL string with query parameters
*/
fun buildStandardUrl( fun buildStandardUrl(
basePart: String, basePart: String,
subpart: String, subpart: String,
@@ -34,15 +16,6 @@ fun buildStandardUrl(
parameters parameters
) )
/**
* Builds a standard URL by combining a base part, subpart, and vararg query parameters.
* The base and subpart are joined with a '/', and query parameters are appended.
*
* @param basePart The base part of the URL (e.g., "https://example.com/api")
* @param subpart The subpart of the URL (e.g., "users")
* @param parameters Query parameters as vararg key-value pairs
* @return The complete URL string with query parameters
*/
fun buildStandardUrl( fun buildStandardUrl(
basePart: String, basePart: String,
subpart: String, subpart: String,

View File

@@ -1,8 +1,3 @@
package dev.inmo.micro_utils.ktor.common package dev.inmo.micro_utils.ktor.common
/**
* An exception used to indicate a correct/normal closure of a connection or stream.
* This is typically used in WebSocket or network communication scenarios where a
* clean shutdown needs to be distinguished from error conditions.
*/
object CorrectCloseException : Exception() object CorrectCloseException : Exception()

View File

@@ -2,14 +2,6 @@ package dev.inmo.micro_utils.ktor.common
private val schemaRegex = Regex("^[^:]*://") private val schemaRegex = Regex("^[^:]*://")
/**
* Converts this string to a correct WebSocket URL by ensuring it starts with "ws://" scheme.
* If the URL already starts with "ws", it is returned unchanged.
* If the URL contains a scheme (e.g., "http://"), it is replaced with "ws://".
* If the URL has no scheme, "ws://" is prepended.
*
* @return A properly formatted WebSocket URL
*/
val String.asCorrectWebSocketUrl: String val String.asCorrectWebSocketUrl: String
get() = if (startsWith("ws")) { get() = if (startsWith("ws")) {
this this

View File

@@ -2,30 +2,14 @@ package dev.inmo.micro_utils.ktor.common
import korlibs.time.DateTime import korlibs.time.DateTime
/**
* Type alias representing a date-time range with optional start and end times.
* First element is the "from" date-time, second is the "to" date-time.
*/
typealias FromToDateTime = Pair<DateTime?, DateTime?> typealias FromToDateTime = Pair<DateTime?, DateTime?>
/**
* Converts this [FromToDateTime] range to URL query parameters.
* Creates "from" and "to" query parameters with Unix millisecond timestamps.
*
* @return A map of query parameters representing the date-time range
*/
val FromToDateTime.asFromToUrlPart: QueryParams val FromToDateTime.asFromToUrlPart: QueryParams
get() = mapOf( get() = mapOf(
"from" to first ?.unixMillis ?.toString(), "from" to first ?.unixMillis ?.toString(),
"to" to second ?.unixMillis ?.toString() "to" to second ?.unixMillis ?.toString()
) )
/**
* Extracts a [FromToDateTime] range from URL query parameters.
* Looks for "from" and "to" parameters containing Unix millisecond timestamps.
*
* @return A [FromToDateTime] pair extracted from the query parameters
*/
val QueryParams.extractFromToDateTime: FromToDateTime val QueryParams.extractFromToDateTime: FromToDateTime
get() = FromToDateTime( get() = FromToDateTime(
get("from") ?.toDoubleOrNull() ?.let { DateTime(it) }, get("from") ?.toDoubleOrNull() ?.let { DateTime(it) },

View File

@@ -2,8 +2,4 @@ package dev.inmo.micro_utils.ktor.common
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
/**
* A function type that provides an [Input] instance.
* This is useful for lazy or deferred input creation in Ktor operations.
*/
typealias LambdaInputProvider = () -> Input typealias LambdaInputProvider = () -> Input

View File

@@ -3,10 +3,4 @@ package dev.inmo.micro_utils.ktor.common
import dev.inmo.micro_utils.common.MPPFile import dev.inmo.micro_utils.common.MPPFile
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
/**
* Creates a Ktor [Input] from this multiplatform file.
* Platform-specific implementations handle file reading for each supported platform.
*
* @return An [Input] stream for reading this file
*/
expect fun MPPFile.input(): Input expect fun MPPFile.input(): Input

View File

@@ -29,13 +29,6 @@ fun String.includeQueryParams(
queryParams: List<QueryParam> queryParams: List<QueryParam>
): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}" ): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}"
/**
* Parses this URL query string into a [QueryParams] map.
* Splits on '&' to separate parameters and '=' to separate keys from values.
* Parameters without values will have null as their value.
*
* @return A map of query parameter keys to their values (or null if no value)
*/
val String.parseUrlQuery: QueryParams val String.parseUrlQuery: QueryParams
get() = split("&").map { get() = split("&").map {
it.split("=").let { pair -> it.split("=").let { pair ->

View File

@@ -5,73 +5,27 @@ package dev.inmo.micro_utils.ktor.common
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.cbor.Cbor
/**
* Type alias for the standard serialization format used in Ktor utilities, which is [BinaryFormat].
*/
typealias StandardKtorSerialFormat = BinaryFormat typealias StandardKtorSerialFormat = BinaryFormat
/**
* Type alias for the standard serialization input data type, which is [ByteArray].
*/
typealias StandardKtorSerialInputData = ByteArray typealias StandardKtorSerialInputData = ByteArray
/**
* The standard Ktor serialization format instance, configured as CBOR.
*/
val standardKtorSerialFormat: StandardKtorSerialFormat = Cbor { } val standardKtorSerialFormat: StandardKtorSerialFormat = Cbor { }
/**
* Decodes data from [StandardKtorSerialInputData] using the standard format.
*
* @param T The type to decode to
* @param deserializationStrategy The deserialization strategy for type [T]
* @param input The byte array input data to decode
* @return The decoded value of type [T]
*/
inline fun <T> StandardKtorSerialFormat.decodeDefault( inline fun <T> StandardKtorSerialFormat.decodeDefault(
deserializationStrategy: DeserializationStrategy<T>, deserializationStrategy: DeserializationStrategy<T>,
input: StandardKtorSerialInputData input: StandardKtorSerialInputData
): T = decodeFromByteArray(deserializationStrategy, input) ): T = decodeFromByteArray(deserializationStrategy, input)
/**
* Encodes data to [StandardKtorSerialInputData] using the standard format.
*
* @param T The type to encode
* @param serializationStrategy The serialization strategy for type [T]
* @param data The data to encode
* @return The encoded byte array
*/
inline fun <T> StandardKtorSerialFormat.encodeDefault( inline fun <T> StandardKtorSerialFormat.encodeDefault(
serializationStrategy: SerializationStrategy<T>, serializationStrategy: SerializationStrategy<T>,
data: T data: T
): StandardKtorSerialInputData = encodeToByteArray(serializationStrategy, data) ): StandardKtorSerialInputData = encodeToByteArray(serializationStrategy, data)
/**
* A CBOR instance for serialization operations.
*/
val cbor = Cbor {} val cbor = Cbor {}
/**
* Decodes data from a hex string using the standard binary format.
*
* @param T The type to decode to
* @param deserializationStrategy The deserialization strategy for type [T]
* @param input The hex string to decode
* @return The decoded value of type [T]
*/
inline fun <T> StandardKtorSerialFormat.decodeHex( inline fun <T> StandardKtorSerialFormat.decodeHex(
deserializationStrategy: DeserializationStrategy<T>, deserializationStrategy: DeserializationStrategy<T>,
input: String input: String
): T = decodeFromHexString(deserializationStrategy, input) ): T = decodeFromHexString(deserializationStrategy, input)
/**
* Encodes data to a hex string using the standard binary format.
*
* @param T The type to encode
* @param serializationStrategy The serialization strategy for type [T]
* @param data The data to encode
* @return The encoded hex string
*/
inline fun <T> StandardKtorSerialFormat.encodeHex( inline fun <T> StandardKtorSerialFormat.encodeHex(
serializationStrategy: SerializationStrategy<T>, serializationStrategy: SerializationStrategy<T>,
data: T data: T

View File

@@ -3,17 +3,8 @@ package dev.inmo.micro_utils.ktor.common
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/**
* The default subdirectory path for storing temporal files during upload operations.
*/
const val DefaultTemporalFilesSubPath = "temp_upload" const val DefaultTemporalFilesSubPath = "temp_upload"
/**
* A value class representing a unique identifier for a temporal file.
* Temporal files are typically used for temporary storage during file upload/processing operations.
*
* @param string The string representation of the temporal file ID
*/
@Serializable @Serializable
@JvmInline @JvmInline
value class TemporalFileId(val string: String) value class TemporalFileId(val string: String)

View File

@@ -4,12 +4,6 @@ import korlibs.time.DateTime
import dev.inmo.micro_utils.ktor.common.FromToDateTime import dev.inmo.micro_utils.ktor.common.FromToDateTime
import io.ktor.http.Parameters import io.ktor.http.Parameters
/**
* Extracts a [FromToDateTime] range from Ktor server [Parameters].
* Looks for "from" and "to" parameters containing Unix millisecond timestamps.
*
* @return A [FromToDateTime] pair extracted from the parameters
*/
val Parameters.extractFromToDateTime: FromToDateTime val Parameters.extractFromToDateTime: FromToDateTime
get() = FromToDateTime( get() = FromToDateTime(
get("from") ?.toDoubleOrNull() ?.let { DateTime(it) }, get("from") ?.toDoubleOrNull() ?.let { DateTime(it) },

View File

@@ -4,13 +4,6 @@ import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond import io.ktor.server.response.respond
/**
* Retrieves a parameter value by [field] name from the request parameters.
* If the parameter is not present, responds with [HttpStatusCode.BadRequest] (400) and an error message.
*
* @param field The name of the parameter to retrieve
* @return The parameter value, or null if not found (after sending error response)
*/
suspend fun ApplicationCall.getParameterOrSendError( suspend fun ApplicationCall.getParameterOrSendError(
field: String field: String
) = parameters[field].also { ) = parameters[field].also {
@@ -19,13 +12,6 @@ suspend fun ApplicationCall.getParameterOrSendError(
} }
} }
/**
* Retrieves all parameter values by [field] name from the request parameters.
* If the parameter is not present, responds with [HttpStatusCode.BadRequest] (400) and an error message.
*
* @param field The name of the parameter to retrieve
* @return A list of parameter values, or null if not found (after sending error response)
*/
suspend fun ApplicationCall.getParametersOrSendError( suspend fun ApplicationCall.getParametersOrSendError(
field: String field: String
) = parameters.getAll(field).also { ) = parameters.getAll(field).also {
@@ -34,33 +20,14 @@ suspend fun ApplicationCall.getParametersOrSendError(
} }
} }
/**
* Retrieves a query parameter value by [field] name from the request.
*
* @param field The name of the query parameter to retrieve
* @return The query parameter value, or null if not found
*/
fun ApplicationCall.getQueryParameter( fun ApplicationCall.getQueryParameter(
field: String field: String
) = request.queryParameters[field] ) = request.queryParameters[field]
/**
* Retrieves all query parameter values by [field] name from the request.
*
* @param field The name of the query parameter to retrieve
* @return A list of query parameter values, or null if not found
*/
fun ApplicationCall.getQueryParameters( fun ApplicationCall.getQueryParameters(
field: String field: String
) = request.queryParameters.getAll(field) ) = request.queryParameters.getAll(field)
/**
* Retrieves a query parameter value by [field] name from the request.
* If the parameter is not present, responds with [HttpStatusCode.BadRequest] (400) and an error message.
*
* @param field The name of the query parameter to retrieve
* @return The query parameter value, or null if not found (after sending error response)
*/
suspend fun ApplicationCall.getQueryParameterOrSendError( suspend fun ApplicationCall.getQueryParameterOrSendError(
field: String field: String
) = getQueryParameter(field).also { ) = getQueryParameter(field).also {
@@ -69,13 +36,6 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
} }
} }
/**
* Retrieves all query parameter values by [field] name from the request.
* If the parameter is not present, responds with [HttpStatusCode.BadRequest] (400) and an error message.
*
* @param field The name of the query parameter to retrieve
* @return A list of query parameter values, or null if not found (after sending error response)
*/
suspend fun ApplicationCall.getQueryParametersOrSendError( suspend fun ApplicationCall.getQueryParametersOrSendError(
field: String field: String
) = getQueryParameters(field).also { ) = getQueryParameters(field).also {

View File

@@ -4,13 +4,6 @@ import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond import io.ktor.server.response.respond
/**
* Responds with the given [data] if it's not null, or responds with [HttpStatusCode.NoContent] (204) if it's null.
* This is useful for API endpoints that may return empty results.
*
* @param T The type of data to respond with
* @param data The data to respond with, or null to respond with No Content
*/
suspend inline fun <reified T : Any> ApplicationCall.respondOrNoContent( suspend inline fun <reified T : Any> ApplicationCall.respondOrNoContent(
data: T? data: T?
) { ) {

View File

@@ -23,9 +23,7 @@ dependencies {
implementation libs.ktor.client.java implementation libs.ktor.client.java
} }
application { mainClassName="MainKt"
mainClass = "MainKt"
}
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17

View File

@@ -5,10 +5,6 @@ import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
/**
* Serializer for [IetfLang] that serializes language codes as their string representation.
* The language code is serialized as a simple string (e.g., "en-US", "fr", "de-DE").
*/
object IetfLangSerializer : KSerializer<IetfLang> { object IetfLangSerializer : KSerializer<IetfLang> {
override val descriptor = String.serializer().descriptor override val descriptor = String.serializer().descriptor

View File

@@ -1,9 +1,3 @@
package dev.inmo.micro_utils.matrix package dev.inmo.micro_utils.matrix
/**
* Represents a matrix as a list of rows, where each row is a list of elements.
* This is essentially a 2D structure represented as `List<List<T>>`.
*
* @param T The type of elements in the matrix
*/
typealias Matrix<T> = List<Row<T>> typealias Matrix<T> = List<Row<T>>

View File

@@ -1,37 +1,15 @@
package dev.inmo.micro_utils.matrix package dev.inmo.micro_utils.matrix
/**
* Creates a matrix using a DSL-style builder.
* Allows defining multiple rows using the [MatrixBuilder] API.
*
* @param T The type of elements in the matrix
* @param block A builder lambda to define the matrix structure
* @return A constructed [Matrix]
*/
fun <T> matrix(block: MatrixBuilder<T>.() -> Unit): Matrix<T> { fun <T> matrix(block: MatrixBuilder<T>.() -> Unit): Matrix<T> {
return MatrixBuilder<T>().also(block).matrix return MatrixBuilder<T>().also(block).matrix
} }
/**
* Creates a single-row matrix using a DSL-style builder.
*
* @param T The type of elements in the matrix
* @param block A builder lambda to define the row elements
* @return A [Matrix] containing a single row
*/
fun <T> flatMatrix(block: RowBuilder<T>.() -> Unit): Matrix<T> { fun <T> flatMatrix(block: RowBuilder<T>.() -> Unit): Matrix<T> {
return MatrixBuilder<T>().apply { return MatrixBuilder<T>().apply {
row(block) row(block)
}.matrix }.matrix
} }
/**
* Creates a single-row matrix from the provided elements.
*
* @param T The type of elements in the matrix
* @param elements The elements to include in the single row
* @return A [Matrix] containing a single row with the specified elements
*/
fun <T> flatMatrix(vararg elements: T): Matrix<T> { fun <T> flatMatrix(vararg elements: T): Matrix<T> {
return MatrixBuilder<T>().apply { return MatrixBuilder<T>().apply {
row { elements.forEach { +it } } row { elements.forEach { +it } }

View File

@@ -1,8 +1,3 @@
package dev.inmo.micro_utils.matrix package dev.inmo.micro_utils.matrix
/**
* Represents a single row in a matrix as a list of elements.
*
* @param T The type of elements in the row
*/
typealias Row<T> = List<T> typealias Row<T> = List<T>

View File

@@ -1,9 +1,3 @@
package dev.inmo.micro_utils.mime_types package dev.inmo.micro_utils.mime_types
/**
* A custom implementation of [MimeType] that wraps a raw MIME type string.
* Use this when you need to work with MIME types that aren't defined in the standard set.
*
* @param raw The raw MIME type string (e.g., "application/custom", "text/x-custom")
*/
data class CustomMimeType(override val raw: String) : MimeType data class CustomMimeType(override val raw: String) : MimeType

View File

@@ -2,24 +2,9 @@ package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/**
* Represents a MIME type (Multipurpose Internet Mail Extensions type).
* A MIME type is a standard way to indicate the nature and format of a document, file, or assortment of bytes.
*
* Examples: "text/html", "application/json", "image/png"
*/
@Serializable(MimeTypeSerializer::class) @Serializable(MimeTypeSerializer::class)
interface MimeType { interface MimeType {
/**
* The raw MIME type string (e.g., "text/html", "application/json").
*/
val raw: String val raw: String
/**
* An array of file extensions commonly associated with this MIME type.
* For example, "text/html" might have extensions ["html", "htm"].
* Returns an empty array by default if no extensions are known.
*/
val extensions: Array<String> val extensions: Array<String>
get() = emptyArray() get() = emptyArray()
} }

View File

@@ -8,11 +8,6 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
/**
* Serializer for [MimeType] that serializes MIME types as their raw string representation.
* Uses the [mimeType] factory function to create appropriate [MimeType] instances during deserialization,
* which will return known MIME types when available or create [CustomMimeType] for unknown types.
*/
@Suppress("OPT_IN_USAGE") @Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class) @Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> { object MimeTypeSerializer : KSerializer<MimeType> {

View File

@@ -2,16 +2,6 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
/**
* Executes [block] for each page in a paginated sequence.
* The [paginationMapper] determines the next pagination to use based on the current result.
* Stops when [paginationMapper] returns null.
*
* @param T The type of items in each page
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param paginationMapper Function that determines the next pagination based on the current result. Return null to stop
* @param block Function that processes each page and returns a [PaginationResult]
*/
inline fun <T> doForAll( inline fun <T> doForAll(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?, paginationMapper: (PaginationResult<T>) -> Pagination?,
@@ -22,14 +12,6 @@ inline fun <T> doForAll(
} }
} }
/**
* Executes [block] for each page in a paginated sequence, automatically moving to the next page
* until an empty page or the last page is reached.
*
* @param T The type of items in each page
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that processes each page and returns a [PaginationResult]
*/
inline fun <T> doForAllWithNextPaging( inline fun <T> doForAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: (Pagination) -> PaginationResult<T>
@@ -41,14 +23,6 @@ inline fun <T> doForAllWithNextPaging(
) )
} }
/**
* Executes [block] for each page in a paginated sequence, automatically moving to the next page
* until an empty page or the last page is reached. Uses current page pagination logic.
*
* @param T The type of items in each page
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that processes each page and returns a [PaginationResult]
*/
inline fun <T> doAllWithCurrentPaging( inline fun <T> doAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: (Pagination) -> PaginationResult<T>
@@ -60,13 +34,6 @@ inline fun <T> doAllWithCurrentPaging(
) )
} }
/**
* Alias for [doAllWithCurrentPaging]. Executes [block] for each page in a paginated sequence.
*
* @param T The type of items in each page
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that processes each page and returns a [PaginationResult]
*/
inline fun <T> doForAllWithCurrentPaging( inline fun <T> doForAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: (Pagination) -> PaginationResult<T>

View File

@@ -2,16 +2,6 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
/**
* Retrieves all items from a paginated source by repeatedly calling [block] with different pagination parameters.
* The [paginationMapper] determines the next pagination to use based on the current result.
*
* @param T The type of items being retrieved
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param paginationMapper Function that determines the next pagination based on the current result. Return null to stop
* @param block Function that retrieves a page of results for the given pagination
* @return A list containing all retrieved items from all pages
*/
inline fun <T> getAll( inline fun <T> getAll(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?, paginationMapper: (PaginationResult<T>) -> Pagination?,
@@ -26,17 +16,6 @@ inline fun <T> getAll(
return results.toList() return results.toList()
} }
/**
* Retrieves all items from a paginated source using a receiver context.
* This is useful when the pagination logic depends on the receiver object's state.
*
* @param T The type of items being retrieved
* @param R The receiver type
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param paginationMapper Function that determines the next pagination based on the current result
* @param block Function that retrieves a page of results for the given pagination using the receiver context
* @return A list containing all retrieved items from all pages
*/
inline fun <T, R> R.getAllBy( inline fun <T, R> R.getAllBy(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: R.(PaginationResult<T>) -> Pagination?, paginationMapper: R.(PaginationResult<T>) -> Pagination?,
@@ -47,15 +26,6 @@ inline fun <T, R> R.getAllBy(
{ block(it) } { block(it) }
) )
/**
* Retrieves all items from a paginated source, automatically moving to the next page
* until an empty page or the last page is reached.
*
* @param T The type of items being retrieved
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that retrieves a page of results for the given pagination
* @return A list containing all retrieved items from all pages
*/
inline fun <T> getAllWithNextPaging( inline fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: (Pagination) -> PaginationResult<T>
@@ -65,16 +35,6 @@ inline fun <T> getAllWithNextPaging(
block block
) )
/**
* Retrieves all items from a paginated source using a receiver context,
* automatically moving to the next page until an empty page or the last page is reached.
*
* @param T The type of items being retrieved
* @param R The receiver type
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that retrieves a page of results for the given pagination using the receiver context
* @return A list containing all retrieved items from all pages
*/
inline fun <T, R> R.getAllByWithNextPaging( inline fun <T, R> R.getAllByWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T> block: R.(Pagination) -> PaginationResult<T>
@@ -83,15 +43,6 @@ inline fun <T, R> R.getAllByWithNextPaging(
{ block(it) } { block(it) }
) )
/**
* Retrieves all items from a paginated source, automatically moving to the next page
* until an empty page or the last page is reached. Uses current page pagination logic.
*
* @param T The type of items being retrieved
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that retrieves a page of results for the given pagination
* @return A list containing all retrieved items from all pages
*/
inline fun <T> getAllWithCurrentPaging( inline fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: (Pagination) -> PaginationResult<T>
@@ -101,17 +52,6 @@ inline fun <T> getAllWithCurrentPaging(
block block
) )
/**
* Retrieves all items from a paginated source using a receiver context,
* automatically moving to the next page until an empty page or the last page is reached.
* Uses current page pagination logic.
*
* @param T The type of items being retrieved
* @param R The receiver type
* @param initialPagination The pagination to start with. Defaults to [FirstPagePagination]
* @param block Function that retrieves a page of results for the given pagination using the receiver context
* @return A list containing all retrieved items from all pages
*/
inline fun <T, R> R.getAllByWithCurrentPaging( inline fun <T, R> R.getAllByWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T> block: R.(Pagination) -> PaginationResult<T>

View File

@@ -1,13 +1,5 @@
package dev.inmo.micro_utils.pagination.utils package dev.inmo.micro_utils.pagination.utils
/**
* Optionally reverses this [Iterable] based on the [reverse] parameter.
* Delegates to specialized implementations for [List] and [Set] for better performance.
*
* @param T The type of items in the iterable
* @param reverse If true, reverses the iterable; otherwise returns it unchanged
* @return The iterable, optionally reversed
*/
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) { fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
is List<T> -> optionallyReverse(reverse) is List<T> -> optionallyReverse(reverse)
is Set<T> -> optionallyReverse(reverse) is Set<T> -> optionallyReverse(reverse)
@@ -17,41 +9,17 @@ fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (thi
this this
} }
} }
/**
* Optionally reverses this [List] based on the [reverse] parameter.
*
* @param T The type of items in the list
* @param reverse If true, reverses the list; otherwise returns it unchanged
* @return The list, optionally reversed
*/
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) { fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
reversed() reversed()
} else { } else {
this this
} }
/**
* Optionally reverses this [Set] based on the [reverse] parameter.
* Note that the resulting set may have a different iteration order than the original.
*
* @param T The type of items in the set
* @param reverse If true, reverses the set; otherwise returns it unchanged
* @return The set, optionally reversed
*/
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) { fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet() reversed().toSet()
} else { } else {
this this
} }
/**
* Optionally reverses this [Array] based on the [reverse] parameter.
*
* @param T The type of items in the array
* @param reverse If true, creates a reversed copy of the array; otherwise returns it unchanged
* @return The array, optionally reversed
*/
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) { inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) { Array(size) {
get(lastIndex - it) get(lastIndex - it)

View File

@@ -2,14 +2,6 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
/**
* Paginates this [Iterable] according to the given [Pagination] parameters.
* Returns a [PaginationResult] containing the items within the specified page range.
*
* @param T The type of items in the iterable
* @param with The pagination parameters specifying which page to retrieve
* @return A [PaginationResult] containing the items from the requested page
*/
fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> { fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
var i = 0 var i = 0
val result = mutableListOf<T>() val result = mutableListOf<T>()
@@ -28,15 +20,6 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
return result.createPaginationResult(with, i.toLong()) return result.createPaginationResult(with, i.toLong())
} }
/**
* Paginates this [List] according to the given [Pagination] parameters.
* Returns a [PaginationResult] containing the items within the specified page range.
* More efficient than the [Iterable] version as it uses direct indexing.
*
* @param T The type of items in the list
* @param with The pagination parameters specifying which page to retrieve
* @return A [PaginationResult] containing the items from the requested page
*/
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> { fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
if (with.firstIndex >= size || with.lastIndex < 0) { if (with.firstIndex >= size || with.lastIndex < 0) {
return emptyPaginationResult(with, size.toLong()) return emptyPaginationResult(with, size.toLong())
@@ -47,14 +30,6 @@ fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
) )
} }
/**
* Paginates this [List] according to the given [Pagination] parameters, optionally in reverse order.
*
* @param T The type of items in the list
* @param with The pagination parameters specifying which page to retrieve
* @param reversed If true, the list will be paginated in reverse order
* @return A [PaginationResult] containing the items from the requested page, optionally reversed
*/
fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> { fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
return if (reversed) { return if (reversed) {
val actualPagination = with.optionallyReverse( val actualPagination = with.optionallyReverse(
@@ -67,14 +42,6 @@ fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<
} }
} }
/**
* Paginates this [Set] according to the given [Pagination] parameters.
* Returns a [PaginationResult] containing the items within the specified page range.
*
* @param T The type of items in the set
* @param with The pagination parameters specifying which page to retrieve
* @return A [PaginationResult] containing the items from the requested page
*/
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> { fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
return this.drop(with.firstIndex).take(with.size).createPaginationResult( return this.drop(with.firstIndex).take(with.size).createPaginationResult(
with, with,
@@ -82,14 +49,6 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
) )
} }
/**
* Paginates this [Set] according to the given [Pagination] parameters, optionally in reverse order.
*
* @param T The type of items in the set
* @param with The pagination parameters specifying which page to retrieve
* @param reversed If true, the set will be paginated in reverse order
* @return A [PaginationResult] containing the items from the requested page, optionally reversed
*/
fun <T> Set<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> { fun <T> Set<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
val actualPagination = with.optionallyReverse( val actualPagination = with.optionallyReverse(
size, size,

View File

@@ -2,15 +2,6 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
/**
* An iterator that lazily fetches items from a paginated data source.
* It automatically fetches the next page when the current page is exhausted.
*
* @param T The type of items being iterated
* @param pageSize The size of each page to fetch
* @param countGetter A function that returns the total count of available items
* @param paginationResultGetter A function that fetches a page of results for a given pagination
*/
class PaginatedIterator<T>( class PaginatedIterator<T>(
pageSize: Int, pageSize: Int,
private val countGetter: () -> Long, private val countGetter: () -> Long,
@@ -31,15 +22,6 @@ class PaginatedIterator<T>(
} }
} }
/**
* An iterable that lazily fetches items from a paginated data source.
* It creates a [PaginatedIterator] that automatically fetches pages as needed.
*
* @param T The type of items being iterated
* @param pageSize The size of each page to fetch
* @param countGetter A function that returns the total count of available items
* @param paginationResultGetter A function that fetches a page of results for a given pagination
*/
class PaginatedIterable<T>( class PaginatedIterable<T>(
private val pageSize: Int, private val pageSize: Int,
private val countGetter: () -> Long, private val countGetter: () -> Long,
@@ -49,14 +31,7 @@ class PaginatedIterable<T>(
} }
/** /**
* Creates an [Iterable] that lazily fetches items from a paginated data source. * Will make iterable using incoming [countGetter] and [paginationResultGetter]
* This is useful for iterating over large datasets without loading all items into memory at once.
*
* @param T The type of items being iterated
* @param countGetter A function that returns the total count of available items
* @param pageSize The size of each page to fetch. Defaults to [defaultPaginationPageSize]
* @param paginationResultGetter A function that fetches a page of results for a given pagination
* @return An [Iterable] that can be used in for-loops or other iterable contexts
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T> makeIterable( inline fun <T> makeIterable(

View File

@@ -3,6 +3,7 @@ package dev.inmo.micro_utils.pagination.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow import dev.inmo.micro_utils.coroutines.MutableRedeliverStateFlow
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.runCatchingLogging
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job

View File

@@ -14,7 +14,7 @@ kotlin {
} }
jvmMain { jvmMain {
dependencies { dependencies {
api libs.jb.exposed.jdbc api libs.jb.exposed
} }
} }
} }

View File

@@ -1,17 +1,7 @@
package dev.inmo.micro_utils.pagination package dev.inmo.micro_utils.pagination
import org.jetbrains.exposed.v1.core.Expression import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.jdbc.Query
/**
* Applies pagination to this Exposed [Query].
* Sets the limit and offset based on the pagination parameters, and optionally orders the results.
*
* @param with The pagination parameters to apply
* @param orderBy Optional pair of expression and sort order to order the results by
* @return The query with pagination and optional ordering applied
*/
fun Query.paginate(with: Pagination, orderBy: Pair<Expression<*>, SortOrder>? = null) = fun Query.paginate(with: Pagination, orderBy: Pair<Expression<*>, SortOrder>? = null) =
limit(with.size) limit(with.size)
.offset(with.firstIndex.toLong()) .offset(with.firstIndex.toLong())
@@ -26,15 +16,6 @@ fun Query.paginate(with: Pagination, orderBy: Pair<Expression<*>, SortOrder>? =
} }
} }
/**
* Applies pagination to this Exposed [Query] with optional ordering and reversal.
* Sets the limit and offset based on the pagination parameters.
*
* @param with The pagination parameters to apply
* @param orderBy Optional expression to order the results by
* @param reversed If true, orders in descending order; otherwise ascending. Defaults to false
* @return The query with pagination and optional ordering applied
*/
fun Query.paginate(with: Pagination, orderBy: Expression<*>?, reversed: Boolean = false) = paginate( fun Query.paginate(with: Pagination, orderBy: Expression<*>?, reversed: Boolean = false) = paginate(
with, with,
orderBy ?.let { it to if (reversed) SortOrder.DESC else SortOrder.ASC } orderBy ?.let { it to if (reversed) SortOrder.DESC else SortOrder.ASC }

Some files were not shown because too many files have changed in this diff Show More