{"id":16208364,"url":"https://github.com/javiersegoviacordoba/kopy","last_synced_at":"2025-04-13T07:49:54.833Z","repository":{"id":253339387,"uuid":"711113032","full_name":"JavierSegoviaCordoba/kopy","owner":"JavierSegoviaCordoba","description":"Nested copies made easy in Kotlin","archived":false,"fork":false,"pushed_at":"2025-03-21T12:04:19.000Z","size":3949,"stargazers_count":238,"open_issues_count":3,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-13T07:49:46.266Z","etag":null,"topics":["codegen","compiler","copies","copy","deep","deep-copies","deep-copy","gradle","k2","kotlin","plugin","runtime"],"latest_commit_sha":null,"homepage":"https://kopy.javiersc.com","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JavierSegoviaCordoba.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"JavierSegoviaCordoba"}},"created_at":"2023-10-28T08:53:54.000Z","updated_at":"2025-04-10T19:11:06.000Z","dependencies_parsed_at":"2024-08-28T23:48:33.117Z","dependency_job_id":"6981c735-ed52-4921-b7f7-f6421a1a04da","html_url":"https://github.com/JavierSegoviaCordoba/kopy","commit_stats":null,"previous_names":["javiersegoviacordoba/kopy"],"tags_count":15,"template":false,"template_full_name":"JavierSegoviaCordoba/kotlin-template-javiersc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JavierSegoviaCordoba%2Fkopy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JavierSegoviaCordoba%2Fkopy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JavierSegoviaCordoba%2Fkopy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JavierSegoviaCordoba%2Fkopy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JavierSegoviaCordoba","download_url":"https://codeload.github.com/JavierSegoviaCordoba/kopy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248681494,"owners_count":21144700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codegen","compiler","copies","copy","deep","deep-copies","deep-copy","gradle","k2","kotlin","plugin","runtime"],"created_at":"2024-10-10T10:16:36.876Z","updated_at":"2025-04-13T07:49:54.809Z","avatar_url":"https://github.com/JavierSegoviaCordoba.png","language":"Kotlin","funding_links":["https://github.com/sponsors/JavierSegoviaCordoba"],"categories":[],"sub_categories":[],"readme":"![Kotlin version](https://img.shields.io/badge/kotlin-2.1.20-blueviolet?logo=kotlin\u0026logoColor=white)\n[![MavenCentral](https://img.shields.io/maven-central/v/com.javiersc.kotlin/kopy-compiler?label=MavenCentral)](https://repo1.maven.org/maven2/com/javiersc/kotlin/kopy-compiler/)\n[![Snapshot](https://img.shields.io/nexus/s/com.javiersc.kotlin/kopy-compiler?server=https%3A%2F%2Foss.sonatype.org%2F\u0026label=Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/com/javiersc/kotlin/kopy-compiler/)\n\n[![Build](https://img.shields.io/github/actions/workflow/status/JavierSegoviaCordoba/kopy/build-kotlin.yaml?label=Build\u0026logo=GitHub)](https://github.com/JavierSegoviaCordoba/kopy/tree/main)\n[![Coverage](https://img.shields.io/sonar/coverage/com.javiersc.kotlin.kopy?label=Coverage\u0026logo=SonarCloud\u0026logoColor=white\u0026server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=com.javiersc.kotlin.kopy)\n[![Quality](https://img.shields.io/sonar/quality_gate/com.javiersc.kotlin.kopy?label=Quality\u0026logo=SonarCloud\u0026logoColor=white\u0026server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=com.javiersc.kotlin.kopy)\n[![Tech debt](https://img.shields.io/sonar/tech_debt/com.javiersc.kotlin.kopy?label=Tech%20debt\u0026logo=SonarCloud\u0026logoColor=white\u0026server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=com.javiersc.kotlin.kopy)\n\n# Kopy\n\n\u003cimg src=\".docs/docs/assets/images/example.gif\" alt=\"example animation\"\u003e\n\n## Usage\n\n### IntelliJ IDEA or Android Studio configuration\n\n#### Enable K2 compiler in the IDE settings:\n\n```\nFile \u003e Settings \u003e Language \u0026 Frameworks \u003e Kotlin \u003e Enable K2 mode\n```\n\nIt is necessary to restart the IDE to do the next step.\n\n\u003cimg src=\".docs/docs/assets/images/k2.png\" width=\"649\" alt=\"k2\"/\u003e\n\n#### Allow third party K2 plugins:\n\nPopup the quick search with `shift + shift` and type `Registry...`, then search for:\n\n```\nkotlin.k2.only.bundled.compiler.plugins.enabled\n```\n\nAnd uncheck it.\n\n\u003cimg src=\".docs/docs/assets/images/registry.png\" width=\"1089\" alt=\"registry\"/\u003e\n\n#### Disclaimer IDE issues\n\n\u003e ⚠️ Note:\n\u003e\n\u003e If the code compiles and run in the terminal, but the IDE is failing with some error, try with a\n\u003e different IDE version.\n\u003e\n\u003e It is recommendable to use the latest IDEA version, including EAP ones if possible.\n\n### Download\n\nApply the plugin in the `build.gradle.kts` or `build.gradle`:\n\n```kotlin\nplugins {\n    id(\"com.javiersc.kotlin.kopy\") version \"$kopyVersion\"\n}\n```\n\nAs the plugin uses under the hood the Atomicfu library, the performance can be improved by\napplying the Atomicfu plugin, **it is important to apply the Kopy plugin before the Atomicfu one**.\n\n```kotlin\nplugins {\n    id(\"com.javiersc.kotlin.kopy\") version \"$kopyVersion\"\n    id(\"org.jetbrains.kotlin.plugin.atomicfu\") version \"$kotlinVersion\"\n}\n```\n\n### Plugin configurations\n\nThe extension `kopy` is available to configure the plugin:\n\n#### Debug mode\n\nThe `debug` option allows to enable the debug mode. It will measure the time each phase takes to be\ncomputed. It can help to benchmark any execution or find any issue. It can be `false` or `true`,\nbeing the default value `false`.\n\nWith the `reportPath` option, it is possible to specify the path where the report will be saved. The\ndefault value is `build/reports/kopy`.\n\n\u003e [!CAUTION]\n\u003e It is not recommended to enable the debug mode by default, as it will increase the build time.\n\n##### Example\n\n```kotlin\nplugins {\n    id(\"com.javiersc.kotlin.kopy\") version \"$version\"\n}\n\nkopy {\n    debug = true\n    reportPath = layout.buildDirectory.dir(\"reports/custom/kopy\")\n}\n```\n\n#### Copy functions\n\nThe `copyFunctions` option accepts the list of functions that will be generated. The default value\nis `listOf(KopyCopyFunctions.Copy, KopyCopyFunctions.Invoke)`.\n\nAn empty list will generate all copy functions (`copy`, and `invoke`).\n\n##### Example\n\n```kotlin\nplugins {\n    id(\"com.javiersc.kotlin.kopy\") version \"$version\"\n}\n\nkopy {\n    copyFunctions = listOf(KopyFunctions.Copy)\n}\n```\n\n#### Transform functions\n\nThe `functions` option accepts the list of functions that will be generated. The default value is\n`listOf(KopyTransformFunctions.Set, KopyTransformFunctions.Update, KopyTransformFunctions.UpdateEach)`.\n\nAn empty list will generate all transform functions, (`set`, `update`, and `updateEach`).\n\n##### Example\n\n```kotlin\nplugins {\n    id(\"com.javiersc.kotlin.kopy\") version \"$version\"\n}\n\nkopy {\n    transformFunctions = listOf(KopyTransformFunctions.Set, KopyTransformFunctions.Update)\n}\n```\n\n#### Visibility\n\nThe `visibility` option allows changing the visibility of the `copy` and `invoke` functions. The\ndefault value is `Auto`, which uses the same visibility the primary constructor has, as the original\n`copy` function does after Kotlin 2.0.20.\n\nPossible values:\n\n- `KopyVisibility.Auto` (Default): The visibility of the primary constructor is used.\n- `KopyVisibility.Public`: The visibility of the generated functions will be `public`.\n- `KopyVisibility.Internal`: The visibility of the generated functions will be `internal`.\n- `KopyVisibility.Protected`: The visibility of the generated functions will be `protected`.\n- `KopyVisibility.Private`: The visibility of the generated functions will be `private`.\n\nIt is possible to have a more restrictive Kopy `copy` and `invoke` functions than the original one,\nfor example by providing the `KopyVisiblity.Private` and the primary constructor being `public` or\n`internal`. The original `copy` function would be `public` or `internal` respectively, and the Kopy\nfunctions would be `private`.\n\n\u003e **Note**\n\u003e\n\u003e If the primary constructor visibility is more restrictive than the specified visibility, the\n\u003e primary constructor one is used.\n\n##### Example\n\n```kotlin\nplugins {\n    id(\"com.javiersc.kotlin.kopy\") version \"$version\"\n}\n\nkopy {\n    visibility = KopyVisibility.Private\n}\n```\n\n### Kopy Example\n\n```kotlin\nfun main() {\n    val house = House(\n        squareMeters = 100,\n        kitchen = Kitchen(\n            cat = Cat(\n                name = \"Garfield\",\n                age = 5,\n                numbers = listOf(1, 2, 3),\n            ),\n            squareMeters = 10,\n        ),\n    )\n    val house2: House = house.copy {\n        squareMeters = 200\n        kitchen.cat.name = \"Felix\"\n        kitchen.cat.age = 7\n        kitchen.cat.numbers.updateEach { it + 1 }\n        kitchen.squareMeters = 20\n    }\n\n    // House(squareMeters=200, kitchen=Kitchen(cat=Cat(name=Felix, age=7, numbers=[2, 3, 4]), squareMeters=20))\n    println(house2)\n}\n\n@Kopy\ndata class House(val squareMeters: Int, val kitchen: Kitchen)\n\n@Kopy\ndata class Kitchen(val cat: Cat, val squareMeters: Int)\n\n@Kopy\ndata class Cat(val name: String, val age: Int, val numbers: List\u003cInt\u003e)\n```\n\n## Features\n\n### `copy` or `invoke`\n\n`copy` and `invoke` create a new instance of the data class with the content specified. There is no\ndifference between both functions.\n\n### `set` or `=`\n\n`set` and `=` do the same, assigning a value.\n\n```kotlin\nval house2: House = house.copy {\n    kitchen.cat.name = \"Felix\"\n}\n\nval house3: House = house.copy {\n    kitchen.cat.name.set(\"Felix\")\n}\n```\n\n### `update`\n\n`update` is a lambda which allows updating the value of the property while having access to the\ncurrent value.\n\n```kotlin\nval house2: House = house.copy {\n    kitchen.cat.name.update { name -\u003e \"$name Jr.\" }\n}\n```\n\n### `updateEach`\n\n`updateEach` is a lambda that allows updating the values of an `Iterable` while having access to the\ncurrent value of each element.\n\n```kotlin\nval house2: House = house.copy {\n    kitchen.cat.numbers.updateEach { it + 1 }\n}\n```\n\n### Kotlin Multiplatform\n\nKopy is compatible with Kotlin Multiplatform projects if it is used exclusively on projects that\napplies the plugin.\n\nCalling `copy` or `invoke` functions from Swift, or anything that is not Kotlin with the Kopy plugin\napplied, will not work.\n\n## KopyKat comparison\n\nKopy uses the K2 compiler plugin whereas [KopyKat](https://github.com/kopykat-kt/kopykat) uses KSP\nto generate code, as a consequence there are different advantages and disadvantages.\n\n### Advantages\n\n- Kopy's code generation is faster than [KopyKat](https://github.com/kopykat-kt/kopykat)'s\n- Kopy does not need to run any Gradle task to get feedback on the IDE:\n    - Autocompletion shows the `copy` and `invoke` functions instantly after annotating a data class\n      with `@Kopy` annotation.\n    - Removing the `@Kopy` annotation instantly removes the `copy` and `invoke` functions without\n      running a Gradle task or manually deleting the `build` directory of a project.\n    - As it is not necessary to assemble/build the project, the feedback loop is faster.\n- Build time should be better too (not tested).\n- Kopy only adds 5 or 6 functions/properties to each data class,\n  whereas [KopyKat](https://github.com/kopykat-kt/kopykat) needs to generate builders and the\n  functions/properties match the number of properties in the data class.\n  In the future, the number of properties Kopy adds to each data class will be reduced to 1 or 2.\n\n### Disadvantages\n\nSimilar to [Kotlin Power Assert](https://github.com/bnorm/kotlin-power-assert), this plugin works on\nthe call site, so it modifies the body of the `copy` or `invoke` lambdas. That means the plugin must\nbe applied to get it working, so it is not a good idea to use it in a library or an SDK as it will\nforce the users to apply the plugin. [KopyKat](https://github.com/kopykat-kt/kopykat) generates all\nthe builders, so it does not have this limitation.\n\nAn error is shown in the IDE or at compilation time if the plugin is not applied and the `copy` or\n`invoke` function from a class in another module is called, but it will not appear if the consumer\nis a Java application/library.\n\n## How it works\n\nThe plugin transforms the lambda into what a developer would do manually with `copy` functions,\nthat means the `copy` or `invoke` lambda can only work if the plugin is applied to the project it is\nbeing called.\n\nIf the plugin is not applied, the `copy` and `invoke` function calls will be marked as\nerrors. Don't suppress them without applying the plugin as they will not work.\n\nIt is not necessary to suppress them manually, the Gradle plugin will suppress them automatically.\n\nThere is no reflection or mutability, the class will have some new functions and properties added.\n\nThe number is limited to 6 independently of the number of properties the data class has:\n\n- `copy` function\n- `invoke` function\n- `_atomic` property\n- `set` function\n- `update` function\n- `updateEach` function\n\nWhen the Context Parameters feature is available, the number of properties and methods added to the\ndata class will be reduced to only 1 or 2:\n\n- `copy` function [optional]\n- `invoke` function [optional]\n\nA new `CopyScope` will be created, and it will be used to store the rest of properties, and it\nwill be added as a context parameter to the `copy` and `invoke` lambdas:\n\n```kotlin\ndata class House(val squareMeters: Int, val kitchen: Kitchen) {\n\n    fun copy(block: CopyScope.() -\u003e Unit): T {\n        // ...\n    }\n\n    operator fun invoke(block: CopyScope.() -\u003e Unit): T {\n        // ...\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaviersegoviacordoba%2Fkopy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaviersegoviacordoba%2Fkopy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaviersegoviacordoba%2Fkopy/lists"}