diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 189ac8396..367e35b59 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -37,5 +37,9 @@ gradlePlugin { id = "valkyrie.abi" implementationClass = "AbiPlugin" } + register("valkyrie.explicit-api") { + id = "valkyrie.explicit-api" + implementationClass = "ExplicitApiPlugin" + } } } diff --git a/build-logic/src/main/kotlin/AbiPlugin.kt b/build-logic/src/main/kotlin/AbiPlugin.kt index 372d1aa55..afa0c5293 100644 --- a/build-logic/src/main/kotlin/AbiPlugin.kt +++ b/build-logic/src/main/kotlin/AbiPlugin.kt @@ -1,5 +1,5 @@ import io.github.composegears.valkyrie.internal.abiValidation -import io.github.composegears.valkyrie.internal.kmpExtension +import io.github.composegears.valkyrie.internal.kotlinMultiplatform import io.github.composegears.valkyrie.internal.kotlinJvm import io.github.composegears.valkyrie.internal.kotlinJvmPluginId import io.github.composegears.valkyrie.internal.kotlinMultiplatformPluginId @@ -12,7 +12,7 @@ class AbiPlugin : Plugin { @OptIn(ExperimentalAbiValidation::class) override fun apply(target: Project) = with(target) { plugins.withId(kotlinMultiplatformPluginId) { - kmpExtension { + kotlinMultiplatform { abiValidation { enabled.set(true) } diff --git a/build-logic/src/main/kotlin/ExplicitApiPlugin.kt b/build-logic/src/main/kotlin/ExplicitApiPlugin.kt new file mode 100644 index 000000000..5275632c6 --- /dev/null +++ b/build-logic/src/main/kotlin/ExplicitApiPlugin.kt @@ -0,0 +1,18 @@ +import io.github.composegears.valkyrie.internal.kotlinJvm +import io.github.composegears.valkyrie.internal.kotlinJvmPluginId +import io.github.composegears.valkyrie.internal.kotlinMultiplatform +import io.github.composegears.valkyrie.internal.kotlinMultiplatformPluginId +import org.gradle.api.Plugin +import org.gradle.api.Project + +class ExplicitApiPlugin : Plugin { + + override fun apply(target: Project) = with(target) { + plugins.withId(kotlinMultiplatformPluginId) { + kotlinMultiplatform { explicitApi() } + } + plugins.withId(kotlinJvmPluginId) { + kotlinJvm { explicitApi() } + } + } +} diff --git a/build-logic/src/main/kotlin/KmpPlugin.kt b/build-logic/src/main/kotlin/KmpPlugin.kt index cc6f82634..5a060f090 100644 --- a/build-logic/src/main/kotlin/KmpPlugin.kt +++ b/build-logic/src/main/kotlin/KmpPlugin.kt @@ -1,6 +1,6 @@ import io.github.composegears.valkyrie.extension.applyTargets import io.github.composegears.valkyrie.internal.configureArchiveBaseName -import io.github.composegears.valkyrie.internal.kmpExtension +import io.github.composegears.valkyrie.internal.kotlinMultiplatform import io.github.composegears.valkyrie.internal.kotlinMultiplatformPluginId import org.gradle.api.Plugin import org.gradle.api.Project @@ -12,7 +12,7 @@ class KmpPlugin : Plugin { override fun apply(target: Project) = with(target) { apply(plugin = kotlinMultiplatformPluginId) - kmpExtension { + kotlinMultiplatform { applyTargets() compilerOptions { diff --git a/build-logic/src/main/kotlin/io/github/composegears/valkyrie/extension/Dependency.kt b/build-logic/src/main/kotlin/io/github/composegears/valkyrie/extension/Dependency.kt new file mode 100644 index 000000000..874c5faa8 --- /dev/null +++ b/build-logic/src/main/kotlin/io/github/composegears/valkyrie/extension/Dependency.kt @@ -0,0 +1,7 @@ +package io.github.composegears.valkyrie.extension + +import org.gradle.api.artifacts.Dependency + +infix fun Dependency?.because(reason: String) { + this?.because(reason) +} \ No newline at end of file diff --git a/build-logic/src/main/kotlin/io/github/composegears/valkyrie/internal/Project.kt b/build-logic/src/main/kotlin/io/github/composegears/valkyrie/internal/Project.kt index 5e4aee92a..b1c89e805 100644 --- a/build-logic/src/main/kotlin/io/github/composegears/valkyrie/internal/Project.kt +++ b/build-logic/src/main/kotlin/io/github/composegears/valkyrie/internal/Project.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.abi.AbiValidationExtension import org.jetbrains.kotlin.gradle.dsl.abi.AbiValidationMultiplatformExtension -internal fun Project.kmpExtension(action: KotlinMultiplatformExtension.() -> Unit) = +internal fun Project.kotlinMultiplatform(action: KotlinMultiplatformExtension.() -> Unit) = extensions.configure(action) internal fun Project.kotlinJvm(action: KotlinJvmProjectExtension.() -> Unit) = diff --git a/components/generator/core/api/core.api b/components/generator/core/api/core.api deleted file mode 100644 index 35bbb7bf9..000000000 --- a/components/generator/core/api/core.api +++ /dev/null @@ -1,44 +0,0 @@ -public final class io/github/composegears/valkyrie/generator/core/FloatFormatterKt { - public static final fun formatFloat (F)Ljava/lang/String; - public static final fun trimTrailingZero (F)Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/core/IconPack { - public static final field Companion Lio/github/composegears/valkyrie/generator/core/IconPack$Companion; - public fun (Ljava/lang/String;Ljava/util/List;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Ljava/util/List;)Lio/github/composegears/valkyrie/generator/core/IconPack; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/core/IconPack;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/core/IconPack; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getNested ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/core/IconPack$Companion { - public final fun fromString (Ljava/lang/String;)Lio/github/composegears/valkyrie/generator/core/IconPack; - public final fun toRawString (Lio/github/composegears/valkyrie/generator/core/IconPack;)Ljava/lang/String; -} - -public abstract interface annotation class io/github/composegears/valkyrie/generator/core/IconPackDsl : java/lang/annotation/Annotation { -} - -public final class io/github/composegears/valkyrie/generator/core/IconPackDslKt { - public static final fun iconpack (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lio/github/composegears/valkyrie/generator/core/IconPack; - public static synthetic fun iconpack$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/core/IconPack; -} - -public final class io/github/composegears/valkyrie/generator/core/PackBuilder { - public fun (Ljava/lang/String;)V - public final fun pack (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun pack$default (Lio/github/composegears/valkyrie/generator/core/PackBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V -} - -public final class io/github/composegears/valkyrie/generator/core/PathNodeRendererKt { - public static final fun asPathDataString (Ljava/util/List;)Ljava/lang/String; - public static final fun asStatement (Lio/github/composegears/valkyrie/sdk/ir/core/IrPathNode;)Ljava/lang/String; -} - diff --git a/components/generator/core/api/core.klib.api b/components/generator/core/api/core.klib.api deleted file mode 100644 index 4a0c2b91d..000000000 --- a/components/generator/core/api/core.klib.api +++ /dev/null @@ -1,44 +0,0 @@ -// Klib ABI Dump -// Targets: [wasmJs] -// Rendering settings: -// - Signature version: 2 -// - Show manifest properties: true -// - Show declarations: true - -// Library unique name: -open annotation class io.github.composegears.valkyrie.generator.core/IconPackDsl : kotlin/Annotation { // io.github.composegears.valkyrie.generator.core/IconPackDsl|null[0] - constructor () // io.github.composegears.valkyrie.generator.core/IconPackDsl.|(){}[0] -} - -final class io.github.composegears.valkyrie.generator.core/IconPack { // io.github.composegears.valkyrie.generator.core/IconPack|null[0] - constructor (kotlin/String, kotlin.collections/List = ...) // io.github.composegears.valkyrie.generator.core/IconPack.|(kotlin.String;kotlin.collections.List){}[0] - - final val name // io.github.composegears.valkyrie.generator.core/IconPack.name|{}name[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.core/IconPack.name.|(){}[0] - final val nested // io.github.composegears.valkyrie.generator.core/IconPack.nested|{}nested[0] - final fun (): kotlin.collections/List // io.github.composegears.valkyrie.generator.core/IconPack.nested.|(){}[0] - - final fun component1(): kotlin/String // io.github.composegears.valkyrie.generator.core/IconPack.component1|component1(){}[0] - final fun component2(): kotlin.collections/List // io.github.composegears.valkyrie.generator.core/IconPack.component2|component2(){}[0] - final fun copy(kotlin/String = ..., kotlin.collections/List = ...): io.github.composegears.valkyrie.generator.core/IconPack // io.github.composegears.valkyrie.generator.core/IconPack.copy|copy(kotlin.String;kotlin.collections.List){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.generator.core/IconPack.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.generator.core/IconPack.hashCode|hashCode(){}[0] - final fun toString(): kotlin/String // io.github.composegears.valkyrie.generator.core/IconPack.toString|toString(){}[0] - - final object Companion { // io.github.composegears.valkyrie.generator.core/IconPack.Companion|null[0] - final fun fromString(kotlin/String): io.github.composegears.valkyrie.generator.core/IconPack // io.github.composegears.valkyrie.generator.core/IconPack.Companion.fromString|fromString(kotlin.String){}[0] - final fun toRawString(io.github.composegears.valkyrie.generator.core/IconPack): kotlin/String // io.github.composegears.valkyrie.generator.core/IconPack.Companion.toRawString|toRawString(io.github.composegears.valkyrie.generator.core.IconPack){}[0] - } -} - -final class io.github.composegears.valkyrie.generator.core/PackBuilder { // io.github.composegears.valkyrie.generator.core/PackBuilder|null[0] - constructor (kotlin/String) // io.github.composegears.valkyrie.generator.core/PackBuilder.|(kotlin.String){}[0] - - final fun pack(kotlin/String, kotlin/Function1 = ...) // io.github.composegears.valkyrie.generator.core/PackBuilder.pack|pack(kotlin.String;kotlin.Function1){}[0] -} - -final fun (io.github.composegears.valkyrie.sdk.ir.core/IrPathNode).io.github.composegears.valkyrie.generator.core/asStatement(): kotlin/String // io.github.composegears.valkyrie.generator.core/asStatement|asStatement@io.github.composegears.valkyrie.sdk.ir.core.IrPathNode(){}[0] -final fun (kotlin.collections/List).io.github.composegears.valkyrie.generator.core/asPathDataString(): kotlin/String // io.github.composegears.valkyrie.generator.core/asPathDataString|asPathDataString@kotlin.collections.List(){}[0] -final fun (kotlin/Float).io.github.composegears.valkyrie.generator.core/formatFloat(): kotlin/String // io.github.composegears.valkyrie.generator.core/formatFloat|formatFloat@kotlin.Float(){}[0] -final fun (kotlin/Float).io.github.composegears.valkyrie.generator.core/trimTrailingZero(): kotlin/String // io.github.composegears.valkyrie.generator.core/trimTrailingZero|trimTrailingZero@kotlin.Float(){}[0] -final fun io.github.composegears.valkyrie.generator.core/iconpack(kotlin/String, kotlin/Function1 = ...): io.github.composegears.valkyrie.generator.core/IconPack // io.github.composegears.valkyrie.generator.core/iconpack|iconpack(kotlin.String;kotlin.Function1){}[0] diff --git a/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/FloatFormatter.kt b/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/FloatFormatter.kt deleted file mode 100644 index 3165b5105..000000000 --- a/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/FloatFormatter.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.composegears.valkyrie.generator.core - -fun Float.trimTrailingZero(): String = toString().removeSuffix(".0") - -fun Float.formatFloat(): String = "${trimTrailingZero()}f" diff --git a/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/IconPack.kt b/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/IconPack.kt deleted file mode 100644 index 64da11a22..000000000 --- a/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/IconPack.kt +++ /dev/null @@ -1,90 +0,0 @@ -package io.github.composegears.valkyrie.generator.core - -data class IconPack( - val name: String, - val nested: List = emptyList(), -) { - - override fun toString(): String { - fun StringBuilder.buildNestedTree(prefix: String, items: List) { - items.forEachIndexed { index, iconPack -> - val isLastChild = index == items.lastIndex - val branchChar = if (isLastChild) "└── " else "├── " - val nextPrefix = prefix + if (isLastChild) "\t" else "│\t" - - appendLine() - append("$prefix$branchChar${iconPack.name}") - - if (iconPack.nested.isNotEmpty()) { - buildNestedTree(prefix = nextPrefix, items = iconPack.nested) - } - } - } - - return buildString { - if (name.isNotEmpty()) { - appendLine() - append(name) - append(":") - buildNestedTree(prefix = "", items = nested) - appendLine() - } - } - } - - companion object { - fun fromString(input: String): IconPack { - fun buildHierarchy(paths: List>): List { - if (paths.all { it.isEmpty() }) return emptyList() - - return paths - .groupBy { it.first() } - .map { (name, nestedPaths) -> - IconPack( - name = name, - nested = buildHierarchy(nestedPaths.mapNotNull { it.drop(1).takeIf { it.isNotEmpty() } }), - ) - } - } - - if (input.isEmpty()) { - return IconPack(name = "") - } - - val paths = input.split(',').map { it.split('.') } - - val rootNames = paths.map { it.first() }.distinct() - if (rootNames.size != 1) { - error("Invalid icon pack structure: expected a single root, but found ${rootNames.size} roots") - } - - return IconPack( - name = rootNames.first(), - nested = buildHierarchy(paths.map { it.drop(1) }), - ) - } - - fun toRawString(iconPack: IconPack): String { - if (iconPack.name.isEmpty()) { - return "" - } - - val paths = mutableListOf() - - fun traverse(node: IconPack, parentPath: String = "") { - val currentPath = if (parentPath.isEmpty()) node.name else "$parentPath.${node.name}" - - if (node.nested.isEmpty()) { - paths.add(currentPath) - } else { - node.nested.forEach { child -> - traverse(child, currentPath) - } - } - } - - traverse(iconPack) - return paths.joinToString(separator = ",") - } - } -} diff --git a/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/IconPackDsl.kt b/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/IconPackDsl.kt deleted file mode 100644 index 5c361a6dd..000000000 --- a/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/IconPackDsl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.composegears.valkyrie.generator.core - -@DslMarker -annotation class IconPackDsl - -@IconPackDsl -fun iconpack(name: String, init: PackBuilder.() -> Unit = {}): IconPack { - val builder = PackBuilder(name) - builder.init() - return builder.build() -} - -@IconPackDsl -class PackBuilder(private val name: String) { - private val nestedPacks = mutableListOf() - - fun pack(name: String, init: PackBuilder.() -> Unit = {}) { - val builder = PackBuilder(name) - builder.init() - nestedPacks.add(builder.build()) - } - - internal fun build(): IconPack = IconPack(name, nestedPacks) -} diff --git a/components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/IconPackDslTest.kt b/components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/IconPackDslTest.kt deleted file mode 100644 index b33ee649d..000000000 --- a/components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/IconPackDslTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -package io.github.composegears.valkyrie.generator.core - -import assertk.assertThat -import assertk.assertions.hasSize -import assertk.assertions.isEqualTo -import kotlin.test.Test - -class IconPackDslTest { - - @Test - fun `simple iconpack`() { - val iconPack = iconpack(name = "ValkyrieIcons") - - assertThat(iconPack.name).isEqualTo("ValkyrieIcons") - assertThat(iconPack.nested).hasSize(0) - } - - @Test - fun `pack with one level of nesting`() { - val iconPack = iconpack(name = "ValkyrieIcons") { - pack(name = "Outlined") - pack(name = "Filled") - } - - assertThat(iconPack.name).isEqualTo("ValkyrieIcons") - assertThat(iconPack.nested).hasSize(2) - assertThat(iconPack.nested[0].name).isEqualTo("Outlined") - assertThat(iconPack.nested[1].name).isEqualTo("Filled") - } - - @Test - fun `iconpack with multiple nested levels`() { - val iconPack = iconpack(name = "ValkyrieIcons") { - pack(name = "Material") { - pack(name = "Rounded") { - pack(name = "Filled") - } - pack(name = "Sharp") { - pack(name = "Outlined") - } - } - pack(name = "Custom") { - pack(name = "Brand") - } - } - - assertThat(iconPack.name).isEqualTo("ValkyrieIcons") - assertThat(iconPack.nested).hasSize(2) - - val material = iconPack.nested[0] - assertThat(material.name).isEqualTo("Material") - assertThat(material.nested).hasSize(2) - - val rounded = material.nested[0] - assertThat(rounded.name).isEqualTo("Rounded") - assertThat(rounded.nested).hasSize(1) - assertThat(rounded.nested[0].name).isEqualTo("Filled") - - val sharp = material.nested[1] - assertThat(sharp.name).isEqualTo("Sharp") - assertThat(sharp.nested).hasSize(1) - assertThat(sharp.nested[0].name).isEqualTo("Outlined") - - val custom = iconPack.nested[1] - assertThat(custom.name).isEqualTo("Custom") - assertThat(custom.nested).hasSize(1) - assertThat(custom.nested[0].name).isEqualTo("Brand") - } -} diff --git a/components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/IconPackTest.kt b/components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/IconPackTest.kt deleted file mode 100644 index a782dac9e..000000000 --- a/components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/IconPackTest.kt +++ /dev/null @@ -1,238 +0,0 @@ -package io.github.composegears.valkyrie.generator.core - -import assertk.assertFailure -import assertk.assertThat -import assertk.assertions.containsAtLeast -import assertk.assertions.hasSize -import assertk.assertions.isEmpty -import assertk.assertions.isEqualTo -import assertk.assertions.isInstanceOf -import kotlin.test.Test -import kotlin.test.assertEquals - -class IconPackTest { - - @Test - fun `toString should handle empty IconPack`() { - val iconPack = IconPack(name = "") - val expected = "" - - assertEquals(expected, iconPack.toString()) - } - - @Test - fun `toString should return formatted tree structure`() { - val iconPack = IconPack( - name = "Root", - nested = listOf( - IconPack( - name = "Child1", - nested = listOf( - IconPack(name = "Grandchild1"), - IconPack(name = "Grandchild2"), - ), - ), - IconPack(name = "Child2"), - ), - ) - - val expected = """ - - Root: - ├── Child1 - │ ├── Grandchild1 - │ └── Grandchild2 - └── Child2 - - """.trimIndent() - - assertEquals(expected, iconPack.toString()) - } - - @Test - fun `toString should handle deeply nested IconPack`() { - val iconPack = IconPack( - name = "Root", - nested = listOf( - IconPack( - name = "Child1", - nested = listOf( - IconPack( - name = "Grandchild1", - nested = listOf( - IconPack( - name = "GreatGrandchild1", - nested = listOf( - IconPack(name = "GreatGreatGrandchild1"), - ), - ), - ), - ), - ), - ), - IconPack( - name = "Child2", - nested = listOf( - IconPack(name = "Grandchild2"), - ), - ), - ), - ) - - val expected = """ - - Root: - ├── Child1 - │ └── Grandchild1 - │ └── GreatGrandchild1 - │ └── GreatGreatGrandchild1 - └── Child2 - └── Grandchild2 - - """.trimIndent() - - assertEquals(expected, iconPack.toString()) - } - - @Test - fun `fromString parsing empty string`() { - val input = "" - val result = IconPack.fromString(input) - - assertThat(result.name).isEqualTo("") - assertThat(result.nested).isEmpty() - - assertThat(IconPack.toRawString(result)).isEqualTo(input) - } - - @Test - fun `fromString parsing with multiple roots`() { - val input = "RootA.Child1,RootB.Child2" - - assertFailure { - IconPack.fromString(input) - }.isInstanceOf(IllegalStateException::class) - } - - @Test - fun `fromString parsing single segment`() { - val input = "Root" - val result = IconPack.fromString(input) - - assertThat(result.name).isEqualTo("Root") - assertThat(result.nested).isEmpty() - - assertThat(IconPack.toRawString(result)).isEqualTo(input) - } - - @Test - fun `fromString parsing L1 hierarchy`() { - val input = "Root.Child1,Root.Child2" - val result = IconPack.fromString(input) - - assertThat(result.name).isEqualTo("Root") - assertThat(result.nested).hasSize(2) - assertThat(result.nested[0].name).isEqualTo("Child1") - assertThat(result.nested[0].nested).isEmpty() - assertThat(result.nested[1].name).isEqualTo("Child2") - assertThat(result.nested[1].nested).isEmpty() - - assertThat(IconPack.toRawString(result)).isEqualTo(input) - } - - @Test - fun `fromString parsing deeper hierarchy`() { - val input = "Root.Child1,Root.Child1.GrandChild1,Root.Child2" - val result = IconPack.fromString(input) - - assertThat(result.name).isEqualTo("Root") - assertThat(result.nested).hasSize(2) - - val child1 = result.nested.find { it.name == "Child1" }!! - val child2 = result.nested.find { it.name == "Child2" }!! - - assertThat(child1.nested).hasSize(1) - assertThat(child1.nested[0].name).isEqualTo("GrandChild1") - assertThat(child1.nested[0].nested).isEmpty() - - assertThat(child2.nested).isEmpty() - - assertThat(IconPack.toRawString(result)).isEqualTo("Root.Child1.GrandChild1,Root.Child2") - } - - @Test - fun `fromString parsing multiple levels deep`() { - val input = "Root.Child1.GrandChild1.GreatGrandChild1,Root.Child2,Root.Child1" - val result = IconPack.fromString(input) - - assertThat(result.name).isEqualTo("Root") - assertThat(result.nested).hasSize(2) - - val child1 = result.nested.find { it.name == "Child1" }!! - val grandChild1 = child1.nested[0] - val greatGrandChild1 = grandChild1.nested[0] - - assertThat(grandChild1.name).isEqualTo("GrandChild1") - assertThat(greatGrandChild1.name).isEqualTo("GreatGrandChild1") - assertThat(greatGrandChild1.nested).isEmpty() - - assertThat(IconPack.toRawString(result)).isEqualTo("Root.Child1.GrandChild1.GreatGrandChild1,Root.Child2") - } - - @Test - fun `fromString parsing with complex nested structure`() { - val input = "Root.A.X,Root.A.Y,Root.B.Z,Root.B.W.V" - val result = IconPack.fromString(input) - - assertThat(result.name).isEqualTo("Root") - assertThat(result.nested).hasSize(2) - - val childA = result.nested.find { it.name == "A" }!! - val childB = result.nested.find { it.name == "B" }!! - - assertThat(childA.nested).hasSize(2) - val namesInA = childA.nested.map { it.name } - assertThat(namesInA).containsAtLeast("X", "Y") - - assertThat(childB.nested).hasSize(2) - val childZ = childB.nested.find { it.name == "Z" }!! - val childW = childB.nested.find { it.name == "W" }!! - - assertThat(childZ.nested).isEmpty() - assertThat(childW.nested).hasSize(1) - - val childV = childW.nested[0] - assertThat(childV.name).isEqualTo("V") - assertThat(childV.nested).isEmpty() - - assertThat(IconPack.toRawString(result)).isEqualTo(input) - } - - @Test - fun `fromString parsing with shared paths`() { - val input = "AAA.BB,AAA.CC,AAA.BB.FF,AAA.BB.FF.CC.AAA,AAA.CC.BB" - val result = IconPack.fromString(input) - - assertThat(result.name).isEqualTo("AAA") - assertThat(result.nested).hasSize(2) - - val childBB = result.nested.find { it.name == "BB" }!! - val childCC = result.nested.find { it.name == "CC" }!! - - assertThat(childBB.nested).hasSize(1) - assertThat(childBB.nested[0].name).isEqualTo("FF") - - val childFF = childBB.nested[0] - assertThat(childFF.nested).hasSize(1) - assertThat(childFF.nested[0].name).isEqualTo("CC") - - val repeatedChildCC = childFF.nested[0] - assertThat(repeatedChildCC.nested).hasSize(1) - assertThat(repeatedChildCC.nested[0].name).isEqualTo("AAA") - - assertThat(childCC.nested).hasSize(1) - assertThat(childCC.nested[0].name).isEqualTo("BB") - - assertThat(IconPack.toRawString(result)).isEqualTo("AAA.BB.FF.CC.AAA,AAA.CC.BB") - } -} diff --git a/components/generator/iconpack/api/iconpack.api b/components/generator/iconpack/api/iconpack.api deleted file mode 100644 index d383467bd..000000000 --- a/components/generator/iconpack/api/iconpack.api +++ /dev/null @@ -1,38 +0,0 @@ -public final class io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator { - public static final field INSTANCE Lio/github/composegears/valkyrie/generator/iconpack/IconPackGenerator; - public final fun create (Lio/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorConfig;)Lio/github/composegears/valkyrie/generator/iconpack/IconPackSpecOutput; -} - -public final class io/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorConfig { - public fun (Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;ZILjava/lang/String;)V - public synthetic fun (Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;ZILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/github/composegears/valkyrie/generator/core/IconPack; - public final fun component3 ()Z - public final fun component4 ()I - public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;ZILjava/lang/String;)Lio/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorConfig; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorConfig;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;ZILjava/lang/String;ILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getIconPack ()Lio/github/composegears/valkyrie/generator/core/IconPack; - public final fun getIndentSize ()I - public final fun getLicense ()Ljava/lang/String; - public final fun getPackageName ()Ljava/lang/String; - public final fun getUseExplicitMode ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/iconpack/IconPackSpecOutput { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/github/composegears/valkyrie/generator/iconpack/IconPackSpecOutput; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/iconpack/IconPackSpecOutput;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/iconpack/IconPackSpecOutput; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - diff --git a/components/generator/iconpack/api/iconpack.klib.api b/components/generator/iconpack/api/iconpack.klib.api deleted file mode 100644 index 40eb0cbd8..000000000 --- a/components/generator/iconpack/api/iconpack.klib.api +++ /dev/null @@ -1,52 +0,0 @@ -// Klib ABI Dump -// Targets: [wasmJs] -// Rendering settings: -// - Signature version: 2 -// - Show manifest properties: true -// - Show declarations: true - -// Library unique name: -final class io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig { // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig|null[0] - constructor (kotlin/String, io.github.composegears.valkyrie.generator.core/IconPack, kotlin/Boolean, kotlin/Int, kotlin/String? = ...) // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.|(kotlin.String;io.github.composegears.valkyrie.generator.core.IconPack;kotlin.Boolean;kotlin.Int;kotlin.String?){}[0] - - final val iconPack // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.iconPack|{}iconPack[0] - final fun (): io.github.composegears.valkyrie.generator.core/IconPack // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.iconPack.|(){}[0] - final val indentSize // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.indentSize|{}indentSize[0] - final fun (): kotlin/Int // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.indentSize.|(){}[0] - final val license // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.license|{}license[0] - final fun (): kotlin/String? // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.license.|(){}[0] - final val packageName // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.packageName|{}packageName[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.packageName.|(){}[0] - final val useExplicitMode // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.useExplicitMode|{}useExplicitMode[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.useExplicitMode.|(){}[0] - - final fun component1(): kotlin/String // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.component1|component1(){}[0] - final fun component2(): io.github.composegears.valkyrie.generator.core/IconPack // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.component2|component2(){}[0] - final fun component3(): kotlin/Boolean // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.component3|component3(){}[0] - final fun component4(): kotlin/Int // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.component4|component4(){}[0] - final fun component5(): kotlin/String? // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.component5|component5(){}[0] - final fun copy(kotlin/String = ..., io.github.composegears.valkyrie.generator.core/IconPack = ..., kotlin/Boolean = ..., kotlin/Int = ..., kotlin/String? = ...): io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.copy|copy(kotlin.String;io.github.composegears.valkyrie.generator.core.IconPack;kotlin.Boolean;kotlin.Int;kotlin.String?){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.hashCode|hashCode(){}[0] - final fun toString(): kotlin/String // io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig.toString|toString(){}[0] -} - -final class io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput { // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput|null[0] - constructor (kotlin/String, kotlin/String) // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.|(kotlin.String;kotlin.String){}[0] - - final val content // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.content|{}content[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.content.|(){}[0] - final val name // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.name|{}name[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.name.|(){}[0] - - final fun component1(): kotlin/String // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.component1|component1(){}[0] - final fun component2(): kotlin/String // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.component2|component2(){}[0] - final fun copy(kotlin/String = ..., kotlin/String = ...): io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.copy|copy(kotlin.String;kotlin.String){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.hashCode|hashCode(){}[0] - final fun toString(): kotlin/String // io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput.toString|toString(){}[0] -} - -final object io.github.composegears.valkyrie.generator.iconpack/IconPackGenerator { // io.github.composegears.valkyrie.generator.iconpack/IconPackGenerator|null[0] - final fun create(io.github.composegears.valkyrie.generator.iconpack/IconPackGeneratorConfig): io.github.composegears.valkyrie.generator.iconpack/IconPackSpecOutput // io.github.composegears.valkyrie.generator.iconpack/IconPackGenerator.create|create(io.github.composegears.valkyrie.generator.iconpack.IconPackGeneratorConfig){}[0] -} diff --git a/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.kt b/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.kt deleted file mode 100644 index b9ad1a5c2..000000000 --- a/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.composegears.valkyrie.generator.iconpack - -expect object IconPackGenerator { - fun create(config: IconPackGeneratorConfig): IconPackSpecOutput -} diff --git a/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorConfig.kt b/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorConfig.kt deleted file mode 100644 index 450d4e486..000000000 --- a/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorConfig.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.github.composegears.valkyrie.generator.iconpack - -import io.github.composegears.valkyrie.generator.core.IconPack - -data class IconPackGeneratorConfig( - val packageName: String, - val iconPack: IconPack, - val useExplicitMode: Boolean, - val indentSize: Int, - val license: String? = null, -) diff --git a/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackSpecOutput.kt b/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackSpecOutput.kt deleted file mode 100644 index 27f0abd8b..000000000 --- a/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackSpecOutput.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.composegears.valkyrie.generator.iconpack - -data class IconPackSpecOutput( - val content: String, - val name: String, -) diff --git a/components/generator/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.jvm.kt b/components/generator/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.jvm.kt deleted file mode 100644 index 4c7ecb49b..000000000 --- a/components/generator/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.jvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.composegears.valkyrie.generator.iconpack - -actual object IconPackGenerator { - actual fun create(config: IconPackGeneratorConfig): IconPackSpecOutput { - return IconPackFileSpec(config).createSpec() - } -} diff --git a/components/generator/iconpack/src/wasmJsMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.wasmJs.kt b/components/generator/iconpack/src/wasmJsMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.wasmJs.kt deleted file mode 100644 index a5f694222..000000000 --- a/components/generator/iconpack/src/wasmJsMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGenerator.wasmJs.kt +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.composegears.valkyrie.generator.iconpack - -import io.github.composegears.valkyrie.generator.core.IconPack - -actual object IconPackGenerator { - - actual fun create(config: IconPackGeneratorConfig): IconPackSpecOutput { - val content = buildString { - if (config.license != null) { - appendLine(config.license.asBlockComment()) - appendLine() - } - appendLine("package ${config.packageName}") - appendLine() - - val iconPack = createIconPack( - iconPack = config.iconPack, - indentSize = config.indentSize, - isExplicit = config.useExplicitMode, - ).clearBlankLines() - appendLine(iconPack) - } - - return IconPackSpecOutput(name = config.iconPack.name, content = content) - } - - private fun createIconPack(iconPack: IconPack, indentSize: Int, isExplicit: Boolean): String { - return buildString { - if (isExplicit) append("public ") - append("object ${iconPack.name}") - - if (iconPack.nested.isNotEmpty()) { - appendLine(" {") - val lastIndex = iconPack.nested.lastIndex - iconPack.nested.forEachIndexed { i, nestedPack -> - val pack = createIconPack( - iconPack = nestedPack, - indentSize = indentSize, - isExplicit = isExplicit, - ) - appendLine(pack.prependIndent(indent = " ".repeat(indentSize))) - if (i != lastIndex) appendLine() - } - append("}") - } - } - } - - private fun String.clearBlankLines(): String = this.lines().joinToString("\n") { it.takeUnless { it.isBlank() }.orEmpty() } -} diff --git a/components/generator/jvm/imagevector/api/imagevector.api b/components/generator/jvm/imagevector/api/imagevector.api deleted file mode 100644 index f27cf6cb7..000000000 --- a/components/generator/jvm/imagevector/api/imagevector.api +++ /dev/null @@ -1,92 +0,0 @@ -public final class io/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports { - public static final field Companion Lio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports$Companion; - public fun ()V - public fun (ZZZ)V - public synthetic fun (ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun component2 ()Z - public final fun component3 ()Z - public final fun copy (ZZZ)Lio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports;ZZZILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports; - public fun equals (Ljava/lang/Object;)Z - public final fun getBrush ()Z - public final fun getColor ()Z - public final fun getOffset ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports$Companion { - public final fun getReservedComposeQualifiers ()Ljava/util/List; -} - -public final class io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGenerator { - public static final field INSTANCE Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGenerator; - public final fun convert (Lio/github/composegears/valkyrie/sdk/ir/core/IrImageVector;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGeneratorConfig;)Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorSpecOutput; -} - -public final class io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGeneratorConfig { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Z - public final fun component11 ()Z - public final fun component12 ()I - public final fun component13 ()Z - public final fun component14 ()Lio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat; - public final fun component6 ()Z - public final fun component7 ()Z - public final fun component8 ()Z - public final fun component9 ()Z - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports;)Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGeneratorConfig; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGeneratorConfig;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports;ILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGeneratorConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getAddTrailingComma ()Z - public final fun getFullQualifiedImports ()Lio/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImports; - public final fun getGeneratePreview ()Z - public final fun getIconPackPackage ()Ljava/lang/String; - public final fun getIndentSize ()I - public final fun getNestedPackName ()Ljava/lang/String; - public final fun getOutputFormat ()Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat; - public final fun getPackName ()Ljava/lang/String; - public final fun getPackageName ()Ljava/lang/String; - public final fun getSuppressUnusedReceiverWarning ()Z - public final fun getUseComposeColors ()Z - public final fun getUseExplicitMode ()Z - public final fun getUseFlatPackage ()Z - public final fun getUsePathDataString ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorSpecOutput { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorSpecOutput; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorSpecOutput;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorSpecOutput; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat : java/lang/Enum { - public static final field BackingProperty Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat; - public static final field Companion Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat$Companion; - public static final field LazyProperty Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getKey ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat; - public static fun values ()[Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat; -} - -public final class io/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat$Companion { - public final fun from (Ljava/lang/String;)Lio/github/composegears/valkyrie/generator/jvm/imagevector/OutputFormat; -} - diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorFileSpec.kt b/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorFileSpec.kt deleted file mode 100644 index bf2ea11e1..000000000 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorFileSpec.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector - -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat.BackingProperty -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat.LazyProperty -import io.github.composegears.valkyrie.generator.jvm.imagevector.spec.BackingPropertySpec -import io.github.composegears.valkyrie.generator.jvm.imagevector.spec.LazyPropertySpec -import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector - -internal data class ImageVectorSpecConfig( - val iconName: String, - val iconPack: String, - val iconPackPackage: String, - val iconNestedPack: String, - val iconPackage: String, - val outputFormat: OutputFormat, - val useComposeColors: Boolean, - val generatePreview: Boolean, - val useFlatPackage: Boolean, - val useExplicitMode: Boolean, - val addTrailingComma: Boolean, - val usePathDataString: Boolean, - val indentSize: Int, - val suppressUnusedReceiverWarning: Boolean, - val fullQualifiedImports: FullQualifiedImports, -) - -internal class ImageVectorFileSpec(private val config: ImageVectorSpecConfig) { - - fun createFileFor(vector: IrImageVector): ImageVectorSpecOutput { - return when (config.outputFormat) { - BackingProperty -> { - BackingPropertySpec(config).createAsBackingProperty(vector) - } - LazyProperty -> { - LazyPropertySpec(config).createAsLazyProperty(vector) - } - } - } -} diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGenerator.kt b/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGenerator.kt deleted file mode 100644 index 08d9c4e79..000000000 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGenerator.kt +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector - -import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector - -data class ImageVectorGeneratorConfig( - val packageName: String, - val iconPackPackage: String, - val packName: String, - val nestedPackName: String, - val outputFormat: OutputFormat, - val useComposeColors: Boolean, - val generatePreview: Boolean, - val useFlatPackage: Boolean, - val useExplicitMode: Boolean, - val addTrailingComma: Boolean, - val usePathDataString: Boolean, - val indentSize: Int, - val suppressUnusedReceiverWarning: Boolean, - val fullQualifiedImports: FullQualifiedImports = FullQualifiedImports(), -) - -data class FullQualifiedImports( - val brush: Boolean = false, - val color: Boolean = false, - val offset: Boolean = false, -) { - companion object { - val reservedComposeQualifiers = listOf("Brush", "Color", "Offset") - } -} - -enum class OutputFormat(val key: String) { - BackingProperty(key = "backing_property"), - LazyProperty(key = "lazy_property"), - ; - - companion object { - fun from(key: String?) = entries.find { it.key == key } ?: BackingProperty - } -} - -data class ImageVectorSpecOutput( - val content: String, - val name: String, -) - -object ImageVectorGenerator { - - fun convert( - vector: IrImageVector, - iconName: String, - config: ImageVectorGeneratorConfig, - ): ImageVectorSpecOutput = ImageVectorFileSpec( - config = ImageVectorSpecConfig( - iconPackage = config.packageName, - iconPack = config.packName, - iconPackPackage = config.iconPackPackage, - iconName = iconName, - iconNestedPack = config.nestedPackName, - outputFormat = config.outputFormat, - useComposeColors = config.useComposeColors, - generatePreview = config.generatePreview, - useFlatPackage = config.useFlatPackage, - useExplicitMode = config.useExplicitMode, - addTrailingComma = config.addTrailingComma, - usePathDataString = config.usePathDataString, - indentSize = config.indentSize, - suppressUnusedReceiverWarning = config.suppressUnusedReceiverWarning, - fullQualifiedImports = config.fullQualifiedImports, - ), - ).createFileFor(vector) -} diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGeneratorPropertiesTest.kt b/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGeneratorPropertiesTest.kt deleted file mode 100644 index a9c2873bc..000000000 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorGeneratorPropertiesTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -class ImageVectorGeneratorPropertiesTest { - - @Test - fun `OutputFormat from function with valid key`() { - val result = OutputFormat.from("backing_property") - assertEquals(OutputFormat.BackingProperty, result) - - val resultLazy = OutputFormat.from("lazy_property") - assertEquals(OutputFormat.LazyProperty, resultLazy) - } - - @Test - fun `OutputFormat from function with invalid key`() { - val result = OutputFormat.from("invalid_key") - assertEquals(OutputFormat.BackingProperty, result) - } - - @Test - fun `OutputFormat from function with null key`() { - val result = OutputFormat.from(null) - assertEquals(OutputFormat.BackingProperty, result) - } -} diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/common/OutputFormatResource.kt b/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/common/OutputFormatResource.kt deleted file mode 100644 index 4f352dfac..000000000 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/common/OutputFormatResource.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.common - -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat.BackingProperty -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat.LazyProperty -import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourceText - -fun OutputFormat.toResourceText( - pathToBackingProperty: String, - pathToLazyProperty: String, -): String = when (this) { - BackingProperty -> getResourceText(pathToBackingProperty) - LazyProperty -> getResourceText(pathToLazyProperty) -} diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/common/TestImageVectorConfig.kt b/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/common/TestImageVectorConfig.kt deleted file mode 100644 index 321a46f5a..000000000 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/common/TestImageVectorConfig.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.common - -import io.github.composegears.valkyrie.generator.jvm.imagevector.FullQualifiedImports -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGeneratorConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat - -internal fun createConfig( - packageName: String = "io.github.composegears.valkyrie.icons", - iconPackPackage: String = packageName, - packName: String = "", - nestedPackName: String = "", - outputFormat: OutputFormat, - useComposeColors: Boolean = false, - generatePreview: Boolean = false, - useFlatPackage: Boolean = false, - useExplicitMode: Boolean = false, - addTrailingComma: Boolean = false, - usePathDataString: Boolean = false, - indentSize: Int = 4, - suppressUnusedReceiverWarning: Boolean = false, - fullQualifiedImports: FullQualifiedImports = FullQualifiedImports(), -): ImageVectorGeneratorConfig { - return ImageVectorGeneratorConfig( - packageName = packageName, - iconPackPackage = iconPackPackage, - packName = packName, - nestedPackName = nestedPackName, - outputFormat = outputFormat, - useComposeColors = useComposeColors, - generatePreview = generatePreview, - useFlatPackage = useFlatPackage, - useExplicitMode = useExplicitMode, - addTrailingComma = addTrailingComma, - usePathDataString = usePathDataString, - indentSize = indentSize, - suppressUnusedReceiverWarning = suppressUnusedReceiverWarning, - fullQualifiedImports = fullQualifiedImports, - ) -} diff --git a/components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/TypeName.kt b/components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/TypeName.kt deleted file mode 100644 index 1411c4250..000000000 --- a/components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/TypeName.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.composegears.valkyrie.generator.jvm.ext - -import com.squareup.kotlinpoet.TypeName - -fun TypeName.nullable() = copy(nullable = true) diff --git a/components/generator/kmp/imagevector/api/imagevector.api b/components/generator/kmp/imagevector/api/imagevector.api deleted file mode 100644 index e77f3333c..000000000 --- a/components/generator/kmp/imagevector/api/imagevector.api +++ /dev/null @@ -1,82 +0,0 @@ -public final class io/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports { - public fun ()V - public fun (ZZZ)V - public synthetic fun (ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun component2 ()Z - public final fun component3 ()Z - public final fun copy (ZZZ)Lio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports;ZZZILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports; - public fun equals (Ljava/lang/Object;)Z - public final fun getBrush ()Z - public final fun getColor ()Z - public final fun getOffset ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGenerator { - public static final field INSTANCE Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGenerator; - public final fun convert (Lio/github/composegears/valkyrie/sdk/ir/core/IrImageVector;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGeneratorConfig;)Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorOutput; -} - -public final class io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGeneratorConfig { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Z - public final fun component11 ()Z - public final fun component12 ()I - public final fun component13 ()Z - public final fun component14 ()Lio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat; - public final fun component6 ()Z - public final fun component7 ()Z - public final fun component8 ()Z - public final fun component9 ()Z - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports;)Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGeneratorConfig; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGeneratorConfig;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports;ILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGeneratorConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getAddTrailingComma ()Z - public final fun getFullQualifiedImports ()Lio/github/composegears/valkyrie/generator/kmp/imagevector/FullQualifiedImports; - public final fun getGeneratePreview ()Z - public final fun getIconPackPackage ()Ljava/lang/String; - public final fun getIndentSize ()I - public final fun getNestedPackName ()Ljava/lang/String; - public final fun getOutputFormat ()Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat; - public final fun getPackName ()Ljava/lang/String; - public final fun getPackageName ()Ljava/lang/String; - public final fun getSuppressUnusedReceiverWarning ()Z - public final fun getUseComposeColors ()Z - public final fun getUseExplicitMode ()Z - public final fun getUseFlatPackage ()Z - public final fun getUsePathDataString ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorOutput { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorOutput; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorOutput;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorOutput; - public fun equals (Ljava/lang/Object;)Z - public final fun getContent ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat : java/lang/Enum { - public static final field BackingProperty Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat; - public static final field LazyProperty Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getKey ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat; - public static fun values ()[Lio/github/composegears/valkyrie/generator/kmp/imagevector/OutputFormat; -} - diff --git a/components/generator/kmp/imagevector/api/imagevector.klib.api b/components/generator/kmp/imagevector/api/imagevector.klib.api deleted file mode 100644 index bf2281410..000000000 --- a/components/generator/kmp/imagevector/api/imagevector.klib.api +++ /dev/null @@ -1,111 +0,0 @@ -// Klib ABI Dump -// Targets: [wasmJs] -// Rendering settings: -// - Signature version: 2 -// - Show manifest properties: true -// - Show declarations: true - -// Library unique name: -final enum class io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat : kotlin/Enum { // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat|null[0] - enum entry BackingProperty // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat.BackingProperty|null[0] - enum entry LazyProperty // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat.LazyProperty|null[0] - - final val entries // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat.entries|#static{}entries[0] - final fun (): kotlin.enums/EnumEntries // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat.entries.|#static(){}[0] - final val key // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat.key|{}key[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat.key.|(){}[0] - - final fun valueOf(kotlin/String): io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat.valueOf|valueOf#static(kotlin.String){}[0] - final fun values(): kotlin/Array // io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat.values|values#static(){}[0] -} - -final class io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports { // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports|null[0] - constructor (kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ...) // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.|(kotlin.Boolean;kotlin.Boolean;kotlin.Boolean){}[0] - - final val brush // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.brush|{}brush[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.brush.|(){}[0] - final val color // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.color|{}color[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.color.|(){}[0] - final val offset // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.offset|{}offset[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.offset.|(){}[0] - - final fun component1(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.component1|component1(){}[0] - final fun component2(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.component2|component2(){}[0] - final fun component3(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.component3|component3(){}[0] - final fun copy(kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ...): io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.copy|copy(kotlin.Boolean;kotlin.Boolean;kotlin.Boolean){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.hashCode|hashCode(){}[0] - final fun toString(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports.toString|toString(){}[0] -} - -final class io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig { // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig|null[0] - constructor (kotlin/String, kotlin/String, kotlin/String, kotlin/String, io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat, kotlin/Boolean, kotlin/Boolean, kotlin/Boolean, kotlin/Boolean, kotlin/Boolean, kotlin/Boolean, kotlin/Int, kotlin/Boolean, io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports = ...) // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.|(kotlin.String;kotlin.String;kotlin.String;kotlin.String;io.github.composegears.valkyrie.generator.kmp.imagevector.OutputFormat;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Int;kotlin.Boolean;io.github.composegears.valkyrie.generator.kmp.imagevector.FullQualifiedImports){}[0] - - final val addTrailingComma // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.addTrailingComma|{}addTrailingComma[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.addTrailingComma.|(){}[0] - final val fullQualifiedImports // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.fullQualifiedImports|{}fullQualifiedImports[0] - final fun (): io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.fullQualifiedImports.|(){}[0] - final val generatePreview // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.generatePreview|{}generatePreview[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.generatePreview.|(){}[0] - final val iconPackPackage // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.iconPackPackage|{}iconPackPackage[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.iconPackPackage.|(){}[0] - final val indentSize // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.indentSize|{}indentSize[0] - final fun (): kotlin/Int // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.indentSize.|(){}[0] - final val nestedPackName // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.nestedPackName|{}nestedPackName[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.nestedPackName.|(){}[0] - final val outputFormat // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.outputFormat|{}outputFormat[0] - final fun (): io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.outputFormat.|(){}[0] - final val packName // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.packName|{}packName[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.packName.|(){}[0] - final val packageName // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.packageName|{}packageName[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.packageName.|(){}[0] - final val suppressUnusedReceiverWarning // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.suppressUnusedReceiverWarning|{}suppressUnusedReceiverWarning[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.suppressUnusedReceiverWarning.|(){}[0] - final val useComposeColors // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.useComposeColors|{}useComposeColors[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.useComposeColors.|(){}[0] - final val useExplicitMode // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.useExplicitMode|{}useExplicitMode[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.useExplicitMode.|(){}[0] - final val useFlatPackage // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.useFlatPackage|{}useFlatPackage[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.useFlatPackage.|(){}[0] - final val usePathDataString // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.usePathDataString|{}usePathDataString[0] - final fun (): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.usePathDataString.|(){}[0] - - final fun component1(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component1|component1(){}[0] - final fun component10(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component10|component10(){}[0] - final fun component11(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component11|component11(){}[0] - final fun component12(): kotlin/Int // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component12|component12(){}[0] - final fun component13(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component13|component13(){}[0] - final fun component14(): io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component14|component14(){}[0] - final fun component2(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component2|component2(){}[0] - final fun component3(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component3|component3(){}[0] - final fun component4(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component4|component4(){}[0] - final fun component5(): io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component5|component5(){}[0] - final fun component6(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component6|component6(){}[0] - final fun component7(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component7|component7(){}[0] - final fun component8(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component8|component8(){}[0] - final fun component9(): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.component9|component9(){}[0] - final fun copy(kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., io.github.composegears.valkyrie.generator.kmp.imagevector/OutputFormat = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Int = ..., kotlin/Boolean = ..., io.github.composegears.valkyrie.generator.kmp.imagevector/FullQualifiedImports = ...): io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.copy|copy(kotlin.String;kotlin.String;kotlin.String;kotlin.String;io.github.composegears.valkyrie.generator.kmp.imagevector.OutputFormat;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Int;kotlin.Boolean;io.github.composegears.valkyrie.generator.kmp.imagevector.FullQualifiedImports){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.hashCode|hashCode(){}[0] - final fun toString(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig.toString|toString(){}[0] -} - -final class io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput { // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput|null[0] - constructor (kotlin/String, kotlin/String) // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.|(kotlin.String;kotlin.String){}[0] - - final val content // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.content|{}content[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.content.|(){}[0] - final val name // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.name|{}name[0] - final fun (): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.name.|(){}[0] - - final fun component1(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.component1|component1(){}[0] - final fun component2(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.component2|component2(){}[0] - final fun copy(kotlin/String = ..., kotlin/String = ...): io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.copy|copy(kotlin.String;kotlin.String){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.hashCode|hashCode(){}[0] - final fun toString(): kotlin/String // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput.toString|toString(){}[0] -} - -final object io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGenerator { // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGenerator|null[0] - final fun convert(io.github.composegears.valkyrie.sdk.ir.core/IrImageVector, kotlin/String, io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGeneratorConfig): io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorOutput // io.github.composegears.valkyrie.generator.kmp.imagevector/ImageVectorGenerator.convert|convert(io.github.composegears.valkyrie.sdk.ir.core.IrImageVector;kotlin.String;io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorGeneratorConfig){}[0] -} diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorFileGenerator.kt b/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorFileGenerator.kt deleted file mode 100644 index 042f5b85d..000000000 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorFileGenerator.kt +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector - -import io.github.composegears.valkyrie.generator.kmp.imagevector.render.BackingPropertyRenderer -import io.github.composegears.valkyrie.generator.kmp.imagevector.render.LazyPropertyRenderer -import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector - -internal data class ImageVectorRenderConfig( - val iconName: String, - val iconPack: String, - val iconPackPackage: String, - val iconNestedPack: String, - val iconPackage: String, - val outputFormat: OutputFormat, - val useComposeColors: Boolean, - val generatePreview: Boolean, - val useFlatPackage: Boolean, - val useExplicitMode: Boolean, - val addTrailingComma: Boolean, - val usePathDataString: Boolean, - val indentSize: Int, - val suppressUnusedReceiverWarning: Boolean, - val fullQualifiedImports: FullQualifiedImports, -) - -internal class ImageVectorFileGenerator(private val config: ImageVectorRenderConfig) { - fun create(vector: IrImageVector): ImageVectorOutput = when (config.outputFormat) { - OutputFormat.BackingProperty -> BackingPropertyRenderer(config).render(vector) - OutputFormat.LazyProperty -> LazyPropertyRenderer(config).render(vector) - } -} diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGenerator.kt b/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGenerator.kt deleted file mode 100644 index cddee8cfa..000000000 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorGenerator.kt +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector - -import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector - -data class ImageVectorGeneratorConfig( - val packageName: String, - val iconPackPackage: String, - val packName: String, - val nestedPackName: String, - val outputFormat: OutputFormat, - val useComposeColors: Boolean, - val generatePreview: Boolean, - val useFlatPackage: Boolean, - val useExplicitMode: Boolean, - val addTrailingComma: Boolean, - val usePathDataString: Boolean, - val indentSize: Int, - val suppressUnusedReceiverWarning: Boolean, - val fullQualifiedImports: FullQualifiedImports = FullQualifiedImports(), -) - -data class FullQualifiedImports( - val brush: Boolean = false, - val color: Boolean = false, - val offset: Boolean = false, -) - -enum class OutputFormat(val key: String) { - BackingProperty(key = "backing_property"), - LazyProperty(key = "lazy_property"), -} - -data class ImageVectorOutput( - val content: String, - val name: String, -) - -object ImageVectorGenerator { - fun convert( - vector: IrImageVector, - iconName: String, - config: ImageVectorGeneratorConfig, - ): ImageVectorOutput = ImageVectorFileGenerator( - config = ImageVectorRenderConfig( - iconPackage = config.packageName, - iconPack = config.packName, - iconPackPackage = config.iconPackPackage, - iconName = iconName, - iconNestedPack = config.nestedPackName, - outputFormat = config.outputFormat, - useComposeColors = config.useComposeColors, - generatePreview = config.generatePreview, - useFlatPackage = config.useFlatPackage, - useExplicitMode = config.useExplicitMode, - addTrailingComma = config.addTrailingComma, - usePathDataString = config.usePathDataString, - indentSize = config.indentSize, - suppressUnusedReceiverWarning = config.suppressUnusedReceiverWarning, - fullQualifiedImports = config.fullQualifiedImports, - ), - ).create(vector) -} diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/BackingPropertyRenderer.kt b/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/BackingPropertyRenderer.kt deleted file mode 100644 index b41fd437f..000000000 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/BackingPropertyRenderer.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.render - -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorOutput -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorRenderConfig -import io.github.composegears.valkyrie.generator.kmp.imagevector.util.ImageVectorRenderer -import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector - -internal class BackingPropertyRenderer(private val config: ImageVectorRenderConfig) { - fun render(irVector: IrImageVector): ImageVectorOutput = ImageVectorOutput( - content = ImageVectorRenderer.renderBacking(config = config, vector = irVector), - name = config.iconName, - ) -} diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/Common.kt b/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/Common.kt deleted file mode 100644 index 096c20a38..000000000 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/Common.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.render - -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorRenderConfig - -internal fun ImageVectorRenderConfig.resolvePackageName(): String = when { - iconNestedPack.isEmpty() -> iconPackage - useFlatPackage -> iconPackage - else -> "$iconPackage.${iconNestedPack.lowercase()}" -} - -internal fun ImageVectorRenderConfig.resolveReceiverName(): String = when { - iconPack.isEmpty() -> "" - iconNestedPack.isEmpty() -> iconPack - else -> "$iconPack.$iconNestedPack" -} - -internal fun ImageVectorRenderConfig.resolveIconBuilderName(): String = when { - iconNestedPack.isEmpty() -> iconName - else -> "$iconNestedPack.$iconName" -} diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/LazyPropertyRenderer.kt b/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/LazyPropertyRenderer.kt deleted file mode 100644 index 1ec9e64a7..000000000 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/render/LazyPropertyRenderer.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.render - -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorOutput -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorRenderConfig -import io.github.composegears.valkyrie.generator.kmp.imagevector.util.ImageVectorRenderer -import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector - -internal class LazyPropertyRenderer(private val config: ImageVectorRenderConfig) { - fun render(irVector: IrImageVector): ImageVectorOutput = ImageVectorOutput( - content = ImageVectorRenderer.renderLazy(config = config, vector = irVector), - name = config.iconName, - ) -} diff --git a/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorParityJvmTest.kt b/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorParityJvmTest.kt deleted file mode 100644 index 7240745b3..000000000 --- a/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/ImageVectorParityJvmTest.kt +++ /dev/null @@ -1,158 +0,0 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector - -import assertk.assertThat -import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.kmp.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.kmp.imagevector.common.toResourceText -import io.github.composegears.valkyrie.parser.unified.ParserType -import io.github.composegears.valkyrie.parser.unified.SvgXmlParser -import io.github.composegears.valkyrie.parser.unified.ext.toIOPath -import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath -import org.junit.jupiter.api.Test - -class ImageVectorParityJvmTest { - - @Test - fun `basic generation parity`() { - assertParity( - iconPath = "imagevector/xml/ic_without_path.xml", - configTransform = { createConfig(outputFormat = it) }, - backingExpected = "imagevector/kt/backing/WithoutPath.kt", - lazyExpected = "imagevector/kt/lazy/WithoutPath.kt", - ) - } - - @Test - fun `nested pack parity`() { - assertParity( - iconPath = "imagevector/xml/ic_without_path.xml", - configTransform = { - createConfig( - packName = "ValkyrieIcons", - nestedPackName = "Colored", - outputFormat = it, - ) - }, - backingExpected = "imagevector/kt/backing/WithoutPath.pack.nested.kt", - lazyExpected = "imagevector/kt/lazy/WithoutPath.pack.nested.kt", - ) - } - - @Test - fun `trailing comma parity`() { - assertParity( - iconPath = "imagevector/xml/ic_fill_color_stroke.xml", - configTransform = { - createConfig( - packName = "ValkyrieIcons", - outputFormat = it, - addTrailingComma = true, - ) - }, - backingExpected = "imagevector/kt/backing/FillColorStroke.trailing.kt", - lazyExpected = "imagevector/kt/lazy/FillColorStroke.trailing.kt", - ) - } - - @Test - fun `pathData mode parity`() { - assertParity( - iconPath = "imagevector/xml/ic_only_path.xml", - configTransform = { - createConfig( - packName = "ValkyrieIcons", - outputFormat = it, - usePathDataString = true, - ) - }, - backingExpected = "imagevector/kt/backing/OnlyPathWithPathData.kt", - lazyExpected = "imagevector/kt/lazy/OnlyPathWithPathData.kt", - ) - } - - @Test - fun `clip path builder parity`() { - assertParity( - iconPath = "imagevector/svg/ic_clip_path_gradient.svg", - configTransform = { createConfig(outputFormat = it) }, - backingExpected = "imagevector/kt/backing/ClipPathGradient.kt", - lazyExpected = "imagevector/kt/lazy/ClipPathGradient.kt", - ) - } - - @Test - fun `preview parity`() { - assertParity( - iconPath = "imagevector/xml/ic_without_path.xml", - configTransform = { - createConfig( - outputFormat = it, - packName = "ValkyrieIcons", - nestedPackName = "Filled", - generatePreview = true, - ) - }, - backingExpected = "imagevector/kt/backing/WithoutPath.pack.nested.preview.androidx.kt", - lazyExpected = "imagevector/kt/lazy/WithoutPath.pack.nested.preview.androidx.kt", - ) - } - - @Test - fun `full qualified imports parity`() { - assertParity( - iconPath = "imagevector/xml/ic_full_qualified.xml", - configTransform = { - createConfig( - outputFormat = it, - useComposeColors = true, - fullQualifiedImports = FullQualifiedImports(brush = true), - ) - }, - backingExpected = "imagevector/kt/backing/FullQualified.brush.kt", - lazyExpected = "imagevector/kt/lazy/FullQualified.brush.kt", - ) - } - - @Test - fun `suppress unused receiver parity`() { - assertParity( - iconPath = "imagevector/xml/ic_without_path.xml", - configTransform = { - createConfig( - packName = "ValkyrieIcons", - outputFormat = it, - suppressUnusedReceiverWarning = true, - ) - }, - backingExpected = "imagevector/kt/backing/WithoutPath.pack.suppress_receiver.kt", - lazyExpected = "imagevector/kt/lazy/WithoutPath.pack.suppress_receiver.kt", - ) - } - - private fun assertParity( - iconPath: String, - configTransform: (OutputFormat) -> ImageVectorGeneratorConfig, - backingExpected: String, - lazyExpected: String, - ) { - val parserOutput = SvgXmlParser.toIrImageVector( - parser = ParserType.Jvm, - path = getResourcePath(iconPath).toIOPath(), - ) - - OutputFormat.entries.forEach { format -> - val output = ImageVectorGenerator.convert( - vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = configTransform(format), - ).content - - val expected = format.toResourceText( - pathToBackingProperty = backingExpected, - pathToLazyProperty = lazyExpected, - ) - - assertThat(output).isEqualTo(expected) - } - } -} diff --git a/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/common/OutputFormatResource.kt b/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/common/OutputFormatResource.kt deleted file mode 100644 index 8f8704be1..000000000 --- a/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/common/OutputFormatResource.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.common - -import io.github.composegears.valkyrie.generator.kmp.imagevector.OutputFormat -import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourceText - -internal fun OutputFormat.toResourceText( - pathToBackingProperty: String, - pathToLazyProperty: String, -): String = when (this) { - OutputFormat.BackingProperty -> getResourceText(pathToBackingProperty) - OutputFormat.LazyProperty -> getResourceText(pathToLazyProperty) -} diff --git a/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/common/TestImageVectorConfig.kt b/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/common/TestImageVectorConfig.kt deleted file mode 100644 index 2ce0f937e..000000000 --- a/components/generator/kmp/imagevector/src/jvmTest/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/common/TestImageVectorConfig.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.common - -import io.github.composegears.valkyrie.generator.kmp.imagevector.FullQualifiedImports -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorGeneratorConfig -import io.github.composegears.valkyrie.generator.kmp.imagevector.OutputFormat - -internal fun createConfig( - packageName: String = "io.github.composegears.valkyrie.icons", - iconPackPackage: String = packageName, - packName: String = "", - nestedPackName: String = "", - outputFormat: OutputFormat, - useComposeColors: Boolean = false, - generatePreview: Boolean = false, - useFlatPackage: Boolean = false, - useExplicitMode: Boolean = false, - addTrailingComma: Boolean = false, - usePathDataString: Boolean = false, - indentSize: Int = 4, - suppressUnusedReceiverWarning: Boolean = false, - fullQualifiedImports: FullQualifiedImports = FullQualifiedImports(), -): ImageVectorGeneratorConfig { - return ImageVectorGeneratorConfig( - packageName = packageName, - iconPackPackage = iconPackPackage, - packName = packName, - nestedPackName = nestedPackName, - outputFormat = outputFormat, - useComposeColors = useComposeColors, - generatePreview = generatePreview, - useFlatPackage = useFlatPackage, - useExplicitMode = useExplicitMode, - addTrailingComma = addTrailingComma, - usePathDataString = usePathDataString, - indentSize = indentSize, - suppressUnusedReceiverWarning = suppressUnusedReceiverWarning, - fullQualifiedImports = fullQualifiedImports, - ) -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5ad1c588..76f980d31 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -81,6 +81,7 @@ valkyrie-kmp = { id = "valkyrie.kmp" } valkyrie-compose = { id = "valkyrie.compose" } valkyrie-wasm-resources = { id = "valkyrie.wasm.resources" } valkyrie-abi = { id = "valkyrie.abi" } +valkyrie-explicit-api = { id = "valkyrie.explicit-api" } valkyrie-kover = { id = "valkyrie.kover" } [bundles] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b52fb7e71..df6a6ad76 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/sdk/core/tree/api/tree.api b/sdk/core/tree/api/tree.api new file mode 100644 index 000000000..1df54a46c --- /dev/null +++ b/sdk/core/tree/api/tree.api @@ -0,0 +1,39 @@ +public final class io/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode : io/github/composegears/valkyrie/sdk/core/tree/TreeNode { + public fun (Ljava/lang/Object;)V + public final fun addChild (Lio/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode;)V + public final fun addChild (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun addChild$default (Lio/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public fun equals (Ljava/lang/Object;)Z + public fun getChildren ()Ljava/util/List; + public fun getData ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface annotation class io/github/composegears/valkyrie/sdk/core/tree/TreeDsl : java/lang/annotation/Annotation { +} + +public final class io/github/composegears/valkyrie/sdk/core/tree/TreeDslKt { + public static final fun buildTree (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static synthetic fun buildTree$default (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static final fun child (Lio/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode;Lio/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode;)V + public static final fun child (Lio/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun child$default (Lio/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V +} + +public abstract interface class io/github/composegears/valkyrie/sdk/core/tree/TreeNode { + public abstract fun getChildren ()Ljava/util/List; + public abstract fun getData ()Ljava/lang/Object; +} + +public final class io/github/composegears/valkyrie/sdk/core/tree/TreeNodeExtensionsKt { + public static final fun contains (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/Object;)Z + public static final fun copy (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/util/List;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/util/List;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static final fun find (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/Object;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static final fun flatten (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;)Ljava/util/List; + public static final fun get (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/Object;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static final fun map (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Lkotlin/jvm/functions/Function1;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static final fun toMutableTree (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;)Lio/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode; +} + diff --git a/sdk/core/tree/api/tree.klib.api b/sdk/core/tree/api/tree.klib.api new file mode 100644 index 000000000..19cb25328 --- /dev/null +++ b/sdk/core/tree/api/tree.klib.api @@ -0,0 +1,44 @@ +// Klib ABI Dump +// Targets: [wasmJs] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +open annotation class io.github.composegears.valkyrie.sdk.core.tree/TreeDsl : kotlin/Annotation { // io.github.composegears.valkyrie.sdk.core.tree/TreeDsl|null[0] + constructor () // io.github.composegears.valkyrie.sdk.core.tree/TreeDsl.|(){}[0] +} + +abstract interface <#A: kotlin/Any?> io.github.composegears.valkyrie.sdk.core.tree/TreeNode { // io.github.composegears.valkyrie.sdk.core.tree/TreeNode|null[0] + abstract val children // io.github.composegears.valkyrie.sdk.core.tree/TreeNode.children|{}children[0] + abstract fun (): kotlin.collections/List> // io.github.composegears.valkyrie.sdk.core.tree/TreeNode.children.|(){}[0] + abstract val data // io.github.composegears.valkyrie.sdk.core.tree/TreeNode.data|{}data[0] + abstract fun (): #A // io.github.composegears.valkyrie.sdk.core.tree/TreeNode.data.|(){}[0] +} + +final class <#A: kotlin/Any?> io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode : io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A> { // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode|null[0] + constructor (#A) // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.|(1:0){}[0] + + final val children // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.children|{}children[0] + final fun (): kotlin.collections/List> // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.children.|(){}[0] + final val data // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.data|{}data[0] + final fun (): #A // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.data.|(){}[0] + + final fun addChild(#A, kotlin/Function1, kotlin/Unit> = ...) // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.addChild|addChild(1:0;kotlin.Function1,kotlin.Unit>){}[0] + final fun addChild(io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode<#A>) // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.addChild|addChild(io.github.composegears.valkyrie.sdk.core.tree.MutableTreeNode<1:0>){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode.toString|toString(){}[0] +} + +final fun <#A: kotlin/Any?, #B: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/map(kotlin/Function1<#A, #B>): io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#B> // io.github.composegears.valkyrie.sdk.core.tree/map|map@io.github.composegears.valkyrie.sdk.core.tree.TreeNode<0:0>(kotlin.Function1<0:0,0:1>){0§;1§}[0] +final fun <#A: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/child(#A, kotlin/Function1, kotlin/Unit> = ...) // io.github.composegears.valkyrie.sdk.core.tree/child|child@io.github.composegears.valkyrie.sdk.core.tree.MutableTreeNode<0:0>(0:0;kotlin.Function1,kotlin.Unit>){0§}[0] +final fun <#A: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/child(io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode<#A>) // io.github.composegears.valkyrie.sdk.core.tree/child|child@io.github.composegears.valkyrie.sdk.core.tree.MutableTreeNode<0:0>(io.github.composegears.valkyrie.sdk.core.tree.MutableTreeNode<0:0>){0§}[0] +final fun <#A: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/contains(#A): kotlin/Boolean // io.github.composegears.valkyrie.sdk.core.tree/contains|contains@io.github.composegears.valkyrie.sdk.core.tree.TreeNode<0:0>(0:0){0§}[0] +final fun <#A: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/copy(kotlin.collections/List> = ...): io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A> // io.github.composegears.valkyrie.sdk.core.tree/copy|copy@io.github.composegears.valkyrie.sdk.core.tree.TreeNode<0:0>(kotlin.collections.List>){0§}[0] +final fun <#A: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/find(#A): io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A>? // io.github.composegears.valkyrie.sdk.core.tree/find|find@io.github.composegears.valkyrie.sdk.core.tree.TreeNode<0:0>(0:0){0§}[0] +final fun <#A: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/flatten(): kotlin.collections/List> // io.github.composegears.valkyrie.sdk.core.tree/flatten|flatten@io.github.composegears.valkyrie.sdk.core.tree.TreeNode<0:0>(){0§}[0] +final fun <#A: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/get(#A): io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A> // io.github.composegears.valkyrie.sdk.core.tree/get|get@io.github.composegears.valkyrie.sdk.core.tree.TreeNode<0:0>(0:0){0§}[0] +final fun <#A: kotlin/Any?> (io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A>).io.github.composegears.valkyrie.sdk.core.tree/toMutableTree(): io.github.composegears.valkyrie.sdk.core.tree/MutableTreeNode<#A> // io.github.composegears.valkyrie.sdk.core.tree/toMutableTree|toMutableTree@io.github.composegears.valkyrie.sdk.core.tree.TreeNode<0:0>(){0§}[0] +final fun <#A: kotlin/Any?> io.github.composegears.valkyrie.sdk.core.tree/buildTree(#A, kotlin/Function1, kotlin/Unit> = ...): io.github.composegears.valkyrie.sdk.core.tree/TreeNode<#A> // io.github.composegears.valkyrie.sdk.core.tree/buildTree|buildTree(0:0;kotlin.Function1,kotlin.Unit>){0§}[0] diff --git a/sdk/core/tree/build.gradle.kts b/sdk/core/tree/build.gradle.kts new file mode 100644 index 000000000..92bd1bc55 --- /dev/null +++ b/sdk/core/tree/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.valkyrie.kmp) + alias(libs.plugins.valkyrie.abi) + alias(libs.plugins.valkyrie.explicit.api) +} + +kotlin { + sourceSets { + commonTest.dependencies { + implementation(libs.bundles.kmp.test) + } + } +} diff --git a/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode.kt b/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode.kt new file mode 100644 index 000000000..7f8ec0157 --- /dev/null +++ b/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/MutableTreeNode.kt @@ -0,0 +1,30 @@ +package io.github.composegears.valkyrie.sdk.core.tree + +public class MutableTreeNode(override val data: T) : TreeNode { + private var parent: MutableTreeNode? = null + + private val _children: MutableList> = mutableListOf() + override val children: List> + get() = _children.toList() + + public fun addChild(data: T, init: MutableTreeNode.() -> Unit = {}) { + addChild(MutableTreeNode(data).apply(init)) + } + + public fun addChild(child: MutableTreeNode) { + require(child !== this) { "A node cannot be added as a child of itself" } + require(child.parent == null) { "A node that already has a parent cannot be added again" } + child.parent = this + _children += child + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is MutableTreeNode<*>) return false + return data == other.data && _children == other._children + } + + override fun hashCode(): Int = 31 * (data?.hashCode() ?: 0) + _children.hashCode() + + override fun toString(): String = "MutableTreeNode($data, $_children)" +} diff --git a/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeDsl.kt b/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeDsl.kt new file mode 100644 index 000000000..3a731fbea --- /dev/null +++ b/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeDsl.kt @@ -0,0 +1,17 @@ +package io.github.composegears.valkyrie.sdk.core.tree + +@DslMarker +public annotation class TreeDsl + +@TreeDsl +public fun buildTree(data: T, init: MutableTreeNode.() -> Unit = {}): TreeNode = MutableTreeNode(data).apply(init) + +@TreeDsl +public fun MutableTreeNode.child(data: T, init: MutableTreeNode.() -> Unit = {}) { + addChild(data, init) +} + +@TreeDsl +public fun MutableTreeNode.child(node: MutableTreeNode) { + addChild(node) +} diff --git a/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeNode.kt b/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeNode.kt new file mode 100644 index 000000000..affca469f --- /dev/null +++ b/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeNode.kt @@ -0,0 +1,6 @@ +package io.github.composegears.valkyrie.sdk.core.tree + +public interface TreeNode { + public val data: T + public val children: List> +} diff --git a/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeNodeExtensions.kt b/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeNodeExtensions.kt new file mode 100644 index 000000000..1feddac2d --- /dev/null +++ b/sdk/core/tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeNodeExtensions.kt @@ -0,0 +1,33 @@ +package io.github.composegears.valkyrie.sdk.core.tree + +public fun TreeNode.toMutableTree(): MutableTreeNode { + val result = MutableTreeNode(data) + children.forEach { result.addChild(it.toMutableTree()) } + return result +} + +public fun TreeNode.find(data: T): TreeNode? { + if (this.data == data) return this + return children.firstNotNullOfOrNull { it.find(data) } +} + +public operator fun TreeNode.get(data: T): TreeNode = find(data) ?: error("Node '$data' not found in tree") + +public operator fun TreeNode.contains(data: T): Boolean = find(data) != null + +public fun TreeNode.copy(children: List> = this.children): TreeNode { + val result = MutableTreeNode(data) + children.forEach { result.addChild(it.toMutableTree()) } + return result +} + +public fun TreeNode.map(transform: (T) -> R): TreeNode { + fun rec(node: TreeNode): MutableTreeNode { + val result = MutableTreeNode(transform(node.data)) + node.children.forEach { result.addChild(rec(it)) } + return result + } + return rec(this) +} + +public fun TreeNode.flatten(): List> = listOf(this) + children.flatMap { it.flatten() } diff --git a/sdk/core/tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/core/tree/MutableTreeNodeTest.kt b/sdk/core/tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/core/tree/MutableTreeNodeTest.kt new file mode 100644 index 000000000..f630e596b --- /dev/null +++ b/sdk/core/tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/core/tree/MutableTreeNodeTest.kt @@ -0,0 +1,55 @@ +package io.github.composegears.valkyrie.sdk.core.tree + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse + +class MutableTreeNodeTest { + + @Test + fun `children should return a snapshot`() { + val root = MutableTreeNode("root") + root.addChild("first") + + val snapshot = root.children + + root.addChild("second") + + assertEquals(listOf("first"), snapshot.map(TreeNode::data)) + assertEquals(listOf("first", "second"), root.children.map(TreeNode::data)) + } + + @Test + fun `equals should not match foreign TreeNode implementation`() { + val node = MutableTreeNode("root") + val foreign = object : TreeNode { + override val data: String = "root" + override val children: List> = emptyList() + } + + assertFalse(node == foreign) + } + + @Test + fun `addChild should reject itself as a child`() { + val root = MutableTreeNode("root") + + assertFailsWith { + root.addChild(root) + } + } + + @Test + fun `addChild should reject a child that already has a parent`() { + val firstRoot = MutableTreeNode("first-root") + val secondRoot = MutableTreeNode("second-root") + val child = MutableTreeNode("child") + + firstRoot.addChild(child) + + assertFailsWith { + secondRoot.addChild(child) + } + } +} diff --git a/sdk/core/tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeDslTest.kt b/sdk/core/tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeDslTest.kt new file mode 100644 index 000000000..10480533a --- /dev/null +++ b/sdk/core/tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/core/tree/TreeDslTest.kt @@ -0,0 +1,23 @@ +package io.github.composegears.valkyrie.sdk.core.tree + +import kotlin.test.Test +import kotlin.test.assertEquals + +class TreeDslTest { + + @Test + fun `buildTree should create nested children`() { + val tree = buildTree("root") { + child("first") + child("second") { + child("nested") + } + } + + assertEquals("root", tree.data) + assertEquals(2, tree.children.size) + assertEquals(listOf("first", "second"), tree.children.map { it.data }) + assertEquals(1, tree.children[1].children.size) + assertEquals(listOf("nested"), tree.children[1].children.map { it.data }) + } +} diff --git a/sdk/figma/converter/build.gradle.kts b/sdk/figma/converter/build.gradle.kts index efa3ae90d..e5fa23848 100644 --- a/sdk/figma/converter/build.gradle.kts +++ b/sdk/figma/converter/build.gradle.kts @@ -16,7 +16,7 @@ kotlin { sourceSets { commonMain.dependencies { implementation(projects.components.parser.unified) - implementation(projects.components.generator.kmp.imagevector) + implementation(projects.sdk.generator.kt.imagevector.kmp) implementation(projects.sdk.ir.core) implementation(libs.kotlinx.serialization.json) } diff --git a/sdk/figma/converter/src/commonMain/kotlin/io/github/composegears/valkyrie/converter/figma/FigmaConverter.kt b/sdk/figma/converter/src/commonMain/kotlin/io/github/composegears/valkyrie/converter/figma/FigmaConverter.kt index 9ce361194..9e6effddf 100644 --- a/sdk/figma/converter/src/commonMain/kotlin/io/github/composegears/valkyrie/converter/figma/FigmaConverter.kt +++ b/sdk/figma/converter/src/commonMain/kotlin/io/github/composegears/valkyrie/converter/figma/FigmaConverter.kt @@ -4,12 +4,14 @@ package io.github.composegears.valkyrie.converter.figma import io.github.composegears.valkyrie.converter.figma.ConverterResult.Error import io.github.composegears.valkyrie.converter.figma.ConverterResult.Success -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorGenerator -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorGeneratorConfig -import io.github.composegears.valkyrie.generator.kmp.imagevector.OutputFormat import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.util.IconNameFormatter +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.ImageVectorGenerator import kotlin.js.ExperimentalJsExport import kotlin.js.JsExport import kotlinx.serialization.SerialName @@ -50,21 +52,21 @@ fun convertSvg( val output = ImageVectorGenerator.convert( vector = parseOutput.irImageVector, - iconName = parseOutput.iconName, - config = ImageVectorGeneratorConfig( + config = ImageVectorGeneratorConfig.simple( + iconName = parseOutput.iconName, packageName = packageName, - iconPackPackage = packageName, - packName = "", - nestedPackName = "", - outputFormat = resolveOutputFormat(outputFormat), - useComposeColors = useComposeColors, - generatePreview = false, - useFlatPackage = false, - addTrailingComma = addTrailingComma, - useExplicitMode = useExplicitMode, - usePathDataString = usePathDataString, - indentSize = indentSize, - suppressUnusedReceiverWarning = suppressUnusedReceiverWarning, + codeStyle = CodeStyleConfig( + useExplicitMode = useExplicitMode, + indentSize = indentSize, + ), + imageVector = ImageVectorConfig( + outputFormat = OutputFormat.from(outputFormat) + ?: error("Unsupported outputFormat '$outputFormat'"), + useComposeColors = useComposeColors, + addTrailingComma = addTrailingComma, + usePathDataString = usePathDataString, + suppressUnusedReceiverWarning = suppressUnusedReceiverWarning, + ), ), ) @@ -81,15 +83,6 @@ fun convertSvg( }.let { Json.encodeToString(it) } } -private fun resolveOutputFormat(outputFormat: String): OutputFormat = when (outputFormat) { - OutputFormat.BackingProperty.key -> OutputFormat.BackingProperty - OutputFormat.LazyProperty.key -> OutputFormat.LazyProperty - else -> throw IllegalArgumentException( - "Unsupported outputFormat '$outputFormat'. Supported values: " + - "'${OutputFormat.BackingProperty.key}', '${OutputFormat.LazyProperty.key}'.", - ) -} - /** * Normalizes a raw icon name into a Kotlin-friendly identifier. */ diff --git a/sdk/generator/kt/common/iconpack-tree/api/iconpack-tree.api b/sdk/generator/kt/common/iconpack-tree/api/iconpack-tree.api new file mode 100644 index 000000000..94856d7a3 --- /dev/null +++ b/sdk/generator/kt/common/iconpack-tree/api/iconpack-tree.api @@ -0,0 +1,8 @@ +public final class io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTreeKt { + public static final fun encode (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;)Ljava/lang/String; + public static final fun iconPackOf (Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static final fun navigate (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public static final fun pathSegments (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;)Ljava/util/List; + public static final fun toPrettyString (Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;)Ljava/lang/String; +} + diff --git a/sdk/generator/kt/common/iconpack-tree/api/iconpack-tree.klib.api b/sdk/generator/kt/common/iconpack-tree/api/iconpack-tree.klib.api new file mode 100644 index 000000000..218eb6b68 --- /dev/null +++ b/sdk/generator/kt/common/iconpack-tree/api/iconpack-tree.klib.api @@ -0,0 +1,13 @@ +// Klib ABI Dump +// Targets: [wasmJs] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final fun (io.github.composegears.valkyrie.sdk.core.tree/TreeNode).io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/encode(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/encode|encode@io.github.composegears.valkyrie.sdk.core.tree.TreeNode(){}[0] +final fun (io.github.composegears.valkyrie.sdk.core.tree/TreeNode).io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/navigate(kotlin/String): io.github.composegears.valkyrie.sdk.core.tree/TreeNode // io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/navigate|navigate@io.github.composegears.valkyrie.sdk.core.tree.TreeNode(kotlin.String){}[0] +final fun (io.github.composegears.valkyrie.sdk.core.tree/TreeNode).io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/pathSegments(): kotlin.collections/List // io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/pathSegments|pathSegments@io.github.composegears.valkyrie.sdk.core.tree.TreeNode(){}[0] +final fun (io.github.composegears.valkyrie.sdk.core.tree/TreeNode).io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/toPrettyString(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/toPrettyString|toPrettyString@io.github.composegears.valkyrie.sdk.core.tree.TreeNode(){}[0] +final fun io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/iconPackOf(kotlin/String): io.github.composegears.valkyrie.sdk.core.tree/TreeNode // io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree/iconPackOf|iconPackOf(kotlin.String){}[0] diff --git a/components/generator/core/build.gradle.kts b/sdk/generator/kt/common/iconpack-tree/build.gradle.kts similarity index 77% rename from components/generator/core/build.gradle.kts rename to sdk/generator/kt/common/iconpack-tree/build.gradle.kts index 5df5e4176..5d0b694f0 100644 --- a/components/generator/core/build.gradle.kts +++ b/sdk/generator/kt/common/iconpack-tree/build.gradle.kts @@ -2,12 +2,13 @@ plugins { alias(libs.plugins.valkyrie.kmp) alias(libs.plugins.valkyrie.abi) alias(libs.plugins.valkyrie.kover) + alias(libs.plugins.valkyrie.explicit.api) } kotlin { sourceSets { commonMain.dependencies { - implementation(projects.sdk.ir.core) + api(projects.sdk.core.tree) } commonTest.dependencies { implementation(libs.bundles.kmp.test) diff --git a/sdk/generator/kt/common/iconpack-tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTree.kt b/sdk/generator/kt/common/iconpack-tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTree.kt new file mode 100644 index 000000000..1a5a3268d --- /dev/null +++ b/sdk/generator/kt/common/iconpack-tree/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTree.kt @@ -0,0 +1,104 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree + +import io.github.composegears.valkyrie.sdk.core.tree.MutableTreeNode +import io.github.composegears.valkyrie.sdk.core.tree.TreeNode +import io.github.composegears.valkyrie.sdk.core.tree.buildTree + +public typealias IconPackTree = TreeNode + +/** + * Returns all node names from root to the deepest first child. + * E.g. ValkyrieIcons -> Material -> Rounded → ["ValkyrieIcons", "Material", "Rounded"] + */ +public fun IconPackTree.pathSegments(): List { + val segments = mutableListOf(data) + var current = this + while (current.children.isNotEmpty()) { + current = current.children.first() + segments.add(current.data) + } + return segments +} + +public fun iconPackOf(input: String): IconPackTree { + fun MutableTreeNode.getOrCreateChild(name: String): MutableTreeNode { + val existingChild = children.firstOrNull { it.data == name } + if (existingChild != null) { + return existingChild as MutableTreeNode + } + + return MutableTreeNode(name).also(::addChild) + } + + if (input.isEmpty()) { + return buildTree("") + } + + val paths = input.split(',').map { it.split('.') } + + val rootNames = paths.map { it.first() }.distinct() + if (rootNames.size != 1) { + error("Invalid icon pack structure: expected a single root, but found ${rootNames.size} roots") + } + + return MutableTreeNode(rootNames.first()).apply { + paths.forEach { path -> + var current = this + path.drop(1).forEach { segment -> + current = current.getOrCreateChild(segment) + } + } + } +} + +public fun IconPackTree.encode(): String { + if (data.isEmpty()) return "" + + val paths = mutableListOf() + + fun traverse(node: IconPackTree, parentPath: String = "") { + val currentPath = if (parentPath.isEmpty()) node.data else "$parentPath.${node.data}" + + if (node.children.isEmpty()) { + paths.add(currentPath) + } else { + node.children.forEach { child -> + traverse(child, currentPath) + } + } + } + + traverse(this) + return paths.joinToString(separator = ",") +} + +public fun IconPackTree.navigate(path: String): IconPackTree { + return path.split('.').fold(this) { node, part -> node.children.first { it.data == part } } +} + +public fun IconPackTree.toPrettyString(): String { + fun StringBuilder.buildNestedTree(prefix: String, items: List) { + items.forEachIndexed { index, iconPack -> + val isLastChild = index == items.lastIndex + val branchChar = if (isLastChild) "└── " else "├── " + val nextPrefix = prefix + if (isLastChild) "\t" else "│\t" + + appendLine() + append("$prefix$branchChar${iconPack.data}") + + if (iconPack.children.isNotEmpty()) { + buildNestedTree(prefix = nextPrefix, items = iconPack.children) + } + } + } + + return buildString { + if (data.isNotEmpty()) { + appendLine() + append(data) + append(":") + buildNestedTree(prefix = "", items = children) + appendLine() + } + } +} diff --git a/sdk/generator/kt/common/iconpack-tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTreeDslTest.kt b/sdk/generator/kt/common/iconpack-tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTreeDslTest.kt new file mode 100644 index 000000000..64daeba6c --- /dev/null +++ b/sdk/generator/kt/common/iconpack-tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTreeDslTest.kt @@ -0,0 +1,71 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree + +import assertk.assertThat +import assertk.assertions.hasSize +import assertk.assertions.isEqualTo +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import kotlin.test.Test + +class IconPackTreeDslTest { + + @Test + fun `simple iconpack`() { + val iconPack = buildTree("ValkyrieIcons") + + assertThat(iconPack.data).isEqualTo("ValkyrieIcons") + assertThat(iconPack.children).hasSize(0) + } + + @Test + fun `pack with one level of nesting`() { + val iconPack = buildTree("ValkyrieIcons") { + child("Outlined") + child("Filled") + } + + assertThat(iconPack.data).isEqualTo("ValkyrieIcons") + assertThat(iconPack.children).hasSize(2) + assertThat(iconPack.children[0].data).isEqualTo("Outlined") + assertThat(iconPack.children[1].data).isEqualTo("Filled") + } + + @Test + fun `iconpack with multiple nested levels`() { + val iconPack = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") { + child("Filled") + } + child("Sharp") { + child("Outlined") + } + } + child("Custom") { + child("Brand") + } + } + + assertThat(iconPack.data).isEqualTo("ValkyrieIcons") + assertThat(iconPack.children).hasSize(2) + + val material = iconPack.children[0] + assertThat(material.data).isEqualTo("Material") + assertThat(material.children).hasSize(2) + + val rounded = material.children[0] + assertThat(rounded.data).isEqualTo("Rounded") + assertThat(rounded.children).hasSize(1) + assertThat(rounded.children[0].data).isEqualTo("Filled") + + val sharp = material.children[1] + assertThat(sharp.data).isEqualTo("Sharp") + assertThat(sharp.children).hasSize(1) + assertThat(sharp.children[0].data).isEqualTo("Outlined") + + val custom = iconPack.children[1] + assertThat(custom.data).isEqualTo("Custom") + assertThat(custom.children).hasSize(1) + assertThat(custom.children[0].data).isEqualTo("Brand") + } +} diff --git a/sdk/generator/kt/common/iconpack-tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTreeTest.kt b/sdk/generator/kt/common/iconpack-tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTreeTest.kt new file mode 100644 index 000000000..e7996ecd7 --- /dev/null +++ b/sdk/generator/kt/common/iconpack-tree/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/tree/IconPackTreeTest.kt @@ -0,0 +1,190 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree + +import assertk.assertFailure +import assertk.assertThat +import assertk.assertions.hasSize +import assertk.assertions.isEmpty +import assertk.assertions.isEqualTo +import assertk.assertions.isInstanceOf +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.core.tree.get +import kotlin.test.Test +import kotlin.test.assertEquals + +class IconPackTreeTest { + + @Test + fun `toString should handle empty IconPack`() { + val iconPack = iconPackOf("") + val expected = "" + + assertEquals(expected, iconPack.toPrettyString()) + } + + @Test + fun `toString should return formatted tree structure`() { + val iconPack = buildTree("Root") { + child("Child1") { + child("Grandchild1") + child("Grandchild2") + } + child("Child2") + } + + val expected = """ + + Root: + ├── Child1 + │ ├── Grandchild1 + │ └── Grandchild2 + └── Child2 + + """.trimIndent() + + assertEquals(expected, iconPack.toPrettyString()) + } + + @Test + fun `toString should handle deeply nested IconPack`() { + val iconPack = buildTree("Root") { + child("Child1") { + child("Grandchild1") { + child("GreatGrandchild1") { + child("GreatGreatGrandchild1") + } + } + } + child("Child2") { + child("Grandchild2") + } + } + + val expected = """ + + Root: + ├── Child1 + │ └── Grandchild1 + │ └── GreatGrandchild1 + │ └── GreatGreatGrandchild1 + └── Child2 + └── Grandchild2 + + """.trimIndent() + + assertEquals(expected, iconPack.toPrettyString()) + } + + @Test + fun `fromString parsing empty string`() { + val input = "" + val pack = iconPackOf(input) + + assertThat(pack.data).isEqualTo("") + assertThat(pack.children).isEmpty() + + assertThat(pack.encode()).isEqualTo(input) + } + + @Test + fun `fromString parsing with multiple roots`() { + val input = "RootA.Child1,RootB.Child2" + + assertFailure { + iconPackOf(input) + }.isInstanceOf(IllegalStateException::class) + } + + @Test + fun `fromString parsing single segment`() { + val input = "Root" + val result = iconPackOf(input) + + assertThat(result.data).isEqualTo("Root") + assertThat(result.children).isEmpty() + + assertThat(result.encode()).isEqualTo(input) + } + + @Test + fun `fromString parsing L1 hierarchy`() { + val input = "Root.Child1,Root.Child2" + val result = iconPackOf(input) + + assertThat(result.data).isEqualTo("Root") + assertThat(result.children).hasSize(2) + assertThat(result["Child1"].children).isEmpty() + assertThat(result["Child2"].children).isEmpty() + + assertThat(result.encode()).isEqualTo(input) + } + + @Test + fun `fromString parsing deeper hierarchy`() { + val input = "Root.Child1,Root.Child1.GrandChild1,Root.Child2" + val result = iconPackOf(input) + + assertThat(result.data).isEqualTo("Root") + assertThat(result.children).hasSize(2) + assertThat(result["Child1"].children).hasSize(1) + assertThat(result["Child1"]["GrandChild1"].children).isEmpty() + assertThat(result["Child2"].children).isEmpty() + + assertThat(result.encode()).isEqualTo("Root.Child1.GrandChild1,Root.Child2") + } + + @Test + fun `fromString parsing multiple levels deep`() { + val input = "Root.Child1.GrandChild1.GreatGrandChild1,Root.Child2,Root.Child1" + val result = iconPackOf(input) + + assertThat(result.data).isEqualTo("Root") + assertThat(result.children).hasSize(2) + assertThat(result["GrandChild1"].data).isEqualTo("GrandChild1") + assertThat(result["GreatGrandChild1"].data).isEqualTo("GreatGrandChild1") + assertThat(result["GreatGrandChild1"].children).isEmpty() + + assertThat(result.encode()).isEqualTo("Root.Child1.GrandChild1.GreatGrandChild1,Root.Child2") + } + + @Test + fun `fromString parsing with complex nested structure`() { + val input = "Root.A.X,Root.A.Y,Root.B.Z,Root.B.W.V" + val result = iconPackOf(input) + + assertThat(result.data).isEqualTo("Root") + assertThat(result.children).hasSize(2) + assertThat(result["A"].children).hasSize(2) + assertThat(result["A"]["X"].children).isEmpty() + assertThat(result["A"]["Y"].children).isEmpty() + assertThat(result["B"].children).hasSize(2) + assertThat(result["B"]["Z"].children).isEmpty() + assertThat(result["B"]["W"].children).hasSize(1) + assertThat(result["B"]["W"]["V"].children).isEmpty() + + assertThat(result.encode()).isEqualTo(input) + } + + @Test + fun `fromString parsing with shared paths`() { + val input = "AAA.BB,AAA.CC,AAA.BB.FF,AAA.BB.FF.CC.AAA,AAA.CC.BB" + val result = iconPackOf(input) + val rootLevelBb = result.children.first { it.data == "BB" } + val cc = result.children.first { it.data == "CC" } + val ff = rootLevelBb.children.first { it.data == "FF" } + val nestedCc = ff.children.first { it.data == "CC" } + val nestedAaa = nestedCc.children.first { it.data == "AAA" } + val ccLevelBb = cc.children.first { it.data == "BB" } + + assertThat(result.data).isEqualTo("AAA") + assertThat(result.children).hasSize(2) + assertThat(rootLevelBb.children).hasSize(1) + assertThat(ff.children).hasSize(1) + assertThat(nestedCc.children).hasSize(1) + assertThat(nestedAaa.children).isEmpty() + assertThat(cc.children).hasSize(1) + assertThat(ccLevelBb.children).isEmpty() + + assertThat(result.encode()).isEqualTo("AAA.BB.FF.CC.AAA,AAA.CC.BB") + } +} diff --git a/sdk/generator/kt/common/ir/api/ir.api b/sdk/generator/kt/common/ir/api/ir.api new file mode 100644 index 000000000..4cf997cff --- /dev/null +++ b/sdk/generator/kt/common/ir/api/ir.api @@ -0,0 +1,4 @@ +public final class io/github/composegears/valkyrie/generator/kt/common/ir/PathNodeRendererKt { + public static final fun asStatement (Lio/github/composegears/valkyrie/sdk/ir/core/IrPathNode;)Ljava/lang/String; +} + diff --git a/sdk/generator/kt/common/ir/api/ir.klib.api b/sdk/generator/kt/common/ir/api/ir.klib.api new file mode 100644 index 000000000..249d8e47e --- /dev/null +++ b/sdk/generator/kt/common/ir/api/ir.klib.api @@ -0,0 +1,9 @@ +// Klib ABI Dump +// Targets: [wasmJs] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final fun (io.github.composegears.valkyrie.sdk.ir.core/IrPathNode).io.github.composegears.valkyrie.generator.kt.common.ir/asStatement(): kotlin/String // io.github.composegears.valkyrie.generator.kt.common.ir/asStatement|asStatement@io.github.composegears.valkyrie.sdk.ir.core.IrPathNode(){}[0] diff --git a/sdk/generator/kt/common/ir/build.gradle.kts b/sdk/generator/kt/common/ir/build.gradle.kts new file mode 100644 index 000000000..f5c64e650 --- /dev/null +++ b/sdk/generator/kt/common/ir/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(libs.plugins.valkyrie.kmp) + alias(libs.plugins.valkyrie.abi) + alias(libs.plugins.valkyrie.kover) + alias(libs.plugins.valkyrie.explicit.api) +} + +kotlin { + sourceSets { + commonMain.dependencies { + implementation(projects.sdk.ir.core) + implementation(projects.sdk.generator.kt.common.util) + } + } +} diff --git a/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/PathNodeRenderer.kt b/sdk/generator/kt/common/ir/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kt/common/ir/PathNodeRenderer.kt similarity index 91% rename from components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/PathNodeRenderer.kt rename to sdk/generator/kt/common/ir/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kt/common/ir/PathNodeRenderer.kt index e8a94f689..b929066e4 100644 --- a/components/generator/core/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/core/PathNodeRenderer.kt +++ b/sdk/generator/kt/common/ir/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kt/common/ir/PathNodeRenderer.kt @@ -1,9 +1,9 @@ -package io.github.composegears.valkyrie.generator.core +package io.github.composegears.valkyrie.generator.kt.common.ir +import io.github.composegears.valkyrie.sdk.generator.kt.util.formatFloat import io.github.composegears.valkyrie.sdk.ir.core.IrPathNode -import io.github.composegears.valkyrie.sdk.ir.core.toPathString -fun IrPathNode.asStatement(): String = when (this) { +public fun IrPathNode.asStatement(): String = when (this) { is IrPathNode.Close -> "close()" is IrPathNode.RelativeMoveTo -> "moveToRelative(${x.formatFloat()}, ${y.formatFloat()})" is IrPathNode.MoveTo -> "moveTo(${x.formatFloat()}, ${y.formatFloat()})" @@ -40,5 +40,3 @@ fun IrPathNode.asStatement(): String = when (this) { "arcTo(${horizontalEllipseRadius.formatFloat()}, ${verticalEllipseRadius.formatFloat()}, ${theta.formatFloat()}, isMoreThanHalf = $isMoreThanHalf, isPositiveArc = $isPositiveArc, ${arcStartX.formatFloat()}, ${arcStartY.formatFloat()})" } } - -fun List.asPathDataString(): String = toPathString() diff --git a/components/generator/jvm/poet-extensions/api/poet-extensions.api b/sdk/generator/kt/common/poet-extensions/api/poet-extensions.api similarity index 86% rename from components/generator/jvm/poet-extensions/api/poet-extensions.api rename to sdk/generator/kt/common/poet-extensions/api/poet-extensions.api index 490ae5c9d..50c65b05c 100644 --- a/components/generator/jvm/poet-extensions/api/poet-extensions.api +++ b/sdk/generator/kt/common/poet-extensions/api/poet-extensions.api @@ -1,4 +1,4 @@ -public final class io/github/composegears/valkyrie/generator/jvm/ext/CodeBlockKt { +public final class io/github/composegears/valkyrie/sdk/generator/kt/poet/CodeBlockKt { public static final fun argumentBlock (Lcom/squareup/kotlinpoet/CodeBlock$Builder;Ljava/lang/String;[Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;)V public static synthetic fun argumentBlock$default (Lcom/squareup/kotlinpoet/CodeBlock$Builder;Ljava/lang/String;[Ljava/lang/Object;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun builderBlock (Lcom/squareup/kotlinpoet/CodeBlock$Builder;Ljava/lang/String;[Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V @@ -6,7 +6,7 @@ public final class io/github/composegears/valkyrie/generator/jvm/ext/CodeBlockKt public static final fun trailingComma (Lcom/squareup/kotlinpoet/CodeBlock$Builder;)V } -public final class io/github/composegears/valkyrie/generator/jvm/ext/SpecKt { +public final class io/github/composegears/valkyrie/sdk/generator/kt/poet/SpecKt { public static final fun fileSpecBuilder (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/FileSpec; public static final fun funSpecBuilder (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/FunSpec; public static final fun getterFunSpecBuilder (Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/FunSpec; @@ -16,7 +16,7 @@ public final class io/github/composegears/valkyrie/generator/jvm/ext/SpecKt { public static final fun setIndent (Lcom/squareup/kotlinpoet/FileSpec$Builder;I)Lcom/squareup/kotlinpoet/FileSpec$Builder; } -public final class io/github/composegears/valkyrie/generator/jvm/ext/TypeNameKt { +public final class io/github/composegears/valkyrie/sdk/generator/kt/poet/TypeNameKt { public static final fun nullable (Lcom/squareup/kotlinpoet/TypeName;)Lcom/squareup/kotlinpoet/TypeName; } diff --git a/components/generator/jvm/poet-extensions/build.gradle.kts b/sdk/generator/kt/common/poet-extensions/build.gradle.kts similarity index 68% rename from components/generator/jvm/poet-extensions/build.gradle.kts rename to sdk/generator/kt/common/poet-extensions/build.gradle.kts index 56a18f44a..e6935dcb4 100644 --- a/components/generator/jvm/poet-extensions/build.gradle.kts +++ b/sdk/generator/kt/common/poet-extensions/build.gradle.kts @@ -1,12 +1,10 @@ plugins { - alias(libs.plugins.valkyrie.abi) alias(libs.plugins.valkyrie.jvm) + alias(libs.plugins.valkyrie.abi) alias(libs.plugins.valkyrie.kover) + alias(libs.plugins.valkyrie.explicit.api) } dependencies { implementation(libs.kotlinpoet) - - testImplementation(libs.bundles.test) - testRuntimeOnly(libs.junit.launcher) } diff --git a/components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/CodeBlock.kt b/sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/CodeBlock.kt similarity index 70% rename from components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/CodeBlock.kt rename to sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/CodeBlock.kt index cb6472915..224410ac6 100644 --- a/components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/CodeBlock.kt +++ b/sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/CodeBlock.kt @@ -1,9 +1,9 @@ -package io.github.composegears.valkyrie.generator.jvm.ext +package io.github.composegears.valkyrie.sdk.generator.kt.poet import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.withIndent -fun CodeBlock.Builder.argumentBlock( +public fun CodeBlock.Builder.argumentBlock( argumentFlow: String, vararg args: Any?, isNested: Boolean = false, @@ -19,7 +19,7 @@ fun CodeBlock.Builder.argumentBlock( } } -fun CodeBlock.Builder.builderBlock( +public fun CodeBlock.Builder.builderBlock( argumentFlow: String, vararg args: Any?, block: CodeBlock.Builder.() -> Unit, @@ -29,11 +29,11 @@ fun CodeBlock.Builder.builderBlock( add("}") } -fun CodeBlock.Builder.newLine() { +public fun CodeBlock.Builder.newLine() { add("\n") } -fun CodeBlock.Builder.trailingComma() { +public fun CodeBlock.Builder.trailingComma() { add(",") newLine() } diff --git a/components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/Spec.kt b/sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/Spec.kt similarity index 61% rename from components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/Spec.kt rename to sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/Spec.kt index 12ba98808..1c6b21dd8 100644 --- a/components/generator/jvm/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/ext/Spec.kt +++ b/sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/Spec.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.generator.jvm.ext +package io.github.composegears.valkyrie.sdk.generator.kt.poet import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec @@ -6,46 +6,46 @@ import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec -fun FileSpec.Builder.setIndent(indent: Int) = indent(" ".repeat(indent)) +public fun FileSpec.Builder.setIndent(indent: Int): FileSpec.Builder = indent(" ".repeat(indent)) -fun FileSpec.removeExplicitModeCode(): String = toString() +public fun FileSpec.removeExplicitModeCode(): String = toString() .replace("public ", "") -inline fun objectBuilder( +public inline fun objectBuilder( name: String, builderAction: TypeSpec.Builder.() -> Unit, -) = TypeSpec.objectBuilder(name) +): TypeSpec = TypeSpec.objectBuilder(name) .apply(builderAction) .build() -inline fun fileSpecBuilder( +public inline fun fileSpecBuilder( packageName: String, fileName: String, builderAction: FileSpec.Builder.() -> Unit, -) = FileSpec +): FileSpec = FileSpec .builder(packageName = packageName, fileName = fileName) .addKotlinDefaultImports() .apply(builderAction) .build() -inline fun funSpecBuilder( +public inline fun funSpecBuilder( name: String, builderAction: FunSpec.Builder.() -> Unit, -) = FunSpec.builder(name) +): FunSpec = FunSpec.builder(name) .apply(builderAction) .build() -inline fun getterFunSpecBuilder( +public inline fun getterFunSpecBuilder( builderAction: FunSpec.Builder.() -> Unit, -) = FunSpec.getterBuilder() +): FunSpec = FunSpec.getterBuilder() .apply(builderAction) .build() -inline fun propertySpecBuilder( +public inline fun propertySpecBuilder( name: String, type: TypeName, builderAction: PropertySpec.Builder.() -> Unit, -) = PropertySpec +): PropertySpec = PropertySpec .builder(name = name, type = type) .apply(builderAction) .build() diff --git a/sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/TypeName.kt b/sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/TypeName.kt new file mode 100644 index 000000000..8a94fd129 --- /dev/null +++ b/sdk/generator/kt/common/poet-extensions/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/poet/TypeName.kt @@ -0,0 +1,5 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.poet + +import com.squareup.kotlinpoet.TypeName + +public fun TypeName.nullable(): TypeName = copy(nullable = true) diff --git a/sdk/generator/kt/common/util/build.gradle.kts b/sdk/generator/kt/common/util/build.gradle.kts new file mode 100644 index 000000000..e5cea52f8 --- /dev/null +++ b/sdk/generator/kt/common/util/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.valkyrie.kmp) + alias(libs.plugins.valkyrie.kover) + alias(libs.plugins.valkyrie.explicit.api) +} + +kotlin { + sourceSets { + commonTest.dependencies { + implementation(libs.bundles.kmp.test) + } + } +} diff --git a/sdk/generator/kt/common/util/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/util/FloatFormatter.kt b/sdk/generator/kt/common/util/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/util/FloatFormatter.kt new file mode 100644 index 000000000..aa9bf3912 --- /dev/null +++ b/sdk/generator/kt/common/util/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/util/FloatFormatter.kt @@ -0,0 +1,5 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.util + +public fun Float.trimTrailingZero(): String = toString().removeSuffix(".0") + +public fun Float.formatFloat(): String = "${trimTrailingZero()}f" diff --git a/components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/FloatFormatterTest.kt b/sdk/generator/kt/common/util/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/util/FloatFormatterTest.kt similarity index 89% rename from components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/FloatFormatterTest.kt rename to sdk/generator/kt/common/util/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/util/FloatFormatterTest.kt index 201d1fe1c..0db74697c 100644 --- a/components/generator/core/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/core/FloatFormatterTest.kt +++ b/sdk/generator/kt/common/util/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/util/FloatFormatterTest.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.generator.core +package io.github.composegears.valkyrie.sdk.generator.kt.util import assertk.assertThat import assertk.assertions.isEqualTo diff --git a/sdk/generator/kt/iconpack/api/iconpack.api b/sdk/generator/kt/iconpack/api/iconpack.api new file mode 100644 index 000000000..7c9c2ec8a --- /dev/null +++ b/sdk/generator/kt/iconpack/api/iconpack.api @@ -0,0 +1,28 @@ +public final class io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator { + public static final field INSTANCE Lio/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator; + public final fun create (Lio/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGeneratorConfig;)Lio/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackSpecOutput; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGeneratorConfig { + public fun (Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;ZILjava/lang/String;)V + public synthetic fun (Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;ZILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getIconPackTree ()Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public final fun getIndentSize ()I + public final fun getLicense ()Ljava/lang/String; + public final fun getPackageName ()Ljava/lang/String; + public final fun getUseExplicitMode ()Z +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackSpecOutput { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackSpecOutput; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackSpecOutput;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackSpecOutput; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/sdk/generator/kt/iconpack/api/iconpack.klib.api b/sdk/generator/kt/iconpack/api/iconpack.klib.api new file mode 100644 index 000000000..6d4189beb --- /dev/null +++ b/sdk/generator/kt/iconpack/api/iconpack.klib.api @@ -0,0 +1,42 @@ +// Klib ABI Dump +// Targets: [wasmJs] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig { // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig|null[0] + constructor (kotlin/String, io.github.composegears.valkyrie.sdk.core.tree/TreeNode, kotlin/Boolean, kotlin/Int, kotlin/String? = ...) // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.|(kotlin.String;io.github.composegears.valkyrie.sdk.core.tree.TreeNode;kotlin.Boolean;kotlin.Int;kotlin.String?){}[0] + + final val iconPackTree // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.iconPackTree|{}iconPackTree[0] + final fun (): io.github.composegears.valkyrie.sdk.core.tree/TreeNode // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.iconPackTree.|(){}[0] + final val indentSize // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.indentSize|{}indentSize[0] + final fun (): kotlin/Int // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.indentSize.|(){}[0] + final val license // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.license|{}license[0] + final fun (): kotlin/String? // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.license.|(){}[0] + final val packageName // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.packageName|{}packageName[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.packageName.|(){}[0] + final val useExplicitMode // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.useExplicitMode|{}useExplicitMode[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig.useExplicitMode.|(){}[0] +} + +final class io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput { // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput|null[0] + constructor (kotlin/String, kotlin/String) // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.|(kotlin.String;kotlin.String){}[0] + + final val content // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.content|{}content[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.content.|(){}[0] + final val name // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.name|{}name[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.name.|(){}[0] + + final fun component1(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.component1|component1(){}[0] + final fun component2(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.component2|component2(){}[0] + final fun copy(kotlin/String = ..., kotlin/String = ...): io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.copy|copy(kotlin.String;kotlin.String){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput.toString|toString(){}[0] +} + +final object io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGenerator { // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGenerator|null[0] + final fun create(io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGeneratorConfig): io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackSpecOutput // io.github.composegears.valkyrie.sdk.generator.kt.iconpack/IconPackGenerator.create|create(io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGeneratorConfig){}[0] +} diff --git a/components/generator/iconpack/build.gradle.kts b/sdk/generator/kt/iconpack/build.gradle.kts similarity index 75% rename from components/generator/iconpack/build.gradle.kts rename to sdk/generator/kt/iconpack/build.gradle.kts index 69025c299..e53050d88 100644 --- a/components/generator/iconpack/build.gradle.kts +++ b/sdk/generator/kt/iconpack/build.gradle.kts @@ -3,15 +3,16 @@ plugins { alias(libs.plugins.valkyrie.abi) alias(libs.plugins.valkyrie.wasm.resources) alias(libs.plugins.valkyrie.kover) + alias(libs.plugins.valkyrie.explicit.api) } kotlin { sourceSets { commonMain.dependencies { - implementation(projects.components.generator.core) + implementation(projects.sdk.generator.kt.common.iconpackTree) } jvmMain.dependencies { - implementation(projects.components.generator.jvm.poetExtensions) + implementation(projects.sdk.generator.kt.common.poetExtensions) implementation(libs.kotlinpoet) } commonTest { diff --git a/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.kt b/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.kt new file mode 100644 index 000000000..805a0a9e6 --- /dev/null +++ b/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.kt @@ -0,0 +1,5 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack + +public expect object IconPackGenerator { + public fun create(config: IconPackGeneratorConfig): IconPackSpecOutput +} diff --git a/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGeneratorConfig.kt b/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGeneratorConfig.kt new file mode 100644 index 000000000..4eeb4da5c --- /dev/null +++ b/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGeneratorConfig.kt @@ -0,0 +1,11 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack + +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree + +public class IconPackGeneratorConfig( + public val packageName: String, + public val iconPackTree: IconPackTree, + public val useExplicitMode: Boolean, + public val indentSize: Int, + public val license: String? = null, +) diff --git a/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackSpecOutput.kt b/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackSpecOutput.kt new file mode 100644 index 000000000..4c2e580ef --- /dev/null +++ b/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackSpecOutput.kt @@ -0,0 +1,6 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack + +public data class IconPackSpecOutput( + val content: String, + val name: String, +) diff --git a/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/LicenseFormatter.kt b/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/LicenseFormatter.kt similarity index 69% rename from components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/LicenseFormatter.kt rename to sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/LicenseFormatter.kt index 716918ff3..46519f87b 100644 --- a/components/generator/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/LicenseFormatter.kt +++ b/sdk/generator/kt/iconpack/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/LicenseFormatter.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.generator.iconpack +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack internal fun String.asBlockComment(): String { if (startsWith("/*") && endsWith("*/")) return this diff --git a/components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorTest.kt b/sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGeneratorTest.kt similarity index 74% rename from components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorTest.kt rename to sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGeneratorTest.kt index 70c4fb3b0..b286a9b5f 100644 --- a/components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackGeneratorTest.kt +++ b/sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGeneratorTest.kt @@ -1,22 +1,23 @@ -package io.github.composegears.valkyrie.generator.iconpack +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.core.IconPack -import io.github.composegears.valkyrie.generator.core.iconpack +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourceText import kotlin.test.Test class IconPackGeneratorTest { private fun createConfig( - iconPack: IconPack = iconpack(name = "ValkyrieIcons"), + iconPackTree: IconPackTree = buildTree("ValkyrieIcons"), useExplicitMode: Boolean = false, indentSize: Int = 4, license: String? = null, ) = IconPackGeneratorConfig( packageName = "io.github.composegears.valkyrie.icons", - iconPack = iconPack, + iconPackTree = iconPackTree, useExplicitMode = useExplicitMode, indentSize = indentSize, license = license, @@ -42,9 +43,9 @@ class IconPackGeneratorTest { fun `generate nested pack level 2`() { val result = IconPackGenerator.create( config = createConfig( - iconPack = iconpack(name = "ValkyrieIcons") { - pack(name = "Filled") - pack(name = "Colored") + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + child("Colored") }, ), ) @@ -57,13 +58,13 @@ class IconPackGeneratorTest { fun `generate nested pack level 3`() { val result = IconPackGenerator.create( config = createConfig( - iconPack = iconpack(name = "ValkyrieIcons") { - pack(name = "Rounded") { - pack(name = "Filled") + iconPackTree = buildTree("ValkyrieIcons") { + child("Rounded") { + child("Filled") } - pack(name = "Sharp") { - pack(name = "Colored") - pack(name = "Dark") + child("Sharp") { + child("Colored") + child("Dark") } }, ), @@ -77,15 +78,15 @@ class IconPackGeneratorTest { fun `generate nested pack level 4`() { val result = IconPackGenerator.create( config = createConfig( - iconPack = iconpack(name = "ValkyrieIcons") { - pack(name = "Material") { - pack(name = "Rounded") { - pack(name = "Filled") - pack(name = "Outlined") + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") { + child("Filled") + child("Outlined") } } - pack(name = "Custom") { - pack(name = "Brand") + child("Custom") { + child("Brand") } }, ), @@ -99,9 +100,9 @@ class IconPackGeneratorTest { fun `generate nested packs explicit`() { val result = IconPackGenerator.create( config = createConfig( - iconPack = iconpack(name = "ValkyrieIcons") { - pack(name = "Filled") - pack(name = "Colored") + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + child("Colored") }, useExplicitMode = true, ), diff --git a/components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackWithIndentTest.kt b/sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackWithIndentTest.kt similarity index 85% rename from components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackWithIndentTest.kt rename to sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackWithIndentTest.kt index 8f19035a9..483241fae 100644 --- a/components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackWithIndentTest.kt +++ b/sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackWithIndentTest.kt @@ -1,26 +1,14 @@ -package io.github.composegears.valkyrie.generator.iconpack +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.core.iconpack +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourceText import kotlin.test.Test class IconPackWithIndentTest { - private fun createConfig( - useExplicitMode: Boolean = false, - indentSize: Int = 4, - ) = IconPackGeneratorConfig( - packageName = "io.github.composegears.valkyrie.icons", - iconPack = iconpack(name = "ValkyrieIcons") { - pack(name = "Filled") - pack(name = "Colored") - }, - useExplicitMode = useExplicitMode, - indentSize = indentSize, - ) - @Test fun `generate nested indent 1 packs`() { val result = IconPackGenerator.create(config = createConfig(indentSize = 1)) @@ -52,4 +40,17 @@ class IconPackWithIndentTest { assertThat(result).isEqualTo(IconPackSpecOutput(name = "ValkyrieIcons", content = expected)) } + + private fun createConfig( + useExplicitMode: Boolean = false, + indentSize: Int = 4, + ) = IconPackGeneratorConfig( + packageName = "io.github.composegears.valkyrie.icons", + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + child("Colored") + }, + useExplicitMode = useExplicitMode, + indentSize = indentSize, + ) } diff --git a/components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/LicenseFormatterTest.kt b/sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/internal/LicenseFormatterTest.kt similarity index 90% rename from components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/LicenseFormatterTest.kt rename to sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/internal/LicenseFormatterTest.kt index 07b1287e0..2ac84906b 100644 --- a/components/generator/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/generator/iconpack/LicenseFormatterTest.kt +++ b/sdk/generator/kt/iconpack/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/internal/LicenseFormatterTest.kt @@ -1,7 +1,8 @@ -package io.github.composegears.valkyrie.generator.iconpack +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack.internal import assertk.assertThat import assertk.assertions.isEqualTo +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.asBlockComment import kotlin.test.Test class LicenseFormatterTest { diff --git a/sdk/generator/kt/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.jvm.kt b/sdk/generator/kt/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.jvm.kt new file mode 100644 index 000000000..d24ff98fe --- /dev/null +++ b/sdk/generator/kt/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.jvm.kt @@ -0,0 +1,9 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack + +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.internal.IconPackFileSpec + +public actual object IconPackGenerator { + public actual fun create(config: IconPackGeneratorConfig): IconPackSpecOutput { + return IconPackFileSpec(config).createSpec() + } +} diff --git a/components/generator/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackFileSpec.kt b/sdk/generator/kt/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/internal/IconPackFileSpec.kt similarity index 51% rename from components/generator/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackFileSpec.kt rename to sdk/generator/kt/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/internal/IconPackFileSpec.kt index 267c89d34..72e4c5d36 100644 --- a/components/generator/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/generator/iconpack/IconPackFileSpec.kt +++ b/sdk/generator/kt/iconpack/src/jvmMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/internal/IconPackFileSpec.kt @@ -1,19 +1,22 @@ -package io.github.composegears.valkyrie.generator.iconpack +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack.internal import com.squareup.kotlinpoet.TypeSpec -import io.github.composegears.valkyrie.generator.core.IconPack -import io.github.composegears.valkyrie.generator.jvm.ext.fileSpecBuilder -import io.github.composegears.valkyrie.generator.jvm.ext.objectBuilder -import io.github.composegears.valkyrie.generator.jvm.ext.removeExplicitModeCode -import io.github.composegears.valkyrie.generator.jvm.ext.setIndent +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackSpecOutput +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.asBlockComment +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree +import io.github.composegears.valkyrie.sdk.generator.kt.poet.fileSpecBuilder +import io.github.composegears.valkyrie.sdk.generator.kt.poet.objectBuilder +import io.github.composegears.valkyrie.sdk.generator.kt.poet.removeExplicitModeCode +import io.github.composegears.valkyrie.sdk.generator.kt.poet.setIndent internal class IconPackFileSpec(private val config: IconPackGeneratorConfig) { fun createSpec(): IconPackSpecOutput { - val iconPackName = config.iconPack.name + val iconPackName = config.iconPackTree.data val iconPackSpec = objectBuilder(name = iconPackName) { - config.iconPack.nested.forEach { pack -> + config.iconPackTree.children.forEach { pack -> addType(createNestedObjectSpec(pack)) } } @@ -39,9 +42,9 @@ internal class IconPackFileSpec(private val config: IconPackGeneratorConfig) { ) } - private fun createNestedObjectSpec(pack: IconPack): TypeSpec { - return objectBuilder(name = pack.name) { - pack.nested.forEach { nestedPack -> + private fun createNestedObjectSpec(pack: IconPackTree): TypeSpec { + return objectBuilder(name = pack.data) { + pack.children.forEach { nestedPack -> addType(createNestedObjectSpec(nestedPack)) } } diff --git a/sdk/generator/kt/iconpack/src/wasmJsMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.wasmJs.kt b/sdk/generator/kt/iconpack/src/wasmJsMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.wasmJs.kt new file mode 100644 index 000000000..8e171dda8 --- /dev/null +++ b/sdk/generator/kt/iconpack/src/wasmJsMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/iconpack/IconPackGenerator.wasmJs.kt @@ -0,0 +1,49 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.iconpack + +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree + +public actual object IconPackGenerator { + + public actual fun create(config: IconPackGeneratorConfig): IconPackSpecOutput { + val content = buildString { + if (config.license != null) { + appendLine(config.license.asBlockComment()) + appendLine() + } + appendLine("package ${config.packageName}") + appendLine() + + val iconPack = createIconPack( + iconPackTree = config.iconPackTree, + indentSize = config.indentSize, + isExplicit = config.useExplicitMode, + ) + appendLine(iconPack) + } + + return IconPackSpecOutput( + name = config.iconPackTree.data, + content = content, + ) + } + + private fun createIconPack(iconPackTree: IconPackTree, indentSize: Int, isExplicit: Boolean): String { + val prefix = if (isExplicit) "public " else "" + if (iconPackTree.children.isEmpty()) { + return "${prefix}object ${iconPackTree.data}" + } + + val indent = " ".repeat(indentSize) + val nested = iconPackTree.children.joinToString("\n\n") { child -> + createIconPack( + iconPackTree = child, + indentSize = indentSize, + isExplicit = isExplicit, + ).indentLines(indent) + } + return "${prefix}object ${iconPackTree.data} {\n$nested\n}" + } + + // prependIndent pads blank lines with spaces; this variant leaves them empty + private fun String.indentLines(indent: String): String = lines().joinToString("\n") { if (it.isBlank()) "" else "$indent$it" } +} diff --git a/sdk/generator/kt/imagevector/common/api/common.api b/sdk/generator/kt/imagevector/common/api/common.api new file mode 100644 index 000000000..9f58d3b5c --- /dev/null +++ b/sdk/generator/kt/imagevector/common/api/common.api @@ -0,0 +1,120 @@ +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig { + public fun ()V + public fun (ZI)V + public synthetic fun (ZIILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component2 ()I + public final fun copy (ZI)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;ZIILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getIndentSize ()I + public final fun getUseExplicitMode ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports { + public static final field Companion Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports$Companion; + public fun ()V + public fun (ZZZ)V + public synthetic fun (ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component2 ()Z + public final fun component3 ()Z + public final fun copy (ZZZ)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;ZZZILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports; + public fun equals (Ljava/lang/Object;)Z + public final fun getBrush ()Z + public final fun getColor ()Z + public final fun getOffset ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports$Companion { + public final fun from (Ljava/util/Collection;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports; + public final fun getReservedComposeTypeNames ()Ljava/util/Set; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig { + public fun ()V + public fun (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat;ZZZZZZ)V + public synthetic fun (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat;ZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat; + public final fun component2 ()Z + public final fun component3 ()Z + public final fun component4 ()Z + public final fun component5 ()Z + public final fun component6 ()Z + public final fun component7 ()Z + public final fun copy (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat;ZZZZZZ)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat;ZZZZZZILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getAddTrailingComma ()Z + public final fun getGeneratePreview ()Z + public final fun getOutputFormat ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat; + public final fun getSuppressUnusedReceiverWarning ()Z + public final fun getUseComposeColors ()Z + public final fun getUseFlatPackage ()Z + public final fun getUsePathDataString ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig { + public static final field Companion Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig; + public final fun component6 ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig; + public final fun component7 ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getCodeStyle ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig; + public final fun getFullyQualifiedImports ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports; + public final fun getIconName ()Ljava/lang/String; + public final fun getIconPackPackage ()Ljava/lang/String; + public final fun getIconPackTree ()Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public final fun getImageVector ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig; + public final fun getPackageName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig$Companion { + public final fun iconPack (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig; + public static synthetic fun iconPack$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig; + public final fun simple (Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig; + public static synthetic fun simple$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig$Companion;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorConfig;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/FullyQualifiedImports;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorOutput { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun getContent ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat : java/lang/Enum { + public static final field BackingProperty Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat; + public static final field Companion Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat$Companion; + public static final field LazyProperty Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getKey ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat; + public static fun values ()[Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat$Companion { + public final fun from (Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormat; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/UtilKt { + public static final fun resolveIconBuilderName (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig;)Ljava/lang/String; + public static final fun resolvePackageName (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig;)Ljava/lang/String; +} + diff --git a/sdk/generator/kt/imagevector/common/api/common.klib.api b/sdk/generator/kt/imagevector/common/api/common.klib.api new file mode 100644 index 000000000..716cd8f2a --- /dev/null +++ b/sdk/generator/kt/imagevector/common/api/common.klib.api @@ -0,0 +1,145 @@ +// Klib ABI Dump +// Targets: [wasmJs] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final enum class io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat : kotlin/Enum { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat|null[0] + enum entry BackingProperty // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.BackingProperty|null[0] + enum entry LazyProperty // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.LazyProperty|null[0] + + final val entries // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.entries.|#static(){}[0] + final val key // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.key|{}key[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.key.|(){}[0] + + final fun valueOf(kotlin/String): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.values|values#static(){}[0] + + final object Companion { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.Companion|null[0] + final fun from(kotlin/String?): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat? // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat.Companion.from|from(kotlin.String?){}[0] + } +} + +final class io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig|null[0] + constructor (kotlin/Boolean = ..., kotlin/Int = ...) // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.|(kotlin.Boolean;kotlin.Int){}[0] + + final val indentSize // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.indentSize|{}indentSize[0] + final fun (): kotlin/Int // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.indentSize.|(){}[0] + final val useExplicitMode // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.useExplicitMode|{}useExplicitMode[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.useExplicitMode.|(){}[0] + + final fun component1(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.component1|component1(){}[0] + final fun component2(): kotlin/Int // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.component2|component2(){}[0] + final fun copy(kotlin/Boolean = ..., kotlin/Int = ...): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.copy|copy(kotlin.Boolean;kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig.toString|toString(){}[0] +} + +final class io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports|null[0] + constructor (kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ...) // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.|(kotlin.Boolean;kotlin.Boolean;kotlin.Boolean){}[0] + + final val brush // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.brush|{}brush[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.brush.|(){}[0] + final val color // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.color|{}color[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.color.|(){}[0] + final val offset // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.offset|{}offset[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.offset.|(){}[0] + + final fun component1(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.component1|component1(){}[0] + final fun component2(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.component2|component2(){}[0] + final fun component3(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.component3|component3(){}[0] + final fun copy(kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ...): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.copy|copy(kotlin.Boolean;kotlin.Boolean;kotlin.Boolean){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.toString|toString(){}[0] + + final object Companion { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.Companion|null[0] + final val reservedComposeTypeNames // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.Companion.reservedComposeTypeNames|{}reservedComposeTypeNames[0] + final fun (): kotlin.collections/Set // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.Companion.reservedComposeTypeNames.|(){}[0] + + final fun from(kotlin.collections/Collection): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports.Companion.from|from(kotlin.collections.Collection){}[0] + } +} + +final class io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig|null[0] + constructor (io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ...) // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.|(io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean){}[0] + + final val addTrailingComma // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.addTrailingComma|{}addTrailingComma[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.addTrailingComma.|(){}[0] + final val generatePreview // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.generatePreview|{}generatePreview[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.generatePreview.|(){}[0] + final val outputFormat // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.outputFormat|{}outputFormat[0] + final fun (): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.outputFormat.|(){}[0] + final val suppressUnusedReceiverWarning // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.suppressUnusedReceiverWarning|{}suppressUnusedReceiverWarning[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.suppressUnusedReceiverWarning.|(){}[0] + final val useComposeColors // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.useComposeColors|{}useComposeColors[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.useComposeColors.|(){}[0] + final val useFlatPackage // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.useFlatPackage|{}useFlatPackage[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.useFlatPackage.|(){}[0] + final val usePathDataString // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.usePathDataString|{}usePathDataString[0] + final fun (): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.usePathDataString.|(){}[0] + + final fun component1(): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.component1|component1(){}[0] + final fun component2(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.component2|component2(){}[0] + final fun component3(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.component3|component3(){}[0] + final fun component4(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.component4|component4(){}[0] + final fun component5(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.component5|component5(){}[0] + final fun component6(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.component6|component6(){}[0] + final fun component7(): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.component7|component7(){}[0] + final fun copy(io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/OutputFormat = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ..., kotlin/Boolean = ...): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.copy|copy(io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig.toString|toString(){}[0] +} + +final class io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig|null[0] + constructor (kotlin/String, kotlin/String, io.github.composegears.valkyrie.sdk.core.tree/TreeNode? = ..., kotlin/String = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports = ...) // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.|(kotlin.String;kotlin.String;io.github.composegears.valkyrie.sdk.core.tree.TreeNode?;kotlin.String;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports){}[0] + + final val codeStyle // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.codeStyle|{}codeStyle[0] + final fun (): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.codeStyle.|(){}[0] + final val fullyQualifiedImports // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.fullyQualifiedImports|{}fullyQualifiedImports[0] + final fun (): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.fullyQualifiedImports.|(){}[0] + final val iconName // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.iconName|{}iconName[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.iconName.|(){}[0] + final val iconPackPackage // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.iconPackPackage|{}iconPackPackage[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.iconPackPackage.|(){}[0] + final val iconPackTree // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.iconPackTree|{}iconPackTree[0] + final fun (): io.github.composegears.valkyrie.sdk.core.tree/TreeNode? // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.iconPackTree.|(){}[0] + final val imageVector // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.imageVector|{}imageVector[0] + final fun (): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.imageVector.|(){}[0] + final val packageName // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.packageName|{}packageName[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.packageName.|(){}[0] + + final fun component1(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.component1|component1(){}[0] + final fun component2(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.component2|component2(){}[0] + final fun component3(): io.github.composegears.valkyrie.sdk.core.tree/TreeNode? // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.component3|component3(){}[0] + final fun component4(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.component4|component4(){}[0] + final fun component5(): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.component5|component5(){}[0] + final fun component6(): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.component6|component6(){}[0] + final fun component7(): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.component7|component7(){}[0] + final fun copy(kotlin/String = ..., kotlin/String = ..., io.github.composegears.valkyrie.sdk.core.tree/TreeNode? = ..., kotlin/String = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports = ...): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.copy|copy(kotlin.String;kotlin.String;io.github.composegears.valkyrie.sdk.core.tree.TreeNode?;kotlin.String;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.toString|toString(){}[0] + + final object Companion { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.Companion|null[0] + final fun iconPack(kotlin/String, kotlin/String, kotlin/String = ..., io.github.composegears.valkyrie.sdk.core.tree/TreeNode, io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports = ...): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.Companion.iconPack|iconPack(kotlin.String;kotlin.String;kotlin.String;io.github.composegears.valkyrie.sdk.core.tree.TreeNode;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports){}[0] + final fun simple(kotlin/String, kotlin/String = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/CodeStyleConfig = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorConfig = ..., io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/FullyQualifiedImports = ...): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig.Companion.simple|simple(kotlin.String;kotlin.String;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports){}[0] + } +} + +final class io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorOutput { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorOutput|null[0] + constructor (kotlin/String, kotlin/String) // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorOutput.|(kotlin.String;kotlin.String){}[0] + + final val content // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorOutput.content|{}content[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorOutput.content.|(){}[0] + final val name // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorOutput.name|{}name[0] + final fun (): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorOutput.name.|(){}[0] +} + +final fun (io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig).io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/resolveIconBuilderName(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/resolveIconBuilderName|resolveIconBuilderName@io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig(){}[0] +final fun (io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig).io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/resolvePackageName(): kotlin/String // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/resolvePackageName|resolvePackageName@io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig(){}[0] diff --git a/sdk/generator/kt/imagevector/common/api/imagevector-common.api b/sdk/generator/kt/imagevector/common/api/imagevector-common.api new file mode 100644 index 000000000..db9f64089 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/api/imagevector-common.api @@ -0,0 +1,84 @@ +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports { + public static final field Companion Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports$Companion; + public fun ()V + public fun (ZZZ)V + public synthetic fun (ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component2 ()Z + public final fun component3 ()Z + public final fun copy (ZZZ)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports;ZZZILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports; + public fun equals (Ljava/lang/Object;)Z + public final fun getBrush ()Z + public final fun getColor ()Z + public final fun getOffset ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports$Companion { + public final fun getReservedComposeQualifiers ()Ljava/util/List; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/ImageVectorGeneratorConfig { + public fun (Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component10 ()Z + public final fun component11 ()I + public final fun component12 ()Z + public final fun component13 ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public final fun component4 ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat; + public final fun component5 ()Z + public final fun component6 ()Z + public final fun component7 ()Z + public final fun component8 ()Z + public final fun component9 ()Z + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/ImageVectorGeneratorConfig; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/ImageVectorGeneratorConfig;Ljava/lang/String;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat;ZZZZZZIZLio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/ImageVectorGeneratorConfig; + public fun equals (Ljava/lang/Object;)Z + public final fun getAddTrailingComma ()Z + public final fun getFullQualifiedImports ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/FullQualifiedImports; + public final fun getGeneratePreview ()Z + public final fun getIconPackPackage ()Ljava/lang/String; + public final fun getIconPackTree ()Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; + public final fun getIndentSize ()I + public final fun getOutputFormat ()Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat; + public final fun getPackageName ()Ljava/lang/String; + public final fun getSuppressUnusedReceiverWarning ()Z + public final fun getUseComposeColors ()Z + public final fun getUseExplicitMode ()Z + public final fun getUseFlatPackage ()Z + public final fun getUsePathDataString ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/ImageVectorOutput { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/ImageVectorOutput; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/ImageVectorOutput;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/ImageVectorOutput; + public fun equals (Ljava/lang/Object;)Z + public final fun getContent ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat : java/lang/Enum { + public static final field BackingProperty Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat; + public static final field Companion Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat$Companion; + public static final field LazyProperty Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getKey ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat; + public static fun values ()[Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat; +} + +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat$Companion { + public final fun from (Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/OutputFormat; +} diff --git a/sdk/generator/kt/imagevector/common/build.gradle.kts b/sdk/generator/kt/imagevector/common/build.gradle.kts new file mode 100644 index 000000000..28a9b6306 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + alias(libs.plugins.valkyrie.kmp) + alias(libs.plugins.valkyrie.abi) + alias(libs.plugins.valkyrie.kover) + alias(libs.plugins.valkyrie.explicit.api) +} + +kotlin { + sourceSets { + commonMain.dependencies { + api(projects.sdk.generator.kt.common.iconpackTree) + } + commonTest.dependencies { + implementation(libs.bundles.kmp.test) + } + } +} diff --git a/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig.kt b/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig.kt new file mode 100644 index 000000000..478d996d3 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig.kt @@ -0,0 +1,181 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common + +import io.github.composegears.valkyrie.sdk.core.tree.flatten +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree + +/** + * Top-level configuration for the ImageVector generator. + * + * @property iconName Simple name of the icon (e.g. `"Add"`). + * @property packageName Kotlin package for the generated file (e.g. `"com.example.icons"`). + * @property iconPackPackage Package of the icon-pack object that will own the icon. + * @property iconPackTree Optional hierarchy description used when generating icon-pack members. + * @property codeStyle Formatting and Kotlin code-style options. Defaults to [CodeStyleConfig]. + * @property imageVector ImageVector content and output options. Defaults to [ImageVectorConfig]. + * @property fullyQualifiedImports Controls which Compose types are referenced by fully-qualified + * name instead of an import. Defaults to [FullyQualifiedImports]. + */ +public data class ImageVectorGeneratorConfig( + val iconName: String, + val packageName: String, + val iconPackTree: IconPackTree? = null, + val iconPackPackage: String = packageName, + val codeStyle: CodeStyleConfig = CodeStyleConfig(), + val imageVector: ImageVectorConfig = ImageVectorConfig(), + val fullyQualifiedImports: FullyQualifiedImports = FullyQualifiedImports(), +) { + init { + if (iconPackTree != null) { + val branching = iconPackTree.flatten().firstOrNull { it.children.size > 1 } + require(branching == null) { + "iconPackTree must be a linear chain (each node has at most one child), " + + "but node '${branching!!.data}' has ${branching.children.size} children" + } + } + } + + public companion object { + /** + * Creates a config for simple (no icon-pack) conversion. + * + * [iconPackPackage] is set to [packageName] and [iconPackTree] is left `null`. + */ + public fun simple( + iconName: String, + packageName: String = "", + codeStyle: CodeStyleConfig = CodeStyleConfig(), + imageVector: ImageVectorConfig = ImageVectorConfig(), + fullyQualifiedImports: FullyQualifiedImports = FullyQualifiedImports(), + ): ImageVectorGeneratorConfig = ImageVectorGeneratorConfig( + iconName = iconName, + packageName = packageName, + iconPackPackage = packageName, + codeStyle = codeStyle, + imageVector = imageVector, + fullyQualifiedImports = fullyQualifiedImports, + ) + + /** + * Creates a config for icon-pack conversion. + * + * Uses the provided [iconPackTree] to describe icon-pack hierarchy. + */ + public fun iconPack( + iconName: String, + packageName: String, + iconPackPackage: String = packageName, + iconPackTree: IconPackTree, + codeStyle: CodeStyleConfig = CodeStyleConfig(), + imageVector: ImageVectorConfig = ImageVectorConfig(), + fullyQualifiedImports: FullyQualifiedImports = FullyQualifiedImports(), + ): ImageVectorGeneratorConfig = ImageVectorGeneratorConfig( + iconName = iconName, + packageName = packageName, + iconPackPackage = iconPackPackage, + iconPackTree = iconPackTree, + codeStyle = codeStyle, + imageVector = imageVector, + fullyQualifiedImports = fullyQualifiedImports, + ) + } +} + +/** + * Kotlin code-style options that affect how the generated source is formatted. + * + * @property useExplicitMode When `true`, explicit types are emitted where Kotlin would normally + * allow them to be inferred. + * @property indentSize Number of spaces used for each indentation level. Must be greater than `0`. + * Defaults to `4`. + */ +public data class CodeStyleConfig( + val useExplicitMode: Boolean = false, + val indentSize: Int = 4, +) { + init { + require(indentSize > 0) { "indentSize must be greater than 0" } + } +} + +/** + * Options that control the shape and content of the generated `ImageVector`. + * + * @property outputFormat Determines the property pattern used for the generated vector + * (`BackingProperty` or `LazyProperty`). + * @property useComposeColors When `true`, well-known colors are referenced via + * `androidx.compose.ui.graphics.Color` constants instead of raw ARGB literals. + * @property generatePreview When `true`, a `@Preview` composable is appended to the file. + * @property useFlatPackage When `true`, the icon is placed directly in [ImageVectorGeneratorConfig.packageName] + * without any sub-package derived from the icon-pack structure. + * @property addTrailingComma When `true`, a trailing comma is added after the last argument in + * multi-line builder calls. + * @property usePathDataString When `true`, path data is emitted as a raw string literal passed to + * `PathData` instead of individual path-builder DSL calls. + * @property suppressUnusedReceiverWarning When `true`, a `@Suppress("UnusedReceiverParameter")` + * annotation is added to the icon property getter. + */ +public data class ImageVectorConfig( + val outputFormat: OutputFormat = OutputFormat.BackingProperty, + val useComposeColors: Boolean = true, + val generatePreview: Boolean = false, + val useFlatPackage: Boolean = false, + val addTrailingComma: Boolean = false, + val usePathDataString: Boolean = false, + val suppressUnusedReceiverWarning: Boolean = false, +) + +/** + * Selects which Compose types must be referenced by their fully-qualified name rather than via a + * regular import. This is useful when the generated file is placed in a package that already + * contains a conflicting declaration. + * + * @property brush When `true`, `androidx.compose.ui.graphics.Brush` is fully qualified. + * @property color When `true`, `androidx.compose.ui.graphics.Color` is fully qualified. + * @property offset When `true`, `androidx.compose.ui.geometry.Offset` is fully qualified. + */ +public data class FullyQualifiedImports( + val brush: Boolean = false, + val color: Boolean = false, + val offset: Boolean = false, +) { + public companion object { + /** + * Simple Compose type names that can be requested as fully-qualified references when + * building a [FullyQualifiedImports] instance from external configuration. + */ + public val reservedComposeTypeNames: Set = setOf("Brush", "Color", "Offset") + + /** + * Builds a [FullyQualifiedImports] from a collection of conflicting icon names. + */ + public fun from(conflictingNames: Collection): FullyQualifiedImports { + return FullyQualifiedImports( + brush = "Brush" in conflictingNames, + color = "Color" in conflictingNames, + offset = "Offset" in conflictingNames, + ) + } + } +} + +/** + * Describes the supported property patterns for an emitted `ImageVector`. + * + * @property key Stable external identifier used by CLI and Gradle configuration. + */ +public enum class OutputFormat(public val key: String) { + /** Classic pattern using a private backing field and a public property. */ + BackingProperty(key = "backing_property"), + + /** Pattern using `by lazy { … }` for deferred initialisation. */ + LazyProperty(key = "lazy_property"), + ; + + public companion object { + /** + * Resolves an [OutputFormat] from its external [key], or returns `null` when [key] is not + * supported. + */ + public fun from(key: String?): OutputFormat? = entries.find { it.key == key } + } +} diff --git a/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorOutput.kt b/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorOutput.kt new file mode 100644 index 000000000..951b0ef98 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorOutput.kt @@ -0,0 +1,12 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common + +/** + * Result produced by the ImageVector generator. + * + * @property content Generated Kotlin source code as a plain string. + * @property name Simple name of the generated icon (matches [ImageVectorGeneratorConfig.iconName]). + */ +public class ImageVectorOutput( + public val content: String, + public val name: String, +) diff --git a/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/Util.kt b/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/Util.kt new file mode 100644 index 000000000..6f034df5f --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/Util.kt @@ -0,0 +1,34 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common + +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.pathSegments + +/** + * Resolves the output package for the generated file. + * + * Returns [packageName] when there is no icon-pack nesting or + * [ImageVectorConfig.useFlatPackage] is set. Otherwise appends each nested segment (lowercased) + * to form a sub-package, e.g. `"com.example.icons.filled"` for a `ValkyrieIcons → Filled` tree. + */ +public fun ImageVectorGeneratorConfig.resolvePackageName(): String { + val nested = iconPackTree?.pathSegments()?.drop(1).orEmpty() + return when { + nested.isEmpty() -> packageName + imageVector.useFlatPackage -> packageName + else -> "$packageName.${nested.joinToString(".") { it.lowercase() }}" + } +} + +/** + * Resolves the `name` argument for `ImageVector.Builder`. + * + * Returns [iconName] when there is no nesting or the tree has no children (root-only). + * Otherwise prefixes with the deepest sub-pack segment, + * e.g. `"Rounded.Add"` for a `ValkyrieIcons → Material → Rounded` tree with icon `Add`. + */ +public fun ImageVectorGeneratorConfig.resolveIconBuilderName(): String { + val segments = iconPackTree?.pathSegments().orEmpty() + return when { + segments.size <= 1 -> iconName + else -> "${segments.last()}.$iconName" + } +} diff --git a/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfigTest.kt b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfigTest.kt new file mode 100644 index 000000000..28b2b3da9 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/CodeStyleConfigTest.kt @@ -0,0 +1,33 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CodeStyleConfigTest { + + @Test + fun `accepts positive indent size`() { + val config = CodeStyleConfig(indentSize = 2) + + assertEquals(2, config.indentSize) + } + + @Test + fun `rejects zero indent size`() { + val error = assertFailsWith { + CodeStyleConfig(indentSize = 0) + } + + assertEquals("indentSize must be greater than 0", error.message) + } + + @Test + fun `rejects negative indent size`() { + val error = assertFailsWith { + CodeStyleConfig(indentSize = -1) + } + + assertEquals("indentSize must be greater than 0", error.message) + } +} diff --git a/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfigTest.kt b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfigTest.kt new file mode 100644 index 000000000..23d2d6806 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfigTest.kt @@ -0,0 +1,83 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common + +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.shared.DEFAULT_PACKAGE +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class ImageVectorGeneratorConfigTest { + + @Test + fun `branching iconPackTree is rejected`() { + val error = assertFailsWith { + ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + child("Outlined") + }, + ) + } + + assertEquals( + "iconPackTree must be a linear chain (each node has at most one child), " + + "but node 'ValkyrieIcons' has 2 children", + error.message, + ) + } + + @Test + fun `deep branching is rejected at the correct node`() { + val error = assertFailsWith { + ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Filled") + child("Outlined") + } + }, + ) + } + + assertEquals( + "iconPackTree must be a linear chain (each node has at most one child), " + + "but node 'Material' has 2 children", + error.message, + ) + } + + @Test + fun `linear iconPackTree is accepted`() { + ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + ) + } + + @Test + fun `5-level linear chain is accepted`() { + ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Level1") { + child("Level2") { + child("Level3") { + child("Level4") { + child("Level5") + } + } + } + } + }, + ) + } +} diff --git a/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormatTest.kt b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormatTest.kt new file mode 100644 index 000000000..7142b099a --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/OutputFormatTest.kt @@ -0,0 +1,23 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class OutputFormatTest { + + @Test + fun `from returns output format for valid key`() { + val result = OutputFormat.from("backing_property") + assertEquals(OutputFormat.BackingProperty, result) + + val resultLazy = OutputFormat.from("lazy_property") + assertEquals(OutputFormat.LazyProperty, resultLazy) + } + + @Test + fun `fromOrNull returns null for unsupported keys`() { + assertNull(OutputFormat.from("invalid_key")) + assertNull(OutputFormat.from(null)) + } +} diff --git a/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ResolveIconBuilderNameTest.kt b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ResolveIconBuilderNameTest.kt new file mode 100644 index 000000000..a29143cf3 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ResolveIconBuilderNameTest.kt @@ -0,0 +1,56 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common + +import assertk.assertThat +import assertk.assertions.isEqualTo +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.shared.DEFAULT_PACKAGE +import kotlin.test.Test + +class ResolveIconBuilderNameTest { + + @Test + fun `no iconPackTree returns iconName`() { + val config = ImageVectorGeneratorConfig.simple( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + ) + assertThat(config.resolveIconBuilderName()).isEqualTo("Add") + } + + @Test + fun `root-only returns iconName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + ) + assertThat(config.resolveIconBuilderName()).isEqualTo("Add") + } + + @Test + fun `one nested level returns child dot iconName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + ) + assertThat(config.resolveIconBuilderName()).isEqualTo("Filled.Add") + } + + @Test + fun `two nested levels returns deepest child dot iconName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") + } + }, + ) + assertThat(config.resolveIconBuilderName()).isEqualTo("Rounded.Add") + } +} diff --git a/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ResolvePackageNameTest.kt b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ResolvePackageNameTest.kt new file mode 100644 index 000000000..97f42bff0 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ResolvePackageNameTest.kt @@ -0,0 +1,83 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common + +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.shared.DEFAULT_PACKAGE +import kotlin.test.Test +import kotlin.test.assertEquals + +class ResolvePackageNameTest { + + @Test + fun `no iconPackTree returns packageName`() { + val config = ImageVectorGeneratorConfig.simple( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + ) + assertEquals(DEFAULT_PACKAGE, config.resolvePackageName()) + } + + @Test + fun `root-only tree returns packageName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + ) + assertEquals(DEFAULT_PACKAGE, config.resolvePackageName()) + } + + @Test + fun `one nested level appends lowercase segment`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + ) + assertEquals("${DEFAULT_PACKAGE}.filled", config.resolvePackageName()) + } + + @Test + fun `two nested levels appends both lowercase segments`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") + } + }, + ) + assertEquals("${DEFAULT_PACKAGE}.material.rounded", config.resolvePackageName()) + } + + @Test + fun `useFlatPackage always returns packageName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + imageVector = ImageVectorConfig(useFlatPackage = true), + ) + assertEquals(DEFAULT_PACKAGE, config.resolvePackageName()) + } + + @Test + fun `useFlatPackage with deep nesting still returns packageName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") + } + }, + imageVector = ImageVectorConfig(useFlatPackage = true), + ) + assertEquals(DEFAULT_PACKAGE, config.resolvePackageName()) + } +} diff --git a/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/shared/TestFixtures.kt b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/shared/TestFixtures.kt new file mode 100644 index 000000000..2d685ada5 --- /dev/null +++ b/sdk/generator/kt/imagevector/common/src/commonTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/shared/TestFixtures.kt @@ -0,0 +1,3 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.shared + +internal const val DEFAULT_PACKAGE = "com.example" diff --git a/sdk/generator/kt/imagevector/jvm/api/jvm.api b/sdk/generator/kt/imagevector/jvm/api/jvm.api new file mode 100644 index 000000000..f5beb19d8 --- /dev/null +++ b/sdk/generator/kt/imagevector/jvm/api/jvm.api @@ -0,0 +1,5 @@ +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorGenerator { + public static final field INSTANCE Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorGenerator; + public final fun convert (Lio/github/composegears/valkyrie/sdk/ir/core/IrImageVector;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorOutput; +} + diff --git a/components/generator/jvm/imagevector/build.gradle.kts b/sdk/generator/kt/imagevector/jvm/build.gradle.kts similarity index 59% rename from components/generator/jvm/imagevector/build.gradle.kts rename to sdk/generator/kt/imagevector/jvm/build.gradle.kts index 92ee29186..661854a75 100644 --- a/components/generator/jvm/imagevector/build.gradle.kts +++ b/sdk/generator/kt/imagevector/jvm/build.gradle.kts @@ -1,7 +1,10 @@ +import io.github.composegears.valkyrie.extension.because + plugins { alias(libs.plugins.valkyrie.abi) alias(libs.plugins.valkyrie.jvm) alias(libs.plugins.valkyrie.kover) + alias(libs.plugins.valkyrie.explicit.api) } sourceSets { @@ -18,14 +21,17 @@ kotlin { dependencies { implementation(projects.sdk.core.extensions) + implementation(projects.sdk.generator.kt.common.ir) + implementation(projects.sdk.generator.kt.common.poetExtensions) + implementation(projects.sdk.generator.kt.common.util) + api(projects.sdk.generator.kt.imagevector.common) because "Shared config model" implementation(projects.sdk.ir.core) - api(projects.components.generator.jvm.poetExtensions) - api(projects.components.generator.core) implementation(libs.kotlinpoet) testImplementation(projects.components.parser.unified) testImplementation(projects.sdk.test.resourceLoader) + testImplementation(projects.sdk.generator.kt.imagevector.testFixtures) testImplementation(libs.bundles.test) testRuntimeOnly(libs.junit.launcher) } diff --git a/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorFileSpec.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorFileSpec.kt new file mode 100644 index 000000000..29a641344 --- /dev/null +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorFileSpec.kt @@ -0,0 +1,18 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm + +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorOutput +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.spec.BackingPropertySpec +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.spec.LazyPropertySpec +import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector + +internal class ImageVectorFileSpec(private val config: ImageVectorGeneratorConfig) { + + fun createFileFor(vector: IrImageVector): ImageVectorOutput { + return when (config.imageVector.outputFormat) { + OutputFormat.BackingProperty -> BackingPropertySpec(config).createAsBackingProperty(vector) + OutputFormat.LazyProperty -> LazyPropertySpec(config).createAsLazyProperty(vector) + } + } +} diff --git a/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorGenerator.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorGenerator.kt new file mode 100644 index 000000000..75f904838 --- /dev/null +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorGenerator.kt @@ -0,0 +1,13 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm + +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorOutput +import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector + +public object ImageVectorGenerator { + + public fun convert( + vector: IrImageVector, + config: ImageVectorGeneratorConfig, + ): ImageVectorOutput = ImageVectorFileSpec(config).createFileFor(vector) +} diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/BackingPropertySpec.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/BackingPropertySpec.kt similarity index 65% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/BackingPropertySpec.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/BackingPropertySpec.kt index 03d5a1615..f622f7560 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/BackingPropertySpec.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/BackingPropertySpec.kt @@ -1,24 +1,25 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.spec +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.spec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.buildCodeBlock -import io.github.composegears.valkyrie.generator.jvm.ext.fileSpecBuilder -import io.github.composegears.valkyrie.generator.jvm.ext.getterFunSpecBuilder -import io.github.composegears.valkyrie.generator.jvm.ext.propertySpecBuilder -import io.github.composegears.valkyrie.generator.jvm.ext.removeExplicitModeCode -import io.github.composegears.valkyrie.generator.jvm.ext.setIndent -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecOutput -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.ClassNames -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.backingPropertyName -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.backingPropertySpec +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorOutput +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.resolvePackageName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.ClassNames +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.backingPropertyName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.backingPropertySpec +import io.github.composegears.valkyrie.sdk.generator.kt.poet.fileSpecBuilder +import io.github.composegears.valkyrie.sdk.generator.kt.poet.getterFunSpecBuilder +import io.github.composegears.valkyrie.sdk.generator.kt.poet.propertySpecBuilder +import io.github.composegears.valkyrie.sdk.generator.kt.poet.removeExplicitModeCode +import io.github.composegears.valkyrie.sdk.generator.kt.poet.setIndent import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector -internal class BackingPropertySpec(private val config: ImageVectorSpecConfig) { +internal class BackingPropertySpec(private val config: ImageVectorGeneratorConfig) { - fun createAsBackingProperty(irVector: IrImageVector): ImageVectorSpecOutput = with(config) { + fun createAsBackingProperty(irVector: IrImageVector): ImageVectorOutput = with(config) { val backingProperty = backingPropertySpec( name = iconName.backingPropertyName(), type = ClassNames.ImageVector, @@ -43,19 +44,19 @@ internal class BackingPropertySpec(private val config: ImageVectorSpecConfig) { iconPackClassName = iconPackClassName, packageName = packageName, ) - setIndent(indentSize) + setIndent(codeStyle.indentSize) } - return ImageVectorSpecOutput( + return ImageVectorOutput( content = when { - useExplicitMode -> fileSpec.toString() + codeStyle.useExplicitMode -> fileSpec.toString() else -> fileSpec.removeExplicitModeCode() }, name = fileSpec.name, ) } - context(config: ImageVectorSpecConfig) + context(config: ImageVectorGeneratorConfig) private fun iconProperty( irVector: IrImageVector, iconPackClassName: ClassName?, @@ -66,7 +67,7 @@ internal class BackingPropertySpec(private val config: ImageVectorSpecConfig) { addSuppressUnusedReceiverAnnotation(iconPackClassName) } - context(config: ImageVectorSpecConfig) + context(config: ImageVectorGeneratorConfig) private fun iconFun(irVector: IrImageVector, backingProperty: PropertySpec): FunSpec { return getterFunSpecBuilder { addCode( diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/Common.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/Common.kt similarity index 54% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/Common.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/Common.kt index 4d1bd7e11..a8952c601 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/Common.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/Common.kt @@ -1,52 +1,28 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.spec +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.spec import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.PropertySpec -import io.github.composegears.valkyrie.generator.core.asStatement -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.ClassNames -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.addGroup -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.addPath -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.addPathData -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.iconPreviewSpec -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.iconPreviewSpecForNestedPack -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.imageVectorBuilderSpecs +import io.github.composegears.valkyrie.generator.kt.common.ir.asStatement +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.resolveIconBuilderName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.ClassNames +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.addGroup +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.addPath +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.addPathData +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.iconPreviewSpec +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.iconPreviewSpecForNestedPack +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.imageVectorBuilderSpecs import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector import io.github.composegears.valkyrie.sdk.ir.core.IrVectorNode -internal fun ImageVectorSpecConfig.resolvePackageName(): String = when { - iconNestedPack.isEmpty() -> iconPackage - else -> { - if (useFlatPackage) { - iconPackage - } else { - "$iconPackage.${iconNestedPack.lowercase()}" - } - } -} - -internal fun ImageVectorSpecConfig.resolveIconPackClassName() = when { - iconPack.isEmpty() -> null - else -> { - if (iconNestedPack.isEmpty()) { - ClassName(iconPackPackage, iconPack) - } else { - ClassName(iconPackPackage, iconPack).nestedClass(iconNestedPack) - } - } -} - -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun CodeBlock.Builder.addImageVectorBlock(irVector: IrImageVector) { add( imageVectorBuilderSpecs( - iconName = when { - config.iconNestedPack.isEmpty() -> config.iconName - else -> "${config.iconNestedPack}.${config.iconName}" - }, + iconName = config.resolveIconBuilderName(), irVector = irVector, path = { irVector.nodes.forEach { node -> @@ -57,12 +33,12 @@ internal fun CodeBlock.Builder.addImageVectorBlock(irVector: IrImageVector) { ) } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun FileSpec.Builder.addPreview( iconPackClassName: ClassName?, packageName: String, ) { - if (config.generatePreview) { + if (config.imageVector.generatePreview) { addFunction( funSpec = when { iconPackClassName != null -> iconPreviewSpecForNestedPack(iconPackClassName = iconPackClassName) @@ -72,9 +48,9 @@ internal fun FileSpec.Builder.addPreview( } } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun PropertySpec.Builder.addSuppressUnusedReceiverAnnotation(iconPackClassName: ClassName?) { - if (iconPackClassName != null && config.suppressUnusedReceiverWarning) { + if (iconPackClassName != null && config.imageVector.suppressUnusedReceiverWarning) { addAnnotation( AnnotationSpec.builder(ClassNames.Suppress) .addMember("%S", "UnusedReceiverParameter") @@ -83,7 +59,7 @@ internal fun PropertySpec.Builder.addSuppressUnusedReceiverAnnotation(iconPackCl } } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.addVectorNode(irVectorNode: IrVectorNode) { when (irVectorNode) { is IrVectorNode.IrGroup -> addGroup( @@ -95,7 +71,7 @@ private fun CodeBlock.Builder.addVectorNode(irVectorNode: IrVectorNode) { }, ) is IrVectorNode.IrPath -> { - if (config.usePathDataString) { + if (config.imageVector.usePathDataString) { addPathData(path = irVectorNode) } else { addPath( diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/LazyPropertySpec.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/LazyPropertySpec.kt similarity index 61% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/LazyPropertySpec.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/LazyPropertySpec.kt index a745753a2..dd762b205 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/spec/LazyPropertySpec.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/LazyPropertySpec.kt @@ -1,21 +1,22 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.spec +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.spec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.buildCodeBlock -import io.github.composegears.valkyrie.generator.jvm.ext.fileSpecBuilder -import io.github.composegears.valkyrie.generator.jvm.ext.propertySpecBuilder -import io.github.composegears.valkyrie.generator.jvm.ext.removeExplicitModeCode -import io.github.composegears.valkyrie.generator.jvm.ext.setIndent -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecOutput -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.ClassNames +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorOutput +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.resolvePackageName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.ClassNames +import io.github.composegears.valkyrie.sdk.generator.kt.poet.fileSpecBuilder +import io.github.composegears.valkyrie.sdk.generator.kt.poet.propertySpecBuilder +import io.github.composegears.valkyrie.sdk.generator.kt.poet.removeExplicitModeCode +import io.github.composegears.valkyrie.sdk.generator.kt.poet.setIndent import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector -internal class LazyPropertySpec(private val config: ImageVectorSpecConfig) { +internal class LazyPropertySpec(private val config: ImageVectorGeneratorConfig) { - fun createAsLazyProperty(irVector: IrImageVector): ImageVectorSpecOutput = with(config) { + fun createAsLazyProperty(irVector: IrImageVector): ImageVectorOutput = with(config) { val iconPackClassName = resolveIconPackClassName() val packageName = resolvePackageName() @@ -33,19 +34,19 @@ internal class LazyPropertySpec(private val config: ImageVectorSpecConfig) { iconPackClassName = iconPackClassName, packageName = packageName, ) - setIndent(indentSize) + setIndent(codeStyle.indentSize) } - return ImageVectorSpecOutput( + return ImageVectorOutput( content = when { - useExplicitMode -> fileSpec.toString() + codeStyle.useExplicitMode -> fileSpec.toString() else -> fileSpec.removeExplicitModeCode() }, name = fileSpec.name, ) } - context(config: ImageVectorSpecConfig) + context(config: ImageVectorGeneratorConfig) private fun iconProperty( irVector: IrImageVector, iconPackClassName: ClassName?, diff --git a/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/ResolveIconPackClassName.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/ResolveIconPackClassName.kt new file mode 100644 index 000000000..f90a87028 --- /dev/null +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/spec/ResolveIconPackClassName.kt @@ -0,0 +1,13 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.spec + +import com.squareup.kotlinpoet.ClassName +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.pathSegments +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig + +internal fun ImageVectorGeneratorConfig.resolveIconPackClassName(): ClassName? { + val tree = iconPackTree ?: return null + val segments = tree.pathSegments() + return segments.drop(1).fold(ClassName(iconPackPackage, segments.first())) { cls, name -> + cls.nestedClass(name) + } +} diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/BackingPropertySpec.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/BackingPropertySpec.kt similarity index 71% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/BackingPropertySpec.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/BackingPropertySpec.kt index 47eb4ca66..0f64b07aa 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/BackingPropertySpec.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/BackingPropertySpec.kt @@ -1,10 +1,10 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeName -import io.github.composegears.valkyrie.generator.jvm.ext.nullable -import io.github.composegears.valkyrie.generator.jvm.ext.propertySpecBuilder +import io.github.composegears.valkyrie.sdk.generator.kt.poet.nullable +import io.github.composegears.valkyrie.sdk.generator.kt.poet.propertySpecBuilder internal fun String.backingPropertyName() = "_$this" diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/GroupBuilder.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/GroupBuilder.kt similarity index 73% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/GroupBuilder.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/GroupBuilder.kt index 6154697fe..e0f39ef1d 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/GroupBuilder.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/GroupBuilder.kt @@ -1,28 +1,28 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent -import io.github.composegears.valkyrie.generator.core.asPathDataString -import io.github.composegears.valkyrie.generator.core.asStatement -import io.github.composegears.valkyrie.generator.core.formatFloat -import io.github.composegears.valkyrie.generator.jvm.ext.builderBlock -import io.github.composegears.valkyrie.generator.jvm.ext.newLine -import io.github.composegears.valkyrie.generator.jvm.ext.trailingComma -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.ClipPathParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.NameParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.PivotXParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.PivotYParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.RotateParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.ScaleXParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.ScaleYParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.TranslationXParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.GroupParams.TranslationYParam +import io.github.composegears.valkyrie.generator.kt.common.ir.asStatement +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.ClipPathParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.NameParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.PivotXParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.PivotYParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.RotateParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.ScaleXParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.ScaleYParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.TranslationXParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.GroupParams.TranslationYParam +import io.github.composegears.valkyrie.sdk.generator.kt.poet.builderBlock +import io.github.composegears.valkyrie.sdk.generator.kt.poet.newLine +import io.github.composegears.valkyrie.sdk.generator.kt.poet.trailingComma +import io.github.composegears.valkyrie.sdk.generator.kt.util.formatFloat import io.github.composegears.valkyrie.sdk.ir.core.IrPathNode import io.github.composegears.valkyrie.sdk.ir.core.IrVectorNode +import io.github.composegears.valkyrie.sdk.ir.core.toPathString -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun CodeBlock.Builder.addGroup( path: IrVectorNode.IrGroup, groupBody: CodeBlock.Builder.() -> Unit, @@ -54,7 +54,7 @@ internal fun CodeBlock.Builder.addGroup( groupParams.forEachIndexed { index, param -> fillGroupArgs(param) if (index == groupParams.lastIndex) { - if (config.addTrailingComma) { + if (config.imageVector.addTrailingComma) { trailingComma() } else { newLine() @@ -74,7 +74,7 @@ internal fun CodeBlock.Builder.addGroup( } } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.fillGroupArgs(param: GroupParams) { when (param) { is NameParam -> nameArg(param) @@ -121,12 +121,12 @@ private fun CodeBlock.Builder.translationYArg(param: TranslationYParam) { add("translationY = %L", param.translationY.formatFloat()) } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.clipPathArg(param: ClipPathParam) { newLine() withIndent { - if (config.usePathDataString) { - val pathData = param.clipPath.asPathDataString() + if (config.imageVector.usePathDataString) { + val pathData = param.clipPath.toPathString() add("clipPathData = %M(%S)", MemberNames.AddPathNodes, pathData) } else { builderBlock("clipPathData = %M {", MemberNames.PathData) { diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/ImageVectorBuilderSpec.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/ImageVectorBuilderSpec.kt similarity index 67% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/ImageVectorBuilderSpec.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/ImageVectorBuilderSpec.kt index 11c4dde76..eaab75d5b 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/ImageVectorBuilderSpec.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/ImageVectorBuilderSpec.kt @@ -1,16 +1,16 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent -import io.github.composegears.valkyrie.generator.core.formatFloat -import io.github.composegears.valkyrie.generator.core.trimTrailingZero -import io.github.composegears.valkyrie.generator.jvm.ext.newLine -import io.github.composegears.valkyrie.generator.jvm.ext.trailingComma -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.poet.newLine +import io.github.composegears.valkyrie.sdk.generator.kt.poet.trailingComma +import io.github.composegears.valkyrie.sdk.generator.kt.util.formatFloat +import io.github.composegears.valkyrie.sdk.generator.kt.util.trimTrailingZero import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun imageVectorBuilderSpecs( iconName: String, irVector: IrImageVector, @@ -27,7 +27,7 @@ internal fun imageVectorBuilderSpecs( trailingComma() add("autoMirror = true") } - if (config.addTrailingComma) { + if (config.imageVector.addTrailingComma) { trailingComma() } else { newLine() diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/Names.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/Names.kt similarity index 97% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/Names.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/Names.kt index 215f51bc4..c8c8f74ec 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/Names.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/Names.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.MemberName diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/PathBuilder.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/PathBuilder.kt similarity index 80% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/PathBuilder.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/PathBuilder.kt index cbef45659..4e7d85a29 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/PathBuilder.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/PathBuilder.kt @@ -1,25 +1,24 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent -import io.github.composegears.valkyrie.generator.core.asPathDataString -import io.github.composegears.valkyrie.generator.core.formatFloat -import io.github.composegears.valkyrie.generator.jvm.ext.argumentBlock -import io.github.composegears.valkyrie.generator.jvm.ext.newLine -import io.github.composegears.valkyrie.generator.jvm.ext.trailingComma -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.FillAlphaParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.FillParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.NameParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.PathFillTypeParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.StrokeAlphaParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.StrokeColorParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.StrokeLineCapParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.StrokeLineJoinParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.StrokeLineMiterParam -import io.github.composegears.valkyrie.generator.jvm.imagevector.util.PathParams.StrokeLineWidthParam import io.github.composegears.valkyrie.sdk.core.extensions.safeAs +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.FillAlphaParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.FillParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.NameParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.PathFillTypeParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.StrokeAlphaParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.StrokeColorParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.StrokeLineCapParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.StrokeLineJoinParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.StrokeLineMiterParam +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util.PathParams.StrokeLineWidthParam +import io.github.composegears.valkyrie.sdk.generator.kt.poet.argumentBlock +import io.github.composegears.valkyrie.sdk.generator.kt.poet.newLine +import io.github.composegears.valkyrie.sdk.generator.kt.poet.trailingComma +import io.github.composegears.valkyrie.sdk.generator.kt.util.formatFloat import io.github.composegears.valkyrie.sdk.ir.core.IrColor import io.github.composegears.valkyrie.sdk.ir.core.IrFill import io.github.composegears.valkyrie.sdk.ir.core.IrPathFillType @@ -27,8 +26,9 @@ import io.github.composegears.valkyrie.sdk.ir.core.IrStroke import io.github.composegears.valkyrie.sdk.ir.core.IrStrokeLineCap import io.github.composegears.valkyrie.sdk.ir.core.IrStrokeLineJoin import io.github.composegears.valkyrie.sdk.ir.core.IrVectorNode +import io.github.composegears.valkyrie.sdk.ir.core.toPathString -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun CodeBlock.Builder.addPath( path: IrVectorNode.IrPath, pathBody: CodeBlock.Builder.() -> Unit, @@ -63,7 +63,7 @@ internal fun CodeBlock.Builder.addPath( pathParams.forEachIndexed { index, param -> fillPathArgs(param) if (index == pathParams.lastIndex) { - if (config.addTrailingComma) { + if (config.imageVector.addTrailingComma) { trailingComma() } else { newLine() @@ -83,10 +83,10 @@ internal fun CodeBlock.Builder.addPath( } } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun CodeBlock.Builder.addPathData(path: IrVectorNode.IrPath) { val pathParams = path.buildPathParams() - val pathData = path.paths.asPathDataString() + val pathData = path.paths.toPathString() add( codeBlock = buildCodeBlock { @@ -97,7 +97,7 @@ internal fun CodeBlock.Builder.addPathData(path: IrVectorNode.IrPath) { trailingComma() } add("pathData = %M(%S)", MemberNames.AddPathNodes, pathData) - if (config.addTrailingComma) { + if (config.imageVector.addTrailingComma) { trailingComma() } else { newLine() @@ -108,7 +108,7 @@ internal fun CodeBlock.Builder.addPathData(path: IrVectorNode.IrPath) { ) } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.fillPathArgs( param: PathParams, handleMultiline: Boolean = false, @@ -131,7 +131,7 @@ private fun CodeBlock.Builder.nameArg(param: NameParam) { add("name = %S", param.name) } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.fillArg( path: FillParam, handleMultiline: Boolean, @@ -167,13 +167,13 @@ private fun CodeBlock.Builder.fillArg( } } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.addLinearGradient(fill: IrFill.LinearGradient) { fun gradientBody() { argumentBlock("colorStops = arrayOf(", isNested = true) { addColorStops(fill.colorStops) } - if (config.fullQualifiedImports.offset) { + if (config.fullyQualifiedImports.offset) { add( "start = %L(${fill.startX.formatFloat()}, ${fill.startY.formatFloat()}),", MemberNames.Offset.canonicalName, @@ -185,7 +185,7 @@ private fun CodeBlock.Builder.addLinearGradient(fill: IrFill.LinearGradient) { ) } newLine() - if (config.fullQualifiedImports.offset) { + if (config.fullyQualifiedImports.offset) { add( "end = %L(${fill.endX.formatFloat()}, ${fill.endY.formatFloat()})", MemberNames.Offset.canonicalName, @@ -198,7 +198,7 @@ private fun CodeBlock.Builder.addLinearGradient(fill: IrFill.LinearGradient) { } } - if (config.fullQualifiedImports.brush) { + if (config.fullyQualifiedImports.brush) { argumentBlock("fill = %L.linearGradient(", ClassNames.Brush.canonicalName) { gradientBody() } @@ -209,13 +209,13 @@ private fun CodeBlock.Builder.addLinearGradient(fill: IrFill.LinearGradient) { } } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.addRadialGradient(fill: IrFill.RadialGradient) { fun gradientBody() { argumentBlock("colorStops = arrayOf(", isNested = true) { addColorStops(fill.colorStops) } - if (config.fullQualifiedImports.offset) { + if (config.fullyQualifiedImports.offset) { add( "center = %L(${fill.centerX.formatFloat()}, ${fill.centerY.formatFloat()}),", MemberNames.Offset.canonicalName, @@ -230,7 +230,7 @@ private fun CodeBlock.Builder.addRadialGradient(fill: IrFill.RadialGradient) { add("radius = ${fill.radius.formatFloat()}") } - if (config.fullQualifiedImports.brush) { + if (config.fullyQualifiedImports.brush) { argumentBlock("fill = %L.radialGradient(", ClassNames.Brush.canonicalName) { gradientBody() } @@ -245,7 +245,7 @@ private fun CodeBlock.Builder.fillAlphaArg(param: FillAlphaParam) { add("fillAlpha = ${param.fillAlpha.formatFloat()}") } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.strokeArg(param: StrokeColorParam) { add("stroke = %M(", MemberNames.SolidColor) addColor(param.strokeColor) @@ -276,16 +276,16 @@ private fun CodeBlock.Builder.pathFillTypeArg(param: PathFillTypeParam) { add("pathFillType = %T.%L", ClassNames.PathFillType, param.pathFillType.name) } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.addColor(color: IrColor) { - if (config.fullQualifiedImports.color) { + if (config.fullyQualifiedImports.color) { add("%L", MemberNames.Color.canonicalName) } else { add("%M", MemberNames.Color) } val name = color.toName() - if (name != null && config.useComposeColors) { + if (name != null && config.imageVector.useComposeColors) { val alphaValue = color.alpha.toFloat() / 0xFF if (alphaValue < 1f) { add(".$name.copy(alpha = ${alphaValue.formatFloat()})") @@ -297,7 +297,7 @@ private fun CodeBlock.Builder.addColor(color: IrColor) { } } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) private fun CodeBlock.Builder.addColorStops(stops: List) { val lastIndex = stops.lastIndex stops.forEachIndexed { index, stop -> diff --git a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/PreviewSpec.kt b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/PreviewSpec.kt similarity index 86% rename from components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/PreviewSpec.kt rename to sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/PreviewSpec.kt index 7891314fe..b3bdb2755 100644 --- a/components/generator/jvm/imagevector/src/main/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/util/PreviewSpec.kt +++ b/sdk/generator/kt/imagevector/jvm/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/util/PreviewSpec.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.util import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName @@ -6,10 +6,10 @@ import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.MemberName import com.squareup.kotlinpoet.buildCodeBlock -import io.github.composegears.valkyrie.generator.jvm.ext.funSpecBuilder -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorSpecConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.poet.funSpecBuilder -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun iconPreviewSpecForNestedPack( iconPackClassName: ClassName, ): FunSpec = funSpecBuilder("${config.iconName}Preview") { @@ -35,7 +35,7 @@ internal fun iconPreviewSpecForNestedPack( ) } -context(config: ImageVectorSpecConfig) +context(config: ImageVectorGeneratorConfig) internal fun iconPreviewSpec( iconPackage: String, ): FunSpec = funSpecBuilder("${config.iconName}Preview") { diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/BrokenSvgParsingTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/BrokenSvgParsingTest.kt similarity index 90% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/BrokenSvgParsingTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/BrokenSvgParsingTest.kt index 89d48a3eb..e1bd87108 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/BrokenSvgParsingTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/BrokenSvgParsingTest.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertFailure import assertk.assertions.isInstanceOf diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/CustomPackageTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/CustomPackageTest.kt similarity index 54% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/CustomPackageTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/CustomPackageTest.kt index 2bf7714ee..0ddc9e4c9 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/CustomPackageTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/CustomPackageTest.kt @@ -1,13 +1,18 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.XML +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedClass @@ -25,10 +30,13 @@ class CustomPackageTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - useFlatPackage = true, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useFlatPackage = true, + ), ), ).content @@ -46,11 +54,14 @@ class CustomPackageTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - useFlatPackage = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useFlatPackage = true, + ), ), ).content @@ -68,12 +79,16 @@ class CustomPackageTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - nestedPackName = "Filled", - outputFormat = outputFormat, - useFlatPackage = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useFlatPackage = true, + ), ), ).content @@ -85,20 +100,52 @@ class CustomPackageTest( assertThat(output).isEqualTo(expected) } + @Test + fun `flat package with deep nested icon pack`() { + val icon = getResourcePath("imagevector/xml/ic_flat_package.xml").toIOPath() + val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) + val output = ImageVectorGenerator.convert( + vector = parserOutput.irImageVector, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") + } + }, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useFlatPackage = true, + ), + ), + ).content + + val expected = outputFormat.toResourceText( + pathToBackingProperty = "imagevector/kt/backing/FlatPackage.pack.deep.nested.kt", + pathToLazyProperty = "imagevector/kt/lazy/FlatPackage.pack.deep.nested.kt", + ) + assertThat(parserOutput.iconType).isEqualTo(XML) + assertThat(output).isEqualTo(expected) + } + @Test fun `different icon pack package`() { val icon = getResourcePath("imagevector/xml/ic_without_path.xml").toIOPath() val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packageName = "io.github.composegears.valkyrie.icons", + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, iconPackPackage = "androidx.compose.material.icons", - useFlatPackage = true, - packName = "Icons", - nestedPackName = "Filled", - outputFormat = outputFormat, + iconPackTree = buildTree("Icons") { + child("Filled") + }, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useFlatPackage = true, + ), ), ).content @@ -116,10 +163,10 @@ class CustomPackageTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, packageName = "", - outputFormat = outputFormat, + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ExplicitModeTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ExplicitModeTest.kt similarity index 59% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ExplicitModeTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ExplicitModeTest.kt index 55e312c25..459f8dec1 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ExplicitModeTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ExplicitModeTest.kt @@ -1,13 +1,17 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.XML +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedClass @@ -25,10 +29,11 @@ class ExplicitModeTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - useExplicitMode = true, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + codeStyle = CodeStyleConfig(useExplicitMode = true), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImportsTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/FullyQualifiedImportsTest.kt similarity index 62% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImportsTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/FullyQualifiedImportsTest.kt index d15bef3ca..70ce7682a 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/FullQualifiedImportsTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/FullyQualifiedImportsTest.kt @@ -1,13 +1,17 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.XML +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedClass @@ -15,7 +19,7 @@ import org.junit.jupiter.params.provider.EnumSource @ParameterizedClass @EnumSource(value = OutputFormat::class) -class FullQualifiedImportsTest( +class FullyQualifiedImportsTest( private val outputFormat: OutputFormat, ) { @@ -25,12 +29,11 @@ class FullQualifiedImportsTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "", - outputFormat = outputFormat, - useComposeColors = true, - fullQualifiedImports = FullQualifiedImports(brush = true), + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + fullyQualifiedImports = FullyQualifiedImports(brush = true), ), ).content @@ -48,12 +51,11 @@ class FullQualifiedImportsTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "", - outputFormat = outputFormat, - useComposeColors = true, - fullQualifiedImports = FullQualifiedImports(color = true), + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + fullyQualifiedImports = FullyQualifiedImports(color = true), ), ).content @@ -66,17 +68,16 @@ class FullQualifiedImportsTest( } @Test - fun `full qualified import, Offest`() { + fun `full qualified import, Offset`() { val icon = getResourcePath("imagevector/xml/ic_full_qualified.xml").toIOPath() val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "", - outputFormat = outputFormat, - useComposeColors = true, - fullQualifiedImports = FullQualifiedImports(offset = true), + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + fullyQualifiedImports = FullyQualifiedImports(offset = true), ), ).content diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorWithIndentTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorWithIndentTest.kt similarity index 66% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorWithIndentTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorWithIndentTest.kt index 73685c861..09efd7a94 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorWithIndentTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorWithIndentTest.kt @@ -1,13 +1,17 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.XML +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedClass @@ -18,20 +22,19 @@ import org.junit.jupiter.params.provider.EnumSource class ImageVectorWithIndentTest( private val outputFormat: OutputFormat, ) { - @Test fun `generation without icon pack with indent 1`() { val icon = getResourcePath("imagevector/xml/ic_without_path.xml").toIOPath() val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - indentSize = 1, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + codeStyle = CodeStyleConfig(indentSize = 1), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content - val expected = outputFormat.toResourceText( pathToBackingProperty = "imagevector/kt/backing/WithoutPath.indent1.kt", pathToLazyProperty = "imagevector/kt/lazy/WithoutPath.indent1.kt", @@ -46,13 +49,13 @@ class ImageVectorWithIndentTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - indentSize = 2, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + codeStyle = CodeStyleConfig(indentSize = 2), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content - val expected = outputFormat.toResourceText( pathToBackingProperty = "imagevector/kt/backing/WithoutPath.indent2.kt", pathToLazyProperty = "imagevector/kt/lazy/WithoutPath.indent2.kt", @@ -67,13 +70,13 @@ class ImageVectorWithIndentTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - indentSize = 3, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + codeStyle = CodeStyleConfig(indentSize = 3), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content - val expected = outputFormat.toResourceText( pathToBackingProperty = "imagevector/kt/backing/WithoutPath.indent3.kt", pathToLazyProperty = "imagevector/kt/lazy/WithoutPath.indent3.kt", @@ -88,13 +91,13 @@ class ImageVectorWithIndentTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - indentSize = 6, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + codeStyle = CodeStyleConfig(indentSize = 6), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content - val expected = outputFormat.toResourceText( pathToBackingProperty = "imagevector/kt/backing/WithoutPath.indent6.kt", pathToLazyProperty = "imagevector/kt/lazy/WithoutPath.indent6.kt", diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorWithPreviewTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorWithPreviewTest.kt similarity index 62% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorWithPreviewTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorWithPreviewTest.kt index 3fe707e42..2a062a5b5 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/ImageVectorWithPreviewTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ImageVectorWithPreviewTest.kt @@ -1,13 +1,18 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.XML +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedClass @@ -25,10 +30,13 @@ class ImageVectorWithPreviewTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - generatePreview = true, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + generatePreview = true, + ), ), ).content @@ -46,11 +54,14 @@ class ImageVectorWithPreviewTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - generatePreview = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + generatePreview = true, + ), ), ).content @@ -68,12 +79,16 @@ class ImageVectorWithPreviewTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - nestedPackName = "Filled", - outputFormat = outputFormat, - generatePreview = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + generatePreview = true, + ), ), ).content diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/PathDataStringTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/PathDataStringTest.kt similarity index 64% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/PathDataStringTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/PathDataStringTest.kt index 82df99424..12835cadd 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/PathDataStringTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/PathDataStringTest.kt @@ -1,14 +1,18 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.SVG import io.github.composegears.valkyrie.parser.unified.model.IconType.XML +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedClass @@ -26,11 +30,14 @@ class PathDataStringTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - usePathDataString = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + usePathDataString = true, + ), ), ).content @@ -48,11 +55,14 @@ class PathDataStringTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - usePathDataString = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + usePathDataString = true, + ), ), ).content @@ -70,10 +80,10 @@ class PathDataStringTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - usePathDataString = false, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -89,14 +99,17 @@ class PathDataStringTest( fun `simple clip path with smooth curves uses path data string format`() { val icon = getResourcePath("imagevector/xml/ic_clip_path.xml").toIOPath() val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) - val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - usePathDataString = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useComposeColors = false, + usePathDataString = true, + ), ), ).content diff --git a/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ResolveIconPackClassNameTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ResolveIconPackClassNameTest.kt new file mode 100644 index 000000000..7f1557475 --- /dev/null +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/ResolveIconPackClassNameTest.kt @@ -0,0 +1,104 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isNull +import com.squareup.kotlinpoet.ClassName +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.spec.resolveIconPackClassName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import org.junit.jupiter.api.Test + +class ResolveIconPackClassNameTest { + + @Test + fun `resolveIconPackClassName - no iconPackTree returns null`() { + val config = ImageVectorGeneratorConfig.simple( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + ) + assertThat(config.resolveIconPackClassName()).isNull() + } + + @Test + fun `resolveIconPackClassName - root-only tree returns root ClassName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + ) + assertThat(config.resolveIconPackClassName()) + .isEqualTo(ClassName(DEFAULT_PACKAGE, "ValkyrieIcons")) + } + + @Test + fun `resolveIconPackClassName - one nested level returns nested ClassName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + ) + assertThat(config.resolveIconPackClassName()) + .isEqualTo(ClassName(DEFAULT_PACKAGE, "ValkyrieIcons").nestedClass("Filled")) + } + + @Test + fun `resolveIconPackClassName - two nested levels returns doubly-nested ClassName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { child("Rounded") } + }, + ) + assertThat(config.resolveIconPackClassName()) + .isEqualTo( + ClassName(DEFAULT_PACKAGE, "ValkyrieIcons") + .nestedClass("Material") + .nestedClass("Rounded"), + ) + } + + @Test + fun `resolveIconPackClassName - three nested levels returns triply-nested ClassName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") { + child("Sharp") + } + } + }, + ) + assertThat(config.resolveIconPackClassName()) + .isEqualTo( + ClassName(DEFAULT_PACKAGE, "ValkyrieIcons") + .nestedClass("Material") + .nestedClass("Rounded") + .nestedClass("Sharp"), + ) + } + + @Test + fun `resolveIconPackClassName - uses iconPackPackage not packageName`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackPackage = "androidx.compose.material.icons", + iconPackTree = buildTree("Icons") { + child("Filled") + }, + ) + assertThat(config.resolveIconPackClassName()) + .isEqualTo( + ClassName("androidx.compose.material.icons", "Icons") + .nestedClass("Filled"), + ) + } +} diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/SvgWithGradientToImageVectorTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/SvgWithGradientToImageVectorTest.kt similarity index 68% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/SvgWithGradientToImageVectorTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/SvgWithGradientToImageVectorTest.kt index 288c7c570..51687daa1 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/SvgWithGradientToImageVectorTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/SvgWithGradientToImageVectorTest.kt @@ -1,13 +1,16 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.SVG +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedClass @@ -25,8 +28,14 @@ class SvgWithGradientToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig(outputFormat = outputFormat), + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useComposeColors = false, + ), + ), ).content val expected = outputFormat.toResourceText( @@ -43,8 +52,11 @@ class SvgWithGradientToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig(outputFormat = outputFormat), + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ), ).content val expected = outputFormat.toResourceText( @@ -61,8 +73,11 @@ class SvgWithGradientToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig(outputFormat = outputFormat), + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ), ).content val expected = outputFormat.toResourceText( @@ -79,8 +94,11 @@ class SvgWithGradientToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig(outputFormat = outputFormat), + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ), ).content val expected = outputFormat.toResourceText( diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/TrailingCommaTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/TrailingCommaTest.kt similarity index 60% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/TrailingCommaTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/TrailingCommaTest.kt index aba93615d..1a6c691d9 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/TrailingCommaTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/TrailingCommaTest.kt @@ -1,13 +1,17 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.XML +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedClass @@ -25,11 +29,14 @@ class TrailingCommaTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - addTrailingComma = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + addTrailingComma = true, + ), ), ).content @@ -47,11 +54,15 @@ class TrailingCommaTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - addTrailingComma = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useComposeColors = false, + addTrailingComma = true, + ), ), ).content diff --git a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/XmlToImageVectorTest.kt b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/XmlToImageVectorTest.kt similarity index 65% rename from components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/XmlToImageVectorTest.kt rename to sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/XmlToImageVectorTest.kt index d67c8f90f..d008a052b 100644 --- a/components/generator/jvm/imagevector/src/test/kotlin/io/github/composegears/valkyrie/generator/jvm/imagevector/XmlToImageVectorTest.kt +++ b/sdk/generator/kt/imagevector/jvm/src/test/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/jvm/XmlToImageVectorTest.kt @@ -1,16 +1,21 @@ -package io.github.composegears.valkyrie.generator.jvm.imagevector +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm import assertk.assertFailure import assertk.assertThat import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.createConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.common.toResourceText import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.model.IconType.XML +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import kotlin.io.path.Path import org.junit.jupiter.api.Test @@ -39,8 +44,11 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig(outputFormat = outputFormat), + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ), ).content val expected = outputFormat.toResourceText( @@ -57,11 +65,13 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - nestedPackName = "Colored", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Colored") + }, + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -73,16 +83,43 @@ class XmlToImageVectorTest( assertThat(output).isEqualTo(expected) } + @Test + fun `generation with deep nested icon pack`() { + val icon = getResourcePath("imagevector/xml/ic_without_path.xml").toIOPath() + val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) + val output = ImageVectorGenerator.convert( + vector = parserOutput.irImageVector, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") + } + }, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ), + ).content + + val expected = outputFormat.toResourceText( + pathToBackingProperty = "imagevector/kt/backing/WithoutPath.pack.deep.nested.kt", + pathToLazyProperty = "imagevector/kt/lazy/WithoutPath.pack.deep.nested.kt", + ) + assertThat(parserOutput.iconType).isEqualTo(XML) + assertThat(output).isEqualTo(expected) + } + @Test fun `empty path xml`() { val icon = getResourcePath("imagevector/xml/ic_without_path.xml").toIOPath() val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -100,10 +137,11 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -121,10 +159,11 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -142,10 +181,11 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -163,10 +203,14 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useComposeColors = false, + ), ), ).content @@ -184,10 +228,14 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useComposeColors = false, + ), ), ).content @@ -205,11 +253,11 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - useComposeColors = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -227,11 +275,10 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "", - outputFormat = outputFormat, - useComposeColors = true, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -249,11 +296,10 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "", - outputFormat = outputFormat, - useComposeColors = true, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -271,10 +317,10 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - outputFormat = outputFormat, - useComposeColors = true, + config = ImageVectorGeneratorConfig.simple( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -292,10 +338,14 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useComposeColors = false, + ), ), ).content @@ -313,10 +363,11 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -334,10 +385,11 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig(outputFormat = outputFormat), ), ).content @@ -355,11 +407,14 @@ class XmlToImageVectorTest( val parserOutput = SvgXmlParser.toIrImageVector(parser = ParserType.Jvm, path = icon) val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = parserOutput.iconName, - config = createConfig( - packName = "ValkyrieIcons", - outputFormat = outputFormat, - suppressUnusedReceiverWarning = true, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + suppressUnusedReceiverWarning = true, + ), ), ).content diff --git a/sdk/generator/kt/imagevector/kmp/api/kmp.api b/sdk/generator/kt/imagevector/kmp/api/kmp.api new file mode 100644 index 000000000..fd6e904ae --- /dev/null +++ b/sdk/generator/kt/imagevector/kmp/api/kmp.api @@ -0,0 +1,5 @@ +public final class io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ImageVectorGenerator { + public static final field INSTANCE Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ImageVectorGenerator; + public final fun convert (Lio/github/composegears/valkyrie/sdk/ir/core/IrImageVector;Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorGeneratorConfig;)Lio/github/composegears/valkyrie/sdk/generator/kt/imagevector/common/ImageVectorOutput; +} + diff --git a/sdk/generator/kt/imagevector/kmp/api/kmp.klib.api b/sdk/generator/kt/imagevector/kmp/api/kmp.klib.api new file mode 100644 index 000000000..9da37a441 --- /dev/null +++ b/sdk/generator/kt/imagevector/kmp/api/kmp.klib.api @@ -0,0 +1,11 @@ +// Klib ABI Dump +// Targets: [wasmJs] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final object io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp/ImageVectorGenerator { // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp/ImageVectorGenerator|null[0] + final fun convert(io.github.composegears.valkyrie.sdk.ir.core/IrImageVector, io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorGeneratorConfig): io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common/ImageVectorOutput // io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp/ImageVectorGenerator.convert|convert(io.github.composegears.valkyrie.sdk.ir.core.IrImageVector;io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig){}[0] +} diff --git a/components/generator/kmp/imagevector/build.gradle.kts b/sdk/generator/kt/imagevector/kmp/build.gradle.kts similarity index 55% rename from components/generator/kmp/imagevector/build.gradle.kts rename to sdk/generator/kt/imagevector/kmp/build.gradle.kts index 3b3218c31..0f2b63244 100644 --- a/components/generator/kmp/imagevector/build.gradle.kts +++ b/sdk/generator/kt/imagevector/kmp/build.gradle.kts @@ -1,15 +1,24 @@ +import io.github.composegears.valkyrie.extension.because + plugins { alias(libs.plugins.valkyrie.kmp) alias(libs.plugins.valkyrie.abi) alias(libs.plugins.valkyrie.wasm.resources) alias(libs.plugins.valkyrie.kover) + alias(libs.plugins.valkyrie.explicit.api) } kotlin { + compilerOptions { + freeCompilerArgs.add("-Xcontext-parameters") + } + sourceSets { commonMain.dependencies { - implementation(projects.components.generator.core) - api(projects.sdk.ir.core) + implementation(projects.sdk.generator.kt.common.util) + api(projects.sdk.generator.kt.imagevector.common) because "Shared config model" + implementation(projects.sdk.generator.kt.common.ir) + implementation(projects.sdk.ir.core) } commonTest.dependencies { implementation(libs.bundles.kmp.test) @@ -20,6 +29,7 @@ kotlin { dependencies { implementation(projects.components.parser.unified) implementation(projects.sdk.test.resourceLoader) + implementation(projects.sdk.generator.kt.imagevector.testFixtures) implementation(libs.bundles.test) } } diff --git a/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ImageVectorGenerator.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ImageVectorGenerator.kt new file mode 100644 index 000000000..30dee5548 --- /dev/null +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ImageVectorGenerator.kt @@ -0,0 +1,19 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp + +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorOutput +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.render.BackingPropertyRenderer +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.render.LazyPropertyRenderer +import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector + +public object ImageVectorGenerator { + + public fun convert( + vector: IrImageVector, + config: ImageVectorGeneratorConfig, + ): ImageVectorOutput = when (config.imageVector.outputFormat) { + OutputFormat.BackingProperty -> BackingPropertyRenderer(config).render(vector) + OutputFormat.LazyProperty -> LazyPropertyRenderer(config).render(vector) + } +} diff --git a/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/common/ResolveReceiverName.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/common/ResolveReceiverName.kt new file mode 100644 index 000000000..25d5b9849 --- /dev/null +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/common/ResolveReceiverName.kt @@ -0,0 +1,9 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.common + +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.pathSegments +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig + +internal fun ImageVectorGeneratorConfig.resolveReceiverName(): String { + val segments = iconPackTree?.pathSegments().orEmpty() + return segments.joinToString(".") +} diff --git a/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/render/BackingPropertyRenderer.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/render/BackingPropertyRenderer.kt new file mode 100644 index 000000000..b11893976 --- /dev/null +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/render/BackingPropertyRenderer.kt @@ -0,0 +1,17 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.render + +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorOutput +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.util.ImageVectorRenderer +import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector + +internal class BackingPropertyRenderer(private val config: ImageVectorGeneratorConfig) { + + fun render(irVector: IrImageVector): ImageVectorOutput = ImageVectorOutput( + content = ImageVectorRenderer.renderBacking( + config = config, + vector = irVector, + ), + name = config.iconName, + ) +} diff --git a/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/render/LazyPropertyRenderer.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/render/LazyPropertyRenderer.kt new file mode 100644 index 000000000..c2ed9e62d --- /dev/null +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/render/LazyPropertyRenderer.kt @@ -0,0 +1,14 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.render + +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorOutput +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.util.ImageVectorRenderer +import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector + +internal class LazyPropertyRenderer(private val config: ImageVectorGeneratorConfig) { + + fun render(irVector: IrImageVector): ImageVectorOutput = ImageVectorOutput( + content = ImageVectorRenderer.renderLazy(config = config, vector = irVector), + name = config.iconName, + ) +} diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/CodeWriter.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/CodeWriter.kt similarity index 85% rename from components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/CodeWriter.kt rename to sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/CodeWriter.kt index 70421a041..c154c0b2d 100644 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/CodeWriter.kt +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/CodeWriter.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.util internal class CodeWriter(private val indentSize: Int) { private val value = StringBuilder() diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/ImageVectorRenderer.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/ImageVectorRenderer.kt similarity index 61% rename from components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/ImageVectorRenderer.kt rename to sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/ImageVectorRenderer.kt index 917dcc9a4..0b9e99af8 100644 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/ImageVectorRenderer.kt +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/ImageVectorRenderer.kt @@ -1,27 +1,28 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.util - -import io.github.composegears.valkyrie.generator.core.asPathDataString -import io.github.composegears.valkyrie.generator.core.asStatement -import io.github.composegears.valkyrie.generator.core.formatFloat -import io.github.composegears.valkyrie.generator.core.trimTrailingZero -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorRenderConfig -import io.github.composegears.valkyrie.generator.kmp.imagevector.render.resolveIconBuilderName -import io.github.composegears.valkyrie.generator.kmp.imagevector.render.resolvePackageName -import io.github.composegears.valkyrie.generator.kmp.imagevector.render.resolveReceiverName +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.util + +import io.github.composegears.valkyrie.generator.kt.common.ir.asStatement +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.resolveIconBuilderName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.resolvePackageName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.common.resolveReceiverName +import io.github.composegears.valkyrie.sdk.generator.kt.util.formatFloat +import io.github.composegears.valkyrie.sdk.generator.kt.util.trimTrailingZero import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector import io.github.composegears.valkyrie.sdk.ir.core.IrVectorNode +import io.github.composegears.valkyrie.sdk.ir.core.toPathString internal object ImageVectorRenderer { - fun renderBacking(config: ImageVectorRenderConfig, vector: IrImageVector): String { - val writer = CodeWriter(config.indentSize) - writeHeader(writer, config, vector) + + fun renderBacking(config: ImageVectorGeneratorConfig, vector: IrImageVector): String = with(config) { + val writer = CodeWriter(config.codeStyle.indentSize) + writeHeader(writer, vector) val targetName = config.targetName() - val explicit = if (config.useExplicitMode) "public " else "" + val explicit = if (config.codeStyle.useExplicitMode) "public " else "" val backingName = "_${config.iconName}" with(writer) { - if (config.suppressUnusedReceiverWarning && config.resolveReceiverName().isNotEmpty()) { + if (config.imageVector.suppressUnusedReceiverWarning && config.resolveReceiverName().isNotEmpty()) { line("@Suppress(\"UnusedReceiverParameter\")") } line("${explicit}val $targetName: ImageVector") @@ -30,7 +31,7 @@ internal object ImageVectorRenderer { line("${indent(3)}return $backingName!!") line("${indent(2)}}") append("${indent(2)}$backingName = ${builderStart(config, vector, 2)}") - writeBody(this, config, vector, level = 2) + writeBody(this, vector, level = 2) line() line("${indent(2)}return $backingName!!") line("${indent(1)}}") @@ -38,7 +39,7 @@ internal object ImageVectorRenderer { line("@Suppress(\"ObjectPropertyName\")") line("private var $backingName: ImageVector? = null") - if (config.generatePreview) { + if (config.imageVector.generatePreview) { line() writePreview(this, config) } @@ -47,23 +48,23 @@ internal object ImageVectorRenderer { return writer.toString() } - fun renderLazy(config: ImageVectorRenderConfig, vector: IrImageVector): String { - val writer = CodeWriter(config.indentSize) - writeHeader(writer, config, vector) + fun renderLazy(config: ImageVectorGeneratorConfig, vector: IrImageVector): String = with(config) { + val writer = CodeWriter(config.codeStyle.indentSize) + writeHeader(writer, vector) val targetName = config.targetName() - val explicit = if (config.useExplicitMode) "public " else "" + val explicit = if (config.codeStyle.useExplicitMode) "public " else "" with(writer) { - if (config.suppressUnusedReceiverWarning && config.resolveReceiverName().isNotEmpty()) { + if (config.imageVector.suppressUnusedReceiverWarning && config.resolveReceiverName().isNotEmpty()) { line("@Suppress(\"UnusedReceiverParameter\")") } line("${explicit}val $targetName: ImageVector by lazy(LazyThreadSafetyMode.NONE) {") append("${indent(1)}${builderStart(config, vector, 1)}") - writeBody(this, config, vector, level = 1) + writeBody(this, vector, level = 1) line("}") - if (config.generatePreview) { + if (config.imageVector.generatePreview) { line() writePreview(this, config) } @@ -72,21 +73,22 @@ internal object ImageVectorRenderer { return writer.toString() } - private fun writeHeader(writer: CodeWriter, config: ImageVectorRenderConfig, vector: IrImageVector) { + context(config: ImageVectorGeneratorConfig) + private fun writeHeader(writer: CodeWriter, vector: IrImageVector) { val packageName = config.resolvePackageName() if (packageName.isNotEmpty()) { writer.line("package $packageName") writer.line() } - val imports = collectImports(config, vector) + val imports = collectImports(vector) imports.forEach(writer::line) if (imports.isNotEmpty()) { writer.line() } } - private fun builderStart(config: ImageVectorRenderConfig, vector: IrImageVector, level: Int): String { + private fun builderStart(config: ImageVectorGeneratorConfig, vector: IrImageVector, level: Int): String { val args = buildList { add("name = \"${config.resolveIconBuilderName().escapeKotlin()}\"") add("defaultWidth = ${vector.defaultWidth.trimTrailingZero()}.dp") @@ -101,16 +103,16 @@ internal object ImageVectorRenderer { return buildString { append("ImageVector.Builder(\n") args.forEachIndexed { index, arg -> - val comma = if (index == args.lastIndex && !config.addTrailingComma) "" else "," - append("${" ".repeat(config.indentSize * (level + 1))}$arg$comma\n") + val comma = if (index == args.lastIndex && !config.imageVector.addTrailingComma) "" else "," + append("${" ".repeat(config.codeStyle.indentSize * (level + 1))}$arg$comma\n") } - append("${" ".repeat(config.indentSize * level)})") + append("${" ".repeat(config.codeStyle.indentSize * level)})") } } + context(config: ImageVectorGeneratorConfig) private fun writeBody( writer: CodeWriter, - config: ImageVectorRenderConfig, vector: IrImageVector, level: Int, ) { @@ -124,60 +126,60 @@ internal object ImageVectorRenderer { append(".apply {") newLine() vector.nodes.forEach { node -> - writeVectorNode(this, config, node, level + 1) + writeVectorNode(this, node, level + 1) } line("${indent(level)}}.build()") } } + context(config: ImageVectorGeneratorConfig) private fun writeVectorNode( writer: CodeWriter, - config: ImageVectorRenderConfig, node: IrVectorNode, level: Int, ) { when (node) { - is IrVectorNode.IrGroup -> writeGroup(writer, config, node, level) - is IrVectorNode.IrPath -> writePath(writer, config, node, level) + is IrVectorNode.IrGroup -> writeGroup(writer, node, level) + is IrVectorNode.IrPath -> writePath(writer, node, level) } } + context(config: ImageVectorGeneratorConfig) private fun writeGroup( writer: CodeWriter, - config: ImageVectorRenderConfig, node: IrVectorNode.IrGroup, level: Int, ) { - val params = collectGroupParams(node, config, level, writer) + val params = collectGroupParams(node, level, writer) writeBlockCall( writer = writer, level = level, call = "group", params = params, - addTrailingComma = config.addTrailingComma, + addTrailingComma = config.imageVector.addTrailingComma, indentMultilineContent = false, ) node.nodes.forEach { child -> - writeVectorNode(writer, config, child, level + 1) + writeVectorNode(writer, child, level + 1) } writer.line("${writer.indent(level)}}") } + context(config: ImageVectorGeneratorConfig) private fun writePath( writer: CodeWriter, - config: ImageVectorRenderConfig, node: IrVectorNode.IrPath, level: Int, ) { - val params = collectPathParams(node, config) - if (config.usePathDataString) { + val params = collectPathParams(node) + if (config.imageVector.usePathDataString) { writeCall( writer = writer, level = level, call = "addPath", - params = params + "pathData = addPathNodes(\"${node.paths.asPathDataString().escapeKotlin()}\")", - addTrailingComma = config.addTrailingComma, + params = params + "pathData = addPathNodes(\"${node.paths.toPathString().escapeKotlin()}\")", + addTrailingComma = config.imageVector.addTrailingComma, indentMultilineContent = true, opensBlock = false, forceMultiline = true, @@ -190,7 +192,7 @@ internal object ImageVectorRenderer { level = level, call = "path", params = params, - addTrailingComma = config.addTrailingComma, + addTrailingComma = config.imageVector.addTrailingComma, indentMultilineContent = true, ) @@ -200,7 +202,7 @@ internal object ImageVectorRenderer { writer.line("${writer.indent(level)}}") } - private fun writePreview(writer: CodeWriter, config: ImageVectorRenderConfig) { + private fun writePreview(writer: CodeWriter, config: ImageVectorGeneratorConfig) { with(writer) { line("@Preview") line("@Composable") @@ -212,7 +214,7 @@ internal object ImageVectorRenderer { } } - private fun ImageVectorRenderConfig.targetName(): String { + private fun ImageVectorGeneratorConfig.targetName(): String { val receiver = resolveReceiverName() return if (receiver.isEmpty()) iconName else "$receiver.$iconName" } diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/ImportCollector.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/ImportCollector.kt similarity index 77% rename from components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/ImportCollector.kt rename to sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/ImportCollector.kt index 4e1820cc6..768056f7f 100644 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/ImportCollector.kt +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/ImportCollector.kt @@ -1,7 +1,7 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.util -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorRenderConfig -import io.github.composegears.valkyrie.generator.kmp.imagevector.render.resolvePackageName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.resolvePackageName import io.github.composegears.valkyrie.sdk.ir.core.IrFill import io.github.composegears.valkyrie.sdk.ir.core.IrImageVector import io.github.composegears.valkyrie.sdk.ir.core.IrPathFillType @@ -10,18 +10,19 @@ import io.github.composegears.valkyrie.sdk.ir.core.IrStrokeLineCap import io.github.composegears.valkyrie.sdk.ir.core.IrStrokeLineJoin import io.github.composegears.valkyrie.sdk.ir.core.IrVectorNode -internal fun collectImports(config: ImageVectorRenderConfig, vector: IrImageVector): List { +context(config: ImageVectorGeneratorConfig) +internal fun collectImports(vector: IrImageVector): List { val usage = UsageFlags.scan(config, vector) val imports = mutableListOf() if ( - config.iconPack.isNotEmpty() && + !config.iconPackTree?.data.isNullOrEmpty() && config.iconPackPackage.isNotBlank() && config.iconPackPackage != config.resolvePackageName() ) { - imports += "import ${config.iconPackPackage}.${config.iconPack}" + imports += "import ${config.iconPackPackage}.${config.iconPackTree?.data}" } - if (config.generatePreview) { + if (config.imageVector.generatePreview) { imports += "import androidx.compose.foundation.Image" imports += "import androidx.compose.foundation.layout.Box" imports += "import androidx.compose.foundation.layout.padding" @@ -31,9 +32,9 @@ internal fun collectImports(config: ImageVectorRenderConfig, vector: IrImageVect } with(usage) { - if (usesOffset && !config.fullQualifiedImports.offset) imports += "import androidx.compose.ui.geometry.Offset" - if (usesBrush && !config.fullQualifiedImports.brush) imports += "import androidx.compose.ui.graphics.Brush" - if (usesColor && !config.fullQualifiedImports.color) imports += "import androidx.compose.ui.graphics.Color" + if (usesOffset && !config.fullyQualifiedImports.offset) imports += "import androidx.compose.ui.geometry.Offset" + if (usesBrush && !config.fullyQualifiedImports.brush) imports += "import androidx.compose.ui.graphics.Brush" + if (usesColor && !config.fullyQualifiedImports.color) imports += "import androidx.compose.ui.graphics.Color" if (usesPathFillType) imports += "import androidx.compose.ui.graphics.PathFillType" if (usesSolidColor) imports += "import androidx.compose.ui.graphics.SolidColor" if (usesStrokeCap) imports += "import androidx.compose.ui.graphics.StrokeCap" @@ -64,22 +65,26 @@ private data class UsageFlags( var usesPathData: Boolean = false, ) { companion object { - fun scan(config: ImageVectorRenderConfig, vector: IrImageVector): UsageFlags { - val flags = UsageFlags(usesAddPathNodes = config.usePathDataString) + fun scan(config: ImageVectorGeneratorConfig, vector: IrImageVector): UsageFlags { + val flags = UsageFlags(usesAddPathNodes = config.imageVector.usePathDataString) fun visit(node: IrVectorNode) { when (node) { is IrVectorNode.IrGroup -> { flags.usesGroup = true when { - node.clipPathData.isNotEmpty() && config.usePathDataString -> flags.usesAddPathNodes = true - node.clipPathData.isNotEmpty() -> flags.usesPathData = true + node.clipPathData.isNotEmpty() && config.imageVector.usePathDataString -> { + flags.usesAddPathNodes = true + } + node.clipPathData.isNotEmpty() -> { + flags.usesPathData = true + } } node.nodes.forEach(::visit) } is IrVectorNode.IrPath -> { when { - config.usePathDataString -> flags.usesAddPathNodes = true + config.imageVector.usePathDataString -> flags.usesAddPathNodes = true else -> flags.usesPath = true } when (val fill = node.fill) { @@ -94,7 +99,7 @@ private data class UsageFlags( flags.usesOffset = true flags.usesColor = true val hasUnnamedColors = fill.colorStops.any { - it.irColor.toName() == null || !config.useComposeColors + it.irColor.toName() == null || !config.imageVector.useComposeColors } if (hasUnnamedColors) { flags.usesColor = true diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/NodeParams.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/NodeParams.kt similarity index 74% rename from components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/NodeParams.kt rename to sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/NodeParams.kt index ff4a7d167..98f0354be 100644 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/NodeParams.kt +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/NodeParams.kt @@ -1,9 +1,8 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.util -import io.github.composegears.valkyrie.generator.core.asPathDataString -import io.github.composegears.valkyrie.generator.core.asStatement -import io.github.composegears.valkyrie.generator.core.formatFloat -import io.github.composegears.valkyrie.generator.kmp.imagevector.ImageVectorRenderConfig +import io.github.composegears.valkyrie.generator.kt.common.ir.asStatement +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.util.formatFloat import io.github.composegears.valkyrie.sdk.ir.core.IrColor import io.github.composegears.valkyrie.sdk.ir.core.IrFill import io.github.composegears.valkyrie.sdk.ir.core.IrPathFillType @@ -11,10 +10,11 @@ import io.github.composegears.valkyrie.sdk.ir.core.IrStroke import io.github.composegears.valkyrie.sdk.ir.core.IrStrokeLineCap import io.github.composegears.valkyrie.sdk.ir.core.IrStrokeLineJoin import io.github.composegears.valkyrie.sdk.ir.core.IrVectorNode +import io.github.composegears.valkyrie.sdk.ir.core.toPathString +context(config: ImageVectorGeneratorConfig) internal fun collectGroupParams( node: IrVectorNode.IrGroup, - config: ImageVectorRenderConfig, level: Int, writer: CodeWriter, ): List { @@ -29,8 +29,8 @@ internal fun collectGroupParams( if (translationX != 0f) add("translationX = ${translationX.formatFloat()}") if (translationY != 0f) add("translationY = ${translationY.formatFloat()}") if (clipPathData.isNotEmpty()) { - if (config.usePathDataString) { - add("clipPathData = addPathNodes(\"${clipPathData.asPathDataString().escapeKotlin()}\")") + if (config.imageVector.usePathDataString) { + add("clipPathData = addPathNodes(\"${clipPathData.toPathString().escapeKotlin()}\")") } else { add( "clipPathData = PathData {\n" + @@ -45,10 +45,8 @@ internal fun collectGroupParams( } } -internal fun collectPathParams( - path: IrVectorNode.IrPath, - config: ImageVectorRenderConfig, -): List { +context(config: ImageVectorGeneratorConfig) +internal fun collectPathParams(path: IrVectorNode.IrPath): List { val params = mutableListOf() if (path.name.isNotEmpty()) { params += "name = \"${path.name.escapeKotlin()}\"" @@ -58,18 +56,18 @@ internal fun collectPathParams( null -> Unit is IrFill.Color -> { if (!fill.irColor.isTransparent()) { - params += "fill = SolidColor(${fill.irColor.renderColor(config)})" + params += "fill = SolidColor(${fill.irColor.renderColor()})" } } - is IrFill.LinearGradient -> params += fill.renderLinearGradient(config) - is IrFill.RadialGradient -> params += fill.renderRadialGradient(config) + is IrFill.LinearGradient -> params += fill.renderLinearGradient() + is IrFill.RadialGradient -> params += fill.renderRadialGradient() } if (path.fillAlpha != 1f) params += "fillAlpha = ${path.fillAlpha.formatFloat()}" val stroke = path.stroke if (stroke is IrStroke.Color && !stroke.irColor.isTransparent()) { - params += "stroke = SolidColor(${stroke.irColor.renderColor(config)})" + params += "stroke = SolidColor(${stroke.irColor.renderColor()})" } with(path) { if (strokeAlpha != 1f) params += "strokeAlpha = ${strokeAlpha.formatFloat()}" @@ -82,14 +80,15 @@ internal fun collectPathParams( return params } -internal fun IrColor.renderColor(config: ImageVectorRenderConfig): String { - val colorRef = if (config.fullQualifiedImports.color) { +context(config: ImageVectorGeneratorConfig) +internal fun IrColor.renderColor(): String { + val colorRef = if (config.fullyQualifiedImports.color) { "androidx.compose.ui.graphics.Color" } else { "Color" } val named = toName() - if (named != null && config.useComposeColors) { + if (named != null && config.imageVector.useComposeColors) { val alphaValue = alpha.toFloat() / 0xFF return if (alphaValue < 1f) { "$colorRef.$named.copy(alpha = ${alphaValue.formatFloat()})" @@ -102,17 +101,17 @@ internal fun IrColor.renderColor(config: ImageVectorRenderConfig): String { internal fun String.escapeKotlin(): String = replace("\\", "\\\\").replace("\"", "\\\"").replace("$", "\\$") +context(config: ImageVectorGeneratorConfig) private fun IrFill.Gradient.renderGradientParams( - config: ImageVectorRenderConfig, gradientType: String, extraParams: String, ): String { - val nestedIndent = " ".repeat(config.indentSize) + val nestedIndent = " ".repeat(config.codeStyle.indentSize) val deepNestedIndent = nestedIndent + nestedIndent val colorStops = colorStops.joinToString(separator = ",\n") { - "$deepNestedIndent${it.offset.formatFloat()} to ${it.irColor.renderColor(config)}" + "$deepNestedIndent${it.offset.formatFloat()} to ${it.irColor.renderColor()}" } - val brush = if (config.fullQualifiedImports.brush) { + val brush = if (config.fullyQualifiedImports.brush) { "androidx.compose.ui.graphics.Brush" } else { "Brush" @@ -124,24 +123,24 @@ private fun IrFill.Gradient.renderGradientParams( ")" } -private fun IrFill.LinearGradient.renderLinearGradient(config: ImageVectorRenderConfig): String { - val offset = if (config.fullQualifiedImports.offset) "androidx.compose.ui.geometry.Offset" else "Offset" - val nestedIndent = " ".repeat(config.indentSize) +context(config: ImageVectorGeneratorConfig) +private fun IrFill.LinearGradient.renderLinearGradient(): String { + val offset = if (config.fullyQualifiedImports.offset) "androidx.compose.ui.geometry.Offset" else "Offset" + val nestedIndent = " ".repeat(config.codeStyle.indentSize) return renderGradientParams( - config = config, gradientType = "linearGradient", extraParams = "${nestedIndent}start = $offset(${startX.formatFloat()}, ${startY.formatFloat()}),\n" + "${nestedIndent}end = $offset(${endX.formatFloat()}, ${endY.formatFloat()})\n", ) } -private fun IrFill.RadialGradient.renderRadialGradient(config: ImageVectorRenderConfig): String { - val offset = if (config.fullQualifiedImports.offset) "androidx.compose.ui.geometry.Offset" else "Offset" - val nestedIndent = " ".repeat(config.indentSize) +context(config: ImageVectorGeneratorConfig) +private fun IrFill.RadialGradient.renderRadialGradient(): String { + val offset = if (config.fullyQualifiedImports.offset) "androidx.compose.ui.geometry.Offset" else "Offset" + val nestedIndent = " ".repeat(config.codeStyle.indentSize) return renderGradientParams( - config = config, gradientType = "radialGradient", extraParams = "${nestedIndent}center = $offset(${centerX.formatFloat()}, ${centerY.formatFloat()}),\n" + "${nestedIndent}radius = ${radius.formatFloat()}\n", diff --git a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/RenderBlocks.kt b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/RenderBlocks.kt similarity index 96% rename from components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/RenderBlocks.kt rename to sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/RenderBlocks.kt index 6b1bf2c49..613f3f856 100644 --- a/components/generator/kmp/imagevector/src/commonMain/kotlin/io/github/composegears/valkyrie/generator/kmp/imagevector/util/RenderBlocks.kt +++ b/sdk/generator/kt/imagevector/kmp/src/commonMain/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/util/RenderBlocks.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.generator.kmp.imagevector.util +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.util internal fun writeBlockCall( writer: CodeWriter, diff --git a/sdk/generator/kt/imagevector/kmp/src/jvmTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ImageVectorParityJvmTest.kt b/sdk/generator/kt/imagevector/kmp/src/jvmTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ImageVectorParityJvmTest.kt new file mode 100644 index 000000000..44559c902 --- /dev/null +++ b/sdk/generator/kt/imagevector/kmp/src/jvmTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ImageVectorParityJvmTest.kt @@ -0,0 +1,253 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp + +import assertk.assertThat +import assertk.assertions.contains +import assertk.assertions.isEqualTo +import io.github.composegears.valkyrie.parser.unified.ParserType +import io.github.composegears.valkyrie.parser.unified.SvgXmlParser +import io.github.composegears.valkyrie.parser.unified.ext.toIOPath +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText +import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedClass +import org.junit.jupiter.params.provider.EnumSource + +@ParameterizedClass +@EnumSource(value = OutputFormat::class) +class ImageVectorParityJvmTest( + private val outputFormat: OutputFormat, +) { + + @Test + fun `basic generation parity`() { + assertParity( + iconPath = "imagevector/xml/ic_without_path.xml", + configTransform = { iconName -> + ImageVectorGeneratorConfig.simple( + iconName = iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ) + }, + backingExpected = "imagevector/kt/backing/WithoutPath.kt", + lazyExpected = "imagevector/kt/lazy/WithoutPath.kt", + ) + } + + @Test + fun `nested pack parity`() { + assertParity( + iconPath = "imagevector/xml/ic_without_path.xml", + configTransform = { iconName -> + ImageVectorGeneratorConfig.iconPack( + iconName = iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Colored") + }, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ) + }, + backingExpected = "imagevector/kt/backing/WithoutPath.pack.nested.kt", + lazyExpected = "imagevector/kt/lazy/WithoutPath.pack.nested.kt", + ) + } + + @Test + fun `deep nested pack parity`() { + val parserOutput = SvgXmlParser.toIrImageVector( + parser = ParserType.Jvm, + path = getResourcePath("imagevector/xml/ic_without_path.xml").toIOPath(), + ) + val output = ImageVectorGenerator.convert( + vector = parserOutput.irImageVector, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") + } + }, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ), + ).content + + assertThat(output).contains("val ValkyrieIcons.Material.Rounded.WithoutPath: ImageVector") + assertThat(output).contains("""name = "Rounded.WithoutPath"""") + assertThat(output).contains("package io.github.composegears.valkyrie.icons.material.rounded") + } + + @Test + fun `trailing comma parity`() { + assertParity( + iconPath = "imagevector/xml/ic_fill_color_stroke.xml", + configTransform = { iconName -> + ImageVectorGeneratorConfig.iconPack( + iconName = iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + addTrailingComma = true, + ), + ) + }, + backingExpected = "imagevector/kt/backing/FillColorStroke.trailing.kt", + lazyExpected = "imagevector/kt/lazy/FillColorStroke.trailing.kt", + ) + } + + @Test + fun `pathData mode parity`() { + assertParity( + iconPath = "imagevector/xml/ic_only_path.xml", + configTransform = { iconName -> + ImageVectorGeneratorConfig.iconPack( + iconName = iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + usePathDataString = true, + ), + ) + }, + backingExpected = "imagevector/kt/backing/OnlyPathWithPathData.kt", + lazyExpected = "imagevector/kt/lazy/OnlyPathWithPathData.kt", + ) + } + + @Test + fun `clip path builder parity`() { + assertParity( + iconPath = "imagevector/svg/ic_clip_path_gradient.svg", + configTransform = { iconName -> + ImageVectorGeneratorConfig.simple( + iconName = iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + ) + }, + backingExpected = "imagevector/kt/backing/ClipPathGradient.kt", + lazyExpected = "imagevector/kt/lazy/ClipPathGradient.kt", + ) + } + + @Test + fun `preview parity`() { + assertParity( + iconPath = "imagevector/xml/ic_without_path.xml", + configTransform = { iconName -> + ImageVectorGeneratorConfig.iconPack( + iconName = iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + generatePreview = true, + ), + ) + }, + backingExpected = "imagevector/kt/backing/WithoutPath.pack.nested.preview.androidx.kt", + lazyExpected = "imagevector/kt/lazy/WithoutPath.pack.nested.preview.androidx.kt", + ) + } + + @Test + fun `full qualified imports parity`() { + assertParity( + iconPath = "imagevector/xml/ic_full_qualified.xml", + configTransform = { iconName -> + ImageVectorGeneratorConfig.simple( + iconName = iconName, + packageName = DEFAULT_PACKAGE, + imageVector = ImageVectorConfig(outputFormat = outputFormat), + fullyQualifiedImports = FullyQualifiedImports(brush = true), + ) + }, + backingExpected = "imagevector/kt/backing/FullQualified.brush.kt", + lazyExpected = "imagevector/kt/lazy/FullQualified.brush.kt", + ) + } + + @Test + fun `deep nested generates correct receiver and builder name`() { + val parserOutput = SvgXmlParser.toIrImageVector( + parser = ParserType.Jvm, + path = getResourcePath("imagevector/xml/ic_flat_package.xml").toIOPath(), + ) + val output = ImageVectorGenerator.convert( + vector = parserOutput.irImageVector, + config = ImageVectorGeneratorConfig.iconPack( + iconName = parserOutput.iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") + } + }, + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useFlatPackage = true, + ), + ), + ).content + + assertThat(output).contains("val ValkyrieIcons.Material.Rounded.FlatPackage: ImageVector") + assertThat(output).contains("""name = "Rounded.FlatPackage"""") + } + + @Test + fun `suppress unused receiver parity`() { + assertParity( + iconPath = "imagevector/xml/ic_without_path.xml", + configTransform = { iconName -> + ImageVectorGeneratorConfig.iconPack( + iconName = iconName, + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + suppressUnusedReceiverWarning = true, + ), + ) + }, + backingExpected = "imagevector/kt/backing/WithoutPath.pack.suppress_receiver.kt", + lazyExpected = "imagevector/kt/lazy/WithoutPath.pack.suppress_receiver.kt", + ) + } + + private fun assertParity( + iconPath: String, + configTransform: (iconName: String) -> ImageVectorGeneratorConfig, + backingExpected: String, + lazyExpected: String, + ) { + val parserOutput = SvgXmlParser.toIrImageVector( + parser = ParserType.Jvm, + path = getResourcePath(iconPath).toIOPath(), + ) + + val output = ImageVectorGenerator.convert( + vector = parserOutput.irImageVector, + config = configTransform(parserOutput.iconName), + ).content + + val expected = outputFormat.toResourceText( + pathToBackingProperty = backingExpected, + pathToLazyProperty = lazyExpected, + ) + + assertThat(output).isEqualTo(expected) + } +} diff --git a/sdk/generator/kt/imagevector/kmp/src/jvmTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ResolveReceiverNameTest.kt b/sdk/generator/kt/imagevector/kmp/src/jvmTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ResolveReceiverNameTest.kt new file mode 100644 index 000000000..0d17fbb1d --- /dev/null +++ b/sdk/generator/kt/imagevector/kmp/src/jvmTest/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/kmp/ResolveReceiverNameTest.kt @@ -0,0 +1,58 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp + +import assertk.assertThat +import assertk.assertions.isEqualTo +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.kmp.common.resolveReceiverName +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.DEFAULT_PACKAGE +import org.junit.jupiter.api.Test + +class ResolveReceiverNameTest { + + @Test + fun `no iconPackTree returns empty`() { + val config = ImageVectorGeneratorConfig.simple( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + ) + assertThat(config.resolveReceiverName()).isEqualTo("") + } + + @Test + fun `root-only returns root`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons"), + ) + assertThat(config.resolveReceiverName()).isEqualTo("ValkyrieIcons") + } + + @Test + fun `one nested level returns root dot child`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Filled") + }, + ) + assertThat(config.resolveReceiverName()).isEqualTo("ValkyrieIcons.Filled") + } + + @Test + fun `two nested levels returns full path`() { + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "Add", + packageName = DEFAULT_PACKAGE, + iconPackTree = buildTree("ValkyrieIcons") { + child("Material") { + child("Rounded") + } + }, + ) + assertThat(config.resolveReceiverName()).isEqualTo("ValkyrieIcons.Material.Rounded") + } +} diff --git a/sdk/generator/kt/imagevector/test-fixtures/build.gradle.kts b/sdk/generator/kt/imagevector/test-fixtures/build.gradle.kts new file mode 100644 index 000000000..74071979d --- /dev/null +++ b/sdk/generator/kt/imagevector/test-fixtures/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + alias(libs.plugins.valkyrie.jvm) + alias(libs.plugins.valkyrie.explicit.api) +} + +dependencies { + implementation(projects.sdk.generator.kt.imagevector.common) + implementation(projects.sdk.test.resourceLoader) +} diff --git a/sdk/generator/kt/imagevector/test-fixtures/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/testfixtures/Defaults.kt b/sdk/generator/kt/imagevector/test-fixtures/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/testfixtures/Defaults.kt new file mode 100644 index 000000000..78421825c --- /dev/null +++ b/sdk/generator/kt/imagevector/test-fixtures/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/testfixtures/Defaults.kt @@ -0,0 +1,3 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures + +public const val DEFAULT_PACKAGE: String = "io.github.composegears.valkyrie.icons" diff --git a/sdk/generator/kt/imagevector/test-fixtures/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/testfixtures/OutputFormatResource.kt b/sdk/generator/kt/imagevector/test-fixtures/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/testfixtures/OutputFormatResource.kt new file mode 100644 index 000000000..7d7e14760 --- /dev/null +++ b/sdk/generator/kt/imagevector/test-fixtures/src/main/kotlin/io/github/composegears/valkyrie/sdk/generator/kt/imagevector/testfixtures/OutputFormatResource.kt @@ -0,0 +1,14 @@ +package io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures + +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat.BackingProperty +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat.LazyProperty +import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourceText + +public fun OutputFormat.toResourceText( + pathToBackingProperty: String, + pathToLazyProperty: String, +): String = when (this) { + BackingProperty -> getResourceText(pathToBackingProperty) + LazyProperty -> getResourceText(pathToLazyProperty) +} diff --git a/sdk/intellij/psi/iconpack/api/iconpack.api b/sdk/intellij/psi/iconpack/api/iconpack.api index 3644249f6..c25fb0de3 100644 --- a/sdk/intellij/psi/iconpack/api/iconpack.api +++ b/sdk/intellij/psi/iconpack/api/iconpack.api @@ -1,13 +1,13 @@ public final class io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo { - public fun (Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lio/github/composegears/valkyrie/generator/core/IconPack; + public final fun component2 ()Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo; - public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;Ljava/lang/String;Lio/github/composegears/valkyrie/generator/core/IconPack;Ljava/lang/String;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo; + public final fun copy (Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo; + public static synthetic fun copy$default (Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo;Ljava/lang/String;Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode;Ljava/lang/String;ILjava/lang/Object;)Lio/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackInfo; public fun equals (Ljava/lang/Object;)Z - public final fun getIconPack ()Lio/github/composegears/valkyrie/generator/core/IconPack; + public final fun getIconPackTree ()Lio/github/composegears/valkyrie/sdk/core/tree/TreeNode; public final fun getLicense ()Ljava/lang/String; public final fun getPackageName ()Ljava/lang/String; public fun hashCode ()I diff --git a/sdk/intellij/psi/iconpack/build.gradle.kts b/sdk/intellij/psi/iconpack/build.gradle.kts index 6a8d6e3b7..5479e9054 100644 --- a/sdk/intellij/psi/iconpack/build.gradle.kts +++ b/sdk/intellij/psi/iconpack/build.gradle.kts @@ -12,7 +12,7 @@ tasks.test { } dependencies { - implementation(projects.components.generator.core) + implementation(projects.sdk.generator.kt.common.iconpackTree) testImplementation(testFixtures(projects.sdk.intellij.testFixtures)) testImplementation(projects.sdk.test.resourceLoader) diff --git a/sdk/intellij/psi/iconpack/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParser.kt b/sdk/intellij/psi/iconpack/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParser.kt index 4d8313a38..fc5564b7e 100644 --- a/sdk/intellij/psi/iconpack/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParser.kt +++ b/sdk/intellij/psi/iconpack/src/main/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParser.kt @@ -1,13 +1,16 @@ package io.github.composegears.valkyrie.sdk.intellij.psi.iconpack import com.intellij.psi.PsiComment -import io.github.composegears.valkyrie.generator.core.IconPack +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.core.tree.toMutableTree +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtObjectDeclaration data class IconPackInfo( val packageName: String, - val iconPack: IconPack, + val iconPackTree: IconPackTree, val license: String? = null, ) @@ -28,13 +31,13 @@ object IconPackPsiParser { return iconPack?.let { IconPackInfo( packageName = ktFile.packageFqName.asString(), - iconPack = it, + iconPackTree = it, license = license, ) } } - private fun buildIconPack(objectDeclaration: KtObjectDeclaration): IconPack? { + private fun buildIconPack(objectDeclaration: KtObjectDeclaration): IconPackTree? { val name = objectDeclaration.name ?: return null val nestedObjects = objectDeclaration.body?.declarations @@ -42,9 +45,10 @@ object IconPackPsiParser { ?.mapNotNull { buildIconPack(it) } .orEmpty() - return IconPack( - name = name, - nested = nestedObjects, - ) + return buildTree(name) { + nestedObjects.forEach { + child(it.toMutableTree()) + } + } } } diff --git a/sdk/intellij/psi/iconpack/src/test/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParserTest.kt b/sdk/intellij/psi/iconpack/src/test/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParserTest.kt index c016d36dc..4bcf72248 100644 --- a/sdk/intellij/psi/iconpack/src/test/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParserTest.kt +++ b/sdk/intellij/psi/iconpack/src/test/kotlin/io/github/composegears/valkyrie/sdk/intellij/psi/iconpack/IconPackPsiParserTest.kt @@ -5,7 +5,10 @@ import assertk.assertions.containsExactly import assertk.assertions.isEqualTo import assertk.assertions.isNotNull import com.intellij.testFramework.runInEdtAndGet -import io.github.composegears.valkyrie.generator.core.IconPack +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.encode +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.iconPackOf +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.navigate import io.github.composegears.valkyrie.sdk.intellij.testfixtures.KotlinCodeInsightTest import org.junit.jupiter.api.Test @@ -19,8 +22,8 @@ class IconPackPsiParserTest : KotlinCodeInsightTest() { assertThat(iconPackInfo).isNotNull().transform { packInfo -> assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi") - assertThat(packInfo.iconPack.name).isEqualTo("SimpleIconPack") - assertThat(packInfo.iconPack.nested.size).isEqualTo(0) + assertThat(packInfo.iconPackTree.data).isEqualTo("SimpleIconPack") + assertThat(packInfo.iconPackTree.children.size).isEqualTo(0) } } } @@ -33,9 +36,9 @@ class IconPackPsiParserTest : KotlinCodeInsightTest() { assertThat(iconPackInfo).isNotNull().transform { packInfo -> assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi") - assertThat(packInfo.iconPack.name).isEqualTo("NestedIconPack") - assertThat(packInfo.iconPack.nested.size).isEqualTo(5) - assertThat(packInfo.iconPack.nested.map { it.name }).containsExactly( + assertThat(packInfo.iconPackTree.data).isEqualTo("NestedIconPack") + assertThat(packInfo.iconPackTree.children.size).isEqualTo(5) + assertThat(packInfo.iconPackTree.children.map { it.data }).containsExactly( "Filled", "Outlined", "TwoTone", @@ -54,8 +57,8 @@ class IconPackPsiParserTest : KotlinCodeInsightTest() { assertThat(iconPackInfo).isNotNull().transform { packInfo -> assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi") - assertThat(packInfo.iconPack.name).isEqualTo("DataObjectIconPack") - assertThat(packInfo.iconPack.nested.size).isEqualTo(0) + assertThat(packInfo.iconPackTree.data).isEqualTo("DataObjectIconPack") + assertThat(packInfo.iconPackTree.children.size).isEqualTo(0) } } } @@ -67,50 +70,34 @@ class IconPackPsiParserTest : KotlinCodeInsightTest() { val iconPackInfo = IconPackPsiParser.parse(ktFile) assertThat(iconPackInfo).isNotNull().transform { packInfo -> + val tree = packInfo.iconPackTree + assertThat(packInfo.packageName).isEqualTo("io.github.composegears.valkyrie.psi") - packInfo.iconPack.assertStructure( - expectedName = "DeepNestedIconPack", - expectedNestedCount = 4, - expectedNestedNames = listOf("Level1", "Branch", "Wide", "Single"), - ) + tree.assertStructure("DeepNestedIconPack", listOf("Level1", "Branch", "Wide", "Single")) // Verify deep linear chain: Level1 -> Level2 -> Level3 -> Level4 -> Level5 - packInfo.iconPack.navigate("Level1").assertStructure("Level1", 1) - packInfo.iconPack.navigate("Level1.Level2").assertStructure("Level2", 1) - packInfo.iconPack.navigate("Level1.Level2.Level3").assertStructure("Level3", 1) - packInfo.iconPack.navigate("Level1.Level2.Level3.Level4").assertStructure("Level4", 1) - packInfo.iconPack.navigate("Level1.Level2.Level3.Level4.Level5").assertStructure("Level5", 0) + tree.navigate("Level1").assertStructure("Level1", 1) + tree.navigate("Level1.Level2").assertStructure("Level2", 1) + tree.navigate("Level1.Level2.Level3").assertStructure("Level3", 1) + tree.navigate("Level1.Level2.Level3.Level4").assertStructure("Level4", 1) + tree.navigate("Level1.Level2.Level3.Level4.Level5").assertStructure("Level5", 0) // Verify Branch with multiple sub-branches - packInfo.iconPack.navigate("Branch").assertStructure( - expectedName = "Branch", - expectedNestedCount = 3, - expectedNestedNames = listOf("Left", "Middle", "Right"), - ) - packInfo.iconPack.navigate("Branch.Left").assertStructure("Left", 1) - packInfo.iconPack.navigate("Branch.Left.LeftDeep1").assertStructure("LeftDeep1", 1) - packInfo.iconPack.navigate("Branch.Left.LeftDeep1.LeftDeep2").assertStructure("LeftDeep2", 0) - packInfo.iconPack.navigate("Branch.Middle").assertStructure("Middle", 0) - packInfo.iconPack.navigate("Branch.Right") - .assertStructure( - expectedName = "Right", - expectedNestedCount = 2, - expectedNestedNames = listOf("RightDeep1", "RightDeep2"), - ) + tree.navigate("Branch").assertStructure("Branch", listOf("Left", "Middle", "Right")) + tree.navigate("Branch.Left").assertStructure("Left", 1) + tree.navigate("Branch.Left.LeftDeep1").assertStructure("LeftDeep1", 1) + tree.navigate("Branch.Left.LeftDeep1.LeftDeep2").assertStructure("LeftDeep2", 0) + tree.navigate("Branch.Middle").assertStructure("Middle", 0) + tree.navigate("Branch.Right").assertStructure("Right", listOf("RightDeep1", "RightDeep2")) // Verify wide tree with multiple children - packInfo.iconPack.navigate("Wide") - .assertStructure( - expectedName = "Wide", - expectedNestedCount = 5, - expectedNestedNames = listOf("Item1", "Item2", "Item3", "Item4", "Item5"), - ) + tree.navigate("Wide").assertStructure("Wide", listOf("Item1", "Item2", "Item3", "Item4", "Item5")) // Verify single leaf node - packInfo.iconPack.navigate("Single").assertStructure("Single", 0) + tree.navigate("Single").assertStructure("Single", 0) // Verify toRawString produces correct paths - val rawString = IconPack.toRawString(packInfo.iconPack) + val rawString = tree.encode() val expectedPaths = listOf( "DeepNestedIconPack.Level1.Level2.Level3.Level4.Level5", "DeepNestedIconPack.Branch.Left.LeftDeep1.LeftDeep2", @@ -127,8 +114,8 @@ class IconPackPsiParserTest : KotlinCodeInsightTest() { assertThat(rawString).isEqualTo(expectedPaths.joinToString(",")) // Verify round-trip conversion (toRawString -> fromString) - val reconstructed = IconPack.fromString(rawString) - assertThat(reconstructed).isEqualTo(packInfo.iconPack) + val reconstructed = iconPackOf(rawString) + assertThat(reconstructed).isEqualTo(tree) } } } @@ -158,33 +145,20 @@ class IconPackPsiParserTest : KotlinCodeInsightTest() { assertThat(iconPackInfo).isNotNull().transform { packInfo -> assertThat(packInfo.packageName).isEqualTo("com.test") - assertThat(packInfo.iconPack.name).isEqualTo("Symbols") - assertThat(packInfo.iconPack.nested.size).isEqualTo(0) + assertThat(packInfo.iconPackTree.data).isEqualTo("Symbols") + assertThat(packInfo.iconPackTree.children.size).isEqualTo(0) assertThat(packInfo.license).isEqualTo(expectedLicense) } } } - private fun IconPack.navigate(path: String): IconPack { - val parts = path.split('.') - var current = this - - parts.forEach { part -> - current = current.nested.first { it.name == part } - } - - return current + private fun IconPackTree.assertStructure(expectedName: String, expectedNestedCount: Int) { + assertThat(data).isEqualTo(expectedName) + assertThat(children.size).isEqualTo(expectedNestedCount) } - private fun IconPack.assertStructure( - expectedName: String, - expectedNestedCount: Int, - expectedNestedNames: List = emptyList(), - ) { - assertThat(name).isEqualTo(expectedName) - assertThat(nested.size).isEqualTo(expectedNestedCount) - if (expectedNestedNames.isNotEmpty()) { - assertThat(nested.map { it.name }).containsExactly(*expectedNestedNames.toTypedArray()) - } + private fun IconPackTree.assertStructure(expectedName: String, expectedNestedNames: List) { + assertThat(data).isEqualTo(expectedName) + assertThat(children.map { it.data }).containsExactly(*expectedNestedNames.toTypedArray()) } } diff --git a/sdk/test/coverage/build.gradle.kts b/sdk/test/coverage/build.gradle.kts index 8e1f4dbd7..eea4e8c94 100644 --- a/sdk/test/coverage/build.gradle.kts +++ b/sdk/test/coverage/build.gradle.kts @@ -5,14 +5,17 @@ plugins { // include only necessary projects for the test coverage dependencies { kover(projects.tools.cli) - kover(projects.components.generator.core) - kover(projects.components.generator.jvm.poetExtensions) - kover(projects.components.generator.iconpack) - kover(projects.components.generator.jvm.imagevector) kover(projects.components.parser.jvm.svg) kover(projects.components.parser.jvm.xml) kover(projects.components.parser.kmp.xml) kover(projects.components.parser.unified) + kover(projects.sdk.generator.kt.common.iconpackTree) + kover(projects.sdk.generator.kt.common.ir) + kover(projects.sdk.generator.kt.common.poetExtensions) + kover(projects.sdk.generator.kt.common.util) + kover(projects.sdk.generator.kt.iconpack) + kover(projects.sdk.generator.kt.imagevector.common) + kover(projects.sdk.generator.kt.imagevector.jvm) kover(projects.sdk.intellij.psi.iconpack) kover(projects.sdk.intellij.psi.imagevector) kover(projects.sdk.ir.compose) diff --git a/sdk/test/sharedTestResources/imagevector/kt/backing/FlatPackage.pack.deep.nested.kt b/sdk/test/sharedTestResources/imagevector/kt/backing/FlatPackage.pack.deep.nested.kt new file mode 100644 index 000000000..fdab50f22 --- /dev/null +++ b/sdk/test/sharedTestResources/imagevector/kt/backing/FlatPackage.pack.deep.nested.kt @@ -0,0 +1,23 @@ +package io.github.composegears.valkyrie.icons + +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.Material.Rounded.FlatPackage: ImageVector + get() { + if (_FlatPackage != null) { + return _FlatPackage!! + } + _FlatPackage = ImageVector.Builder( + name = "Rounded.FlatPackage", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 18f, + viewportHeight = 18f + ).build() + + return _FlatPackage!! + } + +@Suppress("ObjectPropertyName") +private var _FlatPackage: ImageVector? = null diff --git a/sdk/test/sharedTestResources/imagevector/kt/backing/WithoutPath.pack.deep.nested.kt b/sdk/test/sharedTestResources/imagevector/kt/backing/WithoutPath.pack.deep.nested.kt new file mode 100644 index 000000000..0fa736f44 --- /dev/null +++ b/sdk/test/sharedTestResources/imagevector/kt/backing/WithoutPath.pack.deep.nested.kt @@ -0,0 +1,24 @@ +package io.github.composegears.valkyrie.icons.material.rounded + +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import io.github.composegears.valkyrie.icons.ValkyrieIcons + +val ValkyrieIcons.Material.Rounded.WithoutPath: ImageVector + get() { + if (_WithoutPath != null) { + return _WithoutPath!! + } + _WithoutPath = ImageVector.Builder( + name = "Rounded.WithoutPath", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 18f, + viewportHeight = 18f + ).build() + + return _WithoutPath!! + } + +@Suppress("ObjectPropertyName") +private var _WithoutPath: ImageVector? = null diff --git a/sdk/test/sharedTestResources/imagevector/kt/lazy/FlatPackage.pack.deep.nested.kt b/sdk/test/sharedTestResources/imagevector/kt/lazy/FlatPackage.pack.deep.nested.kt new file mode 100644 index 000000000..df6eaa77b --- /dev/null +++ b/sdk/test/sharedTestResources/imagevector/kt/lazy/FlatPackage.pack.deep.nested.kt @@ -0,0 +1,15 @@ +package io.github.composegears.valkyrie.icons + +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp + +val ValkyrieIcons.Material.Rounded.FlatPackage: ImageVector by + lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.FlatPackage", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 18f, + viewportHeight = 18f + ).build() +} diff --git a/sdk/test/sharedTestResources/imagevector/kt/lazy/WithoutPath.pack.deep.nested.kt b/sdk/test/sharedTestResources/imagevector/kt/lazy/WithoutPath.pack.deep.nested.kt new file mode 100644 index 000000000..c6dff9dc3 --- /dev/null +++ b/sdk/test/sharedTestResources/imagevector/kt/lazy/WithoutPath.pack.deep.nested.kt @@ -0,0 +1,16 @@ +package io.github.composegears.valkyrie.icons.material.rounded + +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import io.github.composegears.valkyrie.icons.ValkyrieIcons + +val ValkyrieIcons.Material.Rounded.WithoutPath: ImageVector by + lazy(LazyThreadSafetyMode.NONE) { + ImageVector.Builder( + name = "Rounded.WithoutPath", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 18f, + viewportHeight = 18f + ).build() +} diff --git a/settings.gradle.kts b/settings.gradle.kts index ebd235cf0..02e4fe8e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -70,11 +70,6 @@ include("tools:compose-app") include("tools:gradle-plugin") include("tools:idea-plugin") -include("components:generator:core") -include("components:generator:kmp:imagevector") -include("components:generator:iconpack") -include("components:generator:jvm:poet-extensions") -include("components:generator:jvm:imagevector") include("components:parser:jvm:svg") include("components:parser:jvm:xml") include("components:parser:kmp:xml") @@ -87,7 +82,17 @@ include("sdk:compose:icons") include("sdk:compose:foundation") include("sdk:figma:converter") include("sdk:core:extensions") +include("sdk:core:tree") include("sdk:core:xml") +include("sdk:generator:kt:common:iconpack-tree") +include("sdk:generator:kt:common:ir") +include("sdk:generator:kt:common:util") +include("sdk:generator:kt:common:poet-extensions") +include("sdk:generator:kt:iconpack") +include("sdk:generator:kt:imagevector:common") +include("sdk:generator:kt:imagevector:jvm") +include("sdk:generator:kt:imagevector:kmp") +include("sdk:generator:kt:imagevector:test-fixtures") include("sdk:generator:xml") include("sdk:intellij:psi:iconpack") include("sdk:intellij:psi:imagevector") diff --git a/tools/cli/CHANGELOG.md b/tools/cli/CHANGELOG.md index 87a8f4f31..383428780 100644 --- a/tools/cli/CHANGELOG.md +++ b/tools/cli/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Changed + +- Migrate generator to new SDK architecture with improved IconPackTree structure for better hierarchical icon pack support + ## 1.2.0 - 2026-05-29 ### Added diff --git a/tools/cli/build.gradle.kts b/tools/cli/build.gradle.kts index 7754e7177..a703e8745 100644 --- a/tools/cli/build.gradle.kts +++ b/tools/cli/build.gradle.kts @@ -1,3 +1,7 @@ +import org.gradle.declarative.dsl.schema.FqName.Empty.packageName +import org.gradle.internal.classpath.Instrumented.systemProperty +import org.gradle.internal.execution.caching.CachingState.enabled + plugins { alias(libs.plugins.kotlin.jvm) alias(libs.plugins.buildconfig) @@ -98,10 +102,11 @@ changelog { } dependencies { - implementation(projects.components.generator.iconpack) - implementation(projects.components.generator.jvm.imagevector) implementation(projects.components.parser.unified) implementation(projects.sdk.core.extensions) + implementation(projects.sdk.generator.kt.common.iconpackTree) + implementation(projects.sdk.generator.kt.imagevector.jvm) + implementation(projects.sdk.generator.kt.iconpack) implementation(projects.sdk.ir.core) implementation(kotlin("stdlib")) @@ -109,6 +114,7 @@ dependencies { implementation(cli.clikt.markdown) r8(cli.r8) + testImplementation(projects.sdk.generator.kt.imagevector.testFixtures) testImplementation(projects.sdk.test.resourceLoader) testImplementation(libs.bundles.test) testImplementation(libs.kotlin.test) diff --git a/tools/cli/src/main/kotlin/io/github/composegears/valkyrie/cli/command/IconPackCommand.kt b/tools/cli/src/main/kotlin/io/github/composegears/valkyrie/cli/command/IconPackCommand.kt index 443dc4d37..9ba3afd09 100644 --- a/tools/cli/src/main/kotlin/io/github/composegears/valkyrie/cli/command/IconPackCommand.kt +++ b/tools/cli/src/main/kotlin/io/github/composegears/valkyrie/cli/command/IconPackCommand.kt @@ -12,10 +12,11 @@ import io.github.composegears.valkyrie.cli.ext.intOption import io.github.composegears.valkyrie.cli.ext.outputInfo import io.github.composegears.valkyrie.cli.ext.requiredPathOption import io.github.composegears.valkyrie.cli.ext.requiredStringOption -import io.github.composegears.valkyrie.generator.core.IconPack -import io.github.composegears.valkyrie.generator.iconpack.IconPackGenerator -import io.github.composegears.valkyrie.generator.iconpack.IconPackGeneratorConfig import io.github.composegears.valkyrie.sdk.core.extensions.writeToKt +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGenerator +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.iconPackOf import java.nio.file.Path import kotlin.io.path.absolutePathString @@ -43,12 +44,12 @@ internal class IconPackCommand : CliktCommand(name = "iconpack") { help = "Package name of IconPack object", ) - private val iconPack by option( + private val iconPackTree by option( "--iconpack", help = "Simple or hierarchical icon pack structure (e.g. 'MyIconPack' or 'MyIconPack.Filled,MyIconPack.Outlined')", ).convert { - IconPack.fromString(it) - }.default(IconPack(name = "")) + iconPackOf(it) + }.default(iconPackOf("")) private val indentSize by intOption( "--indent-size", @@ -69,7 +70,7 @@ internal class IconPackCommand : CliktCommand(name = "iconpack") { generateIconPack( outputPath = outputPath, packageName = packageName, - iconPack = iconPack, + iconPackTree = iconPackTree, useExplicitMode = useExplicitMode, indentSize = indentSize, ) @@ -78,7 +79,7 @@ internal class IconPackCommand : CliktCommand(name = "iconpack") { private fun generateIconPack( outputPath: Path, - iconPack: IconPack, + iconPackTree: IconPackTree, useExplicitMode: Boolean, indentSize: Int, packageName: String, @@ -86,7 +87,7 @@ private fun generateIconPack( IconPackGenerator.create( config = IconPackGeneratorConfig( packageName = packageName, - iconPack = iconPack, + iconPackTree = iconPackTree, useExplicitMode = useExplicitMode, indentSize = indentSize, ), diff --git a/tools/cli/src/main/kotlin/io/github/composegears/valkyrie/cli/command/SvgXmlToImageVectorCommand.kt b/tools/cli/src/main/kotlin/io/github/composegears/valkyrie/cli/command/SvgXmlToImageVectorCommand.kt index c7e077e1e..26b417352 100644 --- a/tools/cli/src/main/kotlin/io/github/composegears/valkyrie/cli/command/SvgXmlToImageVectorCommand.kt +++ b/tools/cli/src/main/kotlin/io/github/composegears/valkyrie/cli/command/SvgXmlToImageVectorCommand.kt @@ -15,12 +15,6 @@ import io.github.composegears.valkyrie.cli.ext.outputError import io.github.composegears.valkyrie.cli.ext.outputInfo import io.github.composegears.valkyrie.cli.ext.requiredPathOption import io.github.composegears.valkyrie.cli.ext.requiredStringOption -import io.github.composegears.valkyrie.cli.ext.stringOption -import io.github.composegears.valkyrie.generator.jvm.imagevector.FullQualifiedImports -import io.github.composegears.valkyrie.generator.jvm.imagevector.FullQualifiedImports.Companion.reservedComposeQualifiers -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGenerator -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGeneratorConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.isSvg @@ -28,6 +22,16 @@ import io.github.composegears.valkyrie.parser.unified.ext.isXml import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.parser.unified.util.IconNameFormatter import io.github.composegears.valkyrie.sdk.core.extensions.writeToKt +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.iconPackOf +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.pathSegments +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports.Companion.reservedComposeTypeNames +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.ImageVectorGenerator import java.nio.file.Path import kotlin.io.path.absolutePathString import kotlin.io.path.isDirectory @@ -64,17 +68,10 @@ internal class SvgXmlToImageVectorCommand : CliktCommand(name = "svgxml2imagevec help = "The package name of the generated sources (usually equal to IconPack package)", ) - private val iconPackName by stringOption( - "--iconpack-name", - help = "The name of the existing IconPack", - default = "", - ) - - private val nestedPackName by stringOption( - "--nested-pack-name", - help = "The name of the existing nested IconPack", - default = "", - ) + private val iconPackTree by option( + "--iconpack", + help = "Icon pack structure (e.g. 'ValkyrieIcons' or 'ValkyrieIcons.Filled')", + ).convert { iconPackOf(it) } private val outputFormat by outputFormatOption() @@ -140,8 +137,7 @@ internal class SvgXmlToImageVectorCommand : CliktCommand(name = "svgxml2imagevec inputPath = inputPath, outputPath = outputPath, packageName = packageName, - iconPackName = iconPackName, - nestedPackName = nestedPackName, + iconPackTree = iconPackTree, generatePreview = generatePreview, outputFormat = outputFormat, useComposeColors = useComposeColors, @@ -175,8 +171,7 @@ private fun svgXml2ImageVector( inputPath: Path, outputPath: Path, packageName: String, - iconPackName: String, - nestedPackName: String, + iconPackTree: IconPackTree?, generatePreview: Boolean, outputFormat: OutputFormat, useComposeColors: Boolean, @@ -216,12 +211,12 @@ private fun svgXml2ImageVector( val iconNames = iconPaths.map { IconNameFormatter.format(name = it.name) } - val fullQualifiedNames = iconNames.filter { reservedComposeQualifiers.contains(it) } + val fullyQualifiedNames = iconNames.filter { it in reservedComposeTypeNames } - if (fullQualifiedNames.isNotEmpty()) { + if (fullyQualifiedNames.isNotEmpty()) { outputInfo( "Found icons names that conflict with reserved Compose qualifiers. " + - "Full qualified import will be used for: ${fullQualifiedNames.joinToString(", ")}", + "Full qualified import will be used for: ${fullyQualifiedNames.joinToString(", ")}", ) } @@ -259,26 +254,42 @@ private fun svgXml2ImageVector( ) } - val config = ImageVectorGeneratorConfig( - packageName = packageName, - iconPackPackage = packageName, - packName = iconPackName, - nestedPackName = nestedPackName, + val codeStyle = CodeStyleConfig( + useExplicitMode = useExplicitMode, + indentSize = indentSize, + ) + val imageVector = ImageVectorConfig( outputFormat = outputFormat, useComposeColors = useComposeColors, generatePreview = generatePreview, useFlatPackage = useFlatPackage, - useExplicitMode = useExplicitMode, addTrailingComma = addTrailingComma, usePathDataString = usePathDataString, - indentSize = indentSize, suppressUnusedReceiverWarning = suppressUnusedReceiverWarning, - fullQualifiedImports = FullQualifiedImports( - brush = "Brush" in fullQualifiedNames, - color = "Color" in fullQualifiedNames, - offset = "Offset" in fullQualifiedNames, - ), ) + val fullyQualifiedImports = FullyQualifiedImports.from(fullyQualifiedNames) + val config = when { + iconPackTree != null -> { + ImageVectorGeneratorConfig.iconPack( + iconName = "", + packageName = packageName, + iconPackPackage = packageName, + iconPackTree = iconPackTree, + codeStyle = codeStyle, + imageVector = imageVector, + fullyQualifiedImports = fullyQualifiedImports, + ) + } + else -> { + ImageVectorGeneratorConfig.simple( + iconName = "", + packageName = packageName, + codeStyle = codeStyle, + imageVector = imageVector, + fullyQualifiedImports = fullyQualifiedImports, + ) + } + } iconPaths .sortedBy { it.name } @@ -295,15 +306,17 @@ private fun svgXml2ImageVector( val vectorSpecOutput = ImageVectorGenerator.convert( vector = parseOutput.irImageVector, - iconName = parseOutput.iconName, - config = config, + config = config.copy(iconName = parseOutput.iconName), ) vectorSpecOutput.content.writeToKt( outputDir = when { - useFlatPackage -> outputPath.absolutePathString() - else -> "${outputPath.absolutePathString()}/${nestedPackName.lowercase()}" - }, + useFlatPackage || iconPackTree == null -> outputPath + else -> { + val subDir = iconPackTree.pathSegments().drop(1).joinToString("/") { it.lowercase() } + if (subDir.isNotEmpty()) outputPath.resolve(subDir) else outputPath + } + }.absolutePathString(), nameWithoutExtension = vectorSpecOutput.name, ) } diff --git a/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/IconPackCliTest.kt b/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/IconPackCliTest.kt index 69b16d507..61415c9f5 100644 --- a/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/IconPackCliTest.kt +++ b/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/IconPackCliTest.kt @@ -2,7 +2,7 @@ package io.github.composegears.valkyrie.cli import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.composegears.valkyrie.cli.IconPackCommand.IconPackStructure +import io.github.composegears.valkyrie.cli.IconPackCommand.IconPack import io.github.composegears.valkyrie.cli.IconPackCommand.IndentSize import io.github.composegears.valkyrie.cli.IconPackCommand.OutputPath import io.github.composegears.valkyrie.cli.IconPackCommand.PackageName @@ -34,7 +34,7 @@ class IconPackCliTest( testIconPack( cliTestType = cliTestType, expectedResource = "iconpack/IconPack.kt", - iconPack = IconPackStructure("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), ) } @@ -43,7 +43,7 @@ class IconPackCliTest( testIconPack( cliTestType = cliTestType, expectedResource = "iconpack/IconPack.explicit.kt", - iconPack = IconPackStructure("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), useExplicitMode = UseExplicitMode(true), ) } @@ -53,7 +53,7 @@ class IconPackCliTest( testIconPack( cliTestType = cliTestType, expectedResource = "iconpack/IconPack.nested.L2.kt", - iconPack = IconPackStructure("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), + iconPack = IconPack("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), ) } @@ -62,7 +62,7 @@ class IconPackCliTest( testIconPack( cliTestType = cliTestType, expectedResource = "iconpack/IconPack.nested.explicit.kt", - iconPack = IconPackStructure("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), + iconPack = IconPack("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), useExplicitMode = UseExplicitMode(true), ) } @@ -72,7 +72,7 @@ class IconPackCliTest( testIconPack( cliTestType = cliTestType, expectedResource = "iconpack/IconPack.nested.indent1.kt", - iconPack = IconPackStructure("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), + iconPack = IconPack("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), indentSize = IndentSize(1), ) } @@ -82,7 +82,7 @@ class IconPackCliTest( testIconPack( cliTestType = cliTestType, expectedResource = "iconpack/IconPack.nested.indent2.kt", - iconPack = IconPackStructure("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), + iconPack = IconPack("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), indentSize = IndentSize(2), ) } @@ -92,7 +92,7 @@ class IconPackCliTest( testIconPack( cliTestType = cliTestType, expectedResource = "iconpack/IconPack.nested.indent3.kt", - iconPack = IconPackStructure("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), + iconPack = IconPack("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), indentSize = IndentSize(3), ) } @@ -102,7 +102,7 @@ class IconPackCliTest( testIconPack( cliTestType = cliTestType, expectedResource = "iconpack/IconPack.nested.indent6.kt", - iconPack = IconPackStructure("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), + iconPack = IconPack("ValkyrieIcons.Filled,ValkyrieIcons.Colored"), indentSize = IndentSize(6), ) } @@ -110,7 +110,7 @@ class IconPackCliTest( private fun testIconPack( cliTestType: CliTestType, expectedResource: String, - iconPack: IconPackStructure, + iconPack: IconPack, useExplicitMode: UseExplicitMode? = null, indentSize: IndentSize? = null, ) { @@ -153,7 +153,7 @@ private sealed interface IconPackCommand { override val command: String = "--package-name=$name" } - data class IconPackStructure(val value: String) : IconPackCommand { + data class IconPack(val value: String) : IconPackCommand { override val command: String = "--iconpack=$value" } diff --git a/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/SvgXmlToImageVectorCliTest.kt b/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/SvgXmlToImageVectorCliTest.kt index 2714300de..77a6f2411 100644 --- a/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/SvgXmlToImageVectorCliTest.kt +++ b/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/SvgXmlToImageVectorCliTest.kt @@ -6,11 +6,10 @@ import assertk.assertions.isNotNull import io.github.composegears.valkyrie.cli.SvgXmlCommand.AddTrailingComma import io.github.composegears.valkyrie.cli.SvgXmlCommand.AutoMirror import io.github.composegears.valkyrie.cli.SvgXmlCommand.GeneratePreview -import io.github.composegears.valkyrie.cli.SvgXmlCommand.IconPackName +import io.github.composegears.valkyrie.cli.SvgXmlCommand.IconPack import io.github.composegears.valkyrie.cli.SvgXmlCommand.ImageVectorOutputFormat import io.github.composegears.valkyrie.cli.SvgXmlCommand.IndentSize import io.github.composegears.valkyrie.cli.SvgXmlCommand.InputPath -import io.github.composegears.valkyrie.cli.SvgXmlCommand.NestedPackName import io.github.composegears.valkyrie.cli.SvgXmlCommand.OutputPath import io.github.composegears.valkyrie.cli.SvgXmlCommand.PackageName import io.github.composegears.valkyrie.cli.SvgXmlCommand.UseComposeColors @@ -21,8 +20,8 @@ import io.github.composegears.valkyrie.cli.common.CliTestType import io.github.composegears.valkyrie.cli.common.CliTestType.DirectMain import io.github.composegears.valkyrie.cli.common.CliTestType.JarTerminal import io.github.composegears.valkyrie.cli.common.CommandLineTestRunner -import io.github.composegears.valkyrie.cli.common.toResourceText -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.testfixtures.toResourceText import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourcePath import java.nio.file.Path import kotlin.io.path.absolutePathString @@ -121,7 +120,7 @@ class SvgXmlToImageVectorCliTest( inputResource = "imagevector/xml/ic_flat_package.xml", expectedKtName = "FlatPackage.pack.kt", actualKtName = "FlatPackage.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), useFlatPackage = UseFlatPackage(true), ) } @@ -132,8 +131,18 @@ class SvgXmlToImageVectorCliTest( inputResource = "imagevector/xml/ic_flat_package.xml", expectedKtName = "FlatPackage.pack.nested.kt", actualKtName = "FlatPackage.kt", - iconPackName = IconPackName("ValkyrieIcons"), - nestedPackName = NestedPackName("Filled"), + iconPack = IconPack("ValkyrieIcons.Filled"), + useFlatPackage = UseFlatPackage(true), + ) + } + + @Test + fun `flat package with deep nested icon pack`() { + arg.testConversion( + inputResource = "imagevector/xml/ic_flat_package.xml", + expectedKtName = "FlatPackage.pack.deep.nested.kt", + actualKtName = "FlatPackage.kt", + iconPack = IconPack("ValkyrieIcons.Material.Rounded"), useFlatPackage = UseFlatPackage(true), ) } @@ -204,7 +213,7 @@ class SvgXmlToImageVectorCliTest( inputResource = "imagevector/xml/ic_without_path.xml", expectedKtName = "WithoutPath.pack.preview.androidx.kt", actualKtName = "WithoutPath.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), generatePreview = GeneratePreview(true), ) } @@ -215,19 +224,28 @@ class SvgXmlToImageVectorCliTest( inputResource = "imagevector/xml/ic_without_path.xml", expectedKtName = "WithoutPath.pack.nested.preview.androidx.kt", actualKtName = "filled/WithoutPath.kt", - iconPackName = IconPackName("ValkyrieIcons"), - nestedPackName = NestedPackName("Filled"), + iconPack = IconPack("ValkyrieIcons.Filled"), generatePreview = GeneratePreview(true), ) } + @Test + fun `deep nested icon pack output directory structure`() { + arg.testConversion( + inputResource = "imagevector/xml/ic_without_path.xml", + expectedKtName = "WithoutPath.pack.deep.nested.kt", + actualKtName = "material/rounded/WithoutPath.kt", + iconPack = IconPack("ValkyrieIcons.Material.Rounded"), + ) + } + @Test fun `empty path xml`() { arg.testConversion( inputResource = "imagevector/xml/ic_without_path.xml", expectedKtName = "WithoutPath.pack.kt", actualKtName = "WithoutPath.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), ) } @@ -236,7 +254,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_only_path.xml", expectedKtName = "OnlyPath.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), ) } @@ -245,7 +263,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_fill_color_stroke.xml", expectedKtName = "FillColorStroke.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), ) } @@ -255,7 +273,7 @@ class SvgXmlToImageVectorCliTest( inputResource = "imagevector/xml/ic_fill_color_stroke.xml", expectedKtName = "FillColorStroke.trailing.kt", actualKtName = "FillColorStroke.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), addTrailingComma = AddTrailingComma(true), ) } @@ -266,7 +284,7 @@ class SvgXmlToImageVectorCliTest( inputResource = "imagevector/xml/ic_only_path.xml", expectedKtName = "OnlyPathWithPathData.kt", actualKtName = "OnlyPath.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), usePathDataString = UsePathDataString(true), ) } @@ -276,7 +294,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_all_path_params.xml", expectedKtName = "AllPathParams.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), ) } @@ -285,7 +303,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_all_path_params.xml", expectedKtName = "AllPathParams.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), autoMirror = AutoMirror(true), ) } @@ -295,7 +313,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_all_group_params.xml", expectedKtName = "AllGroupParams.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), useComposeColors = UseComposeColors(false), ) } @@ -305,7 +323,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_all_group_params.xml", expectedKtName = "AllGroupParams.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), useComposeColors = UseComposeColors(false), autoMirror = AutoMirror(false), ) @@ -316,7 +334,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_several_path.xml", expectedKtName = "SeveralPath.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), useComposeColors = UseComposeColors(false), ) } @@ -326,7 +344,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_compose_color.xml", expectedKtName = "ComposeColor.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), useComposeColors = UseComposeColors(true), ) } @@ -356,7 +374,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/ic_transparent_fill_color.xml", expectedKtName = "TransparentFillColor.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), useComposeColors = UseComposeColors(false), ) } @@ -366,7 +384,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/icon_with_named_args.xml", expectedKtName = "IconWithNamedArgs.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), ) } @@ -375,7 +393,7 @@ class SvgXmlToImageVectorCliTest( arg.testConversion( inputResource = "imagevector/xml/icon_with_shorthand_color.xml", expectedKtName = "IconWithShorthandColor.kt", - iconPackName = IconPackName("ValkyrieIcons"), + iconPack = IconPack("ValkyrieIcons"), ) } @@ -416,10 +434,9 @@ class SvgXmlToImageVectorCliTest( inputResource: String, expectedKtName: String, actualKtName: String = expectedKtName, - iconPackName: IconPackName? = null, + iconPack: IconPack? = null, useExplicitMode: UseExplicitMode? = null, useComposeColors: UseComposeColors? = null, - nestedPackName: NestedPackName? = null, useFlatPackage: UseFlatPackage? = null, indentSize: IndentSize? = null, generatePreview: GeneratePreview? = null, @@ -436,8 +453,7 @@ class SvgXmlToImageVectorCliTest( InputPath(input.absolutePathString()), OutputPath(tempDir.absolutePathString()), PackageName("io.github.composegears.valkyrie.icons"), - iconPackName, - nestedPackName, + iconPack, ImageVectorOutputFormat(outputFormat), generatePreview, useFlatPackage, @@ -500,12 +516,8 @@ private sealed interface SvgXmlCommand { override val command: String = "--package-name=$name" } - data class IconPackName(val name: String) : SvgXmlCommand { - override val command: String = "--iconpack-name=$name" - } - - data class NestedPackName(val name: String) : SvgXmlCommand { - override val command: String = "--nested-pack-name=$name" + data class IconPack(val tree: String) : SvgXmlCommand { + override val command: String = "--iconpack=$tree" } data class ImageVectorOutputFormat(val format: OutputFormat) : SvgXmlCommand { diff --git a/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/common/OutputFormatResource.kt b/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/common/OutputFormatResource.kt deleted file mode 100644 index 8ef618539..000000000 --- a/tools/cli/src/test/kotlin/io/github/composegears/valkyrie/cli/common/OutputFormatResource.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.composegears.valkyrie.cli.common - -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat.BackingProperty -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat.LazyProperty -import io.github.composegears.valkyrie.sdk.test.resource.loader.ResourceLoader.getResourceText - -fun OutputFormat.toResourceText( - pathToBackingProperty: String, - pathToLazyProperty: String, -): String = when (this) { - BackingProperty -> getResourceText(pathToBackingProperty) - LazyProperty -> getResourceText(pathToLazyProperty) -} diff --git a/tools/gradle-plugin/CHANGELOG.md b/tools/gradle-plugin/CHANGELOG.md index 347da937c..187da83ba 100644 --- a/tools/gradle-plugin/CHANGELOG.md +++ b/tools/gradle-plugin/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Changed + +- Migrate to new IconPackTree architecture for improved hierarchical icon pack structure support + ## 0.5.1 - 2026-06-03 ### Fixed diff --git a/tools/gradle-plugin/api/gradle-plugin.api b/tools/gradle-plugin/api/gradle-plugin.api index 7dee6c578..b7d3e1853 100644 --- a/tools/gradle-plugin/api/gradle-plugin.api +++ b/tools/gradle-plugin/api/gradle-plugin.api @@ -28,6 +28,7 @@ public abstract class io/github/composegears/valkyrie/gradle/NestedPack { public final fun getAutoMirror ()Lorg/gradle/api/provider/Property; public final fun getName ()Lorg/gradle/api/provider/Property; public final fun getSourceFolder ()Lorg/gradle/api/provider/Property; + public final fun nested (Lkotlin/jvm/functions/Function1;)V } public abstract class io/github/composegears/valkyrie/gradle/ValkyrieExtension { diff --git a/tools/gradle-plugin/build.gradle.kts b/tools/gradle-plugin/build.gradle.kts index 9309af119..054186423 100644 --- a/tools/gradle-plugin/build.gradle.kts +++ b/tools/gradle-plugin/build.gradle.kts @@ -102,9 +102,10 @@ dependencies { compileOnly(libs.kotlin.gradle.plugin) implementation(projects.sdk.core.extensions) + implementation(projects.sdk.generator.kt.common.iconpackTree) + implementation(projects.sdk.generator.kt.iconpack) + implementation(projects.sdk.generator.kt.imagevector.jvm) implementation(projects.sdk.ir.core) - implementation(projects.components.generator.iconpack) - implementation(projects.components.generator.jvm.imagevector) implementation(projects.components.parser.unified) implementation(kotlin("stdlib")) diff --git a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/IconPackExtension.kt b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/IconPackExtension.kt index 6c08f493a..4786df0d0 100644 --- a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/IconPackExtension.kt +++ b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/IconPackExtension.kt @@ -67,7 +67,7 @@ abstract class IconPackExtension @Inject constructor( } } -abstract class NestedPack @Inject constructor(objects: ObjectFactory) { +abstract class NestedPack @Inject constructor(private val objects: ObjectFactory) { /** * Nested icon pack name * @@ -81,28 +81,44 @@ abstract class NestedPack @Inject constructor(objects: ObjectFactory) { * * The path is relative to the configured resources directory (typically `valkyrieResources`). * + * This property is required. + * * Example configuration: * ``` * nested { * name = "Outlined" - * sourceFolder = "outlined" // Icons located at valkyrieResources/outlined/ + * sourceFolder = "outlined" * } * ``` * - * If your project structure is: - * ``` - * valkyrieResources/ - * ├── outlined/ - * │ ├── icon1.svg - * │ └── icon2.svg - * └── filled/ - * ├── icon1.svg - * └── icon2.svg + * Or with recursive nesting: * ``` + * nested { + * name = "Material" + * sourceFolder = "material" * - * Then for the "Outlined" nested pack, set `sourceFolder.set("outlined")`. + * nested { + * name = "Filled" + * sourceFolder = "filled" + * } + * nested { + * name = "Outlined" + * sourceFolder = "outlined" + * } + * } + * ``` * - * This property is required and must be set for each nested pack. + * Folder structure: + * ``` + * valkyrieResources/ + * └── material/ + * ├── outlined/ + * │ ├── icon1.svg + * │ └── icon2.svg + * └── filled/ + * ├── icon1.svg + * └── icon2.svg + *``` */ @get:Input val sourceFolder: Property = objects.property() @@ -119,4 +135,35 @@ abstract class NestedPack @Inject constructor(objects: ObjectFactory) { @get:Input @get:Optional val autoMirror: Property = objects.property() + + /** + * Nested packs within this pack for recursive hierarchy support. + * Allows defining arbitrarily deep icon pack structures. + */ + @get:Nested + internal val nestedPacks: ListProperty = objects + .listProperty() + .convention(emptyList()) + + /** + * Add a nested pack within this pack. + * + * Example: + * ``` + * nested { + * name = "Material" + * sourceFolder = "material" + * + * nested { + * name = "Filled" + * sourceFolder = "filled" + * } + * } + * ``` + */ + @Suppress("unused") + fun nested(action: NestedPack.() -> Unit) { + val config = objects.newInstance().apply(action) + nestedPacks.add(config) + } } diff --git a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/ImageVectorConfigExtension.kt b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/ImageVectorConfigExtension.kt index 4807a741a..dcc1f5e2e 100644 --- a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/ImageVectorConfigExtension.kt +++ b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/ImageVectorConfigExtension.kt @@ -1,7 +1,7 @@ package io.github.composegears.valkyrie.gradle -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat import io.github.composegears.valkyrie.gradle.dsl.property +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat import javax.inject.Inject import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property diff --git a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/common/ExtensionValidator.kt b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/common/ExtensionValidator.kt index d84d8bc83..4aaa1c173 100644 --- a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/common/ExtensionValidator.kt +++ b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/common/ExtensionValidator.kt @@ -1,6 +1,7 @@ package io.github.composegears.valkyrie.gradle.internal.common import io.github.composegears.valkyrie.gradle.IconPackExtension +import io.github.composegears.valkyrie.gradle.NestedPack import io.github.composegears.valkyrie.gradle.ValkyrieExtension import org.gradle.api.GradleException @@ -13,7 +14,7 @@ internal object ExtensionValidator { * Throws [org.gradle.api.GradleException] if configuration is invalid. */ fun validate(extension: ValkyrieExtension) { - if (extension.packageName.orNull.isNullOrBlank()) { + if (!extension.packageName.isPresent || extension.packageName.get().isBlank()) { throw GradleException("\"packageName\" cannot be blank") } @@ -41,7 +42,16 @@ internal object ExtensionValidator { throw GradleException("iconPack \"targetSourceSet\" is required but not set") } + val targetSourceSet = iconPack.targetSourceSet.get() + if (targetSourceSet.isBlank()) { + throw GradleException("iconPack \"targetSourceSet\" cannot be blank") + } + val nestedPacks = iconPack.nestedPacks.get() + validateNestedPacks(nestedPacks) + } + + private fun validateNestedPacks(nestedPacks: List) { val nestedNames = mutableSetOf() val sourceFolders = mutableSetOf() @@ -54,18 +64,22 @@ internal object ExtensionValidator { name.isBlank() -> throw GradleException("nested pack \"name\" cannot be blank") } - when { - sourceFolder == null -> throw GradleException("nested pack \"sourceFolder\" is required but not set") - sourceFolder.isBlank() -> throw GradleException("nested pack \"sourceFolder\" cannot be blank") - } - if (!nestedNames.add(name)) { throw GradleException("Duplicate nested pack name found: \"$name\"") } + when { + sourceFolder == null -> throw GradleException("nested pack \"sourceFolder\" is required but not set") + sourceFolder.isBlank() -> throw GradleException("nested pack \"sourceFolder\" cannot be blank") + } if (!sourceFolders.add(sourceFolder)) { throw GradleException("Duplicate sourceFolder found: \"$sourceFolder\"") } + + val childNestedPacks = nested.nestedPacks.get() + if (childNestedPacks.isNotEmpty()) { + validateNestedPacks(childNestedPacks) + } } } } diff --git a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/DuplicateIconValidator.kt b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/DuplicateIconValidator.kt new file mode 100644 index 000000000..ce084ebb3 --- /dev/null +++ b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/DuplicateIconValidator.kt @@ -0,0 +1,114 @@ +package io.github.composegears.valkyrie.gradle.internal.task + +import io.github.composegears.valkyrie.parser.unified.util.IconNameFormatter +import java.io.File +import org.gradle.api.GradleException + +/** + * Utility for validating icon duplicates across packs. + */ +internal object DuplicateIconValidator { + /** + * Validates for exact and case-insensitive duplicate icons. + * + * @param files List of icon files to check + * @param iconNames Pre-computed formatted icon names + * @param packName Name of icon pack (for error messages) + * @param nestedPacks List of nested packs configuration + * @param useFlatPackage Whether flat package mode is enabled + * + * @throws GradleException if duplicates are found + */ + fun validateDuplicates( + files: List, + iconNames: List, + packName: String, + nestedPacks: List, + useFlatPackage: Boolean, + ) { + if (nestedPacks.isNotEmpty()) { + // Find the common resources directory based on max nesting depth + val maxNestedPackDepth = nestedPacks.maxOfOrNull { it.hierarchyPath.size } ?: 1 + val resourcesDir = files.findResourcesDirectory(maxNestedPackDepth) + + // Map files to their nested pack + val fileToNestedPack = files.associateWith { file -> + val relativeFileDir = if (resourcesDir != null && resourcesDir.isDirectory) { + file.parentFile.toRelativeString(resourcesDir) + } else { + file.parentFile.name + } + // Find matching nested pack - exact path match required + nestedPacks.find { nestedPack -> + val normalizedSourceFolder = nestedPack.sourceFolder.replace('/', File.separatorChar) + relativeFileDir == normalizedSourceFolder + } + } + + // Only validate files that have matching nested packs + val filesToValidate = fileToNestedPack.filterValues { it != null }.keys + + // Group by nested pack + val iconsByPack = filesToValidate.groupBy { file -> + if (useFlatPackage) { + packName // All in same pack when flat + } else { + val nestedPackName = fileToNestedPack[file]?.displayName + if (nestedPackName != null) "$packName.$nestedPackName" else packName + } + } + + iconsByPack.forEach { (packIdentifier, filesInPack) -> + val names = filesInPack.map { IconNameFormatter.format(name = it.name) } + checkDuplicatesInIconNames(names, "\"$packIdentifier\"") + } + } else { + // Single pack - check all files together + checkDuplicatesInIconNames(iconNames, "\"$packName\"") + } + } + + /** + * Validates icon names for duplicates (exact and case-insensitive). + * + * @param names List of formatted icon names + * @param context Description of what's being checked (for error messages) + * + * @throws GradleException if duplicates are found + */ + fun checkDuplicatesInIconNames(names: List, context: String) { + // Check exact duplicates + val exactDuplicates = names + .groupBy { it } + .filter { it.value.size > 1 } + .keys + .toList() + .sorted() + + if (exactDuplicates.isNotEmpty()) { + throw GradleException( + "Found duplicate icon names in $context: ${exactDuplicates.joinToString(", ")}. " + + "Each icon must have a unique name. " + + "Please rename the source files to avoid duplicates.", + ) + } + + // Check case-insensitive duplicates + val caseInsensitiveDuplicates = names + .groupBy { it.lowercase() } + .filter { it.value.size > 1 && it.value.distinct().size > 1 } + .values + .flatten() + .distinct() + .sorted() + + if (caseInsensitiveDuplicates.isNotEmpty()) { + throw GradleException( + "Found icon names that would collide on case-insensitive file systems (macOS/Windows) in $context: " + + "${caseInsensitiveDuplicates.joinToString(", ")}. " + + "These icons would overwrite each other during generation. " + + "Please rename the source files to avoid case-insensitive duplicates.", + ) + } + } +} diff --git a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/FileUtils.kt b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/FileUtils.kt new file mode 100644 index 000000000..aca65f4af --- /dev/null +++ b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/FileUtils.kt @@ -0,0 +1,79 @@ +package io.github.composegears.valkyrie.gradle.internal.task + +import java.io.File + +/** + * Find the common ancestor directory of multiple files. + * Returns null if files list is empty. + * + * Example: + * listOf(File("/a/b/c"), File("/a/b/d")).commonAncestor() -> File("/a/b") + */ +internal fun List.commonAncestor(): File? { + if (isEmpty()) { + return null + } + if (size == 1) { + return first() + } + + val paths = map { it.absolutePath.split(File.separator) } + val commonParts = mutableListOf() + + val minLength = paths.minOf { it.size } + for (i in 0 until minLength) { + val part = paths[0][i] + if (paths.all { it[i] == part }) { + commonParts.add(part) + } else { + break + } + } + + return if (commonParts.isNotEmpty()) { + File(commonParts.joinToString(File.separator)) + } else { + null + } +} + +/** + * Find the resources directory based on nested pack depth. + * Goes up the directory hierarchy from file parents by maxNestingDepth levels. + * Includes fallbacks for edge cases. + * + * Example: + * - 1-level nested (files in "outlined/"): maxDepth=1 → goes up 1 level to "valkyrieResources/" + * - 2-level nested (files in "material/filled/"): maxDepth=2 → goes up 2 levels to "valkyrieResources/" + */ +internal fun List.findResourcesDirectory(maxNestingDepth: Int): File? { + if (isEmpty()) return null + + // Start from first file's parent and go up maxNestingDepth levels + var resourcesDir = firstOrNull()?.parentFile + repeat(maxNestingDepth) { + resourcesDir = resourcesDir?.parentFile + } + + // Fallback to common ancestor logic + if (resourcesDir == null || !resourcesDir.isDirectory) { + resourcesDir = mapNotNull { it.parentFile }.commonAncestor() + repeat((maxNestingDepth - 1).coerceAtLeast(0)) { + resourcesDir = resourcesDir?.parentFile + } + } + + // Final fallback to original logic + if (resourcesDir == null || !resourcesDir.isDirectory) { + resourcesDir = mapNotNull { it.parentFile?.parentFile }.commonAncestor() + ?: firstOrNull()?.parentFile?.parentFile + } + + return resourcesDir +} + +/** + * Find the resources directory based on nested pack depth (Set variant). + * Converts to List and delegates to the List extension. + */ +internal fun Set.findResourcesDirectory(maxNestingDepth: Int): File? = toList().findResourcesDirectory(maxNestingDepth) diff --git a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/GenerateImageVectorsTask.kt b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/GenerateImageVectorsTask.kt index 90f22ebfb..7df3742a9 100644 --- a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/GenerateImageVectorsTask.kt +++ b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/GenerateImageVectorsTask.kt @@ -1,19 +1,24 @@ package io.github.composegears.valkyrie.gradle.internal.task -import io.github.composegears.valkyrie.generator.core.IconPack -import io.github.composegears.valkyrie.generator.iconpack.IconPackGenerator -import io.github.composegears.valkyrie.generator.iconpack.IconPackGeneratorConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.FullQualifiedImports -import io.github.composegears.valkyrie.generator.jvm.imagevector.FullQualifiedImports.Companion.reservedComposeQualifiers -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGenerator -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGeneratorConfig -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat import io.github.composegears.valkyrie.gradle.IconPackExtension import io.github.composegears.valkyrie.gradle.NestedPack import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.util.IconNameFormatter import io.github.composegears.valkyrie.sdk.core.extensions.writeToKt +import io.github.composegears.valkyrie.sdk.core.tree.MutableTreeNode +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGenerator +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.tree.IconPackTree +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.FullyQualifiedImports.Companion.reservedComposeTypeNames +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.ImageVectorGenerator import java.io.File import kotlinx.io.files.Path import org.gradle.api.DefaultTask @@ -77,20 +82,20 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { @get:Input abstract val sourceSet: Property - private val basicConfig: ImageVectorGeneratorConfig - get() = ImageVectorGeneratorConfig( - packageName = packageName.get(), - iconPackPackage = packageName.get(), - packName = "", - nestedPackName = "", + private val codeStyleConfig: CodeStyleConfig + get() = CodeStyleConfig( + useExplicitMode = useExplicitMode.get(), + indentSize = indentSize.get(), + ) + + private val imageVectorConfig: ImageVectorConfig + get() = ImageVectorConfig( outputFormat = outputFormat.get(), useComposeColors = useComposeColors.get(), generatePreview = generatePreview.get(), - useFlatPackage = false, - useExplicitMode = useExplicitMode.get(), + useFlatPackage = iconPack.isPresent && iconPack.get().useFlatPackage.get(), addTrailingComma = addTrailingComma.get(), usePathDataString = usePathDataString.get(), - indentSize = indentSize.get(), suppressUnusedReceiverWarning = suppressUnusedReceiverWarning.get(), ) @@ -111,17 +116,36 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { // Detect icons with names conflicting with reserved Compose qualifiers val iconNames = iconFiles.files.map { IconNameFormatter.format(name = it.name) } - val fullQualifiedNames = iconNames.filter { reservedComposeQualifiers.contains(it) } + val fullyQualifiedNames = iconNames.filter { it in reservedComposeTypeNames } - if (fullQualifiedNames.isNotEmpty()) { + if (fullyQualifiedNames.isNotEmpty()) { logger.lifecycle( "Found icons names that conflict with reserved Compose qualifiers. " + - "Full qualified import will be used for: \"${fullQualifiedNames.joinToString(", ")}\"", + "Full qualified import will be used for: \"${fullyQualifiedNames.joinToString(", ")}\"", ) } // Check for duplicates with nested pack awareness - validateDuplicates(iconFiles.files.toList(), iconNames) + if (iconPack.isPresent) { + val nestedPacks = if (iconPack.get().nestedPacks.get().isNotEmpty()) { + NestedPackFlattener.flatten(iconPack.get().nestedPacks.get()) + } else { + emptyList() + } + DuplicateIconValidator.validateDuplicates( + files = iconFiles.files.toList(), + iconNames = iconNames, + packName = iconPack.get().name.get(), + nestedPacks = nestedPacks, + useFlatPackage = iconPack.get().useFlatPackage.get(), + ) + } else { + // No icon pack - validate as package without nesting + DuplicateIconValidator.checkDuplicatesInIconNames( + names = iconNames, + context = "\"${packageName.get()}\"", + ) + } if (iconPack.isPresent && iconPack.get().targetSourceSet.get() == sourceSet.get()) { generateIconPack(outputDirectory = outputDirectory) @@ -130,12 +154,12 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { if (iconPack.isPresent) { generateIconsWithIconPack( outputDirectory = outputDirectory, - fullQualifiedNames = fullQualifiedNames, + fullQualifiedNames = fullyQualifiedNames, ) } else { generateIconsWithoutPack( outputDirectory = outputDirectory, - fullQualifiedNames = fullQualifiedNames, + fullQualifiedNames = fullyQualifiedNames, ) } @@ -147,17 +171,12 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { val packageName = packageName.get() val iconPackExtension = iconPack.get() - val pack = IconPack( - name = iconPackExtension.name.get(), - nested = iconPackExtension.nestedPacks.get().map { nestedPack -> - IconPack(name = nestedPack.name.get()) - }, - ) + val pack = buildIconPackTree(iconPackExtension) IconPackGenerator.create( config = IconPackGeneratorConfig( packageName = packageName, - iconPack = pack, + iconPackTree = pack, useExplicitMode = useExplicitMode.get(), indentSize = indentSize.get(), ), @@ -169,7 +188,7 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { outputDir = absolutePath, nameWithoutExtension = it.name, ) - logger.lifecycle("Generated \"${pack.name}\" iconpack in package \"$packageName\"") + logger.lifecycle("Generated \"${pack.data}\" iconpack in package \"$packageName\"") } } @@ -188,7 +207,13 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { useFlatPackage = false, ) - val config = basicConfig.copy(fullQualifiedImports = createFullQualifiedImports(fullQualifiedNames)) + val config = ImageVectorGeneratorConfig.simple( + iconName = "", + packageName = packageName, + codeStyle = codeStyleConfig, + imageVector = imageVectorConfig, + fullyQualifiedImports = FullyQualifiedImports.from(fullQualifiedNames), + ) var convertedCount = 0 iconFiles.files.forEach { file -> runCatching { @@ -197,6 +222,7 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { config = config, targetDirectory = targetDirectory, nestedPackName = null, + flattenedNestedPack = null, ) convertedCount++ }.onFailure { @@ -213,9 +239,15 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { val nestedPacks = pack.nestedPacks.get() val useFlatPackage = pack.useFlatPackage.get() - val config = basicConfig.copy( - packName = pack.name.get(), - fullQualifiedImports = createFullQualifiedImports(fullQualifiedNames), + // Note: for ImageVectorConfig, we must pass a linear chain tree (single path) + // Individual nested packs are handled separately in generateIconsForNestedPacks + val config = ImageVectorGeneratorConfig.iconPack( + iconName = "", + packageName = packageName, + iconPackTree = buildTree(pack.name.get()), + codeStyle = codeStyleConfig, + imageVector = imageVectorConfig, + fullyQualifiedImports = FullyQualifiedImports.from(fullQualifiedNames), ) if (iconFiles.isEmpty) { @@ -235,7 +267,7 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { packageName = packageName, useFlatPackage = useFlatPackage, nestedPacks = nestedPacks, - config = config.copy(useFlatPackage = useFlatPackage), + config = config.copy(imageVector = config.imageVector.copy(useFlatPackage = useFlatPackage)), ) } } @@ -260,6 +292,7 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { config = config, targetDirectory = targetDirectory, nestedPackName = null, + flattenedNestedPack = null, ) convertedCount++ }.onFailure { @@ -276,16 +309,38 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { nestedPacks: List, config: ImageVectorGeneratorConfig, ) { - val sourceFolderToNestedPack = nestedPacks.associateBy { it.sourceFolder.get() } + val packName = config.iconPackTree?.data.orEmpty() + + // Flatten nested packs recursively to handle arbitrary depth + val flattenedPacks = NestedPackFlattener.flatten(nestedPacks) val nestedPackIconCounts = mutableMapOf() + // Find the common resources directory. + // We need to go up the hierarchy based on the max nesting depth of nested packs. + // For 1-level nested: depth = 1, go up 1 level to get from "filled/" to "valkyrieResources/" + // For 2-level nested: depth = 2, go up 2 levels to get from "material/filled/" to "valkyrieResources/" + val maxNestedPackDepth = flattenedPacks.maxOfOrNull { it.hierarchyPath.size } ?: 1 + val resourcesDir = iconFiles.files.findResourcesDirectory(maxNestedPackDepth) + iconFiles.files.forEach { file -> - val parentDirName = file.parentFile.name - val nestedPack = sourceFolderToNestedPack[parentDirName] + // Calculate relative path from resources directory + val relativeFileDir = if (resourcesDir != null && resourcesDir.isDirectory) { + file.parentFile.toRelativeString(resourcesDir) + } else { + file.parentFile.name + } + + // Find matching flattened nested pack based on source folder - exact match required + val matchingNestedPack = flattenedPacks.find { nestedPack -> + val normalizedSourceFolder = nestedPack.sourceFolder.replace('/', File.separatorChar) + relativeFileDir == normalizedSourceFolder + } - if (nestedPack != null) { - val nestedPackName = nestedPack.name.get() - val nestedPackConfig = config.copy(nestedPackName = nestedPackName) + if (matchingNestedPack != null) { + val nestedPackName = matchingNestedPack.displayName + val nestedPackConfig = config.copy( + iconPackTree = buildNestedTree(packName, nestedPackName), + ) val nestedTargetDirectory = resolveTargetDirectory( outputDirectory = outputDirectory, packageName = packageName, @@ -299,6 +354,7 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { config = nestedPackConfig, targetDirectory = nestedTargetDirectory, nestedPackName = nestedPackName, + flattenedNestedPack = matchingNestedPack, ) nestedPackIconCounts[nestedPackName] = nestedPackIconCounts.getOrDefault(nestedPackName, 0) + 1 }.onFailure { @@ -318,11 +374,12 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { config: ImageVectorGeneratorConfig, targetDirectory: File, nestedPackName: String?, + flattenedNestedPack: FlattenedNestedPack? = null, ) { val parseOutput = SvgXmlParser.toIrImageVector(ParserType.Jvm, Path(file.absolutePath)) // Apply autoMirror override if specified (nested pack > icon pack > root) - val effectiveAutoMirror = resolveAutoMirror(nestedPackName) + val effectiveAutoMirror = resolveAutoMirror(flattenedNestedPack) val irImageVector = if (effectiveAutoMirror != null) { parseOutput.irImageVector.copy(autoMirror = effectiveAutoMirror) } else { @@ -331,8 +388,7 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { val vectorSpecOutput = ImageVectorGenerator.convert( vector = irImageVector, - iconName = parseOutput.iconName, - config = config, + config = config.copy(iconName = parseOutput.iconName), ) val path = vectorSpecOutput.content.writeToKt( @@ -365,27 +421,16 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { else -> "$packageName.${nestedPackName.lowercase()}" } - private fun createFullQualifiedImports(fullQualifiedNames: List): FullQualifiedImports { - return FullQualifiedImports( - brush = "Brush" in fullQualifiedNames, - color = "Color" in fullQualifiedNames, - offset = "Offset" in fullQualifiedNames, - ) - } - private fun iconWord(count: Int): String = if (count == 1) "icon" else "icons" private fun logFileParseError(file: File, error: Throwable) { logger.warn("Skipping file ${file.name} due to processing error, details: ${error.message}") } - private fun resolveAutoMirror(nestedPackName: String?): Boolean? { + private fun resolveAutoMirror(flattenedNestedPack: FlattenedNestedPack?): Boolean? { // Priority: nested pack > icon pack > root extension - if (iconPack.isPresent && nestedPackName != null) { - val nestedPack = iconPack.get().nestedPacks.get().find { it.name.get() == nestedPackName } - if (nestedPack?.autoMirror?.isPresent == true) { - return nestedPack.autoMirror.get() - } + if (flattenedNestedPack?.autoMirror != null) { + return flattenedNestedPack.autoMirror } if (iconPack.isPresent && iconPack.get().autoMirror.isPresent) { @@ -399,118 +444,44 @@ internal abstract class GenerateImageVectorsTask : DefaultTask() { return null } - private fun validateDuplicates(files: List, iconNames: List) { - if (iconPack.isPresent) { - val pack = iconPack.get() - val nestedPacks = pack.nestedPacks.get() - val useFlatPackage = pack.useFlatPackage.get() - - if (nestedPacks.isNotEmpty()) { - // Build map: file -> nested pack name - val sourceFolderToNestedPack = nestedPacks.associateBy { it.sourceFolder.get() } - - // When useFlatPackage is false, only validate files that are in configured nested pack folders - // (matching the behavior of generateIconsForNestedPacks) - val filesToValidate = if (useFlatPackage) { - files - } else { - files.filter { file -> sourceFolderToNestedPack.containsKey(file.parentFile.name) } - } - - val fileToNestedPack = filesToValidate.associateWith { file -> - sourceFolderToNestedPack[file.parentFile.name]?.name?.get() - } - - // Group by nested pack (or single group if useFlatPackage) - val iconsByPack = filesToValidate.groupBy { file -> - if (useFlatPackage) { - pack.name.get() // All in same pack when flat - } else { - val nestedPackName = fileToNestedPack[file] - if (nestedPackName != null) "${pack.name.get()}.$nestedPackName" else pack.name.get() - } - } + private fun buildIconPackTree(iconPackExtension: IconPackExtension): IconPackTree { + return buildTree(iconPackExtension.name.get()) { + buildNestedPacksTree(this, iconPackExtension.nestedPacks.get()) + } + } - iconsByPack.forEach { (packIdentifier, filesInPack) -> - val names = filesInPack.map { IconNameFormatter.format(name = it.name) } - - // Check exact duplicates within this pack/group - val exactDuplicates = names - .groupBy { it } - .filter { it.value.size > 1 } - .keys - .toList() - .sorted() - - if (exactDuplicates.isNotEmpty()) { - throw GradleException( - "Found duplicate icon names in \"$packIdentifier\": ${exactDuplicates.joinToString(", ")}. " + - "Each icon must have a unique name. " + - "Please rename the source files to avoid duplicates.", - ) - } - - // Check case-insensitive duplicates within this pack/group - val caseInsensitiveDuplicates = names - .groupBy { it.lowercase() } - .filter { it.value.size > 1 && it.value.distinct().size > 1 } - .values - .flatten() - .distinct() - .sorted() - - if (caseInsensitiveDuplicates.isNotEmpty()) { - throw GradleException( - "Found icon names that would collide on case-insensitive file systems (macOS/Windows) in \"$packIdentifier\": " + - "${caseInsensitiveDuplicates.joinToString(", ")}. " + - "These icons would overwrite each other during generation. " + - "Please rename the source files to avoid case-insensitive duplicates.", - ) - } - } - } else { - // Single pack - check all files together - checkDuplicatesInIconNames(iconNames, "icon pack \"${pack.name.get()}\"") + private fun buildNestedPacksTree(parent: MutableTreeNode, nestedPacks: List) { + nestedPacks.forEach { pack -> + parent.child(pack.name.get()) { + val children = pack.nestedPacks.get() + logger.info("Building tree for '${pack.name.get()}' with ${children.size} children") + buildNestedPacksTree(this, children) } - } else { - // No icon pack - check all files together - checkDuplicatesInIconNames(iconNames, "package \"${packageName.get()}\"") } } - private fun checkDuplicatesInIconNames(names: List, context: String) { - // Check exact duplicates - val exactDuplicates = names - .groupBy { it } - .filter { it.value.size > 1 } - .keys - .toList() - .sorted() - - if (exactDuplicates.isNotEmpty()) { - throw GradleException( - "Found duplicate icon names in $context: ${exactDuplicates.joinToString(", ")}. " + - "Each icon must have a unique name. " + - "Please rename the source files to avoid duplicates.", - ) + /** + * Build a nested tree from root pack and display name path. + * Example: buildNestedTree("ValkyrieIcons", "Material.Filled") produces: + * ValkyrieIcons + * └─ Material + * └─ Filled + */ + private fun buildNestedTree(rootPackName: String, displayNamePath: String): IconPackTree { + val parts = displayNamePath.split(".") + if (parts.isEmpty()) { + return buildTree(rootPackName) } - // Check case-insensitive duplicates - val caseInsensitiveDuplicates = names - .groupBy { it.lowercase() } - .filter { it.value.size > 1 && it.value.distinct().size > 1 } - .values - .flatten() - .distinct() - .sorted() - - if (caseInsensitiveDuplicates.isNotEmpty()) { - throw GradleException( - "Found icon names that would collide on case-insensitive file systems (macOS/Windows) in $context: " + - "${caseInsensitiveDuplicates.joinToString(", ")}. " + - "These icons would overwrite each other during generation. " + - "Please rename the source files to avoid case-insensitive duplicates.", - ) + return buildTree(rootPackName) { + // Recursively build nested children + fun addChildLevels(node: MutableTreeNode, levelIndex: Int) { + if (levelIndex >= parts.size) return + node.child(parts[levelIndex]) { + addChildLevels(this, levelIndex + 1) + } + } + addChildLevels(this, 0) } } } diff --git a/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/NestedPackFlattener.kt b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/NestedPackFlattener.kt new file mode 100644 index 000000000..c2dd326b4 --- /dev/null +++ b/tools/gradle-plugin/src/main/kotlin/io/github/composegears/valkyrie/gradle/internal/task/NestedPackFlattener.kt @@ -0,0 +1,107 @@ +package io.github.composegears.valkyrie.gradle.internal.task + +import io.github.composegears.valkyrie.gradle.NestedPack + +/** + * Utility for flattening nested packs into a linear list. + */ +internal object NestedPackFlattener { + /** + * Recursively flattens nested packs into a flat list. + * Each flattened pack contains its display name, source folder, and hierarchy path. + * + * Example configuration: + * ``` + * iconPack { + * name = "ValkyrieIcons" + * nested { + * name = "Material" + * sourceFolder = "material" + * + * nested { + * name = "Filled" + * sourceFolder = "filled" + * } + * } + * } + * ``` + * + * Produces flattened result: + * - FlattenedNestedPack( + * displayName = "Material", + * sourceFolder = "material", + * hierarchyPath = ["Material"] + * ) + * - FlattenedNestedPack( + * displayName = "Material.Filled", + * sourceFolder = "filled", + * hierarchyPath = ["Material", "Filled"] + * ) + * + * Folder structure: + * ``` + * valkyrieResources/ + * └── material/ + * ├── (files for "Material" pack) + * └── filled/ + * └── (files for "Material.Filled" pack) + * ``` + */ + fun flatten( + packs: List, + parentPath: String = "", + hierarchyPath: List = emptyList(), + parentSourceFolder: String = "", + ): List { + val result = mutableListOf() + + packs.forEach { pack -> + val packName = pack.name.get() + val displayName = if (parentPath.isEmpty()) packName else "$parentPath.$packName" + val newHierarchyPath = hierarchyPath + packName + + // sourceFolder is now always required + // Build full path combining parent sourceFolder with this sourceFolder + val sourceFolder = pack.sourceFolder.get() + val fullSourceFolder = if (parentSourceFolder.isEmpty()) { + sourceFolder + } else { + "$parentSourceFolder/$sourceFolder" + } + + result.add( + FlattenedNestedPack( + displayName = displayName, + sourceFolder = fullSourceFolder, + autoMirror = pack.autoMirror.orNull, + hierarchyPath = newHierarchyPath, + ), + ) + + // Recursively process nested packs + val childPacks = pack.nestedPacks.get() + if (childPacks.isNotEmpty()) { + result.addAll( + flatten( + packs = childPacks, + parentPath = displayName, + hierarchyPath = newHierarchyPath, + parentSourceFolder = fullSourceFolder, + ), + ) + } + } + + return result + } +} + +/** + * Represents a flattened nested pack with its full display path, source folder, and hierarchy. + */ +internal data class FlattenedNestedPack( + val displayName: String, + val sourceFolder: String, + val autoMirror: Boolean? = null, + val hierarchyPath: List = emptyList(), +) diff --git a/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/ValkyrieGradlePluginTest.kt b/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/ValkyrieGradlePluginTest.kt index 98184f6cb..f1f94463c 100644 --- a/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/ValkyrieGradlePluginTest.kt +++ b/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/ValkyrieGradlePluginTest.kt @@ -112,7 +112,7 @@ class ValkyrieGradlePluginTest : CommonGradleTest() { fun `Generate from test SVGs with custom config`() { root.resolve("build.gradle.kts").writeText( """ - import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat + import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat plugins { kotlin("jvm") diff --git a/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/NestedPackPluginConfigurationTest.kt b/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/NestedPackPluginConfigurationTest.kt similarity index 79% rename from tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/NestedPackPluginConfigurationTest.kt rename to tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/NestedPackPluginConfigurationTest.kt index 50edb2cd2..1d683d91e 100644 --- a/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/NestedPackPluginConfigurationTest.kt +++ b/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/NestedPackPluginConfigurationTest.kt @@ -1,10 +1,11 @@ -package io.github.composegears.valkyrie.gradle +package io.github.composegears.valkyrie.gradle.iconpack import assertk.assertThat import assertk.assertions.contains import assertk.assertions.doesNotContain import assertk.assertions.exists import assertk.assertions.isEqualTo +import io.github.composegears.valkyrie.gradle.RESOURCES_DIR_SVG import io.github.composegears.valkyrie.gradle.common.CommonGradleTest import io.github.composegears.valkyrie.gradle.common.doesNotExist import io.github.composegears.valkyrie.gradle.internal.TASK_NAME @@ -431,4 +432,106 @@ class NestedPackPluginConfigurationTest : CommonGradleTest() { assertThat(outlinedPackageDir).doesNotExist() assertThat(filledPackageDir).doesNotExist() } + + @Test + fun `generate icon pack with recursive nested structure using DSL`(@TempDir root: Path) { + root.writeSettingsFile() + + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("multiplatform") + id("io.github.composegears.valkyrie") + } + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "commonMain" + + nested { + name = "Material" + sourceFolder = "material" + + nested { + name = "Filled" + sourceFolder = "filled" + } + nested { + name = "Outlined" + sourceFolder = "outlined" + } + } + } + } + kotlin { + jvm() + } + """.trimIndent(), + ) + + // Create nested directories with SVG files + val sourceDir = RESOURCES_DIR_SVG.toPath() + val resourceDir = root.resolve("src/commonMain/valkyrieResources") + + // Filled + val filledDir = resourceDir.resolve("material/filled").createDirectories() + sourceDir.resolve("ic_clip_path_gradient.svg").copyTo(filledDir.resolve("ic_clip_path_gradient.svg")) + sourceDir.resolve("ic_linear_gradient.svg").copyTo(filledDir.resolve("ic_linear_gradient.svg")) + + // Outlined + val outlinedDir = resourceDir.resolve("material/outlined").createDirectories() + sourceDir.resolve("ic_radial_gradient.svg").copyTo(outlinedDir.resolve("ic_radial_gradient.svg")) + sourceDir.resolve("ic_linear_gradient_with_stroke.svg") + .copyTo(outlinedDir.resolve("ic_linear_gradient_with_stroke.svg")) + + val result = runTask(root, TASK_NAME) + + assertThat(result).taskHadResult(":generateValkyrieImageVectorCommonMain", SUCCESS) + assertThat(result).taskHadResult(":generateValkyrieImageVector", SUCCESS) + assertThat(result.output).contains("Generated \"ValkyrieIcons\" iconpack in package \"x.y.z\"") + assertThat(result.output).contains("Generated 2 ImageVector icons in nested pack \"Material.Filled\"") + assertThat(result.output).contains("Generated 2 ImageVector icons in nested pack \"Material.Outlined\"") + + val expectedCode = """ + package x.y.z + + object ValkyrieIcons { + object Material { + object Filled + + object Outlined + } + } + + """.trimIndent() + + val iconPackFile = root.resolveGeneratedPath("commonMain", "x/y/z/ValkyrieIcons.kt") + assertThat(iconPackFile).exists() + assertThat(iconPackFile.readText()).isEqualTo(expectedCode) + + // Count total generated files (1 pack file + 4 icon files) + assertThat(root.allGeneratedFiles().size).isEqualTo(5) + + // Verify material/filled icons are in the correct nested package + val materialFilledFiles = root.resolveGeneratedPath("commonMain", "x/y/z/material/filled") + val materialFilledIconFiles = materialFilledFiles.toFile().listFiles().orEmpty() + assertThat(materialFilledIconFiles.size).isEqualTo(2) + materialFilledIconFiles.forEach { file -> + val content = file.readText() + assertThat(content).contains("package x.y.z.material.filled") + assertThat(content).contains("val ValkyrieIcons.Material.Filled.") + } + + // Verify material/outlined icons are in the correct nested package + val materialOutlinedFiles = root.resolveGeneratedPath("commonMain", "x/y/z/material/outlined") + val materialOutlinedIconFiles = materialOutlinedFiles.toFile().listFiles().orEmpty() + assertThat(materialOutlinedIconFiles.size).isEqualTo(2) + materialOutlinedIconFiles.forEach { file -> + val content = file.readText() + assertThat(content).contains("package x.y.z.material.outlined") + assertThat(content).contains("val ValkyrieIcons.Material.Outlined.") + } + } } diff --git a/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/NestedPacksEdgeCasesTest.kt b/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/NestedPacksEdgeCasesTest.kt new file mode 100644 index 000000000..7ac1bab7e --- /dev/null +++ b/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/NestedPacksEdgeCasesTest.kt @@ -0,0 +1,455 @@ +package io.github.composegears.valkyrie.gradle.iconpack + +import assertk.assertThat +import io.github.composegears.valkyrie.gradle.common.CommonGradleTest +import io.github.composegears.valkyrie.gradle.internal.DEFAULT_RESOURCE_DIRECTORY +import io.github.composegears.valkyrie.gradle.internal.TASK_NAME +import java.nio.file.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.writeText +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +/** + * Edge case and stress tests for nested packs. + */ +class NestedPacksEdgeCasesTest : CommonGradleTest() { + + private val svgContent = """ + + + + """.trimIndent() + + @Test + fun `edge case - nested pack with very similar sourceFolder paths`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Icon" + sourceFolder = "icon" + } + nested { + name = "IconFilled" + sourceFolder = "icon-filled" + } + } + } + """.trimIndent(), + ) + + val iconDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/icon").createDirectories() + iconDir.resolve("home.svg").writeText(svgContent) + + val iconFilledDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/icon-filled").createDirectories() + iconFilledDir.resolve("home.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + } + + @Test + fun `edge case - sourceFolder with special characters`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Pack" + sourceFolder = "pack_v1-test.2" + } + } + } + """.trimIndent(), + ) + + val packDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/pack_v1-test.2").createDirectories() + packDir.resolve("icon.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + } + + @Test + fun `edge case - deeply nested with many icons (stress)`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Material" + sourceFolder = "material" + + nested { + name = "Action" + sourceFolder = "action" + + nested { + name = "Filled" + sourceFolder = "filled" + } + } + } + } + } + """.trimIndent(), + ) + + // Create many icons at deep level + val filledDir = + root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/material/action/filled").createDirectories() + repeat(50) { i -> + filledDir.resolve("icon_$i.svg").writeText(svgContent) + } + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + // Just verify 50 icons were processed (may output as "icons" or similar) + } + + @Test + fun `edge case - multiple nested branches with different depths`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Shallow" + sourceFolder = "shallow" + } + nested { + name = "Deep" + sourceFolder = "d" + + nested { + name = "Deeper" + sourceFolder = "dd" + + nested { + name = "Deepest" + sourceFolder = "ddd" + } + } + } + } + } + """.trimIndent(), + ) + + val shallowDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/shallow").createDirectories() + shallowDir.resolve("icon1.svg").writeText(svgContent) + + val deepestDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/d/dd/ddd").createDirectories() + deepestDir.resolve("icon2.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + } + + @Test + fun `edge case - flatPackage with deep nesting - all names unified`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = true + + nested { + name = "Material" + sourceFolder = "m" + + nested { + name = "Action" + sourceFolder = "a" + + nested { + name = "Filled" + sourceFolder = "f" + } + } + nested { + name = "Shape" + sourceFolder = "s" + + nested { + name = "Filled" + sourceFolder = "f2" + } + } + } + } + } + """.trimIndent(), + ) + + val actionDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/m/a/f").createDirectories() + actionDir.resolve("home.svg").writeText(svgContent) + + val shapeDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/m/s/f2").createDirectories() + shapeDir.resolve("square.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + // All should be in x.y.z package when flat + } + + @Test + fun `edge case - non-flatPackage generates correct package names`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Material" + sourceFolder = "material" + + nested { + name = "Action" + sourceFolder = "action" + + nested { + name = "Filled" + sourceFolder = "filled" + } + } + } + } + } + """.trimIndent(), + ) + + val filledDir = + root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/material/action/filled").createDirectories() + filledDir.resolve("home.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + // Package should be x.y.z.material.action.filled + } + + @Test + fun `edge case - sourceFolders with forward slashes handled correctly`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Material" + sourceFolder = "material/action/filled" + } + } + } + """.trimIndent(), + ) + + // File at full path + val filledDir = + root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/material/action/filled").createDirectories() + filledDir.resolve("home.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + } + + @Test + fun `edge case - files in parent directory not matched to child pack`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Material" + sourceFolder = "material" + + nested { + name = "Action" + sourceFolder = "action" + } + } + } + } + """.trimIndent(), + ) + + // Put icon in parent folder + val materialDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/material").createDirectories() + materialDir.resolve("parent-icon.svg").writeText(svgContent) + + // Put icon in nested folder + val actionDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/material/action").createDirectories() + actionDir.resolve("child-icon.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + // Should be OK - parent icon won't match any pack, child will match Action pack + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + } + + @Test + fun `edge case - unicode in nested pack names`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Solid" + sourceFolder = "solid" + } + } + } + """.trimIndent(), + ) + + val solidDir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/solid").createDirectories() + solidDir.resolve("icon.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + } + + @Test + fun `edge case - identical nested pack names at different levels allowed`(@TempDir root: Path) { + root.writeSettingsFile() + root.resolve("build.gradle.kts").writeText( + """ + plugins { + kotlin("jvm") + id("io.github.composegears.valkyrie") + } + + valkyrie { + packageName = "x.y.z" + + iconPack { + name = "ValkyrieIcons" + targetSourceSet = "main" + useFlatPackage = false + + nested { + name = "Filled" + sourceFolder = "filled1" + + nested { + name = "Filled" + sourceFolder = "filled2" + } + } + } + } + """.trimIndent(), + ) + + val filled2Dir = root.resolve("src/main/${DEFAULT_RESOURCE_DIRECTORY}/filled1/filled2").createDirectories() + filled2Dir.resolve("icon.svg").writeText(svgContent) + + val result = runTask(root, TASK_NAME) + assertThat(result).taskWasSuccessful(":${TASK_NAME}") + } +} diff --git a/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/SimplePackPluginConfigurationTest.kt b/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/SimplePackPluginConfigurationTest.kt similarity index 97% rename from tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/SimplePackPluginConfigurationTest.kt rename to tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/SimplePackPluginConfigurationTest.kt index d0ebe2b43..9c80d7df7 100644 --- a/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/SimplePackPluginConfigurationTest.kt +++ b/tools/gradle-plugin/src/test/kotlin/io/github/composegears/valkyrie/gradle/iconpack/SimplePackPluginConfigurationTest.kt @@ -1,4 +1,4 @@ -package io.github.composegears.valkyrie.gradle +package io.github.composegears.valkyrie.gradle.iconpack import assertk.assertThat import assertk.assertions.contains @@ -125,7 +125,7 @@ class SimplePackPluginConfigurationTest : CommonGradleTest() { assertThat(result).taskHadResult(":generateValkyrieImageVectorCommonTest", SKIPPED) assertThat(result).taskHadResult(":generateValkyrieImageVectorJvmMain", SKIPPED) assertThat(result).taskHadResult(":generateValkyrieImageVectorJvmTest", SKIPPED) - assertThat(result).taskWasSuccessful(":$TASK_NAME") + assertThat(result).taskWasSuccessful(":${TASK_NAME}") assertThat(result.output).contains("Generated \"ValkyrieIcons\" iconpack in package \"x.y.z\"") assertThat(result.output).contains("Generated 17 ImageVector icons in package \"x.y.z\"") diff --git a/tools/idea-plugin/CHANGELOG.md b/tools/idea-plugin/CHANGELOG.md index 489ad5c89..6d5636a29 100644 --- a/tools/idea-plugin/CHANGELOG.md +++ b/tools/idea-plugin/CHANGELOG.md @@ -2,9 +2,17 @@ ## Unreleased +### Added + +- [IconPack] Add TreeView component for hierarchical icon pack structure representation with drag-and-drop support +- [IconPack] Add undo/redo functionality for TreeView component with keyboard shortcuts (`Cmd+Z` / `Cmd+Shift+Z`) +- [IconPack] Add nested drop indicator style to TreeView for improved visual feedback during drag operations + ### Changed - [IconPack] Replace code snippet tooltip on Package and IconPack name fields with a plain text description tooltip +- [IconPack] Allow unlimited nesting of icon packs within the TreeView component for better organization of large icon + sets ### Fixed diff --git a/tools/idea-plugin/build.gradle.kts b/tools/idea-plugin/build.gradle.kts index 3c2d04cfb..9f147831c 100644 --- a/tools/idea-plugin/build.gradle.kts +++ b/tools/idea-plugin/build.gradle.kts @@ -1,3 +1,4 @@ + import io.github.composegears.valkyrie.excludeAndroidBuildTools import io.github.composegears.valkyrie.task.CheckVersionCompatibility import org.jetbrains.changelog.Changelog @@ -48,13 +49,14 @@ configurations.getByName("implementation") { } dependencies { - implementation(projects.components.generator.iconpack) - implementation(projects.components.generator.jvm.imagevector) implementation(projects.components.parser.jvm.svg) implementation(projects.components.parser.unified) implementation(projects.sdk.compose.foundation) implementation(projects.sdk.compose.icons) implementation(projects.sdk.core.extensions) + implementation(projects.sdk.core.tree) + implementation(projects.sdk.generator.kt.iconpack) + implementation(projects.sdk.generator.kt.imagevector.jvm) implementation(projects.sdk.intellij.psi.iconpack) implementation(projects.sdk.intellij.psi.imagevector) implementation(projects.sdk.ir.core) diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/Toolbar.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/Toolbar.kt index aa09e6c67..78755cd27 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/Toolbar.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/Toolbar.kt @@ -257,6 +257,20 @@ fun PreviewCodeAction( ) } +@Composable +fun MoreHorizontalAction( + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + TooltipIconButton( + modifier = modifier, + key = AllIconsKeys.Actions.MoreHorizontal, + contentDescription = stringResource("accessibility.edit"), + onClick = onClick, + tooltipText = stringResource("component.toolbar.edit.tooltip"), + ) +} + @Composable fun EditToggleAction( selected: Boolean, diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt index 3fa697a9a..ac98bc69e 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/settings/InMemorySettings.kt @@ -2,7 +2,7 @@ package io.github.composegears.valkyrie.settings import com.composegears.tiamat.navigation.SavedState import com.intellij.openapi.project.Project -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat import io.github.composegears.valkyrie.sdk.shared.ValkyrieMode import io.github.composegears.valkyrie.service.PersistentSettings import io.github.composegears.valkyrie.service.PersistentSettings.Companion.persistentSettings @@ -98,7 +98,7 @@ class InMemorySettings(project: Project) { .split(",") .filter { it.isNotEmpty() }, generatePreview = generatePreview, - outputFormat = OutputFormat.from(outputFormat), + outputFormat = OutputFormat.from(outputFormat) ?: OutputFormat.BackingProperty, useComposeColors = useComposeColors, indentSize = indentSize, flatPackage = flatPackage, diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/TreeView.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/TreeView.kt new file mode 100644 index 000000000..16d3b2859 --- /dev/null +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/TreeView.kt @@ -0,0 +1,850 @@ +package io.github.composegears.valkyrie.ui.screen.mode.iconpack.common + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.isCtrlPressed +import androidx.compose.ui.input.key.isMetaPressed +import androidx.compose.ui.input.key.isShiftPressed +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.key.type +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.boundsInRoot +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupPositionProvider +import androidx.compose.ui.window.PopupProperties +import io.github.composegears.valkyrie.jewel.MoreHorizontalAction +import io.github.composegears.valkyrie.jewel.button.TooltipIconButton +import io.github.composegears.valkyrie.sdk.compose.foundation.rememberMutableState +import io.github.composegears.valkyrie.sdk.core.tree.TreeNode +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.core.tree.contains +import io.github.composegears.valkyrie.sdk.core.tree.copy +import io.github.composegears.valkyrie.sdk.core.tree.find +import java.awt.Cursor +import kotlin.math.abs +import kotlin.math.roundToInt +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.icons.AllIconsKeys + +enum class DropPosition { Before, After, Inside } + +internal enum class DropIndicatorStyle { Sibling, NestedAppend } + +private val DragPointerIcon = PointerIcon(Cursor(Cursor.MOVE_CURSOR)) + +internal data class NodeDropLayout( + val rowLeft: Float, + val rowTop: Float, + val rowCenterY: Float, + val subtreeBottom: Float, +) + +internal data class ResolvedDropTarget( + val target: T, + val position: DropPosition, + val isInvalid: Boolean, + val indicatorStyle: DropIndicatorStyle = DropIndicatorStyle.Sibling, +) + +@Stable +class TreeHistoryState( + initialTree: TreeNode, + private val maxHistorySize: Int = 100, +) { + var tree: TreeNode by mutableStateOf(initialTree) + private set + + private val undoStack = ArrayDeque>() + private val redoStack = ArrayDeque>() + + val canUndo: Boolean + get() = undoStack.isNotEmpty() + + val canRedo: Boolean + get() = redoStack.isNotEmpty() + + fun move(from: T, to: T, position: DropPosition) { + update { currentTree -> + currentTree.moveNode( + from = from, + to = to, + position = position, + ) + } + } + + fun update(transform: (TreeNode) -> TreeNode) { + val updatedTree = transform(tree) + if (updatedTree == tree) return + + undoStack.addLast(tree) + while (undoStack.size > maxHistorySize) { + undoStack.removeFirst() + } + redoStack.clear() + tree = updatedTree + } + + fun undo() { + val previousTree = undoStack.removeLastOrNull() ?: return + redoStack.addLast(tree) + tree = previousTree + } + + fun redo() { + val nextTree = redoStack.removeLastOrNull() ?: return + undoStack.addLast(tree) + tree = nextTree + } + + fun reset(newTree: TreeNode) { + tree = newTree + undoStack.clear() + redoStack.clear() + } +} + +@Composable +fun rememberTreeHistoryState( + initialTree: TreeNode, + maxHistorySize: Int = 100, +): TreeHistoryState = remember(initialTree, maxHistorySize) { + TreeHistoryState( + initialTree = initialTree, + maxHistorySize = maxHistorySize, + ) +} + +@Stable +private class DragState { + var dragging: T? by mutableStateOf(null) + var dropTarget: T? by mutableStateOf(null) + var dropPosition: DropPosition by mutableStateOf(DropPosition.Before) + var isDropInvalid: Boolean by mutableStateOf(false) + var dropIndicatorStyle: DropIndicatorStyle by mutableStateOf(DropIndicatorStyle.Sibling) + var dragPosition: Offset? by mutableStateOf(null) + var grabOffset: Offset = Offset.Zero + val nodeLayouts = mutableStateMapOf() + val contentWidths = mutableStateMapOf() + + fun updateNodeRowLayout(node: T, coords: LayoutCoordinates) { + val bounds = coords.boundsInRoot() + val current = nodeLayouts[node] + nodeLayouts[node] = NodeDropLayout( + rowLeft = bounds.left, + rowTop = bounds.top, + rowCenterY = bounds.center.y, + subtreeBottom = current?.subtreeBottom ?: bounds.bottom, + ) + } + + fun updateNodeSubtreeLayout(node: T, coords: LayoutCoordinates) { + val bounds = coords.boundsInRoot() + val current = nodeLayouts[node] + nodeLayouts[node] = NodeDropLayout( + rowLeft = current?.rowLeft ?: bounds.left, + rowTop = current?.rowTop ?: bounds.top, + rowCenterY = current?.rowCenterY ?: bounds.center.y, + subtreeBottom = bounds.bottom, + ) + } + + fun removeNode(node: T) { + nodeLayouts.remove(node) + contentWidths.remove(node) + if (dropTarget == node) { + dropTarget = null + dropPosition = DropPosition.Before + isDropInvalid = false + dropIndicatorStyle = DropIndicatorStyle.Sibling + } + if (dragging == node) { + reset() + } + } + + fun updateDrop( + rootPosition: Offset, + indentPx: Float, + insideHalfZonePx: Float, + isValidTarget: (T) -> Boolean = { true }, + ) { + val resolved = resolveDropTarget( + rootPosition = rootPosition, + indentPx = indentPx, + insideHalfZonePx = insideHalfZonePx, + dragging = dragging, + layouts = nodeLayouts, + isValidTarget = isValidTarget, + ) + + if (resolved == null) { + dropTarget = null + dropPosition = DropPosition.Before + isDropInvalid = false + dropIndicatorStyle = DropIndicatorStyle.Sibling + return + } + + dropTarget = resolved.target + dropPosition = resolved.position + isDropInvalid = resolved.isInvalid + dropIndicatorStyle = resolved.indicatorStyle + } + + fun commit(onMove: (T, T, DropPosition) -> Unit) { + val from = dragging + val to = dropTarget + if (from != null && to != null && from != to && !isDropInvalid) { + onMove(from, to, dropPosition) + } + reset() + } + + fun reset() { + dragging = null + dropTarget = null + dropPosition = DropPosition.Before + isDropInvalid = false + dropIndicatorStyle = DropIndicatorStyle.Sibling + dragPosition = null + grabOffset = Offset.Zero + } +} + +internal fun resolveDropTarget( + rootPosition: Offset, + indentPx: Float, + insideHalfZonePx: Float, + dragging: T?, + layouts: Map, + isValidTarget: (T) -> Boolean = { true }, +): ResolvedDropTarget? { + return layouts.entries + .asSequence() + .filter { it.key != dragging } + .map { (target, layout) -> + val rootY = rootPosition.y + val position = when { + abs(rootY - layout.rowCenterY) <= insideHalfZonePx -> DropPosition.Inside + rootY < layout.rowCenterY -> DropPosition.Before + else -> DropPosition.After + } + val anchorX = when (position) { + DropPosition.Before, + DropPosition.After, + -> layout.rowLeft + indentPx / 2f + DropPosition.Inside -> layout.rowLeft + indentPx + } + val anchorY = when (position) { + DropPosition.Before -> layout.rowTop + DropPosition.Inside -> layout.rowCenterY + DropPosition.After -> layout.subtreeBottom + } + Triple( + ResolvedDropTarget( + target = target, + position = position, + isInvalid = !isValidTarget(target), + indicatorStyle = when { + position == DropPosition.After && layout.rowLeft > 0f -> DropIndicatorStyle.NestedAppend + else -> DropIndicatorStyle.Sibling + }, + ), + abs(rootPosition.x - anchorX) + abs(rootY - anchorY), + layout.subtreeBottom, + ) + } + .minWithOrNull(compareBy, Float, Float>> { it.second }.thenByDescending { it.third }) + ?.first +} + +@Suppress("ktlint:compose:content-slot-reused") +@Composable +fun TreeView( + root: TreeNode, + modifier: Modifier = Modifier, + indent: Dp = 24.dp, + lineColor: Color = JewelTheme.globalColors.borders.normal, + lineStrokeWidth: Dp = 1.dp, + onMove: ((from: T, to: T, position: DropPosition) -> Unit)? = null, + content: @Composable (data: T, isDragging: Boolean) -> Unit, +) { + val dragState = if (onMove != null) remember { DragState() } else null + val descendantsByNode = remember(root) { root.descendantsByNode() } + + Column(modifier = modifier.width(IntrinsicSize.Max)) { + content(root.data, dragState?.dragging != null) + TreeNodes( + nodes = root.children, + descendantsByNode = descendantsByNode, + indent = indent, + lineColor = lineColor, + lineStrokeWidth = lineStrokeWidth, + dragState = dragState, + onMove = onMove, + content = content, + ) + } + + val draggingData = dragState?.dragging + val dragPos = dragState?.dragPosition + if (draggingData != null && dragPos != null) { + val grabOffset = dragState.grabOffset + val positionProvider = remember(dragPos) { + object : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: androidx.compose.ui.unit.IntRect, + windowSize: androidx.compose.ui.unit.IntSize, + layoutDirection: androidx.compose.ui.unit.LayoutDirection, + popupContentSize: androidx.compose.ui.unit.IntSize, + ): IntOffset = IntOffset( + x = (dragPos.x - grabOffset.x).roundToInt(), + y = (dragPos.y - grabOffset.y).roundToInt(), + ) + } + } + Popup( + popupPositionProvider = positionProvider, + properties = PopupProperties(focusable = false), + ) { + Box( + modifier = Modifier + .pointerHoverIcon(DragPointerIcon) + .background( + color = JewelTheme.globalColors.panelBackground, + shape = RoundedCornerShape(6.dp), + ) + .border( + width = 1.dp, + color = JewelTheme.globalColors.borders.normal, + shape = RoundedCornerShape(6.dp), + ) + .padding(horizontal = 8.dp, vertical = 4.dp) + .alpha(0.92f), + ) { + content(draggingData, true) + } + } + } +} + +@Composable +private fun TreeNodes( + nodes: List>, + descendantsByNode: Map>, + indent: Dp, + lineColor: Color, + lineStrokeWidth: Dp, + dragState: DragState?, + onMove: ((T, T, DropPosition) -> Unit)?, + content: @Composable (T, Boolean) -> Unit, +) { + Column { + nodes.forEachIndexed { index, node -> + TreeNodeRow( + node = node, + isLast = index == nodes.lastIndex, + descendantsByNode = descendantsByNode, + indent = indent, + lineColor = lineColor, + lineStrokeWidth = lineStrokeWidth, + dragState = dragState, + onMove = onMove, + content = content, + ) + } + } +} + +@Composable +private fun TreeNodeRow( + node: TreeNode, + isLast: Boolean, + descendantsByNode: Map>, + indent: Dp, + lineColor: Color, + lineStrokeWidth: Dp, + dragState: DragState?, + onMove: ((T, T, DropPosition) -> Unit)?, + content: @Composable (T, Boolean) -> Unit, +) { + val isDraggingThis = dragState?.dragging == node.data + val isDropTarget = dragState?.dropTarget == node.data + val isInvalidDropTarget = dragState != null && isDropTarget && dragState.isDropInvalid + val isAnyDragging = dragState?.dragging != null + val draggablePointerIcon = when { + isDraggingThis -> DragPointerIcon + isAnyDragging -> PointerIcon.Default + else -> PointerIcon.Hand + } + val insideColor = JewelTheme.globalColors.outlines.focused + val invalidDropColor = JewelTheme.globalColors.outlines.error + var outerCoords by remember(node.data) { mutableStateOf(null) } + + DisposableEffect(dragState, node.data) { + onDispose { + dragState?.removeNode(node.data) + } + } + + Column( + modifier = Modifier + .onGloballyPositioned { coords -> + outerCoords = coords + dragState?.updateNodeSubtreeLayout(node.data, coords) + } + .let { mod -> + if (dragState != null && onMove != null) { + mod + .pointerHoverIcon(draggablePointerIcon) + .pointerInput(node.data) { + detectDragGestures( + onDragStart = { startOffset -> + dragState.dragging = node.data + dragState.grabOffset = startOffset + dragState.dragPosition = outerCoords?.positionInRoot()?.plus(startOffset) + }, + onDrag = { change, _ -> + val rootPos = outerCoords?.positionInRoot()?.plus(change.position) + dragState.dragPosition = rootPos + rootPos?.let { + val invalidTargets = descendantsByNode[dragState.dragging].orEmpty() + dragState.updateDrop( + rootPosition = it, + indentPx = indent.toPx(), + insideHalfZonePx = 9.dp.toPx(), + isValidTarget = { candidate -> candidate !in invalidTargets }, + ) + } + }, + onDragEnd = { dragState.commit(onMove) }, + onDragCancel = { dragState.reset() }, + ) + } + } else { + mod + } + }, + ) { + if (isDropTarget && dragState.dropPosition == DropPosition.Before) { + DropIndicator( + color = if (isInvalidDropTarget) invalidDropColor else lineColor, + indent = indent, + isInvalid = isInvalidDropTarget, + style = DropIndicatorStyle.Sibling, + ) + } + + val isInsideTarget = isDropTarget && dragState.dropPosition == DropPosition.Inside + Row( + modifier = Modifier + .height(IntrinsicSize.Min) + .fillMaxWidth() + .onGloballyPositioned { dragState?.updateNodeRowLayout(node.data, it) } + .drawWithContent { + drawContent() + if (isInsideTarget) { + val targetColor = if (isInvalidDropTarget) invalidDropColor else insideColor + val accentW = 3.dp.toPx() + val radius = 4.dp.toPx() + val indentPx = indent.toPx() + val lineXPx = indentPx / 2 + val innerWidth = dragState.contentWidths[node.data] + ?: (size.width - indentPx) + val contentRight = indentPx + innerWidth + 8.dp.toPx() + drawRoundRect( + color = targetColor.copy(alpha = if (isInvalidDropTarget) 0.14f else 0.10f), + topLeft = Offset(indentPx, 0f), + size = Size(contentRight - indentPx, size.height), + cornerRadius = CornerRadius(radius), + ) + drawRoundRect( + color = targetColor.copy(alpha = 0.95f), + topLeft = Offset(lineXPx - accentW / 2, 0f), + size = Size(accentW, size.height), + cornerRadius = CornerRadius(accentW / 2), + ) + } + } + .alpha(if (isDraggingThis) 0.35f else 1f), + ) { + TreeBranch( + isLast = isLast, + indent = indent, + color = lineColor, + strokeWidth = lineStrokeWidth, + modifier = Modifier.fillMaxHeight(), + ) + Box(modifier = Modifier.weight(1f).padding(vertical = 4.dp)) { + Box( + modifier = Modifier.onGloballyPositioned { coords -> + dragState?.contentWidths?.set(node.data, coords.size.width.toFloat()) + }, + ) { + content(node.data, isAnyDragging) + } + } + } + + if (node.children.isNotEmpty()) { + Row(modifier = Modifier.height(IntrinsicSize.Min).fillMaxWidth()) { + Box( + modifier = Modifier + .width(indent) + .fillMaxHeight() + .drawBehind { + if (!isLast) { + drawLine( + color = lineColor, + start = Offset(size.width / 2, 0f), + end = Offset(size.width / 2, size.height), + strokeWidth = lineStrokeWidth.toPx(), + ) + } + }, + ) + Column(modifier = Modifier.weight(1f)) { + TreeNodes( + nodes = node.children, + descendantsByNode = descendantsByNode, + indent = indent, + lineColor = lineColor, + lineStrokeWidth = lineStrokeWidth, + dragState = dragState, + onMove = onMove, + content = content, + ) + } + } + } + + if (isDropTarget && dragState.dropPosition == DropPosition.After) { + DropIndicator( + color = if (isInvalidDropTarget) invalidDropColor else lineColor, + indent = indent, + isInvalid = isInvalidDropTarget, + style = dragState.dropIndicatorStyle, + ) + } + } +} + +@Composable +private fun DropIndicator( + color: Color, + indent: Dp, + isInvalid: Boolean = false, + style: DropIndicatorStyle = DropIndicatorStyle.Sibling, +) { + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(8.dp), + ) { + val cy = size.height / 2 + val r = 3.dp.toPx() + val strokeW = 1.5.dp.toPx() + val lineX = indent.toPx() / 2 + val indicatorStartX = lineX + r * 2 + val nestedStartX = indent.toPx() + + when (style) { + DropIndicatorStyle.Sibling -> { + drawCircle( + color = color, + radius = r, + center = Offset(lineX, cy), + style = Stroke(width = strokeW), + ) + drawLine( + color = color, + start = Offset(indicatorStartX, cy), + end = Offset(size.width, cy), + strokeWidth = strokeW, + ) + } + DropIndicatorStyle.NestedAppend -> { + drawLine( + color = color, + start = Offset(lineX, 0f), + end = Offset(lineX, cy), + strokeWidth = strokeW, + ) + drawCircle( + color = color, + radius = r, + center = Offset(lineX, cy), + ) + drawLine( + color = color, + start = Offset(nestedStartX, cy), + end = Offset(size.width, cy), + strokeWidth = strokeW, + ) + } + } + + if (isInvalid) { + val crossHalfSize = 3.dp.toPx() + val crossCenter = Offset( + x = when (style) { + DropIndicatorStyle.Sibling -> indicatorStartX + 8.dp.toPx() + DropIndicatorStyle.NestedAppend -> nestedStartX + 8.dp.toPx() + }, + y = cy, + ) + drawLine( + color = color, + start = Offset(crossCenter.x - crossHalfSize, crossCenter.y - crossHalfSize), + end = Offset(crossCenter.x + crossHalfSize, crossCenter.y + crossHalfSize), + strokeWidth = strokeW, + ) + drawLine( + color = color, + start = Offset(crossCenter.x - crossHalfSize, crossCenter.y + crossHalfSize), + end = Offset(crossCenter.x + crossHalfSize, crossCenter.y - crossHalfSize), + strokeWidth = strokeW, + ) + } + } +} + +@Composable +private fun TreeBranch( + isLast: Boolean, + indent: Dp, + color: Color, + strokeWidth: Dp, + modifier: Modifier = Modifier, +) { + Canvas( + modifier = modifier + .width(indent) + .height(24.dp), + ) { + val midX = size.width / 2 + val midY = size.height / 2 + val sw = strokeWidth.toPx() + + drawLine( + color = color, + start = Offset(midX, 0f), + end = Offset(midX, if (isLast) midY else size.height), + strokeWidth = sw, + ) + drawLine( + color = color, + start = Offset(midX, midY), + end = Offset(size.width, midY), + strokeWidth = sw, + ) + } +} + +internal fun TreeNode.moveNode(from: T, to: T, position: DropPosition): TreeNode { + val dragged = find(from) ?: return this + if (to in dragged) return this + val withoutDragged = removeNode(from) + return withoutDragged.insertNode(dragged, to, position) +} + +private fun TreeNode.descendantsByNode(): Map> { + val descendants = mutableMapOf>() + + fun visit(node: TreeNode): Set { + val directChildren = node.children.map { it.data }.toSet() + val nestedChildren = node.children.flatMapTo(mutableSetOf()) { visit(it) } + return (directChildren + nestedChildren).also { descendants[node.data] = it } + } + + visit(this) + return descendants +} + +private fun TreeNode.removeNode(target: T): TreeNode { + if (data == target) return this + return copy(children = children.filter { it.data != target }.map { it.removeNode(target) }) +} + +private fun TreeNode.insertNode(node: TreeNode, nearTarget: T, position: DropPosition): TreeNode { + if (data == nearTarget && position == DropPosition.Inside) { + return copy(children = children + node) + } + val idx = children.indexOfFirst { it.data == nearTarget } + if (idx != -1) { + if (position == DropPosition.Inside) { + return copy( + children = children.map { + if (it.data == nearTarget) it.copy(children = it.children + node) else it + }, + ) + } + val newChildren = children.toMutableList() + newChildren.add(if (position == DropPosition.Before) idx else idx + 1, node) + return copy(children = newChildren) + } + return copy(children = children.map { it.insertNode(node, nearTarget, position) }) +} + +private fun Modifier.undoRedoKeybindings( + canUndo: Boolean, + canRedo: Boolean, + onUndo: () -> Unit, + onRedo: () -> Unit, +): Modifier = onPreviewKeyEvent { keyEvent -> + when { + keyEvent.isUndoShortcut() && canUndo -> { + onUndo() + true + } + keyEvent.isRedoShortcut() && canRedo -> { + onRedo() + true + } + else -> false + } +} + +private fun KeyEvent.isUndoShortcut(): Boolean { + return type == KeyEventType.KeyDown && + (isCtrlPressed || isMetaPressed) && + !isShiftPressed && + key == Key.Z +} + +private fun KeyEvent.isRedoShortcut(): Boolean { + return type == KeyEventType.KeyDown && when { + (isCtrlPressed || isMetaPressed) && isShiftPressed && key == Key.Z -> true + isCtrlPressed && key == Key.Y -> true + else -> false + } +} + +@OptIn(ExperimentalComposeUiApi::class) +@Preview +@Composable +private fun TreeDemoPreview() { + val initialTree = buildTree("ValkyrieIcons") { + child("Filled") { + child("Add") + child("Arrows") { + child("ArrowUp") + child("ArrowDown") + } + child("Close") + } + child("Outlined") { + child("Home") + child("Search") + } + child("Logo") + } + + val history = rememberTreeHistoryState(initialTree = initialTree) + + Column( + modifier = Modifier + .padding(16.dp) + .undoRedoKeybindings( + canUndo = history.canUndo, + canRedo = history.canRedo, + onUndo = history::undo, + onRedo = history::redo, + ) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + TooltipIconButton( + key = AllIconsKeys.Actions.Undo, + contentDescription = "Undo", + enabled = history.canUndo, + onClick = history::undo, + tooltipText = "Undo move (Cmd/Ctrl+Z)", + ) + TooltipIconButton( + key = AllIconsKeys.Actions.Redo, + contentDescription = "Redo", + enabled = history.canRedo, + onClick = history::redo, + tooltipText = "Redo move (Cmd/Ctrl+Shift+Z)", + ) + } + + TreeView( + root = history.tree, + onMove = history::move, + ) { value, isDragging -> + var isHovered by rememberMutableState { false } + + Row( + modifier = Modifier + .heightIn(min = 24.dp) + .padding(start = 4.dp) + .onPointerEvent(PointerEventType.Enter) { isHovered = true } + .onPointerEvent(PointerEventType.Exit) { isHovered = false }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text(text = value) + MoreHorizontalAction( + modifier = Modifier.alpha(if (isHovered && !isDragging) 1f else 0f), + onClick = {}, + ) + } + } + } +} diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/util/IconPackWriter.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/util/IconPackWriter.kt index c0b307196..67426da86 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/util/IconPackWriter.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/util/IconPackWriter.kt @@ -4,9 +4,10 @@ import com.intellij.openapi.command.writeCommandAction import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil -import io.github.composegears.valkyrie.generator.core.IconPack -import io.github.composegears.valkyrie.generator.iconpack.IconPackGenerator -import io.github.composegears.valkyrie.generator.iconpack.IconPackGeneratorConfig +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGenerator +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGeneratorConfig import io.github.composegears.valkyrie.sdk.shared.ValkyrieMode import io.github.composegears.valkyrie.settings.InMemorySettings import io.github.composegears.valkyrie.settings.updateNestedPack @@ -34,10 +35,11 @@ object IconPackWriter { val iconPack = IconPackGenerator.create( config = IconPackGeneratorConfig( packageName = currentSettings.packageName, - iconPack = IconPack( - name = currentSettings.iconPackName, - nested = currentSettings.nestedPacks.map(::IconPack), - ), + iconPackTree = buildTree(currentSettings.iconPackName) { + currentSettings.nestedPacks.onEach { + child(it) + } + }, useExplicitMode = currentSettings.useExplicitMode, indentSize = currentSettings.indentSize, license = inputFieldState.license.text.ifEmpty { null }, diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/IconPackConversionViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/IconPackConversionViewModel.kt index 6b700005c..ad01ac2ba 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/IconPackConversionViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/conversion/IconPackConversionViewModel.kt @@ -6,14 +6,18 @@ import com.composegears.leviathan.compose.inject import com.composegears.tiamat.navigation.MutableSavedState import com.composegears.tiamat.navigation.recordOf import com.intellij.openapi.project.Project -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGenerator -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGeneratorConfig import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.isSvg import io.github.composegears.valkyrie.parser.unified.ext.isXml import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.sdk.core.extensions.safeAs +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.ImageVectorGenerator import io.github.composegears.valkyrie.settings.ValkyriesSettings import io.github.composegears.valkyrie.ui.di.DI import io.github.composegears.valkyrie.ui.extension.updateState @@ -162,8 +166,8 @@ class IconPackConversionViewModel( val settings = inMemorySettings.current val output = ImageVectorGenerator.convert( vector = icon.irImageVector, - iconName = icon.iconName.name, config = settings.toImageVectorConfig( + iconName = icon.iconName, packageName = icon.iconPack.iconPackage, nestedPackName = icon.iconPack.currentNestedPack, ), @@ -189,8 +193,8 @@ class IconPackConversionViewModel( is IconPack.Nested -> { val vectorSpecOutput = ImageVectorGenerator.convert( vector = icon.irImageVector, - iconName = icon.iconName.name, config = settings.toImageVectorConfig( + iconName = icon.iconName, packageName = icon.iconPack.iconPackage, nestedPackName = iconPack.currentNestedPack, ), @@ -208,8 +212,8 @@ class IconPackConversionViewModel( is IconPack.Single -> { val vectorSpecOutput = ImageVectorGenerator.convert( vector = icon.irImageVector, - iconName = icon.iconName.name, config = settings.toImageVectorConfig( + iconName = icon.iconName, packageName = icon.iconPack.iconPackage, nestedPackName = "", ), @@ -362,23 +366,32 @@ class IconPackConversionViewModel( } private fun ValkyriesSettings.toImageVectorConfig( + iconName: IconName, packageName: String, nestedPackName: String = "", ): ImageVectorGeneratorConfig { - return ImageVectorGeneratorConfig( + return ImageVectorGeneratorConfig.iconPack( + iconName = iconName.name, packageName = packageName, iconPackPackage = iconPackPackage, - packName = iconPackName, - nestedPackName = nestedPackName, - outputFormat = outputFormat, - useComposeColors = useComposeColors, - generatePreview = generatePreview, - useFlatPackage = flatPackage, - useExplicitMode = useExplicitMode, - addTrailingComma = addTrailingComma, - usePathDataString = usePathDataString, - indentSize = indentSize, - suppressUnusedReceiverWarning = suppressUnusedReceiverWarning, + iconPackTree = buildTree(iconPackName) { + if (nestedPackName.isNotEmpty()) { + child(nestedPackName) + } + }, + codeStyle = CodeStyleConfig( + useExplicitMode = useExplicitMode, + indentSize = indentSize, + ), + imageVector = ImageVectorConfig( + outputFormat = outputFormat, + useComposeColors = useComposeColors, + generatePreview = generatePreview, + useFlatPackage = flatPackage, + addTrailingComma = addTrailingComma, + usePathDataString = usePathDataString, + suppressUnusedReceiverWarning = suppressUnusedReceiverWarning, + ), ) } } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt index 476fb8ff9..e59d11598 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt @@ -4,10 +4,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.composegears.leviathan.compose.inject import com.intellij.openapi.project.Project -import io.github.composegears.valkyrie.generator.core.IconPack -import io.github.composegears.valkyrie.generator.iconpack.IconPackGenerator -import io.github.composegears.valkyrie.generator.iconpack.IconPackGeneratorConfig import io.github.composegears.valkyrie.sdk.core.extensions.safeAs +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGenerator +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGeneratorConfig import io.github.composegears.valkyrie.sdk.intellij.psi.iconpack.IconPackInfo import io.github.composegears.valkyrie.sdk.intellij.psi.iconpack.IconPackPsiParser import io.github.composegears.valkyrie.ui.di.DI @@ -92,20 +93,21 @@ class ExistingPackViewModel : ViewModel() { private fun previewIconPackObject() = viewModelScope.launch { val inputFieldState = currentState.safeAs()?.inputFieldState ?: return@launch - val iconPackCode = IconPackGenerator.create( + val iconPackTreeCode = IconPackGenerator.create( config = IconPackGeneratorConfig( packageName = inputFieldState.packageName.text, - iconPack = IconPack( - name = inputFieldState.iconPackName.text, - nested = inputFieldState.nestedPacks.map { IconPack(it.inputFieldState.text) }, - ), + iconPackTree = buildTree(inputFieldState.iconPackName.text) { + inputFieldState.nestedPacks.forEach { + child(it.inputFieldState.text) + } + }, useExplicitMode = inMemorySettings.current.useExplicitMode, indentSize = inMemorySettings.current.indentSize, license = inputFieldState.license.text.ifEmpty { null }, ), ).content - _events.send(ExistingPackEvent.PreviewIconPackObject(code = iconPackCode)) + _events.send(ExistingPackEvent.PreviewIconPackObject(code = iconPackTreeCode)) } private fun saveIconPack(project: Project, isModified: Boolean) { @@ -130,12 +132,12 @@ class ExistingPackViewModel : ViewModel() { private fun IconPackInfo.toInputFieldState(): InputFieldState { return InputFieldState( license = InputState(text = license.orEmpty()), - iconPackName = InputState(text = iconPack.name, enabled = false), + iconPackName = InputState(text = iconPackTree.data, enabled = false), packageName = InputState(text = packageName, enabled = false), - nestedPacks = iconPack.nested.mapIndexed { index, pack -> + nestedPacks = iconPackTree.children.mapIndexed { index, pack -> NestedPack( id = index.toString(), - inputFieldState = InputState(text = pack.name, enabled = false), + inputFieldState = InputState(text = pack.data, enabled = false), ) }, ) diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt index 110681a01..9564688fc 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt @@ -4,11 +4,12 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.composegears.leviathan.compose.inject import com.intellij.openapi.project.Project -import io.github.composegears.valkyrie.generator.core.IconPack -import io.github.composegears.valkyrie.generator.iconpack.IconPackGenerator -import io.github.composegears.valkyrie.generator.iconpack.IconPackGeneratorConfig import io.github.composegears.valkyrie.parser.unified.util.PackageExtractor import io.github.composegears.valkyrie.sdk.core.extensions.safeAs +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGenerator +import io.github.composegears.valkyrie.sdk.generator.kt.iconpack.IconPackGeneratorConfig import io.github.composegears.valkyrie.ui.di.DI import io.github.composegears.valkyrie.ui.extension.updateState import io.github.composegears.valkyrie.ui.screen.mode.iconpack.common.model.DirectoryState @@ -114,10 +115,11 @@ class NewPackViewModel : ViewModel() { val iconPackCode = IconPackGenerator.create( config = IconPackGeneratorConfig( packageName = inputFieldState.packageName.text, - iconPack = IconPack( - name = inputFieldState.iconPackName.text, - nested = inputFieldState.nestedPacks.map { IconPack(it.inputFieldState.text) }, - ), + iconPackTree = buildTree(inputFieldState.iconPackName.text) { + inputFieldState.nestedPacks.forEach { + child(it.inputFieldState.text) + } + }, useExplicitMode = inMemorySettings.current.useExplicitMode, indentSize = inMemorySettings.current.indentSize, license = inputFieldState.license.text.ifEmpty { null }, diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/simple/conversion/SimpleConversionViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/simple/conversion/SimpleConversionViewModel.kt index 5f51a5e79..9fef31f8c 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/simple/conversion/SimpleConversionViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/simple/conversion/SimpleConversionViewModel.kt @@ -6,12 +6,14 @@ import com.composegears.leviathan.compose.inject import com.composegears.tiamat.navigation.MutableSavedState import com.composegears.tiamat.navigation.asStateFlow import com.composegears.tiamat.navigation.recordOf -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGenerator -import io.github.composegears.valkyrie.generator.jvm.imagevector.ImageVectorGeneratorConfig import io.github.composegears.valkyrie.parser.unified.ParserType import io.github.composegears.valkyrie.parser.unified.SvgXmlParser import io.github.composegears.valkyrie.parser.unified.ext.toIOPath import io.github.composegears.valkyrie.sdk.core.extensions.safeAs +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.CodeStyleConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.ImageVectorGeneratorConfig +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.jvm.ImageVectorGenerator import io.github.composegears.valkyrie.ui.di.DI import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconContent import io.github.composegears.valkyrie.ui.screen.mode.simple.conversion.model.IconSource.FileBasedIcon @@ -181,8 +183,7 @@ class SimpleConversionViewModel( val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = name, - config = createGeneratorConfig(), + config = createGeneratorConfig(iconName = name), ) IconContent( name = name, @@ -201,8 +202,7 @@ class SimpleConversionViewModel( val output = ImageVectorGenerator.convert( vector = parserOutput.irImageVector, - iconName = iconName, - config = createGeneratorConfig(), + config = createGeneratorConfig(iconName = iconName), ) IconContent( name = iconName, @@ -212,24 +212,25 @@ class SimpleConversionViewModel( } } - private fun createGeneratorConfig(): ImageVectorGeneratorConfig { + private fun createGeneratorConfig(iconName: String): ImageVectorGeneratorConfig { val valkyriesSettings = inMemorySettings.current - return ImageVectorGeneratorConfig( + return ImageVectorGeneratorConfig.simple( + iconName = iconName, // don't add package name for single icon conversion, let user decide where to put it packageName = "", - iconPackPackage = "", - packName = "", - nestedPackName = "", - outputFormat = valkyriesSettings.outputFormat, - useComposeColors = valkyriesSettings.useComposeColors, - generatePreview = valkyriesSettings.generatePreview, - useFlatPackage = false, - useExplicitMode = valkyriesSettings.useExplicitMode, - addTrailingComma = valkyriesSettings.addTrailingComma, - usePathDataString = valkyriesSettings.usePathDataString, - indentSize = valkyriesSettings.indentSize, - suppressUnusedReceiverWarning = valkyriesSettings.suppressUnusedReceiverWarning, + codeStyle = CodeStyleConfig( + useExplicitMode = valkyriesSettings.useExplicitMode, + indentSize = valkyriesSettings.indentSize, + ), + imageVector = ImageVectorConfig( + outputFormat = valkyriesSettings.outputFormat, + useComposeColors = valkyriesSettings.useComposeColors, + generatePreview = valkyriesSettings.generatePreview, + addTrailingComma = valkyriesSettings.addTrailingComma, + usePathDataString = valkyriesSettings.usePathDataString, + suppressUnusedReceiverWarning = valkyriesSettings.suppressUnusedReceiverWarning, + ), ) } } diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/SettingsViewModel.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/SettingsViewModel.kt index 1a1bc5d98..54d1cb066 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/SettingsViewModel.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/SettingsViewModel.kt @@ -3,7 +3,7 @@ package io.github.composegears.valkyrie.ui.screen.settings import androidx.lifecycle.ViewModel import com.composegears.leviathan.compose.inject import com.intellij.collaboration.async.mapState -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat import io.github.composegears.valkyrie.sdk.shared.ValkyrieMode import io.github.composegears.valkyrie.sdk.shared.ValkyrieMode.Unspecified import io.github.composegears.valkyrie.settings.updateOutputFormat diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/model/SettingsAction.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/model/SettingsAction.kt index cb15a5c1f..271fb34d4 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/model/SettingsAction.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/model/SettingsAction.kt @@ -1,6 +1,6 @@ package io.github.composegears.valkyrie.ui.screen.settings.model -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat import io.github.composegears.valkyrie.ui.domain.model.PreviewType sealed interface SettingsAction { diff --git a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/tabs/generator/GeneratorSettingsScreen.kt b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/tabs/generator/GeneratorSettingsScreen.kt index f95d2b403..a96a0fe2a 100644 --- a/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/tabs/generator/GeneratorSettingsScreen.kt +++ b/tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/settings/tabs/generator/GeneratorSettingsScreen.kt @@ -12,8 +12,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.composegears.tiamat.compose.navController import com.composegears.tiamat.compose.navDestination -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat.BackingProperty -import io.github.composegears.valkyrie.generator.jvm.imagevector.OutputFormat.LazyProperty import io.github.composegears.valkyrie.jewel.EditorText import io.github.composegears.valkyrie.jewel.highlight.rememberCodeHighlight import io.github.composegears.valkyrie.jewel.settings.CheckboxSettingsRow @@ -24,6 +22,8 @@ import io.github.composegears.valkyrie.jewel.settings.RadioButtonGroup import io.github.composegears.valkyrie.jewel.settings.RadioButtonTooltipRow import io.github.composegears.valkyrie.jewel.tooling.ProjectPreviewTheme import io.github.composegears.valkyrie.sdk.compose.foundation.rememberMutableState +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat.BackingProperty +import io.github.composegears.valkyrie.sdk.generator.kt.imagevector.common.OutputFormat.LazyProperty import io.github.composegears.valkyrie.ui.screen.settings.GeneratorSettings import io.github.composegears.valkyrie.ui.screen.settings.SettingsViewModel import io.github.composegears.valkyrie.ui.screen.settings.model.SettingsAction diff --git a/tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/TreeViewMoveNodeTest.kt b/tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/TreeViewMoveNodeTest.kt new file mode 100644 index 000000000..07ac188eb --- /dev/null +++ b/tools/idea-plugin/src/test/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/TreeViewMoveNodeTest.kt @@ -0,0 +1,175 @@ +package io.github.composegears.valkyrie.ui.screen.mode.iconpack.common + +import androidx.compose.ui.geometry.Offset +import assertk.assertThat +import assertk.assertions.isEqualTo +import io.github.composegears.valkyrie.sdk.core.tree.buildTree +import io.github.composegears.valkyrie.sdk.core.tree.child +import org.junit.jupiter.api.Test + +class TreeViewMoveNodeTest { + + private val sampleTree = buildTree("root") { + child("parent") { + child("child") + } + child("sibling") + } + + @Test + fun `moving node into its own descendant keeps tree unchanged`() { + val movedTree = sampleTree.moveNode( + from = "parent", + to = "child", + position = DropPosition.Inside, + ) + + assertThat(movedTree).isEqualTo(sampleTree) + } + + @Test + fun `moving node next to sibling still works`() { + val movedTree = sampleTree.moveNode( + from = "child", + to = "sibling", + position = DropPosition.After, + ) + + assertThat(movedTree).isEqualTo( + buildTree("root") { + child("parent") + child("sibling") + child("child") + }, + ) + } + + @Test + fun `undo restores previous tree after move`() { + val history = TreeHistoryState(initialTree = sampleTree) + + history.move( + from = "child", + to = "sibling", + position = DropPosition.After, + ) + + history.undo() + + assertThat(history.tree).isEqualTo(sampleTree) + } + + @Test + fun `redo reapplies undone move`() { + val history = TreeHistoryState(initialTree = sampleTree) + + history.move( + from = "child", + to = "sibling", + position = DropPosition.After, + ) + val movedTree = history.tree + + history.undo() + history.redo() + + assertThat(history.tree).isEqualTo(movedTree) + } + + @Test + fun `new move clears redo history`() { + val history = TreeHistoryState(initialTree = sampleTree) + + history.move( + from = "child", + to = "sibling", + position = DropPosition.After, + ) + history.undo() + + history.move( + from = "parent", + to = "sibling", + position = DropPosition.After, + ) + + assertThat(history.canRedo).isEqualTo(false) + } + + @Test + fun `drop target can resolve to parent after subtree bottom`() { + val resolved = resolveDropTarget( + rootPosition = Offset(x = 8f, y = 88f), + indentPx = 24f, + insideHalfZonePx = 9f, + dragging = "logo", + layouts = mapOf( + "outlined" to NodeDropLayout( + rowLeft = 0f, + rowTop = 0f, + rowCenterY = 10f, + subtreeBottom = 90f, + ), + "search" to NodeDropLayout( + rowLeft = 24f, + rowTop = 40f, + rowCenterY = 50f, + subtreeBottom = 60f, + ), + "logo" to NodeDropLayout( + rowLeft = 0f, + rowTop = 70f, + rowCenterY = 80f, + subtreeBottom = 90f, + ), + ), + ) + + assertThat(resolved).isEqualTo( + ResolvedDropTarget( + target = "outlined", + position = DropPosition.After, + isInvalid = false, + ), + ) + } + + @Test + fun `drop target can resolve to last child after when pointer is on nested indent`() { + val resolved = resolveDropTarget( + rootPosition = Offset(x = 34f, y = 68f), + indentPx = 24f, + insideHalfZonePx = 9f, + dragging = "logo", + layouts = mapOf( + "outlined" to NodeDropLayout( + rowLeft = 0f, + rowTop = 0f, + rowCenterY = 10f, + subtreeBottom = 60f, + ), + "search" to NodeDropLayout( + rowLeft = 24f, + rowTop = 40f, + rowCenterY = 50f, + subtreeBottom = 60f, + ), + "logo" to NodeDropLayout( + rowLeft = 0f, + rowTop = 70f, + rowCenterY = 80f, + subtreeBottom = 90f, + ), + ), + ) + + assertThat(resolved).isEqualTo( + ResolvedDropTarget( + target = "search", + position = DropPosition.After, + isInvalid = false, + indicatorStyle = DropIndicatorStyle.NestedAppend, + ), + ) + } +}