From a9f6b02adc3f2c2bd17e9d04d5cf1c621e42a9dd Mon Sep 17 00:00:00 2001 From: danijoo Date: Thu, 24 Sep 2015 00:16:00 +0200 Subject: [PATCH] initial commit --- .gitignore | 7 + build.gradle | 37 ++++ libs/btc-ascii-table-1.0.jar | Bin 0 -> 16444 bytes settings.gradle | 2 + .../kotlin/net/headlezz/resdiff/CLOptions.kt | 30 ++++ .../kotlin/net/headlezz/resdiff/ResDiff.kt | 168 ++++++++++++++++++ .../kotlin/net/headlezz/resdiff/Resource.kt | 61 +++++++ .../net/headlezz/resdiff/ResourceTest.kt | 110 ++++++++++++ src/test/res/testres.xml | 24 +++ src/test/res/testres2.xml | 23 +++ 10 files changed, 462 insertions(+) create mode 100644 .gitignore create mode 100644 build.gradle create mode 100644 libs/btc-ascii-table-1.0.jar create mode 100644 settings.gradle create mode 100644 src/main/kotlin/net/headlezz/resdiff/CLOptions.kt create mode 100644 src/main/kotlin/net/headlezz/resdiff/ResDiff.kt create mode 100644 src/main/kotlin/net/headlezz/resdiff/Resource.kt create mode 100644 src/test/kotlin/net/headlezz/resdiff/ResourceTest.kt create mode 100644 src/test/res/testres.xml create mode 100644 src/test/res/testres2.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18dd732 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build +.idea +gradlew +gradlew.bat +*.iml +.gradle +gradle \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6f3bd4a --- /dev/null +++ b/build.gradle @@ -0,0 +1,37 @@ +group 'net.headlezz.resdiff' +version '1.0' + +apply plugin: 'kotlin' +apply plugin: 'application' + +sourceCompatibility = 1.6 +mainClassName = "net.headlezz.resdiff.ResdiffPackage" + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.13.1514' + } +} + +repositories { + mavenCentral() +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + compile 'org.jetbrains.kotlin:kotlin-stdlib:0.13.1514' + compile 'org.jetbrains.kotlin:kotlin-reflect:0.13.1514' + compile 'commons-cli:commons-cli:1.3.1' + testCompile group: 'junit', name: 'junit', version: '4.11' +} + +sourceSets { + test.kotlin.srcDirs += 'src/test/kotlin' +} + +task wrapper(type: Wrapper) { + gradleVersion = '2.7' +} diff --git a/libs/btc-ascii-table-1.0.jar b/libs/btc-ascii-table-1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..59c0d72767715d6028f2444ececd0059bbc94b6a GIT binary patch literal 16444 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 { + + private val options = Options() + private val clOptions: CommandLine + + init { + options.addOption("h", "help", false, "Show this screen") + options.addOption("t", "type", true, "only compare this resource type (string, bool, integer, color, dimen)") + options.addOption("y", "side-by-side", false, "output in columns") + + val optionParser = DefaultParser() + clOptions = optionParser.parse(options, args) + } + + public fun printHelp() { + HelpFormatter().printHelp("resdiff [OPTIONS]... FILES", options) + } + + public fun hasFlag(flag: String) : Boolean = clOptions.hasOption(flag) + + public fun getOptionValue(flag: String) : String = clOptions.getOptionValue(flag) + +} \ No newline at end of file diff --git a/src/main/kotlin/net/headlezz/resdiff/ResDiff.kt b/src/main/kotlin/net/headlezz/resdiff/ResDiff.kt new file mode 100644 index 0000000..e666cec --- /dev/null +++ b/src/main/kotlin/net/headlezz/resdiff/ResDiff.kt @@ -0,0 +1,168 @@ +package net.headlezz.resdiff + +import com.bethecoder.ascii_table.ASCIITable +import org.xml.sax.SAXParseException +import java.io.File +import java.util.* +import kotlin.dom.elements +import kotlin.dom.get +import kotlin.dom.parseXml +import kotlin.reflect.KClass + +public fun main(args: Array) { + // parse arguments + val clo = CLOptions(args) + if (clo.hasFlag("h")) { + clo.printHelp() + System.exit(0) + } + val printTable = clo.hasFlag("y") + val compareBooleanRes = !clo.hasFlag("t") || clo.getOptionValue("t").contains("bool") + val compareStringRes = !clo.hasFlag("t") || clo.getOptionValue("t").contains("string") + val compareColorRes = !clo.hasFlag("t") || clo.getOptionValue("t").contains("color") + val compareIntegerRes = !clo.hasFlag("t") || clo.getOptionValue("t").contains("integer") + val compareDimensionRes = !clo.hasFlag("t") || clo.getOptionValue("t").contains("dimen") + + + // assert we have two valid file paths + if (args.size() < 2 || + !File(args.get(args.size() - 2)).exists() || + !File(args.get(args.size() - 1)).exists()) { + println("Missing file arguments.") + clo.printHelp() + System.exit(1) + } + + // get all resources + val resourceList1 = ArrayList() + getFiles(args.get(args.size() - 2)).map { file -> getResourcesFromFile(file) }.forEach { resources -> resourceList1.addAll(resources) } + + val resourceList2 = ArrayList() + getFiles(args.get(args.size() - 1)).map { file -> getResourcesFromFile(file) }.forEach { resources -> resourceList2.addAll(resources) } + + // and compare them! + val differences = ArrayList>() + if (compareStringRes) + differences.addAll(getDifferencesForClass(StringResource::class, resourceList1, resourceList2)) + if (compareBooleanRes) + differences.addAll(getDifferencesForClass(BooleanResource::class, resourceList1, resourceList2)) + if (compareIntegerRes) + differences.addAll(getDifferencesForClass(IntegerResource::class, resourceList1, resourceList2)) + if (compareDimensionRes) + differences.addAll(getDifferencesForClass(DimensionResource::class, resourceList1, resourceList2)) + if (compareColorRes) + differences.addAll(getDifferencesForClass(ColorResource::class, resourceList1, resourceList2)) + + val addCount = differences.count { pair -> pair.first == null && pair.second != null } + val delCount = differences.count { pair -> pair.first != null && pair.second == null } + println("Found ${differences.size()} differences ($addCount added, $delCount removed).") + + if (printTable) + printDiffInTable(differences) + else + printDiffNormal(differences) +} + +fun printDiffNormal(differences: List>) { + differences.forEach { pair -> + val resA = pair.first + val resB = pair.second + val type = (resA?.type ?: resB!!.type).toString().toLowerCase() + val name = resA?.name ?: resB!!.name + println("<$type name=\"$name\">") + if (resA != null) + println("<\t${resA.value}") + if (resA != null && resB != null) + println("---") + if (resB != null) + println(">\t${resB.value}") + } +} + +fun printDiffInTable(differences: List>) { + val header = arrayOf("Type", "Name", "Left", "Right") + val data = differences.map { pair -> + val type = (pair.first?.type ?: pair.second!!.type).toString() + val name = pair.first?.name ?: pair.second!!.name + arrayOf(type, name, pair.first?.value ?: "", pair.second?.value ?: "") + }.toTypedArray() + ASCIITable.getInstance().printTable(header, data) +} + +fun getDifferencesForClass(clazz: KClass, resourceList1: ArrayList, resourceList2: ArrayList): List> { + val className = clazz.simpleName + return getDifferences( + resourceList1.filter { r -> r.javaClass.simpleName == className }, + resourceList2.filter { r -> r.javaClass.simpleName == className }) +} + +fun getDifferences(from: List, to: List): ArrayList> { + val diffs = ArrayList>() + + val pairs = genResourcePairs(from, to) + + pairs.forEach { pair -> + if (!Resource.match(pair.first, pair.second)) + diffs.add(pair) + } + + return diffs +} + +/** + * creates Pairs of resource with matching identity + */ +fun genResourcePairs(from: List, to: List): List> { + val pairs = ArrayList>() + + val tempFrom = ArrayList(from.size()) + tempFrom.addAll(from) + val tempTo = ArrayList(to.size()) + tempTo.addAll(to) + + loop@for (f in tempFrom) { + for (t in tempTo) { + if (f.matchIdent(t)) { + pairs.add(Pair(f, t)) + continue@loop + } + } + pairs.add(Pair(f, null)) + } + pairs.forEach { pair -> + tempFrom.remove(pair.first) + tempTo.remove(pair.second) + } + tempTo.forEach { t -> pairs.add(Pair(null, t)) } + + return pairs +} + +/** + * If location points to a directory, returns all files in this directory (not recursive), else, + * it just returns the file + */ +fun getFiles(location: String): Array { + val file = File(location) + if (file.isDirectory) { + return file.listFiles { file, filter -> true }.filterNot { file -> file.isDirectory }.toTypedArray() + } else + return arrayOf(file) +} + +/** + * returns a list of all resources found in the given file + */ +fun getResourcesFromFile(file: File): ArrayList { + try { + val doc = parseXml(file.absoluteFile) + val resources = doc.get("resources").first() + return resources.elements.map { elem -> Resource.fromElement(elem) }.filterNotNull().toArrayList() + } catch(e: SAXParseException) { + println("${file.name} is not a valid xml file") + return arrayListOf() + } catch(e: NoSuchElementException) { + println("${file.name} is not a valid xml file") + return arrayListOf() + } +} diff --git a/src/main/kotlin/net/headlezz/resdiff/Resource.kt b/src/main/kotlin/net/headlezz/resdiff/Resource.kt new file mode 100644 index 0000000..4b0c80b --- /dev/null +++ b/src/main/kotlin/net/headlezz/resdiff/Resource.kt @@ -0,0 +1,61 @@ +package net.headlezz.resdiff + +import org.w3c.dom.Element + +abstract class Resource( + public val name: String, + public val value: String, + public val type: Resource.Type) { + + enum class Type { + String, Dimension, Boolean, Integer, Color + } + + companion object { + public fun fromElement(elem: Element) : Resource? { + val type= elem.tagName + val name = elem.getAttribute("name") + val value = elem.firstChild.nodeValue + + return when(type) { + "string" -> StringResource(name, value) + "dimen" -> DimensionResource(name, value) + "bool" -> BooleanResource(name, value) + "integer" -> IntegerResource(name, value) + "color" -> ColorResource(name, value) + else -> null + } + } + + public fun match(resource1: Resource?, resource2: Resource?) : Boolean { + if(resource1 == null) + return false + + if(resource2 == null) + return false + + return resource1.match(resource2) + } + + } + + /** + * Check if the given resource matches the this one + */ + public fun match(resource2: Resource) : Boolean { + return matchIdent(resource2) && value == resource2.value + } + + /** + * Check if identity of two resources match (resource name matching) + */ + public fun matchIdent(resource2: Resource) : Boolean { + return this.type == resource2.type && this.name == resource2.name + } +} + +class StringResource(name: String, value: String) : Resource(name, value, Resource.Type.String) +class BooleanResource(name: String, value: String) : Resource(name, value, Resource.Type.Boolean) +class ColorResource(name: String, value: String) : Resource(name, value, Resource.Type.Color) +class IntegerResource(name: String, value: String) : Resource(name, value, Resource.Type.Integer) +class DimensionResource(name: String, value: String) : Resource(name, value, Resource.Type.Dimension) diff --git a/src/test/kotlin/net/headlezz/resdiff/ResourceTest.kt b/src/test/kotlin/net/headlezz/resdiff/ResourceTest.kt new file mode 100644 index 0000000..50d0f55 --- /dev/null +++ b/src/test/kotlin/net/headlezz/resdiff/ResourceTest.kt @@ -0,0 +1,110 @@ +package net.headlezz.resdiff + +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.nio.charset.StandardCharsets +import kotlin.dom.elements +import kotlin.dom.parseXml +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ResourceTest { + + @Test + fun match() { + val resA = StringResource("nameA", "valueA") + val resB = StringResource("nameA", "valueA") + assertTrue(resA.match(resB)) + } + + @Test + fun match_notMatching() { + val resA = StringResource("nameA", "valueA") + val resB = StringResource("nameA", "valueB") + assertFalse(resA.match(resB)) + } + + @Test + fun ident() { + val resA = StringResource("nameA", "valueA") + val resB = StringResource("nameA", "valueA") + assertTrue(resA.matchIdent(resB)) + } + + @Test + fun ident_notMatching() { + val resA = StringResource("nameA", "valueA") + val resB = StringResource("nameB", "valueA") + assertFalse(resA.matchIdent(resB)) + } + + @Test + fun ident_differentResourceTypes() { + val resA = object: Resource("nameA", "valueA", Resource.Type.Boolean){} + val resB = object: Resource("nameA", "valueA", Resource.Type.String){} + assertFalse(resA.matchIdent(resB)) + } + + @Test + fun fromElement_String() { + val res = "a value" + val elem = parseXml(buildInputStream(res)).elements.first() + + val stringResource = Resource.fromElement(elem) + assert(stringResource is StringResource) + assertEquals("a name", stringResource!!.name) + assertEquals("a value", stringResource.value) + } + + @Test + fun fromElement_Boolean() { + val res = "true" + val elem = parseXml(buildInputStream(res)).elements.first() + + val booleanResource = Resource.fromElement(elem) + assert(booleanResource is BooleanResource) + assertEquals("a name", booleanResource!!.name) + assertEquals("true", booleanResource.value) + } + + @Test + fun fromElement_Integer() { + val res = "-123" + val elem = parseXml(buildInputStream(res)).elements.first() + + val intResource = Resource.fromElement(elem) + assert(intResource is IntegerResource) + assertEquals("a name", intResource!!.name) + assertEquals("-123", intResource.value) + } + + @Test + fun fromElement_Color() { + val res = "#000001" + val elem = parseXml(buildInputStream(res)).elements.first() + + val colorResource = Resource.fromElement(elem) + assert(colorResource is ColorResource) + assertEquals("a name", colorResource!!.name) + assertEquals("#000001", colorResource.value) + } + + @Test + fun fromElement_Dimension() { + val res = "12dp" + val elem = parseXml(buildInputStream(res)).elements.first() + + val dimensionResource = Resource.fromElement(elem) + assert(dimensionResource is DimensionResource) + assertEquals("a name", dimensionResource!!.name) + assertEquals("12dp", dimensionResource.value) + } + + + fun buildInputStream(value: String) : InputStream { + return ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8)) + } + +} \ No newline at end of file diff --git a/src/test/res/testres.xml b/src/test/res/testres.xml new file mode 100644 index 0000000..3e09ae1 --- /dev/null +++ b/src/test/res/testres.xml @@ -0,0 +1,24 @@ + + + This resource is matching + This resource is not matching + + true + false + + 123 + 122 + + #000000 + #001230 + + 12dp + 1sp + + This misses in the other file + + @string/is_just_a_string + @string/is_just_a_string + + true + \ No newline at end of file diff --git a/src/test/res/testres2.xml b/src/test/res/testres2.xml new file mode 100644 index 0000000..b76f16e --- /dev/null +++ b/src/test/res/testres2.xml @@ -0,0 +1,23 @@ + + + This resource is matching + This resource is not matching!!!! + + true + true + + 123 + 533 + + #000000 + #666666 + + 12dp + 99px + + This misses in the other file + + @string/is_just_a_string + @string/is_just_another_string + + \ No newline at end of file