Skip to content

Commit

Permalink
Merge pull request #20 from careem/feature/json-output
Browse files Browse the repository at this point in the history
Enable tldr to format the output as a json
  • Loading branch information
ahmedre authored Jul 4, 2023
2 parents ed0d6d7 + 29cac20 commit 953a153
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 54 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8

[*.{kt,kts,gradle}]
indent_style = space
indent_size = 2
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand =



1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build
/.gradle
/.idea
!.idea/fileTemplates/
15 changes: 15 additions & 0 deletions .idea/fileTemplates/includes/File Header.java

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ upgrades (ex upgrading Firebase here also updates it in ...)
passing a collapse of `com.careem.care` would result in collapsing updates
of `com.careem.care:dep1` and `com.careem.care:dep2` to just
`com.careem.care:*`. This flag is repeatable.
* `-o, --output` - output type, you can pass `plain` or `json`, `plain` is the default argument

## Other Utilities

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ fun upgradeEffects(old: String, new: String, collapseKeys: List<String>): String
if (index > 0) {
appendLine()
}
append("Changing ${effects.first.artifact} to ${effects.first.version} will also change:")
append("Changing ${effects.first.artifact} to ${effects.first.version}${effects.first.alternativeVersion?.let { ", (changed from $it)"}} will also change:")
effects.second
.forEach { affectedDependency -> writeTree(affectedDependency, 1) }
appendLine()
Expand Down
8 changes: 7 additions & 1 deletion src/main/kotlin/com/careem/gradle/dependencies/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.careem.gradle.dependencies

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.multiple
import com.github.ajalt.clikt.parameters.options.option
Expand All @@ -38,14 +39,19 @@ class Tldr : CliktCommand(name = "dependency-diff-tldr") {
help = "Collapse packages with a matching group under a group.*. Collapsing " +
"will only occur if all version numbers match. (ex --collapse com.careem.ridehail --collapse com.careem.now)."
).multiple()
private val output by option(
"-o",
"--output",
help = "Output type, \"plain\" and \"json\" are supported"
).default("plain")

private val old by argument("old.txt").path(mustExist = true)
private val new by argument("new.txt").path(mustExist = true)

override fun run() {
val oldContents = old.readText()
val newContents = new.readText()
print(tldr(oldContents, newContents, collapse))
print(tldr(oldContents, newContents).toString(collapse, outputType = output.toOutputType()))
if (sideEffects) {
val upgradeEffects = upgradeEffects(oldContents, newContents, collapse)
if (upgradeEffects.isNotEmpty()) {
Expand Down
16 changes: 16 additions & 0 deletions src/main/kotlin/com/careem/gradle/dependencies/mismatched/main.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* Copyright Careem, an Uber Technologies Inc. company
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:JvmName("MismatchedVersionFinder")
package com.careem.gradle.dependencies.mismatched

Expand Down
114 changes: 114 additions & 0 deletions src/main/kotlin/com/careem/gradle/dependencies/printer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright Careem, an Uber Technologies Inc. company
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.careem.gradle.dependencies
enum class OutputType{
PLAIN, JSON
}

fun String.toOutputType(): OutputType = when(this){
"json" -> OutputType.JSON
else -> OutputType.PLAIN
}


fun VersionDifferences.toString(collapse: List<String>, outputType: OutputType): String {
return when(outputType){
OutputType.PLAIN -> writePlain(this, collapse)
OutputType.JSON -> writeJson(this, collapse)
}
}

private fun writePlain(versionDifferences: VersionDifferences, collapse: List<String>): String = buildString {
val (added, removed, upgraded) = versionDifferences
writeList("New Dependencies", added)
writeList("Removed Dependencies", removed, added.isNotEmpty())
writeList("Upgraded Dependencies", collapseDependencies(upgraded, collapse),
removed.isNotEmpty() || added.isNotEmpty())
}

private fun writeJson(versionDifferences: VersionDifferences, collapse: List<String>): String = buildString {
val (added, removed, upgraded) = versionDifferences
append("{")
writeJsonObject("new_dependencies", added, trailingComma = true)
writeJsonObject("removed_dependencies", removed, trailingComma = true)
writeJsonObject("upgraded_dependencies", collapseDependencies(upgraded, collapse))
append("}")
}

private fun StringBuilder.writeList(title: String, list: List<VersionedDependency>, newLineBeforeTitle: Boolean = false) {
if (list.isNotEmpty()) {
if (newLineBeforeTitle) {
append("\n")
}
append(title)
append("\n")
list.forEach {
append(it.artifact)
if (it.version.isNotBlank()) {
append(":")
append(it.version)
it.alternativeVersion?.let { altVersion -> append(", (changed from $altVersion)") }
}
append("\n")
}
}
}

private fun StringBuilder.writeJsonObject(key: String, list: List<VersionedDependency>, trailingComma: Boolean = false) {
append("\"$key\":[")
if (list.isNotEmpty()) {
list.forEachIndexed { index, versionedDependency ->
append(writeJsonVersionedDependency(versionedDependency))
if(index + 1 < list.size){
append(",")
}
}
}
append("]")
if(trailingComma){
append(",")
}
}

private fun writeJsonVersionedDependency(versionedDependency: VersionedDependency): String {
return "{\"artifact\":\"${versionedDependency.artifact}\",\"version\":\"${versionedDependency.version}\",\"other_version\":\"${versionedDependency.alternativeVersion}\"}"
}


private fun collapseDependencies(
dependencies: List<VersionedDependency>,
collapses: List<String>
): List<VersionedDependency> {
val add = mutableSetOf<VersionedDependency>()
val remove = mutableSetOf<VersionedDependency>()

collapses.forEach { collapse ->
val matchingToCollapse = dependencies.filter { it.artifact.startsWith(collapse) }
val versions = matchingToCollapse.map { it.version }.toSet().size
if (versions == 1) {
remove.addAll(matchingToCollapse)
add.add(matchingToCollapse[0].copy(artifact = collapse))
}
}

return if (add.isNotEmpty()) {
(dependencies - remove) + add
} else {
dependencies
}
}

56 changes: 5 additions & 51 deletions src/main/kotlin/com/careem/gradle/dependencies/treeTldr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,8 @@

package com.careem.gradle.dependencies

fun tldr(old: String, new: String, collapse: List<String>): String {
val (added, removed, upgraded) = dependencyDifferences(old, new)

return buildString {
writeList("New Dependencies", added)
writeList("Removed Dependencies", removed, added.isNotEmpty())
writeList("Upgraded Dependencies", collapseDependencies(upgraded, collapse),
removed.isNotEmpty() || added.isNotEmpty())
}
fun tldr(old: String, new: String): VersionDifferences {
return dependencyDifferences(old, new)
}

fun dependencyDifferences(old: String, new: String): VersionDifferences {
Expand Down Expand Up @@ -68,30 +61,7 @@ private fun extractDependencies(deps: String): Set<VersionedDependency> {
.toSet()
}

private fun collapseDependencies(
dependencies: List<VersionedDependency>,
collapses: List<String>
): List<VersionedDependency> {
val add = mutableSetOf<VersionedDependency>()
val remove = mutableSetOf<VersionedDependency>()

collapses.forEach { collapse ->
val matchingToCollapse = dependencies.filter { it.artifact.startsWith(collapse) }
val versions = matchingToCollapse.map { it.version }.toSet().size
if (versions == 1) {
remove.addAll(matchingToCollapse)
add.add(matchingToCollapse[0].copy(artifact = collapse))
}
}

return if (add.isNotEmpty()) {
(dependencies - remove) + add
} else {
dependencies
}
}

data class VersionedDependency(val artifact: String, val version: String) {
data class VersionedDependency(val artifact: String, val version: String, val alternativeVersion: String? = null) {
val group by lazy { artifact.substringBefore(':').trim() }
}
data class VersionDifferences(
Expand All @@ -115,7 +85,7 @@ private fun partitionDifferences(
removedVersion != null -> {
mutableRemovedMap.remove(artifact)
upgrades.add(
VersionedDependency(artifact, "$version, (changed from $removedVersion)"))
VersionedDependency(artifact, version, alternativeVersion = removedVersion))
}
else -> {
additions.add(VersionedDependency(artifact, version))
Expand All @@ -133,20 +103,4 @@ private fun partitionDifferences(
)
}

private fun StringBuilder.writeList(title: String, list: List<VersionedDependency>, newLineBeforeTitle: Boolean = false) {
if (list.isNotEmpty()) {
if (newLineBeforeTitle) {
append("\n")
}
append(title)
append("\n")
list.forEach {
append(it.artifact)
if (it.version.isNotBlank()) {
append(":")
append(it.version)
}
append("\n")
}
}
}

1 change: 1 addition & 0 deletions src/test/fixtures/local_project_update/expected_json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"new_dependencies":[],"removed_dependencies":[],"upgraded_dependencies":[{"artifact":"androidx.activity:activity","version":"1.5.1","other_version":"1.2.4"},{"artifact":"androidx.annotation:annotation-experimental","version":"1.3.0","other_version":"1.1.0"},{"artifact":"androidx.appcompat:appcompat","version":"1.5.1","other_version":"1.4.1"},{"artifact":"androidx.appcompat:appcompat-resources","version":"1.5.1","other_version":"1.4.1"},{"artifact":"androidx.core:core","version":"1.9.0","other_version":"1.7.0"},{"artifact":"androidx.core:core-ktx","version":"1.9.0","other_version":"1.7.0"},{"artifact":"androidx.databinding:viewbinding","version":"7.3.0","other_version":"7.1.2"},{"artifact":"androidx.emoji2:emoji2","version":"1.2.0","other_version":"1.0.0"},{"artifact":"androidx.emoji2:emoji2-views-helper","version":"1.2.0","other_version":"1.0.0"},{"artifact":"androidx.lifecycle:lifecycle-common","version":"2.5.1","other_version":"2.4.0"},{"artifact":"androidx.lifecycle:lifecycle-livedata-core","version":"2.5.1","other_version":"2.3.1"},{"artifact":"androidx.lifecycle:lifecycle-process","version":"2.4.1","other_version":"2.4.0"},{"artifact":"androidx.lifecycle:lifecycle-runtime","version":"2.5.1","other_version":"2.4.0"},{"artifact":"androidx.lifecycle:lifecycle-viewmodel","version":"2.5.1","other_version":"2.3.1"},{"artifact":"androidx.lifecycle:lifecycle-viewmodel-savedstate","version":"2.5.1","other_version":"2.3.1"},{"artifact":"androidx.resourceinspection:resourceinspection-annotation","version":"1.0.1","other_version":"1.0.0"},{"artifact":"androidx.savedstate:savedstate","version":"1.2.0","other_version":"1.1.0"},{"artifact":"androidx.startup:startup-runtime","version":"1.1.1","other_version":"1.0.0"},{"artifact":"com.google.android.material:material","version":"1.6.1","other_version":"1.5.0"},{"artifact":"com.squareup.okhttp3:okhttp","version":"4.10.0","other_version":"4.9.3"},{"artifact":"com.squareup.okhttp3:okhttp-dnsoverhttps","version":"4.10.0","other_version":"4.9.3"},{"artifact":"com.squareup.okio:okio","version":"3.2.0","other_version":"3.0.0"},{"artifact":"com.squareup.okio:okio-jvm","version":"3.2.0","other_version":"3.0.0"},{"artifact":"org.jetbrains.kotlin:kotlin-stdlib","version":"1.7.10","other_version":"1.6.10"},{"artifact":"org.jetbrains.kotlin:kotlin-stdlib-common","version":"1.7.10","other_version":"1.6.10"},{"artifact":"org.jetbrains.kotlin:kotlin-stdlib-jdk7","version":"1.7.10","other_version":"1.6.10"},{"artifact":"org.jetbrains.kotlin:kotlin-stdlib-jdk8","version":"1.7.10","other_version":"1.6.10"},{"artifact":"org.jetbrains.kotlinx:kotlinx-coroutines-android","version":"1.6.4","other_version":"1.6.0"},{"artifact":"org.jetbrains.kotlinx:kotlinx-coroutines-bom","version":"1.6.4","other_version":"1.6.0"},{"artifact":"org.jetbrains.kotlinx:kotlinx-coroutines-core","version":"1.6.4","other_version":"1.6.0"},{"artifact":"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm","version":"1.6.4","other_version":"1.6.0"}]}
1 change: 1 addition & 0 deletions src/test/fixtures/noop_update/expected_json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"new_dependencies":[],"removed_dependencies":[],"upgraded_dependencies":[]}
1 change: 1 addition & 0 deletions src/test/fixtures/project_update/expected_json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"new_dependencies":[{"artifact":"androidx.concurrent:concurrent-futures","version":"1.0.0","other_version":"null"},{"artifact":"androidx.constraintlayout:constraintlayout","version":"2.0.1","other_version":"null"},{"artifact":"androidx.constraintlayout:constraintlayout-solver","version":"2.0.1","other_version":"null"},{"artifact":"androidx.documentfile:documentfile","version":"1.0.0","other_version":"null"},{"artifact":"androidx.dynamicanimation:dynamicanimation","version":"1.0.0","other_version":"null"},{"artifact":"androidx.emoji2:emoji2","version":"1.0.0","other_version":"null"},{"artifact":"androidx.emoji2:emoji2-views-helper","version":"1.0.0","other_version":"null"},{"artifact":"androidx.legacy:legacy-support-core-utils","version":"1.0.0","other_version":"null"},{"artifact":"androidx.lifecycle:lifecycle-process","version":"2.4.0","other_version":"null"},{"artifact":"androidx.lifecycle:lifecycle-viewmodel-savedstate","version":"2.3.1","other_version":"null"},{"artifact":"androidx.localbroadcastmanager:localbroadcastmanager","version":"1.0.0","other_version":"null"},{"artifact":"androidx.print:print","version":"1.0.0","other_version":"null"},{"artifact":"androidx.resourceinspection:resourceinspection-annotation","version":"1.0.0","other_version":"null"},{"artifact":"androidx.startup:startup-runtime","version":"1.0.0","other_version":"null"},{"artifact":"androidx.tracing:tracing","version":"1.0.0","other_version":"null"},{"artifact":"com.google.guava:listenablefuture","version":"1.0","other_version":"null"},{"artifact":"com.squareup.okio:okio-jvm","version":"3.0.0","other_version":"null"},{"artifact":"org.jetbrains.kotlinx:kotlinx-coroutines-bom","version":"1.6.0","other_version":"null"}],"removed_dependencies":[],"upgraded_dependencies":[{"artifact":"androidx.activity:activity","version":"1.2.4","other_version":"1.0.0"},{"artifact":"androidx.annotation:annotation","version":"1.3.0","other_version":"1.1.0"},{"artifact":"androidx.annotation:annotation-experimental","version":"1.1.0","other_version":"1.0.0"},{"artifact":"androidx.appcompat:appcompat","version":"1.4.1","other_version":"1.2.0"},{"artifact":"androidx.appcompat:appcompat-resources","version":"1.4.1","other_version":"1.2.0"},{"artifact":"androidx.arch.core:core-runtime","version":"2.1.0","other_version":"2.0.0"},{"artifact":"androidx.core:core","version":"1.7.0","other_version":"1.3.2"},{"artifact":"androidx.core:core-ktx","version":"1.7.0","other_version":"1.3.2"},{"artifact":"androidx.customview:customview","version":"1.1.0","other_version":"1.0.0"},{"artifact":"androidx.databinding:viewbinding","version":"7.1.2","other_version":"7.0.0-alpha03"},{"artifact":"androidx.drawerlayout:drawerlayout","version":"1.1.1","other_version":"1.0.0"},{"artifact":"androidx.fragment:fragment","version":"1.3.6","other_version":"1.1.0"},{"artifact":"androidx.lifecycle:lifecycle-common","version":"2.4.0","other_version":"2.1.0"},{"artifact":"androidx.lifecycle:lifecycle-livedata-core","version":"2.3.1","other_version":"2.0.0"},{"artifact":"androidx.lifecycle:lifecycle-runtime","version":"2.4.0","other_version":"2.1.0"},{"artifact":"androidx.lifecycle:lifecycle-viewmodel","version":"2.3.1","other_version":"2.1.0"},{"artifact":"androidx.savedstate:savedstate","version":"1.1.0","other_version":"1.0.0"},{"artifact":"androidx.versionedparcelable:versionedparcelable","version":"1.1.1","other_version":"1.1.0"},{"artifact":"com.google.android.material:material","version":"1.5.0","other_version":"1.2.1"},{"artifact":"com.jakewharton.timber:timber","version":"5.0.1","other_version":"4.7.1"},{"artifact":"com.squareup.okhttp3:okhttp","version":"4.9.3","other_version":"4.9.0"},{"artifact":"com.squareup.okhttp3:okhttp-dnsoverhttps","version":"4.9.3","other_version":"4.9.0"},{"artifact":"com.squareup.okio:okio","version":"3.0.0","other_version":"2.9.0"},{"artifact":"org.jetbrains.kotlin:kotlin-stdlib","version":"1.6.10","other_version":"1.4.21"},{"artifact":"org.jetbrains.kotlin:kotlin-stdlib-common","version":"1.6.10","other_version":"1.4.21"},{"artifact":"org.jetbrains.kotlin:kotlin-stdlib-jdk7","version":"1.6.10","other_version":"1.4.10"},{"artifact":"org.jetbrains.kotlin:kotlin-stdlib-jdk8","version":"1.6.10","other_version":"1.4.10"},{"artifact":"org.jetbrains.kotlinx:kotlinx-coroutines-android","version":"1.6.0","other_version":"1.4.2"},{"artifact":"org.jetbrains.kotlinx:kotlinx-coroutines-core","version":"1.6.0","other_version":"1.4.2"},{"artifact":"org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm","version":"1.6.0","other_version":"1.4.2"},{"artifact":"org.jetbrains:annotations","version":"20.1.0","other_version":"16.0.1"}]}
27 changes: 26 additions & 1 deletion src/test/kotlin/com/careem/gradle/dependencies/FixturesTest.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* Copyright Careem, an Uber Technologies Inc. company
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.careem.gradle.dependencies

import com.google.common.truth.Truth.assertThat
Expand All @@ -15,7 +31,16 @@ class FixturesTest(private val fixtureDir: File) {
val before = fixtureDir.resolve("before.txt").readText()
val after = fixtureDir.resolve("after.txt").readText()
val expected = fixtureDir.resolve("expected.txt").readText()
val actual = tldr(before, after, emptyList())
val actual = tldr(before, after).toString(collapse = emptyList(), outputType = OutputType.PLAIN)
assertThat(actual).isEqualTo(expected)
}

@Test
fun testTldrJson() {
val before = fixtureDir.resolve("before.txt").readText()
val after = fixtureDir.resolve("after.txt").readText()
val expected = fixtureDir.resolve("expected_json.json").readText()
val actual = tldr(before, after).toString(collapse = emptyList(), outputType = OutputType.JSON)
assertThat(actual).isEqualTo(expected)
}

Expand Down

0 comments on commit 953a153

Please sign in to comment.