Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v1.0.0-alpha03 #16

Merged
merged 21 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ jobs:
uses: gradle/gradle-build-action@v2
with:
arguments: build --stacktrace

- name: Upload Coverage report to CodeCov
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: build/coverageReport/coverage.xml
flags: unittests
fail_ci_if_error: true
verbose: true
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<a href="https://github.com/PatilShreyas/mutekt/actions/workflows/release.yml"><img src="https://github.com/PatilShreyas/mutekt/actions/workflows/release.yml/badge.svg"/></a>
<a href="https://search.maven.org/search?q=g:dev.shreyaspatil.mutekt"><img src="https://img.shields.io/maven-central/v/dev.shreyaspatil.mutekt/mutekt-codegen?label=Maven%20Central&logo=android&style=flat-square"/></a>
<a href="LICENSE"><img src="https://img.shields.io/github/license/PatilShreyas/mutekt?label=License)"/></a>
<a href="https://codecov.io/gh/PatilShreyas/mutekt"><img src="https://codecov.io/gh/PatilShreyas/mutekt/branch/main/graph/badge.svg?token=t5722h7jWn"/></a>
</p>

Generates mutable models from immutable model definitions. It's based on Kotlin's Symbol Processor (KSP).
Expand Down Expand Up @@ -60,13 +61,16 @@ fun setLoading() {
}

fun setNotes() {
_state.apply {
_state.update {
isLoading = false
notes = listOf("Lorem Ipsum")
}
}
```

> **Note**
> Use method `update{}` on Mutable model instance to mutate multiple fields atomically.

### 3. Getting reactive immutable value updates

To get immutable instance with reactive state updates, use method `asStateFlow()` which returns instance of
Expand Down Expand Up @@ -122,8 +126,7 @@ _You can find the latest version and changelogs in the [releases](https://github

#### 1.3 Include generated classes in sources

> **Note**
> In order to make IDE aware of generated code, it's important to include KSP generated sources in the project source sets.
In order to make IDE aware of generated code, it's important to include KSP generated sources in the project source sets.

Include generated sources as follows:

Expand Down
20 changes: 20 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
kotlin("jvm") version libs.versions.kotlin.asProvider().get() apply false
alias(libs.plugins.spotless)
alias(libs.plugins.mavenPublish) apply false
alias(libs.plugins.kotlin.kover)
}

repositories {
Expand All @@ -10,6 +11,7 @@ repositories {

subprojects {
apply(plugin = rootProject.libs.plugins.spotless.get().pluginId)
apply(plugin = rootProject.libs.plugins.kotlin.kover.get().pluginId)
configure<com.diffplug.gradle.spotless.SpotlessExtension> {
kotlin {
target("**/*.kt")
Expand All @@ -19,4 +21,22 @@ subprojects {
licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
}
}
}

koverMerged {
enable()
filters {
projects {
excludes += listOf("example")
}
}
xmlReport {
onCheck.set(true)
reportFile.set(layout.buildDirectory.file("coverageReport/coverage.xml"))
}

htmlReport {
onCheck.set(true)
reportDir.set(layout.buildDirectory.dir("coverageReport/html"))
}
}
2 changes: 1 addition & 1 deletion example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies {
implementation(project(":mutekt-core"))
ksp(project(":mutekt-codegen"))

// val mutektVersion = "1.0.0-alpha02"
// val mutektVersion = "1.0.0-alpha03"
// implementation("dev.shreyaspatil.mutekt:mutekt-core:$mutektVersion")
// ksp("dev.shreyaspatil.mutekt:mutekt-codegen:$mutektVersion")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,24 @@ class NotesViewModel(private val viewModelScope: CoroutineScope) {

try {
val allNotes = getNotes()
_state.apply {
setState {
isLoading = false
notes = allNotes
}
} catch (e: Throwable) {
_state.apply {
setState {
isLoading = false
error = e.message ?: "Error occurred"
}
}
}
}

/**
* Mutates state atomically
*/
private fun setState(mutate: MutableNotesState.() -> Unit) = _state.update(mutate)

/**
* Randomly either returns notes or fails with Exception.
*/
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ SONATYPE_HOST=DEFAULT
RELEASE_SIGNING_ENABLED=true

GROUP=dev.shreyaspatil.mutekt
VERSION_NAME=1.0.0-alpha02
VERSION_NAME=1.0.0-alpha03

POM_INCEPTION_YEAR=2022
POM_URL=https://github.com/PatilShreyas/mutekt/
Expand Down
8 changes: 7 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ coroutine = "1.6.4"
junitJupiter = "5.8.1"
spotless = "6.9.0"
mavenPublish = "0.21.0"
kotlinCompileTesting = "1.4.9"
kotlinKover = "0.6.0"

[libraries]
ksp-symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
kotlinPoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinPoet" }

kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutine" }
kotlinx-coroutines-testing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutine" }

junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitJupiter" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junitJupiter" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitJupiter" }
kotlin-compile-testing = { module = "com.github.tschuchortdev:kotlin-compile-testing-ksp", version.ref = "kotlinCompileTesting" }

[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
kotlin-kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kotlinKover" }
2 changes: 2 additions & 0 deletions mutekt-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ dependencies {
implementation(libs.kotlinPoet.ksp)

testImplementation(libs.junit.jupiter.api)
testImplementation(libs.junit.jupiter.params)
testRuntimeOnly(libs.junit.jupiter.engine)
testImplementation(libs.kotlin.compile.testing)
}

tasks.test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ package dev.shreyaspatil.mutekt.codegen.codebuild
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import dev.shreyaspatil.mutekt.core.StateFlowable
import dev.shreyaspatil.mutekt.core.AtomicExecutor
import dev.shreyaspatil.mutekt.core.MutektMutableState

object ClassNames {

fun stateFlowableOf(clazz: ClassName) = ClassName(
StateFlowable::class.java.packageName,
StateFlowable::class.simpleName!!
).parameterizedBy(clazz)
fun mutektMutableState(
immutableInterface: ClassName,
mutableInterface: ClassName
) = MutektMutableState::class.asClassName().parameterizedBy(immutableInterface, mutableInterface)

fun stateFlowOf(clazz: ClassName) = ClassName(
"kotlinx.coroutines.flow",
Expand All @@ -47,4 +49,6 @@ object ClassNames {
"kotlinx.coroutines.flow",
"FlowCollector"
).parameterizedBy(clazz)

fun atomicExecutor() = AtomicExecutor::class.asClassName()
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ object MemberNames {
val COROUTINE_SCOPE = MemberName("kotlinx.coroutines", "coroutineScope")
val COMBINE = MemberName("kotlinx.coroutines.flow", "combine")
val STATE_IN = MemberName("kotlinx.coroutines.flow", "stateIn")
val FILTER_NOT_NULL = MemberName("kotlinx.coroutines.flow", "filterNotNull")
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import dev.shreyaspatil.mutekt.codegen.codebuild.mutableModel.MutableInterfaceMo
import dev.shreyaspatil.mutekt.codegen.codebuild.mutableModel.impl.MutableClassModelImplBuilder
import dev.shreyaspatil.mutekt.codegen.codebuild.mutableModel.impl.MutableModelFactoryFunctionBuilder

/**
* Builds a class containing required interfaces and implementations for Mutekt.
*/
class ModelClassFileBuilder(private val state: KSClassDeclaration) {
private val packageName = state.packageName.asString()
private val generatedClassFilename = "${state.simpleName.asString()}_Generated"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import dev.shreyaspatil.mutekt.codegen.codebuild.ext.getPublicAbstractProperties

/**
* Generates private immutable data class for a state model.
*
* @property immutableStateInterface Immutable state interface class name
* @property publicProperties Public properties of interface
*/
class ImmutableDataClassModelBuilder(
private val immutableStateInterface: ClassName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ import dev.shreyaspatil.mutekt.codegen.codebuild.ClassNames
import dev.shreyaspatil.mutekt.codegen.codebuild.ext.eachToProperty
import dev.shreyaspatil.mutekt.codegen.codebuild.ext.getPublicAbstractProperties

/**
* Builds and generates interface definition of a mutable model
*
* @property immutableStateInterface Immutable state interface class name
* @property publicProperties Public properties of interface
*/
class MutableInterfaceModelBuilder(
private val immutableStateInterface: ClassName,
private val publicProperties: Sequence<KSPropertyDeclaration>
Expand All @@ -35,12 +41,14 @@ class MutableInterfaceModelBuilder(

fun build() = TypeSpec.interfaceBuilder(interfaceName)
.addSuperinterface(immutableStateInterface)
.addSuperinterface(ClassNames.stateFlowableOf(immutableStateInterface))
.addSuperinterface(ClassNames.mutektMutableState(immutableStateInterface, thisClass()))
.addKdoc("Mutable state model for [%L]", immutableStateInterface.simpleName)
.addMutableStateModelFields()
.build()

private fun TypeSpec.Builder.addMutableStateModelFields() = apply {
publicProperties.eachToProperty { mutable().addModifiers(KModifier.OVERRIDE) }.forEach { addProperty(it) }
}

private fun thisClass() = ClassName(immutableStateInterface.packageName, interfaceName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.NOTHING
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.buildCodeBlock
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.withIndent
Expand All @@ -31,6 +34,11 @@ import dev.shreyaspatil.mutekt.codegen.codebuild.ext.eachToParameter

/**
* Generates a mutable model class implementation
*
* @property immutableStateInterface Immutable state interface class name
* @property publicProperties Public properties of a state model interface
* @property mutableModelInterfaceName Mutable state interface class name
* @property immutableDataClassName Immutable data class name
*/
class MutableClassModelImplBuilder(
private val immutableStateInterface: ClassName,
Expand All @@ -44,9 +52,11 @@ class MutableClassModelImplBuilder(
.addModifiers(KModifier.PRIVATE)
.addSuperinterface(mutableModelInterfaceName)
.primaryConstructor()
.privateAtomicExecutor()
.addPrivateStateFlowAndGetterSetterFields()
.immutableStateFlowImpl()
.overrideFunAsStateFlow()
.overrideFunUpdate()
.build()

private fun TypeSpec.Builder.primaryConstructor() = apply {
Expand All @@ -58,6 +68,15 @@ class MutableClassModelImplBuilder(
)
}

private fun TypeSpec.Builder.privateAtomicExecutor() = apply {
addProperty(
PropertySpec.builder("_atomicExecutor", ClassNames.atomicExecutor())
.initializer("%T()", ClassNames.atomicExecutor())
.addModifiers(KModifier.PRIVATE)
.build()
)
}

/**
* Creates MutableStateFlow property from [property]
*/
Expand Down Expand Up @@ -120,6 +139,24 @@ class MutableClassModelImplBuilder(
.build()
)
}

private fun TypeSpec.Builder.overrideFunUpdate() = apply {
addFunction(
FunSpec.builder("update")
.addModifiers(KModifier.OVERRIDE)
.addParameter(
ParameterSpec.builder(
name = "mutate",
type = LambdaTypeName.get(
receiver = mutableModelInterfaceName,
returnType = Unit::class.asClassName()
)
).build()
)
.addStatement("_atomicExecutor.execute { mutate() }")
.build()
)
}
}

/**
Expand Down Expand Up @@ -191,27 +228,24 @@ class StateFlowImplBuilder(
beginControlFlow("%M", MemberNames.COROUTINE_SCOPE)
withIndent {
beginControlFlow(
"%M(%L) { params ->",
"%M(_atomicExecutor.executing, %L) { params ->",
MemberNames.COMBINE,
publicProperties.joinToString { "_${it.simpleName.asString()}" }
)

withIndent {
addStatement("Immutable%L(", type.simpleName)
addStatement("val isUpdating = params[0] as Boolean")

withIndent {
publicProperties.forEachIndexed { index, field ->
addStatement(
"%L = params[%L] as %L,",
field.simpleName.asString(),
index,
field.type.toTypeName()
)
}
}
addStatement(")")
beginControlFlow("if (!isUpdating)")
addStatement("value")
endControlFlow()

beginControlFlow("else")
addStatement("null")
endControlFlow()
}
endControlFlow()
addStatement(".%M()", MemberNames.FILTER_NOT_NULL)
addStatement(".%M(this)", MemberNames.STATE_IN)
addStatement(".collect(collector)")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import com.squareup.kotlinpoet.KModifier
import dev.shreyaspatil.mutekt.codegen.codebuild.ext.eachToParameter

/**
* Generates a top level function for creating instance of mutable model interface
* Generates a top level function for creating instance of mutable model interface.
*
* @property mutableInterfaceName Mutable state interface class name
* @property mutableImplClassName Mutable state interface's implementor class name
* @property publicProperties Public properties of a state model interface
*/
class MutableModelFactoryFunctionBuilder(
private val mutableInterfaceName: ClassName,
Expand Down
Loading