Compare commits

...

16 Commits

Author SHA1 Message Date
4c8d92b4c3 update ktor 2023-02-28 19:32:32 +06:00
8bbd33c896 now all android modules depends on jvm 2023-02-28 19:08:39 +06:00
ac33a3580f start 0.17.1 2023-02-28 19:04:04 +06:00
a64a32fbe6 Merge pull request #225 from InsanusMokrassar/0.17.0
0.17.0
2023-02-28 12:15:41 +06:00
9493e97a38 remove redundant part from defaultAndroidSettings.gradle 2023-02-27 22:46:08 +06:00
88bd770260 fill dependencies updates changelog 2023-02-27 22:45:35 +06:00
a7bd33b7bf update kotlin and ksp versions 2023-02-27 17:53:37 +06:00
73c724a2e5 small cleanup in build scripts 2023-02-27 17:03:09 +06:00
d8cf3c6726 update dependencies and at least it is buildable 2023-02-27 16:56:39 +06:00
15dee238b5 start 0.17.0 2023-02-27 15:54:37 +06:00
c70626734e Merge pull request #224 from InsanusMokrassar/0.16.13
0.16.13
2023-02-27 15:51:20 +06:00
5a765ea1bc get back publishing signing 2023-02-27 15:43:52 +06:00
8215f9d2c6 temporal solution of generating problem 2023-02-26 14:37:41 +06:00
d2e6d2ec80 generators for models has been created 2023-02-25 19:56:12 +06:00
3718181e8f start 0.16.13 2023-02-25 18:44:07 +06:00
0d825cf424 Merge pull request #223 from InsanusMokrassar/0.16.12
0.16.12
2023-02-22 10:30:45 +06:00
23 changed files with 574 additions and 51 deletions

View File

@@ -1,5 +1,25 @@
# Changelog
## 0.17.1
* **Hotfix** for absence of jvm dependencies in android modules
* `Versions`:
* `Ktor`: `2.2.3` -> `2.2.4`
## 0.17.0
* `Versions`:
* `Kotlin`: `1.7.20` -> `1.8.10`
* `Serialization`: `1.4.1` -> `1.5.0`
* `KSLog`: `0.5.4` -> `1.0.0`
* `AppCompat`: `1.6.0` -> `1.6.1`
## 0.16.13
* `Repos`:
* `Generator`:
* Module has been created
## 0.16.12
* `Repos`:

View File

@@ -18,6 +18,7 @@ kotlin {
api project(":micro_utils.coroutines")
api libs.android.fragment
}
dependsOn jvmMain
}
}
}

View File

@@ -1,30 +1,5 @@
apply plugin: 'com.getkeepsafe.dexcount'
ext {
jvmKotlinFolderFile = {
String sep = File.separator
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
}
enableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
if (jvmKotlinFolder.exists()) {
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
}
}
disableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
String[] oldDirs = android.sourceSets.main.java.srcDirs
android.sourceSets.main.java.srcDirs = []
for (oldDir in oldDirs) {
if (oldDir != jvmKotlinFolder.path) {
android.sourceSets.main.java.srcDirs += oldDir
}
}
}
}
android {
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
buildToolsVersion libs.versions.android.props.buildTools.get()
@@ -58,10 +33,4 @@ android {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
String sep = File.separator
main.java.srcDirs += "src${sep}main${sep}kotlin"
enableIncludingJvmCodeInAndroidPart()
}
}

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.16.12
android_code_version=180
version=0.17.1
android_code_version=183

View File

@@ -1,36 +1,36 @@
[versions]
kt = "1.7.20"
kt-serialization = "1.4.1"
kt = "1.8.10"
kt-serialization = "1.5.0"
kt-coroutines = "1.6.4"
kslog = "0.5.4"
kslog = "1.0.0"
jb-compose = "1.2.2"
jb-compose = "1.3.1-rc01"
jb-exposed = "0.41.1"
jb-dokka = "1.7.20"
klock = "3.4.0"
uuid = "0.6.0"
uuid = "0.7.0"
ktor = "2.2.3"
ktor = "2.2.4"
gh-release = "2.4.1"
koin = "3.3.2"
ksp = "1.7.20-1.0.8"
ksp = "1.8.10-1.0.9"
kotlin-poet = "1.12.0"
android-gradle = "7.3.0"
dexcount = "3.1.0"
android-gradle = "7.3.1"
dexcount = "4.0.0"
android-coreKtx = "1.9.0"
android-recyclerView = "1.2.1"
android-appCompat = "1.6.0"
android-appCompat = "1.6.1"
android-fragment = "1.5.5"
android-espresso = "3.4.0"
android-test = "1.1.3"
android-espresso = "3.5.1"
android-test = "1.1.5"
android-props-minSdk = "21"
android-props-compileSdk = "33"

View File

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

View File

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

View File

@@ -44,7 +44,8 @@ actual suspend fun <T> HttpClient.uniUpload(
val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
val formData = formData {
data.forEach { (k, v) ->
for (k in data.keys) {
val v = data[k] ?: continue
when (v) {
is File -> append(
k,
@@ -89,7 +90,7 @@ actual suspend fun <T> HttpClient.uniUpload(
submitForm(
url,
Parameters.build {
formData.forEach {
for (it in formData) {
val formItem = (it as PartData.FormItem)
append(it.name!!, it.value)
}

View File

@@ -17,5 +17,8 @@ kotlin {
api libs.ktor.io
}
}
androidMain {
dependsOn jvmMain
}
}
}

View File

@@ -50,6 +50,8 @@ kotlin {
implementation libs.android.espresso
}
}
androidMain.dependsOn jvmMain
}
}

View File

@@ -61,6 +61,8 @@ kotlin {
implementation libs.android.espresso
}
}
androidMain.dependsOn jvmMain
}
}

View File

@@ -32,5 +32,3 @@ kotlin {
}
}
}
disableIncludingJvmCodeInAndroidPart()

View File

@@ -0,0 +1,35 @@
package dev.inmo.micro_utils.repos.annotations
import kotlin.reflect.KClass
/**
* Use this annotation and ksp generator (module `micro_utils.repos.generator`) to create the next hierarchy of models:
*
* * New model. For example: data class NewTest
* * Registered model. For example: data class RegisteredTest
*
* @param registeredSupertypes These [KClass]es will be used as supertypes for registered model
* @param serializable If true (default) will generate @[kotlinx.serialization.Serializable] for models. Affects [generateSerialName]
* @param serializable If true (default) will generate @[kotlinx.serialization.SerialName] for models with their names as values
*
* @see GenerateCRUDModelExcludeOverride
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class GenerateCRUDModel(
vararg val registeredSupertypes: KClass<*>,
val serializable: Boolean = true,
val generateSerialName: Boolean = true
)
/**
* Use this annotation on properties which should be excluded from overriding in models.
*
* @see GenerateCRUDModel
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.PROPERTY)
annotation class GenerateCRUDModelExcludeOverride

154
repos/generator/README.md Normal file
View File

@@ -0,0 +1,154 @@
# Koin generator
It is Kotlin Symbol Processing generator for simple creating of typical models: `New` and `Registered`.
1. [What may do this generator](#what-may-do-this-generator)
2. [How to add generator](#how-to-add-generator)
## What may do this generator
So, you have several known things related to models:
* Interface with all necessary properties
* Id class or some registered marker
Minimal sample will be next:
```kotlin
@GenerateCRUDModel
interface Sample {
val property1: String
val property2: Int
}
```
And generator will create:
```kotlin
@Serializable
@SerialName("NewSample")
data class NewSample(
override val property1: String,
override val property2: Int,
) : Sample
@Serializable
@SerialName("RegisteredSample")
data class RegisteredSample(
override val property1: String,
override val property2: Int,
) : Sample
fun Sample.asNew(): NewSample = NewSample(property1, property2)
fun Sample.asRegistered(): RegisteredSample = RegisteredSample(property1, property2)
```
But in most cases you will need to create some id class and registered interface:
```kotlin
@Serializable
@JvmInline
value class SampleId(
val long: Long
)
sealed interface IRegisteredSample : Sample {
val id: SampleId
@GenerateCRUDModelExcludeOverride
val excludedProperty2: Boolean
get() = false
}
```
As you may see, we have added `GenerateCRUDModelExcludeOverride` annotation. Properties marked with this annotation
WILL NOT be inclued into overriding in registered class (or your base interface if used there). So, if you will wish to
create model with id, use next form:
```kotlin
@GenerateCRUDModel(IRegisteredSample::class)
interface Sample {
val property1: String
val property2: Int
}
```
And generated registered class will be changed:
```kotlin
@Serializable
@SerialName(value = "NewSample")
data class NewSample(
override val property1: String,
override val property2: Int,
) : Sample
@Serializable
@SerialName(value = "RegisteredSample")
data class RegisteredSample(
override val id: SampleId,
override val property1: String,
override val property2: Int,
) : Sample, IRegisteredSample
fun Sample.asNew(): NewSample = NewSample(property1, property2)
fun Sample.asRegistered(id: SampleId): RegisteredSample = RegisteredSample(id, property1, property2)
```
So, full sample will look like:
```kotlin
/**
* Your id value class. In fact, but it is not necessary
*/
@Serializable
@JvmInline
value class SampleId(
val long: Long
)
@GenerateCRUDModel(IRegisteredSample::class)
sealed interface Sample {
val property1: String
val property2: Int
@GenerateCRUDModelExcludeOverride
val excludedProperty: String
get() = "excluded"
}
sealed interface IRegisteredSample : Sample {
val id: SampleId
@GenerateCRUDModelExcludeOverride
val excludedProperty2: Boolean
get() = false
}
```
You always may:
* Use any number of registered classes
* Disable serialization for models
* Disable serial names generation
## How to add generator
**Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project**
**Note: $microutils_version in the version of MicroUtils library in your project**
1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`)
2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"`
3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.repos.generator:$microutils_version`:
```groovy
dependencies {
add("kspCommonMainMetadata", "dev.inmo:micro_utils.repos.generator:$microutils_version") // will work in commonMain of your multiplatform module
add("kspJvm", "dev.inmo:micro_utils.repos.generator:$microutils_version") // will work in main of your JVM module
}
ksp { // this generator do not require any arguments and we should left `ksp` empty
}
```

View File

@@ -0,0 +1,16 @@
plugins {
id "org.jetbrains.kotlin.jvm"
}
apply from: "$publishJvmOnlyPath"
repositories {
mavenCentral()
}
dependencies {
api libs.kt.reflect
api project(":micro_utils.repos.common")
api libs.kotlin.poet
api libs.ksp
}

View File

@@ -0,0 +1,217 @@
package dev.inmo.micro_utils.repos.generator
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.isAnnotationPresent
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSClassifierReference
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSReferenceElement
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeAlias
import com.google.devtools.ksp.symbol.KSValueArgument
import com.google.devtools.ksp.symbol.Nullability
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.ksp.toAnnotationSpec
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModel
import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModelExcludeOverride
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.io.File
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
private fun KSClassifierReference.quilifiedName(): String = "${qualifier ?.let { "${it.quilifiedName()}." } ?: ""}${referencedName()}"
class Processor(
private val codeGenerator: CodeGenerator
) : SymbolProcessor {
private val KSPropertyDeclaration.typeName: TypeName
get() {
return runCatching {
type.toTypeName()
}.getOrElse {
val element = type.element as KSClassifierReference
(type.element as KSClassifierReference).let {
ClassName(
element.qualifier ?.quilifiedName() ?: "",
element.referencedName()
)
}
}
}
@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
val toRetry = resolver.getSymbolsWithAnnotation(
GenerateCRUDModel::class.qualifiedName!!
).filterIsInstance<KSClassDeclaration>().filterNot { ksClassDeclaration ->
val ksFile = ksClassDeclaration.containingFile ?: return@filterNot false
runCatching {
FileSpec.builder(
ksClassDeclaration.packageName.asString(),
"GeneratedModels${ksFile.fileName.removeSuffix(".kt")}"
).apply {
val annotation = ksClassDeclaration.getAnnotationsByType(GenerateCRUDModel::class).first()
addFileComment(
"""
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
TO REGENERATE IT JUST DELETE FILE
ORIGINAL FILE: ${ksFile.fileName}
""".trimIndent()
)
val newName = "New${ksClassDeclaration.simpleName.getShortName()}"
val registeredName = "Registered${ksClassDeclaration.simpleName.getShortName()}"
val allKSClassProperties = ksClassDeclaration.getAllProperties()
val excludedKSClassProperties = allKSClassProperties.filter {
it.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class)
}
val excludedKSClassPropertiesNames = excludedKSClassProperties.map { it.simpleName.asString() }
val ksClassProperties = allKSClassProperties.filter {
it !in excludedKSClassProperties
}
val ksClassPropertiesNames = ksClassProperties.map { it.simpleName.asString() }
val newNewType = TypeSpec.classBuilder(newName).apply {
val typeBuilder = this
addSuperinterface(ksClassDeclaration.toClassName())
addModifiers(KModifier.DATA)
if (annotation.serializable) {
addAnnotation(Serializable::class)
if (annotation.generateSerialName) {
addAnnotation(AnnotationSpec.get(SerialName(newName)))
}
}
primaryConstructor(
FunSpec.constructorBuilder().apply {
ksClassProperties.forEach {
addParameter(it.simpleName.getShortName(), it.typeName)
typeBuilder.addProperty(
PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply {
initializer(it.simpleName.getShortName())
}.build()
)
}
}.build()
)
}.build()
addType(
newNewType
)
val registeredSupertypes = ksClassDeclaration.annotations.filter {
it.shortName.asString() == GenerateCRUDModel::class.simpleName &&
it.annotationType.resolve().declaration.qualifiedName ?.asString() == GenerateCRUDModel::class.qualifiedName
}.flatMap {
(it.arguments.first().value as List<KSType>).map { it.declaration as KSClassDeclaration }
}.toList()
val registeredTypesProperties: List<KSPropertyDeclaration> = registeredSupertypes.flatMap { registeredType ->
registeredType.getAllProperties()
}.filter {
it.simpleName.asString() !in excludedKSClassPropertiesNames && it.getAnnotationsByType(GenerateCRUDModelExcludeOverride::class).none()
}
val allProperties: List<KSPropertyDeclaration> = ksClassProperties.toList() + registeredTypesProperties
val propertiesToOverrideInRegistered = allProperties.distinctBy { it.simpleName.asString() }.sortedBy { property ->
val name = property.simpleName.asString()
ksClassPropertiesNames.indexOf(name).takeIf { it > -1 } ?.let {
it + allProperties.size
} ?: allProperties.indexOfFirst { it.simpleName.asString() == name }
}
val newRegisteredType = TypeSpec.classBuilder(registeredName).apply {
val typeBuilder = this
addSuperinterface(ksClassDeclaration.toClassName())
if (annotation.serializable) {
addAnnotation(Serializable::class)
if (annotation.generateSerialName) {
addAnnotation(
AnnotationSpec.get(SerialName(registeredName))
)
}
}
addSuperinterfaces(registeredSupertypes.map { it.toClassName() })
addModifiers(KModifier.DATA)
primaryConstructor(
FunSpec.constructorBuilder().apply {
propertiesToOverrideInRegistered.forEach {
addParameter(
ParameterSpec.builder(it.simpleName.getShortName(), it.typeName).apply {
annotations += it.annotations.map { it.toAnnotationSpec() }
}.build()
)
typeBuilder.addProperty(
PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply {
initializer(it.simpleName.getShortName())
}.build()
)
}
}.build()
)
}.build()
addType(
newRegisteredType
)
addFunction(
FunSpec.builder("asNew").apply {
receiver(ksClassDeclaration.toClassName())
addCode(
CodeBlock.of(
"return ${newNewType.name}(${newNewType.propertySpecs.joinToString { it.name }})"
)
)
returns(ClassName(packageName, newNewType.name!!))
}.build()
)
addFunction(
FunSpec.builder("asRegistered").apply {
receiver(ksClassDeclaration.toClassName())
(registeredTypesProperties.filter { it.simpleName.asString() !in ksClassPropertiesNames }).forEach {
addParameter(it.simpleName.asString(), it.typeName)
}
addCode(
CodeBlock.of(
"return ${newRegisteredType.name}(${newRegisteredType.propertySpecs.joinToString { it.name }})"
)
)
returns(ClassName(packageName, newRegisteredType.name!!))
}.build()
)
}.build().let {
File(
File(ksFile.filePath).parent,
"GeneratedModels${ksFile.fileName}"
).takeIf { !it.exists() } ?.apply {
parentFile.mkdirs()
writer().use { writer ->
it.writeTo(writer)
}
}
}
}.isSuccess
}.toList()
return toRetry
}
}

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.repos.generator
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
environment.codeGenerator
)
}

View File

@@ -0,0 +1 @@
dev.inmo.micro_utils.repos.generator.Provider

View File

@@ -0,0 +1,27 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "com.google.devtools.ksp"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.repos.common")
}
}
}
}
dependencies {
add("kspCommonMainMetadata", project(":micro_utils.repos.generator"))
}
ksp {
}

View File

@@ -0,0 +1,31 @@
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
// TO REGENERATE IT JUST DELETE FILE
// ORIGINAL FILE: Test.kt
package dev.inmo.micro_utils.repos.generator.test
import kotlin.Int
import kotlin.String
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName(value = "NewTest")
public data class NewTest(
public override val property1: String,
public override val property2: Int,
public override val parent: ParentTypeId?,
) : Test
@Serializable
@SerialName(value = "RegisteredTest")
public data class RegisteredTest(
public override val id: TestId,
public override val property1: String,
public override val property2: Int,
public override val parent: ParentTypeId?,
) : Test, IRegisteredTest
public fun Test.asNew(): NewTest = NewTest(property1, property2, parent)
public fun Test.asRegistered(id: TestId): RegisteredTest = RegisteredTest(id, property1, property2,
parent)

View File

@@ -0,0 +1,33 @@
package dev.inmo.micro_utils.repos.generator.test
import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModel
import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModelExcludeOverride
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
@Serializable
@JvmInline
value class TestId(
val long: Long
)
typealias ParentTypeId = TestId
@GenerateCRUDModel(IRegisteredTest::class)
sealed interface Test {
val property1: String
val property2: Int
val parent: ParentTypeId?
@GenerateCRUDModelExcludeOverride
val excludedProperty: String
get() = "excluded"
}
sealed interface IRegisteredTest : Test {
val id: TestId
@GenerateCRUDModelExcludeOverride
val excludedProperty2: Boolean
get() = false
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.repos.generator.test"/>

View File

@@ -18,6 +18,8 @@ String[] includes = [
":language_codes",
":language_codes:generator",
":repos:common",
":repos:generator",
":repos:generator:test",
":repos:cache",
":repos:exposed",
":repos:inmemory",